diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b1d3c462..a15deb055 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,9 +114,9 @@ jobs: if: github.event_name != 'push' uses: actions-rust-lang/rustfmt@4066006ec54a31931b9b1fddfd38f2fdf2d27143 # v1.1.2 - name: Check clippy warnings - run: cargo clippy --release --workspace --all-targets --features ix-ffi/parallel,ix-ffi/net,ix-ffi/test-ffi,ixon/sharing-profile -- -D warnings + run: cargo clippy --release --workspace --all-targets --features ix-ffi/parallel,ix-ffi/net,ix-ffi/test-ffi -- -D warnings - name: Check *everything* compiles - run: cargo check --release --workspace --all-targets --features ix-ffi/parallel,ix-ffi/net,ix-ffi/test-ffi,ixon/sharing-profile + run: cargo check --release --workspace --all-targets --features ix-ffi/parallel,ix-ffi/net,ix-ffi/test-ffi - name: Tests run: cargo nextest run --release --profile ci --workspace ${{ github.event_name == 'push' && '--no-run' || '' }} - name: Get Rust version diff --git a/.github/workflows/merge-tests.yml b/.github/workflows/merge-tests.yml index be7c166c0..078340a7d 100644 --- a/.github/workflows/merge-tests.yml +++ b/.github/workflows/merge-tests.yml @@ -116,36 +116,82 @@ jobs: issue-number: ${{ steps.target.outputs.pr }} body-path: comment-body.md + # One build of the test revision for the partitions below, in place of one + # each. It uses their sticky disk (ci.yml's lean-test lineage, same paths) + # and builds what they build: the test binary with the executables it + # needs, the ZK voting prover, the standalone certified kernel package, + # the certification statements' freeze and the kernel gate's + # executables. RunsOn snapshots the disk when the job ends, under the + # run's ref, and the partitions start after it and restore that ref's + # latest snapshot. A partition that restores an older one (`main`'s) + # rebuilds what it needs, as before, and its log says so. + build: + name: Build merge-test targets + needs: prepare + if: ${{ github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch' }} + # On-demand, as the partitions are: every partition waits for it. + runs-on: runs-on=${{ github.run_id }}-merge-build-${{ github.run_attempt }}/cpu=32/family=r7i+r8i+r7a+r8a/spot=false/image=ubuntu26-full-x64/volume=100gb/sticky=ci-lean-test-x86-64-v4:50gb/extras=s3-cache + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/pull/{0}/merge', needs.prepare.outputs.pr) || needs.prepare.outputs.revision }} + persist-credentials: false + + - name: Verify test revision + env: + TEST_REVISION: ${{ needs.prepare.outputs.revision }} + run: | + checked_out=$(git rev-parse HEAD) + if [ "$checked_out" != "$TEST_REVISION" ]; then + echo "::error::PR merge ref changed before the run started; comment !merge-tests again" + exit 1 + fi + + - uses: runs-on/action@dfae4d98c5537a0dc7bbb7e6b9211f30ba240f6d # v2.4.0 + with: + sccache: s3 + sticky_cache: | + rust + custom,path=.lake,path=target,path=Models/SetTheory/.lake + - uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 + - uses: $/.github/actions/setup-rust-toolchain + with: + codegen: portable + use-github-cache: "false" + - uses: leanprover/lean-action@50fcf42d2e460296f1a34b402e990d1b24f8b596 # v1.6.0 + with: + auto-config: false + use-github-cache: false + + # The kernel gate's strict list of audit modules (`lake run + # check-kernel`) is left to the kernel partition: its modules are + # leaves over the closures built here. + - name: Build the partitions' targets + run: | + lake build --wfail IxTests Apps.ZKVoting.Prover + lake -d IxC build --wfail + lake build --wfail Ix.Cert.Audit kernel-layering kernel-trust-surface kernel-entry-cases kernel-meta-names + + - name: Record the built revision + env: + TEST_REVISION: ${{ needs.prepare.outputs.revision }} + run: echo "$TEST_REVISION" > .lake/merge-build-revision + merge-tests: name: ${{ matrix.name }} - needs: prepare + needs: [prepare, build] if: ${{ github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch' }} strategy: fail-fast: false matrix: include: - - name: Lake ignored tests (compile pipeline) - kind: lake - test_args: --tier ci:compile-pipeline - name: Lake ignored tests (compiler closures) kind: lake test_args: --tier ci:compiler-closures - - name: Lake ignored tests (compile) - kind: lake - test_args: --tier ci:compile - - name: Lake ignored tests (decompile) - kind: lake - test_args: --tier ci:decompile - name: Lake ignored tests (misc) kind: lake test_args: --tier ci:misc zk_voting: true - - name: Lake ignored tests (landing, compilers) - kind: lake - test_args: --tier ci:landing-compilers - - name: Lake ignored tests (landing, Init+Std) - kind: lake - test_args: --tier ci:landing-initstd - name: Lake ignored tests (landing, Init+Std through ix) kind: lake test_args: --tier ci:landing-initstd-cli @@ -159,7 +205,8 @@ jobs: # The sticky disk is ci.yml's lean-test lineage: same toolchain, same # portable codegen, same cached paths, and that job's push-to-main runs # are what keep the lineage's `main` snapshot fresh. A merge-queue ref - # has no snapshot of its own and falls back to that one. + # has no snapshot of its own until the build job above saves one; a + # partition that does not find it falls back to `main`'s. runs-on: runs-on=${{ github.run_id }}-merge-tests-${{ github.run_attempt }}-${{ strategy.job-index }}/cpu=32/family=r7i+r8i+r7a+r8a/spot=false/image=ubuntu26-full-x64/volume=100gb/sticky=ci-lean-test-x86-64-v4:50gb/extras=s3-cache steps: - name: Validate merge-test variant @@ -205,6 +252,18 @@ jobs: auto-config: false use-github-cache: false + # Whether the disk is the build job's snapshot. Either way the steps + # below build what is stale; this only says whether they had to. + - name: Check for the build job's snapshot + env: + TEST_REVISION: ${{ needs.prepare.outputs.revision }} + run: | + if [ "$(cat .lake/merge-build-revision 2>/dev/null)" = "$TEST_REVISION" ]; then + echo "Restored the build job's snapshot of $TEST_REVISION" + else + echo "::notice::Did not restore the build job's snapshot of $TEST_REVISION; this partition builds its own targets" + fi + - name: Run ignored Lake test partition if: ${{ matrix.kind == 'lake' }} run: lake test --wfail -- ${{ matrix.test_args }} @@ -215,9 +274,9 @@ jobs: # The certified kernel gate (docs/kernel.md): the standalone IxC # package build, which has no dependencies and so proves the kernel needs - # only the toolchain, then the host tests, the Rust differentials, the - # set-theory model, the fences, the entry cases, the reader fidelity, the - # `.meta` and `.source` names, and quotient Source on Init and Std. + # only the toolchain, then the host audits and kernel tests, the + # set-theory model, the layering and trust-surface fences, the entry + # cases, and the `.meta` and `.source` names. # The kernel artifacts come from .lake/kernel on the sticky disk. The # model lives in its own workspace so Mathlib stays out of the root # package; its objects come from Mathlib's cache and persist on the @@ -229,7 +288,7 @@ jobs: Mathlib.SetTheory.Cardinal.Regular \ Mathlib.SetTheory.ZFC.VonNeumann \ Mathlib.SetTheory.ZFC.Cardinal - - name: Check certified kernel, codec and order differentials, model, entry cases, reader fidelity and quotient Source + - name: Check certified kernel, host audits, model, fences, entry cases and meta names if: ${{ matrix.kind == 'kernel' }} run: lake run check-kernel --with-model @@ -240,7 +299,7 @@ jobs: # Valgrind cannot decode AVX-512, so this job builds generic artifacts and # keeps them in the S3-backed actions cache under their own key rather # than sharing a sticky disk with the x86-64-v4 partitions. - runs-on: runs-on=${{ github.run_id }}-valgrind-${{ github.run_attempt }}/cpu=8/family=r7i+r8i+r7a+r8a/image=ubuntu26-full-x64/volume=100gb/extras=s3-cache + runs-on: runs-on=${{ github.run_id }}-valgrind-${{ github.run_attempt }}/cpu=16/family=r7i+r8i+r7a+r8a/image=ubuntu26-full-x64/volume=100gb/extras=s3-cache steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -258,6 +317,9 @@ jobs: fi - uses: runs-on/action@dfae4d98c5537a0dc7bbb7e6b9211f30ba240f6d # v2.4.0 + with: + sccache: s3 + - uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 - uses: $/.github/actions/setup-rust-toolchain with: codegen: generic @@ -273,8 +335,13 @@ jobs: auto-config: false use-github-cache: false + # `ffi-tests` is the primary tier's `ffi` suite as an executable of its + # own, whose import closure leaves out the test binary's proofs. + - name: Build the FFI suite + run: lake build --wfail ffi-tests + - name: Run FFI tests normally - run: lake test --wfail -- ffi + run: .lake/build/bin/ffi-tests - name: Install valgrind run: | @@ -290,11 +357,11 @@ jobs: --track-origins=yes \ --error-exitcode=1 \ --suppressions=.github/valgrind.supp \ - .lake/build/bin/IxTests ffi + .lake/build/bin/ffi-tests merge-gate: if: ${{ always() && github.event_name != 'issue_comment' }} - needs: [prepare, merge-tests, valgrind] + needs: [prepare, build, merge-tests, valgrind] permissions: actions: read checks: read @@ -308,7 +375,7 @@ jobs: always() && github.event_name == 'workflow_dispatch' && needs.prepare.result == 'success' - needs: [prepare, merge-tests, valgrind, merge-gate] + needs: [prepare, build, merge-tests, valgrind, merge-gate] runs-on: ubuntu-latest permissions: issues: write @@ -318,11 +385,12 @@ jobs: env: MERGE_SHA: ${{ needs.prepare.outputs.revision }} PR_NUMBER: ${{ needs.prepare.outputs.pr }} + BUILD_RESULT: ${{ needs.build.result }} TEST_RESULT: ${{ needs.merge-tests.result }} VALGRIND_RESULT: ${{ needs.valgrind.result }} GATE_RESULT: ${{ needs.merge-gate.result }} run: | - if [ "$GATE_RESULT" = success ] && [ "$TEST_RESULT" = success ] && [ "$VALGRIND_RESULT" = success ]; then + if [ "$GATE_RESULT" = success ] && [ "$BUILD_RESULT" = success ] && [ "$TEST_RESULT" = success ] && [ "$VALGRIND_RESULT" = success ]; then heading="## ✅ Merge tests passed" summary="All test partitions and Valgrind passed" elif [ "$GATE_RESULT" = success ]; then diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 6a3ab3ecc..c417e0306 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -32,7 +32,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - # sccache serves only the `nix develop` build below: `nix build` and + # sccache serves only the `nix develop` build at the end: `nix build` and # `nix flake check` compile inside Nix's sandbox, which sees neither # the wrapper nor the network, and rely on Cachix instead. The dev # shell inherits RUSTC_WRAPPER and keeps the outer PATH, so the @@ -60,10 +60,14 @@ jobs: # Ix CLI - run: nix build --print-build-logs --accept-flake-config - run: nix run .#ix -- --help - # A single invocation lets Nix schedule independent checks concurrently. + # The flake's one check, `ix-tests`: the primary tier from the + # Nix-built test binary, in the sandbox, on a fresh copy of the + # tracked sources. CI Jobs' lean-test runs the same tier on its + # incremental sticky disk and its rust-test job runs clippy and + # nextest, so neither runs again here. - run: nix flake check --print-build-logs --accept-flake-config - # Run the real build and test commands inside the development environment. - - run: nix develop --accept-flake-config --command bash -c "lake build && lake test" + # The development shell builds the library; the tests ran above. + - run: nix develop --accept-flake-config --command lake build nix-gate: if: ${{ always() }} diff --git a/Apps/ZKVoting/Prover.lean b/Apps/ZKVoting/Prover.lean index 4f7dfa103..64b35b94f 100644 --- a/Apps/ZKVoting/Prover.lean +++ b/Apps/ZKVoting/Prover.lean @@ -4,6 +4,7 @@ import Ix.ClaimContext import Ix.Address import Ix.Meta import Ix.CompileM +import Ix.CompileCertified import Batteries inductive Candidate @@ -31,8 +32,8 @@ def runElection (votes: List Candidate) : Result := def main : IO UInt32 := do let mut env : Lean.Environment ← get_env! - -- 1. Full compilation via Rust - let phases ← Ix.CompileM.rsCompilePhases env + -- 1. Full compilation via certified pipeline + let phases ← Ix.CompileM.compilePhasesOf env env.constants.toList let mut compileEnv := Ix.Commit.mkCompileEnv phases -- simulate getting the votes from somewhere diff --git a/BENCHMARKS.md b/BENCHMARKS.md index 332b8fc9a..2de02bbea 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -1,20 +1,20 @@ > Historical pipeline results (the refresh below is dated 2026-08-07). > The measurements and methodology describe that pipeline: its `Ix.Tc` -> checker, regenerating Lean decompiler, canonical block order and -> auxiliary regeneration, all since removed. Recorded agreement concerns +> checker, regenerating Lean decompiler, canonical block order, +> auxiliary regeneration, and the uncertified Rust compiler/checker crates +> and CLI flags, all since removed. Recorded agreement concerns > the measured inputs. > -> Current `ix check-lean` uses IxC and rejects nonzero `--clear-every`, -> including the recorded `--clear-every 50`. The compiler now compiles -> Lean verbatim. See the [current benchmark commands](docs/benchmarking.md#local-usage) +> Current `ix check-lean` uses IxC. The compiler now compiles +> Lean verbatim through the certified Lean pipeline (`Ix.Cert.compileReport`). +> See the [current benchmark commands](docs/benchmarking.md#local-usage) > and [checker contract and commands](docs/kernel.md#environment-check). -# Ix pipeline benchmarks: pure-Lean vs Rust +# Ix pipeline benchmarks: pure-Lean vs Rust (historical, 2026-08-07) -Head-to-head timings for every stage of the Ix pipeline across three -environments, comparing the pure-Lean implementation (`Ix.CompileM` / -`Ix.DecompileM` / `Ix.Tc`) against the Rust implementation -(`crates/compile` / `crates/kernel`). +Head-to-head timings for every stage of the historical 2026-08-07 Ix pipeline +across three environments, comparing the pure-Lean implementation (`Ix.CompileM` / +`Ix.DecompileM` / retired `Ix.Tc`) against the retired Rust implementation. ## Methodology @@ -26,10 +26,10 @@ environments, comparing the pure-Lean implementation (`Ix.CompileM` / gaps of interest). - Lean-file elaboration (`getFileEnv`) is excluded everywhere — both sides time their core operation after the environment is in memory. -- The two implementations are **output-equivalent by construction**: - compilation is byte-identical (`ix compile-lean --rust-check` ALIGNED) - and decompilation is hash-identical against the canonicalized source, - so every row compares identical work. +- At the time of measurement, the two implementations were **output-equivalent + by construction**: compilation was byte-identical (verified by the retired + Rust/Lean comparison mode) and decompilation was hash-identical against the + canonicalized source, so every row compares identical work. - The Lean implementation is the *correctness reference*; its performance is reported, not optimized (per the alignment ground rules, Lean-side performance work is out of scope). @@ -113,9 +113,9 @@ compilation totals above. Lean: `ix validate-lean` phase 2 (`Ixon.deEnv` parse **plus** a byte-exactness re-serialization — the parse alone is not separately instrumented; the reported figure is parse + reser). Rust: `Env::get` -is not separately reported either; the figure is derived as -`ix check-rs` total wall time minus its reported check time (load + -parse + ingress setup). +was not separately reported either; the historical figure was derived as +the retired Rust kernel checker's total wall time minus its reported check time +(load + parse + ingress setup). | Environment | Lean deEnv (+reser) | Rust env load (derived) | |---|---|---| @@ -142,12 +142,12 @@ kept for reference. | Lean | 268.7 s | 1,317 s | 12.68 s | | Mathlib | 3,926.3 s | — | 221.0 s | -## Typechecking (`.ixe` through the kernel, meta mode) +## Typechecking (`.ixe` through the historical 2026-08-07 kernel, meta mode) -Rust: `ix check-rs`. Lean: `ix check-lean`. Same constants checked, -full verdict parity. +Historical Rust kernel checker vs historical Lean `ix check-lean` (`Ix.Tc`). +Same constants checked, full verdict parity. -| Environment | Constants | Lean `check-lean` | Rust `check-rs` | Ratio | +| Environment | Constants | Lean `check-lean` (historical `Ix.Tc`) | Rust kernel (retired) | Ratio | |---|---|---|---|---| | InitStd | 105,492 | 64.8 s (~1,630/s) | 24.1 s (~4,380/s) | 2.7× | | Lean | 188,999 | 78.1 s (~2,420/s) | 35.0 s (~5,400/s) | 2.2× | diff --git a/Benchmarks/Kernel/AnthropicFLT/README.md b/Benchmarks/Kernel/AnthropicFLT/README.md index c576219cb..f52a2bd69 100644 --- a/Benchmarks/Kernel/AnthropicFLT/README.md +++ b/Benchmarks/Kernel/AnthropicFLT/README.md @@ -72,7 +72,10 @@ by hex prefixes (8–64 digits). It prints all aliases, not a single guessed name. This separate diagnostic reads the full file into RAM: apply an appropriate memory limit for large artifacts. Name lookup is not checking. -`cases.json` pins the exact existing `.ixe` by SHA-256 and byte length. It +`cases.json` pins the exact existing `.ixe` by Blake3 digest (`artifact.blake3`, +checked with `b3sum`) and byte length; the committed manifest still carries the +v3 artifact's SHA-256 instead, so the harness refuses to run against it until a +Blake3 digest is recorded. It tracks 138 target addresses: 120 fuel failures, four depth failures, five unfinished tails, and nine passing-baseline controls. The default **25-case core** contains all five original tails, all four depth cases, ten fuel failures, diff --git a/Benchmarks/Kernel/AnthropicFLT/RunSuite.lean b/Benchmarks/Kernel/AnthropicFLT/RunSuite.lean index 6831cb0e8..e87c88428 100644 --- a/Benchmarks/Kernel/AnthropicFLT/RunSuite.lean +++ b/Benchmarks/Kernel/AnthropicFLT/RunSuite.lean @@ -28,10 +28,13 @@ structure Defaults where workers : Nat deriving FromJson, ToJson +/-- The pinned artifact: its file name, size and Blake3 digest. A manifest that +pins no Blake3 digest (the committed one still carries the v3 artifact's +SHA-256) validates, but `run` refuses to check against it. -/ structure Artifact where filename : String bytes : Nat - sha256 : String + blake3 : Option String := none deriving FromJson, ToJson structure Manifest where @@ -69,7 +72,7 @@ def need (value : Except String α) : IO α := | .error e => throw (IO.userError e) def validate (m : Manifest) : Except String Unit := do - unless m.schema == 1 && isHex m.artifact.sha256 do throw "invalid manifest/artifact" + unless m.schema == 1 && m.artifact.blake3.all isHex do throw "invalid manifest/artifact" unless m.defaults.workers == 1 do throw "suite requires one fresh worker" unless 0 < m.defaults.memory_gib && m.defaults.memory_gib ≤ 96 do throw "memory budget must be between 1 and 96 GiB" @@ -168,10 +171,12 @@ def checkedOutput (cmd : String) (args : Array String) : IO String := do unless r.exitCode == 0 do throw (IO.userError s!"{cmd}: {r.stderr}") return r.stdout.trimAscii.toString +/-- A file's Blake3 digest, by `b3sum`: the harness runs under `lean --run`, +where the `Blake3` package's FFI hasher (`Ix.FileDigest`) is not linked. -/ def digest (path : FilePath) : IO String := do - let output ← checkedOutput "sha256sum" #["--", path.toString] + let output ← checkedOutput "b3sum" #["--", path.toString] let hash := (output.splitOn " ").head! - unless isHex hash do throw (IO.userError "invalid sha256sum output") + unless isHex hash do throw (IO.userError "invalid b3sum output") return hash def identities (paths : Array FilePath) : IO (Array String) := @@ -364,6 +369,8 @@ def run (opts : Options) : IO UInt32 := do let m : Manifest ← need (fromJson? manifestJson) need (validate m) if opts.selfTest then selfTest m; return 0 + if m.artifact.blake3.isNone then + throw (IO.userError "the manifest pins no Blake3 digest for its artifact; record artifact.blake3 first") unless [opts.ixe, opts.baseline, opts.adaptive, opts.output].all (!·.isEmpty) do throw (IO.userError "required: --ixe FILE --baseline BIN --adaptive BIN --output NEW_DIR") for id in opts.cases do @@ -381,7 +388,7 @@ def run (opts : Options) : IO UInt32 := do let out ← IO.FS.realPath out announce "Fingerprinting input and executables before timed checks..." let hashes ← paths.mapM digest - ensure (hashes[0]! == m.artifact.sha256) "artifact SHA-256 mismatch" + ensure (some hashes[0]! == m.artifact.blake3) "artifact Blake3 digest mismatch" ensure (hashes[1]! != hashes[2]!) "baseline/adaptive binaries are identical" ensure ((← identities paths) == initial) "input changed during fingerprinting" let runId := s!"{← IO.Process.getPID}-{← IO.monoMsNow}" @@ -389,36 +396,15 @@ def run (opts : Options) : IO UInt32 := do saveJson (out / "run.json") (Json.mkObj [ ("schema", toJson (1 : Nat)), ("run_id", toJson runId), ("manifest", manifestJson), ("selected_case_ids", toJson (selected.map (·.id))), - ("paths", toJson (paths.map (·.toString))), ("sha256", toJson hashes), + ("paths", toJson (paths.map (·.toString))), ("blake3", toJson hashes), ("file_identity", toJson initial), ("rounds", toJson opts.rounds), ("allow_work_changes", toJson opts.allowWorkChanges), ("host", toJson (← checkedOutput "uname" #["-a"])), - ("driver_sha256", toJson (← digest (suiteDir / "RunSuite.lean"))), + ("driver_blake3", toJson (← digest (suiteDir / "RunSuite.lean"))), ("started_utc", toJson (← checkedOutput "date" #["-u", "+%Y-%m-%dT%H:%M:%SZ"])), ("scope", toJson "subject-only; dependencies trusted"), ("workers", toJson (1 : Nat))]) -- Explicit prototype source snapshot; no unrelated worktree files. - for relative in #["crates/kernel/src/env.rs", "crates/kernel/src/env/scratch.rs", - "crates/kernel/src/subst.rs", "crates/kernel/src/subst/scratch_tests.rs", - "crates/kernel/src/infer.rs", "crates/kernel/src/infer/binders.rs", - "crates/kernel/src/infer/binders/tests.rs", - "crates/kernel/src/infer/application.rs", - "crates/kernel/src/infer/application/prefix.rs", - "crates/kernel/src/infer/application/tests.rs", - "crates/kernel/src/infer/summary.rs", - "crates/kernel/src/infer/summary/tests.rs", "crates/kernel/src/perf.rs", - "crates/kernel/src/perf/same_head.rs", - "crates/kernel/src/perf/hot_misses.rs", - "crates/kernel/src/def_eq.rs", "crates/kernel/src/tc.rs", - "crates/kernel/src/tc/spine_tests.rs", "crates/kernel/src/whnf.rs", - "crates/kernel/src/def_eq/projection_tests.rs", - "crates/kernel/src/def_eq/application.rs", - "crates/kernel/src/def_eq/application/tests.rs", - "crates/kernel/src/def_eq/same_head_tests.rs", - "crates/kernel/src/def_eq/speculation.rs", - "crates/kernel/src/def_eq/binders.rs", - "crates/kernel/src/def_eq/binders/tests.rs", - "crates/ffi/examples/check_anon_subject.rs", "Cargo.toml", "Cargo.lock", - "crates/kernel/Cargo.toml", "rust-toolchain.toml", "lean-toolchain", + for relative in #["Cargo.toml", "Cargo.lock", "rust-toolchain.toml", "lean-toolchain", "flake.nix", "flake.lock", ".cargo/config.toml", "Benchmarks/Kernel/AnthropicFLT/RunSuite.lean"] do let src : FilePath := relative diff --git a/Benchmarks/Kernel/CheckIxe.lean b/Benchmarks/Kernel/CheckIxe.lean index b898179bf..6bde7ac04 100644 --- a/Benchmarks/Kernel/CheckIxe.lean +++ b/Benchmarks/Kernel/CheckIxe.lean @@ -3,6 +3,7 @@ import Benchmarks.Kernel.CheckIxeStream import Benchmarks.Kernel.CheckIxePool import Benchmarks.Kernel.CheckIxeMeta import Ix.KernelCheck.Binding +import Ix.RoleBinding.UserAxioms /-! # Environment check of a compiled Ixon environment (untrusted) @@ -27,19 +28,31 @@ gives them and their addresses, `Nat.add@a1b2c3d4` (`Reader.spellWith` over `Key.toName` does). `kernel-check-ixe --meta` runs the check at `.meta` too and compares the two (`Benchmarks.Kernel.CheckIxeMeta`). -Usage: `kernel-check-ixe [--load stream|eager] [--jobs ] - [limit]`. +Usage: `kernel-check-ixe [--load stream|eager] [--jobs ] [--allow-user-axioms] + [limit]`. -* `--load stream` (the default) streams the records - (`Benchmarks.Kernel.CheckIxeStream`): the `.ixe` is loaded metadata-light, - the setup is built over record skeletons, and a record is decoded in full - only at its turn and its bytes dropped once it is read. `--load eager` - decodes the whole environment up front (`Ixon.deEnv`) and keeps every - record. The rows are the same; the streaming load holds less. +* `--load stream` (the default) uses the skeleton/pre-read loader + (`Benchmarks.Kernel.CheckIxeStream`): the `.ixe` is loaded metadata-light + (`Ixon.deEnvAnon`), parallel chunks decode each record to build the + skeleton store for `setup` and pre-read context-free records into `Read` + entries (retaining decoded `Ixon.Constant`s only for context-dependent or + tier records), and each pre-read or decoded entry is dropped as the check + loop consumes it. `--load eager` decodes the whole environment with full + metadata up front (`Ixon.deEnv`) and keeps every full record in the store. + The rows are the same. * `--jobs ` checks in two phases (`Benchmarks.Kernel.CheckIxePool`): every record installed in order, then the recorded checks on `n` worker threads. The rows are the per-record check's, each record's `micros` its install plus its checks. +* `--allow-user-axioms` admits safe axioms beyond Lean's standard three + (`Key.isCustomAxiomEligible`) on every path: streamed, eager, pooled and from + the read cache. Without it the check is strict and such an axiom declines. + The artifact's role binding must pass the kernel's role-table check for user + axioms (`Admission.admitAxiomPolicy .allowUser`: `sorryAx`, `propext` and + `Classical.choice` bound; `Ix.RoleBinding.UserAxioms.requireAxiomRoles`), + which the per-record step never reaches, so it runs once before any path, or + the run is refused with exit code 2; an accepted run lists the user axioms it + admitted (`CONDITIONAL on K user axioms`) after its counts. The watchdog (`CHECK_IXE_WATCH_MS`, default 60000; `CHECK_IXE_WATCH_MB`, default 20000) appends a runaway record's address to `.runaway` and @@ -85,30 +98,33 @@ inductive Load where | eager structure Options where - input : System.FilePath - output : System.FilePath + input : System.FilePath := ⟨""⟩ + output : System.FilePath := ⟨""⟩ limit : Option Nat := none load : Load := .stream jobs : Option Nat := none + allowUserAxioms : Bool := false def usage : String := - "usage: kernel-check-ixe [--load stream|eager] [--jobs ] [limit]" + "usage: kernel-check-ixe [--load stream|eager] [--jobs ] [--allow-user-axioms] \ + [limit]" /-- The options; the flags may come before or after the positional arguments. -/ def parseArgs (args : List String) : Option Options := - go args .stream none #[] + go args {} #[] where - go : List String → Load → Option Nat → Array String → Option Options - | "--load" :: "stream" :: rest, _, jobs, pos => go rest .stream jobs pos - | "--load" :: "eager" :: rest, _, jobs, pos => go rest .eager jobs pos - | "--jobs" :: n :: rest, load, _, pos => do + go : List String → Options → Array String → Option Options + | "--load" :: "stream" :: rest, o, pos => go rest { o with load := .stream } pos + | "--load" :: "eager" :: rest, o, pos => go rest { o with load := .eager } pos + | "--jobs" :: n :: rest, o, pos => do let k ← n.toNat? guard (k ≥ 1) - go rest load (some k) pos - | a :: rest, load, jobs, pos => if a.startsWith "--" then none else go rest load jobs (pos.push a) - | [], load, jobs, pos => match pos.toList with - | [input, output] => some { input, output, load, jobs } - | [input, output, limit] => do some { input, output, limit := some (← limit.toNat?), load, jobs } + go rest { o with jobs := some k } pos + | "--allow-user-axioms" :: rest, o, pos => go rest { o with allowUserAxioms := true } pos + | a :: rest, o, pos => if a.startsWith "--" then none else go rest o (pos.push a) + | [], o, pos => match pos.toList with + | [input, output] => some { o with input, output } + | [input, output, limit] => do some { o with input, output, limit := some (← limit.toNat?) } | _ => none /-- The order of a run: the setup's, or with `CHECK_IXE_ROOTS` the closure of @@ -128,7 +144,7 @@ longest-running check in the slots has run longer than `watchMs`, or the process's resident memory exceeds `watchKb` while a check runs, it appends that check's record to `runaway` and exits with code 3. -/ def watchdog (slots : Array CheckIxePool.Slot) (running : IO.Ref Bool) (watchMs watchKb : Nat) - (runaway : String) : IO Unit := do + (runaway : String) (onExit : IO Unit := pure ()) : IO Unit := do while ← running.get do IO.sleep 500 let mut oldest : Option (Address × Nat) := none @@ -140,6 +156,7 @@ def watchdog (slots : Array CheckIxePool.Slot) (running : IO.Ref Bool) (watchMs let rss ← statusKb "VmRSS:" if elapsed > watchMs || rss > watchKb then IO.eprintln s!"check-ixe: watchdog: {address} ran {elapsed} ms, RSS {rss / 1024} MB; exiting" + onExit let file ← IO.FS.Handle.mk runaway .append file.putStrLn (toString address) file.flush @@ -150,12 +167,20 @@ def setupLine (s : Setup) (blobs roles hints : Nat) (pre : Prelude .anon) : Stri s!"{s.store.size} records, {s.ordered.size} primary, {blobs} blobs, {roles} roles bound, \ {s.cx.index.recs.size} recursors indexed, prelude {pre.ix.decls.size} declarations, {hints} hints" -/-- Mark a live run's read-only inputs persistent before the loop's thread -takes them (`run`). `Runtime.markPersistent` is `unsafe` because a persistent -object is never freed; these live to the end of the run. -/ +/-- Mark a live run's report names persistent before the loop's thread takes +them (`run`). `Runtime.markPersistent` is `unsafe` because a persistent object +is never freed; the names live to the end of the run. -/ +def markNames (names : Std.HashMap Address (Array String)) : IO Unit := do + let _ ← unsafe Runtime.markPersistent names + +/-- Mark a live run's read-only inputs, its setup and its names, persistent +before the loop's thread takes them (`run`); as `markNames`, they live to the +end of the run. The streaming load marks its setup itself (`loadEnv`'s +`persistent`), before its pre-read workers share it, so that path marks only +the names. -/ def markInputs (s : Setup) (names : Std.HashMap Address (Array String)) : IO Unit := do let _ ← unsafe Runtime.markPersistent s - let _ ← unsafe Runtime.markPersistent names + markNames names /-- Counts of a run: outcomes and first causes. -/ abbrev Counts := Std.HashMap String Nat × Std.HashMap String Nat @@ -165,6 +190,18 @@ def run (args : List String) : IO UInt32 := do | IO.eprintln usage; return 2 let started ← IO.monoMsNow let bytes ← IO.FS.readBinFile options.input + -- `--allow-user-axioms` is refused up front, whichever path runs, unless the + -- artifact's binding passes the kernel's role-table check for user axioms + -- (`admitAxiomPolicy .allowUser`): the per-record step + -- (`annotDeclStepWithUserAxioms`) never reaches admission + if options.allowUserAxioms then + let binding := (← IO.ofExcept (Ixon.deEnvAnon bytes)).roleBinding + if let .error e := Ix.RoleBinding.UserAxioms.requireAxiomRoles binding then + IO.eprintln s!"check-ixe: --allow-user-axioms: {e}" + return 2 + -- the user axioms the run read (with their names) and the records it accepted + let userAxioms ← IO.mkRef ({} : Std.HashMap Address (Array String)) + let accepted ← IO.mkRef ({} : Std.HashSet Address) let rootsEnv ← IO.getEnv "CHECK_IXE_ROOTS" -- the persistent read cache (`CheckIxeReadCache`): full-order runs only let cacheDir ← IO.getEnv "CHECK_IXE_READ_CACHE" @@ -187,31 +224,58 @@ def run (args : List String) : IO UInt32 := do (Array.range (options.jobs.getD 1 - 1)).mapM fun _ => IO.mkRef (none : Option (Address × Nat)) let slots := #[slot0] ++ others let running ← IO.mkRef true - let _ ← IO.asTask (prio := .dedicated) - (watchdog slots running watchMs watchKb (options.output.toString ++ ".runaway")) let handle ← IO.FS.Handle.mk options.output .write - let emit := fun (row : Row) => do handle.putStrLn row.json.compress; handle.flush + let emitBuf ← IO.mkRef ("", 0) + let flushEmit : IO Unit := do + let (buf, n) ← emitBuf.modifyGet fun s => (s, ("", 0)) + if n > 0 then handle.putStr buf + handle.flush + let _ ← IO.asTask (prio := .dedicated) + (watchdog slots running watchMs watchKb (options.output.toString ++ ".runaway") flushEmit) + let emit := fun (row : Row) => do + if options.allowUserAxioms && row.outcome == "accept" then accepted.modify (·.insert row.address) + let line := row.jsonLine ++ "\n" + let flushNow? ← emitBuf.modifyGet fun (buf, n) => + let buf' := buf ++ line + let n' := n + 1 + if n' >= 512 then (some buf', ("", 0)) else (none, (buf', n')) + if let some chunk := flushNow? then + handle.putStr chunk + handle.flush let log := fun (line : String) => do IO.eprintln s!"check-ixe: {line}; at {(← IO.monoMsNow) - started} ms, \ RSS {(← statusKb "VmRSS:") / 1024} MB, peak {(← statusKb "VmHWM:") / 1024} MB" let limited := fun (a : Array Address) => match options.limit with | some n => a.extract 0 (min n a.size) | none => a - -- the loop over a source: the per-record check, or with `--jobs` the pool + -- the loop over a source: the per-record check, or with `--jobs` the pool; + -- strict unless `--allow-user-axioms`, on every source (streamed, eager or + -- the read cache's plan) + let allowUserAxioms := options.allowUserAxioms let check {σ : Type} (ev : Ix.Kernel.Evidences .anon) (src : LoopSource σ) (names : Address → Array String) (addresses : Array Address) (onRead : Address → Except ReadError (Read .anon) → Nat → IO Unit) (spell : Ix.Kernel.Key .anon → String := toString) : IO Counts := do + let onRead := fun (a : Address) (r : Except ReadError (Read .anon)) (n : Nat) => do + if allowUserAxioms then + if let .ok rd := r then + for x in Ix.RoleBinding.UserAxioms.ofDecls rd.decls.toList do + userAxioms.modify (·.insert x (names x)) + onRead a r n let total := addresses.size let progress := fun (i : Nat) (counts : Std.HashMap String Nat) => do if i % 1000 == 0 then IO.eprintln s!"check-ixe: {i}/{total} after {(← IO.monoMsNow) - started} ms; {counts.toList}; \ RSS {(← statusKb "VmRSS:") / 1024} MB" match options.jobs with - | some _ => CheckIxePool.run src ev names addresses skip slots emit log progress onRead spell + | some _ => + CheckIxePool.run src ev names addresses skip slots emit log progress onRead spell + (tier := if cacheDir.isNone then src.tierHook else none) + (allowUserAxioms := allowUserAxioms) | none => do let st ← src.init - let out ← checkLoopWith src.view src.read src.commit st (Checker.stepRecord ev) {} + let out ← checkLoopWith src.view src.read src.commit st + (Checker.stepRecordWithUserAxioms ev allowUserAxioms) {} names addresses skip emit (before := fun a => do slot0.set (some (a, ← IO.monoMsNow))) (after := slot0.set none) @@ -223,11 +287,11 @@ def run (args : List String) : IO UInt32 := do -- multi-threaded, and every reference count on them made atomic let record := cache.isSome && options.limit.isNone && rootsEnv.isNone let live {σ : Type} (s : Setup) (src : LoopSource σ) (names : Std.HashMap Address (Array String)) - (addresses : Array Address) (display : DisplayTable .anon) : IO Counts := do + (addresses : Array Address) (display : Thunk (DisplayTable .anon)) : IO Counts := do let readings ← IO.mkRef (#[] : Array (Address × Except ReadError (Read .anon))) let out ← check s.evidence src (names.getD · #[]) addresses (fun a r _ => if record then readings.modify (·.push (a, r)) else pure ()) - (spell := spellWith display) + (spell := fun k => spellWith display.get k) if let some (path, ixe) := cache then if record then let t0 ← IO.monoMsNow @@ -253,13 +317,12 @@ def run (args : List String) : IO UInt32 := do onThread (check plan.evidence plan.source (names.getD · #[]) (plan.addresses options.limit) fun _ _ _ => pure ()) | none => do - let contextEnv ← IO.ofExcept (Ixon.deEnvAnon bytes) - let (table, pre) ← IO.ofExcept (Ix.KernelCheck.Binding.context contextEnv) match options.load with | .eager => do let env ← IO.ofExcept (Ixon.deEnv bytes) - let mut store : RecordStore := {} - for (address, lazy) in env.consts.toList do + let (table, pre) ← IO.ofExcept (Ix.KernelCheck.Binding.context env) + let mut store : RecordStore := Std.HashMap.emptyWithCapacity env.consts.size + for (address, lazy) in env.consts do store := store.insert address (← IO.ofExcept lazy.get) let names := reportNames env store let hints := Hints.ofStore store env.anonHints @@ -267,24 +330,40 @@ def run (args : List String) : IO UInt32 := do log s!"eager load: {setupLine s env.blobs.size s.cx.roles.names.size hints.hints.size pre}" let addresses := limited (← orderOf s env.named pre rootsEnv) if thread then markInputs s names - onThread (live s s.source names addresses (CheckIxeMeta.displayTable env s.cx)) + onThread (live s s.source names addresses + (Thunk.mk fun () => CheckIxeMeta.displayTable env s.cx)) | .stream => do - let l ← CheckIxeStream.load bytes table pre + let contextEnv ← IO.ofExcept (Ixon.deEnvAnon bytes) + let (table, pre) ← IO.ofExcept (Ix.KernelCheck.Binding.context contextEnv) + -- the setup is marked persistent by the load itself, before its + -- pre-read workers share it, when the loop runs on its own thread + let l ← CheckIxeStream.loadEnv contextEnv table pre (persistent := thread) let s := l.setup let names := reportNames l.env s.store log s!"streaming load: {setupLine s l.env.blobs.size s.cx.roles.names.size l.env.anonHints.size pre}" let addresses := limited (← orderOf s l.env.named pre rootsEnv) - -- the setup and the names only: the records' windows go to the loop - -- unmarked, so that they are freed once it has taken their bytes - if thread then markInputs s names - onThread (live s l.source names addresses (CheckIxeMeta.displayTable l.env s.cx)) + -- the setup (marked by the load) and the names only: the records' + -- windows go to the loop unmarked, so that they are freed once it has + -- taken their bytes + if thread then markNames names + onThread (live s l.source names addresses + (Thunk.mk fun () => CheckIxeMeta.displayTable l.env s.cx)) running.set false + flushEmit + handle.flush let ranked := reasons.toArray.qsort (fun a b => a.2 > b.2) IO.eprintln s!"check-ixe: done in {(← IO.monoMsNow) - started} ms; {counts.toList}" IO.eprintln s!"check-ixe: peak RSS {(← statusKb "VmHWM:") / 1024} MB" IO.eprintln "check-ixe: first-cause reasons by frequency:" for (reason, count) in ranked.extract 0 25 do IO.eprintln s!" {count}\t{reason}" + if allowUserAxioms then + let accepted ← accepted.get + let admitted := (← userAxioms.get).toArray.filter (accepted.contains ·.1) + |>.qsort (fun a b => compare a.1 b.1 == .lt) + IO.eprintln s!"check-ixe: CONDITIONAL on {admitted.size} user axioms:" + for (address, names) in admitted do + IO.eprintln s!" {address} {" | ".intercalate names.toList}" return 0 end Benchmarks.Kernel.CheckIxe diff --git a/Benchmarks/Kernel/CheckIxeFold.lean b/Benchmarks/Kernel/CheckIxeFold.lean index bdf90631c..3f0646481 100644 --- a/Benchmarks/Kernel/CheckIxeFold.lean +++ b/Benchmarks/Kernel/CheckIxeFold.lean @@ -205,11 +205,6 @@ def run (args : List String) : IO UInt32 := do IO.eprintln s!"fold: read {decls.size} declarations ({prepared.size} prepared) in {(r1 - r0) / 1000000} ms" let (moved, guarded) := Ix.Kernel.Frontend.prepareReport pre.ix decls IO.eprintln s!"fold: hoist would move {moved} records; guard fired: {guarded}" - let u0 ← IO.monoNanosNow - let units ← IO.lazyPure fun _ => Ix.Kernel.checkUnits prepared - match units with - | .ok () => IO.eprintln s!"fold: units check passed in {((← IO.monoNanosNow) - u0) / 1000000} ms" - | .error (e, i) => IO.eprintln s!"fold: units check FAILED at {i}: {e}" let prepared ← if (← IO.getEnv "FOLD_SHARE") == some "1" then do let s0 ← IO.monoNanosNow let shared ← IO.lazyPure fun _ => ShareCommon.shareCommon' prepared diff --git a/Benchmarks/Kernel/CheckIxePaired.lean b/Benchmarks/Kernel/CheckIxePaired.lean index 3cb87bc3a..abbb2d648 100644 --- a/Benchmarks/Kernel/CheckIxePaired.lean +++ b/Benchmarks/Kernel/CheckIxePaired.lean @@ -1,4 +1,5 @@ import Benchmarks.Kernel.CheckIxeRows +import Ix.FileDigest /-! # Paired environment-check runs (untrusted tooling) @@ -15,7 +16,7 @@ two share. It exits 1 when the current rows lose a baseline acceptance. `kernel-check-ixe --paired --binary --baseline-binary --baseline-revision --input --output-dir [--revision -] [--baseline-source-sha256 ] [--source ] [--limit N] [--fuel N] +] [--baseline-source-blake3 ] [--source ] [--limit N] [--fuel N] [--runs N] [--warmups N] [--timeout s] [--time-binary ] [--top N]` alternates fresh processes of the two binaries over the same `.ixe` (one warmup and three measured samples each by default, the order swapped every @@ -30,7 +31,7 @@ source that changes during the run. It exits 1 when a pair loses a baseline acceptance, 2 on an error. The comparison's JSON is `Benchmarks.Kernel.CheckIxeRows`'s. The runner's own -fingerprint (`runner_sha256`) is that of the executable running it, and +fingerprint (`runner_blake3`) is that of the executable running it, and `--fuel`, which the current driver does not take, is passed only when given. -/ @@ -155,10 +156,8 @@ def writeJson (path : String) (v : Value) : IO Unit := do IO.FS.writeFile tmp (v.dumps 2 true ++ "\n") IO.FS.rename tmp path -def sha256 (path : System.FilePath) : IO String := do - let out ← IO.Process.output { cmd := "sha256sum", args := #["--", path.toString] } - unless out.exitCode == 0 do throw <| IO.userError s!"sha256sum {path}: {out.stderr}" - return (out.stdout.splitOn " ").headD "" +/-- The Blake3 digest of a file (`Ix.FileDigest.blake3File`). -/ +def blake3 (path : System.FilePath) : IO String := Ix.FileDigest.blake3File path /-- Python's `Path` order: component by component. -/ def pathLt (a b : String) : Bool := @@ -190,7 +189,7 @@ def sourceFingerprint (root : System.FilePath) : IO String := do h.write ((p.drop prefix_.length).toString.toUTF8 ++ ⟨#[0]⟩) h.write ((← IO.FS.readBinFile p) ++ ⟨#[0]⟩) h.flush - sha256 tmp + blake3 tmp /-- `shutil.which`. -/ def which (cmd : String) : IO (Option String) := do @@ -239,7 +238,7 @@ structure Options where source : Option String := none revision : Option String := none baselineRevision : String := "" - baselineSourceSha : Option String := none + baselineSourceBlake3 : Option String := none limit : Nat := 4300 fuel : Option Nat := none runs : Nat := 3 @@ -306,7 +305,7 @@ def sample (binary timer : String) (o : Options) (stem : String) : IO (Array (St | _ => result := result.push ("resources_error", .str s!"no resources in {metricsPath}") try let rows ← readRows rowsPath - result := result ++ rowSummary rows ++ #[("rows_sha256", .str (← sha256 rowsPath))] + result := result ++ rowSummary rows ++ #[("rows_blake3", .str (← blake3 rowsPath))] catch e => result := result.push ("rows_error", .str (toString e)) return result @@ -341,23 +340,23 @@ def run (o : Options) : IO UInt32 := do | some t => pure t | none => throw <| IO.userError "GNU time is required (or pass --time-binary)" let source := o.source.getD "." - let binaryShas ← binaries.mapM fun (_, p) => sha256 p - let inputSha ← sha256 o.input - let sourceSha ← sourceFingerprint source + let binaryDigests ← binaries.mapM fun (_, p) => blake3 p + let inputDigest ← blake3 o.input + let sourceDigest ← sourceFingerprint source let optStr (s : Option String) : Value := (s.map Value.str).getD .null let header : Array (String × Value) := #[("schema", .int 1), ("scope", .str "environment-check supported-profile coverage; not a certified whole-environment verdict"), - ("input", .obj #[("path", .str o.input), ("sha256", .str inputSha)]), - ("source_sha256", .str sourceSha), + ("input", .obj #[("path", .str o.input), ("blake3", .str inputDigest)]), + ("source_blake3", .str sourceDigest), ("source_note", .str "local source at invocation; caller must build the binary from this source"), - ("baseline_source_sha256", optStr o.baselineSourceSha), + ("baseline_source_blake3", optStr o.baselineSourceBlake3), ("toolchain", .str (← IO.FS.readFile (source ++ "/lean-toolchain")).trimAscii.toString), - ("runner_sha256", .str (← sha256 (← IO.appPath))), + ("runner_blake3", .str (← blake3 (← IO.appPath))), ("machine", ← uname), ("limit", .int o.limit), ("fuel", (o.fuel.map (Value.int ·)).getD .null), ("runs", .int o.runs), ("warmups", .int o.warmups), ("timeout_seconds", if o.timeoutGiven then .float o.timeout else .int 180), - ("binaries", .obj ((binaries.zip binaryShas).map fun ((side, path), sha) => - (side, .obj #[("path", .str path), ("sha256", .str sha), + ("binaries", .obj ((binaries.zip binaryDigests).map fun ((side, path), digest) => + (side, .obj #[("path", .str path), ("blake3", .str digest), ("revision", if side == "baseline" then .str o.baselineRevision else optStr o.revision)])))] let mut samples : Array Value := #[] let mut pairs : Array Value := #[] @@ -401,9 +400,9 @@ def run (o : Options) : IO UInt32 := do if !warmup then pairs := pairs.push (compare (paired.getD "baseline" {}) (paired.getD "current" {}) o.top) writeJson summaryPath (report samples pairs false #[]) - for ((side, path), sha) in binaries.zip binaryShas do - if (← sha256 path) != sha then throw <| IO.userError s!"{side} binary changed during the run" - if (← sha256 o.input) != inputSha || (← sourceFingerprint source) != sourceSha then + for ((side, path), digest) in binaries.zip binaryDigests do + if (← blake3 path) != digest then throw <| IO.userError s!"{side} binary changed during the run" + if (← blake3 o.input) != inputDigest || (← sourceFingerprint source) != sourceDigest then throw <| IO.userError "input or source changed during the run" let mut wall : Array (String × Value) := #[] let mut medians : Std.HashMap String Float := {} @@ -433,7 +432,7 @@ def run (o : Options) : IO UInt32 := do def usage : String := "usage: kernel-check-ixe --compare [--output ] [--top N]\n \ kernel-check-ixe --paired --binary --baseline-binary --baseline-revision \ - --input --output-dir [--revision ] [--baseline-source-sha256 ] \ + --input --output-dir [--revision ] [--baseline-source-blake3 ] \ [--source ] [--limit N] [--fuel N] [--runs N] [--warmups N] [--timeout s] \ [--time-binary ] [--top N]" @@ -470,7 +469,7 @@ def runPaired (args : List String) : IO UInt32 := do | "--source" :: v :: rest, o => parse rest { o with source := some v } | "--revision" :: v :: rest, o => parse rest { o with revision := some v } | "--baseline-revision" :: v :: rest, o => parse rest { o with baselineRevision := v } - | "--baseline-source-sha256" :: v :: rest, o => parse rest { o with baselineSourceSha := some v } + | "--baseline-source-blake3" :: v :: rest, o => parse rest { o with baselineSourceBlake3 := some v } | "--limit" :: v :: rest, o => v.toNat?.bind fun n => parse rest { o with limit := n } | "--fuel" :: v :: rest, o => v.toNat?.bind fun n => parse rest { o with fuel := some n } | "--runs" :: v :: rest, o => v.toNat?.bind fun n => parse rest { o with runs := n } diff --git a/Benchmarks/Kernel/CheckIxeReadCache.lean b/Benchmarks/Kernel/CheckIxeReadCache.lean index ad571ec38..12688b073 100644 --- a/Benchmarks/Kernel/CheckIxeReadCache.lean +++ b/Benchmarks/Kernel/CheckIxeReadCache.lean @@ -165,14 +165,4 @@ def Plan.source (plan : Plan) : LoopSource Unit := commit := fun _ _ _ => () init := pure () } -/-- The check loop over a plan, with the per-record step. -/ -def checkLoopPlan (plan : Plan) (limit : Option Nat) - (skip : Std.HashSet String) (emit : Row → IO Unit) - (before : Address → IO Unit := fun _ => pure ()) (after : IO Unit := pure ()) - (progress : Nat → Outcome → IO Unit := fun _ _ => pure ()) : IO Outcome := do - let src := plan.source - let names := plan.nameMap - checkLoopWith src.view src.read src.commit (← src.init) (Checker.stepRecord plan.evidence) {} - (names.getD · #[]) (plan.addresses limit) skip emit before after progress - end Benchmarks.Kernel.CheckIxeReadCache diff --git a/Benchmarks/Kernel/PinGen.lean b/Benchmarks/Kernel/PinGen.lean index 988a580d4..b656ea147 100644 --- a/Benchmarks/Kernel/PinGen.lean +++ b/Benchmarks/Kernel/PinGen.lean @@ -8,10 +8,12 @@ import Ix.Meta import Ix.CanonM import Ix.GraphM import Ix.CondenseM +import Ix.Compile.FrontEnd import Ix.LeanIngress import Ix.Cert.RoleClosurePrepare import IxC.Kernel.Verify.Rename.Basic import IxC.Kernel.RoleEquations +import Ix.FileDigest /-! # The default role table, its evidence and the Ixon prelude, from Ixon (untrusted) @@ -108,10 +110,6 @@ def regenerate : List String := IxC/Kernel/Ixon/RoleData.lean", "lake exe IxTests cert-statement # then: RoleEvidenceChecks on the committed file"] -def sha256 (path : System.FilePath) : IO String := do - let out ← IO.Process.output { cmd := "sha256sum", args := #["--", path.toString] } - return (out.stdout.splitOn " ").headD "" - /-- Separate large data initializers so elaboration does not keep every row on one recursion stack. The public array retains the entries in order. -/ def fixtureDefinition (name : String) (rows : Array String) : String := Id.run do @@ -191,7 +189,7 @@ def sourceEvidenceOf (pre : Prelude .source) (leanEnv : Lean.Environment) (worke Task.spawn fun _ => Ix.CanonM.canonChunk chunk let env : Ix.Environment := { consts := tasks.foldl (init := {}) fun m t => t.get.foldl (fun m (n, c) => m.insert n c) m } - let blocks := Ix.CondenseM.run (Ix.GraphM.envParallel env) + let blocks := Ix.CondenseM.run (Ix.Compile.FrontEnd.graph env) let groups := Ix.LeanIngress.groupsOf env blocks let g := Ix.LeanIngress.graphOf blocks groups (groups.map Ix.LeanIngress.convert) let mut st := pre.state @@ -756,8 +754,8 @@ def run (args : List String) : IO UInt32 := do IO.eprintln s!"pin-gen: quotient names: {qNames.size} names; reads back as the reader's proofs; \ {(← IO.monoMsNow) - qStarted} ms" -- 5: output - let digest ← sha256 input - let certDigest ← sha256 certInput + let digest ← Ix.FileDigest.blake3File input + let certDigest ← Ix.FileDigest.blake3File certInput let rowTerms (xs : Array (Address × ByteArray)) : Array String := xs.map fun (a, b) => s!" ({(toString a).quote},\n {(hexOfBytes b).quote})" let rowsOf (xs : Array (Address × ByteArray)) : String := @@ -775,9 +773,9 @@ To regenerate: {recipe} The default table binds every role (`Ix.Kernel.Role`) to the constant of the -compiled Init (sha256 {digest}) under the role's Lean name, with the +compiled Init (blake3 {digest}) under the role's Lean name, with the structural `Nat` operations' `defeq` evidence and, for each well-founded one, -the theorems of `IxC/Kernel/PinGen/Certs.lean` (compiled, sha256 {certDigest}) +the theorems of `IxC/Kernel/PinGen/Certs.lean` (compiled, blake3 {certDigest}) closed over the operation's dependency closure and written as theorem records. Every bound constant's record, the evidence and both literal capabilities were checked by the verified fold through the Ixon reader under @@ -818,7 +816,7 @@ every regeneration. -/ namespace Ix.Kernel.Reader.RoleData -def source : String := {s!"sha256:{digest} sha256:{certDigest}".quote} +def source : String := {s!"blake3:{digest} blake3:{certDigest}".quote} def table : String := {(hexOfBytes tableBytes).quote} diff --git a/Benchmarks/ShardMap.lean b/Benchmarks/ShardMap.lean deleted file mode 100644 index 27c5df47e..000000000 --- a/Benchmarks/ShardMap.lean +++ /dev/null @@ -1,365 +0,0 @@ -import Ix.ShardMap -import Ix.Benchmark.Bench - -open Ix -open BgroupM - -namespace Benchmarks.ShardMap - -/-! ## Helper Functions -/ - -/-- Insert N sequential keys into a map -/ -def insertN (map : ShardMap Nat Nat) (n : Nat) : IO Unit := do - for i in [:n] do - map.insert i i - -/-- Look up N sequential keys (all hits) -/ -def getN (map : ShardMap Nat Nat) (n : Nat) : IO Unit := do - for i in [:n] do - let _ ← map.get? i - -/-- Look up N keys that don't exist (all misses) -/ -def getMissN (map : ShardMap Nat Nat) (n : Nat) (offset : Nat) : IO Unit := do - for i in [:n] do - let _ ← map.get? (offset + i) - -/-- Remove N sequential keys -/ -def removeN (map : ShardMap Nat Nat) (n : Nat) : IO Unit := do - for i in [:n] do - let _ ← map.remove i - -/-- Insert items one by one in a loop -/ -def insertLoop (map : ShardMap Nat Nat) (items : Array (Nat × Nat)) : IO Unit := do - for (k, v) in items do - map.insert k v - -/-- Generate an array of N key-value pairs -/ -def genItems (n : Nat) : Array (Nat × Nat) := - Array.range n |>.map fun i => (i, i) - -/-- Concurrent workload: each thread inserts and reads its own range of keys -/ -def concurrentWorkload (map : ShardMap Nat Nat) (threads : Nat) (opsPerThread : Nat) : IO Unit := do - let mut tasks : Array (Task (Except IO.Error Unit)) := #[] - for t in [:threads] do - let task ← IO.asTask do - for i in [:opsPerThread] do - let key := t * opsPerThread + i - map.insert key key - let _ ← map.get? key - tasks := tasks.push task - for task in tasks do - let _ ← IO.ofExcept task.get - -/-- Concurrent reads only: all threads read the same pre-populated keys -/ -def concurrentReads (map : ShardMap Nat Nat) (threads : Nat) (keysToRead : Nat) : IO Unit := do - let mut tasks : Array (Task (Except IO.Error Unit)) := #[] - for _ in [:threads] do - let task ← IO.asTask do - for i in [:keysToRead] do - let _ ← map.get? i - tasks := tasks.push task - for task in tasks do - let _ ← IO.ofExcept task.get - -/-- Concurrent writes only: each thread writes to different keys -/ -def concurrentWrites (map : ShardMap Nat Nat) (threads : Nat) (keysPerThread : Nat) : IO Unit := do - let mut tasks : Array (Task (Except IO.Error Unit)) := #[] - for t in [:threads] do - let task ← IO.asTask do - for i in [:keysPerThread] do - let key := t * keysPerThread + i - map.insert key key - tasks := tasks.push task - for task in tasks do - let _ ← IO.ofExcept task.get - -/-- Mixed read/write workload with configurable read ratio -/ -def mixedWorkload (map : ShardMap Nat Nat) (threads : Nat) (opsPerThread : Nat) - (readRatio : Float) : IO Unit := do - let readOps := (readRatio * opsPerThread.toFloat).toUInt64.toNat - let writeOps := opsPerThread - readOps - let mut tasks : Array (Task (Except IO.Error Unit)) := #[] - for t in [:threads] do - let task ← IO.asTask do - -- Writes first - for i in [:writeOps] do - let key := t * opsPerThread + i - map.insert key key - -- Then reads - for i in [:readOps] do - let key := t * opsPerThread + (i % writeOps.max 1) - let _ ← map.get? key - tasks := tasks.push task - for task in tasks do - let _ ← IO.ofExcept task.get - -/-- Hot shard workload: all threads access keys that hash to same shard -/ -def hotShardWorkload (map : ShardMap Nat Nat) (threads : Nat) (opsPerThread : Nat) : IO Unit := do - -- All keys will be multiples of 256 to hit the same shard (assuming 256 shards) - let mut tasks : Array (Task (Except IO.Error Unit)) := #[] - for t in [:threads] do - let task ← IO.asTask do - for i in [:opsPerThread] do - let key := (t * opsPerThread + i) * 256 - map.insert key key - let _ ← map.get? key - tasks := tasks.push task - for task in tasks do - let _ ← IO.ofExcept task.get - -/-- Distributed workload: keys are spread across all shards -/ -def distributedWorkload (map : ShardMap Nat Nat) (threads : Nat) (opsPerThread : Nat) : IO Unit := do - let mut tasks : Array (Task (Except IO.Error Unit)) := #[] - for t in [:threads] do - let task ← IO.asTask do - for i in [:opsPerThread] do - -- Use a prime multiplier to distribute across shards - let key := (t * opsPerThread + i) * 127 - map.insert key key - let _ ← map.get? key - tasks := tasks.push task - for task in tasks do - let _ ← IO.ofExcept task.get - -/-! ## Benchmark Groups -/ - -/-- Basic single-threaded operations -/ -def basicBench : IO (Array BenchReport) := do - IO.println "=== Basic Operations (Single-threaded) ===\n" - bgroup "shardmap-basic" {} do - -- Insert throughput - throughput (.Elements 1000 "ops") - benchIO "insert 1K" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - insertN map 1000) () - throughput (.Elements 10000 "ops") - benchIO "insert 10K" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - insertN map 10000) () - throughput (.Elements 100000 "ops") - benchIO "insert 100K" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - insertN map 100000) () - - -- Lookup throughput (pre-populated) - throughput (.Elements 10000 "ops") - benchIO "get 10K hits" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - insertN map 10000 - getN map 10000) () - benchIO "get 10K misses" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - insertN map 10000 - getMissN map 10000 100000) () - - -- Remove throughput - benchIO "remove 10K" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - insertN map 10000 - removeN map 10000) () - -/-- Compare insertMany vs individual inserts -/ -def bulkBench : IO (Array BenchReport) := do - IO.println "=== Bulk Operations Comparison ===\n" - let items10K := genItems 10000 - bgroup "shardmap-bulk" {} do - throughput (.Elements 10000 "ops") - benchIO "insertMany 10K" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - map.insertMany items10K) () - benchIO "insert loop 10K" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - insertLoop map items10K) () - -/-- Concurrent read scaling with SharedMutex -/ -def concurrentReadBench : IO (Array BenchReport) := do - IO.println "=== Concurrent Read Scaling ===\n" - -- Pre-populate a shared map - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - insertN map 10000 - -- Every row does 10_000 total reads across its threads, so using a shared - -- throughput lets us directly compare ops/s across thread counts. - bgroup "shardmap-concurrent-read" {} do - throughput (.Elements 10000 "ops") - benchIO "read 1 thread" (fun () => concurrentReads map 1 10000) () - benchIO "read 2 threads" (fun () => concurrentReads map 2 5000) () - benchIO "read 4 threads" (fun () => concurrentReads map 4 2500) () - benchIO "read 8 threads" (fun () => concurrentReads map 8 1250) () - benchIO "read 16 threads" (fun () => concurrentReads map 16 625) () - -/-- Concurrent write scaling -/ -def concurrentWriteBench : IO (Array BenchReport) := do - IO.println "=== Concurrent Write Scaling ===\n" - -- Every row performs 10_000 total writes across its threads. - bgroup "shardmap-concurrent-write" {} do - throughput (.Elements 10000 "ops") - benchIO "write 1 thread 10K" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - concurrentWrites map 1 10000) () - benchIO "write 2 threads 5K each" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - concurrentWrites map 2 5000) () - benchIO "write 4 threads 2.5K each" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - concurrentWrites map 4 2500) () - benchIO "write 8 threads 1.25K each" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - concurrentWrites map 8 1250) () - -/-- Mixed read/write workload -/ -def mixedWorkloadBench : IO (Array BenchReport) := do - IO.println "=== Mixed Read/Write Workload (8 threads) ===\n" - -- 8 threads * 1000 ops = 8000 total ops per row. - bgroup "shardmap-mixed" {} do - throughput (.Elements 8000 "ops") - benchIO "mixed 50/50 8 threads" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - mixedWorkload map 8 1000 0.5) () - benchIO "mixed 80/20 read/write 8 threads" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - mixedWorkload map 8 1000 0.8) () - benchIO "mixed 95/5 read/write 8 threads" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - mixedWorkload map 8 1000 0.95) () - -/-- Compare different shard configurations -/ -def shardConfigBench : IO (Array BenchReport) := do - IO.println "=== Shard Configuration Impact (8 threads) ===\n" - -- `concurrentWorkload` does one insert + one get per op, so 8*1000*2 = 16_000 ops. - bgroup "shardmap-shards" {} do - throughput (.Elements 16000 "ops") - benchIO "shardBits=2 (4 shards)" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) (shardBits := 2) - concurrentWorkload map 8 1000) () - benchIO "shardBits=4 (16 shards)" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) (shardBits := 4) - concurrentWorkload map 8 1000) () - benchIO "shardBits=6 (64 shards)" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) (shardBits := 6) - concurrentWorkload map 8 1000) () - benchIO "shardBits=8 (256 shards)" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) (shardBits := 8) - concurrentWorkload map 8 1000) () - -/-- Contention patterns: hot shard vs distributed access -/ -def contentionBench : IO (Array BenchReport) := do - IO.println "=== Contention Patterns (8 threads) ===\n" - -- Both workloads do 1 insert + 1 get per op, so 8*500*2 = 8000 total ops. - bgroup "shardmap-contention" {} do - throughput (.Elements 8000 "ops") - benchIO "hot shard (worst case)" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - hotShardWorkload map 8 500) () - benchIO "distributed (best case)" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - distributedWorkload map 8 500) () - -/-- Pre-allocated capacity impact -/ -def capacityBench : IO (Array BenchReport) := do - IO.println "=== Capacity Pre-allocation Impact ===\n" - bgroup "shardmap-capacity" {} do - throughput (.Elements 10000 "ops") - benchIO "insert 10K (no prealloc)" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - insertN map 10000) () - benchIO "insert 10K (prealloc 32/shard)" (fun () => do - let map ← Ix.ShardMap.newWithCapacity (α := Nat) (β := Nat) - (capacityPerShard := 32) - insertN map 10000) () - benchIO "insert 10K (prealloc 64/shard)" (fun () => do - let map ← Ix.ShardMap.newWithCapacity (α := Nat) (β := Nat) - (capacityPerShard := 64) - insertN map 10000) () - -/-- Look up N sequential keys using get?Fast (all hits) -/ -def getNFast (map : ShardMap Nat Nat) (n : Nat) : IO Unit := do - for i in [:n] do - let _ ← map.get?Fast i - -/-- Concurrent reads using get?Fast -/ -def concurrentReadsFast (map : ShardMap Nat Nat) (threads : Nat) (keysToRead : Nat) : IO Unit := do - let mut tasks : Array (Task (Except IO.Error Unit)) := #[] - for _ in [:threads] do - let task ← IO.asTask do - for i in [:keysToRead] do - let _ ← map.get?Fast i - tasks := tasks.push task - for task in tasks do - let _ ← IO.ofExcept task.get - -/-- Hot shard workload using get?Fast: all threads access keys that hash to same shard -/ -def hotShardWorkloadFast (map : ShardMap Nat Nat) (threads : Nat) (opsPerThread : Nat) : IO Unit := do - let mut tasks : Array (Task (Except IO.Error Unit)) := #[] - for t in [:threads] do - let task ← IO.asTask do - for i in [:opsPerThread] do - let key := (t * opsPerThread + i) * 256 - map.insert key key - let _ ← map.get?Fast key - tasks := tasks.push task - for task in tasks do - let _ ← IO.ofExcept task.get - -/-- Compare get? vs get?Fast under different contention levels -/ -def fastPathBench : IO (Array BenchReport) := do - IO.println "=== get?Fast vs get? Comparison ===\n" - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - insertN map 10000 - bgroup "shardmap-fast-path" {} do - -- Single-threaded comparison (10_000 reads per row) - throughput (.Elements 10000 "ops") - benchIO "get? 10K (single thread)" (fun () => getN map 10000) () - benchIO "get?Fast 10K (single thread)" (fun () => getNFast map 10000) () - -- Concurrent comparison (8 * 1250 = 10_000 reads per row) - benchIO "get? 8 threads" (fun () => concurrentReads map 8 1250) () - benchIO "get?Fast 8 threads" (fun () => concurrentReadsFast map 8 1250) () - -- Hot shard comparison: 8 * 500 * 2 = 8000 ops (insert + get per iteration) - throughput (.Elements 8000 "ops") - benchIO "hot shard get?" (fun () => do - let m ← Ix.ShardMap.new (α := Nat) (β := Nat) - hotShardWorkload m 8 500) () - benchIO "hot shard get?Fast" (fun () => do - let m ← Ix.ShardMap.new (α := Nat) (β := Nat) - hotShardWorkloadFast m 8 500) () - -/-- Compare sequential vs parallel insertMany -/ -def parallelInsertBench : IO (Array BenchReport) := do - IO.println "=== Parallel insertMany Performance ===\n" - let items50K := genItems 50000 - let items100K := genItems 100000 - bgroup "shardmap-parallel-insert" {} do - throughput (.Elements 50000 "ops") - benchIO "insertMany 50K items" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - map.insertMany items50K) () - throughput (.Elements 100000 "ops") - benchIO "insertMany 100K items" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - map.insertMany items100K) () - throughput (.Elements 50000 "ops") - benchIO "insert loop 50K items" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - insertLoop map items50K) () - throughput (.Elements 100000 "ops") - benchIO "insert loop 100K items" (fun () => do - let map ← Ix.ShardMap.new (α := Nat) (β := Nat) - insertLoop map items100K) () - -end Benchmarks.ShardMap - -def main : IO Unit := do - IO.println "ShardMap Performance Benchmarks\n" - IO.println "================================\n" - - let _ ← Benchmarks.ShardMap.basicBench - let _ ← Benchmarks.ShardMap.bulkBench - let _ ← Benchmarks.ShardMap.concurrentReadBench - let _ ← Benchmarks.ShardMap.concurrentWriteBench - let _ ← Benchmarks.ShardMap.mixedWorkloadBench - let _ ← Benchmarks.ShardMap.shardConfigBench - let _ ← Benchmarks.ShardMap.contentionBench - let _ ← Benchmarks.ShardMap.capacityBench - let _ ← Benchmarks.ShardMap.fastPathBench - let _ ← Benchmarks.ShardMap.parallelInsertBench - - IO.println "\nBenchmarks complete!" diff --git a/Benchmarks/TruthMinesSpec/Main.lean b/Benchmarks/TruthMinesSpec/Main.lean index 3685baacb..6e4269f24 100644 --- a/Benchmarks/TruthMinesSpec/Main.lean +++ b/Benchmarks/TruthMinesSpec/Main.lean @@ -1,4 +1,9 @@ -/- +import Benchmarks.TruthMinesSpec.Projection +import Ix.Catalog +import Ix.Common +import Ix.Watchdog + +/-! `lake exe truthmines` — the corpus driver: Mathlib-parity ergonomics for the TruthMines catalog. Everything is projected from the typed records in `Benchmarks.TruthMinesSpec`; there is no version or toolchain handshake @@ -33,7 +38,7 @@ its source workspaces and compatibility patches stay owned by Palomar.ix. check [--mini] [--ixc DIR.ixc] [--only Q[,Q…]] [--jobs N] - per-piece KERNEL sweep: `ix check-rs --anon` over + per-piece KERNEL sweep: `ix check-lean --anon` over each member's piece in the `.ixc` directory — the fat-profile rung-1 checking story. Each piece is a self-contained env checked in its own subprocess @@ -53,10 +58,6 @@ Run from the repo root; `build` needs `lake build ix` first. -/ -import Benchmarks.TruthMinesSpec.Projection -import Ix.Catalog -import Ix.Common -import Ix.Watchdog open TruthMinesSpec @@ -524,7 +525,7 @@ private def parseCheck : List String → Except String CheckOptions pure { ← parseCheck rest with mini := true } | arg :: _ => .error s!"unknown check argument `{arg}`" -/-- Per-piece kernel sweep: `ix check-rs --anon` over each member's +/-- Per-piece kernel sweep: `ix check-lean --anon` over each member's piece in the `.ixc` directory. Every piece is a self-contained env checked in its own subprocess, so the pool's peak is one mmap'd member (~4–6 GiB RSS), never a union — no watchdog needed. Output @@ -554,7 +555,7 @@ build{if options.mini then " --mini" else ""}` first" let jobs := options.jobs?.getD 4 let exe ← IO.FS.realPath ixExe stageLine s!"[truthmines] check: {labels.size} piece(s), {jobs} in flight \ -({tier} tier; per-piece `ix check-rs --anon` over {ixc})" +({tier} tier; per-piece `ix check-lean --anon` over {ixc})" let runOne (q : String) : IO (MemberOutcome × String) := do let piece := ixc / s!"{q}.ixe" unless (← piece.pathExists) do @@ -562,7 +563,7 @@ build{if options.mini then " --mini" else ""}` first" s!"piece {piece} missing — stale manifest or partial build") let out ← IO.Process.output { cmd := exe.toString - args := #["check-rs", piece.toString, "--anon"] + args := #["check-lean", piece.toString, "--anon"] env := #[("IX_MAX_REC_FUEL", some "1000000000")] } let tail := if out.exitCode == 0 then "" else s!"── {q} stderr (first 4000) ──\n{out.stderr.take 4000}\n\ @@ -589,7 +590,7 @@ build{if options.mini then " --mini" else ""}` first" let piece := ixc / s!"{outcome.qualifier}.ixe" stageLine s!"[truthmines] {outcome.qualifier}: kernel REJECTED \ ({outcome.exit}) — rerun solo: IX_MAX_REC_FUEL=1000000000 \ -{ixExe} check-rs {piece} --anon" +{ixExe} check-lean {piece} --anon" unless tail.isEmpty do IO.eprintln tail stageLine s!"[truthmines] check done: {passed}/{labels.size} piece(s) \ kernel-clean{if failures.isEmpty then "" else s!"; failed: {failures}"}" diff --git a/Benchmarks/Typecheck.lean b/Benchmarks/Typecheck.lean index f5dd94323..319f9aea2 100644 --- a/Benchmarks/Typecheck.lean +++ b/Benchmarks/Typecheck.lean @@ -396,7 +396,7 @@ def runTypecheckCmd (p : Cli.Parsed) : IO UInt32 := do | IO.eprintln "error: --ixe is required"; return 1 let ixePath := ixeArg.as! String -- `--consts` comma-list ∪ `--consts-file`, shared grammar + dedup - -- (Ix.Cli.ConstsFile — same parser as `ix check-rs`). Raw strings: + -- (Ix.Cli.ConstsFile — same parser as `ix check-lean`). Raw strings: -- resolution against the env happens later (so the `toString` fallback can -- see the displayed form the user wrote). let nameArgs ← Ix.Cli.ConstsFile.gather p diff --git a/Benchmarks/UniformHard.lean b/Benchmarks/UniformHard.lean index 58002f3a2..78edc00b2 100644 --- a/Benchmarks/UniformHard.lean +++ b/Benchmarks/UniformHard.lean @@ -20,7 +20,7 @@ lake exe uniform-hard --scan-k # constants with more th ``` The default run's limits can be overridden with `UNIFORM_STATES` / `UNIFORM_EVALS`. `--tiered` and `--work` run the compiler's construction under -the compiler's limits, which `IX_SHARING_LIMITS` overrides. +the compiler's limits. -/ namespace Benchmarks.UniformHard @@ -60,13 +60,10 @@ def headName : Ix.Sharing.Exact.Head → String | .all .. => "all" | .letE _ => "let" -/-- The compiler's sharing limits (`Ix.CompileM.compilerSharingLimitsFromEnv`, -honouring `IX_SHARING_LIMITS`), under which the tiered runs measure the -production construction. -/ -def compilerLimits : IO Ix.Sharing.Exact.Limits := do - match ← Ix.CompileM.compilerSharingLimitsFromEnv with - | .ok l => pure l - | .error e => throw (IO.userError e) +/-- The compiler's sharing limits (`Ix.CompileM.compilerSharingLimits`), +under which the tiered runs measure the production construction. -/ +def compilerLimits : IO Ix.Sharing.Exact.Limits := + pure Ix.CompileM.compilerSharingLimits /-- The tiered construction on the named constants under the TagN layout and the compiler's limits: the three candidate lengths and the winning width. -/ diff --git a/Cargo.lock b/Cargo.lock index d74ea6602..9ff3172d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1745,7 +1745,6 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.0", - "rayon", "serde", "serde_core", ] @@ -1969,65 +1968,26 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" -[[package]] -name = "ix-bench" -version = "0.1.0" -dependencies = [ - "serde_json", - "tracing-texray", -] - [[package]] name = "ix-common" version = "0.1.0" dependencies = [ "bignat", "blake3", - "indexmap", "quickcheck", - "quickcheck_macros", "rustc-hash", ] -[[package]] -name = "ix-compile" -version = "0.1.0" -dependencies = [ - "anyhow", - "bignat", - "blake3", - "dashmap", - "indexmap", - "itertools 0.14.0", - "ix-common", - "ix-kernel", - "ixon", - "num-bigint 0.4.6", - "rayon", - "rustc-hash", - "sha2 0.10.9", -] - [[package]] name = "ix-ffi" version = "0.1.0" dependencies = [ "aiur", - "anyhow", - "bignat", "bincode", "blake3", - "bytes", - "dashmap", "getrandom 0.3.4", - "indexmap", "iroh", - "iroh-base", - "itertools 0.14.0", - "ix-bench", "ix-common", - "ix-compile", - "ix-kernel", "ix-proof-compression", "ixon", "ixvm-codegen", @@ -2036,7 +1996,6 @@ dependencies = [ "mimalloc", "multi-stark", "n0-error", - "num-bigint 0.4.6", "rayon", "rustc-hash", "serde", @@ -2049,27 +2008,6 @@ dependencies = [ "tracing-texray", ] -[[package]] -name = "ix-kernel" -version = "0.1.0" -dependencies = [ - "bignat", - "blake3", - "dashmap", - "indexmap", - "itertools 0.14.0", - "ix-common", - "ixon", - "log", - "num-bigint 0.4.6", - "quickcheck", - "quickcheck_macros", - "rayon", - "rustc-hash", - "smallvec", - "stacker", -] - [[package]] name = "ix-proof-compression" version = "0.1.0" @@ -2113,18 +2051,14 @@ dependencies = [ "bignat", "blake3", "dashmap", - "indexmap", "ix-common", "memmap2", - "mimalloc", "nom", "num-bigint 0.4.6", "quickcheck", "quickcheck_macros", "rayon", "rustc-hash", - "sha2 0.10.9", - "tiny-keccak", ] [[package]] @@ -2132,11 +2066,9 @@ name = "ixvm-codegen" version = "0.1.0" dependencies = [ "aiur", - "dashmap", "ix-common", "ixon", "multi-stark", - "num-bigint 0.4.6", "rayon", "rustc-hash", "stacker", diff --git a/Cargo.toml b/Cargo.toml index 448744c50..5d62a1651 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,11 @@ [workspace] members = [ "crates/aiur", - "crates/bench", "crates/common", - "crates/compile", "crates/proof-compression", "crates/ffi", "crates/ixvm-codegen", "crates/ixon", - "crates/kernel", ] resolver = "2" @@ -17,7 +14,7 @@ panic = "abort" [profile.release] panic = "abort" -# Cross-crate inlining across the ixon/compile/ffi boundary — the hot +# Cross-crate inlining across the ixon/ffi boundary — the hot # loops (blake3 digests, subterm sharing, serialization) span crates. # "thin" keeps build times tolerable vs "fat" for near-equal wins. lto = "thin" @@ -31,25 +28,19 @@ license = "MIT OR Apache-2.0" # Internal crates aiur = { path = "crates/aiur" } ixvm-codegen = { path = "crates/ixvm-codegen" } -ix-bench = { path = "crates/bench" } ix-common = { path = "crates/common" } -ix-compile = { path = "crates/compile" } ixon = { path = "crates/ixon" } -ix-kernel = { path = "crates/kernel" } # lean-ffi tree (lean-ffi crate + factored-out bignat sub-crate) bignat = { git = "https://github.com/argumentcomputer/lean-ffi.git", rev = "93c7e52952ae94546be08313f4ff3922984c84d5" } lean-ffi = { git = "https://github.com/argumentcomputer/lean-ffi.git", rev = "93c7e52952ae94546be08313f4ff3922984c84d5" } # External shared deps -anyhow = "1" blake3 = "1.8.4" dashmap = "6.1.0" hashbrown = "0.15" indexmap = "2" -itertools = "0.14.0" libc = "0.2" -log = "0.4" memmap2 = "0.9" # Bundle v3.5.1 to fix large-arena failures (microsoft/mimalloc#1309). mimalloc = { git = "https://github.com/argumentcomputer/mimalloc_rust.git", rev = "616cc1f130d9ea2a1111a46b4e98c140e078f882", default-features = false } @@ -62,8 +53,6 @@ rayon = "1" rustc-hash = "2" serde_json = "1" sha2 = "0.10" -smallvec = "1.15.1" -stacker = "0.1" tiny-keccak = { version = "2", features = ["keccak"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/Ix.lean b/Ix.lean index 564635f03..c2ce7029c 100644 --- a/Ix.lean +++ b/Ix.lean @@ -1,5 +1,3 @@ --- This module serves as the root of the `Ix` library. --- Import modules here that should be built as part of the library. module public import Ix.Environment public import Ix.CanonM @@ -26,3 +24,6 @@ public import Ix.Watchdog public import Ix.Benchmark.Bench public import Ix.Aiur public import Ix.TracingTexray + +/-! This module serves as the root of the `Ix` library. +Import modules here that should be built as part of the library. -/ diff --git a/Ix/Aggr/Circuit.lean b/Ix/Aggr/Circuit.lean index c5897bb35..faf75d795 100644 --- a/Ix/Aggr/Circuit.lean +++ b/Ix/Aggr/Circuit.lean @@ -111,7 +111,7 @@ namespace Aggr def circuit := ⟦ enum AggrNextAssumption { Done, - More(Addr, List‹Addr›, List‹Addr›) + More(Addr, G, List‹Addr›, List‹Addr›) } /- ## Small strict byte readers @@ -415,21 +415,64 @@ def circuit := ⟦ } } + -- Assert `output` is the sorted, disjoint union of two sorted unique inputs. + -- Used for flat pair subject sets (`aggr_pair`), where child subject sets + -- must partition the joined subjects without overlap. + fn aggr_assert_disjoint_union(left: List‹Addr›, right: List‹Addr›, + output: List‹Addr›) { + match (load(left), load(right)) { + (ListNode.Nil, _) => aggr_assert_same_list(right, output), + (_, ListNode.Nil) => aggr_assert_same_list(left, output), + (ListNode.Cons(a, ar), ListNode.Cons(b, br)) => + let ListNode.Cons(o, or) = load(output); + match aggr_address_order(a, b) { + 0 => + assert_eq!(address_eq(a, o), 1, "aggr: subject union mismatch"); + aggr_assert_disjoint_union(ar, right, or), + 1 => + assert_eq!(0, 1, "aggr: child subject sets overlap"); + (), + _ => + assert_eq!(address_eq(b, o), 1, "aggr: subject union mismatch"); + aggr_assert_disjoint_union(left, br, or), + }, + } + } + + -- Assert two sorted address lists are disjoint. Used in `aggr_pair` to + -- enforce `subjects_i ∩ assumptions_i = ∅` per child so a child's subjects + -- can never discharge its own assumptions. + fn aggr_assert_disjoint(left: List‹Addr›, right: List‹Addr›) { + match (load(left), load(right)) { + (ListNode.Nil, _) => (), + (_, ListNode.Nil) => (), + (ListNode.Cons(a, ar), ListNode.Cons(b, br)) => + match aggr_address_order(a, b) { + 0 => aggr_assert_disjoint(ar, right), + 1 => + assert_eq!(0, 1, "aggr: subject set overlaps assumption set"); + (), + _ => aggr_assert_disjoint(left, br), + }, + } + } + -- Select the next unique value from the union of two sorted assumption - -- lists, returning the unconsumed tails. + -- lists, tagging its origin (`0 = left only`, `1 = both`, `2 = right only`) + -- and returning the unconsumed tails. fn aggr_next_assumption(left: List‹Addr›, right: List‹Addr›) -> AggrNextAssumption { match (load(left), load(right)) { (ListNode.Nil, ListNode.Nil) => AggrNextAssumption.Done, (ListNode.Cons(a, ar), ListNode.Nil) => - AggrNextAssumption.More(a, ar, right), + AggrNextAssumption.More(a, 0, ar, right), (ListNode.Nil, ListNode.Cons(b, br)) => - AggrNextAssumption.More(b, left, br), + AggrNextAssumption.More(b, 2, left, br), (ListNode.Cons(a, ar), ListNode.Cons(b, br)) => match aggr_address_order(a, b) { - 0 => AggrNextAssumption.More(a, ar, right), - 1 => AggrNextAssumption.More(a, ar, br), - _ => AggrNextAssumption.More(b, left, br), + 0 => AggrNextAssumption.More(a, 0, ar, right), + 1 => AggrNextAssumption.More(a, 1, ar, br), + _ => AggrNextAssumption.More(b, 2, left, br), }, } } @@ -458,7 +501,7 @@ def circuit := ⟦ assert_eq!(load(output), ListNode.Nil, "aggr: output has an extra assumption"); (), - AggrNextAssumption.More(candidate, left_rest, right_rest) => + AggrNextAssumption.More(candidate, _source, left_rest, right_rest) => let (discharged, subject_rest) = aggr_seek_subject(candidate, subjects); match discharged { 1 => aggr_assert_difference(left_rest, right_rest, subject_rest, output), @@ -476,8 +519,11 @@ def circuit := ⟦ Structural pairs do not open their subject trees. Channel 6 carries one choice for every unique input-assumption candidate, keyed by the raw candidate address. A carried candidate must occur next in the canonical - output-assumption list; a discharged candidate must supply a valid Merkle - path into the one-hash structural output root. + output-assumption list; a discharged candidate must supply a valid cross-child + Merkle path into the opposite child's subject root (`right_root` for a + left-only assumption, `left_root` for a right-only assumption) and step into + the one-hash structural output root. An assumption shared by both children + cannot be discharged and must be carried. -/ fn aggr_fold_path(hash: Addr, remaining: G, stream: ByteStream) @@ -497,11 +543,15 @@ def circuit := ⟦ } } - -- Return 1 when `candidate` is discharged by a path into `root`, or 0 - -- when it is explicitly carried. The payload is strict: + -- Return 1 when `candidate` is discharged by a cross-child path into the + -- opposite child's subject root (`right_root` for a left-only assumption, + -- `left_root` for a right-only assumption), or 0 when it is explicitly + -- carried. An assumption present in both children (`source == 1`) cannot be + -- discharged at this join and must be carried. The payload is strict: -- carried: 0 -- discharged: 1 || count:u8 || (side:u8 || sibling:32)* - fn aggr_discharge_choice(candidate: Addr, root: Addr) -> G { + fn aggr_discharge_choice(candidate: Addr, source: G, + left_root: Addr, right_root: Addr) -> G { let (idx, len) = io_get_info(6, load(candidate)); let bytes = read_checked_byte_stream(6, idx, len); let (choice, rest) = aggr_read_byte(bytes); @@ -514,37 +564,60 @@ def circuit := ⟦ 0, _ => let (count, path) = aggr_read_byte(rest); + assert_eq!(u8_less_than(0u8, count), 1, + "aggr structural: Merkle path must have at least 1 step"); assert_eq!(u8_less_than(count, 65u8), 1, "aggr structural: Merkle path exceeds 64 steps"); - let (actual, stop) = aggr_fold_path(aggr_leaf_hash(candidate), - to_field(count), path); + let (child_actual, last_step) = aggr_fold_path(aggr_leaf_hash(candidate), + to_field(count) - 1, path); + let (last_side, s1) = aggr_read_byte(last_step); + let (last_sibling, stop) = aggr_read_address(s1); assert_eq!(load(stop), ListNode.Nil, "aggr structural: trailing bytes after Merkle path"); - assert_eq!(address_eq(actual, root), 1, - "aggr structural: assumption path does not reach subject root"); - 1, + match source { + 0 => + assert_eq!(last_side, 0u8, + "aggr structural: left assumption must be discharged by right child"); + assert_eq!(address_eq(last_sibling, left_root), 1, + "aggr structural: final step sibling is not left subject root"); + assert_eq!(address_eq(child_actual, right_root), 1, + "aggr structural: assumption path does not reach right subject root"); + 1, + 2 => + assert_eq!(last_side, 1u8, + "aggr structural: right assumption must be discharged by left child"); + assert_eq!(address_eq(last_sibling, right_root), 1, + "aggr structural: final step sibling is not right subject root"); + assert_eq!(address_eq(child_actual, left_root), 1, + "aggr structural: assumption path does not reach left subject root"); + 1, + _ => + assert_eq!(0, 1, + "aggr structural: shared assumption cannot be discharged"); + 0, + }, } } -- Every unique candidate from `left ∪ right` is either discharged by a - -- membership path or consumed from the strictly-sorted output list. + -- cross-child membership path or consumed from the strictly-sorted output list. fn aggr_assert_structural_difference(left: List‹Addr›, right: List‹Addr›, - subject_root: Addr, output: List‹Addr›) { + left_root: Addr, right_root: Addr, output: List‹Addr›) { match aggr_next_assumption(left, right) { AggrNextAssumption.Done => assert_eq!(load(output), ListNode.Nil, "aggr structural: output has an extra assumption"); (), - AggrNextAssumption.More(candidate, left_rest, right_rest) => - match aggr_discharge_choice(candidate, subject_root) { + AggrNextAssumption.More(candidate, source, left_rest, right_rest) => + match aggr_discharge_choice(candidate, source, left_root, right_root) { 1 => aggr_assert_structural_difference(left_rest, right_rest, - subject_root, output), + left_root, right_root, output), _ => let ListNode.Cons(actual, output_rest) = load(output); assert_eq!(address_eq(candidate, actual), 1, "aggr structural: outstanding assumption mismatch"); aggr_assert_structural_difference(left_rest, right_rest, - subject_root, output_rest), + left_root, right_root, output_rest), }, } } @@ -820,7 +893,9 @@ def circuit := ⟦ let output_subjects = aggr_load_canonical_tree(output_root); let output_assumptions = aggr_load_optional_tree(output_asm); - aggr_assert_union(left_subjects, right_subjects, output_subjects); + aggr_assert_disjoint(left_subjects, left_assumptions); + aggr_assert_disjoint(right_subjects, right_assumptions); + aggr_assert_disjoint_union(left_subjects, right_subjects, output_subjects); aggr_assert_difference(left_assumptions, right_assumptions, output_subjects, output_assumptions); () @@ -850,6 +925,8 @@ def circuit := ⟦ aggr_assert_same_context(left_ctx, right_ctx); aggr_assert_same_context(left_ctx, output_ctx); + assert_eq!(address_eq(left_root, right_root), 0, + "aggr structural: child subject roots must be distinct"); let expected_root = aggr_node_hash(left_root, right_root); assert_eq!(address_eq(output_root, expected_root), 1, "aggr structural: output subject root is not nodeHash(left, right)"); @@ -858,7 +935,7 @@ def circuit := ⟦ let right_assumptions = aggr_load_optional_tree(right_asm); let output_assumptions = aggr_load_optional_tree(output_asm); aggr_assert_structural_difference(left_assumptions, right_assumptions, - output_root, output_assumptions); + left_root, right_root, output_assumptions); () } @@ -1017,6 +1094,8 @@ def circuit := ⟦ aggr_assert_same_context((left_spec, left_class, left_axioms, 5), output_ctx); aggr_assert_same_context((right_spec, right_class, right_axioms, 5), output_ctx); + assert_eq!(address_eq(left_root, right_root), 0, + "aggr cross-binding: child subject roots must be distinct"); let expected_root = aggr_node_hash(left_root, right_root); assert_eq!(address_eq(output_root, expected_root), 1, "aggr cross-binding: output subject root is not nodeHash(left, right)"); @@ -1024,7 +1103,7 @@ def circuit := ⟦ let right_assumptions = aggr_load_optional_tree(right_asm); let output_assumptions = aggr_load_optional_tree(output_asm); aggr_assert_structural_difference(left_assumptions, right_assumptions, - output_root, output_assumptions); + left_root, right_root, output_assumptions); () } diff --git a/Ix/Aggr/Host.lean b/Ix/Aggr/Host.lean index f50797f20..1be27282a 100644 --- a/Ix/Aggr/Host.lean +++ b/Ix/Aggr/Host.lean @@ -60,8 +60,13 @@ def ofClaim (claim : Ix.Claim) | some assumptionRoot => let some assumptionTree := trees.get? assumptionRoot | throw s!"aggr: missing assumption tree {assumptionRoot}" - pure (some (← canonicalizeAt "aggr assumptions" - assumptionRoot assumptionTree)) + let canonicalAssumptions ← canonicalizeAt "aggr assumptions" + assumptionRoot assumptionTree + let subjectSet := subjects.leaves.foldl (fun set addr => set.insert addr) + ({} : Std.HashSet Address) + if canonicalAssumptions.leaves.any (fun addr => subjectSet.contains addr) then + throw "aggr: subject set overlaps assumption set" + pure (some canonicalAssumptions) pure { ctx, subjects, assumptions } private def assumptionLeaves (statement : CheckEnvTrees) : Array Address := @@ -75,41 +80,67 @@ def assumptionCandidates (left right : CheckEnvTrees) : Array Address := | none => #[] | some tree => tree.leaves -/-- Canonical pair fold: - -`subjects = subjectsL ∪ subjectsR` - -`assumptions = (assumptionsL ∪ assumptionsR) ∖ subjects`. - -The circuit independently verifies this relation with sorted linear merges, -and that both children and the output share one context (the left child's -here); the host implementation uses a hash set only to construct the witness. -/ +/-- True when `candidate` is discharged by the opposite child's subjects: +a left-only assumption by `right.subjects`, or a right-only assumption by +`left.subjects`. Shared assumptions (`candidate ∈ left.assumptions ∩ right.assumptions`) +and self-assumptions cannot be discharged. -/ +private def isCrossDischarged (left right : CheckEnvTrees) (candidate : Address) : Bool := + let inLeftAsm := left.assumptions.any (·.contains candidate) + let inRightAsm := right.assumptions.any (·.contains candidate) + if inLeftAsm && !inRightAsm then + right.subjects.contains candidate + else if inRightAsm && !inLeftAsm then + left.subjects.contains candidate + else + false + +/-- Canonical pair fold (`aggr_pair`): + +`subjects = subjectsL ∪ subjectsR` (disjoint for flat canonical pairs) + +`assumptions = ((assumptionsL ∖ subjectsR) ∪ (assumptionsR ∖ subjectsL))`. + +For flat canonical pairs (`aggr_pair`), the circuit verifies subject-set +disjointness, the union, and the difference with sorted linear merges, and that +both children and the output share one context (the left child's here). +(Structural and cross-binding joins check `left_root != right_root` in-circuit +without opening the subject trees, while the Rust host's shard check enforces +subject disjointness out-of-circuit; `aggrDerivation_sound` does not depend on +subject disjointness.) -/ def join (left right : CheckEnvTrees) : CheckEnvTrees := let subjectLeaves := left.subjects.leaves ++ right.subjects.leaves let subjects := (Ix.AssumptionTree.canonical subjectLeaves).get! - let subjectSet := subjects.leaves.foldl (fun set addr => set.insert addr) - ({} : Std.HashSet Address) let remaining := assumptionCandidates left right |>.filter - (fun addr => !subjectSet.contains addr) + (fun addr => !isCrossDischarged left right addr) { ctx := left.ctx, subjects, assumptions := Ix.AssumptionTree.canonical remaining } /-- Structural fold. Subjects are committed in O(1) as a free-form node over the two child roots. Assumptions retain canonical set semantics and drop -precisely those candidates for which the resulting subject forest can produce -a membership path. -/ +precisely those candidates discharged by the opposite child's subject tree. -/ def joinStructural (left right : CheckEnvTrees) : CheckEnvTrees := let subjects := Ix.AssumptionTree.join left.subjects right.subjects let remaining := assumptionCandidates left right |>.filter - (fun addr => !subjects.contains addr) + (fun addr => !isCrossDischarged left right addr) { ctx := left.ctx, subjects, assumptions := Ix.AssumptionTree.canonical remaining } /-- One channel-6 choice per structural-pair candidate. `some path` discharges -the candidate against the structural output root; `none` carries it into the -output assumption set. -/ -def structuralPathAdvice (left right output : CheckEnvTrees) : +the candidate against the opposite child's subject root and steps into the +structural output root; `none` carries it into the output assumption set. -/ +def structuralPathAdvice (left right _output : CheckEnvTrees) : Array (Address × Option Ix.Merkle.MerklePath) := assumptionCandidates left right |>.map fun candidate => - (candidate, output.subjects.merkleProof candidate) + let inLeftAsm := left.assumptions.any (·.contains candidate) + let inRightAsm := right.assumptions.any (·.contains candidate) + let path? := + if inLeftAsm && !inRightAsm then + (right.subjects.merkleProof candidate).map fun p => + p.push (left.subjects.root, true) + else if inRightAsm && !inLeftAsm then + (left.subjects.merkleProof candidate).map fun p => + p.push (right.subjects.root, false) + else + none + (candidate, path?) /-- The present trees in channel-5 order for one pair fold. Optional empty assumption sets contribute no advice entry. -/ diff --git a/Ix/Aggr/Verification.lean b/Ix/Aggr/Verification.lean index 38b979491..266b1094b 100644 --- a/Ix/Aggr/Verification.lean +++ b/Ix/Aggr/Verification.lean @@ -58,6 +58,177 @@ def auditAggregateConstants (env : Ixon.Env) (statement : Aggr.CheckEnvTrees) : {env.consts.size} constants, subject tree has {leaves.size} leaves" pure env.consts.size +/-! ## Set-discharge composition soundness + +Formalization of the abstract set-discharge invariant across `CheckEnv` leaf +claims and recursive `ix_aggr` pair folds (flat, structural, and cross-binding): + +1. Every base leaf claim `(S, A)` in the abstract model satisfies `S ∩ A = ∅` + (`disjoint`), checks every owned subject `x ∈ S` under its dependencies + (`checked`), and ensures every direct dependency `y` of an owned subject + `x ∈ S` lies in `S ∪ A` (`closed`). (In the Aiur circuit, `env_walk_leaves` + guards `S ∩ A = ∅` by interned store pointer, which holds under honest + content-addressed interning; note that `aggrDerivation_sound`'s root closure + and validity conclusions use only `checked` and `closed`, not `disjoint`.) +2. Every binary join `(S_L, A_L), (S_R, A_R) ↦ (S_out, A_out)` unions the + subject sets (`S_out = S_L ∪ S_R`) and discharges an assumption only + against the opposite child's subjects (`A_L ∖ A_out ⊆ S_R` and + `A_R ∖ A_out ⊆ S_L`). +3. By induction over any aggregate derivation tree, every subject `x ∈ S` at + any node was checked at some base leaf, every discharged assumption belongs + to `S` and was checked at some base leaf, and when `A = ∅` at the root the + subject set `S` is closed under dependencies so well-founded induction along + the acyclic content-addressed dependency relation yields unconditional + validity for every `x ∈ S`. +-/ + +/-- The abstract base-leaf obligations modeled for `IxVM`'s `run_check_env` on a +`CheckEnv` claim with subject set `S` and assumption set `A`. -/ +structure LeafValid (Checked : α → Prop) (Dep : α → α → Prop) + (S A : α → Prop) : Prop where + /-- Owned subjects and assumptions are disjoint (modeled at the set level; + checked by interned store pointer in `env_walk_leaves` for honest provers, and + not required by `aggrDerivation_sound`'s root closure/validity proof). -/ + disjoint : ∀ x, S x → ¬ A x + /-- Every owned subject is typechecked by `run_check` / `check_muts_all`. -/ + checked : ∀ x, S x → Checked x + /-- Every direct dependency of an owned subject is either owned or assumed. -/ + closed : ∀ x y, S x → Dep x y → S y ∨ A y + +/-- The binary cross-discharge relation verified by `aggr_pair`, +`aggr_pair_structural`, and `aggr_cross_binding`. -/ +structure JoinRel (SL AL SR AR Sout Aout : α → Prop) : Prop where + /-- Output subjects are the union of the children's subjects. -/ + subjects_iff : ∀ x, Sout x ↔ SL x ∨ SR x + /-- A left assumption absent from `Aout` must be discharged by `SR`. -/ + discharge_left : ∀ x, AL x → ¬ Aout x → SR x + /-- A right assumption absent from `Aout` must be discharged by `SL`. -/ + discharge_right : ∀ x, AR x → ¬ Aout x → SL x + /-- Every output assumption originates from at least one child. -/ + assumptions_sub : ∀ x, Aout x → AL x ∨ AR x + +/-- An aggregate derivation tree built from `IxVM` leaves, pass-through wraps, +and binary set-discharge joins. -/ +inductive AggrDerivation (Checked : α → Prop) (Dep : α → α → Prop) : + (α → Prop) → (α → Prop) → Prop where + | leaf {S A : α → Prop} : + LeafValid Checked Dep S A → + AggrDerivation Checked Dep S A + | wrap {S A : α → Prop} : + AggrDerivation Checked Dep S A → + AggrDerivation Checked Dep S A + | join {SL AL SR AR Sout Aout : α → Prop} : + AggrDerivation Checked Dep SL AL → + AggrDerivation Checked Dep SR AR → + JoinRel SL AL SR AR Sout Aout → + AggrDerivation Checked Dep Sout Aout + +/-- Every subject of any aggregate derivation was checked by some base leaf. -/ +theorem aggrDerivation_subject_checked + {Checked : α → Prop} {Dep : α → α → Prop} {S A : α → Prop} + (h : AggrDerivation Checked Dep S A) : + ∀ x, S x → Checked x := by + induction h with + | leaf hLeaf => + intro x hx + exact hLeaf.checked x hx + | wrap _ ih => + exact ih + | join _ _ hJoin ihL ihR => + intro x hx + rcases (hJoin.subjects_iff x).mp hx with hxL | hxR + · exact ihL x hxL + · exact ihR x hxR + +/-- Every assumption discharged at a binary join belongs to the joined subject +set and was checked by some base leaf. -/ +theorem join_discharged_subject_checked + {Checked : α → Prop} {Dep : α → α → Prop} + {SL AL SR AR Sout Aout : α → Prop} + (hL : AggrDerivation Checked Dep SL AL) + (hR : AggrDerivation Checked Dep SR AR) + (hJoin : JoinRel SL AL SR AR Sout Aout) : + ∀ x, (AL x ∨ AR x) → ¬ Aout x → Sout x ∧ Checked x := by + intro x hAsm hNotOut + rcases hAsm with hAL | hAR + · have hSR : SR x := hJoin.discharge_left x hAL hNotOut + exact ⟨(hJoin.subjects_iff x).mpr (.inr hSR), + aggrDerivation_subject_checked hR x hSR⟩ + · have hSL : SL x := hJoin.discharge_right x hAR hNotOut + exact ⟨(hJoin.subjects_iff x).mpr (.inl hSL), + aggrDerivation_subject_checked hL x hSL⟩ + +/-- At every node of an aggregate derivation, every direct dependency of a +subject is either in the node's subject set or in its assumption set. -/ +theorem aggrDerivation_closed_union + {Checked : α → Prop} {Dep : α → α → Prop} {S A : α → Prop} + (h : AggrDerivation Checked Dep S A) : + ∀ x y, S x → Dep x y → S y ∨ A y := by + induction h with + | leaf hLeaf => + exact hLeaf.closed + | wrap _ ih => + exact ih + | join _ _ hJoin ihL ihR => + intro x y hx hDep + rcases (hJoin.subjects_iff x).mp hx with hxL | hxR + · rcases ihL x y hxL hDep with hSL | hAL + · exact .inl ((hJoin.subjects_iff y).mpr (.inl hSL)) + · rcases Classical.em _ with hAout | hAout + · exact .inr hAout + · exact .inl ((hJoin.subjects_iff y).mpr + (.inr (hJoin.discharge_left y hAL hAout))) + · rcases ihR x y hxR hDep with hSR | hAR + · exact .inl ((hJoin.subjects_iff y).mpr (.inr hSR)) + · rcases Classical.em _ with hAout | hAout + · exact .inr hAout + · exact .inl ((hJoin.subjects_iff y).mpr + (.inl (hJoin.discharge_right y hAR hAout))) + +/-- When an aggregate root has no remaining assumptions (`∀ x, ¬ A x`), its +subject set `S` is closed under the dependency relation `Dep`. -/ +theorem aggrDerivation_unconditional_closed + {Checked : α → Prop} {Dep : α → α → Prop} {S A : α → Prop} + (h : AggrDerivation Checked Dep S A) + (hEmpty : ∀ x, ¬ A x) : + ∀ x y, S x → Dep x y → S y := by + intro x y hx hDep + rcases aggrDerivation_closed_union h x y hx hDep with hS | hA + · exact hS + · exact False.elim (hEmpty y hA) + +/-- End-to-end set-discharge soundness: if the dependency relation `Dep` is +well-founded (acyclic by Blake3 content addressing) and checking `x` given +valid dependencies establishes `Valid x`, then every subject of an +unconditional aggregate root (`∀ x, ¬ A x`) is valid. -/ +theorem aggrDerivation_sound + {Checked Valid : α → Prop} {Dep : α → α → Prop} {S A : α → Prop} + (hWf : WellFounded (fun y x => Dep x y)) + (hStep : ∀ x, Checked x → (∀ y, Dep x y → Valid y) → Valid x) + (hDeriv : AggrDerivation Checked Dep S A) + (hEmpty : ∀ x, ¬ A x) : + ∀ x, S x → Valid x := by + have hClosed := aggrDerivation_unconditional_closed hDeriv hEmpty + have hChecked := aggrDerivation_subject_checked hDeriv + intro x + induction x using hWf.induction with + | h x ih => + intro hx + exact hStep x (hChecked x hx) (fun y hDep => ih y hDep (hClosed x y hx hDep)) + +/-- Cross-discharge forbids self-discharging a duplicated conditional claim +`join(q, q)` whenever `S ∩ A = ∅`: if `x ∈ A`, then `x` cannot be discharged +into `Aout = ∅`. -/ +theorem joinRel_rejects_duplicate_discharge + {S A Sout Aout : α → Prop} + (hDisjoint : ∀ x, S x → ¬ A x) + (hJoin : JoinRel S A S A Sout Aout) + {x : α} (hx : A x) : + Aout x := by + apply Classical.byContradiction + intro hNotOut + have hS : S x := hJoin.discharge_left x hx hNotOut + exact hDisjoint x hS hx end Aggr end diff --git a/Ix/Aiur/Protocol.lean b/Ix/Aiur/Protocol.lean index 66ea40f1b..ca685ff75 100644 --- a/Ix/Aiur/Protocol.lean +++ b/Ix/Aiur/Protocol.lean @@ -380,22 +380,9 @@ opaque proofToAdviceBytes : @& AiurSystem → end AiurSystem -/-- Write a `.ixes` manifest for an EXPLICIT partition — the block lists - a run actually produced (splits included) rather than a planner's - output. `shardsBlob`: per shard, a 4-byte LE block count followed by - that many 32-byte block addresses; every env block must appear in - exactly one shard. `peaksBlob`: one 8-byte LE measured prover peak - per shard in order, recorded on the manifest for schedulers. Own - sizes, foreign blocks, cross-ingress and assumption roots are - recomputed from the env's static profile; prints the manifest - summary to stderr. -/ -@[extern "rs_shard_manifest_from_partition"] -opaque shardManifestFromPartition : @& EnvHandle → - @& ByteArray → @& ByteArray → @& String → IO Unit - /-- Refine an existing `.ixes` manifest (`sourcePath`) by cutting some of its leaves into parts and write the result to `outPath` - (`ShardManifest::refine`, `crates/kernel/src/shard.rs`). Every other + (`ShardManifest::refine`, `crates/ixon/src/shard.rs`). Every other leaf keeps its block list, record, id and place in the aggregation tree; a refined leaf's place becomes a balanced subtree over its parts, part 0 keeps the leaf's id and later parts take fresh ids after @@ -416,7 +403,7 @@ opaque shardManifestRefine : @& EnvHandle → @& String → private opaque detectedRamBudgetFFI : IO UInt64 /-- The prover/execution RAM budget the Rust side detects on this machine: - 85 % of `MemAvailable` (`ix_kernel::shard::RAM_USABLE_FRAC`) — the policy + 85 % of `MemAvailable` (`ixon::shard::RAM_USABLE_FRAC`) — the policy the check batch's RAM gate and `ix prove --max-ram 0` use. `0` when `/proc/meminfo` is unreadable; callers that need a budget fail closed on it instead of running ungated. -/ diff --git a/Ix/Benchmark/Bench.lean b/Ix/Benchmark/Bench.lean index 50a90cd53..1af71cbbb 100644 --- a/Ix/Benchmark/Bench.lean +++ b/Ix/Benchmark/Bench.lean @@ -51,7 +51,7 @@ enum doc comments for details. - `normal` — default; adds warmup + running lines, plus the outlier-variance warning when moderate or severe - `verbose` — adds R² alongside the time line, always prints outlier-variance + Tukey breakdown -Example: `BENCH_VERBOSITY=v lake exe bench-shardmap` +Example: `BENCH_VERBOSITY=v lake exe bench-blake3` ## Limitations - Measures time in nanoseconds using `IO.monoNanosNow`, which is less precise than the picoseconds used in Criterion.rs diff --git a/Ix/Benchmark/Common.lean b/Ix/Benchmark/Common.lean index ab5ec2e9a..821fa6f3a 100644 --- a/Ix/Benchmark/Common.lean +++ b/Ix/Benchmark/Common.lean @@ -163,7 +163,7 @@ Overrides config values with the corresponding `BENCH_*` env vars. - `BENCH_E2E`: `"1"` to emit an end-to-end total (sum of per-stage times) at the end of each group - `BENCH_VERBOSITY`: `q` (quiet) | `v` (verbose); omit for normal -Example: `BENCH_VERBOSITY=v lake exe bench-shardmap` +Example: `BENCH_VERBOSITY=v lake exe bench-blake3` -/ def getConfigEnv (config : Config) : IO Config := do let serde : SerdeFormat := if (← IO.getEnv "BENCH_SERDE") == some "ixon" then .ixon else config.serde diff --git a/Ix/Benchmark/Results.lean b/Ix/Benchmark/Results.lean index c84f87c84..a4d42c821 100644 --- a/Ix/Benchmark/Results.lean +++ b/Ix/Benchmark/Results.lean @@ -4,19 +4,25 @@ Every measured tool reports results as one JSON object per benchmark name in a single file: - { "": { "status": "ok" | "rejected" | "oom", + { "": { "status": "ok" | "rejected" | "partial" | "conditional" + | "oom" | "crash", "": , ... } } Rows are flushed after every name, so a killed process still leaves a complete file of the rows measured so far. Tools write `ok` or `rejected`; - `oom` is merged in by the orchestrator (`ix bench run`) after an abnormal - exit — a tool never observes its own OOM kill. + `ix check-lean` also writes `partial` (every declaration passed, but the + whole artifact asked for carries no role binding) and `conditional` (it + passed only with user axioms admitted). `oom` and `crash` are merged in by + the orchestrator (`ix bench run`) after an abnormal exit — a tool never + observes its own OOM kill. Only `ok` rows become bencher data points + (`ix bench bmf`); a `rejected` row fails `ix bench run`'s gate, as a + missing row does. Exit codes are the failure channel (no stdout markers, no log grepping): 0 = every row ok; `exitUsage` = bad invocation; `exitRejected` = the - kernel rejected at least one constant (its row is on disk). Any other - exit is an infrastructure failure. The Rust mirror of this contract is - `crates/bench` (`ix-bench`); the constants must stay in lockstep. + kernel rejected at least one constant (its row is on disk); `ix check-lean` + exits 4 with a `partial` row and 5 with a `conditional` one. Any other + exit is an infrastructure failure. -/ module public import Lean.Data.Json diff --git a/Ix/CanonM.lean b/Ix/CanonM.lean index 2954b968b..34de6730f 100644 --- a/Ix/CanonM.lean +++ b/Ix/CanonM.lean @@ -22,7 +22,7 @@ module - `canonName` / `uncanonName`: Lean.Name <-> Ix.Name - `canonLevel` / `uncanonLevel`: Lean.Level <-> Ix.Level - `canonExpr` / `uncanonExpr`: Lean.Expr <-> Ix.Expr - - `canonEnv` / `uncanonEnv`: whole-environment conversion + - `canonChunk`: a slice of constants, through one memo - `compareEnvsParallel`: parallel structural equality check between environments -/ @@ -742,37 +742,6 @@ def uncanonConst : Ix.ConstantInfo → UncanonM Lean.ConstantInfo isUnsafe := v.isUnsafe } -/-- Canonicalize an entire Lean environment, sharing a single CanonState for - deduplication across all constants. -/ -def canonEnv (env : Lean.Environment) : CanonM Ix.Environment := do - let mut consts : HashMap Ix.Name Ix.ConstantInfo := {} - for (name, const) in env.constants do - let name' <- canonName name - let const' <- canonConst const - consts := consts.insert name' const' - return { consts := consts } - -/-- Uncanonicalize an Ix environment back to a map of Lean constants. -/ -def uncanonEnv (env : Ix.Environment) : UncanonM (HashMap Lean.Name Lean.ConstantInfo) := do - let mut result : HashMap Lean.Name Lean.ConstantInfo := {} - for (name, const) in env.consts do - let name' ← uncanonName name - let const' ← uncanonConst const - result := result.insert name' const' - return result - -/-- Format milliseconds as human-readable time string. -/ -def formatTime (ms : Nat) : String := - if ms < 1000 then s!"{ms}ms" - else if ms < 60000 then - let s := ms / 1000 - let frac := (ms % 1000) / 100 - s!"{s}.{frac}s" - else - let m := ms / 60000 - let s := (ms % 60000) / 1000 - s!"{m}m{s}s" - /- ## Optimized equality with pointer-pair caching -/ /-- A pair of pointers for caching equality results. -/ @@ -1000,53 +969,6 @@ def chunks (arr : Array α) (chunkSize : Nat) : Array (Array α) := Id.run do let const' ← canonConst const pure (name', const')).run' (CanonState.withCapacity chunk.size) -/-- Canonicalize an entire Lean environment in parallel (pure). - Returns just the constants map. -/ -def canonEnvParallel (env : Lean.Environment) (numWorkers : Nat := 8) - : HashMap Ix.Name Ix.ConstantInfo := - let constArr := env.constants.toList.toArray - let chunkSize := (constArr.size + numWorkers - 1) / numWorkers - let chunkArr := chunks constArr chunkSize - let tasks := chunkArr.map fun chunk => - Task.spawn fun _ => canonChunk chunk - Id.run do - let mut consts : HashMap Ix.Name Ix.ConstantInfo := - HashMap.emptyWithCapacity constArr.size - for task in tasks do - for (name, const) in task.get do - consts := consts.insert name const - consts - -/-- Process a chunk of Ix constants back to Lean (pure). - Returns just the uncanonicalized constants. -/ -def uncanonChunk (chunk : Array (Ix.Name × Ix.ConstantInfo)) - : Array (Lean.Name × Lean.ConstantInfo) := Id.run do - let mut state : UncanonState := {} - let mut results : Array (Lean.Name × Lean.ConstantInfo) := #[] - for (name, const) in chunk do - let (name', state') := StateT.run (uncanonName name) state - let (const', state'') := StateT.run (uncanonConst const) state' - state := state'' - results := results.push (name', const') - results - -/-- Uncanonicalize an Ix environment in parallel (pure). - Returns a HashMap of Lean constants. -/ -def uncanonEnvParallel (consts : HashMap Ix.Name Ix.ConstantInfo) (numWorkers : Nat := 8) - : HashMap Lean.Name Lean.ConstantInfo := - let constArr := consts.toArray - let chunkSize := (constArr.size + numWorkers - 1) / numWorkers - let chunkArr := chunks constArr chunkSize - let tasks := chunkArr.map fun chunk => - Task.spawn fun _ => uncanonChunk chunk - Id.run do - let mut result : HashMap Lean.Name Lean.ConstantInfo := - HashMap.emptyWithCapacity constArr.size - for task in tasks do - for (name, const) in task.get do - result := result.insert name const - result - end Ix.CanonM end diff --git a/Ix/Cert/Audit.lean b/Ix/Cert/Audit.lean index 788113868..7740db0a9 100644 --- a/Ix/Cert/Audit.lean +++ b/Ix/Cert/Audit.lean @@ -864,7 +864,7 @@ constructor: -- `Ix.Cert.Statement`, unchanged, because the check reads it. -- -- The driver is the pure wave fold `Ix.CompileM.compileEnvFold`, the --- one driver of `compile` and of the IO pipeline (`compileDecoratedConsts`); +-- one driver of `compile`; -- the queue drivers `compileEnvAux` (sequential, live state) and -- `compileEnvParallelAux` (waves applied in completion order) go. Only the -- driver's name changes in this body; its arguments are the same. @@ -1958,6 +1958,20 @@ fun names large n => #guard_msgs (whitespace := lax) in #print axioms Ix.Cert.Canon.UniverseBridge +/-- info: [cert-audit] canon correspondence: 22 roots; axioms used: [propext, Classical.choice, Quot.sound] -/ +#guard_msgs (whitespace := lax) in +open Ix.CompileCert.UniverseAudit (checkRoots) in +run_cmd do + checkRoots (tag := "cert-audit") "canon correspondence" + (#[`canonMCorrect, `canonChunkCorrect, `find?_canonChunk, + `numbering_nameS, `numbering_pos_nameS, `numbering_params_nameS, + `ingressConstantVal_constValS, `convertAxiom_axiomValS, `convertQuotient_quotValS, + `ingressDefinitionVal_defnValS, `convertDefinitions_singleton, `convertDefinitions_defnValS, + `ingressTheoremVal_thmValS, `convertDefinitions_thmValS, + `ingressOpaqueVal_opaqueValS, `convertDefinitions_opaqueValS, + `ingressInductiveVal_inductValS, `ingressConstructorVal_ctorValS, `ingressRecursorRule_recRuleS, + `universe_bridge, `univMeaningS, `universeBridge].map (`Ix.Cert.Canon ++ ·)) + /-! ## Component theorems: the reading chain's tables The certified reader's reading of an in-flight compiler expression survives @@ -3448,9 +3462,13 @@ against the wave's snapshot, applied one by one (`applySeq`). The tasks a wave is split into are contiguous slices of the ready blocks and a spawned task's value is its function's, so the split (`width`) and the trace (`dbg`) change nothing (`compileEnvFold_width`); the IO pipeline's -`IO.lazyPure` is `pure` of the value (`lazyPure_eq`). Blocks of one wave -being independent (the reference schedule) stays a test -(`compile-schedule-identity`, `cert-compile-parity-initstd`). +`IO.lazyPure` is `pure` of the value (`lazyPure_eq`). Wave-snapshot freshness +and disjointness across blocks in a wave are proved in `Ix.Cert.FreshSnapshot` +(feeding `Ix.Cert.RunOwnership`, which proves semantic correctness of the +wave-snapshot fold directly; that the wave-snapshot fold also equals a +one-block-at-a-time live-state fold is a property of block independence within +a wave, not proved in `Ix.Cert.Schedule`), and `Ix.Compile.ReadyCompile.reportIO` +proves by type that the IO worker-queue pipeline computes `Ix.Cert.compileReport`. The runtime projects each worker snapshot before spawning its tasks. The exact block read frame and task equality add two proof roots. The original @@ -3841,9 +3859,9 @@ the same environment (`Ix.Cert.Evidence.acceptsRef_covers`). The roles agree when every published name whose ingress name is a role's Lean name sits at the role table's address for that role (`Ix.Cert.PublishedRolesBound`, decided by `publishedRolesBound`; the reading of a role's constant is then at the table's -address, `Ix.Cert.publishedAddress_role`); `cert-statement`, -`cert-compile-parity-initstd` and `cert-roles-initstd` run it on the fixtures and -on Init+Std. `RolesBound` and +address, `Ix.Cert.publishedAddress_role`); `cert-statement` runs it on the +fixtures, while `Ix.Compile.ReadyCompile.reportIO` proves by type that the IO +worker-queue compile returns `Ix.Cert.compileReport`. `RolesBound` and `RolesBound.of_closure` are removed; `RolesBound` trusts stored name hashes and is superseded by `PublishedRolesBound`. -/ @@ -4187,14 +4205,35 @@ fun ixe => #guard_msgs (whitespace := lax) in #check @Ix.Cert.sourceAcceptsQ_addrAt -/-- info: [cert-audit] quotient addresses: 11 roots; axioms used: [propext, Classical.choice, Quot.sound] -/ +/-- info: [cert-audit] quotient addresses: 36 roots; axioms used: [propext, Classical.choice, Quot.sound] -/ #guard_msgs (whitespace := lax) in open Ix.CompileCert.UniverseAudit (checkRoots) in run_cmd do checkRoots (tag := "cert-audit") "quotient addresses" (#[`ValueModel.pullback, `publishedRolesBound_iff, `byLeanName_foldl, `roleOf_leanName, `tableBound_of, `publishedAgree_iff, `nameIsName_iff, `publishedAddress_role, `publishedAddress_id, - `reading_id, `sourceAcceptsQ_addrAt].map (`Ix.Cert ++ ·)) + `reading_id, `sourceAcceptsQ_addrAt, `LawModel.Rel, `ValueModel.comp, `LawModel.comp, + `ValueModel.pullback_rel, `substCval, `EnvSubstRel, `EnvSubstRel.find_some, + `EnvSubstRel.findProj?, `Denotes.substPullback, `Denotes.subst_const_push, + `Model.substPullback, `defn_value_substPullback, `ValueModel.substPullback, + `ValueModel.substPullback_rel, `EnvSubstModelPullback, `EnvSubstRel.toModelPullback, + `EnvSubstModelPullback.comp_envRel_valueModel, `EnvSubstPullback, + `EnvSubstModelPullback.toPullback, `EnvSubstRel.toPullback, + `EnvSubstRel.toPullback_of_laws, `EnvSubstPullback.comp_envRel_valueModel, + `evAddr_subst_key, `evAddr_subst_ingressKey, `evAddr_subst_named].map (`Ix.Cert ++ ·)) + +/-- info: [cert-audit] nominal quotient: 19 roots; axioms used: [propext, Classical.choice, Quot.sound] -/ +#guard_msgs (whitespace := lax) in +open Ix.CompileCert.UniverseAudit (checkRoots) in +run_cmd do + checkRoots (tag := "cert-audit") "nominal quotient" + (#[`checkSource_has_model, `checkGroupIntraInjective_sound, `checkHintsAgreeAt_sound, + `checkGroupFiberSafe_sound, `step_fresh_or_beqF, `alias_fresh_or_beqF, + `collapseGroup_alias_beqF, `collapseRead_fresh_or_beqF, `collapseRead_kept_beqF, + `collapseRead_substKeys_canon_beqF, `prepareSourceCollapsedAtUsing_trace, + `prepareSourceCollapsedAt_trace, `canonExprM_run, `canonValM_run, + `Converted.canon_idem, `canonDecl_idem, `Converted.quotient_eq_substKeys_canon, + `canonDecl_substKeys, `Declaration.beqF_refl].map (`Ix.LeanIngress ++ ·)) /-! ## Distinct name hashes @@ -6002,7 +6041,66 @@ fun ixe t cx envQ envA => #guard_msgs (whitespace := lax) in #check @Ix.Cert.CompileCorrect.nominal -/-- info: [cert-audit] final statement: 31 roots; axioms used: [propext, Classical.choice, Quot.sound] -/ +/-- info: @Ix.Cert.IxonCommutesCovering.nominalBridgeAt : ∀ {P : Ix.Kernel.Mode.source.Id → Prop} {ixe : Ixon.Env} + {sub : Ix.Environment} {t : Ix.Kernel.RoleTable} {envS envQ : Ix.Kernel.Env Ix.Kernel.Mode.source}, + Ix.Cert.IxonCommutesCovering ixe sub t envQ → + Ix.Cert.EnvSubstRel P (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) envS envQ → + Ix.Cert.NominalModelBridgeAt ixe sub t envS envQ -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Cert.IxonCommutesCovering.nominalBridgeAt + +/-- info: @Ix.Cert.IxonCommutesCovering.nominalPullback : ∀ {ixe : Ixon.Env} {sub : Ix.Environment} {t : Ix.Kernel.RoleTable} + {envS envQ : Ix.Kernel.Env Ix.Kernel.Mode.source}, + Ix.Cert.IxonCommutesCovering ixe sub t envQ → + Ix.Cert.EnvSubstPullback (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) envS envQ → + Ix.Cert.NominalSemanticBridgeAt ixe sub t envS envQ -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Cert.IxonCommutesCovering.nominalPullback + +/-- info: @Ix.Cert.NominalSemanticBridgeAt.of_covering : ∀ {P : Ix.Kernel.Mode.source.Id → Prop} {ixe : Ixon.Env} + {sub : Ix.Environment} {t : Ix.Kernel.RoleTable} {envS envQ : Ix.Kernel.Env Ix.Kernel.Mode.source}, + Ix.Cert.IxonCommutesCovering ixe sub t envQ → + Ix.Cert.EnvSubstRel P (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) envS envQ → + Ix.Cert.LawModel.Rel (Ix.LeanIngress.Key.subst (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t))) + envS envQ → + Ix.Cert.NominalSemanticBridgeAt ixe sub t envS envQ -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Cert.NominalSemanticBridgeAt.of_covering + +/-- info: Ix.Cert.CompileCorrect.nominalBridgeAt : Ix.Cert.CompileCorrect → + ∀ {P : Ix.Kernel.Mode.source.Id → Prop} {env : Ix.Environment} {roots : List Ix.Name} {sub : Ix.Environment} + {ixe : Ixon.Env} {failures : Ix.Cert.Failures} {t : Ix.Kernel.RoleTable} + {envS envQ : Ix.Kernel.Env Ix.Kernel.Mode.source}, + Ix.Kernel.Reader.defaultTable = Except.ok t → + Ix.Cert.SourceAcceptsQ Ix.Cert.blocksOf env roots sub ixe t envQ → + Ix.Cert.EnvSubstRel P (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) envS envQ → + Ix.Cert.compileReport env = Except.ok (ixe, failures) → + Ix.Cert.NoFailure failures sub → + Ix.Cert.PublishedRolesBound t ixe → + Ix.Cert.PublishedAgree ixe → + Ix.Cert.EvidenceCheck.EvidenceAgrees t ixe → + Ix.Cert.RoleClosure.RoleEvidenceChecks → + Ix.Cert.Published ixe sub ∧ Ix.Cert.NominalModelBridgeAt ixe sub t envS envQ -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Cert.CompileCorrect.nominalBridgeAt + +/-- info: Ix.Cert.CompileCorrect.nominalPullback : Ix.Cert.CompileCorrect → + ∀ {env : Ix.Environment} {roots : List Ix.Name} {sub : Ix.Environment} {ixe : Ixon.Env} {failures : Ix.Cert.Failures} + {t : Ix.Kernel.RoleTable} {envS envQ : Ix.Kernel.Env Ix.Kernel.Mode.source}, + Ix.Kernel.Reader.defaultTable = Except.ok t → + Ix.Cert.SourceAcceptsQ Ix.Cert.blocksOf env roots sub ixe t envQ → + Ix.Cert.EnvSubstPullback (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) envS envQ → + Ix.Cert.compileReport env = Except.ok (ixe, failures) → + Ix.Cert.NoFailure failures sub → + Ix.Cert.PublishedRolesBound t ixe → + Ix.Cert.PublishedAgree ixe → + Ix.Cert.EvidenceCheck.EvidenceAgrees t ixe → + Ix.Cert.RoleClosure.RoleEvidenceChecks → + Ix.Cert.Published ixe sub ∧ Ix.Cert.NominalSemanticBridgeAt ixe sub t envS envQ -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Cert.CompileCorrect.nominalPullback + +/-- info: [cert-audit] final statement: 40 roots; axioms used: [propext, Classical.choice, Quot.sound] -/ #guard_msgs (whitespace := lax) in open Ix.CompileCert.UniverseAudit (checkRoots) in run_cmd do @@ -6037,7 +6135,16 @@ run_cmd do `compileCorrect_iff_published, `CompileCorrect.artifactChecked, `CompileCorrect.checked, - `CompileCorrect.nominal].map + `CompileCorrect.nominal, + `CommutesStrong.substPullback, + `NominalModelBridgeAt, + `IxonCommutesCovering.nominalModelPullback, + `IxonCommutesCovering.nominalBridgeAt, + `NominalSemanticBridgeAt, + `IxonCommutesCovering.nominalPullback, + `NominalSemanticBridgeAt.of_covering, + `CompileCorrect.nominalBridgeAt, + `CompileCorrect.nominalPullback].map (`Ix.Cert ++ ·)) /-! ## The composition skeleton diff --git a/Ix/Cert/Canon.lean b/Ix/Cert/Canon.lean index b01c35c64..28b445ef3 100644 --- a/Ix/Cert/Canon.lean +++ b/Ix/Cert/Canon.lean @@ -56,6 +56,75 @@ the same smart constructors (so the same hash fields). They are defined beside the conversions, in the module `Ix.CanonM`, under this namespace, so that the memo's entries can carry their proofs. -/ +/-- `canonConst`'s action on `Lean.AxiomVal`, without its memo. -/ +def axiomValS (v : Lean.AxiomVal) : Ix.AxiomVal := + { cnst := constValS v.toConstantVal, isUnsafe := v.isUnsafe } + +/-- `canonConst`'s action on `Lean.DefinitionVal`, without its memo. -/ +def defnValS (v : Lean.DefinitionVal) : Ix.DefinitionVal := + { cnst := constValS v.toConstantVal, value := exprS v.value, hints := v.hints, + safety := v.safety, all := (v.all.map nameS).toArray } + +/-- `canonConst`'s action on `Lean.TheoremVal`, without its memo. -/ +def thmValS (v : Lean.TheoremVal) : Ix.TheoremVal := + { cnst := constValS v.toConstantVal, value := exprS v.value, all := (v.all.map nameS).toArray } + +/-- `canonConst`'s action on `Lean.OpaqueVal`, without its memo. -/ +def opaqueValS (v : Lean.OpaqueVal) : Ix.OpaqueVal := + { cnst := constValS v.toConstantVal, value := exprS v.value, isUnsafe := v.isUnsafe, + all := (v.all.map nameS).toArray } + +/-- `canonConst`'s action on `Lean.QuotVal`, without its memo. -/ +def quotValS (v : Lean.QuotVal) : Ix.QuotVal := + { cnst := constValS v.toConstantVal, kind := v.kind } + +/-- `canonConst`'s action on `Lean.InductiveVal`, without its memo. -/ +def inductValS (v : Lean.InductiveVal) : Ix.InductiveVal := + { cnst := constValS v.toConstantVal, numParams := v.numParams, numIndices := v.numIndices, + all := (v.all.map nameS).toArray, ctors := (v.ctors.map nameS).toArray, + numNested := v.numNested, isRec := v.isRec, isUnsafe := v.isUnsafe, + isReflexive := v.isReflexive } + +/-- `canonConst`'s action on `Lean.ConstructorVal`, without its memo. -/ +def ctorValS (v : Lean.ConstructorVal) : Ix.ConstructorVal := + { cnst := constValS v.toConstantVal, induct := nameS v.induct, cidx := v.cidx, + numParams := v.numParams, numFields := v.numFields, isUnsafe := v.isUnsafe } + +/-- `canonConst`'s action on `Lean.RecursorRule`, without its memo. -/ +def recRuleS (r : Lean.RecursorRule) : Ix.RecursorRule := + { ctor := nameS r.ctor, nfields := r.nfields, rhs := exprS r.rhs } + +/-- `canonConst`'s action on `Lean.RecursorVal`, without its memo. -/ +def recValS (v : Lean.RecursorVal) : Ix.RecursorVal := + { cnst := constValS v.toConstantVal, all := (v.all.map nameS).toArray, + numParams := v.numParams, numIndices := v.numIndices, numMotives := v.numMotives, + numMinors := v.numMinors, rules := (v.rules.map recRuleS).toArray, + k := v.k, isUnsafe := v.isUnsafe } + +@[simp] theorem constS_axiomInfo (v : Lean.AxiomVal) : + constS (.axiomInfo v) = .axiomInfo (axiomValS v) := rfl + +@[simp] theorem constS_defnInfo (v : Lean.DefinitionVal) : + constS (.defnInfo v) = .defnInfo (defnValS v) := rfl + +@[simp] theorem constS_thmInfo (v : Lean.TheoremVal) : + constS (.thmInfo v) = .thmInfo (thmValS v) := rfl + +@[simp] theorem constS_opaqueInfo (v : Lean.OpaqueVal) : + constS (.opaqueInfo v) = .opaqueInfo (opaqueValS v) := rfl + +@[simp] theorem constS_quotInfo (v : Lean.QuotVal) : + constS (.quotInfo v) = .quotInfo (quotValS v) := rfl + +@[simp] theorem constS_inductInfo (v : Lean.InductiveVal) : + constS (.inductInfo v) = .inductInfo (inductValS v) := rfl + +@[simp] theorem constS_ctorInfo (v : Lean.ConstructorVal) : + constS (.ctorInfo v) = .ctorInfo (ctorValS v) := rfl + +@[simp] theorem constS_recInfo (v : Lean.RecursorVal) : + constS (.recInfo v) = .recInfo (recValS v) := rfl + /-! ## 2. `CanonM` computes the specification (proved) `Ix.CanonM`'s memo entries carry the proof that their image is the @@ -134,6 +203,16 @@ theorem canonChunkCorrect : CanonChunkCorrect := by rcases chunk with ⟨l⟩ simp +theorem mem_canonChunk_iff (chunk : Array (Lean.Name × Lean.ConstantInfo)) + (p : Ix.Name × Ix.ConstantInfo) : + p ∈ canonChunk chunk ↔ ∃ nc ∈ chunk, (nameS nc.1, constS nc.2) = p := by + rw [canonChunkCorrect chunk, Array.mem_map] + +theorem mem_canonChunk_of_mem {chunk : Array (Lean.Name × Lean.ConstantInfo)} + {n : Lean.Name} {c : Lean.ConstantInfo} (h : (n, c) ∈ chunk) : + (nameS n, constS c) ∈ canonChunk chunk := + (mem_canonChunk_iff chunk (nameS n, constS c)).mpr ⟨(n, c), h, rfl⟩ + end Production /-! ## 3. Source mode reads Lean's term directly -/ @@ -376,6 +455,303 @@ theorem numbering_nameS (names : Array Lean.Name) (large : Bool) (n : Lean.Name) rw [key] rfl +theorem list_find?_canon (l : List (Lean.Name × Lean.ConstantInfo)) (n : Lean.Name) : + (l.map fun (m, c) => (nameS m, constS c)).find? (fun p => Ix.Name.beqRaw p.1 (nameS n)) = + (l.find? (fun p => p.1 == n)).map (fun (m, c) => (nameS m, constS c)) := by + induction l with + | nil => rfl + | cons hd tl ih => + rcases hd with ⟨m, c⟩ + simp only [List.map_cons, List.find?_cons, beqRaw_nameS m n] + cases m == n <;> simp [ih] + +theorem find?_canonChunk (chunk : Array (Lean.Name × Lean.ConstantInfo)) (n : Lean.Name) : + (Ix.CanonM.canonChunk chunk).find? (fun p => Ix.Name.beqRaw p.1 (nameS n)) = + (chunk.find? (fun p => p.1 == n)).map (fun (m, c) => (nameS m, constS c)) := by + rcases chunk with ⟨l⟩ + rw [canonChunkCorrect] + simpa using list_find?_canon l n + +/-! ### Direct Lean constant and declaration-group readers -/ + +/-- `Numbering.params`, over Lean's level-parameter names. -/ +def leanParams (lps : List Lean.Name) (large : Bool := false) : List LevelParam := + lps.map (leanPos lps.toArray large) + +theorem numbering_pos_nameS (lps : List Lean.Name) (large : Bool) : + (Ix.LeanIngress.Numbering.pos ⟨(lps.map nameS).toArray, large⟩ ∘ nameS) = + leanPos lps.toArray large := by + have h : (lps.map nameS).toArray = lps.toArray.map nameS := by simp + rw [h] + funext n + exact numbering_nameS lps.toArray large n + +theorem numbering_params_nameS (lps : List Lean.Name) (large : Bool := false) : + (Ix.LeanIngress.Numbering.mk (lps.map nameS).toArray large).params = + leanParams lps large := by + unfold Ix.LeanIngress.Numbering.params leanParams + show ((lps.map nameS).map (Ix.LeanIngress.Numbering.pos ⟨(lps.map nameS).toArray, large⟩)) = + lps.map (leanPos lps.toArray large) + rw [List.map_map, numbering_pos_nameS] + +@[simp] theorem numbering_beq_self (σ : Ix.LeanIngress.Numbering) : (σ == σ) = true := by + show (σ.large == σ.large && σ.names.isEqv σ.names Ix.Name.beqRaw) = true + rw [Bool.and_eq_true, Array.isEqv_iff_rel] + exact ⟨by cases σ.large <;> rfl, rfl, fun _ _ => Ix.Name.beqRaw_iff.mpr rfl⟩ + +theorem exprsOf_singleton (σ : Ix.LeanIngress.Numbering) (e : Ix.Expr) : + Ix.LeanIngress.exprsOf #[(σ, e)] = + match Ix.LeanIngress.ingressExpr σ.pos e with + | .ok k => .ok #[k] + | .error err => .error (.malformed s!"source term: {err}") := by + unfold Ix.LeanIngress.exprsOf + simp only [Ix.LeanIngress.ingressExprs, List.forIn_toArray, List.forIn_cons, List.forIn_nil, + List.mapM_cons, List.mapM_nil, bind, Except.bind, pure, Except.pure] + cases Ix.LeanIngress.ingressExpr σ.pos e with + | error err => rfl + | ok k => + simp only [Ix.LeanIngress.nameExprs_eq] + rfl + +theorem exprsOf_pair (σ : Ix.LeanIngress.Numbering) (e₁ e₂ : Ix.Expr) : + Ix.LeanIngress.exprsOf #[(σ, e₁), (σ, e₂)] = + match do + let k₁ ← Ix.LeanIngress.ingressExpr σ.pos e₁ + let k₂ ← Ix.LeanIngress.ingressExpr σ.pos e₂ + pure #[k₁, k₂] with + | .ok ks => .ok ks + | .error err => .error (.malformed s!"source term: {err}") := by + unfold Ix.LeanIngress.exprsOf + simp only [Ix.LeanIngress.ingressExprs, List.forIn_toArray, List.forIn_cons, List.forIn_nil, + numbering_beq_self, ↓reduceIte, Array.toList_push, List.cons_append, List.nil_append, + List.mapM_cons, List.mapM_nil, bind, Except.bind, pure, Except.pure] + cases Ix.LeanIngress.ingressExpr σ.pos e₁ with + | error err => rfl + | ok k₁ => + cases Ix.LeanIngress.ingressExpr σ.pos e₂ with + | error err => rfl + | ok k₂ => + simp only [Ix.LeanIngress.nameExprs_eq] + rfl + +@[simp] theorem constValS_levelParams (cv : Lean.ConstantVal) : + (constValS cv).levelParams = (cv.levelParams.map nameS).toArray := rfl + +/-- Source-mode reading of an `Ix.ConstantVal`. -/ +def ingressConstantVal (cv : Ix.ConstantVal) (large : Bool := false) : + Except ExprError (Ix.Kernel.ConstantVal .source) := do + let σ : Ix.LeanIngress.Numbering := ⟨cv.levelParams, large⟩ + let ty ← Ix.LeanIngress.ingressExpr σ.pos cv.type + pure ⟨Ix.LeanIngress.ingressKey cv.name, σ.params, ty⟩ + +/-- Direct Source-mode reading of a `Lean.ConstantVal`, without `Ix.ConstantVal`. -/ +def leanConstantVal (cv : Lean.ConstantVal) (large : Bool := false) : + Except ExprError (Ix.Kernel.ConstantVal .source) := do + let ty ← leanExpr (leanPos cv.levelParams.toArray large) cv.type + pure ⟨leanKey cv.name, leanParams cv.levelParams large, ty⟩ + +theorem ingressConstantVal_constValS (cv : Lean.ConstantVal) (large : Bool := false) : + ingressConstantVal (constValS cv) large = leanConstantVal cv large := by + simp only [ingressConstantVal, leanConstantVal, constValS, ingressKey_nameS, + numbering_params_nameS, ingressExpr_exprS, numbering_pos_nameS] + +/-- Direct Source-mode conversion of a `Lean.AxiomVal`. -/ +def leanAxiomVal (v : Lean.AxiomVal) : + Except Ix.Kernel.Reader.ReadError Ix.LeanIngress.Converted := do + if v.isUnsafe then throw (.declined "unsafe axiom") + match leanConstantVal v.toConstantVal false with + | .ok cv => pure (.axiom cv) + | .error err => throw (.malformed s!"source term: {err}") + +theorem convertAxiom_axiomValS (v : Lean.AxiomVal) : + Ix.LeanIngress.convert (.axiom (axiomValS v)) = leanAxiomVal v := by + unfold Ix.LeanIngress.convert leanAxiomVal axiomValS + rw [← ingressConstantVal_constValS v.toConstantVal false] + unfold ingressConstantVal + cases v.isUnsafe + · simp only [Bool.false_eq_true, ↓reduceIte, exprsOf_singleton, bind, Except.bind, pure, + Except.pure] + cases Ix.LeanIngress.ingressExpr + (Ix.LeanIngress.Numbering.pos ⟨(constValS v.toConstantVal).levelParams, false⟩) + (constValS v.toConstantVal).type <;> rfl + · rfl + +/-- Direct Source-mode conversion of a `Lean.QuotVal`. -/ +def leanQuotVal (v : Lean.QuotVal) : + Except Ix.Kernel.Reader.ReadError Ix.LeanIngress.Converted := + match leanConstantVal v.toConstantVal false with + | .ok cv => .ok (.quot (Ix.LeanIngress.quotKindOf v.kind) cv) + | .error err => .error (.malformed s!"source term: {err}") + +theorem convertQuotient_quotValS (v : Lean.QuotVal) : + Ix.LeanIngress.convert (.quot (quotValS v)) = leanQuotVal v := by + unfold Ix.LeanIngress.convert leanQuotVal quotValS + rw [← ingressConstantVal_constValS v.toConstantVal false] + unfold ingressConstantVal + simp only [exprsOf_singleton, bind, Except.bind, pure, Except.pure] + cases Ix.LeanIngress.ingressExpr + (Ix.LeanIngress.Numbering.pos ⟨(constValS v.toConstantVal).levelParams, false⟩) + (constValS v.toConstantVal).type <;> rfl + +/-- Source-mode reading of an `Ix.DefinitionVal` as a `DefMember`. -/ +def ingressDefinitionVal (v : Ix.DefinitionVal) : + Except ExprError Ix.LeanIngress.DefMember := do + let cv ← ingressConstantVal v.cnst false + let val ← Ix.LeanIngress.ingressExpr (Ix.LeanIngress.Numbering.pos ⟨v.cnst.levelParams, false⟩) v.value + pure ⟨cv, val, .defn, Ix.LeanIngress.safetyOf v.safety, Ix.LeanIngress.hintOf v.hints⟩ + +/-- Direct Source-mode reading of a `Lean.DefinitionVal` as a `DefMember`. -/ +def leanDefinitionVal (v : Lean.DefinitionVal) : + Except ExprError Ix.LeanIngress.DefMember := do + let cv ← leanConstantVal v.toConstantVal false + let val ← leanExpr (leanPos v.levelParams.toArray false) v.value + pure ⟨cv, val, .defn, Ix.LeanIngress.safetyOf v.safety, Ix.LeanIngress.hintOf v.hints⟩ + +theorem ingressDefinitionVal_defnValS (v : Lean.DefinitionVal) : + ingressDefinitionVal (defnValS v) = leanDefinitionVal v := by + simp only [ingressDefinitionVal, leanDefinitionVal, defnValS, ingressConstantVal_constValS, + constValS_levelParams, ingressExpr_exprS, numbering_pos_nameS] + +set_option linter.unusedSimpArgs false in +theorem convertDefinitions_singleton (ci : Ix.ConstantInfo) + (kind : Ix.DefKind) (safety : Ix.DefinitionSafety) + (hint : Ix.Kernel.ReducibilityHint) (val : Ix.Expr) + (hs : Ix.LeanIngress.definitionShape ci = some (kind, safety, hint)) + (hv : Ix.LeanIngress.definitionValue ci = some val) : + Ix.LeanIngress.convertDefinitions #[ci] = + match (do + let cv ← ingressConstantVal ci.getCnst false + let v' ← Ix.LeanIngress.ingressExpr + (Ix.LeanIngress.Numbering.pos ⟨ci.getCnst.levelParams, false⟩) val + pure (Ix.LeanIngress.DefMember.mk cv v' kind safety hint) : + Except ExprError Ix.LeanIngress.DefMember) with + | .ok m => .ok (.definitions #[m]) + | .error err => .error (.malformed s!"source term: {err}") := by + simp [Ix.LeanIngress.convertDefinitions, hs, hv, ingressConstantVal, exprsOf_pair] + cases Ix.LeanIngress.ingressExpr + (Ix.LeanIngress.Numbering.pos ⟨ci.getCnst.levelParams, false⟩) ci.getCnst.type with + | error err => simp [bind, Except.bind, pure, Except.pure, Functor.map, Except.map] + | ok k₁ => + cases Ix.LeanIngress.ingressExpr + (Ix.LeanIngress.Numbering.pos ⟨ci.getCnst.levelParams, false⟩) val with + | error err => simp [bind, Except.bind, pure, Except.pure, Functor.map, Except.map] + | ok k₂ => simp [bind, Except.bind, pure, Except.pure, Functor.map, Except.map] + +theorem convertDefinitions_defnValS (v : Lean.DefinitionVal) : + Ix.LeanIngress.convertDefinitions #[.defnInfo (defnValS v)] = + match leanDefinitionVal v with + | .ok m => .ok (.definitions #[m]) + | .error err => .error (.malformed s!"source term: {err}") := by + rw [← ingressDefinitionVal_defnValS] + exact convertDefinitions_singleton (.defnInfo (defnValS v)) _ _ _ _ rfl rfl + +/-- Source-mode reading of an `Ix.TheoremVal` as a `DefMember`. -/ +def ingressTheoremVal (v : Ix.TheoremVal) : + Except ExprError Ix.LeanIngress.DefMember := do + let cv ← ingressConstantVal v.cnst false + let val ← Ix.LeanIngress.ingressExpr (Ix.LeanIngress.Numbering.pos ⟨v.cnst.levelParams, false⟩) v.value + pure ⟨cv, val, .thm, .safe, .opaque⟩ + +/-- Direct Source-mode reading of a `Lean.TheoremVal` as a `DefMember`. -/ +def leanTheoremVal (v : Lean.TheoremVal) : + Except ExprError Ix.LeanIngress.DefMember := do + let cv ← leanConstantVal v.toConstantVal false + let val ← leanExpr (leanPos v.levelParams.toArray false) v.value + pure ⟨cv, val, .thm, .safe, .opaque⟩ + +theorem ingressTheoremVal_thmValS (v : Lean.TheoremVal) : + ingressTheoremVal (thmValS v) = leanTheoremVal v := by + simp only [ingressTheoremVal, leanTheoremVal, thmValS, ingressConstantVal_constValS, + constValS_levelParams, ingressExpr_exprS, numbering_pos_nameS] + +theorem convertDefinitions_thmValS (v : Lean.TheoremVal) : + Ix.LeanIngress.convertDefinitions #[.thmInfo (thmValS v)] = + match leanTheoremVal v with + | .ok m => .ok (.definitions #[m]) + | .error err => .error (.malformed s!"source term: {err}") := by + rw [← ingressTheoremVal_thmValS] + exact convertDefinitions_singleton (.thmInfo (thmValS v)) _ _ _ _ rfl rfl + +/-- Source-mode reading of an `Ix.OpaqueVal` as a `DefMember`. -/ +def ingressOpaqueVal (v : Ix.OpaqueVal) : + Except ExprError Ix.LeanIngress.DefMember := do + let cv ← ingressConstantVal v.cnst false + let val ← Ix.LeanIngress.ingressExpr (Ix.LeanIngress.Numbering.pos ⟨v.cnst.levelParams, false⟩) v.value + pure ⟨cv, val, .opaq, if v.isUnsafe then .unsaf else .safe, .opaque⟩ + +/-- Direct Source-mode reading of a `Lean.OpaqueVal` as a `DefMember`. -/ +def leanOpaqueVal (v : Lean.OpaqueVal) : + Except ExprError Ix.LeanIngress.DefMember := do + let cv ← leanConstantVal v.toConstantVal false + let val ← leanExpr (leanPos v.levelParams.toArray false) v.value + pure ⟨cv, val, .opaq, if v.isUnsafe then .unsaf else .safe, .opaque⟩ + +theorem ingressOpaqueVal_opaqueValS (v : Lean.OpaqueVal) : + ingressOpaqueVal (opaqueValS v) = leanOpaqueVal v := by + simp only [ingressOpaqueVal, leanOpaqueVal, opaqueValS, ingressConstantVal_constValS, + constValS_levelParams, ingressExpr_exprS, numbering_pos_nameS] + rfl + +theorem convertDefinitions_opaqueValS (v : Lean.OpaqueVal) : + Ix.LeanIngress.convertDefinitions #[.opaqueInfo (opaqueValS v)] = + match leanOpaqueVal v with + | .ok m => .ok (.definitions #[m]) + | .error err => .error (.malformed s!"source term: {err}") := by + rw [← ingressOpaqueVal_opaqueValS] + exact convertDefinitions_singleton (.opaqueInfo (opaqueValS v)) _ _ _ _ rfl rfl + +/-- Source-mode reading of an `Ix.InductiveVal`'s header and counts. -/ +def ingressInductiveVal (v : Ix.InductiveVal) : + Except ExprError (Ix.Kernel.ConstantVal .source × Nat × Nat) := do + let cv ← ingressConstantVal v.cnst false + pure (cv, v.numParams, v.numIndices) + +/-- Direct Source-mode reading of a `Lean.InductiveVal`'s header and counts. -/ +def leanInductiveVal (v : Lean.InductiveVal) : + Except ExprError (Ix.Kernel.ConstantVal .source × Nat × Nat) := do + let cv ← leanConstantVal v.toConstantVal false + pure (cv, v.numParams, v.numIndices) + +theorem ingressInductiveVal_inductValS (v : Lean.InductiveVal) : + ingressInductiveVal (inductValS v) = leanInductiveVal v := by + simp only [ingressInductiveVal, leanInductiveVal, inductValS, ingressConstantVal_constValS] + +/-- Source-mode reading of an `Ix.ConstructorVal`. -/ +def ingressConstructorVal (c : Ix.ConstructorVal) : + Except ExprError (Ix.Kernel.ConstantVal .source × Nat × Nat) := do + let cv ← ingressConstantVal c.cnst false + pure (cv, c.numParams, c.numFields) + +/-- Direct Source-mode reading of a `Lean.ConstructorVal`. -/ +def leanConstructorVal (c : Lean.ConstructorVal) : + Except ExprError (Ix.Kernel.ConstantVal .source × Nat × Nat) := do + let cv ← leanConstantVal c.toConstantVal false + pure (cv, c.numParams, c.numFields) + +theorem ingressConstructorVal_ctorValS (c : Lean.ConstructorVal) : + ingressConstructorVal (ctorValS c) = leanConstructorVal c := by + simp only [ingressConstructorVal, leanConstructorVal, ctorValS, ingressConstantVal_constValS] + +/-- Source-mode reading of an `Ix.RecursorRule` under level-parameter numbering `σ`. -/ +def ingressRecursorRule (σ : Ix.LeanIngress.Numbering) (r : Ix.RecursorRule) : + Except ExprError (Ix.Kernel.RecRule .source) := do + let rhs ← Ix.LeanIngress.ingressExpr σ.pos r.rhs + pure ⟨Ix.LeanIngress.ingressKey r.ctor, r.nfields, 0, .inert, rhs, false, false, false⟩ + +/-- Direct Source-mode reading of a `Lean.RecursorRule` under level parameters `lps` and +large-eliminator flag `large`. -/ +def leanRecursorRule (lps : List Lean.Name) (large : Bool) (r : Lean.RecursorRule) : + Except ExprError (Ix.Kernel.RecRule .source) := do + let rhs ← leanExpr (leanPos lps.toArray large) r.rhs + pure ⟨leanKey r.ctor, r.nfields, 0, .inert, rhs, false, false, false⟩ + +theorem ingressRecursorRule_recRuleS (lps : List Lean.Name) (large : Bool) + (r : Lean.RecursorRule) : + ingressRecursorRule ⟨(lps.map nameS).toArray, large⟩ (recRuleS r) = + leanRecursorRule lps large r := by + simp only [ingressRecursorRule, leanRecursorRule, recRuleS, ingressExpr_exprS, + numbering_pos_nameS, ingressKey_nameS] + /-! ## 5. Universes: composing with canonical universe evaluation The Ixon side is the canonical universe semantics: `Ix.CompileCert.univEval` is the value of a wire diff --git a/Ix/Cert/Compile.lean b/Ix/Cert/Compile.lean index 135d963da..5ee4b0a82 100644 --- a/Ix/Cert/Compile.lean +++ b/Ix/Cert/Compile.lean @@ -51,13 +51,6 @@ for both compilation and checking; `compileReport_eq_shared` proves that sharing this work preserves the function. These are checked `csimp` replacements, not a separate compiler. -The optional streamed pipeline (`--streamed`, -`Ix.CompileM.compileLeanInputValidated`) retains a different front end: it -canonicalizes theorem and opaque bodies on demand through a hybrid -environment, and takes sharing limits and task widths from `DriverOptions`. -Its driver is the same `compileEnvFold`; its front end is tested against the -certified path and remains an uncertified comparison option. - **Failures.** The compile records what it leaves out instead of dropping it: `compileReport` returns, with the environment, every constant the ground filter removed, with its named refusal (`Ix.GroundError.refusal`, the text @@ -76,9 +69,12 @@ inconsistency is an error, with no artifact. The statements' coverage and authenticity (`CompileCovers`, `CompileAuthentic`) therefore hold by construction, without an invariant of the driver's loop. -`Tests.Ix.Cert.Statement` compares the serialized output of the two on the -Source-mode fixtures (`cert-statement`) and on Init+Std -(`cert-compile-parity-initstd`, in the merge queue). -/ +`Tests.Ix.Cert.Statement` compares the serialized output of `Ix.Cert.compile` +and the certified IO pipeline on the Source-mode fixtures (`cert-statement`), +while `Ix.Compile.ReadyCompile.reportIO` proves by type that the IO +worker-queue compile returns `Ix.Cert.compileReport` (`CompileCorrect` proves +semantic correctness of the wave-snapshot fold directly; equivalence with a +one-block-at-a-time live-state fold is not proved in `Ix.Cert.Schedule`). -/ public section @@ -117,8 +113,9 @@ private theorem proliferate_empty (errors : Ix.Map Ix.Name Ix.GroundError) The failure branch retains the original propagation and filtering order. -/ @[expose] def groundSplitLazy (env : Ix.Environment) : Ix.Map Ix.Name (Ix.Set Ix.Name) × Ix.Map Ix.Name Ix.GroundError := - let outRefsTask := Task.spawn fun _ => Ix.GraphM.envSerial env - let immediate := Ix.Compile.FrontEnd.immediate env + let constsArr := env.consts.toList.toArray + let outRefsTask := Task.spawn fun _ => Ix.Compile.FrontEnd.graphWith env constsArr + let immediate := Ix.Compile.FrontEnd.immediateWith env constsArr let outRefs := outRefsTask.get let ungrounded := if immediate.keysArray.isEmpty then immediate else let inRefs : Ix.Map Ix.Name (Ix.Set Ix.Name) := outRefs.fold (init := {}) fun m n refs => @@ -135,11 +132,16 @@ The failure branch retains the original propagation and filtering order. -/ @[csimp] theorem groundSplit_eq_lazy : @groundSplit = @groundSplitLazy := by funext env + have hGraph : (Task.spawn fun _ => Ix.Compile.FrontEnd.graphWith env env.consts.toList.toArray).get = + Ix.GraphM.envSerial env := by + change Ix.Compile.FrontEnd.graph env = Ix.GraphM.envSerial env + exact Ix.Compile.FrontEnd.graph_eq.symm ▸ rfl + have hImm : Ix.Compile.FrontEnd.immediateWith env env.consts.toList.toArray = + Ix.Compile.FrontEnd.immediate env := rfl by_cases h : (Ix.Compile.FrontEnd.immediate env).keysArray.isEmpty = true - · simp only [groundSplit, groundSplitLazy, Ix.Compile.FrontEnd.ground_eq, + · simp only [groundSplit, groundSplitLazy, hGraph, hImm, Ix.Compile.FrontEnd.ground_eq, Ix.Compile.FrontEnd.ground, h, ite_true, proliferate_empty _ _ h] - rfl - · simp only [groundSplit, groundSplitLazy, Ix.Compile.FrontEnd.ground_eq, + · simp only [groundSplit, groundSplitLazy, hGraph, hImm, Ix.Compile.FrontEnd.ground_eq, Ix.Compile.FrontEnd.ground, h] rfl @@ -164,8 +166,7 @@ structure Failures where error (`Ix.CompileM.CompileEnv.ungrounded`). -/ blocks : Std.HashMap Ix.Name String := {} -/-- Every failure in one map, as the IO pipeline records them -(`Ix.CompileM.LeanPipelineOut.cenv`'s `ungrounded`). -/ +/-- Every failure in one map (`CompileEnv.ungrounded`). -/ @[expose] def Failures.toMap (f : Failures) : Std.HashMap Ix.Name String := f.blocks.fold (init := f.ungrounded) fun m n e => if m.contains n then m else m.insert n e @@ -628,10 +629,12 @@ statements read names structurally (`Ix.nameCompareExact`). -/ | some m => if nameCompareExactEqFast m n then acc else (acc.1, some (m, n)) | none => (acc.1.insert n.getHash n, none) -@[expose] def nameHashCollisionFast (env : Ix.Environment) (ixe : Ixon.Env) : Option (Ix.Name × Ix.Name) := +@[expose] def nameHashCollisionFast (env : Ix.Environment) (ixe : Ixon.Env) + (envKeys : List Ix.Name := env.consts.keys) + (namedKeys : List Ix.Name := ixe.named.keys) : Option (Ix.Name × Ix.Name) := let init : Std.HashMap Address Ix.Name × Option (Ix.Name × Ix.Name) := (Std.HashMap.emptyWithCapacity (env.consts.size + ixe.named.size), none) - (ixe.named.keys.foldl nameHashStep (env.consts.keys.foldl nameHashStep init)).2 + (namedKeys.foldl nameHashStep (envKeys.foldl nameHashStep init)).2 private theorem nameHashStep_foldl_eq (l : List Ix.Name) (m₁ m₂ : Std.HashMap Address Ix.Name) (o : Option (Ix.Name × Ix.Name)) @@ -818,6 +821,132 @@ expressions (`storedRefs`). -/ | some (_, owner) => owner | none => a +/-- Whether the uncached decoder's first TagN can introduce a projection record +(`cPrj`, `rPrj`, `iPrj`, or `dPrj`). -/ +@[expose] def projectionRecordHeader (buf : ByteArray) (off : Nat) : Bool := + match (Ixon.getTagN 4).run { idx := off, bytes := buf } with + | .ok tag _ => + tag.flag == Ixon.Constant.FLAG && + (tag.value == 4 || tag.value == 5 || tag.value == 6 || tag.value == 7) + | .error _ _ => false + +/-- `recordBlock` with a header filter that returns `a` without scanning a +non-projection record's expressions (`recordBlockFast_eq`). -/ +@[expose] def recordBlockFast (ixe : Ixon.Env) (a : Address) : Address := + match ixe.consts.get? a with + | some lc => + match lc.cache with + | some _ => recordBlock ixe a + | none => + match projectionRecordHeader lc.buf lc.off with + | true => recordBlock ixe a + | false => a + | none => a + +private theorem bind_ok {α β : Type} {action : Ixon.GetM α} {next : α → Ixon.GetM β} + {start finish : Ixon.GetState} {out : β} (h : (action >>= next) start = .ok out finish) : + ∃ middle state, action start = .ok middle state ∧ next middle state = .ok out finish := by + change EStateM.bind action next start = _ at h + cases ran : action start with + | error e state => simp [EStateM.bind, ran] at h + | ok middle state => exact ⟨middle, state, rfl, by simpa [EStateM.bind, ran] using h⟩ + +private theorem skipInfo_some_header {start finish : Ixon.GetState} {b : Address} + (h : RecordRefs.skipInfo start = .ok (some b) finish) : + projectionRecordHeader start.bytes start.idx = true := by + unfold RecordRefs.skipInfo at h + obtain ⟨tag, state, ht, h⟩ := bind_ok h + have hinfo : + tag.flag = Ixon.Constant.FLAG ∧ + (tag.value = 4 ∨ tag.value = 5 ∨ tag.value = 6 ∨ tag.value = 7) := by + split at h + · obtain ⟨_, _, _, h⟩ := bind_ok h + cases h + · split at h + · rename_i flag + refine ⟨beq_iff_eq.mp flag, ?_⟩ + by_cases v0 : tag.value = 0 + · simp only [v0] at h; obtain ⟨_, _, _, h⟩ := bind_ok h; cases h + by_cases v1 : tag.value = 1 + · simp only [v1] at h; obtain ⟨_, _, _, h⟩ := bind_ok h; cases h + by_cases v2 : tag.value = 2 + · simp only [v2] at h; obtain ⟨_, _, _, h⟩ := bind_ok h; cases h + by_cases v3 : tag.value = 3 + · simp only [v3] at h; obtain ⟨_, _, _, h⟩ := bind_ok h; cases h + by_cases v4 : tag.value = 4 + · exact Or.inl v4 + by_cases v5 : tag.value = 5 + · exact Or.inr (Or.inl v5) + by_cases v6 : tag.value = 6 + · exact Or.inr (Or.inr (Or.inl v6)) + by_cases v7 : tag.value = 7 + · exact Or.inr (Or.inr (Or.inr v7)) + · simp only at h + cases h + · cases h + rcases hinfo with ⟨hf, hv | hv | hv | hv⟩ <;> + simp [projectionRecordHeader, EStateM.run, ht, hf, hv] + +private theorem getRefs_some_header {buf : ByteArray} {off maxUnivNodes : Nat} + {refs : Array Address} {b : Address} {finish : Ixon.GetState} + (h : (RecordRefs.getRefs maxUnivNodes).run { idx := off, bytes := buf } = .ok (refs, some b) finish) : + projectionRecordHeader buf off = true := by + unfold RecordRefs.getRefs EStateM.run at h + obtain ⟨block, state, hinfo, h⟩ := bind_ok h + obtain ⟨_, _, _, h⟩ := bind_ok h + obtain ⟨_, _, _, h⟩ := bind_ok h + obtain ⟨_, _, _, h⟩ := bind_ok h + obtain ⟨_, _, _, h⟩ := bind_ok h + obtain ⟨_, _, _, h⟩ := bind_ok h + obtain ⟨_, _, _, h⟩ := bind_ok h + simp only [pure, EStateM.pure, EStateM.Result.ok.injEq, Prod.mk.injEq] at h + obtain ⟨⟨_, rfl⟩, _⟩ := h + exact skipInfo_some_header hinfo + +theorem recordBlockFast_eq (ixe : Ixon.Env) (a : Address) : + recordBlockFast ixe a = recordBlock ixe a := by + unfold recordBlockFast + cases hget : ixe.consts.get? a with + | none => + unfold recordBlock storedRefs + rw [hget] + rfl + | some lc => + dsimp only + cases hcache : lc.cache with + | some c => rfl + | none => + dsimp only + cases hhdr : projectionRecordHeader lc.buf lc.off with + | true => rfl + | false => + dsimp only + unfold recordBlock storedRefs RecordRefs.lazyRefs + rw [hget] + dsimp only [Option.bind] + rw [hcache] + dsimp only + cases hrefs : RecordRefs.refsAt lc.buf lc.off lc.len with + | none => rfl + | some rb => + rcases rb with ⟨refs, block⟩ + cases block with + | none => rfl + | some b => + unfold RecordRefs.refsAt at hrefs + split at hrefs + · cases hrun : (RecordRefs.getRefs Ixon.loadMaxUnivNodes).run { idx := lc.off, bytes := lc.buf } with + | error _ _ => simp [hrun] at hrefs + | ok r s => + simp only [hrun] at hrefs + split at hrefs + · simp only [Option.some.injEq] at hrefs + subst r + rw [getRefs_some_header hrun] at hhdr + cases hhdr + · cases hrefs + · cases hrefs + /-- A name's published record and that record's block (`Ix.Cert.ClosureLayout.nameRecords`). -/ @[expose] def publishedRecords (ixe : Ixon.Env) (n : Ix.Name) : List Address := @@ -914,14 +1043,44 @@ of that family (`Ix.Cert.RecursorsOwned`). -/ (parMap (fun (e : Ix.Name × Ixon.Named) => unownedRecursorOf env ixe idx cands e.1 e.2) ixe.named.toList.toArray).findSome? id +/-- `unownedRecursorOf` using `recordBlockFast` (`unownedRecursorOfFast_eq`). -/ +@[expose] def unownedRecursorOfFast (env : Ix.Environment) (ixe : Ixon.Env) + (idx : Ix.Kernel.Reader.RecIndex) (cands : Std.HashMap Address (Array Ix.Name)) + (x : Ix.Name) (named : Ixon.Named) : + Option (Address × Ix.Name × Option (Ix.Kernel.ConstRef Address)) := + if env.consts.contains x then + let blk := recordBlockFast ixe named.addr + match idx.blocks[blk]? with + | some shape => + match familyFirst? env x with + | some first => + (shape.recs.find? fun r => !familyOwns env ixe cands first r.block).map fun r => + (blk, x, some r) + | none => some (blk, x, none) + | none => none + else none + +theorem unownedRecursorOfFast_eq (env : Ix.Environment) (ixe : Ixon.Env) + (idx : Ix.Kernel.Reader.RecIndex) (cands : Std.HashMap Address (Array Ix.Name)) + (x : Ix.Name) (named : Ixon.Named) : + unownedRecursorOfFast env ixe idx cands x named = + unownedRecursorOf env ixe idx cands x named := by + simp only [unownedRecursorOfFast, unownedRecursorOf, recordBlockFast_eq] + /-- `unownedRecursor` over precomputed `storedRecursorRecords ixe`. -/ @[expose] def unownedRecursorWith (env : Ix.Environment) (ixe : Ixon.Env) (recs : Array (Address × Ixon.Constant)) - (cands : Std.HashMap Address (Array Ix.Name) := familyRecursors env) : + (cands : Std.HashMap Address (Array Ix.Name) := familyRecursors env) + (namedArr : Array (Ix.Name × Ixon.Named) := ixe.named.toList.toArray) : Option (Address × Ix.Name × Option (Ix.Kernel.ConstRef Address)) := let idx := Ix.Kernel.Reader.buildIndex (storedRecord ixe) recs - (parMap (fun (e : Ix.Name × Ixon.Named) => unownedRecursorOf env ixe idx cands e.1 e.2) - ixe.named.toList.toArray).findSome? id + (parMap (fun (e : Ix.Name × Ixon.Named) => unownedRecursorOfFast env ixe idx cands e.1 e.2) + namedArr).findSome? id + +theorem unownedRecursorWith_eq (env : Ix.Environment) (ixe : Ixon.Env) : + unownedRecursorWith env ixe (storedRecursorRecords ixe) (familyRecursors env) = + unownedRecursor env ixe := by + simp only [unownedRecursorWith, unownedRecursor, unownedRecursorOfFast_eq] /-- The kind of the specification of the role `r` (`(Ix.Kernel.Role.spec r).kind`, `Ix.Cert.roleKind_eq`), without building the specification's expressions. -/ @@ -987,6 +1146,19 @@ axiom role's address an axiom (`Ix.Cert.RoleRecordKinds`). -/ | none => none else none +/-- `roleRecordKind` over precomputed `ixe.consts.toList`. -/ +@[expose] def roleRecordKindWith (ixe : Ixon.Env) + (constsList : List (Address × Ixon.LazyConstant) := ixe.consts.toList) : + Option (Ix.Kernel.Role × Address) := + let bound := roleTableBound + let addrs := roleAddrs bound + constsList.findSome? fun (p, lc) => + if addrs.contains p then + match lc.get? with + | some c => (roleRecordKindAt bound p c).map fun r => (r, p) + | none => none + else none + /-- The roles a literal is checked against: the `Nat` basis and the string support (the roles of `Ix.Kernel.Reader.strLitSupportNames`). -/ @[expose] def literalSupportRoles : List Ix.Kernel.Role := @@ -1404,14 +1576,15 @@ copies; quotient Source reads it by its name. -/ /-- `recursorShapeError` over precomputed `storedRecursorRecords ixe`. -/ @[expose] def recursorShapeErrorWith (env : Ix.Environment) (ixe : Ixon.Env) - (recs : Array (Address × Ixon.Constant)) : Option RecursorShapeError := + (recs : Array (Address × Ixon.Constant)) + (constsArr : Array (Ix.Name × Ix.ConstantInfo) := env.consts.toArray) : Option RecursorShapeError := match recursorSlotErrorWith ixe recs with | some e => some e | none => (parMap (fun (e : Ix.Name × Ix.ConstantInfo) => match e.2 with | .recInfo rv => recursorNameError env ixe e.1 rv - | _ => none) env.consts.toArray).findSome? id + | _ => none) constsArr).findSome? id /-- A stored record's definition projection: the member it projects (its block's address and its index) and the record's address. -/ @@ -1805,10 +1978,11 @@ structure CheckTasks where /-- `uncovered` computed on tasks (`parMap`). -/ -@[expose] def uncoveredPar (env : Ix.Environment) (ixe : Ixon.Env) (failures : Failures) : +@[expose] def uncoveredPar (env : Ix.Environment) (ixe : Ixon.Env) (failures : Failures) + (keysArr : Array Ix.Name := env.consts.keys.toArray) : List Ix.Name := ((parMap (fun n => if ixe.named.contains n == failures.failed n then some n else none) - env.consts.keys.toArray).filterMap id).toList + keysArr).filterMap id).toList private theorem filterMap_if_eq {α : Type} (p : α → Bool) (l : List α) : (l.filterMap fun a => if p a then some a else none) = l.filter p := by @@ -1825,11 +1999,13 @@ theorem uncoveredPar_eq (env : Ix.Environment) (ixe : Ixon.Env) (failures : Fail exact filterMap_if_eq (fun n => ixe.named.contains n == failures.failed n) env.consts.keys /-- `unauthentic` computed on tasks (`parMap`). -/ -@[expose] def unauthenticPar (ixe : Ixon.Env) : List Address := +@[expose] def unauthenticPar (ixe : Ixon.Env) + (constsArr : Array (Address × Ixon.LazyConstant) := ixe.consts.toList.toArray) + (blobsArr : Array (Address × ByteArray) := ixe.blobs.toList.toArray) : List Address := ((parMap (fun (a, lc) => if a != Address.admissionHash lc.rawBytes then some a else none) - ixe.consts.toList.toArray).filterMap id).toList ++ + constsArr).filterMap id).toList ++ ((parMap (fun (a, bytes) => if a != Address.admissionHash bytes then some a else none) - ixe.blobs.toList.toArray).filterMap id).toList + blobsArr).filterMap id).toList private theorem filterMap_fst_eq {α β : Type} (p : α → β → Bool) (l : List (α × β)) : (l.filterMap fun (a, b) => if p a b then some a else none) = @@ -1848,9 +2024,13 @@ theorem unauthenticPar_eq (ixe : Ixon.Env) : unauthenticPar ixe = unauthentic ix · exact filterMap_fst_eq (fun a lc => a != Address.admissionHash lc.rawBytes) ixe.consts.toList · exact filterMap_fst_eq (fun a bytes => a != Address.admissionHash bytes) ixe.blobs.toList +/-- `xs.toList.findSome? f` computed on tasks (`parMap`) over `xs`. -/ +@[expose] def parFindSomeArr? {α β : Type} (f : α → Option β) (xs : Array α) : Option β := + ((parMap f xs).toList).findSome? id + /-- `l.findSome? f` computed on tasks (`parMap`) over `l.toArray`. -/ @[expose] def parFindSome? {α β : Type} (f : α → Option β) (l : List α) : Option β := - ((parMap f l.toArray).toList).findSome? id + parFindSomeArr? f l.toArray private theorem list_findSome?_map_id {α β : Type} (f : α → Option β) (l : List α) : (l.map f).findSome? id = l.findSome? f := by @@ -1862,19 +2042,25 @@ private theorem list_findSome?_map_id {α β : Type} (f : α → Option β) (l : theorem parFindSome?_eq {α β : Type} (f : α → Option β) (l : List α) : parFindSome? f l = l.findSome? f := by - simp only [parFindSome?, parMap, Ix.Compile.Parallel.map_eq, + simp only [parFindSome?, parFindSomeArr?, parMap, Ix.Compile.Parallel.map_eq, Array.toList_map, list_findSome?_map_id] -/-- `mutsMismatch` computed on tasks (`parFindSome?`). -/ -@[expose] def mutsMismatchPar (ixe : Ixon.Env) : Option (Ix.Name × Nat × Address) := +theorem parFindSomeArr?_toArray {α β : Type} (f : α → Option β) (l : List α) : + parFindSomeArr? f l.toArray = l.findSome? f := + parFindSome?_eq f l + +/-- `mutsMismatch` computed on tasks (`parFindSomeArr?`). -/ +@[expose] def mutsMismatchPar (ixe : Ixon.Env) + (namedArr : Array (Ix.Name × Ixon.Named) := ixe.named.toList.toArray) : + Option (Ix.Name × Nat × Address) := let byHash : Std.HashMap Address Ix.Name := ixe.named.fold (init := Std.HashMap.emptyWithCapacity ixe.named.size) fun m n _ => m.insert n.getHash n - parFindSome? (fun (entry, named) => + parFindSomeArr? (fun (entry, named) => match named.constMeta.info with | .muts all _ => all.toList.zipIdx.findSome? fun (cls, i) => (cls.toList.find? fun h => !publishedAtMember ixe byHash named.addr i h).map fun h => (entry, i, h) - | _ => none) ixe.named.toList + | _ => none) namedArr private theorem byHash_foldl_get? (l : List (Ix.Name × Ixon.Named)) (m₁ m₂ : Std.HashMap Address Ix.Name) (hm : ∀ k, m₁.get? k = m₂.get? k) : @@ -1900,12 +2086,13 @@ theorem mutsMismatchPar_eq (ixe : Ixon.Env) : mutsMismatchPar ixe = mutsMismatch (fun k => by simp [Std.HashMap.get?_eq_getElem?]) h simp only [Std.HashMap.fold_eq_foldl_toList] at hGet ⊢ rw [hGet] - simp only [mutsMismatchPar, mutsMismatch, parFindSome?_eq, hPub] + simp only [mutsMismatchPar, mutsMismatch, parFindSomeArr?_toArray, hPub] /-- `recordGraph` reading `ixe.consts.toList` in one pass instead of looking up each key of `ixe.consts.keys` in `ixe.consts`. -/ -@[expose] def recordGraphPar (ixe : Ixon.Env) : Array Address × Array (Array Nat) := - let entries := ixe.consts.toList.toArray +@[expose] def recordGraphPar (ixe : Ixon.Env) + (entries : Array (Address × Ixon.LazyConstant) := ixe.consts.toList.toArray) : + Array Address × Array (Array Nat) := let addrs := entries.map Prod.fst let index : Std.HashMap Address Nat := Std.HashMap.ofList addrs.toList.zipIdx let info := recordRefs (entries.map fun (a, lc) => (a, some lc)) @@ -1933,8 +2120,10 @@ theorem recordGraphPar_eq (ixe : Ixon.Env) : recordGraphPar ixe = recordGraph ix simp only [recordGraphPar, recordGraph, hAddrs, hEntries] /-- `referenceCycle` over `recordGraphPar`. -/ -@[expose] def referenceCyclePar (ixe : Ixon.Env) : Option (List Address) := - let (addrs, adj) := recordGraphPar ixe +@[expose] def referenceCyclePar (ixe : Ixon.Env) + (entries : Array (Address × Ixon.LazyConstant) := ixe.consts.toList.toArray) : + Option (List Address) := + let (addrs, adj) := recordGraphPar ixe entries let cyclic (c : Array Nat) : Bool := c.size != 1 || c.any fun i => (adj[i]?.getD #[]).contains i match Ix.Compile.Canon.condensation adj with @@ -1955,44 +2144,49 @@ theorem resolutionMismatchPar_eq (resolved : Std.HashMap Ix.Compile.ExactKey.Exa (ixe : Ixon.Env) : resolutionMismatchPar resolved ixe = resolutionMismatch resolved ixe := by simp only [resolutionMismatchPar, resolutionMismatch, parFindSome?_eq] -/-- `unresolvedPublished` computed on tasks (`parFindSome?`). -/ +/-- `unresolvedPublished` computed on tasks (`parFindSomeArr?`). -/ @[expose] def unresolvedPublishedPar (env : Ix.Environment) - (resolved : Std.HashMap Ix.Compile.ExactKey.ExactName Address) (ixe : Ixon.Env) : + (resolved : Std.HashMap Ix.Compile.ExactKey.ExactName Address) (ixe : Ixon.Env) + (namedArr : Array (Ix.Name × Ixon.Named) := ixe.named.toList.toArray) : Option Ix.Name := - parFindSome? (fun (n, _) => - if env.consts.contains n && !resolved.contains n then some n else none) ixe.named.toList + parFindSomeArr? (fun (n, _) => + if env.consts.contains n && !resolved.contains n then some n else none) namedArr theorem unresolvedPublishedPar_eq (env : Ix.Environment) (resolved : Std.HashMap Ix.Compile.ExactKey.ExactName Address) (ixe : Ixon.Env) : unresolvedPublishedPar env resolved ixe = unresolvedPublished env resolved ixe := by - simp only [unresolvedPublishedPar, unresolvedPublished, parFindSome?_eq] + simp only [unresolvedPublishedPar, unresolvedPublished, parFindSomeArr?_toArray] -/-- `misnamedInput` computed on tasks (`parFindSome?`). -/ -@[expose] def misnamedInputPar (env : Ix.Environment) : Option Ix.Name := - parFindSome? (fun (n, ci) => - if nameCompareExactEqFast ci.getCnst.name n then none else some n) env.consts.toList +/-- `misnamedInput` computed on tasks (`parFindSomeArr?`). -/ +@[expose] def misnamedInputPar (env : Ix.Environment) + (constsArr : Array (Ix.Name × Ix.ConstantInfo) := env.consts.toList.toArray) : Option Ix.Name := + parFindSomeArr? (fun (n, ci) => + if nameCompareExactEqFast ci.getCnst.name n then none else some n) constsArr theorem misnamedInputPar_eq (env : Ix.Environment) : misnamedInputPar env = misnamedInput env := by - simp only [misnamedInputPar, misnamedInput, parFindSome?_eq, nameCompareExactEqFast_eq] + simp only [misnamedInputPar, misnamedInput, parFindSomeArr?_toArray, nameCompareExactEqFast_eq] rfl -/-- `strayConstructor` computed on tasks (`parFindSome?`). -/ -@[expose] def strayConstructorPar (env : Ix.Environment) : Option Ix.Name := - parFindSome? (fun (n, ci) => +/-- `strayConstructor` computed on tasks (`parFindSomeArr?`). -/ +@[expose] def strayConstructorPar (env : Ix.Environment) + (constsArr : Array (Ix.Name × Ix.ConstantInfo) := env.consts.toList.toArray) : Option Ix.Name := + parFindSomeArr? (fun (n, ci) => match ci with | .inductInfo iv => iv.ctors.toList.find? fun c => match env.consts.get? c with | some (.ctorInfo cv) => !(cv.induct == n) | _ => true - | _ => none) env.consts.toList + | _ => none) constsArr theorem strayConstructorPar_eq (env : Ix.Environment) : strayConstructorPar env = strayConstructor env := by - simp only [strayConstructorPar, strayConstructor, parFindSome?_eq] + simp only [strayConstructorPar, strayConstructor, parFindSomeArr?_toArray] /-- `projectionCollision` computed on tasks (`parMap`). -/ -@[expose] def projectionCollisionPar (ixe : Ixon.Env) : Option (Address × Nat) := - let entries := ((parMap definitionProjectionFast ixe.consts.toList.toArray).filterMap id).toList +@[expose] def projectionCollisionPar (ixe : Ixon.Env) + (constsArr : Array (Address × Ixon.LazyConstant) := ixe.consts.toList.toArray) : + Option (Address × Nat) := + let entries := ((parMap definitionProjectionFast constsArr).filterMap id).toList let first : Std.HashMap (Address × Nat) Address := entries.foldl (init := {}) fun m (r, a) => if m.contains r then m else m.insert r a entries.findSome? fun (r, a) => if decide (first[r]? = some a) then none else some r @@ -2003,57 +2197,63 @@ theorem projectionCollisionPar_eq (ixe : Ixon.Env) : Ix.Compile.Parallel.map_eq, Array.toList_filterMap, Array.toList_map, List.filterMap_map, Function.id_comp] -/-- `unreadablePublished` computed on tasks (`parFindSome?`). -/ -@[expose] def unreadablePublishedPar (env : Ix.Environment) (ixe : Ixon.Env) : Option Ix.Name := - parFindSome? (fun (n, named) => +/-- `unreadablePublished` computed on tasks (`parFindSomeArr?`). -/ +@[expose] def unreadablePublishedPar (env : Ix.Environment) (ixe : Ixon.Env) + (namedArr : Array (Ix.Name × Ixon.Named) := ixe.named.toList.toArray) : Option Ix.Name := + parFindSomeArr? (fun (n, named) => if env.consts.contains n then match ixe.consts.get? named.addr with | some lc => if blockHeader lc.rawBytes then some n else none | none => some n - else none) ixe.named.toList + else none) namedArr theorem unreadablePublishedPar_eq (env : Ix.Environment) (ixe : Ixon.Env) : unreadablePublishedPar env ixe = unreadablePublished env ixe := by - simp only [unreadablePublishedPar, unreadablePublished, parFindSome?_eq] + simp only [unreadablePublishedPar, unreadablePublished, parFindSomeArr?_toArray] -/-- `blobRecordCollision` computed on tasks (`parFindSome?`). -/ -@[expose] def blobRecordCollisionPar (ixe : Ixon.Env) : Option Address := - parFindSome? (fun (a, _) => if ixe.consts.contains a then some a else none) ixe.blobs.toList +/-- `blobRecordCollision` computed on tasks (`parFindSomeArr?`). -/ +@[expose] def blobRecordCollisionPar (ixe : Ixon.Env) + (blobsArr : Array (Address × ByteArray) := ixe.blobs.toList.toArray) : Option Address := + parFindSomeArr? (fun (a, _) => if ixe.consts.contains a then some a else none) blobsArr theorem blobRecordCollisionPar_eq (ixe : Ixon.Env) : blobRecordCollisionPar ixe = blobRecordCollision ixe := by - simp only [blobRecordCollisionPar, blobRecordCollision, parFindSome?_eq] + simp only [blobRecordCollisionPar, blobRecordCollision, parFindSomeArr?_toArray] -/-- `unstoredFamilyName` computed on tasks (`parFindSome?`). -/ -@[expose] def unstoredFamilyNamePar (env : Ix.Environment) : Option (Ix.Name × Ix.Name) := - parFindSome? (fun (n, ci) => +/-- `unstoredFamilyName` computed on tasks (`parFindSomeArr?`). -/ +@[expose] def unstoredFamilyNamePar (env : Ix.Environment) + (constsArr : Array (Ix.Name × Ix.ConstantInfo) := env.consts.toList.toArray) : + Option (Ix.Name × Ix.Name) := + parFindSomeArr? (fun (n, ci) => ((familyNamesOf ci).find? fun a => !(env.consts.get? a).any fun ci' => nameCompareExactEqFast ci'.getCnst.name a).map (n, ·)) - env.consts.toList + constsArr theorem unstoredFamilyNamePar_eq (env : Ix.Environment) : unstoredFamilyNamePar env = unstoredFamilyName env := by - simp only [unstoredFamilyNamePar, unstoredFamilyName, parFindSome?_eq, nameCompareExactEqFast_eq] + simp only [unstoredFamilyNamePar, unstoredFamilyName, parFindSomeArr?_toArray, nameCompareExactEqFast_eq] -/-- `misplacedConstructor` computed on tasks (`parFindSome?`). -/ -@[expose] def misplacedConstructorPar (env : Ix.Environment) : Option Ix.Name := - parFindSome? (fun (_, ci) => +/-- `misplacedConstructor` computed on tasks (`parFindSomeArr?`). -/ +@[expose] def misplacedConstructorPar (env : Ix.Environment) + (constsArr : Array (Ix.Name × Ix.ConstantInfo) := env.consts.toList.toArray) : Option Ix.Name := + parFindSomeArr? (fun (_, ci) => match ci with | .inductInfo iv => (iv.ctors.toList.zipIdx.find? fun (c, j) => match env.consts.get? c with | some (.ctorInfo cv) => cv.cidx != j | _ => false).map (·.1) - | _ => none) env.consts.toList + | _ => none) constsArr theorem misplacedConstructorPar_eq (env : Ix.Environment) : misplacedConstructorPar env = misplacedConstructor env := by - simp only [misplacedConstructorPar, misplacedConstructor, parFindSome?_eq] + simp only [misplacedConstructorPar, misplacedConstructor, parFindSomeArr?_toArray] -/-- `strayLevelParams` computed on tasks (`parFindSome?`). -/ -@[expose] def strayLevelParamsPar (env : Ix.Environment) : Option Ix.Name := +/-- `strayLevelParams` computed on tasks (`parFindSomeArr?`). -/ +@[expose] def strayLevelParamsPar (env : Ix.Environment) + (constsArr : Array (Ix.Name × Ix.ConstantInfo) := env.consts.toList.toArray) : Option Ix.Name := let recLps := recursorLevelParams env - parFindSome? (fun (n, ci) => + parFindSomeArr? (fun (n, ci) => match ci with | .inductInfo iv => match iv.all.find? (fun m => @@ -2073,11 +2273,11 @@ theorem misplacedConstructorPar_eq (env : Ix.Environment) : recursorLevelCountOk env first rv then none else some n | none => none - | _ => none) env.consts.toList + | _ => none) constsArr theorem strayLevelParamsPar_eq (env : Ix.Environment) : strayLevelParamsPar env = strayLevelParams env := by - simp only [strayLevelParamsPar, strayLevelParams, parFindSome?_eq, namesEqFast_eq] + simp only [strayLevelParamsPar, strayLevelParams, parFindSomeArr?_toArray, namesEqFast_eq] rfl /-- `strayFamilyMember` computed on tasks (`parFindSome?`) with precomputed `familyRecursors env`. -/ @@ -2175,17 +2375,18 @@ theorem strayFamilyMemberPar_eq (env : Ix.Environment) (blocks : Ix.CondensedBlo strayFamilyMemberPar env blocks = strayFamilyMember env blocks := by simp only [strayFamilyMemberPar, strayFamilyMemberWith, strayFamilyMember, parFindSome?_eq] -/-- `unhintedDefinition` computed on tasks (`parFindSome?`). -/ -@[expose] def unhintedDefinitionPar (env : Ix.Environment) (ixe : Ixon.Env) : Option Ix.Name := - parFindSome? (fun (n, named) => +/-- `unhintedDefinition` computed on tasks (`parFindSomeArr?`). -/ +@[expose] def unhintedDefinitionPar (env : Ix.Environment) (ixe : Ixon.Env) + (namedArr : Array (Ix.Name × Ixon.Named) := ixe.named.toList.toArray) : Option Ix.Name := + parFindSomeArr? (fun (n, named) => match env.consts.get? n with | some (.defnInfo _) | some (.thmInfo _) | some (.opaqueInfo _) => if ixe.anonHints.contains named.addr then none else some n - | _ => none) ixe.named.toList + | _ => none) namedArr theorem unhintedDefinitionPar_eq (env : Ix.Environment) (ixe : Ixon.Env) : unhintedDefinitionPar env ixe = unhintedDefinition env ixe := by - simp only [unhintedDefinitionPar, unhintedDefinition, parFindSome?_eq] + simp only [unhintedDefinitionPar, unhintedDefinition, parFindSomeArr?_toArray] /-- `Ix.LeanIngress.sourceAddrs` computed on tasks (`parMap`). -/ @[expose] def sourceAddrsPar (ixe : Ixon.Env) : Std.HashMap Ix.Kernel.Name Address := @@ -2197,34 +2398,53 @@ theorem unhintedDefinitionPar_eq (env : Ix.Environment) (ixe : Ixon.Env) : simp only [sourceAddrsPar, parMap, Ix.Compile.Parallel.map_eq, Array.foldl_map, List.foldl_toArray', Ix.LeanIngress.sourceAddrs, Std.HashMap.fold_eq_foldl_toList] -/-- `checkReport` with every check started as a task before any result is read. -/ -@[expose] def checkReportPar (env : Ix.Environment) +/-- Spawn `checkReport`'s twenty-one tasks, sharing materialized collection +snapshots across checks. -/ +@[expose] def CheckTasks.spawn (env : Ix.Environment) (cenv : Ix.CompileM.CompileEnv) (out : Ixon.Env × Failures) - (blocks : Ix.CondensedBlocks := compileBlocks env) : Except String Unit := + (blocks : Ix.CondensedBlocks := compileBlocks env) : CheckTasks := + let envConstsList := env.consts.toList + let envConstsArr := envConstsList.toArray + let envConstsDirectArr := env.consts.toArray + let envConstsKeys := env.consts.keys + let envConstsKeysArr := envConstsKeys.toArray + let ixeConstsList := out.1.consts.toList + let ixeConstsArr := ixeConstsList.toArray + let ixeNamedList := out.1.named.toList + let ixeNamedArr := ixeNamedList.toArray + let ixeBlobsList := out.1.blobs.toList + let ixeBlobsArr := ixeBlobsList.toArray let recRecords := Task.spawn fun _ => storedRecursorRecords out.1 let famRecs := Task.spawn fun _ => familyRecursors env - checkReportOf out { - uncovered := Task.spawn fun _ => uncoveredPar env out.1 out.2 - unauthentic := Task.spawn fun _ => unauthenticPar out.1 - referenceCycle := Task.spawn fun _ => referenceCyclePar out.1 - nameHashCollision := Task.spawn fun _ => nameHashCollisionFast env out.1 - mutsMismatch := Task.spawn fun _ => mutsMismatchPar out.1 + { uncovered := Task.spawn fun _ => uncoveredPar env out.1 out.2 envConstsKeysArr + unauthentic := Task.spawn fun _ => unauthenticPar out.1 ixeConstsArr ixeBlobsArr + referenceCycle := Task.spawn fun _ => referenceCyclePar out.1 ixeConstsArr + nameHashCollision := Task.spawn fun _ => nameHashCollisionFast env out.1 envConstsKeys + mutsMismatch := Task.spawn fun _ => mutsMismatchPar out.1 ixeNamedArr resolutionMismatch := Task.spawn fun _ => resolutionMismatchPar cenv.nameToAddr out.1 - unresolvedPublished := Task.spawn fun _ => unresolvedPublishedPar env cenv.nameToAddr out.1 - misnamedInput := Task.spawn fun _ => misnamedInputPar env - strayConstructor := Task.spawn fun _ => strayConstructorPar env - projectionCollision := Task.spawn fun _ => projectionCollisionPar out.1 - unreadablePublished := Task.spawn fun _ => unreadablePublishedPar env out.1 - blobRecordCollision := Task.spawn fun _ => blobRecordCollisionPar out.1 - unownedRecursor := Task.spawn fun _ => unownedRecursorWith env out.1 recRecords.get famRecs.get - roleRecordKind := Task.spawn fun _ => roleRecordKind out.1 - literalRoleUnnamed := Task.spawn fun _ => literalRoleUnnamed env out.1 - unstoredFamilyName := Task.spawn fun _ => unstoredFamilyNamePar env - misplacedConstructor := Task.spawn fun _ => misplacedConstructorPar env - strayLevelParams := Task.spawn fun _ => strayLevelParamsPar env + unresolvedPublished := Task.spawn fun _ => unresolvedPublishedPar env cenv.nameToAddr out.1 ixeNamedArr + misnamedInput := Task.spawn fun _ => misnamedInputPar env envConstsArr + strayConstructor := Task.spawn fun _ => strayConstructorPar env envConstsArr + projectionCollision := Task.spawn fun _ => projectionCollisionPar out.1 ixeConstsArr + unreadablePublished := Task.spawn fun _ => unreadablePublishedPar env out.1 ixeNamedArr + blobRecordCollision := Task.spawn fun _ => blobRecordCollisionPar out.1 ixeBlobsArr + unownedRecursor := Task.spawn fun _ => + unownedRecursorWith env out.1 recRecords.get famRecs.get ixeNamedArr + roleRecordKind := Task.spawn fun _ => roleRecordKindWith out.1 ixeConstsList + literalRoleUnnamed := Task.spawn fun _ => Ix.Cert.literalRoleUnnamed env out.1 + unstoredFamilyName := Task.spawn fun _ => unstoredFamilyNamePar env envConstsArr + misplacedConstructor := Task.spawn fun _ => misplacedConstructorPar env envConstsArr + strayLevelParams := Task.spawn fun _ => strayLevelParamsPar env envConstsArr strayFamilyMember := Task.spawn fun _ => strayFamilyMemberWith env blocks famRecs.get - recursorShapeError := Task.spawn fun _ => recursorShapeErrorWith env out.1 recRecords.get - unhintedDefinition := Task.spawn fun _ => unhintedDefinitionPar env out.1 } + recursorShapeError := Task.spawn fun _ => + recursorShapeErrorWith env out.1 recRecords.get envConstsDirectArr + unhintedDefinition := Task.spawn fun _ => unhintedDefinitionPar env out.1 ixeNamedArr } + +/-- `checkReport` with every check started as a task before any result is read. -/ +@[expose] def checkReportPar (env : Ix.Environment) + (cenv : Ix.CompileM.CompileEnv) (out : Ixon.Env × Failures) + (blocks : Ix.CondensedBlocks := compileBlocks env) : Except String Unit := + checkReportOf out (CheckTasks.spawn env cenv out blocks) /-- **`checkReport` runs as `checkReportPar`**: a spawned task's value is its function's (`Task.spawn`), so the two are the same function. -/ @@ -2236,16 +2456,20 @@ function's (`Task.spawn`), so the two are the same function. -/ strayFamilyMemberPar_eq env blocks have hUnowned : unownedRecursorWith env out.1 (Task.spawn fun _ => storedRecursorRecords out.1).get - (Task.spawn fun _ => familyRecursors env).get = unownedRecursor env out.1 := rfl + (Task.spawn fun _ => familyRecursors env).get = unownedRecursor env out.1 := + unownedRecursorWith_eq env out.1 have hRecShape : recursorShapeErrorWith env out.1 (Task.spawn fun _ => storedRecursorRecords out.1).get = recursorShapeError env out.1 := rfl - simp only [checkReportPar, uncoveredPar_eq, unauthenticPar_eq, referenceCyclePar_eq, - nameHashCollisionFast_eq, + have hRoleKind : + roleRecordKindWith out.1 = roleRecordKind out.1 := rfl + simp only [checkReportPar, CheckTasks.spawn, uncoveredPar_eq, unauthenticPar_eq, + referenceCyclePar_eq, nameHashCollisionFast_eq, mutsMismatchPar_eq, resolutionMismatchPar_eq, unresolvedPublishedPar_eq, misnamedInputPar_eq, strayConstructorPar_eq, projectionCollisionPar_eq, unreadablePublishedPar_eq, - blobRecordCollisionPar_eq, hUnowned, unstoredFamilyNamePar_eq, misplacedConstructorPar_eq, - strayLevelParamsPar_eq, hStray, hRecShape, unhintedDefinitionPar_eq] + blobRecordCollisionPar_eq, hUnowned, hRoleKind, unstoredFamilyNamePar_eq, + misplacedConstructorPar_eq, strayLevelParamsPar_eq, hStray, hRecShape, + unhintedDefinitionPar_eq] rfl /-- **The pure compiler, with its failures.** A block that fails to compile diff --git a/Ix/Cert/CompileCorrect.lean b/Ix/Cert/CompileCorrect.lean index ca61b4d3e..d16e0f1ef 100644 --- a/Ix/Cert/CompileCorrect.lean +++ b/Ix/Cert/CompileCorrect.lean @@ -1,5 +1,6 @@ import Ix.Cert.StrongCommutation import Ix.LeanIngress.Rename +import Ix.LeanIngress.NominalQuotient import Ix.Cert.EvidenceCheck import Ix.Cert.Quotient import Ix.Cert.RoleClosure @@ -437,4 +438,265 @@ theorem CompileCorrect.nominal (h : CompileCorrect.{w}) {env : Ix.Environment} exact ⟨envQ, quotient, h env roots sub ixe failures t envQ table quotient compiled none bound agree evidenceAgrees evidence⟩ +/-! ## Strengthened nominal semantic bridge along `Key.subst (fiberAliasAt ixe (roleAtMap t))` -/ + +/-- **Strong commutation pulls back along `EnvSubstPullback`**: composing +`CommutesStrong read envQ envA` with `EnvSubstPullback ρ envS envQ` yields +`CommutesStrong (fun c => read (ConstantInfo.substKeys ρ c)) envS envA`. -/ +theorem CommutesStrong.substPullback {ρ : Ix.LeanIngress.FiberMap} + {read : Ix.Kernel.ConstantInfo .source → Option (Ix.Kernel.Expr .anon)} + {envS envQ : Env .source} {envA : Env .anon} + (hpull : EnvSubstPullback.{w} ρ envS envQ) + (hcomm : CommutesStrong.{w} read envQ envA) : + CommutesStrong.{w} + (fun c => read (Ix.LeanIngress.ConstantInfo.substKeys ρ c)) envS envA := by + intro V _ M + obtain ⟨NQ, hNQ⟩ := hcomm V M + obtain ⟨NS, hNS⟩ := hpull.lawModel V NQ + refine ⟨NS, fun c hc e hre φ ρv => ?_⟩ + have hd := hNQ (Ix.LeanIngress.ConstantInfo.substKeys ρ c) (hpull.mem hc) e hre φ ρv + rw [Ix.LeanIngress.ConstantInfo.substKeys_name, ← hNS c.name φ] at hd + exact hd + +/-- **Direct value-model semantic bridge between nominal `envS` and the compiled +Ixon environment `envA`** via quotient `envQ` along the composite key map +`(Key.map (evAddr ixe b)) ∘ (Key.subst (fiberAliasAt ixe (roleAtMap t)))`, +requiring only `EnvSubstModelPullback` (or `EnvSubstRel`, without `LawModel.Rel`): +`ValueModel.Rel` composes directly from `envA` to `envS`, and for every strong +target model `M` of `envA` there is a nominal `ValueModel V envS` in which every +nominal constant `c ∈ envS.consts` whose substituted representative reads in `cx` +is stored in `envA` at its composite compiled key and denotes that key in `envA`. -/ +def NominalModelBridgeAt (ixe : Ixon.Env) (sub : Ix.Environment) + (t : Ix.Kernel.RoleTable) (envS envQ : Env .source) : Prop := + let ρ := Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t) + ∃ binding, Ix.Kernel.Admission.defaultBinding = .ok binding ∧ + ∃ limits order envA cx b, + IxonAcceptsRef binding limits ixe (Seed ixe sub) order envA ∧ + admissionContext binding limits ixe order = some cx ∧ + Ix.RoleEvidence.quotientBundle () = .ok b ∧ + RenamesSource ixe t cx envQ envA ∧ + ReadingCovers ixe t cx sub envQ ∧ + ValueModel.Rel.{w} + ((Ix.Kernel.Key.map (Ix.LeanIngress.evAddr ixe b)) ∘ (Ix.LeanIngress.Key.subst ρ)) + envS envA ∧ + ∀ (V : Type w) [SetTheory V] (M : Ix.Kernel.Cached.StrongModel V envA), + ∃ NS : ValueModel V envS, + ∀ c ∈ envS.consts, + let cQ := Ix.LeanIngress.ConstantInfo.substKeys ρ c + let d := Ix.Kernel.ConstantInfo.mapKeys (Ix.LeanIngress.evAddr ixe b) cQ + cQ ∈ envQ.consts ∧ + ∀ e, + readingAt ixe t cx cQ = some e → + e = .const + ((Ix.LeanIngress.Key.subst ρ c.name).map (Ix.LeanIngress.evAddr ixe b)) + (c.toConstantVal.levelParams.map .param) ∧ + envA.find? ((Ix.LeanIngress.Key.subst ρ c.name).map (Ix.LeanIngress.evAddr ixe b)) = + some d ∧ + d ∈ envA.consts ∧ + ∀ φ ρv, Ix.Kernel.Denotes M.model.cval envA φ ρv e (NS.cval c.name φ) + +/-- **Combining `IxonCommutesCovering` on `envQ` with `EnvSubstModelPullback` from +`envQ` to `envS` yields `NominalModelBridgeAt`** without requiring `LawModel.Rel`, +discharging all intermediate lookup and membership premises in `envQ` and `envA` +directly from `c ∈ envS.consts`. -/ +theorem IxonCommutesCovering.nominalModelPullback {ixe : Ixon.Env} {sub : Ix.Environment} + {t : Ix.Kernel.RoleTable} {envS envQ : Env .source} + (hcov : IxonCommutesCovering.{w} ixe sub t envQ) + (hpull : EnvSubstModelPullback.{w} + (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) envS envQ) : + NominalModelBridgeAt.{w} ixe sub t envS envQ := by + let ρ := Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t) + obtain ⟨binding, hbind, limits, order, envA, cx, hacc, hcx, hren, hcomm, hread⟩ := hcov + obtain ⟨b, hb, hrel, hrenRead⟩ := hren + refine ⟨binding, hbind, limits, order, envA, cx, b, hacc, hcx, hb, + ⟨b, hb, hrel, hrenRead⟩, hread, + hpull.comp_envRel_valueModel hrel, ?_⟩ + intro V _ M + obtain ⟨NQ, hNQ⟩ := hcomm V M + obtain ⟨NS, hNS⟩ := hpull.valueModel V NQ.toValueModel + refine ⟨NS, fun c hc => ?_⟩ + have hmemQ := hpull.mem hc + refine ⟨hmemQ, fun e hre => ?_⟩ + obtain ⟨he, hfindA⟩ := hrenRead (Ix.LeanIngress.ConstantInfo.substKeys ρ c) hmemQ e hre + simp only [Ix.LeanIngress.ConstantInfo.substKeys_name, + Ix.LeanIngress.ConstantInfo.substKeys_toConstantVal, + Ix.LeanIngress.ConstantVal.substKeys_levelParams] at he hfindA + have hmemA : Ix.Kernel.ConstantInfo.mapKeys (Ix.LeanIngress.evAddr ixe b) + (Ix.LeanIngress.ConstantInfo.substKeys ρ c) ∈ envA.consts := + List.mem_of_find?_eq_some hfindA + refine ⟨he, hfindA, hmemA, fun φ ρv => ?_⟩ + have hd := hNQ (Ix.LeanIngress.ConstantInfo.substKeys ρ c) hmemQ e hre φ ρv + rw [Ix.LeanIngress.ConstantInfo.substKeys_name, ← hNS c.name φ] at hd + exact hd + +/-- **Construct `NominalModelBridgeAt` directly from `IxonCommutesCovering` and +`EnvSubstRel`** (without `LawModel.Rel`), so callers only need `c ∈ envS.consts`. -/ +theorem IxonCommutesCovering.nominalBridgeAt {P : Ix.Kernel.Mode.Id .source → Prop} + {ixe : Ixon.Env} {sub : Ix.Environment} + {t : Ix.Kernel.RoleTable} {envS envQ : Env .source} + (hcov : IxonCommutesCovering.{w} ixe sub t envQ) + (hrel : EnvSubstRel P + (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) envS envQ) : + NominalModelBridgeAt.{w} ixe sub t envS envQ := + hcov.nominalModelPullback hrel.toModelPullback + +/-- **Direct semantic bridge between nominal `envS` and the compiled Ixon +environment `envA`** via quotient `envQ` along the composite key map +`(Key.map (evAddr ixe b)) ∘ (Key.subst (fiberAliasAt ixe (roleAtMap t)))`: +both `ValueModel.Rel` and `CommutesStrong` (producing a nominal `LawModel`) +compose directly from `envA` to `envS`, and every nominal constant whose +substituted representative reads in `cx` is stored in `envA` and denotes its +composite compiled key in `envA`. -/ +def NominalSemanticBridgeAt (ixe : Ixon.Env) (sub : Ix.Environment) + (t : Ix.Kernel.RoleTable) (envS envQ : Env .source) : Prop := + let ρ := Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t) + ∃ binding, Ix.Kernel.Admission.defaultBinding = .ok binding ∧ + ∃ limits order envA cx b, + IxonAcceptsRef binding limits ixe (Seed ixe sub) order envA ∧ + admissionContext binding limits ixe order = some cx ∧ + Ix.RoleEvidence.quotientBundle () = .ok b ∧ + RenamesSource ixe t cx envQ envA ∧ + ReadingCovers ixe t cx sub envQ ∧ + ValueModel.Rel.{w} + ((Ix.Kernel.Key.map (Ix.LeanIngress.evAddr ixe b)) ∘ (Ix.LeanIngress.Key.subst ρ)) + envS envA ∧ + CommutesStrong.{w} + (fun c => readingAt ixe t cx (Ix.LeanIngress.ConstantInfo.substKeys ρ c)) + envS envA ∧ + ∀ (V : Type w) [SetTheory V] (M : Ix.Kernel.Cached.StrongModel V envA), + ∃ NS : LawModel V envS, + ∀ c ∈ envS.consts, + let cQ := Ix.LeanIngress.ConstantInfo.substKeys ρ c + let d := Ix.Kernel.ConstantInfo.mapKeys (Ix.LeanIngress.evAddr ixe b) cQ + cQ ∈ envQ.consts ∧ + ∀ e, + readingAt ixe t cx cQ = some e → + e = .const + ((Ix.LeanIngress.Key.subst ρ c.name).map (Ix.LeanIngress.evAddr ixe b)) + (c.toConstantVal.levelParams.map .param) ∧ + envA.find? ((Ix.LeanIngress.Key.subst ρ c.name).map (Ix.LeanIngress.evAddr ixe b)) = + some d ∧ + d ∈ envA.consts ∧ + ∀ φ ρv, Ix.Kernel.Denotes M.model.cval envA φ ρv e (NS.cval c.name φ) + +/-- **Combining `IxonCommutesCovering` on `envQ` with `EnvSubstPullback` from +`envQ` to `envS` yields `NominalSemanticBridgeAt`**, discharging all intermediate +lookup and membership premises in `envQ` and `envA` directly from `c ∈ envS.consts`. -/ +theorem IxonCommutesCovering.nominalPullback {ixe : Ixon.Env} {sub : Ix.Environment} + {t : Ix.Kernel.RoleTable} {envS envQ : Env .source} + (hcov : IxonCommutesCovering.{w} ixe sub t envQ) + (hpull : EnvSubstPullback.{w} + (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) envS envQ) : + NominalSemanticBridgeAt.{w} ixe sub t envS envQ := by + let ρ := Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t) + obtain ⟨binding, hbind, limits, order, envA, cx, hacc, hcx, hren, hcomm, hread⟩ := hcov + obtain ⟨b, hb, hrel, hrenRead⟩ := hren + refine ⟨binding, hbind, limits, order, envA, cx, b, hacc, hcx, hb, + ⟨b, hb, hrel, hrenRead⟩, hread, + hpull.comp_envRel_valueModel hrel, + CommutesStrong.substPullback hpull hcomm, ?_⟩ + intro V _ M + obtain ⟨NS, hNS⟩ := CommutesStrong.substPullback hpull hcomm V M + refine ⟨NS, fun c hc => ?_⟩ + have hmemQ := hpull.mem hc + refine ⟨hmemQ, fun e hre => ?_⟩ + obtain ⟨he, hfindA⟩ := hrenRead (Ix.LeanIngress.ConstantInfo.substKeys ρ c) hmemQ e hre + simp only [Ix.LeanIngress.ConstantInfo.substKeys_name, + Ix.LeanIngress.ConstantInfo.substKeys_toConstantVal, + Ix.LeanIngress.ConstantVal.substKeys_levelParams] at he hfindA + have hmemA : Ix.Kernel.ConstantInfo.mapKeys (Ix.LeanIngress.evAddr ixe b) + (Ix.LeanIngress.ConstantInfo.substKeys ρ c) ∈ envA.consts := + List.mem_of_find?_eq_some hfindA + exact ⟨he, hfindA, hmemA, hNS c hc e hre⟩ + +/-- Construct `NominalSemanticBridgeAt` directly from `IxonCommutesCovering`, +`EnvSubstRel`, and `LawModel.Rel`, discharging all intermediate lookup and +membership premises in `envQ` and `envA` from `c ∈ envS.consts`. + +Note: Quotient Source (`CompileCorrect` + `Ix.LeanIngress.prepareSourceCollapsedAt_trace`) +is the primary unconditional security guarantee: quotient collapse only merges +declarations that are structurally identical (`Declaration.beqF`, hence `=`) +under the fiber-alias substitution `Key.subst ρ`, and `CompileCorrect` transports +both `ValueModel` and `LawModel` unconditionally between `envQ` and `envA`. +This theorem is a conditional model-theoretic corollary for pulling models of +`envQ` back to a nominal environment `envS` satisfying `EnvSubstRel` and +`LawModel.Rel`; deriving `LawModel.Rel` from `EnvSubstRel` and discharging +`EnvSubstRel` from `Ix.LeanIngress.FiberAliasSafe` on closures whose alias +fibers do not mix reducibility hints is left for future work. -/ +theorem NominalSemanticBridgeAt.of_covering {P : Ix.Kernel.Mode.Id .source → Prop} + {ixe : Ixon.Env} {sub : Ix.Environment} + {t : Ix.Kernel.RoleTable} {envS envQ : Env .source} + (hcov : IxonCommutesCovering.{w} ixe sub t envQ) + (hrel : EnvSubstRel P + (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) envS envQ) + (hlaw : LawModel.Rel.{w} + (Ix.LeanIngress.Key.subst (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t))) + envS envQ) : + NominalSemanticBridgeAt.{w} ixe sub t envS envQ := + hcov.nominalPullback (hrel.toPullback hlaw) + +/-- **Nominal value-model bridge corollary of `CompileCorrect`** (requiring only +`EnvSubstRel`, without `LawModel.Rel`). + +Quotient Source (`CompileCorrect` + `Ix.LeanIngress.prepareSourceCollapsedAt_trace`) +is the primary unconditional security guarantee: quotient collapse only merges +declarations that are structurally identical (`Declaration.beqF`, hence `=`) +under the fiber-alias substitution `Key.subst ρ`, and `CompileCorrect` transports +both `ValueModel` and `LawModel` unconditionally between `envQ` and `envA`. +`nominalBridgeAt` is a conditional model-theoretic corollary for pulling +`ValueModel`s of `envQ` back to a nominal environment `envS` accepted by nominal +Source (`SourceAccepts`) and satisfying `EnvSubstRel`; discharging `EnvSubstRel` +from `Ix.LeanIngress.FiberAliasSafe` on closures whose alias fibers do not mix +reducibility hints is left for future work. -/ +theorem CompileCorrect.nominalBridgeAt (h : CompileCorrect.{w}) + {P : Ix.Kernel.Mode.Id .source → Prop} {env : Ix.Environment} + {roots : List Ix.Name} {sub : Ix.Environment} {ixe : Ixon.Env} {failures : Failures} + {t : Ix.Kernel.RoleTable} {envS envQ : Env .source} + (table : Ix.Kernel.Reader.defaultTable = .ok t) + (quotient : SourceAcceptsQ blocksOf env roots sub ixe t envQ) + (hrel : EnvSubstRel P + (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) envS envQ) + (compiled : compileReport env = .ok (ixe, failures)) (none : NoFailure failures sub) + (bound : PublishedRolesBound t ixe) (agree : PublishedAgree ixe) + (evidenceAgrees : EvidenceCheck.EvidenceAgrees t ixe) + (evidence : RoleClosure.RoleEvidenceChecks) : + Published ixe sub ∧ NominalModelBridgeAt.{w} ixe sub t envS envQ := by + obtain ⟨hpub, hcov⟩ := + h env roots sub ixe failures t envQ table quotient compiled none bound + agree evidenceAgrees evidence + exact ⟨hpub, hcov.nominalBridgeAt hrel⟩ + +/-- **Nominal pullback corollary of `CompileCorrect`**: when `CompileCorrect` +holds and nominal `envS` pulls back from quotient `envQ` along +`Key.subst (fiberAliasAt ixe (roleAtMap t))`, both `LawModel` and +`ValueModel` compose to relate nominal `envS` directly to the compiled Ixon +environment `envA` (`NominalSemanticBridgeAt`). + +Quotient Source (`CompileCorrect` + `Ix.LeanIngress.prepareSourceCollapsedAt_trace`) +is the primary unconditional security guarantee: quotient collapse only merges +declarations that are structurally identical (`Declaration.beqF`, hence `=`) +under the fiber-alias substitution `Key.subst ρ`, and `CompileCorrect` transports +both `ValueModel` and `LawModel` unconditionally between `envQ` and `envA`. +`nominalPullback` is a conditional model-theoretic corollary for pulling models +of `envQ` back to a nominal environment `envS` satisfying `EnvSubstRel` and +`LawModel.Rel`; deriving `LawModel.Rel` from `EnvSubstRel` and discharging +`EnvSubstRel` from `Ix.LeanIngress.FiberAliasSafe` on closures whose alias +fibers do not mix reducibility hints is left for future work. -/ +theorem CompileCorrect.nominalPullback (h : CompileCorrect.{w}) {env : Ix.Environment} + {roots : List Ix.Name} {sub : Ix.Environment} {ixe : Ixon.Env} {failures : Failures} + {t : Ix.Kernel.RoleTable} {envS envQ : Env .source} + (table : Ix.Kernel.Reader.defaultTable = .ok t) + (quotient : SourceAcceptsQ blocksOf env roots sub ixe t envQ) + (hpull : EnvSubstPullback.{w} + (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) envS envQ) + (compiled : compileReport env = .ok (ixe, failures)) (none : NoFailure failures sub) + (bound : PublishedRolesBound t ixe) (agree : PublishedAgree ixe) + (evidenceAgrees : EvidenceCheck.EvidenceAgrees t ixe) + (evidence : RoleClosure.RoleEvidenceChecks) : + Published ixe sub ∧ NominalSemanticBridgeAt.{w} ixe sub t envS envQ := by + obtain ⟨hpub, hcov⟩ := + h env roots sub ixe failures t envQ table quotient compiled none bound + agree evidenceAgrees evidence + exact ⟨hpub, hcov.nominalPullback hpull⟩ + end Ix.Cert diff --git a/Ix/Cert/Condense.lean b/Ix/Cert/Condense.lean index 9fa843a5e..bb21c8897 100644 --- a/Ix/Cert/Condense.lean +++ b/Ix/Cert/Condense.lean @@ -136,8 +136,7 @@ def blockRefsOf (refs : Ix.Map Ix.Name (Ix.Set Ix.Name)) (blocks : Ix.Map Ix.Nam let mut rs : Ix.Set Ix.Name := {} for a in all do rs := rs.union (refs.getD a {}) - rs := rs.filter (!all.contains ·) - blockRefs := blockRefs.insert lo rs + blockRefs := blockRefs.insert lo (rs.filter (!all.contains ·)) return blockRefs theorem blockRefs_eq (refs : Ix.Map Ix.Name (Ix.Set Ix.Name)) : @@ -222,8 +221,8 @@ theorem condense_blockRefs {refs : Ix.Map Ix.Name (Ix.Set Ix.Name)} {rep : Ix.Na `CondenseM.run` as the composition of its loops (`blocks_eq`, by `rfl`): the nodes and their positions (`nodesOf`), the edges (`adjOf`), Tarjan's -components, each node's representative (`rootNames`), the nodes in discovery -order (`discOf`), and the blocks (`blocksOfDisc`). -/ +components, each node's representative (`rootNames`, `discOf`), and the +blocks (`blocksOfDisc`). -/ /-- `CondenseM.run`'s first loop. -/ def nodesOf (refs : Ix.Map Ix.Name (Ix.Set Ix.Name)) : Array (Ix.Name × Ix.Set Ix.Name) × Std.HashMap Ix.Name Nat := Id.run do @@ -257,24 +256,27 @@ def rootNames (names : Array Ix.Name) (c : Ix.Compile.Canon.Condensation) rootName := rootName.setIfInBounds v rn return rootName +structure Disc (names : Array Ix.Name) where + c : Ix.Compile.Canon.Condensation + rn : Vector Ix.Name names.size + hr : ∀ p ∈ c.comps.zip c.roots, p.2 < names.size + +def Disc.get? {names : Array Ix.Name} (d : Disc names) (v : Nat) : Option (Ix.Name × Ix.Name) := + if h : v < names.size then some (names[v], d.rn[v]) else none + def discOf (names : Array Ix.Name) (c : Ix.Compile.Canon.Condensation) (rootName : Vector Ix.Name names.size) - (hv : ∀ v ∈ c.order, v < names.size) : Std.HashMap UInt64 (Ix.Name × Ix.Name) := Id.run do - let mut byDiscovery : Std.HashMap UInt64 (Ix.Name × Ix.Name) := {} - let mut d : Nat := 0 - for h : v in c.order do - have hv : v < names.size := hv v h - byDiscovery := byDiscovery.insert d.toUInt64 (names[v], rootName[v]) - d := d + 1 - return byDiscovery - -def blocksOfDisc (byDiscovery : Std.HashMap UInt64 (Ix.Name × Ix.Name)) : Ix.Map Ix.Name (Ix.Set Ix.Name) := Id.run do + (hr : ∀ p ∈ c.comps.zip c.roots, p.2 < names.size) : Disc names := + ⟨c, rootName, hr⟩ + +def blocksOfDisc {names : Array Ix.Name} (bd : Disc names) : Ix.Map Ix.Name (Ix.Set Ix.Name) := Id.run do let mut blocks : Ix.Map Ix.Name (Ix.Set Ix.Name) := {} - let mut lowLinks : Map Ix.Name Ix.Name := {} - for (_, (name, lowName)) in byDiscovery do - lowLinks := lowLinks.insert name lowName - blocks := blocks.alter lowName fun x => match x with - | .some s => .some (s.insert name) - | .none => .some {name} + for h : p in bd.c.comps.zip bd.c.roots do + have hr : p.2 < names.size := bd.hr p h + let rn := names[p.2] + let mut s : Set Ix.Name := {rn} + for v in p.1 do + s := if hv : v < names.size then s.insert names[v] else s + blocks := blocks.insert rn s return blocks /-- The condensation's nodes, as names. -/ @@ -300,9 +302,10 @@ theorem namesOf_size (refs : Ix.Map Ix.Name (Ix.Set Ix.Name)) : theorem blocks_eq (refs : Ix.Map Ix.Name (Ix.Set Ix.Name)) : ∃ hr hv, (CondenseM.run refs).blocks = blocksOfDisc (discOf (namesOf refs) (compsOf refs) (rootNames (namesOf refs) (compsOf refs) hr) hv) := by - refine ⟨fun p h => ?_, fun v h => ?_, rfl⟩ - · rw [namesOf_size]; exact Ix.CompileCert.Canon.condensation_roots_lt (compsOf_eq refs) (Array.of_mem_zip h).2 - · rw [namesOf_size]; exact Ix.CompileCert.Canon.condensation_order_lt (compsOf_eq refs) h + have hr : ∀ p ∈ (compsOf refs).comps.zip (compsOf refs).roots, p.2 < (namesOf refs).size := fun p h => by + rw [namesOf_size] + exact Ix.CompileCert.Canon.condensation_roots_lt (compsOf_eq refs) (Array.of_mem_zip h).2 + exact ⟨hr, hr, rfl⟩ /-! ## Paths in the reference graph and closures -/ @@ -553,70 +556,122 @@ theorem rootNames_get {names : Array Ix.Name} {c : Ix.Compile.Canon.Condensation · exact ih _ h exact hit _ _ (by rw [Array.mem_toList_iff]; exact Array.mem_attach _ _) -/-! ## The nodes in discovery order, and the blocks -/ +/-! ## The nodes, their representatives, and the blocks -/ -/-- **The discovery map holds nodes with their representatives.** -/ +/-- **The node lookup returns a node with its representative.** -/ theorem discOf_get {names : Array Ix.Name} {c : Ix.Compile.Canon.Condensation} - {rn : Vector Ix.Name names.size} {hv : ∀ v ∈ c.order, v < names.size} {key : UInt64} + {rn : Vector Ix.Name names.size} {hv : ∀ p ∈ c.comps.zip c.roots, p.2 < names.size} {key : Nat} {val : Ix.Name × Ix.Name} (h : (discOf names c rn hv).get? key = some val) : ∃ (v : Nat) (hv : v < names.size), val = (names[v], rn[v]) := by - unfold discOf at h - simp only [Id.run, Array.forIn'_pure_yield_eq_foldl] at h - change (Array.foldl (fun b (x : {v // v ∈ c.order}) => - (b.fst.insert b.snd.toUInt64 ((names[x.val]'(hv _ x.property)), rn[x.val]'(hv _ x.property)), b.snd + 1)) - ((∅ : Std.HashMap UInt64 (Ix.Name × Ix.Name)), 0) c.order.attach).fst.get? key = some val at h - rw [← Array.foldl_toList] at h - refine foldl_inv (fun s : Std.HashMap UInt64 (Ix.Name × Ix.Name) × Nat => ∀ key val, s.1.get? key = some val → - ∃ (v : Nat) (hv : v < names.size), val = (names[v], rn[v])) _ _ _ - (fun key val h => by rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_empty] at h; cases h) - (fun s x _ hs key val h => ?_) key val h - dsimp only at h - rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_insert] at h + unfold discOf Disc.get? at h split at h + · rename_i hlt + cases h + exact ⟨key, hlt, rfl⟩ · cases h - exact ⟨x.val, hv _ x.property, rfl⟩ - · rw [← Std.HashMap.get?_eq_getElem?] at h - exact hs key val h - -/-- **A block's members come from the discovery map**: each member of the -block at `rep` hashes like a node's name whose representative hashes like -`rep`. -/ -theorem blocksOfDisc_mem {bd : Std.HashMap UInt64 (Ix.Name × Ix.Name)} {rep x : Ix.Name} - {all : Ix.Set Ix.Name} (h : (blocksOfDisc bd).get? rep = some all) (hx : all.contains x = true) : - ∃ key val, bd.get? key = some val ∧ (val.2 == rep) = true ∧ (val.1 == x) = true := by + +/-- One component's member set, as built by the inner loop of `blocksOfDisc`. -/ +private theorem compSet_mem {names : Array Ix.Name} (rn : Ix.Name) (comp : Array Nat) : + let out := Array.foldl (fun (s : Ix.Set Ix.Name) v => + if hv : v < names.size then s.insert names[v] else s) ({rn} : Ix.Set Ix.Name) comp + Std.HashSet.contains out rn = true ∧ + ∀ x, Std.HashSet.contains out x = true → + (rn == x) = true ∨ ∃ v ∈ comp, ∃ hv : v < names.size, (names[v] == x) = true := by + dsimp only + rw [← Array.foldl_toList] + have h0 : Std.HashSet.contains ({rn} : Ix.Set Ix.Name) rn = true := by + change ((∅ : Ix.Set Ix.Name).insert rn).contains rn = true + exact Std.HashSet.contains_insert_self + have h1 : ∀ x, Std.HashSet.contains ({rn} : Ix.Set Ix.Name) x = true → (rn == x) = true := by + intro x hx + change ((∅ : Ix.Set Ix.Name).insert rn).contains x = true at hx + rw [Std.HashSet.contains_insert, Std.HashSet.contains_empty, Bool.or_false] at hx + exact hx + refine foldl_inv (fun s : Ix.Set Ix.Name => + Std.HashSet.contains s rn = true ∧ + ∀ x, Std.HashSet.contains s x = true → + (rn == x) = true ∨ ∃ v ∈ comp, ∃ hv : v < names.size, (names[v] == x) = true) + _ comp.toList {rn} ⟨h0, fun x hx => .inl (h1 x hx)⟩ ?_ + intro s v hv ⟨hrn, hmem⟩ + split + · rename_i hlt + refine ⟨by rw [Std.HashSet.contains_insert, hrn, Bool.or_true], fun x hx => ?_⟩ + rw [Std.HashSet.contains_insert] at hx + rcases Bool.or_eq_true_iff.mp hx with e | hx + · exact .inr ⟨v, Array.mem_toList_iff.mp hv, hlt, e⟩ + · exact hmem x hx + · exact ⟨hrn, hmem⟩ + +private theorem blocksOfDisc_mem_aux {names : Array Ix.Name} (bd : Disc names) + {rep x : Ix.Name} {all : Ix.Set Ix.Name} + (h : (blocksOfDisc bd).get? rep = some all) (hx : Std.HashSet.contains all x = true) : + ∃ p : {p // p ∈ bd.c.comps.zip bd.c.roots}, + let rn := names[p.1.2]'(bd.hr p.1 p.2) + (rn == rep) = true ∧ + ((rn == x) = true ∨ ∃ v ∈ p.1.1, ∃ hlt : v < names.size, (names[v] == x) = true) := by unfold blocksOfDisc at h - simp only [Id.run, Std.HashMap.forIn_eq_forIn_toList, List.forIn_pure_yield_eq_foldl] at h - change (List.foldl (fun (b : Ix.Map Ix.Name (Ix.Set Ix.Name) × Ix.Map Ix.Name Ix.Name) a => - (Std.HashMap.alter b.fst a.2.snd fun x => match x with - | some s => some (Std.HashSet.insert s a.2.fst) - | none => some {a.2.fst}, - Std.HashMap.insert b.snd a.2.fst a.2.snd)) (∅, ∅) bd.toList).fst.get? rep = some all at h - refine foldl_inv (fun s : Ix.Map Ix.Name (Ix.Set Ix.Name) × Ix.Map Ix.Name Ix.Name => ∀ rep all x, - s.1.get? rep = some all → all.contains x = true → - ∃ key val, bd.get? key = some val ∧ (val.2 == rep) = true ∧ (val.1 == x) = true) _ _ _ - (fun rep all x h _ => by rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_empty] at h; cases h) - (fun s e he hs rep all x h hx => ?_) rep all x h hx - have hbe : bd.get? e.1 = some e.2 := by - rw [Std.HashMap.get?_eq_getElem?, ← Std.HashMap.mem_toList_iff_getElem?_eq_some] - exact he + simp only [Id.run, Array.forIn_pure_yield_eq_foldl, pure_bind, Array.forIn'_pure_yield_eq_foldl] at h + let stepInner (s : Ix.Set Ix.Name) (v : Nat) : Ix.Set Ix.Name := + if hlt : v < names.size then s.insert names[v] else s + change (Array.foldl (fun (b : Ix.Map Ix.Name (Ix.Set Ix.Name)) + (p : {p // p ∈ bd.c.comps.zip bd.c.roots}) => + let rn := names[p.1.2]'(bd.hr p.1 p.2) + b.insert rn (Array.foldl stepInner {rn} p.1.1)) + ∅ (bd.c.comps.zip bd.c.roots).attach).get? rep = some all at h + rw [← Array.foldl_toList] at h + refine foldl_inv (fun b : Ix.Map Ix.Name (Ix.Set Ix.Name) => + ∀ rep all, b.get? rep = some all → ∀ x, Std.HashSet.contains all x = true → + ∃ p : {p // p ∈ bd.c.comps.zip bd.c.roots}, + let rn := names[p.1.2]'(bd.hr p.1 p.2) + (rn == rep) = true ∧ + ((rn == x) = true ∨ ∃ v ∈ p.1.1, ∃ hlt : v < names.size, (names[v] == x) = true)) + _ _ ∅ + (fun rep all h => by rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_empty] at h; cases h) + (fun b p _ ih rep all h x hx => ?_) rep all h x hx dsimp only at h - rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_alter] at h + rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_insert] at h split at h · rename_i same - split at h - · rename_i s hs' - cases h - rw [Std.HashSet.contains_insert] at hx - rcases Bool.or_eq_true_iff.mp hx with e' | hx - · exact ⟨e.1, e.2, hbe, same, e'⟩ - · rw [Std.HashMap.getElem?_congr same, ← Std.HashMap.get?_eq_getElem?] at hs' - exact hs _ _ x hs' hx - · cases h - rw [show ({e.2.1} : Ix.Set Ix.Name) = (∅ : Ix.Set Ix.Name).insert e.2.1 from rfl, - Std.HashSet.contains_insert, Std.HashSet.contains_empty, Bool.or_false] at hx - exact ⟨e.1, e.2, hbe, same, hx⟩ + cases h + exact ⟨p, same, (compSet_mem _ p.1.1).2 x hx⟩ · rw [← Std.HashMap.get?_eq_getElem?] at h - exact hs rep all x h hx + exact ih rep all h x hx + +/-- **A block's members come from a component**: each member of the block at `rep` +hashes like a node's name whose representative hashes like `rep`. -/ +theorem blocksOfDisc_mem {refs : Ix.Map Ix.Name (Ix.Set Ix.Name)} + {hr : ∀ p ∈ (compsOf refs).comps.zip (compsOf refs).roots, p.2 < (namesOf refs).size} + {hv : ∀ p ∈ (compsOf refs).comps.zip (compsOf refs).roots, p.2 < (namesOf refs).size} + {rep x : Ix.Name} {all : Ix.Set Ix.Name} + (h : (blocksOfDisc (discOf (namesOf refs) (compsOf refs) (rootNames (namesOf refs) (compsOf refs) hr) hv)).get? rep = some all) + (hx : all.contains x = true) : + ∃ key val, + (discOf (namesOf refs) (compsOf refs) (rootNames (namesOf refs) (compsOf refs) hr) hv).get? key = some val ∧ + (val.2 == rep) = true ∧ (val.1 == x) = true := by + obtain ⟨⟨⟨comp, r⟩, hp⟩, hrep, hmem⟩ := blocksOfDisc_mem_aux _ h hx + dsimp only [discOf] at hp hrep hmem + have hc := compsOf_eq refs + obtain ⟨k, hk, ek⟩ := Array.mem_iff_getElem.mp hp + rw [Array.getElem_zip] at ek + have hk1 : k < (compsOf refs).comps.size := by rw [Array.size_zip] at hk; omega + have hk2 : k < (compsOf refs).roots.size := by rw [Array.size_zip] at hk; omega + have hcomp : (compsOf refs).comps[k]? = some comp := by + rw [Array.getElem?_eq_getElem hk1, (Prod.mk.inj ek).1] + have hroot : (compsOf refs).roots[k]? = some r := by + rw [Array.getElem?_eq_getElem hk2, (Prod.mk.inj ek).2] + have hrn : r < (namesOf refs).size := hv (comp, r) hp + have hrmem : r ∈ comp := (CondenseRoots.condensation_roots_mem hc).2 k r comp hroot hcomp + obtain ⟨v, hvc, hlt, hvx⟩ : ∃ v ∈ comp, ∃ hlt : v < (namesOf refs).size, ((namesOf refs)[v] == x) = true := by + rcases hmem with erx | ⟨v, hvc, hlt, evx⟩ + · exact ⟨r, hrmem, hrn, erx⟩ + · exact ⟨v, hvc, hlt, evx⟩ + have hrget := rootNames_get (hr := hr) (Ix.CompileCert.Canon.condensation_unique hc) hcomp hroot hvc hlt hrn + refine ⟨v, ((namesOf refs)[v], (rootNames (namesOf refs) (compsOf refs) hr)[v]), ?_, ?_, hvx⟩ + · unfold discOf Disc.get? + simp [hlt] + · dsimp only + rw [hrget] + exact hrep /-! ## Blocks are strongly connected -/ @@ -700,31 +755,29 @@ theorem condense_scc {refs : Ix.Map Ix.Name (Ix.Set Ix.Name)} {rep x y : Ix.Name simp only [namesOf, Array.getElem_map] at hnx hny exact (p.congr_left hnx).congr_right hny -/-- A block of the discovery map's blocks is not empty. -/ -theorem blocksOfDisc_nonempty {bd : Std.HashMap UInt64 (Ix.Name × Ix.Name)} {rep : Ix.Name} +/-- A block of `blocksOfDisc` is not empty. -/ +theorem blocksOfDisc_nonempty {names : Array Ix.Name} {bd : Disc names} {rep : Ix.Name} {all : Ix.Set Ix.Name} (h : (blocksOfDisc bd).get? rep = some all) : all.isEmpty = false := by unfold blocksOfDisc at h - simp only [Id.run, Std.HashMap.forIn_eq_forIn_toList, List.forIn_pure_yield_eq_foldl] at h - change (List.foldl (fun (b : Ix.Map Ix.Name (Ix.Set Ix.Name) × Ix.Map Ix.Name Ix.Name) a => - (Std.HashMap.alter b.fst a.2.snd fun x => match x with - | some s => some (Std.HashSet.insert s a.2.fst) - | none => some {a.2.fst}, - Std.HashMap.insert b.snd a.2.fst a.2.snd)) (∅, ∅) bd.toList).fst.get? rep = some all at h - refine foldl_inv (fun s : Ix.Map Ix.Name (Ix.Set Ix.Name) × Ix.Map Ix.Name Ix.Name => ∀ rep all, - s.1.get? rep = some all → all.isEmpty = false) _ _ _ + simp only [Id.run, Array.forIn_pure_yield_eq_foldl, pure_bind, Array.forIn'_pure_yield_eq_foldl] at h + let stepInner (s : Ix.Set Ix.Name) (v : Nat) : Ix.Set Ix.Name := + if hlt : v < names.size then s.insert names[v] else s + change (Array.foldl (fun (b : Ix.Map Ix.Name (Ix.Set Ix.Name)) + (p : {p // p ∈ bd.c.comps.zip bd.c.roots}) => + let rn := names[p.1.2]'(bd.hr p.1 p.2) + b.insert rn (Array.foldl stepInner {rn} p.1.1)) + ∅ (bd.c.comps.zip bd.c.roots).attach).get? rep = some all at h + rw [← Array.foldl_toList] at h + refine foldl_inv (fun s : Ix.Map Ix.Name (Ix.Set Ix.Name) => ∀ rep all, + s.get? rep = some all → all.isEmpty = false) _ _ ∅ (fun rep all h => by rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_empty] at h; cases h) - (fun s e _ hs rep all h => ?_) rep all h + (fun s p _ hs rep all h => ?_) rep all h dsimp only at h - rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_alter] at h + rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_insert] at h split at h - · rw [Std.HashSet.isEmpty_eq_false_iff_exists_contains_eq_true] - split at h - · cases h - exact ⟨_, Std.HashSet.contains_insert_self⟩ - · cases h - exact ⟨e.2.1, by - rw [show ({e.2.1} : Ix.Set Ix.Name) = (∅ : Ix.Set Ix.Name).insert e.2.1 from rfl] - exact Std.HashSet.contains_insert_self⟩ + · cases h + rw [Std.HashSet.isEmpty_eq_false_iff_exists_contains_eq_true] + exact ⟨_, (compSet_mem _ p.1.1).1⟩ · rw [← Std.HashMap.get?_eq_getElem?] at h exact hs rep all h diff --git a/Ix/Cert/GraphWalk.lean b/Ix/Cert/GraphWalk.lean index e9e4fa259..3b5c5a647 100644 --- a/Ix/Cert/GraphWalk.lean +++ b/Ix/Cert/GraphWalk.lean @@ -7,13 +7,13 @@ import IxC.Kernel.Ingress.Records /-! # The reference graph's walk is exact -`Ix.graphExpr` walks a term with a memo of each expression's references, keyed exactly -(`Ix.Compile.ExactKey.ExprKey`). So the references it returns for a term are exactly the names -the term mentions (`Refs`: a constant it applies, or a projection's structure), up to the names' -hash (`graphExpr_refs`), and `Ix.graphConst` returns a constant's terms' mentions with the names -its kind adds. The memo holds only correct entries (`MemoOK`): an entry is inserted once its -expression's references are computed, and a hit returns the entry of an expression equal to the -one asked. -/ +`Ix.graphExpr` walks a term into an accumulator `acc : Set Ix.Name` with a visited set of +already-walked expressions (`GraphState.visited : Std.HashSet ExprKey`), keyed by structural +equality (`Ix.Compile.ExactKey.ExprKey`). Under the invariant that every expression in `visited` +already has all of its references recorded in `acc` (`VisitedOK`), the references `Ix.graphExpr` +returns for a term from `GraphState.init` are exactly the names the term mentions (`Refs`: a +constant it applies, or a projection's structure), up to the names' hash (`graphExpr_refs`), and +`Ix.graphConst` returns a constant's terms' mentions with the names its kind adds. -/ public section @@ -54,50 +54,47 @@ inductive Refs (n : Ix.Name) : Ix.Expr → Prop | letB {x : Ix.Name} {t v b : Ix.Expr} {nd : Bool} {h : Address} : Refs n b → Refs n (.letE x t v b nd h) | mdata {d : Array (Ix.Name × Ix.DataValue)} {e : Ix.Expr} {h : Address} : Refs n e → Refs n (.mdata d e h) -/-- **The walk's memo is correct**: every entry holds exactly its expression's mentions. -/ -def MemoOK (s : GraphState) : Prop := - ∀ (k : ExprKey) (v : Ix.Set Ix.Name), s.exprCache.get? k = some v → ∀ n, v.contains n = true ↔ Refs n k.expr +/-- **The walk's visited set is sound for `acc`**: every visited expression has all of its +mentions already present in `acc`. -/ +def MemoOK (s : GraphState) (acc : Ix.Set Ix.Name) : Prop := + ∀ (k : ExprKey), s.exprSeen.contains k = true → ∀ n, Refs n k.expr → acc.contains n = true -theorem memoOK_init : MemoOK GraphState.init := fun k v h => by - change (∅ : Std.HashMap ExprKey (Ix.Set Ix.Name)).get? k = some v at h - rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_empty] at h +theorem memoOK_init (acc : Ix.Set Ix.Name) : MemoOK GraphState.init acc := fun k h => by + change (∅ : Std.HashSet ExprKey).contains k = true at h + rw [Std.HashSet.contains_empty] at h cases h -theorem memoOK_insert {s : GraphState} (hs : MemoOK s) {e : Ix.Expr} {v : Ix.Set Ix.Name} - (hv : ∀ n, v.contains n = true ↔ Refs n e) : - MemoOK { s with exprCache := s.exprCache.insert ⟨e⟩ v } := by - intro k w h n - rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_insert] at h - split at h - · rename_i same - cases h - have e' : (⟨e⟩ : ExprKey) = k := beq_iff_eq.mp same +theorem memoOK_mono {s : GraphState} {acc acc' : Ix.Set Ix.Name} (hs : MemoOK s acc) + (hsub : ∀ n, acc.contains n = true → acc'.contains n = true) : MemoOK s acc' := + fun k hk n hr => hsub n (hs k hk n hr) + +theorem memoOK_insert {s : GraphState} {acc : Ix.Set Ix.Name} (hs : MemoOK s acc) {e : Ix.Expr} + (he : ∀ n, Refs n e → acc.contains n = true) : + MemoOK { s with exprSeen := s.exprSeen.insert ⟨e⟩ } acc := by + intro k h n hr + rw [Std.HashSet.contains_insert] at h + rcases Bool.or_eq_true_iff.mp h with same | h + · have e' : (⟨e⟩ : ExprKey) = k := beq_iff_eq.mp same subst e' - exact hv n - · rw [← Std.HashMap.get?_eq_getElem?] at h - exact hs k w h n + exact he n hr + · exact hs k h n hr theorem bind_run {α β : Type} (x : GraphM α) (k : α → GraphM β) (env : Ix.Environment) (s : GraphState) : (x >>= k) env s = k (x env s).1 env (x env s).2 := rfl -/-- One step of the walk, from the memo. -/ -private theorem graphExpr_run (e : Ix.Expr) (env : Ix.Environment) (s : GraphState) : - graphExpr e env s = match s.exprCache.get? ⟨e⟩ with - | some x => (x, s) - | none => ((graphExpr.go e env s).1, - { (graphExpr.go e env s).2 with - exprCache := (graphExpr.go e env s).2.exprCache.insert ⟨e⟩ (graphExpr.go e env s).1 }) := by - rw [graphExpr, bind_run] +theorem pure_run {α : Type} (x : α) (env : Ix.Environment) (s : GraphState) : + (pure x : GraphM α) env s = (x, s) := rfl + +/-- One step of the accumulator walk, from the visited set. -/ +private theorem graphExprAcc_run (e : Ix.Expr) (acc : Ix.Set Ix.Name) (env : Ix.Environment) (s : GraphState) : + graphExprAcc e acc env s = if s.exprSeen.contains ⟨e⟩ then (acc, s) + else ((graphExprAcc.go e acc env s).1, + { (graphExprAcc.go e acc env s).2 with + exprSeen := (graphExprAcc.go e acc env s).2.exprSeen.insert ⟨e⟩ }) := by + rw [graphExprAcc, bind_run] have hg : (get : GraphM GraphState) env s = (s, s) := rfl rw [hg] - cases s.exprCache.get? ⟨e⟩ <;> rfl - -theorem map_run (g : GraphM (Ix.Set Ix.Name)) (t : Ix.Name) (env : Ix.Environment) (s : GraphState) : - ((fun r => Std.HashSet.insert r t) <$> g) env s = ((g env s).1.insert t, (g env s).2) := rfl - -theorem union_run (g h : GraphM (Ix.Set Ix.Name)) (env : Ix.Environment) (s : GraphState) : - (Std.HashSet.union <$> g <*> h) env s = - ((g env s).1.union (h env (g env s).2).1, (h env (g env s).2).2) := rfl + cases s.exprSeen.contains ⟨e⟩ <;> rfl theorem contains_union_iff {a b : Ix.Set Ix.Name} {n : Ix.Name} : (a.union b).contains n = true ↔ a.contains n = true ∨ b.contains n = true := by @@ -108,112 +105,146 @@ theorem contains_insert_iff {a : Ix.Set Ix.Name} {t n : Ix.Name} : (a.insert t).contains n = true ↔ (t == n) = true ∨ a.contains n = true := by rw [Std.HashSet.contains_insert, Bool.or_eq_true] -theorem contains_singleton_iff {t n : Ix.Name} : ({t} : Ix.Set Ix.Name).contains n = true ↔ (t == n) = true := by - change ((∅ : Ix.Set Ix.Name).insert t).contains n = true ↔ _ - rw [contains_insert_iff, Std.HashSet.contains_empty] - simp - theorem not_contains_empty {n : Ix.Name} : ¬ (∅ : Ix.Set Ix.Name).contains n = true := by rw [Std.HashSet.contains_empty] simp -/-- **The walk returns a term's mentions**, from a correct memo, and keeps the memo correct. -/ -theorem graphExpr_refs : ∀ (e : Ix.Expr) (env : Ix.Environment) (s : GraphState), MemoOK s → - MemoOK (graphExpr e env s).2 ∧ ∀ n, (graphExpr e env s).1.contains n = true ↔ Refs n e := by - -- a node whose own step is correct, given its memo's correctness after the step - have node : ∀ (e : Ix.Expr) (env : Ix.Environment) (s : GraphState), MemoOK s → - (MemoOK (graphExpr.go e env s).2 ∧ ∀ n, (graphExpr.go e env s).1.contains n = true ↔ Refs n e) → - MemoOK (graphExpr e env s).2 ∧ ∀ n, (graphExpr e env s).1.contains n = true ↔ Refs n e := by - intro e env s hs ⟨hgs, hgr⟩ - rw [graphExpr_run] +/-- **The accumulator walk adds a term's mentions**, from a sound visited set, and keeps it sound. -/ +theorem graphExprAcc_refs : ∀ (e : Ix.Expr) (acc : Ix.Set Ix.Name) (env : Ix.Environment) (s : GraphState), + MemoOK s acc → + MemoOK (graphExprAcc e acc env s).2 (graphExprAcc e acc env s).1 ∧ + ∀ n, (graphExprAcc e acc env s).1.contains n = true ↔ acc.contains n = true ∨ Refs n e := by + have node : ∀ (e : Ix.Expr) (acc : Ix.Set Ix.Name) (env : Ix.Environment) (s : GraphState), MemoOK s acc → + (MemoOK (graphExprAcc.go e acc env s).2 (graphExprAcc.go e acc env s).1 ∧ + ∀ n, (graphExprAcc.go e acc env s).1.contains n = true ↔ acc.contains n = true ∨ Refs n e) → + MemoOK (graphExprAcc e acc env s).2 (graphExprAcc e acc env s).1 ∧ + ∀ n, (graphExprAcc e acc env s).1.contains n = true ↔ acc.contains n = true ∨ Refs n e := by + intro e acc env s hs ⟨hgs, hgr⟩ + rw [graphExprAcc_run] split - · rename_i x hx - exact ⟨hs, hs ⟨e⟩ x hx⟩ - · exact ⟨memoOK_insert hgs hgr, hgr⟩ + · rename_i hseen + refine ⟨hs, fun n => ⟨fun h => .inl h, fun h => ?_⟩⟩ + rcases h with h | hr + · exact h + · exact hs ⟨e⟩ hseen n hr + · exact ⟨memoOK_insert hgs (fun n hr => (hgr n).mpr (.inr hr)), hgr⟩ intro e induction e with | bvar _ _ | fvar _ _ | mvar _ _ | sort _ _ | lit _ _ => - intro env s hs - refine node _ env s hs ?_ - simp only [graphExpr.go] - exact ⟨hs, fun n => ⟨fun h => absurd h not_contains_empty, fun h => by cases h⟩⟩ + intro acc env s hs + refine node _ acc env s hs ?_ + simp only [graphExprAcc.go, pure_run] + exact ⟨hs, fun n => ⟨fun h => .inl h, fun h => h.elim id fun hr => by cases hr⟩⟩ | const m us h => - intro env s hs - refine node _ env s hs ?_ - simp only [graphExpr.go] - refine ⟨hs, fun n => ?_⟩ - show ({m} : Ix.Set Ix.Name).contains n = true ↔ _ - rw [contains_singleton_iff] - exact ⟨fun h => .const h, fun h => by cases h; assumption⟩ + intro acc env s hs + refine node _ acc env s hs ?_ + simp only [graphExprAcc.go, pure_run] + refine ⟨memoOK_mono hs (fun n hn => contains_insert_iff.mpr (.inr hn)), fun n => ?_⟩ + show (acc.insert m).contains n = true ↔ _ + rw [contains_insert_iff] + exact ⟨fun h => h.elim (.inr ∘ .const) .inl, fun h => h.elim .inr fun hr => by cases hr; exact .inl ‹_›⟩ | app f a h ihf iha => - intro env s hs - refine node _ env s hs ?_ - simp only [graphExpr.go, union_run] - obtain ⟨s1, r1⟩ := ihf env s hs - obtain ⟨s2, r2⟩ := iha env _ s1 + intro acc env s hs + refine node _ acc env s hs ?_ + simp only [graphExprAcc.go, bind_run] + obtain ⟨s1, r1⟩ := ihf acc env s hs + obtain ⟨s2, r2⟩ := iha _ env _ s1 refine ⟨s2, fun n => ?_⟩ - rw [contains_union_iff, r1, r2] - exact ⟨fun h => h.elim .appF .appA, fun h => by cases h <;> simp_all⟩ + rw [r2, r1] + exact ⟨fun h => h.elim (fun h => h.elim .inl (.inr ∘ .appF)) (.inr ∘ .appA), + fun h => h.elim (.inl ∘ .inl) fun hr => by cases hr with + | appF hf => exact .inl (.inr hf) + | appA ha => exact .inr ha⟩ | lam x t b bi h iht ihb => - intro env s hs - refine node _ env s hs ?_ - simp only [graphExpr.go, union_run] - obtain ⟨s1, r1⟩ := iht env s hs - obtain ⟨s2, r2⟩ := ihb env _ s1 + intro acc env s hs + refine node _ acc env s hs ?_ + simp only [graphExprAcc.go, bind_run] + obtain ⟨s1, r1⟩ := iht acc env s hs + obtain ⟨s2, r2⟩ := ihb _ env _ s1 refine ⟨s2, fun n => ?_⟩ - rw [contains_union_iff, r1, r2] - exact ⟨fun h => h.elim .lamT .lamB, fun h => by cases h <;> simp_all⟩ + rw [r2, r1] + exact ⟨fun h => h.elim (fun h => h.elim .inl (.inr ∘ .lamT)) (.inr ∘ .lamB), + fun h => h.elim (.inl ∘ .inl) fun hr => by cases hr with + | lamT ht => exact .inl (.inr ht) + | lamB hb => exact .inr hb⟩ | forallE x t b bi h iht ihb => - intro env s hs - refine node _ env s hs ?_ - simp only [graphExpr.go, union_run] - obtain ⟨s1, r1⟩ := iht env s hs - obtain ⟨s2, r2⟩ := ihb env _ s1 + intro acc env s hs + refine node _ acc env s hs ?_ + simp only [graphExprAcc.go, bind_run] + obtain ⟨s1, r1⟩ := iht acc env s hs + obtain ⟨s2, r2⟩ := ihb _ env _ s1 refine ⟨s2, fun n => ?_⟩ - rw [contains_union_iff, r1, r2] - exact ⟨fun h => h.elim .allT .allB, fun h => by cases h <;> simp_all⟩ + rw [r2, r1] + exact ⟨fun h => h.elim (fun h => h.elim .inl (.inr ∘ .allT)) (.inr ∘ .allB), + fun h => h.elim (.inl ∘ .inl) fun hr => by cases hr with + | allT ht => exact .inl (.inr ht) + | allB hb => exact .inr hb⟩ | letE x t v b nd h iht ihv ihb => - intro env s hs - refine node _ env s hs ?_ - simp only [graphExpr.go, union_run] - obtain ⟨s1, r1⟩ := iht env s hs - obtain ⟨s2, r2⟩ := ihv env _ s1 - obtain ⟨s3, r3⟩ := ihb env _ s2 + intro acc env s hs + refine node _ acc env s hs ?_ + simp only [graphExprAcc.go, bind_run] + obtain ⟨s1, r1⟩ := iht acc env s hs + obtain ⟨s2, r2⟩ := ihv _ env _ s1 + obtain ⟨s3, r3⟩ := ihb _ env _ s2 refine ⟨s3, fun n => ?_⟩ - rw [contains_union_iff, r1, contains_union_iff, r2, r3] - exact ⟨fun h => h.elim .letT (fun h => h.elim .letV .letB), fun h => by cases h <;> simp_all⟩ + rw [r3, r2, r1] + exact ⟨fun h => h.elim (fun h => h.elim (fun h => h.elim .inl (.inr ∘ .letT)) (.inr ∘ .letV)) (.inr ∘ .letB), + fun h => h.elim (.inl ∘ .inl ∘ .inl) fun hr => by cases hr with + | letT ht => exact .inl (.inl (.inr ht)) + | letV hv => exact .inl (.inr hv) + | letB hb => exact .inr hb⟩ | mdata d e h ih => - intro env s hs - refine node _ env s hs ?_ - simp only [graphExpr.go] - obtain ⟨s1, r1⟩ := ih env s hs - exact ⟨s1, fun n => (r1 n).trans ⟨.mdata, fun h => by cases h; assumption⟩⟩ + intro acc env s hs + refine node _ acc env s hs ?_ + simp only [graphExprAcc.go] + obtain ⟨s1, r1⟩ := ih acc env s hs + refine ⟨s1, fun n => (r1 n).trans ⟨fun h => h.elim .inl (.inr ∘ .mdata), fun h => ?_⟩⟩ + rcases h with h | hr + · exact .inl h + · cases hr; exact .inr ‹_› | proj t i s' h ih => - intro env s hs - refine node _ env s hs ?_ - simp only [graphExpr.go, map_run] - obtain ⟨s1, r1⟩ := ih env s hs - refine ⟨s1, fun n => ?_⟩ + intro acc env s hs + refine node _ acc env s hs ?_ + simp only [graphExprAcc.go, bind_run, pure_run] + obtain ⟨s1, r1⟩ := ih acc env s hs + have hins : MemoOK (graphExprAcc s' acc env s).2 ((graphExprAcc s' acc env s).1.insert t) := + memoOK_mono s1 (fun n hn => contains_insert_iff.mpr (.inr hn)) + refine ⟨hins, fun n => ?_⟩ + show ((graphExprAcc s' acc env s).1.insert t).contains n = true ↔ _ rw [contains_insert_iff, r1] - exact ⟨fun h => h.elim .projName .projStruct, fun h => by cases h <;> simp_all⟩ - -/-- Two terms walked in turn, from the empty memo: the union of their mentions. -/ + exact ⟨fun h => h.elim (.inr ∘ .projName) (fun h => h.elim .inl (.inr ∘ .projStruct)), + fun h => h.elim (.inr ∘ .inl) fun hr => by cases hr with + | projName ht => exact .inl ht + | projStruct hs => exact .inr (.inr hs)⟩ + +/-- **The walk returns a term's mentions**, from the empty accumulator. -/ +theorem graphExpr_refs (e : Ix.Expr) (env : Ix.Environment) (s : GraphState) (hs : MemoOK s ∅) : + MemoOK (graphExpr e env s).2 (graphExpr e env s).1 ∧ + ∀ n, (graphExpr e env s).1.contains n = true ↔ Refs n e := by + unfold graphExpr + obtain ⟨s1, r1⟩ := graphExprAcc_refs e ∅ env s hs + refine ⟨s1, fun n => (r1 n).trans ⟨fun h => h.elim (fun h => absurd h not_contains_empty) id, .inr⟩⟩ + +/-- Two terms walked in turn into one accumulator, from the empty visited set: the union of their mentions. -/ theorem union_init (a b : Ix.Expr) (env : Ix.Environment) (n : Ix.Name) : - ((Std.HashSet.union <$> graphExpr a <*> graphExpr b) env GraphState.init).1.contains n = true ↔ + ((do let acc ← graphExprAcc a ∅; graphExprAcc b acc) env GraphState.init).1.contains n = true ↔ Refs n a ∨ Refs n b := by - rw [union_run, contains_union_iff] - obtain ⟨s1, r1⟩ := graphExpr_refs a env GraphState.init memoOK_init - rw [r1, (graphExpr_refs b _ _ s1).2] + rw [bind_run] + obtain ⟨s1, r1⟩ := graphExprAcc_refs a ∅ env GraphState.init (memoOK_init ∅) + obtain ⟨_, r2⟩ := graphExprAcc_refs b _ env _ s1 + rw [r2, r1] + exact ⟨fun h => h.elim (fun h => h.elim (fun h => absurd h not_contains_empty) .inl) .inr, + fun h => h.elim (.inl ∘ .inr) .inr⟩ /-! ## A constant's references -/ theorem graphConst_axiom (v : Ix.AxiomVal) (env : Ix.Environment) (n : Ix.Name) : (graphConst (.axiomInfo v) env GraphState.init).1.contains n = true ↔ Refs n v.cnst.type := - (graphExpr_refs v.cnst.type env GraphState.init memoOK_init).2 n + (graphExpr_refs v.cnst.type env GraphState.init (memoOK_init ∅)).2 n theorem graphConst_quot (v : Ix.QuotVal) (env : Ix.Environment) (n : Ix.Name) : (graphConst (.quotInfo v) env GraphState.init).1.contains n = true ↔ Refs n v.cnst.type := - (graphExpr_refs v.cnst.type env GraphState.init memoOK_init).2 n + (graphExpr_refs v.cnst.type env GraphState.init (memoOK_init ∅)).2 n theorem graphConst_defn (v : Ix.DefinitionVal) (env : Ix.Environment) (n : Ix.Name) : (graphConst (.defnInfo v) env GraphState.init).1.contains n = true ↔ @@ -232,38 +263,45 @@ theorem graphConst_opaque (v : Ix.OpaqueVal) (env : Ix.Environment) (n : Ix.Name theorem graphConst_induct (v : Ix.InductiveVal) (env : Ix.Environment) {n : Ix.Name} (hn : Refs n v.cnst.type) : (graphConst (.inductInfo v) env GraphState.init).1.contains n = true := by - have ht := ((graphExpr_refs v.cnst.type env GraphState.init memoOK_init).2 n).mpr hn + have ht := ((graphExpr_refs v.cnst.type env GraphState.init (memoOK_init ∅)).2 n).mpr hn exact contains_union_iff.mpr (.inl ht) theorem graphConst_ctor (v : Ix.ConstructorVal) (env : Ix.Environment) {n : Ix.Name} (hn : Refs n v.cnst.type) : (graphConst (.ctorInfo v) env GraphState.init).1.contains n = true := by - have ht := ((graphExpr_refs v.cnst.type env GraphState.init memoOK_init).2 n).mpr hn + have ht := ((graphExpr_refs v.cnst.type env GraphState.init (memoOK_init ∅)).2 n).mpr hn exact contains_insert_iff.mpr (.inr ht) -/-- The recursor loop: from a correct memo, the references it starts from stay and each rule's -right-hand side's mentions join them, for a body that adds a rule's constructor and walks its -right-hand side. -/ +/-- The recursor loop: from a sound visited set, the references it starts from stay, each rule's +constructor is added, and each rule's right-hand side's mentions join them, for a body that adds a +rule's constructor and walks its right-hand side into the accumulator. -/ theorem forIn_rules (f : Ix.RecursorRule → Ix.Set Ix.Name → GraphM (ForInStep (Ix.Set Ix.Name))) (hf : ∀ rule r env s, f rule r env s = - (.yield ((r.insert rule.ctor).union (graphExpr rule.rhs env s).1), (graphExpr rule.rhs env s).2)) + (.yield (graphExprAcc rule.rhs (r.insert rule.ctor) env s).1, + (graphExprAcc rule.rhs (r.insert rule.ctor) env s).2)) (env : Ix.Environment) : - ∀ (rules : List Ix.RecursorRule) (r0 : Ix.Set Ix.Name) (s : GraphState), MemoOK s → + ∀ (rules : List Ix.RecursorRule) (r0 : Ix.Set Ix.Name) (s : GraphState), MemoOK s r0 → (∀ n, r0.contains n = true → (forIn rules r0 f env s).1.contains n = true) ∧ + (∀ rule ∈ rules, (forIn rules r0 f env s).1.contains rule.ctor = true) ∧ ∀ rule ∈ rules, ∀ n, Refs n rule.rhs → (forIn rules r0 f env s).1.contains n = true - | [], r0, s, _ => ⟨fun _ h => h, fun _ h => nomatch h⟩ + | [], r0, s, _ => ⟨fun _ h => h, fun _ h => (nomatch h), fun _ h => (nomatch h)⟩ | rule :: rules, r0, s, hs => by - obtain ⟨s1, r1⟩ := graphExpr_refs rule.rhs env s hs + have hs' : MemoOK s (r0.insert rule.ctor) := + memoOK_mono hs (fun n hn => contains_insert_iff.mpr (.inr hn)) + obtain ⟨s1, r1⟩ := graphExprAcc_refs rule.rhs (r0.insert rule.ctor) env s hs' have step : forIn (rule :: rules) r0 f env s = - forIn rules ((r0.insert rule.ctor).union (graphExpr rule.rhs env s).1) f env - (graphExpr rule.rhs env s).2 := by + forIn rules (graphExprAcc rule.rhs (r0.insert rule.ctor) env s).1 f env + (graphExprAcc rule.rhs (r0.insert rule.ctor) env s).2 := by rw [List.forIn_cons, bind_run, hf] rw [step] - obtain ⟨keep, rest⟩ := forIn_rules f hf env rules _ _ s1 - refine ⟨fun n h => keep n (contains_union_iff.mpr (.inl (contains_insert_iff.mpr (.inr h)))), - fun rule' hr n hn => ?_⟩ - rcases List.mem_cons.mp hr with rfl | hr - · exact keep n (contains_union_iff.mpr (.inr ((r1 n).mpr hn))) - · exact rest rule' hr n hn + obtain ⟨keep, ctors, rest⟩ := forIn_rules f hf env rules _ _ s1 + refine ⟨fun n h => keep n ((r1 n).mpr (.inl (contains_insert_iff.mpr (.inr h)))), + fun rule' hr => ?_, fun rule' hr n hn => ?_⟩ + · rcases List.mem_cons.mp hr with rfl | hr + · exact keep _ ((r1 _).mpr (.inl (contains_insert_iff.mpr (.inl BEq.rfl)))) + · exact ctors rule' hr + · rcases List.mem_cons.mp hr with rfl | hr + · exact keep n ((r1 n).mpr (.inr hn)) + · exact rest rule' hr n hn /-- Inserting names keeps the names held. -/ theorem foldl_insert_keeps (rs : Ix.Set Ix.Name) (p : Ix.Name → Bool) (l : Array Ix.Name) {n : Ix.Name} @@ -282,10 +320,10 @@ theorem foldl_insert_keeps (rs : Ix.Set Ix.Name) (p : Ix.Name → Bool) (l : Arr theorem graphConst_rec (v : Ix.RecursorVal) (env : Ix.Environment) {n : Ix.Name} (hn : Refs n v.cnst.type ∨ ∃ rule ∈ v.rules.toList, Refs n rule.rhs) : (graphConst (.recInfo v) env GraphState.init).1.contains n = true := by - obtain ⟨st, rt⟩ := graphExpr_refs v.cnst.type env GraphState.init memoOK_init + obtain ⟨st, rt⟩ := graphExpr_refs v.cnst.type env GraphState.init (memoOK_init ∅) rw [graphConst, bind_run, bind_run, ← Array.forIn_toList] refine foldl_insert_keeps _ _ _ ?_ - obtain ⟨keep, rules⟩ := forIn_rules _ (fun _ _ _ _ => rfl) env v.rules.toList _ _ st + obtain ⟨keep, -, rules⟩ := forIn_rules _ (fun _ _ _ _ => rfl) env v.rules.toList _ _ st rcases hn with hn | ⟨rule, hr, hn⟩ · exact keep n ((rt n).mpr hn) · exact rules rule hr n hn diff --git a/Ix/Cert/GroupRefs.lean b/Ix/Cert/GroupRefs.lean index 0c1ebb653..3d8fa4a77 100644 --- a/Ix/Cert/GroupRefs.lean +++ b/Ix/Cert/GroupRefs.lean @@ -352,70 +352,49 @@ theorem scanGroup_covers (blocks : Ix.CondensedBlocks) (groupOf : Ix.Map TableNa /-! ## The condensation: a name's block and the block's references -/ -/-- `CondenseM.run`'s loop that files each name under its representative. -/ -def discMaps (byDiscovery : Std.HashMap UInt64 (Ix.Name × Ix.Name)) : - Ix.Map Ix.Name (Ix.Set Ix.Name) × Ix.Map Ix.Name Ix.Name := Id.run do - let mut blocks : Ix.Map Ix.Name (Ix.Set Ix.Name) := {} +/-- `CondenseM.run`'s loop that files each block's members under the block's representative. -/ +def lowLinksOf (blocks : Ix.Map Ix.Name (Ix.Set Ix.Name)) : Ix.Map Ix.Name Ix.Name := Id.run do let mut lowLinks : Ix.Map Ix.Name Ix.Name := {} - for (_, (name, lowName)) in byDiscovery do - lowLinks := lowLinks.insert name lowName - blocks := blocks.alter lowName fun x => match x with - | .some s => .some (s.insert name) - | .none => .some {name} - return (blocks, lowLinks) - -/-- **`CondenseM.run`'s representatives and blocks, from one loop.** -/ -theorem condense_maps (refs : Ix.Map Ix.Name (Ix.Set Ix.Name)) : - ∃ bd, (CondenseM.run refs).blocks = (discMaps bd).1 ∧ (CondenseM.run refs).lowLinks = (discMaps bd).2 := by - obtain ⟨hr, hv, -⟩ := Condense.blocks_eq refs - exact ⟨Condense.discOf (Condense.namesOf refs) (Condense.compsOf refs) - (Condense.rootNames (Condense.namesOf refs) (Condense.compsOf refs) hr) hv, rfl, rfl⟩ - -theorem discMaps_lowLinks (bd : Std.HashMap UInt64 (Ix.Name × Ix.Name)) {n lo : Ix.Name} - (h : (discMaps bd).2.get? n = some lo) : - ∃ all, (discMaps bd).1.get? lo = some all ∧ all.contains n = true := by - let I : Ix.Map Ix.Name (Ix.Set Ix.Name) × Ix.Map Ix.Name Ix.Name → Prop := fun s => - ∀ n lo, s.2.get? n = some lo → ∃ all, s.1.get? lo = some all ∧ all.contains n = true - suffices hI : I (discMaps bd) from hI n lo h - unfold discMaps - simp only [Id.run, Std.HashMap.forIn_eq_forIn_toList, List.forIn_pure_yield_eq_foldl] + for (rn, s) in blocks do + for nm in s do + lowLinks := lowLinks.insert nm rn + return lowLinks + +theorem lowLinks_eq (refs : Ix.Map Ix.Name (Ix.Set Ix.Name)) : + (CondenseM.run refs).lowLinks = lowLinksOf (CondenseM.run refs).blocks := rfl + +theorem lowLinksOf_get (blocks : Ix.Map Ix.Name (Ix.Set Ix.Name)) {n lo : Ix.Name} + (h : (lowLinksOf blocks).get? n = some lo) : + ∃ all, blocks.get? lo = some all ∧ all.contains n = true := by + let I : Ix.Map Ix.Name Ix.Name → Prop := fun m => + ∀ n lo, m.get? n = some lo → ∃ all, blocks.get? lo = some all ∧ all.contains n = true + suffices hI : I (lowLinksOf blocks) from hI n lo h + unfold lowLinksOf + simp only [Id.run, Std.HashMap.forIn_eq_forIn_toList, Std.HashSet.forIn_eq_forIn_toList, + List.forIn_pure_yield_eq_foldl, pure_bind] refine Condense.foldl_inv I _ _ _ (fun n lo h => by - rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_empty] at h; cases h) fun s x _ hs n lo h => ?_ - obtain ⟨d, name, lowName⟩ := x - change (s.2.insert name lowName).get? n = some lo at h - change ∃ all, (s.1.alter lowName fun x => match x with - | .some s => .some (s.insert name) - | .none => .some {name}).get? lo = some all ∧ all.contains n = true - rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_insert] at h - rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_alter] - split at h + rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_empty] at h; cases h) fun m x hx hm => ?_ + obtain ⟨rn, s⟩ := x + have hblk : blocks.get? rn = some s := Condense.get_of_mem_toList hx (BEq.refl rn) + refine Condense.foldl_inv I _ _ _ hm fun m' nm hnm hm' n' lo' h' => ?_ + change (m'.insert nm rn).get? n' = some lo' at h' + rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_insert] at h' + split at h' · rename_i same - have e := Option.some.inj h - subst e - simp only [BEq.rfl, ↓reduceIte] - cases s.1[lowName]? with - | some s0 => exact ⟨_, rfl, by rw [Std.HashSet.contains_insert, same, Bool.true_or]⟩ - | none => - refine ⟨_, rfl, ?_⟩ - show (({} : Ix.Set Ix.Name).insert name).contains n = true - rw [Std.HashSet.contains_insert, same, Bool.true_or] - · rw [← Std.HashMap.get?_eq_getElem?] at h - obtain ⟨all, hall, hn⟩ := hs n lo h - split - · rename_i same - rw [Std.HashMap.getElem?_congr same, ← Std.HashMap.get?_eq_getElem?, hall] - exact ⟨_, rfl, by rw [Std.HashSet.contains_insert, hn, Bool.or_true]⟩ - · rw [← Std.HashMap.get?_eq_getElem?] - exact ⟨all, hall, hn⟩ + cases h' + have hnm_in : s.contains nm = true := + Std.HashSet.mem_iff_contains.mp (Std.HashSet.mem_of_mem_toList hnm) + rw [Std.HashSet.contains_congr same] at hnm_in + exact ⟨s, hblk, hnm_in⟩ + · rw [← Std.HashMap.get?_eq_getElem?] at h' + exact hm' n' lo' h' /-- **A name's representative's block holds the name.** -/ theorem condense_lowLinks {refs : Ix.Map Ix.Name (Ix.Set Ix.Name)} {n lo : Ix.Name} (h : (CondenseM.run refs).lowLinks.get? n = some lo) : ∃ all, (CondenseM.run refs).blocks.get? lo = some all ∧ all.contains n = true := by - obtain ⟨bd, hb, hl⟩ := condense_maps refs - rw [hl] at h - rw [hb] - exact discMaps_lowLinks bd h + rw [lowLinks_eq] at h + exact lowLinksOf_get (CondenseM.run refs).blocks h /-- A fold of unions holds every element of a listed member's set. -/ theorem foldl_union_contains (refs : Ix.Map Ix.Name (Ix.Set Ix.Name)) {a r : Ix.Name} diff --git a/Ix/Cert/Hybrid.lean b/Ix/Cert/Hybrid.lean index 3db04c90f..2321758f1 100644 --- a/Ix/Cert/Hybrid.lean +++ b/Ix/Cert/Hybrid.lean @@ -6,8 +6,8 @@ import Ix.Cert.Statement /-! # The hybrid environment compiles as the materialized one `Ix.Cert.compile` runs the driver `Ix.CompileM.compileEnvFold` over the -materialized environment (`{ consts := env.consts }`); the IO pipeline -(`Ix.CompileM.compileDecoratedConsts`) runs it over a hybrid one: the code +materialized environment (`{ consts := env.consts }`); the hybrid model +runs it over a hybrid one: the code kinds materialized, theorem and opaque bodies canonicalized again on demand through `fallback?` (`hybridEnv`). The driver and its validations read the environment through `Ix.Environment.get?` only, apart from the order of the @@ -39,14 +39,17 @@ restatements, behaviour-identical). So: one condensation, under those premises. **What stays a premise, and why:** the group order is no longer one (the -validations order groups by the first members' set). The IO layer's own steps are tested, not proved: -that its loops build the hybrid environment's pieces as stated (`code`, -`canon`, and `inductives`: the code constants hold exactly the input's -inductives), that its streamed -canonicalization, graph, ground filter and condensation are -`Ix.Cert.groundSplit`'s and `blocksOf`'s (`cert-compile-parity-initstd`), and -that the blocks of one wave are independent (`compile-schedule-identity`'s -reference schedule). -/ +validations order groups by the first members' set). For the hybrid model, +its premises record that the materialized code constants and fallback +canonicalization match `Ix.Cert.Canon.constS` (`code`, `canon`, and +`inductives`: the code constants hold exactly the input's inductives). In the +certified production path, `Ix.Compile.ReadyCompile.reportIO` proves directly +by type that the IO worker-queue pipeline returns `Ix.Cert.compileReport`, and +`Ix.Cert.FreshSnapshot` proves wave-snapshot freshness and disjointness across +blocks in a wave (used to prove semantic correctness of the wave-snapshot fold +directly; equivalence between the wave-snapshot fold and a one-block-at-a-time +live-state fold is a property of block independence within a wave, not proved +in `Ix.Cert.Schedule`). -/ namespace Ix.Cert.Hybrid @@ -178,8 +181,8 @@ theorem compileEnvFold_lookups {e₁ e₂ : Ix.Environment} (lookups : ∀ n, e /-! ## The IO pipeline's environment -/ -/-- The IO pipeline's hybrid environment, as `compileDecoratedConsts` builds -it from its pieces: the materialized code constants, and every input +/-- The hybrid environment, built +from its pieces: the materialized code constants, and every input constant canonicalized again on demand. -/ def hybridEnv (codeConsts : Std.HashMap Ix.Name Ix.ConstantInfo) (leanByIx : Std.HashMap Ix.Name (Lean.Name × Lean.ConstantInfo)) : Ix.Environment := diff --git a/Ix/Cert/ModuleAudit.lean b/Ix/Cert/ModuleAudit.lean index db9419327..681e9241a 100644 --- a/Ix/Cert/ModuleAudit.lean +++ b/Ix/Cert/ModuleAudit.lean @@ -1,11 +1,14 @@ import IxC.Kernel.Audit.Axioms +import Ix.Cert.Canon import Ix.Cert.Closure +import Ix.Cert.CompileCorrect import Ix.Cert.ConversionValues import Ix.Cert.Hybrid import Ix.Cert.ReaderClosureKeys import Ix.Compile.ReadyCompile import Ix.CompileCertified import Ix.HostAdmission.Theorems +import Ix.LeanIngress.NominalQuotient import Ix.RoleBinding.ArtifactOrderTheorems import Ix.RoleBinding.ArtifactTheorems import Ix.RoleBinding.ArtifactTieredTheorems @@ -59,11 +62,30 @@ bound. A named module fails if it is no longer imported or declares nothing. -/ Ix.Compile.ReadyQueue Ix.Compile.ReadyCompile Ix.CompileCertified + Ix.LeanIngress.Theorems + Ix.LeanIngress.NominalQuotient #guard_kernel_axioms Ix.RoleBinding.RecordDecoding.parallel_eq [propext, Classical.choice, Quot.sound] #guard_kernel_axioms Ix.RoleBinding.recordStore_eq_parallel [propext, Classical.choice, Quot.sound] #guard_kernel_axioms Ix.CompileM.WorkerSnapshot.runBlock_same [propext, Classical.choice, Quot.sound] #guard_kernel_axioms Ix.CompileM.waveTasks_eq_projected [propext, Classical.choice, Quot.sound] #guard_kernel_axioms Ix.Compile.ReadyCompile.reportIO [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.LeanIngress.prepareSourceCollapsedAt_trace [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.LeanIngress.Converted.quotient_eq_substKeys_canon [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.LeanIngress.canonDecl_substKeys [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.LeanIngress.checkGroupFiberSafe_sound [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.LeanIngress.collapseRead_substKeys_canon_beqF [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Cert.EnvSubstRel.toModelPullback [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Cert.EnvSubstRel.toPullback [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Cert.IxonCommutesCovering.nominalModelPullback [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Cert.IxonCommutesCovering.nominalBridgeAt [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Cert.IxonCommutesCovering.nominalPullback [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Cert.NominalSemanticBridgeAt.of_covering [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Cert.CompileCorrect.nominalBridgeAt [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Cert.CompileCorrect.nominalPullback [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.model_exists_of_no_user_axioms [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.checkDecls_consistent [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Cached.checkDeclsWithUserAxioms_sound [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Cached.no_proof_of_False_cached_userAxioms [propext, Classical.choice, Quot.sound] #guard_module_axioms_under Ix.Cert Ix.CompileCert diff --git a/Ix/Cert/Quotient.lean b/Ix/Cert/Quotient.lean index ad13c1eef..02dd3f216 100644 --- a/Ix/Cert/Quotient.lean +++ b/Ix/Cert/Quotient.lean @@ -1,4 +1,5 @@ import Ix.Cert.Statement +import Ix.Cert.SourceLaws import Ix.LeanIngress.Rename import IxC.Kernel.Verify.Rename.Model import IxC.Kernel.Ixon.ReaderSpec @@ -28,7 +29,19 @@ the statements need around it: names sit at is a reference accept at the artifact's addresses. Its evidence is the bundle's renamed by `evAddr` (a bundle-only name goes to the bundle's address for it, the content address its records cite), not the Ixon run's; only injectivity is used, - and moving acceptance between evidences is `Ix.Cert.Evidence.acceptsRef_covers`'s. -/ + and moving acceptance between evidences is `Ix.Cert.Evidence.acceptsRef_covers`'s; +* `EnvSubstRel`, `EnvSubstModelPullback`, `EnvSubstPullback`: conditional model-theoretic + pullbacks from the quotient Source environment `envQ` to a nominal Source environment + `envS` along the fiber-alias substitution `Key.subst ρ`. Quotient Source (`CompileCorrect` + together with `Ix.LeanIngress.prepareSourceCollapsedAt_trace`) is the primary + unconditional security guarantee: quotient collapse only merges declarations that are + structurally identical (`Declaration.beqF`, hence `=`) under `Key.subst ρ`, and + `CompileCorrect` transports both `ValueModel` and `LawModel` unconditionally between + `envQ` and `envA`. Pulling models of `envQ` back to a nominal `envS` is conditioned on + `EnvSubstRel` (for `ValueModel`) and `LawModel.Rel` (for `LawModel`); deriving + `LawModel.Rel` from `EnvSubstRel` and discharging `EnvSubstRel` from + `Ix.LeanIngress.FiberAliasSafe` on closures whose alias fibers do not mix reducibility + hints is left for future work. -/ namespace Ix.Cert @@ -267,4 +280,511 @@ theorem sourceAcceptsQ_addrAt {condense : Ix.Environment → Ix.CondensedBlocks} Ix.LeanIngress.acceptsRef_prepareSourceCollapsedAt .verified (tableBound_of checked bound) prepared run exact ⟨b, hb, ds, envA, runA, rel⟩ +/-! ## Semantic model pullback and composition along non-injective `Key.subst ρ` -/ + +/-- **Law-model pullback along a key map `σ`**: every law model of `env₂` +pulls back to a law model of `env₁` whose constant values agree with `env₂`'s +along `σ`. -/ +def LawModel.Rel {md₁ md₂ : Ix.Kernel.Mode} + (σ : Ix.Kernel.Key md₁ → Ix.Kernel.Key md₂) + (env₁ : Env md₁) (env₂ : Env md₂) : Prop := + ∀ (V : Type w) [SetTheory V] (M : LawModel V env₂), + ∃ N : LawModel V env₁, ∀ k φ, N.cval k φ = M.cval (σ k) φ + +/-- **Composition of `ValueModel.Rel`**: pulling back along `f` and `g` pulls back +along `g ∘ f`. -/ +theorem ValueModel.comp {md₁ md₂ md₃ : Ix.Kernel.Mode} + {f : Ix.Kernel.Key md₁ → Ix.Kernel.Key md₂} + {g : Ix.Kernel.Key md₂ → Ix.Kernel.Key md₃} + {env₁ : Env md₁} {env₂ : Env md₂} {env₃ : Env md₃} + (hf : ValueModel.Rel.{w} f env₁ env₂) + (hg : ValueModel.Rel.{w} g env₂ env₃) : + ValueModel.Rel.{w} (g ∘ f) env₁ env₃ := by + intro V _ M₃ + obtain ⟨M₂, hM₂⟩ := hg V M₃ + obtain ⟨M₁, hM₁⟩ := hf V M₂ + exact ⟨M₁, fun k φ => (hM₁ k φ).trans (hM₂ (f k) φ)⟩ + +/-- **Composition of `LawModel.Rel`**: pulling back along `f` and `g` pulls back +along `g ∘ f`. -/ +theorem LawModel.comp {md₁ md₂ md₃ : Ix.Kernel.Mode} + {f : Ix.Kernel.Key md₁ → Ix.Kernel.Key md₂} + {g : Ix.Kernel.Key md₂ → Ix.Kernel.Key md₃} + {env₁ : Env md₁} {env₂ : Env md₂} {env₃ : Env md₃} + (hf : LawModel.Rel.{w} f env₁ env₂) + (hg : LawModel.Rel.{w} g env₂ env₃) : + LawModel.Rel.{w} (g ∘ f) env₁ env₃ := by + intro V _ M₃ + obtain ⟨M₂, hM₂⟩ := hg V M₃ + obtain ⟨M₁, hM₁⟩ := hf V M₂ + exact ⟨M₁, fun k φ => (hM₁ k φ).trans (hM₂ (f k) φ)⟩ + +/-- `ValueModel.pullback` packaged as a `ValueModel.Rel` along `Key.map f`. -/ +theorem ValueModel.pullback_rel {md md' : Ix.Kernel.Mode} + {P : md.Id → Prop} {f : md.Id → md'.Id} {env : Env md} {env' : Env md'} + (h : Ix.Kernel.EnvRel P f env env') : + ValueModel.Rel.{w} (Ix.Kernel.Key.map f) env env' := + fun _ _ N => ⟨ValueModel.pullback h N, fun _ _ => rfl⟩ + +/-- **The pulled-back constant values along `Key.subst ρ`**: `k` denotes what +`Key.subst ρ k` does. -/ +def substCval {V : Type w} (ρ : Ix.LeanIngress.FiberMap) + (cval' : Ix.Kernel.Key .source → (LevelParam → Nat) → V) : + Ix.Kernel.Key .source → (LevelParam → Nat) → V := + fun k φ => cval' (Ix.LeanIngress.Key.subst ρ k) φ + +/-- Rename a projection-table entry's keys by `Key.subst ρ`. -/ +def ProjEntry.substKeys (ρ : Ix.LeanIngress.FiberMap) + (e : Ix.Kernel.ProjEntry .source) : Ix.Kernel.ProjEntry .source := + { structName := Ix.LeanIngress.Key.subst ρ e.structName, + idx := e.idx, + levelParams := e.levelParams, + numParams := e.numParams, + ctor := Ix.LeanIngress.Key.subst ρ e.ctor, + numFields := e.numFields, + body := Ix.LeanIngress.Expr.substKeys ρ e.body, + fieldSort := e.fieldSort, + structSort := e.structSort, + off := e.off } + +theorem ProjTable.substKeys_entry (ρ : Ix.LeanIngress.FiberMap) + (t : Ix.Kernel.ProjTable .source) (i : Nat) : + (Ix.LeanIngress.ProjTable.substKeys ρ t).entry i = + ProjEntry.substKeys ρ (t.entry i) := by + simp only [Ix.Kernel.ProjTable.entry, Ix.LeanIngress.ProjTable.substKeys, + ProjEntry.substKeys, Array.getD_eq_getD_getElem?, Array.getElem?_map] + cases t.bodies[i]? <;> rfl + +@[simp] theorem natLitToConstructor_substKeys (ρ : Ix.LeanIngress.FiberMap) (n : Nat) : + Ix.LeanIngress.Expr.substKeys ρ (Ix.Kernel.natLitToConstructor n) = + Ix.Kernel.natLitToConstructor n := by + cases n <;> rfl + +@[simp] theorem strLitToConstructor_substKeys (ρ : Ix.LeanIngress.FiberMap) (s : String) : + Ix.LeanIngress.Expr.substKeys ρ (Ix.Kernel.strLitToConstructor s) = + Ix.Kernel.strLitToConstructor s := by + unfold Ix.Kernel.strLitToConstructor + simp only [Ix.LeanIngress.Expr.substKeys, Ix.LeanIngress.Key.subst] + congr 1 + generalize s.toList = cs + induction cs with + | nil => rfl + | cons c cs ih => + simp only [List.foldr_cons, Ix.LeanIngress.Expr.substKeys, Ix.LeanIngress.Key.subst, ih] + +/-- **Environment relation along a non-injective fiber map `Key.subst ρ`**: +`envS`'s constants are in scope `P`, every constant of `envS` maps into `envQ` +under `ConstantInfo.substKeys ρ`, and every lookup at a good key of `envS` +is answered in `envQ` at its substituted key by the substituted constant. -/ +structure EnvSubstRel (P : Ix.Kernel.Mode.Id .source → Prop) + (ρ : Ix.LeanIngress.FiberMap) (envS envQ : Env .source) : Prop where + all : envS.Good (Ix.Kernel.KeyScope.ofId P) + find : ∀ k : Ix.Kernel.Key .source, k.Good (Ix.Kernel.KeyScope.ofId P) → + envQ.find? (Ix.LeanIngress.Key.subst ρ k) = + (envS.find? k).map (Ix.LeanIngress.ConstantInfo.substKeys ρ) + mem : ∀ {c : Ix.Kernel.ConstantInfo .source}, + c ∈ envS.consts → Ix.LeanIngress.ConstantInfo.substKeys ρ c ∈ envQ.consts + +theorem EnvSubstRel.find_some {P : Ix.Kernel.Mode.Id .source → Prop} + {ρ : Ix.LeanIngress.FiberMap} {envS envQ : Env .source} + (h : EnvSubstRel P ρ envS envQ) {k : Ix.Kernel.Key .source} + {c : Ix.Kernel.ConstantInfo .source} (hc : envS.find? k = some c) : + envQ.find? (Ix.LeanIngress.Key.subst ρ k) = + some (Ix.LeanIngress.ConstantInfo.substKeys ρ c) := by + have hk : k.Good (Ix.Kernel.KeyScope.ofId P) := by + obtain ⟨cs⟩ := envS + simp only [Ix.Kernel.Env.find?] at hc + have hname := List.find?_some hc + have hmem := List.mem_of_find?_eq_some hc + rw [beq_iff_eq] at hname + rw [← hname] + exact (h.all c hmem).name + rw [h.find k hk, hc] + rfl + +theorem EnvSubstRel.findProj? {P : Ix.Kernel.Mode.Id .source → Prop} + {ρ : Ix.LeanIngress.FiberMap} {envS envQ : Env .source} + (h : EnvSubstRel P ρ envS envQ) + {T : Ix.Kernel.Key .source} (hT : T.Good (Ix.Kernel.KeyScope.ofId P)) (i : Nat) : + envQ.findProj? (Ix.LeanIngress.Key.subst ρ T) i = + (envS.findProj? T i).map (ProjEntry.substKeys ρ) := by + unfold Ix.Kernel.Env.findProj? + have hk := h.find (Ix.Kernel.projTableName T) hT + simp only [Ix.Kernel.projTableName, Ix.Kernel.auxKey, Ix.LeanIngress.Key.subst] at hk ⊢ + rw [hk] + cases envS.find? (.aux T .projTable) with + | none => rfl + | some c => + cases c with + | projInfo tbl => + simp only [Option.map_some, Ix.LeanIngress.ConstantInfo.substKeys] + have hn : (Ix.LeanIngress.ProjTable.substKeys ρ tbl).numFields = tbl.numFields := rfl + rw [hn] + split + · simp [ProjTable.substKeys_entry] + · rfl + | _ => rfl + +/-- **A good term denotes, in `envS`, what its `substKeys ρf` image denotes in a +related `envQ`**, at the pulled-back constant values `substCval ρf cval'`. -/ +theorem Denotes.substPullback {P : Ix.Kernel.Mode.Id .source → Prop} + {ρf : Ix.LeanIngress.FiberMap} {envS envQ : Env .source} + (h : EnvSubstRel P ρf envS envQ) + {V : Type w} [SetTheory V] + {cval' : Ix.Kernel.Key .source → (LevelParam → Nat) → V} + {φ : LevelParam → Nat} {ρ : BVarIdx → V} + {e' : Ix.Kernel.Expr .source} {v : V} + (hd : Ix.Kernel.Denotes cval' envQ φ ρ e' v) : + ∀ {e : Ix.Kernel.Expr .source}, + e' = Ix.LeanIngress.Expr.substKeys ρf e → + e.Good (Ix.Kernel.KeyScope.ofId P) → + Ix.Kernel.Denotes (substCval ρf cval') envS φ ρ e v := by + induction hd with + | bvar => + intro e he _ + cases e <;> simp [Ix.LeanIngress.Expr.substKeys] at he + subst he + exact .bvar + | sort => + intro e he _ + cases e <;> simp [Ix.LeanIngress.Expr.substKeys] at he + subst he + exact .sort + | @const ρv n us ci hf hlen => + intro e he hg + cases e <;> simp [Ix.LeanIngress.Expr.substKeys] at he + rename_i m us' + obtain ⟨rfl, rfl⟩ := he + have hm : m.Good (Ix.Kernel.KeyScope.ofId P) := hg + rw [h.find m hm] at hf + obtain ⟨ci₀, hci₀, rfl⟩ := Option.map_eq_some_iff.mp hf + have hlps : (Ix.LeanIngress.ConstantInfo.substKeys ρf ci₀).toConstantVal.levelParams = + ci₀.toConstantVal.levelParams := by + simp [Ix.LeanIngress.ConstantInfo.substKeys_toConstantVal] + rw [hlps] at hlen ⊢ + exact .const hci₀ hlen + | app _ _ ihf iha => + intro e he hg + cases e <;> simp [Ix.LeanIngress.Expr.substKeys] at he + obtain ⟨rfl, rfl⟩ := he + exact .app (ihf rfl hg.1) (iha rfl hg.2) + | lam _ _ hP ihA ihF => + intro e he hg + cases e <;> simp [Ix.LeanIngress.Expr.substKeys] at he + obtain ⟨rfl, rfl, rfl⟩ := he + exact .lam (ihA rfl hg.1) (fun x hx => ihF x hx rfl hg.2) hP + | pi _ _ hP ihA ihB => + intro e he hg + cases e <;> simp [Ix.LeanIngress.Expr.substKeys] at he + obtain ⟨rfl, rfl, rfl⟩ := he + exact .pi (ihA rfl hg.1) (fun x hx => ihB x hx rfl hg.2) hP + | @proj_table ρv T i e₀ entry P₀ ht _ ihe => + intro e he hg + cases e <;> simp [Ix.LeanIngress.Expr.substKeys] at he + rename_i T₀ i₀ e₁ + obtain ⟨rfl, rfl, rfl⟩ := he + rw [h.findProj? hg.1] at ht + obtain ⟨entry₀, hentry₀, rfl⟩ := Option.map_eq_some_iff.mp ht + have hoff : (ProjEntry.substKeys ρf entry₀).off = entry₀.off := rfl + rw [hoff] + exact .proj_table hentry₀ (ihe rfl hg.2) + | @proj_fst ρv T e₀ P₀ ht _ ihe => + intro e he hg + cases e <;> simp [Ix.LeanIngress.Expr.substKeys] at he + rename_i T₀ i₀ e₁ + obtain ⟨rfl, rfl, rfl⟩ := he + rw [h.findProj? hg.1] at ht + exact .proj_fst (Option.map_eq_none_iff.mp ht) (ihe rfl hg.2) + | @proj_snd ρv T e₀ P₀ ht _ ihe => + intro e he hg + cases e <;> simp [Ix.LeanIngress.Expr.substKeys] at he + rename_i T₀ i₀ e₁ + obtain ⟨rfl, rfl, rfl⟩ := he + rw [h.findProj? hg.1] at ht + exact .proj_snd (Option.map_eq_none_iff.mp ht) (ihe rfl hg.2) + | @natLit ρv n X _ ih => + intro e he _ + cases e <;> simp [Ix.LeanIngress.Expr.substKeys] at he + subst he + exact .natLit (ih (natLitToConstructor_substKeys ρf n).symm + (Ix.Kernel.natLitToConstructor_good (Ix.Kernel.KeyScope.ofId_pre P) n)) + | @strLit ρv s X _ ih => + intro e he _ + cases e <;> simp [Ix.LeanIngress.Expr.substKeys] at he + subst he + exact .strLit (ih (strLitToConstructor_substKeys ρf s).symm + (Ix.Kernel.strLitToConstructor_good (Ix.Kernel.KeyScope.ofId_pre P) + (Ix.Kernel.KeyScope.ofId_str P) s)) + +/-- A constant's denotation at the pulled-back values is its substituted key's. -/ +theorem Denotes.subst_const_push {P : Ix.Kernel.Mode.Id .source → Prop} + {ρf : Ix.LeanIngress.FiberMap} {envS envQ : Env .source} + (h : EnvSubstRel P ρf envS envQ) + {V : Type w} [SetTheory V] + {cval' : Ix.Kernel.Key .source → (LevelParam → Nat) → V} + {φ : LevelParam → Nat} {ρ : BVarIdx → V} + {k : Ix.Kernel.Key .source} {us : List Ix.Kernel.Level} {v : V} + (hd : Ix.Kernel.Denotes (substCval ρf cval') envS φ ρ (.const k us) v) : + Ix.Kernel.Denotes cval' envQ φ ρ (.const (Ix.LeanIngress.Key.subst ρf k) us) v := by + cases hd with + | const hf hlen => + rename_i ci + have hf' := h.find_some hf + have hlps : (Ix.LeanIngress.ConstantInfo.substKeys ρf ci).toConstantVal.levelParams = + ci.toConstantVal.levelParams := by + simp [Ix.LeanIngress.ConstantInfo.substKeys_toConstantVal] + have hd' := Ix.Kernel.Denotes.const (cval := cval') (φ := φ) (ρ := ρ) (us := us) hf' + (by rw [hlps]; exact hlen) + rw [hlps] at hd' + exact hd' + +/-- **A model of the quotient environment pulls back to the nominal environment** +along `Key.subst ρf`. -/ +def Model.substPullback {P : Ix.Kernel.Mode.Id .source → Prop} + {ρf : Ix.LeanIngress.FiberMap} {envS envQ : Env .source} + (h : EnvSubstRel P ρf envS envQ) + {V : Type w} [SetTheory V] (M : Ix.Kernel.Model V envQ) : + Ix.Kernel.Model V envS where + cval := substCval ρf M.cval + mem c hc φ ρ := by + obtain ⟨T, hT, hmem⟩ := M.mem _ (h.mem hc) φ ρ + have hty : (Ix.LeanIngress.ConstantInfo.substKeys ρf c).toConstantVal.type = + Ix.LeanIngress.Expr.substKeys ρf c.toConstantVal.type := by + simp [Ix.LeanIngress.ConstantInfo.substKeys_toConstantVal, Ix.LeanIngress.ConstantVal.substKeys_type] + rw [hty] at hT + refine ⟨T, Denotes.substPullback h hT rfl (h.all c hc).toConstantVal.2, ?_⟩ + have hn : (Ix.LeanIngress.ConstantInfo.substKeys ρf c).name = + Ix.LeanIngress.Key.subst ρf c.name := Ix.LeanIngress.ConstantInfo.substKeys_name ρf c + rw [hn] at hmem + exact hmem + false_empty φ ρ F hF := M.false_empty φ ρ F (Denotes.subst_const_push h hF) + eq_equality u φ ρ E A a b hE := M.eq_equality u φ ρ E A a b (Denotes.subst_const_push h hE) + +/-- **The definition-value clause pulls back along `Key.subst ρf`**. -/ +theorem defn_value_substPullback {P : Ix.Kernel.Mode.Id .source → Prop} + {ρf : Ix.LeanIngress.FiberMap} {envS envQ : Env .source} + (h : EnvSubstRel P ρf envS envQ) + {V : Type w} [SetTheory V] + {cval' : Ix.Kernel.Key .source → (LevelParam → Nat) → V} + (hv : ∀ (cv : Ix.Kernel.ConstantVal .source) (value : Ix.Kernel.Expr .source) + (hint : Ix.Kernel.ReducibilityHint), + Ix.Kernel.ConstantInfo.defnInfo cv value hint ∈ envQ.consts → + ∀ (φ : LevelParam → Nat) (ρ : BVarIdx → V), + Ix.Kernel.Denotes cval' envQ φ ρ value (cval' cv.name φ)) : + ∀ (cv : Ix.Kernel.ConstantVal .source) (value : Ix.Kernel.Expr .source) + (hint : Ix.Kernel.ReducibilityHint), + Ix.Kernel.ConstantInfo.defnInfo cv value hint ∈ envS.consts → + ∀ (φ : LevelParam → Nat) (ρ : BVarIdx → V), + Ix.Kernel.Denotes (substCval ρf cval') envS φ ρ value + (substCval ρf cval' cv.name φ) := by + intro cv value hint hc φ ρ + have hc' := h.mem hc + have hg := h.all _ hc + exact Denotes.substPullback h (hv _ _ hint hc' φ ρ) rfl hg.2 + +/-- **A value model of `envQ` pulls back along `Key.subst ρf` to `envS`**. -/ +def ValueModel.substPullback {P : Ix.Kernel.Mode.Id .source → Prop} + {ρf : Ix.LeanIngress.FiberMap} {envS envQ : Env .source} + {V : Type w} [SetTheory V] + (h : EnvSubstRel P ρf envS envQ) (N : ValueModel V envQ) : ValueModel V envS := + { Model.substPullback h N.toModel with + defn_value := defn_value_substPullback h N.defn_value } + +theorem ValueModel.substPullback_rel {P : Ix.Kernel.Mode.Id .source → Prop} + {ρf : Ix.LeanIngress.FiberMap} {envS envQ : Env .source} + (h : EnvSubstRel P ρf envS envQ) : + ValueModel.Rel.{w} (Ix.LeanIngress.Key.subst ρf) envS envQ := + fun _ _ N => ⟨ValueModel.substPullback h N, fun _ _ => rfl⟩ + +/-- **Nominal-to-quotient value-model pullback along `Key.subst ρ`**: every +constant and lookup of `envS` maps into `envQ` under `ConstantInfo.substKeys ρ`, +and `ValueModel` (`Model` and `defn_value`) pulls back from `envQ` to `envS` +along `Key.subst ρ` without requiring `LawModel.Rel`. -/ +structure EnvSubstModelPullback (ρ : Ix.LeanIngress.FiberMap) (envS envQ : Env .source) : Prop where + mem : ∀ {c : Ix.Kernel.ConstantInfo .source}, + c ∈ envS.consts → Ix.LeanIngress.ConstantInfo.substKeys ρ c ∈ envQ.consts + find_some : ∀ {k : Ix.Kernel.Key .source} {c : Ix.Kernel.ConstantInfo .source}, + envS.find? k = some c → + envQ.find? (Ix.LeanIngress.Key.subst ρ k) = + some (Ix.LeanIngress.ConstantInfo.substKeys ρ c) + valueModel : ValueModel.Rel.{w} (Ix.LeanIngress.Key.subst ρ) envS envQ + +/-- Construct `EnvSubstModelPullback` unconditionally from `EnvSubstRel` +(without `LawModel.Rel`). -/ +theorem EnvSubstRel.toModelPullback {P : Ix.Kernel.Mode.Id .source → Prop} + {ρ : Ix.LeanIngress.FiberMap} {envS envQ : Env .source} + (h : EnvSubstRel P ρ envS envQ) : + EnvSubstModelPullback.{w} ρ envS envQ := + ⟨h.mem, h.find_some, ValueModel.substPullback_rel h⟩ + +/-- **Composing `EnvSubstModelPullback` with `EnvRel` gives a direct `ValueModel.Rel` +from `envS` to `envA` along `(Key.map f) ∘ (Key.subst ρ)`** without requiring +`LawModel.Rel`. -/ +theorem EnvSubstModelPullback.comp_envRel_valueModel + {ρ : Ix.LeanIngress.FiberMap} {envS envQ : Env .source} + {P : Ix.Kernel.Mode.Id .source → Prop} + {f : Ix.Kernel.Mode.Id .source → Ix.Kernel.Mode.Id .anon} + {envA : Env .anon} + (hsub : EnvSubstModelPullback.{w} ρ envS envQ) + (hrel : Ix.Kernel.EnvRel P f envQ envA) : + ValueModel.Rel.{w} + ((Ix.Kernel.Key.map f) ∘ (Ix.LeanIngress.Key.subst ρ)) envS envA := + ValueModel.comp hsub.valueModel (ValueModel.pullback_rel hrel) + +/-- **Nominal-to-quotient semantic pullback along `Key.subst ρ`**: every constant +and lookup of `envS` maps into `envQ` under `ConstantInfo.substKeys ρ`, and both +`ValueModel` and `LawModel` pull back from `envQ` to `envS` along `Key.subst ρ`. + +Note: Quotient Source (`CompileCorrect` + `Ix.LeanIngress.prepareSourceCollapsedAt_trace`) +is the primary unconditional security guarantee, transporting `ValueModel` and +`LawModel` unconditionally between `envQ` and `envA` while ensuring quotient +collapse only merges declarations that are structurally identical (`Declaration.beqF`, +hence `=`) under `Key.subst ρ`. `EnvSubstPullback` packages the hypotheses for +conditional model-theoretic corollaries that pull models of `envQ` back to a +nominal environment `envS` satisfying `EnvSubstRel` and `LawModel.Rel`. Deriving +`LawModel.Rel` from `EnvSubstRel` and discharging `EnvSubstRel` from +`Ix.LeanIngress.FiberAliasSafe` on closures whose alias fibers do not mix +reducibility hints is left for future work. -/ +structure EnvSubstPullback (ρ : Ix.LeanIngress.FiberMap) (envS envQ : Env .source) : Prop + extends EnvSubstModelPullback.{w} ρ envS envQ where + lawModel : LawModel.Rel.{w} (Ix.LeanIngress.Key.subst ρ) envS envQ + +/-- Upgrade `EnvSubstModelPullback` to `EnvSubstPullback` given `LawModel.Rel`. -/ +theorem EnvSubstModelPullback.toPullback {ρ : Ix.LeanIngress.FiberMap} + {envS envQ : Env .source} + (h : EnvSubstModelPullback.{w} ρ envS envQ) + (hlaw : LawModel.Rel.{w} (Ix.LeanIngress.Key.subst ρ) envS envQ) : + EnvSubstPullback.{w} ρ envS envQ := + ⟨h, hlaw⟩ + +/-- Construct `EnvSubstPullback` from `EnvSubstRel` and `LawModel.Rel`. -/ +theorem EnvSubstRel.toPullback {P : Ix.Kernel.Mode.Id .source → Prop} + {ρ : Ix.LeanIngress.FiberMap} {envS envQ : Env .source} + (h : EnvSubstRel P ρ envS envQ) + (hlaw : LawModel.Rel.{w} (Ix.LeanIngress.Key.subst ρ) envS envQ) : + EnvSubstPullback.{w} ρ envS envQ := + h.toModelPullback.toPullback hlaw + +/-- Construct `EnvSubstPullback` from `EnvSubstRel` when `RecRuleLaws` and +`CapabilityLaws` hold on the pulled-back valuation `substCval ρ N.cval` for every +quotient `LawModel` `N`. -/ +theorem EnvSubstRel.toPullback_of_laws {P : Ix.Kernel.Mode.Id .source → Prop} + {ρ : Ix.LeanIngress.FiberMap} {envS envQ : Env .source} + (h : EnvSubstRel P ρ envS envQ) + (hlaws : ∀ (V : Type w) [SetTheory V] (N : LawModel V envQ), + RecRuleLaws (substCval ρ N.cval) envS ∧ + CapabilityLaws (substCval ρ N.cval) envS) : + EnvSubstPullback.{w} ρ envS envQ := + h.toPullback fun V _ N => + ⟨{ ValueModel.substPullback h N.toValueModel with + rec_rule := (hlaws V N).1 + caps := (hlaws V N).2 }, + fun _ _ => rfl⟩ + +/-- **Composing `EnvSubstPullback` with `EnvRel` gives a direct `ValueModel.Rel` +from `envS` to `envA` along `(Key.map f) ∘ (Key.subst ρ)`**. -/ +theorem EnvSubstPullback.comp_envRel_valueModel + {ρ : Ix.LeanIngress.FiberMap} {envS envQ : Env .source} + {P : Ix.Kernel.Mode.Id .source → Prop} + {f : Ix.Kernel.Mode.Id .source → Ix.Kernel.Mode.Id .anon} + {envA : Env .anon} + (hsub : EnvSubstPullback.{w} ρ envS envQ) + (hrel : Ix.Kernel.EnvRel P f envQ envA) : + ValueModel.Rel.{w} + ((Ix.Kernel.Key.map f) ∘ (Ix.LeanIngress.Key.subst ρ)) envS envA := + hsub.toEnvSubstModelPullback.comp_envRel_valueModel hrel + +/-- **Composite key map on a published Source name**: under a well-formed role table +`t` with `PublishedRolesBound t ixe`, for every Source name `i` published at +address `a` (`(sourceAddrs ixe)[i]? = some a`), the composite map +`(Key.map (evAddr ixe b)) ∘ (Key.subst (fiberAliasAt ixe (roleAtMap t)))` sends +`Key.ofName i` to `.role r` when `(roleAtMap t)[a]? = some r` (equivalently +`t.addressOf? r = some a`), and to `.id a` when `(roleAtMap t)[a]? = none`. -/ +theorem evAddr_subst_key {ixe : Ixon.Env} {t : Ix.Kernel.RoleTable} + (checked : t.check = .ok ()) (bound : PublishedRolesBound t ixe) + (b : Ix.RoleEvidence.Bundle) {i : Ix.Kernel.Name} {a : Address} + (hi : (sourceAddrs ixe)[i]? = some a) : + (∃ r, (Ix.LeanIngress.roleAtMap t)[a]? = some r ∧ t.addressOf? r = some a ∧ + (Ix.LeanIngress.Key.subst + (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) + (Ix.Kernel.Key.ofName i)).map (Ix.LeanIngress.evAddr ixe b) = .role r) ∨ + ((Ix.LeanIngress.roleAtMap t)[a]? = none ∧ + (Ix.LeanIngress.Key.subst + (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) + (Ix.Kernel.Key.ofName i)).map (Ix.LeanIngress.evAddr ixe b) = .id a) := by + have htb : Ix.LeanIngress.TableBound ixe (Ix.LeanIngress.roleAtMap t) := + tableBound_of checked bound + have hne : (Ix.LeanIngress.fiberMembers (sourceAddrs ixe)).getD a [] ≠ [] := by + intro h + have := Ix.LeanIngress.mem_fiberMembers.mpr hi + rw [h] at this + cases this + obtain ⟨rep, hrep⟩ := + Ix.LeanIngress.repAt_isSome (bound := (Ix.LeanIngress.roleAtMap t)[a]?) hne + fun x hx r hxr => htb x r a (Ix.LeanIngress.mem_fiberMembers.mp hx) hxr + have hsub := Ix.LeanIngress.fiberAliasAt_subst hi hrep + rw [hsub] + cases hr : (Ix.LeanIngress.roleAtMap t)[a]? with + | some r => + left + rw [hr] at hrep + simp only [Ix.LeanIngress.repAt] at hrep + split at hrep + · cases hrep + exact ⟨r, rfl, (Ix.LeanIngress.roleAtMap_spec checked).mp hr, rfl⟩ + · cases hrep + | none => + right + refine ⟨rfl, ?_⟩ + rw [hr] at hrep + simp only [Ix.LeanIngress.repAt] at hrep + obtain ⟨k, hk, hkr⟩ := Ix.LeanIngress.fiberRep_mem hrep + have hka : (sourceAddrs ixe)[k]? = some a := Ix.LeanIngress.mem_fiberMembers.mp hk + cases hrole : roleOf k with + | some r => + have := htb k r a hka hrole + rw [hr] at this + cases this + | none => + rw [Ix.LeanIngress.roleOf_eq_none hrole] at hkr + subst hkr + simp [Ix.LeanIngress.evAddr, hka] + +/-- **Composite key map on a compiled user constant `n : Ix.Name`**: when +`(sourceAddrs ixe)[ingressName n]? = some a`, the composite map +`(Key.map (evAddr ixe b)) ∘ (Key.subst (fiberAliasAt ixe (roleAtMap t)))` sends +`ingressKey n` to `.role r` when `(roleAtMap t)[a]? = some r` (`t.addressOf? r = some a`) +and to `.id a` when `(roleAtMap t)[a]? = none`. -/ +theorem evAddr_subst_ingressKey {ixe : Ixon.Env} {t : Ix.Kernel.RoleTable} + (checked : t.check = .ok ()) (bound : PublishedRolesBound t ixe) + (b : Ix.RoleEvidence.Bundle) {n : Ix.Name} {a : Address} + (hn : (sourceAddrs ixe)[ingressName n]? = some a) : + (∃ r, (Ix.LeanIngress.roleAtMap t)[a]? = some r ∧ t.addressOf? r = some a ∧ + (Ix.LeanIngress.Key.subst + (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) + (Ix.LeanIngress.ingressKey n)).map (Ix.LeanIngress.evAddr ixe b) = .role r) ∨ + ((Ix.LeanIngress.roleAtMap t)[a]? = none ∧ + (Ix.LeanIngress.Key.subst + (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) + (Ix.LeanIngress.ingressKey n)).map (Ix.LeanIngress.evAddr ixe b) = .id a) := + evAddr_subst_key checked bound b hn + +/-- **Composite key map on a published entry `(n, d) ∈ ixe.named.toList`**: under +`PublishedAgree ixe`, the composite map sends `ingressKey n` to `.role r` when +`(roleAtMap t)[d.addr]? = some r` and to `.id d.addr` when +`(roleAtMap t)[d.addr]? = none`. -/ +theorem evAddr_subst_named {ixe : Ixon.Env} {t : Ix.Kernel.RoleTable} + (checked : t.check = .ok ()) (bound : PublishedRolesBound t ixe) + (agree : PublishedAgree ixe) (b : Ix.RoleEvidence.Bundle) + {n : Ix.Name} {d : Ixon.Named} (hmem : (n, d) ∈ ixe.named.toList) : + (∃ r, (Ix.LeanIngress.roleAtMap t)[d.addr]? = some r ∧ t.addressOf? r = some d.addr ∧ + (Ix.LeanIngress.Key.subst + (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) + (Ix.LeanIngress.ingressKey n)).map (Ix.LeanIngress.evAddr ixe b) = .role r) ∨ + ((Ix.LeanIngress.roleAtMap t)[d.addr]? = none ∧ + (Ix.LeanIngress.Key.subst + (Ix.LeanIngress.fiberAliasAt ixe (Ix.LeanIngress.roleAtMap t)) + (Ix.LeanIngress.ingressKey n)).map (Ix.LeanIngress.evAddr ixe b) = .id d.addr) := + evAddr_subst_ingressKey checked bound b (agree n d hmem) + end Ix.Cert diff --git a/Ix/Cert/RecursorIndexFacts.lean b/Ix/Cert/RecursorIndexFacts.lean index 47f5326a8..db975bd66 100644 --- a/Ix/Cert/RecursorIndexFacts.lean +++ b/Ix/Cert/RecursorIndexFacts.lean @@ -536,35 +536,14 @@ theorem recLps_params {names : Array Ix.Name} {L : Nat} (hnd : names.toList.Nodu /-! ## A recursor references its rules' constructors -/ -open Ix.Cert.GraphWalk (MemoOK) in -theorem forIn_rules_ctor (f : Ix.RecursorRule → Ix.Set Ix.Name → Ix.GraphM (ForInStep (Ix.Set Ix.Name))) - (hf : ∀ rule r env s, f rule r env s = - (.yield ((r.insert rule.ctor).union (Ix.graphExpr rule.rhs env s).1), (Ix.graphExpr rule.rhs env s).2)) - (env : Ix.Environment) : - ∀ (rules : List Ix.RecursorRule) (r0 : Ix.Set Ix.Name) (s : Ix.GraphState), MemoOK s → - ∀ rule ∈ rules, (forIn rules r0 f env s).1.contains rule.ctor = true - | [], _, _, _ => fun _ h => nomatch h - | rule :: rules, r0, s, hs => by - obtain ⟨s1, -⟩ := GraphWalk.graphExpr_refs rule.rhs env s hs - have step : forIn (rule :: rules) r0 f env s = - forIn rules ((r0.insert rule.ctor).union (Ix.graphExpr rule.rhs env s).1) f env - (Ix.graphExpr rule.rhs env s).2 := by - rw [List.forIn_cons, GraphWalk.bind_run, hf] - rw [step] - obtain ⟨keep, -⟩ := GraphWalk.forIn_rules f hf env rules _ _ s1 - intro rule' hr - rcases List.mem_cons.mp hr with rfl | hr - · exact keep _ (GraphWalk.contains_union_iff.mpr (.inl (GraphWalk.contains_insert_iff.mpr (.inl BEq.rfl)))) - · exact forIn_rules_ctor f hf env rules _ _ s1 rule' hr - /-- **A recursor references each rule's constructor.** -/ theorem refsOf_rule (v : Ix.RecursorVal) {rule : Ix.RecursorRule} (hr : rule ∈ v.rules.toList) : (refsOf (.recInfo v)).contains rule.ctor = true := by - obtain ⟨st, -⟩ := GraphWalk.graphExpr_refs v.cnst.type { consts := {} } Ix.GraphState.init GraphWalk.memoOK_init + obtain ⟨st, -⟩ := GraphWalk.graphExpr_refs v.cnst.type { consts := {} } Ix.GraphState.init (GraphWalk.memoOK_init ∅) show (Ix.graphConst (.recInfo v) { consts := {} } Ix.GraphState.init).1.contains rule.ctor = true rw [Ix.graphConst, GraphWalk.bind_run, GraphWalk.bind_run, ← Array.forIn_toList] refine GraphWalk.foldl_insert_keeps _ _ _ ?_ - exact forIn_rules_ctor _ (fun _ _ _ _ => rfl) _ v.rules.toList _ _ st rule hr + exact (GraphWalk.forIn_rules _ (fun _ _ _ _ => rfl) _ v.rules.toList _ _ st).2.1 rule hr /-! ## The compile's headers -/ diff --git a/Ix/Cert/RuntimeAudit.lean b/Ix/Cert/RuntimeAudit.lean index 01fb80b1c..1bdbbbb9d 100644 --- a/Ix/Cert/RuntimeAudit.lean +++ b/Ix/Cert/RuntimeAudit.lean @@ -41,7 +41,7 @@ symbol that no library defines, so a binary that links them fails. -/ -- beside the BLAKE3 primitives it reaches. Nothing else changes. -- Both `Ix.Cert.compile` (which drives `Ix.Compile.compileEnv` / `compileEnvFold`) and -- `Ix.Compile.ReadyCompile.reportIO` (the parallel ready-queue driver used by --- `Ix.CompileCertified.compileCertifiedIO`) are audited here. +-- `Ix.CompileM.compileCertifiedPrepared`) are audited here. /-- error: project-level execution replacements reached from [Ix.Cert.compile]: [Address.admissionHash, Ix.groundExpr, @@ -150,22 +150,17 @@ run_cmd do /-! ## The CLI certified compiler and role-binding finalizer -`ix compile` and `ix compile-lean` invoke `Ix.CanonM.canonEnv`, -`Ix.CompileM.compileCertified` (over `Ix.Compile.compileEnv`, alongside -`Ix.Compile.ReadyCompile.reportIO`, the parallel ready-queue driver used by -`Ix.CompileCertified.compileCertifiedIO`, audited above), -`Ix.CompileM.validateFinalized`, `Ix.RoleBinding.Compile.finalize`, and -`Ixon.serEnv`. Beyond `Init`, `Std`, `Lean`, and the `compileChecked` list -above, their runtime closure reaches the source-contract environment -extensions, the syntax equality helper, the Merkle tree builder, the name -topological sort, and the `partial` helpers of role-evidence and role-binding -synthesis, frozen here. -/ - -/-- error: project-level execution replacements reached from [Ix.CanonM.canonEnv, - Ix.CompileM.compileCertified, - Ix.CompileM.validateFinalized, - Ix.RoleBinding.Compile.finalize, - Ixon.serEnv]: +`ix compile`, `ix compile-lean`, `ix compile-export` and `ix validate-lean` +invoke `Ix.CompileM.compileAndFinalize`: `Ix.CompileM.compileCertified` (over +`Ix.Compile.ReadyCompile.reportIO`, the parallel ready-queue driver audited +above), `Ix.RoleBinding.Compile.finalize` with the certified backend, +`Ix.CompileM.validateFinalized` and `Ixon.serEnv`. Beyond `Init`, `Std`, +`Lean`, and the `compileChecked` list above, their runtime closure reaches the +source-contract environment extensions, the syntax equality helper, the +Merkle tree builder, the name topological sort, and the `partial` helpers of +role-evidence and role-binding synthesis, frozen here. -/ + +/-- error: project-level execution replacements reached from [Ix.CompileM.compileAndFinalize]: [Ix.groundExpr, Ix.groundLevel, instBEqSyntax_ix.beq, @@ -193,9 +188,7 @@ synthesis, frozen here. -/ #guard_msgs (whitespace := lax) in run_cmd do Ix.Kernel.Audit.checkRuntimeWith - #[``Ix.CanonM.canonEnv, ``Ix.CompileM.compileCertified, - ``Ix.CompileM.validateFinalized, ``Ix.RoleBinding.Compile.finalize, - ``Ixon.serEnv] + #[``Ix.CompileM.compileAndFinalize] #[`Init, `Std, `Lean] { Ix.Kernel.Audit.runtimeRulings with csimpModules := Ix.Kernel.Audit.runtimeRulings.csimpModules.push `Ix, diff --git a/Ix/Cert/Schedule.lean b/Ix/Cert/Schedule.lean index b4fdf65e7..1a7a53b93 100644 --- a/Ix/Cert/Schedule.lean +++ b/Ix/Cert/Schedule.lean @@ -2,8 +2,7 @@ import Ix.Cert.Compile /-! # The driver's schedule is a fold -`Ix.CompileM.compileEnvFold` is the driver of both `Ix.Cert.compile` and the -IO pipeline (`Ix.CompileM.compileDecoratedConsts`). Each wave computes its +`Ix.CompileM.compileEnvFold` is the driver of `Ix.Cert.compile`. Each wave computes its ready blocks' outcomes (`auxBlockOutcome`) against the wave's snapshot on at most `width` tasks (`waveTasks`), and applies them in block-key order. This file proves that the schedule is only a schedule: @@ -27,12 +26,14 @@ This file proves that the schedule is only a schedule: "Sequential" is the fold's own order: within a wave every outcome is computed against the wave's snapshot (the state the previous wave left), -then the outcomes are applied in key order. That this equals compiling each -block against the live state in some dependency order (the reference -schedule `Tests.Ix.Compile.ScheduleReference`) is a -property of the blocks' independence within a wave, tested by -`compile-schedule-identity` and `cert-compile-parity-initstd`, not proved -here. +then the outcomes are applied in key order. Wave-snapshot freshness and +disjointness across blocks in a wave are proved in `Ix.Cert.FreshSnapshot` +(feeding `Ix.Cert.RunOwnership`, which proves semantic correctness of the +wave-snapshot fold directly), while `Ix.Compile.ReadyCompile.reportIO` +proves by type that the IO worker-queue driver computes `Ix.Cert.compileReport`. +(That the wave-snapshot fold also equals compiling each block one at a time +against the live state is a property of the blocks' independence within a +wave, not proved here.) The driver is restated with its apply step as a parameter (`foldWaveWith`), by `rfl` (`foldWave_eq_with`): an edit of `foldWave` @@ -220,8 +221,7 @@ theorem compileEnvFold_width (env : Ix.Environment) (blocks : Ix.CondensedBlocks compileEnvFold env blocks dbg' sharingLimits width' := by rw [compileEnvFold_eq_seq, compileEnvFold_eq_seq] -/-- The IO pipeline's wrapper (`compileDecoratedConsts` runs the driver -under `IO.lazyPure`) is `pure` of the value. -/ +/-- The `IO.lazyPure` wrapper is `pure` of the value. -/ theorem lazyPure_eq {α : Type} (f : Unit → α) : IO.lazyPure f = pure (f ()) := rfl end Ix.Cert.Schedule diff --git a/Ix/Cert/Statement.lean b/Ix/Cert/Statement.lean index ec247441e..540f3d926 100644 --- a/Ix/Cert/Statement.lean +++ b/Ix/Cert/Statement.lean @@ -1366,4 +1366,13 @@ def env (e : Env md) : Env md' := ⟨e.consts.map (constantInfo σ)⟩ end Rename +/-- **Value-model pullback along a key map `σ`**: every value model of `env₂` +pulls back to a value model of `env₁` whose constant values agree with `env₂`'s +along `σ`. -/ +def ValueModel.Rel {md₁ md₂ : Ix.Kernel.Mode} + (σ : Ix.Kernel.Key md₁ → Ix.Kernel.Key md₂) + (env₁ : Env md₁) (env₂ : Env md₂) : Prop := + ∀ (V : Type w) [SetTheory V] (M : ValueModel V env₂), + ∃ N : ValueModel V env₁, ∀ k φ, N.cval k φ = M.cval (σ k) φ + end Ix.Cert diff --git a/Ix/Cert/TraceFold.lean b/Ix/Cert/TraceFold.lean index fd3c2ad65..02f9f3bec 100644 --- a/Ix/Cert/TraceFold.lean +++ b/Ix/Cert/TraceFold.lean @@ -139,41 +139,57 @@ theorem groundSplit_key {E : Ix.Environment} {n : Ix.Name} {rs : Ix.Set Ix.Name} · exact ih n rs h /-- **A block's members are its discovery map's nodes, exactly.** -/ -theorem blocksOfDisc_exact {bd : Std.HashMap UInt64 (Ix.Name × Ix.Name)} {rep x : Ix.Name} +theorem blocksOfDisc_exact {names : Array Ix.Name} {bd : Condense.Disc names} {rep x : Ix.Name} {all : Ix.Set Ix.Name} (h : (Condense.blocksOfDisc bd).get? rep = some all) (hx : x ∈ all.toList) : ∃ key val, bd.get? key = some val ∧ val.1 = x := by + have comp_exact : ∀ (r : Nat) (hr : r < names.size) (comp : Array Nat) (x : Ix.Name), + x ∈ (comp.foldl (fun (s : Ix.Set Ix.Name) v => + if hv : v < names.size then s.insert names[v] else s) {names[r]}).toList → + ∃ v, ∃ hv : v < names.size, names[v] = x := by + intro r hr comp x hx + rw [← Array.foldl_toList] at hx + refine Condense.foldl_inv (fun s : Ix.Set Ix.Name => + ∀ x, x ∈ s.toList → ∃ v, ∃ hv : v < names.size, names[v] = x) + _ comp.toList {names[r]} ?_ ?_ x hx + · intro x hx + rw [show ({names[r]} : Ix.Set Ix.Name) = (∅ : Ix.Set Ix.Name).insert names[r] from rfl] at hx + rcases set_mem_toList_insert hx with rfl | hx + · exact ⟨r, hr, rfl⟩ + · simp at hx + · intro s v _ ih x hx + split at hx + · rename_i hlt + rcases set_mem_toList_insert hx with rfl | hx + · exact ⟨v, hlt, rfl⟩ + · exact ih x hx + · exact ih x hx unfold Condense.blocksOfDisc at h - simp only [Id.run, Std.HashMap.forIn_eq_forIn_toList, List.forIn_pure_yield_eq_foldl] at h - change (List.foldl (fun (b : Ix.Map Ix.Name (Ix.Set Ix.Name) × Ix.Map Ix.Name Ix.Name) a => - (Std.HashMap.alter b.fst a.2.snd fun x => match x with - | some s => some (Std.HashSet.insert s a.2.fst) - | none => some {a.2.fst}, - Std.HashMap.insert b.snd a.2.fst a.2.snd)) (∅, ∅) bd.toList).fst.get? rep = some all at h - refine Condense.foldl_inv (fun s : Ix.Map Ix.Name (Ix.Set Ix.Name) × Ix.Map Ix.Name Ix.Name => ∀ rep all x, - s.1.get? rep = some all → x ∈ all.toList → ∃ key val, bd.get? key = some val ∧ val.1 = x) _ _ _ - (fun rep all x h _ => by rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_empty] at h; cases h) - (fun s e he hs rep all x h hx => ?_) rep all x h hx - have hbe : bd.get? e.1 = some e.2 := by - rw [Std.HashMap.get?_eq_getElem?, ← Std.HashMap.mem_toList_iff_getElem?_eq_some] - exact he - dsimp only at h - rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_alter] at h - split at h - · rename_i same + simp only [Id.run, Array.forIn_pure_yield_eq_foldl, pure_bind, Array.forIn'_pure_yield_eq_foldl] at h + let stepInner (s : Ix.Set Ix.Name) (v : Nat) : Ix.Set Ix.Name := + if hv : v < names.size then s.insert names[v] else s + change (Array.foldl (fun (b : Ix.Map Ix.Name (Ix.Set Ix.Name)) + (p : {p // p ∈ bd.c.comps.zip bd.c.roots}) => + let rn := names[p.1.2]'(bd.hr p.1 p.2) + b.insert rn (Array.foldl stepInner {rn} p.1.1)) + ∅ (bd.c.comps.zip bd.c.roots).attach).get? rep = some all at h + rw [← Array.foldl_toList] at h + obtain ⟨v, hv, rfl⟩ : ∃ v, ∃ hv : v < names.size, names[v] = x := by + refine Condense.foldl_inv (fun b : Ix.Map Ix.Name (Ix.Set Ix.Name) => + ∀ rep all x, b.get? rep = some all → x ∈ all.toList → + ∃ v, ∃ hv : v < names.size, names[v] = x) + _ _ ∅ + (fun rep all x h _ => by rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_empty] at h; cases h) + (fun b p _ ih rep all x h hx => ?_) rep all x h hx + dsimp only at h + rw [Std.HashMap.get?_eq_getElem?, Std.HashMap.getElem?_insert] at h split at h - · rename_i s hs' - cases h - rcases set_mem_toList_insert hx with e' | hx - · exact ⟨e.1, e.2, hbe, e'.symm⟩ - · rw [Std.HashMap.getElem?_congr same, ← Std.HashMap.get?_eq_getElem?] at hs' - exact hs _ _ x hs' hx · cases h - rw [show ({e.2.1} : Ix.Set Ix.Name) = (∅ : Ix.Set Ix.Name).insert e.2.1 from rfl] at hx - rcases set_mem_toList_insert hx with e' | hx - · exact ⟨e.1, e.2, hbe, e'.symm⟩ - · simp at hx - · rw [← Std.HashMap.get?_eq_getElem?] at h - exact hs rep all x h hx + exact comp_exact p.1.2 (bd.hr p.1 p.2) p.1.1 x hx + · rw [← Std.HashMap.get?_eq_getElem?] at h + exact ih rep all x h hx + refine ⟨v, (names[v], bd.rn[v]), ?_, rfl⟩ + unfold Condense.Disc.get? + simp [hv] /-- **A block's members are its graph's keys, exactly.** -/ theorem block_exact {refs : Ix.Map Ix.Name (Ix.Set Ix.Name)} {rep x : Ix.Name} {all : Ix.Set Ix.Name} diff --git a/Ix/Check.lean b/Ix/Check.lean index d0b21a1d5..de5970c40 100644 --- a/Ix/Check.lean +++ b/Ix/Check.lean @@ -1,4 +1,3 @@ -/- IxVM claim execution and target selection shared by check and prove. -/ module public import Ix.Address public import Ix.Aiur.Compiler @@ -15,7 +14,8 @@ public import Ix.Ixon public import Ix.Meta public import Ix.Store public import Ix.Cli.NameResolve -public import Ix.CompileResource + +/-! IxVM claim execution and target selection shared by check and prove. -/ public section @@ -252,34 +252,11 @@ def runInterp (decls : Aiur.Source.Decls) /-- The environment a claim about `name` is checked against when no `.ixe` is given: the closure of `name`, with the constants the kernel fabricates during reduction and the roles' constants (the selection of - `IxVM.ClaimHarness.loadIxonEnv`), bound to the default role table. - - By default the closure is compiled by the validated streamed Lean pipeline - (`Ix.CompileM.compileLeanConstsValidated`: streamed canonicalization and - condensation, the compile fold, and `Ix.Cert.checkReport`), and any failed - constant refuses the load. With `rust` it is compiled by the Rust compiler - (`loadIxonEnv`), which runs none of those checks. -/ -def loadNamedIxonEnv (name : Lean.Name) (leanEnv : Lean.Environment) (rust : Bool := false) : - IO Ixon.Env := do - if rust then return ← IxVM.ClaimHarness.loadIxonEnv name leanEnv - let mut seen : Lean.NameSet := {} - let mut deduped : Array (Lean.Name × Lean.ConstantInfo) := #[] - let names : Array Lean.Name := - #[name] ++ IxVM.ClaimHarness.synthesizedPrimNames ++ IxVM.ClaimHarness.roleNames - for n in names do - if leanEnv.constants.contains n then - for entry in Lean.collectDependencies n leanEnv.constants do - if !seen.contains entry.fst then - seen := seen.insert entry.fst - deduped := deduped.push entry - match ← Ix.CompileM.compileLeanConstsValidated deduped.toList with - | .error e => throw (IO.userError s!"the validated Lean compile of {name}'s closure failed: {e}") - | .ok out => - unless out.cenv.ungrounded.isEmpty do - throw (IO.userError s!"the validated Lean compile of {name}'s closure: \ - {out.cenv.ungrounded.size} constant(s) failed to compile: \ - {Ix.CompileM.recordedFailures out.cenv.ungrounded}") - IO.ofExcept (IxVM.ClaimHarness.withDefaultRoleBinding out.env) + `IxVM.ClaimHarness.loadIxonEnv`), compiled by the certified Lean compiler + (`Ix.CompileM.compileCertified`) and bound to the default role table. -/ +def loadNamedIxonEnv (name : Lean.Name) (leanEnv : Lean.Environment) : + IO Ixon.Env := + IxVM.ClaimHarness.loadIxonEnv name leanEnv /-- Shared driver for `ix check` / `ix prove`. Loads either a `.ixe` env (with optional `--claim` over a persisted claim, or per-name @@ -300,14 +277,12 @@ def loadNamedIxonEnv (name : Lean.Name) (leanEnv : Lean.Environment) (rust : Boo Lean env (no `.ixe` to mmap) or when the persisted `--claim` variant isn't `check addr none`. - Without a `.ixe`, each name's closure is compiled by the validated - streamed Lean pipeline, or with `rust` by the uncertified Rust - compiler (`loadNamedIxonEnv`). -/ + Without a `.ixe`, each name's closure is compiled by the certified + Lean compiler (`loadNamedIxonEnv`). -/ def forEachClaim (ixePath : Option String) (claimHex : Option String) (names : List String) (keepGoing : Bool) (logTag : String) (forceLeanWitness : Bool) (runOne : Ix.Claim → Option Aiur.EnvHandle → Target → String → IO UInt32) - (rust : Bool := false) : IO UInt32 := do let mut failures : Array String := #[] match ixePath with @@ -393,7 +368,7 @@ def forEachClaim -- during reduction (Bool/Nat ctors, String-literal ctor form), -- whose bytes must be in ch 2 even when the target's proof body -- never references them. - let ixonEnv ← loadNamedIxonEnv name env rust + let ixonEnv ← loadNamedIxonEnv name env let addr ← IxVM.ClaimHarness.lookupAddr ixonEnv name let claim ← checkClaimOf ixonEnv addr if forceLeanWitness then diff --git a/Ix/Cli/AddrOfCmd.lean b/Ix/Cli/AddrOfCmd.lean index 37d932680..e8f4ee502 100644 --- a/Ix/Cli/AddrOfCmd.lean +++ b/Ix/Cli/AddrOfCmd.lean @@ -1,14 +1,3 @@ -/- - `ix addr-of [--ixe ]`: resolve a Lean.Name to its - 32-byte content address. Without `--ixe`, the lookup compiles the - name's transitive closure from the compiled-in Lean env with the - certified Lean pipeline (`Ix.Check.loadNamedIxonEnv`; `--rust` uses the - Rust compiler) and reads its address. With `--ixe`, the - lookup reads the env from disk and dispatches `Ixon.Env.getAddr?`. - - Prints the resulting address hex on stdout (one line, no prefix), so - the output can be piped into `ix claim check $(ix addr-of …)` etc. --/ module public import Cli public import Ix.Address @@ -20,6 +9,18 @@ public import Ix.Meta public import Ix.Cli.NameResolve public import Ix.Check +/-! + `ix addr-of [--ixe ]`: resolve a Lean.Name to its + 32-byte content address. Without `--ixe`, the lookup compiles the + name's transitive closure from the compiled-in Lean env with the + certified Lean pipeline (`Ix.Check.loadNamedIxonEnv`) and reads its + address. With `--ixe`, the lookup reads the env from disk and + dispatches `Ixon.Env.getAddr?`. + + Prints the resulting address hex on stdout (one line, no prefix), so + the output can be piped into `ix claim check $(ix addr-of …)` etc. +-/ + public section open Ix.Cli.NameResolve @@ -36,7 +37,7 @@ def runAddrOfCmd (p : Cli.Parsed) : IO UInt32 := do match ixePath with | some path => let bytes ← IO.FS.readBinFile path - let ixonEnv ← match Ixon.rsDeEnv bytes with + let ixonEnv ← match Ixon.deEnv bytes with | .error e => IO.eprintln s!"error: failed to deserialize {path}: {e}"; return 1 | .ok env => pure env @@ -50,7 +51,7 @@ def runAddrOfCmd (p : Cli.Parsed) : IO UInt32 := do if !env.constants.contains name then IO.eprintln s!"error: {name} not found in compiled-in Lean env" return 1 - let ixonEnv ← Ix.Check.loadNamedIxonEnv name env (rust := p.hasFlag "rust") + let ixonEnv ← Ix.Check.loadNamedIxonEnv name env let addr ← IxVM.ClaimHarness.lookupAddr ixonEnv name IO.println (toString addr) return 0 @@ -64,7 +65,6 @@ def addrOfCmd : Cli.Cmd := `[Cli| FLAGS: "ixe" : String; "Path to a serialized `.ixe` env to resolve the name in. Without this, the name's closure in the compiled-in Lean env is compiled with the certified Lean pipeline and the name looked up there." - "rust"; "Without --ixe: compile the closure with the Rust compiler instead (uncertified: it runs none of the compile's checks)." ARGS: name : String; "Fully-qualified Lean.Name to resolve (e.g. `Nat.add_comm` or `Tests.Ix.Kernel.TutorialDefs.basicDef`)." diff --git a/Ix/Cli/BenchCmd.lean b/Ix/Cli/BenchCmd.lean index 006fe0db2..6bb7cf890 100644 --- a/Ix/Cli/BenchCmd.lean +++ b/Ix/Cli/BenchCmd.lean @@ -1,4 +1,12 @@ -/- +module +public import Cli +public import Lean.Data.Json +public import Ix.BenchConstants +public import Ix.Benchmark.Results +public import Ix.Cli.ConstsFile +public import Ix.Watchdog + +/-! `ix bench run`: the benchmark orchestrator — one command that reproduces a CI benchmark run locally, byte-for-byte on the same tools. @@ -11,7 +19,7 @@ compile` — except for the `compile` backend, where the compile IS the benchmark); 3. spawns the run's measured tool — `bench-typecheck` (aiur), - `ix check-rs` (ooc), + `ix check-lean` (ooc), `ix compile` (compile) — wrapped in the RAM watchdog (`Ix.Watchdog`: cgroup `memory.max` via a systemd user scope; the kernel OOM-kills at the ceiling). The per-constant backend (aiur) spawns @@ -29,13 +37,6 @@ module (`envSpecs`/`backendSpecs`) — one language, one owner. -/ -module -public import Cli -public import Lean.Data.Json -public import Ix.BenchConstants -public import Ix.Benchmark.Results -public import Ix.Cli.ConstsFile -public import Ix.Watchdog public section @@ -744,19 +745,19 @@ is not a benchmark run" -- 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-lean", 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 constant. ONE process for all names - -- (unlike the per-constant backends below): the check-rs rows mode + -- (unlike the per-constant backends below): the check-lean rows mode -- attributes per name internally with the env loaded once — a -- per-constant process would re-pay the multi-minute Mathlib env parse -- per name — and out-of-circuit checks don't approach the RAM ceiling. if !names.isEmpty then IO.FS.writeFile namesFile ("\n".intercalate names.toList ++ "\n") let exit ← runGuarded watchdog ceilingGb ix - #["check-rs", ixe, "--anon", "--consts-file", namesFile, "--json", out] + #["check-lean", ixe, "--anon", "--consts-file", namesFile, "--json", out] if exit != 0 && exit != exitRejected then IO.eprintln s!"[bench] per-constant checks failed (exit {exit})" | "aiur-sharded-env" => diff --git a/Ix/Cli/BenchReport.lean b/Ix/Cli/BenchReport.lean index cde6ae2cf..cf3565c63 100644 --- a/Ix/Cli/BenchReport.lean +++ b/Ix/Cli/BenchReport.lean @@ -125,8 +125,7 @@ def human (v : Option Float) (metric : String) : String := /-- Metrics where a LARGER value is the improvement; everything else is lower-is-better (times, RAM, cycles, sizes). `throughput` means - constants checked per second on EVERY backend (`ix_bench::throughput` - is the one calculator). -/ + constants checked per second on EVERY backend. -/ def higherIsBetter (metric : String) : Bool := dropStagePrefix metric == "throughput" @@ -391,7 +390,7 @@ def renderCompare (a : CompareArgs) : String := Id.run do /-! ## Per-constant drill-down (ooc `--per-const` attribution CSVs) -/ -/-- One entry of a `check-rs --per-const` attribution CSV: the +/-- One entry of a `--per-const` attribution CSV: the aggregated measurements of one constant's (or Muts block's) check. -/ structure PerConstEntry where nanos : Float := 0 @@ -498,7 +497,7 @@ private def moverDriver (m pr : PerConstEntry) : String := | none => "—" /-- Render the top-movers drill-down from two per-constant attribution - CSVs (`check-rs --per-const`). + CSVs (`--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 @@ -725,12 +724,12 @@ def shardMeasure (k : String) : String := else k /-- Rows JSON → Bencher Metric Format. Rows with `status ≠ ok` are dropped - whole — a rejected or OOM'd constant must never become a bencher data - point. Numeric fields (`phase-` included) pass through as - measures. Nested per-shard objects (`shard-cycles: {"0": …}`) become - per-shard BENCHMARKS (`/shard-0`) sharing the parent's measure - slugs — multiplicity belongs in bencher's benchmark dimension, not as - one measure per shard index. -/ + whole — a rejected, OOM'd, partial or conditional run must never become + a bencher data point. Numeric fields (`phase-` included) pass + through as measures. Nested per-shard objects (`shard-cycles: {"0": …}`) + become per-shard BENCHMARKS (`/shard-0`) sharing the parent's + measure slugs — multiplicity belongs in bencher's benchmark dimension, + not as one measure per shard index. -/ def toBmf (rows : Json) : Json := Id.run do let mut out : Array (String × Json) := #[] for name in rowNames rows do diff --git a/Ix/Cli/CheckCmd.lean b/Ix/Cli/CheckCmd.lean index cf198f133..141203460 100644 --- a/Ix/Cli/CheckCmd.lean +++ b/Ix/Cli/CheckCmd.lean @@ -1,7 +1,10 @@ -/- +module +public import Cli +public import Ix.Shard.Check + +/-! `ix check`: execute the IxVM Aiur kernel over a Lean or `.ixe` environment, one constant at a time. - The Rust kernel typechecker that used to live under this name is now `ix check-rs`. Usage shape: @@ -16,9 +19,6 @@ The Rust-side `[compile_env]` / `[Env::put]` progress logs are off unless `IX_VERBOSE=1`; they add nothing at this layer. -/ -module -public import Cli -public import Ix.Shard.Check public section namespace Ix.Cli.CheckCmd @@ -134,10 +134,10 @@ def runCheckCmd (p : Cli.Parsed) : IO UInt32 := do if jobs != 1 && !interpSource && claimHex.isNone then runBatchCheck ixe names jobs toplevel useBytecode else - forEachClaim ixePath claimHex names keepGoing "check" interpSource (rust := p.hasFlag "rust") + forEachClaim ixePath claimHex names keepGoing "check" interpSource runOne | _, _ => - forEachClaim ixePath claimHex names keepGoing "check" interpSource (rust := p.hasFlag "rust") + forEachClaim ixePath claimHex names keepGoing "check" interpSource runOne end Ix.Cli.CheckCmd @@ -151,7 +151,6 @@ def checkCmd : Cli.Cmd := `[Cli| interp : String; "Use an interpreter instead of the codegen'd IxVM Rust kernel. Modes: `source` = Aiur source interpreter (richer per-execution error diagnostics, slowest); `bytecode` = generic Aiur bytecode interpreter (skips the regen + cargo rebuild cycle when iterating on `Ix/IxVM/*.lean`). Omit the flag entirely for the native codegen kernel." "keep-going"; "Continue past failures and report them at the end instead of halting on the first." "ixe" : String; "Path to a serialized `.ixe` env. When set, the binary reads the env from disk instead of using the compiled-in Lean env." - "rust"; "Without --ixe: compile each name's closure with the Rust compiler instead of the certified Lean pipeline. The Rust compiler runs none of the compile's checks, and its output is not covered by the compiler's correctness theorem." "claim" : String; "32-byte hex address of a persisted `Ix.Claim` in `~/.ix/store/`. When set, runs the `verify_claim` entrypoint once over the claim's witness against the `--ixe` env (single execution, skips per-const iteration)." "stats-out" : String; "Redirect the per-circuit statistics dump to this file (only used when exactly one constant is targeted)." "ixes" : String; "Path to a `.ixes` shard manifest (with --ixe). With --shard K: check the constants owned by shard K (ingress their closure, skip the frontier). Without --shard: check every shard of the partition concurrently, after a coverage check." diff --git a/Ix/Cli/CheckLeanCmd.lean b/Ix/Cli/CheckLeanCmd.lean index 187d72a43..435e7e5b7 100644 --- a/Ix/Cli/CheckLeanCmd.lean +++ b/Ix/Cli/CheckLeanCmd.lean @@ -6,6 +6,8 @@ public import Ix.Cli.CheckLeanTargets public import Ix.KernelCheck.Run public import Ix.KernelCheck.Stream public import Ix.HostAdmission +public import Ix.RoleBinding.Compile +public import Ix.RoleBinding.UserAxioms public import Ix.Store public import Ix.Runtime.Thread public import Ix.Benchmark.Results @@ -13,12 +15,21 @@ public import Ix.Benchmark.Results public section /-! The IxC host record checker behind check-lean. Both modes authenticate -input bytes. Bound whole-artifact runs additionally require the actual logical -or tiered entry. Selected/capped runs report only their selected declarations; +input bytes. A whole-artifact run of a bound artifact also runs the certified +logical or tiered entry (`Ix.HostAdmission.certify`) and reports its host +statement. Selected/capped runs report only their selected declarations; actual emitted-key coverage is required in both modes. A run that asks for the whole artifact of an artifact with no role binding cannot run the whole-artifact entry: it says so and exits with `exitPartial`, even when every declaration -passed. -/ +passed. + +Checking is strict: a safe axiom other than Lean's standard three declines. +`--allow-user-axioms` admits such axioms (`Key.isCustomAxiomEligible`) in the +declaration checks, and a whole run runs the kernel's entry that admits them +once, which lists the user axioms among the declarations. With none listed, +that run is the certified entry's (T1) and the host statement is issued. With +some, the run lists them, issues no host statement and exits with +`exitConditional`; so does a selected run whose declaration checks read one. -/ namespace Ix.Cli.CheckLeanCmd @@ -43,6 +54,11 @@ def targetError : CheckLeanTargets.Error → DriverError | .input n e => .refused n s!"{(readOutcome e).1}: {(readOutcome e).2}" | .coverage n s => .refused n s!"decline: {s}" +/-- Whether a selection asks for every declaration. -/ +def _root_.Ix.Cli.CheckLeanTargets.Selection.isWhole : Selection → Bool + | .all => true + | .only _ => false + structure CheckCfg where workers : Nat := 1 verbose : Bool := false @@ -61,6 +77,11 @@ structure PreparedInput where anon : Bool bound : Bool := false hasTier : Bool := false + /-- `--allow-user-axioms`: the declaration checks admit user axioms, and a + whole run takes the entry that admits them (`checkWhole`). Set only by + `prepareInput`, after the binding has passed the kernel's role-table check + (`UserAxioms.requireAxiomRoles`). -/ + allowUserAxioms : Bool := false prepareMs : Nat := 0 /-- Explicit coverage limits for the complete artifact entry, shared by both @@ -68,8 +89,17 @@ display modes. These are the host's whole-library byte/expansion bounds. -/ def artifactLimits : Admission.Limits := ⟨1 <<< 22, 1 <<< 22, 1 <<< 34, 1 <<< 28, 1 <<< 26⟩ /-- Authenticate first; retain raw windows and reader skeletons. No metadata -field is rebuilt through an incomplete Env literal. -/ -def prepareInput (bytes : ByteArray) (anon : Bool := false) : IO (Except DriverError PreparedInput) := do +field is rebuilt through an incomplete Env literal. A bound artifact's binding +is admitted with its records (`Artifact.prepareRecords`). In meta mode its +published names must agree with the binding (`RoleBinding.Compile.roleBindingError`, +which the compiler's `finalize` also runs). With `allowUserAxioms` the binding +must pass the kernel's role-table check for user axioms +(`Admission.admitAxiomPolicy .allowUser`, through `UserAxioms.requireAxiomRoles`: +`sorryAx`, `propext` and `Classical.choice` bound) before any declaration is +checked, since the record-by-record checks never reach admission; an artifact +with no binding is refused. -/ +def prepareInput (bytes : ByteArray) (anon : Bool := false) (allowUserAxioms : Bool := false) : + IO (Except DriverError PreparedInput) := do let start ← IO.monoMsNow let result : Except DriverError PreparedInput ← IO.lazyPure fun _ => do let parts ← (Ixon.deEnvVerifiedLazy bytes).mapError DriverError.infrastructure @@ -77,6 +107,13 @@ def prepareInput (bytes : ByteArray) (anon : Bool := false) : IO (Except DriverE (Ix.RoleBinding.Artifact.prepareRecords parts.env artifactLimits).map some |>.mapError (DriverError.artifact ∘ toString) else pure none + if allowUserAxioms then + (Ix.RoleBinding.UserAxioms.requireAxiomRoles parts.env.roleBinding).mapError + fun e => DriverError.artifact s!"artifact: decline: --allow-user-axioms: {e}" + if !anon && boundInput.isSome && !parts.namedRows.isEmpty then + let named ← parts.materializeAllNamed.mapError DriverError.infrastructure + if let some e := Ix.RoleBinding.Compile.roleBindingError named then + throw (.artifact s!"artifact: role binding: {e}") let (table, pre) ← match boundInput with | some input => pure (input.prepared.table, input.prepared.pre) | none => do @@ -107,7 +144,7 @@ def prepareInput (bytes : ByteArray) (anon : Bool := false) : IO (Except DriverE let inventory ← if anon then (anonymousCandidates store).mapError targetError else pure (namedCandidates store (parts.namedRows.map fun (r : Ixon.NamedRow) => (toString r.name, r.addr))) pure { parts, setup, pre, hints, inventory, anon, bound := boundInput.isSome - hasTier := (boundInput.map (·.hasTier)).getD false : PreparedInput } + hasTier := (boundInput.map (·.hasTier)).getD false, allowUserAxioms : PreparedInput } let elapsed := (← IO.monoMsNow) - start return result.map fun p => { p with prepareMs := elapsed } @@ -117,13 +154,20 @@ def PreparedInput.record (p : PreparedInput) (a : Address) : Except ReadError Ix | none => match p.setup.store[a]? with | some c => .ok c | none => .error (.malformed "record is missing") +/-- The whole-artifact entry that ran and what it accepted. `conditional` is +the verdict of the entry `--allow-user-axioms` runs (`Artifact.checkConditional`, +or `checkTieredConditional` with its tier census in `typed`) when the kernel +lists user axioms among the declarations: no theorem covers it and it issues no +host statement, and it holds only under `axioms`, those the kernel listed. -/ inductive ArtifactAdmission where | logical (records : Nat) | tiered (records typed : Nat) + | conditional (records : Nat) (typed : Option Nat) (axioms : Array Address) deriving BEq, Repr structure CheckReport where targets : Array TargetResult + rows : Array Row := #[] execution : Execution omitted : Array String records : Nat @@ -134,27 +178,75 @@ structure CheckReport where supportMicros : Nat readMicros : Nat bound : Bool := false + /-- A whole run of a bound artifact: the binding's roles, `(admitted, + refused)`, a role refused when this run refused its record. -/ + roles : Option (Nat × Nat) := none + /-- The user axioms whose records the declaration checks read and accepted + (`--allow-user-axioms`, `admittedUserAxioms`). -/ + userAxioms : Array Address := #[] artifactAdmission : Option ArtifactAdmission := none hostStatement : Option Ix.HostAdmission.Statement := none artifactMs : Nat := 0 -/-- Route from authenticated record headers before either entry runs. A -failure never selects another checker. Selected/capped runs make no complete -artifact claim, and unresolved thin bundles have no external-store fallback. -/ -def checkWhole (p : PreparedInput) (selection : Selection) (maxGroups : Option Nat) : - Except DriverError (Option (ArtifactAdmission × Ix.HostAdmission.Statement)) := do - unless p.bound do return none - match selection, maxGroups with - | .all, none => pure () - | _, _ => return none +/-- Whether a run of `selection`, capped at `maxGroups`, runs the +whole-artifact entry: a bound artifact, every declaration, no cap. An +unresolved thin artifact declines; there is no external-store fallback. -/ +def wholeEntry (p : PreparedInput) (selection : Selection) (maxGroups : Option Nat) : + Except DriverError Bool := do + unless p.bound && selection.isWhole && maxGroups.isNone do return false unless p.parts.env.assumptions.isEmpty do throw (.artifact "artifact: decline: thin artifact assumptions require explicit resolution") - let ⟨statement, checked⟩ ← (Ix.HostAdmission.certify p.parts.env artifactLimits).mapError + return true + +/-- The verdict of a certified whole-artifact run. -/ +def ArtifactAdmission.ofRun {artifact : Ixon.Env} {limits : Admission.Limits} : + Ix.HostAdmission.Run artifact limits → ArtifactAdmission + | .logical receipt => .logical receipt.prepared.records.length + | .tiered receipt => .tiered receipt.prepared.records.length receipt.tier.length + +/-- A certified run's verdict and its host statement, derived from the run as +`HostAdmission.certify` derives it (`HostAdmission.statementOf`). -/ +def certifiedVerdict (artifact : Ixon.Env) (run : Ix.HostAdmission.Run artifact artifactLimits) : + Except DriverError (Option (ArtifactAdmission × Option Ix.HostAdmission.Statement)) := do + let statement ← (Ix.HostAdmission.statementOf artifact artifactLimits run).mapError (DriverError.artifact ∘ toString) - let admission := match checked.run with - | .logical receipt => ArtifactAdmission.logical receipt.prepared.records.length - | .tiered receipt => .tiered receipt.prepared.records.length receipt.tier.length - return some (admission, statement) + return some (ArtifactAdmission.ofRun run, some statement) + +/-- Route from authenticated record headers before either entry runs. A +failure never selects another checker. Selected/capped runs make no complete +artifact claim. Without `allowUserAxioms` the certified entry runs +(`HostAdmission.certify`). With it, the entry that admits user axioms runs once +(`Artifact.checkConditional`, `checkTieredConditional`: the kernel's +`Admission.checkBytesUnder .allowUser` and its tiered form), and the kernel's +list of the user axioms among the declarations it ran over chooses the verdict: +with none, its receipt is the certified entry's (T1, +`ReceiptConditional.toReceipt`, `TierReceiptConditional.toTierReceipt`) and the +host statement is derived from it as `certify` derives it; with some, the +verdict is conditional on them and there is no statement. -/ +def checkWhole (p : PreparedInput) (selection : Selection) (maxGroups : Option Nat) : + Except DriverError (Option (ArtifactAdmission × Option Ix.HostAdmission.Statement)) := do + unless (← wholeEntry p selection maxGroups) do return none + let artifact := p.parts.env + unless p.allowUserAxioms do + let ⟨statement, checked⟩ ← (Ix.HostAdmission.certify artifact artifactLimits).mapError + (DriverError.artifact ∘ toString) + return some (ArtifactAdmission.ofRun checked.run, some statement) + if p.hasTier then + let receipt ← (Ix.RoleBinding.Artifact.checkTieredConditional artifact artifactLimits).mapError + (DriverError.artifact ∘ toString) + match noAxioms : receipt.axioms with + | [] => certifiedVerdict artifact (.tiered (receipt.toTierReceipt noAxioms)) + | _ :: _ => + return some (.conditional receipt.prepared.records.length (some receipt.tier.length) + (Ix.RoleBinding.UserAxioms.addresses receipt.axioms), none) + else + let receipt ← (Ix.RoleBinding.Artifact.checkConditional artifact artifactLimits).mapError + (DriverError.artifact ∘ toString) + match noAxioms : receipt.axioms with + | [] => certifiedVerdict artifact (.logical (receipt.toReceipt noAxioms)) + | _ :: _ => + return some (.conditional receipt.prepared.records.length none + (Ix.RoleBinding.UserAxioms.addresses receipt.axioms), none) def success (outcome : String) : Bool := outcome == "accept" || outcome == "typed" def CheckReport.targetsCovered (r : CheckReport) : Nat := r.targets.size @@ -163,11 +255,35 @@ def CheckReport.typed (r : CheckReport) : Nat := (r.targets.filter (·.outcome = def CheckReport.failures (r : CheckReport) : Array (String × String) := (r.targets.filter (!success ·.outcome)).map fun t => (t.target.label, s!"{t.outcome}: {t.reason}") +/-- The user axioms a run's verdict is conditional on: the conditional +whole-artifact entry's, otherwise those its declaration checks read and accepted. -/ +def CheckReport.conditionalOn (r : CheckReport) : Array Address := + match r.artifactAdmission with + | some (.conditional _ _ axioms) => axioms + | _ => r.userAxioms + +/-- The binding's roles after a whole run's declaration checks: `(admitted, +refused)`, where a role is refused when this run refused its record. -/ +def roleCounts (p : PreparedInput) (rows : Array Row) : Nat × Nat := + let failed : Std.HashSet Address := + rows.foldl (fun s r => if success r.outcome then s else s.insert r.address) {} + let refused := p.setup.table.bound.filter fun (_, a) => + (p.setup.store[a]?).any fun c => failed.contains (owner a c) + (p.setup.table.bound.size - refused.size, refused.size) + /-- **The exit status of a partial check**: every declaration checked passed, but the whole-artifact admission the run asked for did not run, because the artifact carries no role binding. Distinct from success (0), a usage error (2) and a refusal (3). -/ def exitPartial : UInt32 := 4 +/-- **The exit status of a conditional run**: every declaration checked passed +with user axioms admitted (`--allow-user-axioms`), so the verdict holds only +under the axioms the run lists: a whole artifact accepted by the uncertified +conditional entry, or a selected run whose declaration checks read a user +axiom. Distinct from success (0), an infrastructure failure (1), a usage +error (2), a refusal (3) and a partial check (`exitPartial`). -/ +def exitConditional : UInt32 := 5 + /-- What a completed run did not check, if anything: a selected or capped run covers only its selection; a run that asked for the whole artifact (`wholeRequested`) of an artifact with no role binding covers only the per-declaration checks. -/ @@ -175,7 +291,10 @@ def coverageNotice (report : CheckReport) (wholeRequested : Bool) : Option Strin match report.artifactAdmission with | some _ => none | none => - if report.bound then some "[check-lean] selected checking only; no whole-artifact receipt" + if report.bound then + if wholeRequested then + some "[check-lean] whole-artifact admission did not run because declaration checks failed" + else some "[check-lean] selected checking only; no whole-artifact receipt" else if wholeRequested then some s!"[check-lean] partial check: the artifact carries no role binding, so the \ whole-artifact admission did not run; only the per-declaration checks ran \ @@ -184,14 +303,37 @@ whole-artifact admission did not run; only the per-declaration checks ran \ some "[check-lean] selected checking only; the artifact carries no role binding, so no \ whole-artifact admission could run" -/-- The exit status of a completed run: a refusal (`exitRejected`) when a checked -declaration failed; otherwise `exitPartial` when the whole artifact was asked for and -its admission did not run, else 0. -/ -def runExit (report : CheckReport) (wholeRequested : Bool) : UInt32 := +/-- The exit status of a completed run: a refusal (`exitRejected`) when a +checked declaration failed; otherwise a usage error when a host statement was +asked for (`hostRequested`) and the run issued none; otherwise `exitPartial` +when the whole artifact was asked for and its admission did not run; otherwise +`exitConditional` when the verdict is conditional on user axioms; else 0. -/ +def runExit (report : CheckReport) (wholeRequested : Bool) (hostRequested : Bool := false) : UInt32 := if !report.failures.isEmpty then Ix.Benchmark.Results.exitRejected + else if hostRequested && report.hostStatement.isNone then Ix.Benchmark.Results.exitUsage else if wholeRequested && report.artifactAdmission.isNone then exitPartial + else if !report.conditionalOn.isEmpty then exitConditional else 0 +/-- The results-row status of a completed run (`Ix.Benchmark.Results`): its +exit status (`runExit`, no host statement asked for) by name. `rejected` (3), +`partial` (`exitPartial`), `conditional` (`exitConditional`), else `ok`. -/ +def runStatus (report : CheckReport) (wholeRequested : Bool) : String := + let code := runExit report wholeRequested + if code == Ix.Benchmark.Results.exitRejected then "rejected" + else if code == exitPartial then "partial" + else if code == exitConditional then "conditional" + else "ok" + +/-- The user axioms a run's declaration checks admitted: of those they +read (`read`), the ones whose records they accepted (`rows`), by address. A +record read and then blocked, declined or rejected admits nothing; this is how +`kernel-check-ixe` lists them. -/ +def admittedUserAxioms (read : Std.HashSet Address) (rows : Array Row) : Array Address := + let accepted := rows.foldl (init := ({} : Std.HashSet Address)) fun s r => + if r.outcome == "accept" then s.insert r.address else s + (read.toArray.filter accepted.contains).qsort (compare · · == .lt) + /-- No persistent marking: each call owns fresh serial checker/reader state. The exact cached tier declarations and onRead values supply key observations. -/ def runMode (p : PreparedInput) (cfg : CheckCfg) (plan : Plan) (cx : Ctx md) (ev : Evidences md) @@ -207,6 +349,7 @@ def runMode (p : PreparedInput) (cfg : CheckCfg) (plan : Plan) (cx : Ctx md) (ev if let .ok decl := d then observed := noteTier erase observed a decl let seen ← IO.mkRef observed let actual ← IO.mkRef (tiers.fold (init := {}) fun (set : Std.HashSet Address) a _ => set.insert a) + let userAxioms ← IO.mkRef ({} : Std.HashSet Address) let src : LoopSource (State md) md := { view := p.setup.view, init := pure {}, commit := fun st _ rd => st.commit rd, read := fun st a => (p.record a >>= readRecord cx st a).map decorate, @@ -228,11 +371,17 @@ def runMode (p : PreparedInput) (cfg : CheckCfg) (plan : Plan) (cx : Ctx md) (ev IO.eprintln s!"{cfg.tag} {subjects}/{plan.targets.size} subjects, {records} record rows, elapsed {(now - start) / 1000}s" last := now try - let run ← runRecords src ev (fun _ => #[]) plan.addresses { workers := some cfg.workers, tier := true } + let run ← runRecords src ev (fun _ => #[]) plan.addresses + { workers := some cfg.workers, tier := true, allowUserAxioms := p.allowUserAxioms } (spell := spell) (onRead := fun a r _ => do actual.modify (·.insert a) - if let .ok rd := r then seen.modify (noteRead erase · a rd)) + if let .ok rd := r then + seen.modify (noteRead erase · a rd) + if p.allowUserAxioms then + for d in rd.decls do + if let some cv := d.userAxiom? then + if let .id address := erase cv.name then userAxioms.modify (·.insert address)) (emit := fun row => do let selected := (byProducer.getD row.address []).reverse completed.modify fun (n, r) => (n + selected.length, r + 1) @@ -257,16 +406,19 @@ def runMode (p : PreparedInput) (cfg : CheckCfg) (plan : Plan) (cx : Ctx md) (ev let reads := run.rows.foldl (fun n r => if actual.contains r.address then n + r.readMicros else n) 0 let elapsedMs := (← IO.monoMsNow) - start return .ok { - targets, execution := run.execution, omitted := plan.omitted, records := actual.size + targets, rows := run.rows, execution := run.execution, omitted := plan.omitted, records := actual.size inputMs := p.prepareMs, prepareMs, elapsedMs, subjectMicros := subject - supportMicros := total - subject, readMicros := reads } + supportMicros := total - subject, readMicros := reads + userAxioms := admittedUserAxioms (← userAxioms.get) run.rows } finally stop.set true let _ ← IO.wait reporter pure () /-- Preparation is shareable; every run owns ordinary reclaimable checker -state. Tier support explicitly chooses one effective worker. -/ +state. Tier support explicitly chooses one effective worker. The declaration +checks run first; the whole-artifact entry (`checkWhole`) runs only when they +all pass. -/ def runPrepared (p : PreparedInput) (cfg : CheckCfg) (selection : Selection) (maxGroups : Option Nat := none) (failOut? : Option IO.FS.Handle := none) : IO (Except DriverError CheckReport) := do if cfg.workers == 0 then return .error (.usage "--workers must be greater than zero") @@ -274,8 +426,11 @@ def runPrepared (p : PreparedInput) (cfg : CheckCfg) (selection : Selection) let start ← IO.monoMsNow let plan ← match select p.setup (p.pre.records.map fun (a, c) => owner a c) p.inventory selection maxGroups with | .ok v => pure v | .error e => return .error (targetError e) + -- an unresolved thin whole run declines before any declaration is checked + let whole ← match wholeEntry p selection maxGroups with + | .ok w => pure w | .error e => return .error e if !cfg.silent then IO.eprintln s!"{cfg.tag} {plan.targets.size} selected subjects, {plan.producers.size} producers, {plan.addresses.size} support/prelude records; workers requested {cfg.workers}, effective 1 (serial typing tier); {plan.omitted.size} omitted by cap" - let job : IO (Except DriverError CheckReport) ← if p.anon then + let declarations : IO (Except DriverError CheckReport) ← if p.anon then pure (runMode p cfg plan p.setup.cx p.setup.evidence id id id toString start failOut?) else do let mut included := plan.addresses.foldl (fun (s : Std.HashSet Address) a => s.insert a) {} @@ -298,30 +453,30 @@ def runPrepared (p : PreparedInput) (cfg : CheckCfg) (selection : Selection) pure (runMode p cfg plan cx ev (fun k => Key.map (md := .meta) (md' := .anon) id k) (fun rd => (Ix.KernelDisplay.nameRead nc (withDisplay display rd)).1) (fun d => (nameTier nc d {}).1) (spellWith display) start failOut?) - let diagnostics := job - let job : IO (Except DriverError CheckReport) := do - let started ← IO.monoMsNow - let admitted ← IO.lazyPure fun _ => checkWhole p selection maxGroups - let elapsed := (← IO.monoMsNow) - started - match admitted with + let run : IO (Except DriverError CheckReport) := do + match ← declarations with | .error e => return .error e - | .ok admission => - return (← diagnostics).map fun report => { report with - bound := p.bound, artifactAdmission := admission.map (·.1) - hostStatement := admission.map (·.2), artifactMs := elapsed - prepareMs := report.prepareMs - elapsed } + | .ok report => + let report := { report with + bound := p.bound, roles := if whole then some (roleCounts p report.rows) else none } + if !whole || !report.failures.isEmpty then return .ok report + let started ← IO.monoMsNow + let admitted ← IO.lazyPure fun _ => checkWhole p selection maxGroups + let elapsed := (← IO.monoMsNow) - started + match admitted with + | .error e => return .error e + | .ok admission => + return .ok { report with + artifactAdmission := admission.map (·.1), hostStatement := admission.bind (·.2) + artifactMs := elapsed } if cfg.stackBytes != 0 then Ix.Runtime.setLeanThreadStackSize (USize.ofNat cfg.stackBytes) - IO.ofExcept (← IO.wait (← IO.asTask (prio := .dedicated) job)) + IO.ofExcept (← IO.wait (← IO.asTask (prio := .dedicated) run)) catch e => return .error (.infrastructure e.toString) def runMetaCheck (bytes : ByteArray) (cfg : CheckCfg) (selection : Selection) (maxGroups : Option Nat := none) (failOut? : Option IO.FS.Handle := none) : IO (Except DriverError CheckReport) := do match ← prepareInput bytes with | .error e => return .error e | .ok p => runPrepared p cfg selection maxGroups failOut? -def runAnonCheck (bytes : ByteArray) (cfg : CheckCfg) (selection : Selection) - (maxGroups : Option Nat := none) (failOut? : Option IO.FS.Handle := none) : IO (Except DriverError CheckReport) := do - match ← prepareInput bytes true with - | .error e => return .error e | .ok p => runPrepared p cfg selection maxGroups failOut? /-- First supplied setting wins; malformed values are usage errors. -/ def envNat (names : List String) (dflt : Nat) : IO (Except DriverError Nat) := do @@ -338,74 +493,264 @@ def defaultWorkers : IO Nat := do | .error _ => return 8 def checkConfig (p : Cli.Parsed) : IO (Except DriverError CheckCfg) := do - if p.hasFlag "no-verify" then return .error (.usage "--no-verify is unsupported by IxC; byte authentication is required") - if ((p.flag? "clear-every").map (·.as! Nat)).getD 0 != 0 then return .error (.usage "--clear-every must be 0; Ix.Tc cache controls do not apply to IxC") - for name in ["IX_TC_STEP_TRACE", "IX_TC_STATS", "IX_TC_NO_ACCEL", "IX_KERNEL_CHECK_ACTIVE_SLOW_MS"] do - if let some v ← IO.getEnv name then - if v.trimAscii.toString.toNat? != some 0 then return .error (.usage s!"{name} is unsupported by IxC; unset it or use 0") - if (← IO.getEnv "IX_TC_DEBUG_CONST").any (!·.trimAscii.isEmpty) then return .error (.usage "IX_TC_DEBUG_CONST is unsupported by IxC") - if (← IO.getEnv "IX_MAX_REC_FUEL").isSome then return .error (.usage "IX_MAX_REC_FUEL cannot override IxC's checker limits") let workers ← match (p.flag? "workers").map (·.as! Nat) with | some n => pure n | none => defaultWorkers if workers == 0 then return .error (.usage "--workers must be greater than zero") let progress ← envNat ["IX_KERNEL_CHECK_PROGRESS_MS", "IX_PROGRESS_MS"] 2000 let slow ← envNat ["IX_KERNEL_CHECK_SLOW_MS"] 7000 return do pure { workers, verbose := p.hasFlag "verbose", progressMs := ← progress, slowMs := ← slow } +private def csvField (s : String) : String := + if s.any (fun c => c == ',' || c == '"') + then "\"" ++ s.replace "\"" "\"\"" ++ "\"" + else s + +/-- One CSV row per record, its first row when runs repeat it (the per-name +runs share support records). -/ +private def writePerConstCsv (p : PreparedInput) (rows : Array Row) (outCsv : String) : IO Unit := do + let mut named : Std.HashMap String String := {} + for r in p.parts.namedRows do + let key := toString r.addr + if !named.contains key then + named := named.insert key (toString r.name) + let mut viaPrj : Std.HashMap String String := {} + for (caddr, c) in p.setup.store do + let blk? := match c.info with + | .iPrj prj => some prj.block + | .cPrj prj => some prj.block + | .rPrj prj => some prj.block + | .dPrj prj => some prj.block + | _ => none + let some blk := blk? | continue + let key := toString blk + if !viaPrj.contains key then + if let some n := named[toString caddr]? then + viaPrj := viaPrj.insert key n + let mut out := "name,addr,consts,nanos,heartbeats,subst,whnf,def_eq,nat_arith,intern,cost\n" + let mut written : Std.HashSet Address := {} + for row in rows do + if written.contains row.address then continue + written := written.insert row.address + let addr := toString row.address + let name := match named[addr]? with + | some n => n + | none => match viaPrj[addr]? with + | some n => n + | none => s!"@{addr.take 16}" + let nanos := (row.micros + row.readMicros) * 1000 + out := out ++ s!"{csvField name},{addr},1,{nanos},0,0,0,0,0,0,0\n" + IO.FS.writeFile outCsv out + +/-- A benchmark results row (`Ix.Benchmark.Results`) for one run. `constants` +is the number of records the run checked or read, whole-artifact or per name; +`throughput` is that count per second of `check-time`. `status` is the run's +exit status by name (`runStatus`): `rejected`, `partial`, `conditional` or `ok`. -/ +private def writeJsonReportRow (jsonPath key : String) (report : CheckReport) + (wholeRequested : Bool) : IO Unit := do + let secs := report.elapsedMs.toFloat / 1000.0 + let tput := if report.elapsedMs > 0 + then report.records.toFloat * 1000.0 / report.elapsedMs.toFloat else 0.0 + let peakRss := (← statusKb "VmHWM:") * 1024 + let status := runStatus report wholeRequested + Ix.Benchmark.Results.writeRow jsonPath key status + [ ("constants", Lean.toJson report.records) + , ("check-time", Ix.Benchmark.Results.jsonRound 3 secs) + , ("throughput", Ix.Benchmark.Results.jsonRound 2 tput) + , ("peak-rss", Lean.toJson peakRss) ] + +/-- The command line, read once. -/ +structure Options where + path : String + cfg : CheckCfg + anon : Bool + allowUserAxioms : Bool + /-- Whether `--consts` or `--consts-file` was given (possibly empty). -/ + hasConsts : Bool + only : Array String + cap : Option Nat + json? : Option String + jsonName? : Option String + perConst? : Option String + failOut? : Option String + hostOut? : Option String + hostStore : Bool + +def Options.selection (o : Options) : Selection := if o.hasConsts then .only o.only else .all +def Options.wholeRequested (o : Options) : Bool := o.selection.isWhole && o.cap.isNone +def Options.hostRequested (o : Options) : Bool := o.hostOut?.isSome || o.hostStore +/-- `--json` with a selection: one full-closure run and row per selected name, +into this results file. -/ +def Options.perName? (o : Options) : Option String := if o.hasConsts then o.json? else none + +def parseOptions (p : Cli.Parsed) (path : String) : IO (Except DriverError Options) := do + let cfg ← match ← checkConfig p with | .ok c => pure c | .error e => return .error e + let string (flag : String) := (p.flag? flag).map (·.as! String) + let o : Options := { + path, cfg, anon := p.hasFlag "anon", allowUserAxioms := p.hasFlag "allow-user-axioms" + hasConsts := (p.flag? "consts").isSome || (p.flag? "consts-file").isSome + only := (← ConstsFile.gather p), cap := (p.flag? "max").map (·.as! Nat) + json? := string "json", jsonName? := string "json-name", perConst? := string "per-const" + failOut? := string "fail-out", hostOut? := string "host-out", hostStore := p.hasFlag "host-store" } + if o.hostRequested && (o.hasConsts || o.cap.isSome) then + return .error (.usage "[check-lean] host statement output requires a complete bound artifact check") + if o.perName?.isSome && o.failOut?.isSome then + return .error (.usage "[check-lean] --json per-name rows and --fail-out cannot be combined") + if o.perName?.isSome && o.only.isEmpty then + return .error (.usage "[check-lean] explicit selector is empty") + return .ok o + +/-- Print a refusal and record it in the fail-out file; its exit status. -/ +def refuse (out : Option IO.FS.Handle) (e : DriverError) : IO UInt32 := do + IO.eprintln s!"[check-lean] {e.message}" + if let some h := out then + h.putStrLn s!"# {e.message.replace "\n" " | "}" + if let .refused label _ := e then h.putStrLn (if label.startsWith "#" then (label.drop 1).toString else label) + h.flush + return e.exitCode + +/-- The lines that list the user axioms a verdict is conditional on: each +axiom's address, with its names in meta mode. -/ +def conditionalLines (p : PreparedInput) (scope : String) (axioms : Array Address) : Array String := Id.run do + let mut names : Std.HashMap Address (Array String) := {} + unless p.anon do + for r in p.parts.namedRows do + names := names.insert r.addr ((names.getD r.addr #[]).push (toString r.name)) + let mut lines := #[s!"[check-lean] {scope}: CONDITIONAL on {axioms.size} user axioms:"] + for a in axioms do + lines := lines.push <| match names[a]? with + | some ns => s!"[check-lean] {a} {" | ".intercalate ns.toList}" + | none => s!"[check-lean] {a}" + return lines + +/-- One full-closure run per selected name, each written as its own results +row as soon as it finishes: keyed by the name, after `/` when +`--json-name` is given. A name that resolves to no declaration, or whose run +refuses its input, is reported and the remaining names still run. The result +merges the runs, with the unresolved names and the refused ones. -/ +def runPerName (o : Options) (p : PreparedInput) (json : String) : + IO (Except DriverError (CheckReport × Array String × Array String)) := do + -- anonymous runs also resolve Lean names, through the named inventory + let index := candidateIndex p.inventory + let namedIndex := if p.anon then + candidateIndex (namedCandidates p.setup.store + (p.parts.namedRows.map fun (r : Ixon.NamedRow) => (toString r.name, r.addr))) + else index + let key (name : String) := match o.jsonName? with | some stem => s!"{stem}/{name}" | none => name + let mut merged : CheckReport := { + targets := #[], execution := .serialTier (some o.cfg.workers), omitted := #[], records := 0 + inputMs := p.prepareMs, prepareMs := 0, elapsedMs := 0, subjectMicros := 0, supportMicros := 0 + readMicros := 0, bound := p.bound } + let mut userAxioms : Std.HashSet Address := {} + let mut unresolved : Array String := #[] + let mut refused : Array String := #[] + for name in o.only do + let found : Except CheckLeanTargets.Error Candidate := match findIndexed index name with + | .ok c => .ok c + | .error e => if p.anon then findIndexed namedIndex name else .error e + match found with + | .error e => + IO.eprintln s!"[check-lean] {name}: unresolved: {(targetError e).message}" + unresolved := unresolved.push name + | .ok c => + let single := { p with inventory := #[{ c with label := name, fileLabel := name }] } + match ← runPrepared single o.cfg (.only #[name]) o.cap none with + | .error (.refused label message) => + IO.eprintln s!"[check-lean] {label}: {message}" + Ix.Benchmark.Results.writeRow json (key name) "rejected" [] + refused := refused.push name + | .error e => return .error e + | .ok report => + writeJsonReportRow json (key name) report false + userAxioms := report.userAxioms.foldl (·.insert ·) userAxioms + merged := { merged with + targets := merged.targets ++ report.targets, rows := merged.rows ++ report.rows + execution := report.execution, records := merged.records + report.records + prepareMs := merged.prepareMs + report.prepareMs, elapsedMs := merged.elapsedMs + report.elapsedMs + subjectMicros := merged.subjectMicros + report.subjectMicros + supportMicros := merged.supportMicros + report.supportMicros + readMicros := merged.readMicros + report.readMicros } + return .ok ({ merged with userAxioms := userAxioms.toArray.qsort (compare · · == .lt) }, unresolved, refused) + +/-- Report a completed run: the fail-out total, the per-record CSV, the role +and whole-artifact verdict lines (or the coverage notice), the host statement, +the summary and machine row, and the whole-run results row. -/ +def emitReport (o : Options) (p : PreparedInput) (report : CheckReport) (out : Option IO.FS.Handle) : IO Unit := do + if let some h := out then h.putStrLn s!"\n# total failures: {report.failures.size}"; h.flush + if let some csv := o.perConst? then writePerConstCsv p report.rows csv + if let some (admitted, refused) := report.roles then + IO.println s!"[check-lean] bound roles: {admitted} admitted, {refused} refused" + match report.artifactAdmission with + | some (.logical records) => + IO.println s!"[check-lean] whole artifact: logical entry accepted all {records} records ({report.artifactMs}ms)" + | some (.tiered records typed) => + IO.println s!"[check-lean] whole artifact: logical entry and typing tier accepted all {records} records; {typed} tier constants typed only ({report.artifactMs}ms)" + | some (.conditional records typed axioms) => + for line in conditionalLines p "whole artifact" axioms do IO.println line + let entry := match typed with + | some n => s!"conditional logical entry and typing tier ({n} tier constants typed only)" + | none => "conditional logical entry" + IO.println s!"[check-lean] {entry}: {records} records checked with these axioms admitted; \ +uncertified, no host statement ({report.artifactMs}ms)" + | none => + if report.failures.isEmpty && !report.userAxioms.isEmpty then + for line in conditionalLines p "selected checking" report.userAxioms do IO.println line + if let some notice := coverageNotice report o.wholeRequested then IO.println notice + if let some statement := report.hostStatement then + let hostBytes ← IO.ofExcept statement.bytes + let address := Address.admissionHash hostBytes + IO.println s!"[check-lean] host admission statement: {address}" + if report.failures.isEmpty then + if let some output := o.hostOut? then + IO.FS.writeBinFile output hostBytes + if o.hostStore then + let stored ← StoreIO.toIO (Store.write hostBytes) + unless stored == address do throw (IO.userError "stored host statement address differs") + else if o.hostRequested && report.failures.isEmpty then + let axioms := report.conditionalOn + IO.eprintln <| if axioms.isEmpty then + "[check-lean] host statement output requires a complete bound artifact check" + else s!"[check-lean] host statement output requires an unconditional whole-artifact \ +admission: this run is CONDITIONAL on {axioms.size} user axioms and issues no host statement" + IO.println s!"[check-lean] {report.passed}/{report.targetsCovered} subjects passed ({report.typed} typed only), {report.records} checked/read records" + IO.println s!"[check-lean] authenticated input {report.inputMs}ms; run preparation {report.prepareMs}ms; selected checker {report.subjectMicros / 1000}ms; support checker {report.supportMicros / 1000}ms; reading {report.readMicros / 1000}ms; run {report.elapsedMs}ms" + IO.println s!"##check-lean## {report.elapsedMs} {report.passed} {report.failures.size} {report.targetsCovered}" + if let some json := o.json? then + writeJsonReportRow json (o.jsonName?.getD "env") report o.wholeRequested + def runCheckLeanCmd (p : Cli.Parsed) : IO UInt32 := do let some path := p.positionalArg? "path" | p.printError "error: must specify "; return 2 - let cfg ← match ← checkConfig p with - | .ok c => pure c | .error e => IO.eprintln e.message; return e.exitCode try - let only ← ConstsFile.gather p - let selection := if (p.flag? "consts").isSome || (p.flag? "consts-file").isSome then Selection.only only else .all - let cap := (p.flag? "max").map (·.as! Nat) - let wholeRequested := (match selection with | .all => true | .only _ => false) && cap.isNone - let persistHost := p.hasFlag "host-out" || p.hasFlag "host-store" - if persistHost && ((match selection with | .all => false | .only _ => true) || cap.isSome) then - IO.eprintln "[check-lean] host statement output requires a complete bound artifact check" - return 2 - let bytes ← IO.FS.readBinFile (path.as! String) - let out ← match p.flag? "fail-out" with - | none => pure none - | some f => do - let h ← IO.FS.Handle.mk (f.as! String) .write - h.putStrLn s!"# ix check-lean failures\n# env: {path.as! String}" - pure (some h) - IO.println s!"Running IxC record check ({if p.hasFlag "anon" then "anon" else "meta"} mode)" - let result ← if p.hasFlag "anon" then runAnonCheck bytes cfg selection cap out else runMetaCheck bytes cfg selection cap out - match result with - | .error e => - IO.eprintln s!"[check-lean] {e.message}" - if let some h := out then - h.putStrLn s!"# {e.message.replace "\n" " | "}" - if let .refused label _ := e then h.putStrLn (if label.startsWith "#" then (label.drop 1).toString else label) - h.flush - return e.exitCode + let o ← match ← parseOptions p (path.as! String) with + | .ok o => pure o | .error e => IO.eprintln e.message; return e.exitCode + let out ← o.failOut?.mapM fun f => do + let h ← IO.FS.Handle.mk f .write + h.putStrLn s!"# ix check-lean failures\n# env: {o.path}" + pure h + let bytes ← IO.FS.readBinFile o.path + IO.println s!"Running IxC record check ({if o.anon then "anon" else "meta"} mode\ +{if o.allowUserAxioms then "; user axioms allowed" else ""})" + let prepared ← match ← prepareInput bytes o.anon o.allowUserAxioms with + | .ok prep => pure prep + | .error e => return (← refuse out e) + if let some json := o.perName? then + let startAll ← IO.monoMsNow + let (report, unresolved, refused) ← match ← runPerName o prepared json with + | .ok r => pure r + | .error e => return (← refuse out e) + if let some csv := o.perConst? then writePerConstCsv prepared report.rows csv + if report.failures.isEmpty && refused.isEmpty && !report.userAxioms.isEmpty then + for line in conditionalLines prepared "selected checking" report.userAxioms do IO.println line + let failed := report.failures.size + refused.size + IO.println s!"[check-lean] {report.passed}/{o.only.size} subjects passed ({report.typed} typed only), \ +{failed} failed, {unresolved.size} unresolved" + IO.println s!"##check-lean## {(← IO.monoMsNow) - startAll} {report.passed} {failed + unresolved.size} {o.only.size}" + return if !unresolved.isEmpty then Ix.Benchmark.Results.exitUsage + else if !refused.isEmpty then Ix.Benchmark.Results.exitRejected + else runExit report false + match ← runPrepared prepared o.cfg o.selection o.cap out with + | .error e => return (← refuse out e) | .ok report => - if persistHost && report.hostStatement.isNone then - IO.eprintln "[check-lean] host statement output requires a complete bound artifact check" - return 2 - if let some h := out then h.putStrLn s!"\n# total failures: {report.failures.size}"; h.flush - match report.artifactAdmission with - | some (.logical records) => - IO.println s!"[check-lean] whole artifact: logical entry accepted all {records} records ({report.artifactMs}ms)" - | some (.tiered records typed) => - IO.println s!"[check-lean] whole artifact: logical entry and typing tier accepted all {records} records; {typed} tier constants typed only ({report.artifactMs}ms)" - | none => if let some notice := coverageNotice report wholeRequested then IO.println notice - if let some statement := report.hostStatement then - let hostBytes ← IO.ofExcept statement.bytes - let address := Address.admissionHash hostBytes - IO.println s!"[check-lean] host admission statement: {address}" - if report.failures.isEmpty then - if let some output := p.flag? "host-out" then - IO.FS.writeBinFile (output.as! String) hostBytes - if p.hasFlag "host-store" then - let stored ← StoreIO.toIO (Store.write hostBytes) - unless stored == address do throw (IO.userError "stored host statement address differs") - IO.println s!"[check-lean] {report.passed}/{report.targetsCovered} subjects passed ({report.typed} typed only), {report.records} checked/read records" - IO.println s!"[check-lean] authenticated input {report.inputMs}ms; run preparation {report.prepareMs}ms; selected checker {report.subjectMicros / 1000}ms; support checker {report.supportMicros / 1000}ms; reading {report.readMicros / 1000}ms; run {report.elapsedMs}ms" - IO.println s!"##check-lean## {report.elapsedMs} {report.passed} {report.failures.size} {report.targetsCovered}" - return runExit report wholeRequested + emitReport o prepared report out + return runExit report o.wholeRequested o.hostRequested catch e => IO.eprintln s!"[check-lean] {e}"; return 1 end Ix.Cli.CheckLeanCmd @@ -413,18 +758,20 @@ end Ix.Cli.CheckLeanCmd open Ix.Cli.CheckLeanCmd in def checkLeanCmd : Cli.Cmd := `[Cli| "check-lean" VIA runCheckLeanCmd; - "Check authenticated .ixe declarations with IxC (bound whole inputs use actual artifact admission; meta display by default). Exit 0: everything asked for passed; 3: a declaration failed; 4: every declaration passed but the whole artifact, asked for, carries no role binding, so its admission did not run; 2: usage; 1: infrastructure" + "Check authenticated .ixe declarations with IxC (bound whole inputs use actual artifact admission; meta display by default). Strict: a safe axiom other than propext, Classical.choice and Quot.sound declines unless --allow-user-axioms. Exit 0: everything asked for passed; 3: a declaration failed or the artifact was refused; 4: every declaration passed but the whole artifact, asked for, carries no role binding, so its admission did not run; 5: CONDITIONAL, everything passed only with the listed user axioms admitted (--allow-user-axioms); 2: usage, including a selector naming no declaration and a host statement the run could not issue; 1: infrastructure" FLAGS: anon; "Anonymous keys and #hex display labels" workers : Nat; "Requested workers (>0); the tier-capable path reports effective 1" + "allow-user-axioms"; "Admit safe axioms beyond the standard three; the role binding must bind sorryAx, propext and Classical.choice. A whole run whose declarations hold any runs the uncertified conditional entry, lists the axioms, issues no host statement and exits 5; so does a selected run that reads one" "fail-out" : String; "Write failing selected labels in consts-file format" - "host-out" : String; "Write the checked whole-artifact host admission statement" + "host-out" : String; "Write the checked whole-artifact host admission statement (certified, unconditional runs only)" "host-store"; "Persist the checked host admission statement in the content-addressed store" consts : String; "Comma-separated selected names or anonymous addresses" "consts-file" : String; "Selected labels, one per line; # introduces comments" max : Nat; "Select the first N producer groups; all dependencies/prelude remain" - "clear-every" : Nat; "Compatibility option: only 0 is supported by IxC" - "no-verify"; "Unsupported: IxC requires byte authentication" + "per-const" : String; "Write a per-record attribution CSV (name, addr, consts, nanos, counters, cost) to this path; with --json and a selection, over every per-name run, one row per record" + json : String; "Write benchmark results rows to this path: the whole-run row keyed by --json-name, or with --consts/--consts-file one full-closure row per selected name; constants is the records checked or read" + "json-name" : String; "Key of the whole-run --json row (default: env); with a selection, the prefix / of every per-name row key" verbose; "Print one outcome per selected subject" ARGS: path : String; "Path to a serialized .ixe environment" diff --git a/Ix/Cli/CheckRsCmd.lean b/Ix/Cli/CheckRsCmd.lean deleted file mode 100644 index e89ef5ba0..000000000 --- a/Ix/Cli/CheckRsCmd.lean +++ /dev/null @@ -1,431 +0,0 @@ -/- - `ix check `: typecheck a serialized `.ixe` environment through - the Ix Rust kernel. Path is positional and required. - - Two kernel modes: - - - Default (Meta): kernel runs with metadata fields populated (Lean.Name, - binder info, mdata). Supports `--ns` / `--consts` / `--consts-file` - for seed filtering and `--fail-out` for bisect-loop workflows. Seeded - meta checks are SUBJECT-ONLY: each seed is checked with its deps - lazily ingressed but trusted, not re-checked. - - `--anon` (metadata-free): the env is loaded via `Env::get_anon` — - `named`/`names`/`comms` sections are discarded at load time, never - reaching the kernel. The kernel's typechecking logic structurally - cannot read metadata (`M::MField` is `()` in Anon mode); progress - labels are `@` addresses, not names. - - Without a filter, every kernel-checkable address is checked (whole - env). With `--consts` / `--consts-file`, the named constants are - checked together with their full dependency closures. Add - `--skip-deps` for a subject-only check (deps trusted). On a bound - artifact, both also check the closure of the binding's role records - and certificates: role admission assumes them, so a verdict on any - constant that reaches an admitted role rests on them. - `--ns` prefix filtering stays meta-only. - - Direct Lean → kernel typechecking (compile-and-check from source) is - available via the `rsCheckConstsFFI` API for tests - (`Tests/Ix/Kernel/Tutorial.lean`, `Tests/Ix/Kernel/CheckEnv.lean`). - Use `ix compile ` first, then `ix check `. --/ -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 - -public section - -open System (FilePath) -open Ix.KernelCheck - -namespace Ix.Cli.CheckRsCmd - -/-- Combined seed selector: prefixes (`--ns`) ∪ exact names - (`--consts`, `--consts-file`). Meta-mode only. -/ -private structure SeedSpec where - prefixes : List Lean.Name := [] - exacts : List Lean.Name := [] - -private def SeedSpec.isEmpty (s : SeedSpec) : Bool := - s.prefixes.isEmpty && s.exacts.isEmpty - -/-- Build a `SeedSpec` from `--ns`, `--consts`, and `--consts-file`. -/ -private def resolveSeedSpec (p : Cli.Parsed) : IO (Option SeedSpec) := do - let nsFlag := p.flag? "ns" - let constsFlag := p.flag? "consts" - let fileFlag := p.flag? "consts-file" - if nsFlag.isNone && constsFlag.isNone && fileFlag.isNone then - return none - let mut prefixes : List Lean.Name := [] - let mut exacts : List Lean.Name := [] - if let some flag := nsFlag then - let raw := flag.as! String - prefixes := parsePrefixes raw - if prefixes.isEmpty then - IO.println s!"[check] warning: --ns '{raw}' parsed to empty list" - if let some flag := constsFlag then - let raw := flag.as! String - let parsed := parsePrefixes raw - if parsed.isEmpty then - IO.println s!"[check] warning: --consts '{raw}' parsed to empty list" - exacts := exacts ++ parsed - if let some flag := fileFlag then - let path := flag.as! String - -- Shared grammar (Ix.Cli.ConstsFile); meta seeds resolve via `toName`. - let parsed := (← ConstsFile.read path).toList.map (·.toName) - if parsed.isEmpty then - IO.println s!"[check] warning: --consts-file '{path}' yielded zero names" - else - IO.println s!"[check] --consts-file '{path}': read {parsed.length} name(s)" - exacts := exacts ++ parsed - let spec : SeedSpec := { prefixes, exacts } - if spec.isEmpty then - IO.println "[check] warning: filter flags supplied but parsed to empty selection; checking full env" - return none - return some spec - -/-- Filter an `.ixe`'s checkable names down to the seed spec. -/ -private def selectNamesIxon (allNames : Array Lean.Name) - (spec : Option SeedSpec) : IO (Array Lean.Name) := do - match spec with - | none => pure allNames - | some s => - let exactSet : Std.HashSet Lean.Name := - s.exacts.foldl (fun acc n => acc.insert n) (Std.HashSet.emptyWithCapacity s.exacts.length) - -- O(|allNames|) build-up to O(1)-per-check; the previous - -- `allNames.contains n` was O(|allNames|) per missing-name check, - -- which at mathlib scale (~700k env × thousands of seed names) - -- could spend seconds on the preflight alone. - let allNamesSet : Std.HashSet Lean.Name := - allNames.foldl (fun acc n => acc.insert n) (Std.HashSet.emptyWithCapacity allNames.size) - let mut missing : Array Lean.Name := #[] - for n in s.exacts do - if !allNamesSet.contains n then - missing := missing.push n - if !missing.isEmpty then - IO.println s!"[check] warning: {missing.size}/{s.exacts.length} exact name(s) not in env:" - let shown := min 20 missing.size - for n in missing[:shown] do - IO.println s!" - {n}" - if missing.size > 20 then - IO.println s!" … ({missing.size - 20} more not shown)" - let seeds := allNames.filter fun n => - exactSet.contains n || s.prefixes.any (·.isPrefixOf n) - 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 - if failures.isEmpty then return - IO.println s!"[check] {failures.size} failure(s):" - let shown := min limit failures.size - for (label, msg) in failures[:shown] do - IO.println s!" ✗ {label}: {msg}" - if failures.size > limit then - IO.println s!" … ({failures.size - limit} more failures suppressed)" - -/-- Anon-mode runner: dispatch to `rsCheckAnonFFI`. The Rust side checks - every kernel-checkable address in the env and streams failures to - `failOutPath` (when nonempty). With `--json`, the run is additionally - recorded as one env-keyed results row (key from `--json-name`). -/ -private def runCheckAnon (envPath : String) (p : Cli.Parsed) : IO UInt32 := do - let verbose := p.flag? "verbose" |>.isSome - let failOutPath : String := - match p.flag? "fail-out" with - | 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 - match res with - | none => passed := passed + 1 - -- Label with the full content address (`#`) to match the - -- Rust-side progress / fail-out output. Pre-#48 we emitted - -- `#{i}` (result index), which made the CLI summary unjoinable - -- with the fail-out file's `#` entries. - | some err => failures := failures.push (s!"#{hex}", err.message) - - IO.println s!"[check] checked {results.size} constants in {elapsed.formatMs}" - IO.println s!"[check] {passed}/{results.size} passed" - reportFailures failures - - if !failOutPath.isEmpty then - IO.println s!"[check] streamed {failures.size} failure(s) to {failOutPath}" - - if let some flag := p.flag? "json" then - let key := (p.flag? "json-name").map (·.as! String) |>.getD "env" - let secs := elapsed.toFloat / 1000.0 - let tput := if elapsed > 0 - then results.size.toFloat * 1000.0 / elapsed.toFloat else 0.0 - let peakRss ← TracingTexray.peakTreeRssBytes - let status := if failures.isEmpty then "ok" else "rejected" - Ix.Benchmark.Results.writeRow (flag.as! String) key status - [ ("constants", Lean.toJson results.size) - , ("check-time", Ix.Benchmark.Results.jsonRound 3 secs) - , ("throughput", Ix.Benchmark.Results.jsonRound 2 tput) - , ("peak-rss", Lean.toJson peakRss) ] - - return if failures.isEmpty then 0 else Ix.Benchmark.Results.exitRejected - -/-- Anon-mode per-constant runner: dispatch to `rsCheckAnonConstsFFI`. Checks - the named constants and (by default) their full dependency closures — the - zkVM hosts' semantics — or subject-only under `--skip-deps`. Either way - the check set includes the closure of a bound artifact's role records - and certificates. With `--json`, each name runs as its own closure check - and records its own per-name row (see `Ix.Benchmark.Results`); without - it, the names union into one check set. -/ -private def runCheckAnonConsts (envPath : String) (p : Cli.Parsed) : IO UInt32 := do - let verbose := p.flag? "verbose" |>.isSome - let skipDeps := p.hasFlag "skip-deps" - let failOutPath : String := - match p.flag? "fail-out" with - | some flag => flag.as! String - | none => "" - let jsonPath : String := - match p.flag? "json" with - | some flag => flag.as! String - | none => "" - if !jsonPath.isEmpty && !failOutPath.isEmpty then - p.printError "error: --json per-name rows and --fail-out cannot be combined" - return Ix.Benchmark.Results.exitUsage - -- Raw strings (no toName round-trip): the FFI resolves displayed forms - -- against the env's `named` map, matching the zkVM hosts' resolution. - let names ← ConstsFile.gather p - if names.isEmpty then - IO.println "[check] error: --consts/--consts-file resolved to zero names" - return Ix.Benchmark.Results.exitUsage - - let scope := if skipDeps then "subject-only" else "full-closure" - let shape := if jsonPath.isEmpty then "union" else "per-name rows" - IO.println s!"Running Ix kernel check (anon mode, {scope}, {shape}) on {envPath}" - IO.println s!"[check] {names.size} seed constant(s): {", ".intercalate names.toList}" - let start ← IO.monoMsNow - let results ← rsCheckAnonConstsFFI envPath names skipDeps (!verbose) - failOutPath jsonPath - let elapsed := (← IO.monoMsNow) - start - - let mut passed := 0 - let mut failures : Array (String × String) := #[] - for (hex, res) in results do - match res with - | none => passed := passed + 1 - | some err => failures := failures.push (s!"#{hex}", err.message) - - IO.println s!"[check] checked {results.size} constants in {elapsed.formatMs}" - IO.println s!"[check] {passed}/{results.size} passed" - reportFailures failures - - if !failOutPath.isEmpty then - IO.println s!"[check] streamed {failures.size} failure(s) to {failOutPath}" - - return if failures.isEmpty then 0 else Ix.Benchmark.Results.exitRejected - -/-- Meta-mode runner: dispatch to `rsCheckIxonFFI` with seed filtering. -/ -private def runCheckMeta (envPath : String) (p : Cli.Parsed) : IO UInt32 := do - let verbose := p.flag? "verbose" |>.isSome - IO.println s!"Running Ix kernel check (meta mode) on {envPath}" - let spec ← resolveSeedSpec p - - let seedNames ← - match spec with - | some s => - if s.prefixes.isEmpty && !s.exacts.isEmpty then - IO.println s!"[check] exact-only filter: {s.exacts.length} name(s); skipping full env name preflight" - pure s.exacts.toArray - else - let namesInEnv ← rsIxonNamesFFI envPath - IO.println s!"Total checkable names in env: {namesInEnv.size}" - selectNamesIxon namesInEnv spec - | none => - let namesInEnv ← rsIxonNamesFFI envPath - IO.println s!"Total checkable names in env: {namesInEnv.size}" - pure namesInEnv - if spec.isSome && seedNames.isEmpty then - IO.println "[check] error: filter resolved to zero constants; refusing to run full-env check" - return 1 - IO.println s!"[check] checking {seedNames.size} seed constant(s)" - - let expectPass : Array Bool := Array.replicate seedNames.size true - let failOutPath : String := - match p.flag? "fail-out" with - | some flag => flag.as! String - | none => "" - let start ← IO.monoMsNow - let results ← rsCheckIxonFFI envPath seedNames expectPass (!verbose) failOutPath - let elapsed := (← IO.monoMsNow) - start - - let mut passed := 0 - let mut failures : Array (String × String) := #[] - for i in [:seedNames.size] do - match results[i]! with - | none => passed := passed + 1 - | some err => failures := failures.push (toString seedNames[i]!, err.message) - - IO.println s!"[check] checked {seedNames.size} constants in {elapsed.formatMs}" - IO.println s!"[check] {passed}/{seedNames.size} passed" - reportFailures failures - - if !failOutPath.isEmpty then - IO.println s!"[check] streamed {failures.size} failure(s) to {failOutPath}" - - return if failures.isEmpty then 0 else Ix.Benchmark.Results.exitRejected - -def runCheckRsCmd (p : Cli.Parsed) : IO UInt32 := do - let some pathArg := p.positionalArg? "path" - | p.printError "error: must specify to a .ixe file" - return 1 - let envPath := pathArg.as! String - - -- Start the process-tree RSS sampler so `--json` rows can report an - -- accurate peak-rss (the parallel kernel check's high-water mark). - TracingTexray.startSampler - - -- `--workers N` is plumbed through the existing - -- `IX_KERNEL_CHECK_WORKERS` env var that `resolve_kernel_check_workers` - -- (`src/ffi/kernel.rs`) reads. Setting `1` forces a single-threaded - -- runner, useful for isolating per-worker memory usage and timing. - if let some flag := p.flag? "workers" then - let n := flag.as! Nat - if n == 0 then - p.printError "error: --workers must be > 0" - return 1 - Std.Internal.UV.System.osSetenv "IX_KERNEL_CHECK_WORKERS" (toString n) - - let anon := p.flag? "anon" |>.isSome - let hasConsts := (p.flag? "consts").isSome || (p.flag? "consts-file").isSome - if p.hasFlag "skip-deps" && !(anon && hasConsts) then - p.printError "error: --skip-deps only applies to `--anon --consts/--consts-file` \ - (meta-mode seeded checks are always subject-only)" - return 1 - if anon then - if p.flag? "ns" |>.isSome then - p.printError "error: --ns prefix filtering is meta-only; --anon supports --consts/--consts-file" - return 1 - if hasConsts then - runCheckAnonConsts envPath p - else - runCheckAnon envPath p - else - runCheckMeta envPath p - -end Ix.Cli.CheckRsCmd - -open Ix.Cli.CheckRsCmd in -def checkRsCmd : Cli.Cmd := `[Cli| - "check-rs" VIA runCheckRsCmd; - "Typecheck a `.ixe` through the Rust kernel. Exits 0 when everything passes, 3 when the kernel rejects any constant (with --json, the rejected rows are on disk), nonzero otherwise on infrastructure failures." - - FLAGS: - anon; "Run the kernel in anon mode (no metadata read from .ixe)" - ns : String; "Comma-separated Lean.Name prefixes to filter on (meta mode only)" - consts : String; "Comma-separated EXACT constant names. Meta mode: subject-only seed check. Anon mode: full-closure check of each name (the zkVM hosts' semantics; --skip-deps for subject-only)." - "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 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, estimated 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." - verbose; "Log every constant on its own line (default: quiet)" - - ARGS: - path : String; "Path to a serialized .ixe environment" -] - -end diff --git a/Ix/Cli/CodegenCmd.lean b/Ix/Cli/CodegenCmd.lean index cb4995c3c..2146b1338 100644 --- a/Ix/Cli/CodegenCmd.lean +++ b/Ix/Cli/CodegenCmd.lean @@ -127,14 +127,10 @@ def emitRoleSpecs (checkOnly : Bool) : IO Bool := do IO.println s!"[codegen] wrote {roleSpecPath}" return true -/-- Keep the Rust validator and the shared shape fixture derived from the +/-- Keep the shared role specification shape fixture derived from the same specification data used by the verified kernel and circuit renderer. -/ def emitRustRoleSpecs (checkOnly : Bool) : IO Bool := do - let source ← match Ix.RoleBinding.RustSpecGen.render with - | .ok source => pure source - | .error error => IO.eprintln s!"Rust role specifications: {error}"; return false let outputs := #[ - ("crates/kernel/src/role_admission/specs.rs", source), ("Tests/Fixtures/role-binding-v2/spec-shape.tsv", Ix.RoleBinding.RustSpecGen.shape)] let mut ok := true for (path, contents) in outputs do diff --git a/Ix/Cli/Common.lean b/Ix/Cli/Common.lean new file mode 100644 index 000000000..43353974e --- /dev/null +++ b/Ix/Cli/Common.lean @@ -0,0 +1,44 @@ +module +public import Ix.CompileM +public import Ix.Merkle + +/-! + Helpers shared by the compile commands (`ix compile`, `ix compile-lean`, + `ix compile-export`, `ix validate-lean`) and by the certified compile's + failure listing (`Ix.CompileM.recordedFailures`). +-/ + +public section + +namespace Ix.Cli + +/-- The process's resident set size, from `/proc/self/status` (`?` where it + cannot be read). -/ +def residentSetSize : IO String := do + try + let status ← IO.FS.readFile "/proc/self/status" + let rss := (status.splitOn "\n").findSome? fun l => + if l.startsWith "VmRSS" then (l.splitOn ":")[1]? else none + return (rss.getD "?").trimAscii.toString + catch _ => return "?" + +/-- Failures listed root causes first: a constant that failed only because a + dependency is missing is a cascade, listed after the ones that name the + cause (a refusal's message must be visible in the bounded listing). -/ +def rootCausesFirst {α : Type} (xs : List (α × String)) : List (α × String) := + let cascade (e : String) := e.startsWith "missingConstant" || e.startsWith "missing constant" + xs.filter (!cascade ·.2) ++ xs.filter (cascade ·.2) + +/-- The canonical Merkle root of `env`'s stored constants, formatted as a hex string. -/ +def rootOf (env : Ixon.Env) : String := + toString ((Ix.Merkle.merkleRootCanonical (env.consts.toArray.map (·.1))).getD Ix.Merkle.zeroAddress) + +/-- Initial `CompileEnvStatus` for `original` and `failures` prior to finalization and serialization. -/ +def statusOf (original : Ixon.Env) (failures : Std.HashMap Ix.Name String) : + Ix.CompileM.CompileEnvStatus := + let ungrounded := (failures.toArray.map fun (n, r) => (n.pretty, r)).qsort (fun a b => a.1 < b.1) + { root := rootOf original, ungrounded, nonCanonical := #[], bytes := 0, named := 0, uniqueAnon := 0 } + +end Ix.Cli + +end diff --git a/Ix/Cli/CompileCmd.lean b/Ix/Cli/CompileCmd.lean index 34e8d27d6..9f253299b 100644 --- a/Ix/Cli/CompileCmd.lean +++ b/Ix/Cli/CompileCmd.lean @@ -9,52 +9,22 @@ public import Ix.TracingTexray public import Ix.Benchmark.Results public import Ix.Cli.ConstsFile public import Ix.Cli.ValidateCmd -public import Ix.Cli.DriverOptions import Std.Internal.UV.System +/-! `ix compile`: compile a Lean source file to an Ixon `.ixe` artifact using +the certified Lean compiler (`Ix.Cert.compileReport`). -/ + public section open System (FilePath) open Ix.EnvScope +open Ix.Cli (rootCausesFirst rootOf statusOf) private def defaultOutPathFor (pathStr : String) : String := let path := FilePath.mk pathStr let stem := path.fileStem.getD (path.fileName.getD pathStr) stem.toLower ++ ".ixe" -/-- `--sharing-limits SPEC`: validate the override (the format of -`Ix.Sharing.Exact.Limits.withOverrides`, which also accepts the Rust limit -keys) and publish it through `IX_SHARING_LIMITS`, which both compilers read. -Returns the parse error, if any. -/ -def applySharingLimitsFlag (p : Cli.Parsed) : IO (Option String) := do - let some flag := p.flag? "sharing-limits" | return none - let spec := flag.as! String - match Ix.CompileM.compilerSharingLimits.withOverrides spec with - | .error e => return some s!"--sharing-limits: {e}" - | .ok _ => - Std.Internal.UV.System.osSetenv Ix.CompileM.sharingLimitsEnvVar spec - return none - -/-- The certified compiler runs at the default sharing limits: `Ix.Cert.compileReport`, -the function its correctness theorem is about, fixes them, and other limits can -change which constants compile. A valid override (`--sharing-limits`, or a nonempty -`IX_SHARING_LIMITS`) is therefore refused there, with the reason; the uncertified -compilers (`--streamed`, `--rust`) take one. An invalid override is reported by its -parser first. -/ -def certifiedLimitsRefusal (p : Cli.Parsed) : IO (Option String) := do - let inherited := (← IO.getEnv Ix.CompileM.sharingLimitsEnvVar).any (!·.trimAscii.isEmpty) - unless (p.flag? "sharing-limits").isSome || inherited do return none - return some "the certified compiler runs at the default sharing limits, which its \ -correctness theorem fixes; --sharing-limits and IX_SHARING_LIMITS apply only to the \ -uncertified compilers (--streamed, --rust)" - -/-- Failures listed root causes first: a constant that failed only because a - dependency is missing is a cascade, listed after the ones that name the - cause (a refusal's message must be visible in the bounded listing). -/ -private def rootCausesFirst {α : Type} (xs : List (α × String)) : List (α × String) := - let cascade (e : String) := e.startsWith "missingConstant" || e.startsWith "missing constant" - xs.filter (!cascade ·.2) ++ xs.filter (cascade ·.2) - /-- The report describes this invocation's published envelope. An abort never opens a possibly pre-existing output file. The supported headers are one byte; checked object/claim versions are independent of this value. -/ @@ -69,36 +39,12 @@ def Ix.Cli.publishedEnvelopeVersion (published : Bool) (path : FilePath) : IO (O def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do -- Keep the environment-variable interface for scripts, while giving the - -- CLI an ordinary discoverable switch. The Rust compiler and serializer - -- both consult IX_VERBOSE, so setting it once enables the whole pipeline. + -- CLI an ordinary discoverable switch. if p.hasFlag "verbose" then Std.Internal.UV.System.osSetenv "IX_VERBOSE" "1" - if let some e ← applySharingLimitsFlag p then - p.printError s!"error: {e}" - return 1 - -- The certified compiler (`Ix.Cert.compileReport`) is the default, at the - -- default sharing limits; `--rust` selects the uncertified Rust compiler and - -- `--streamed` the uncertified streamed pipeline. The streamed pipeline's - -- options: the sharing limits (`--sharing-limits` / `IX_SHARING_LIMITS`) and, - -- with `--verbose`, its phase timings. - let useRust := p.hasFlag "rust" - let streamed := p.hasFlag "streamed" - if useRust && streamed then - p.printError "error: --rust and --streamed select different compilers" - return 1 let workers := match p.flag? "workers" with | some f => max 1 (f.as! Nat) | none => 32 - let opts ← if useRust then pure {} else - match ← Ix.Cli.driverOptionsFromEnv (dbg := p.hasFlag "verbose") with - | .ok opts => pure opts - | .error e => - p.printError s!"error: {e}" - return 1 - unless useRust || streamed do - if let some e ← certifiedLimitsRefusal p then - p.printError s!"error: {e}" - return 1 let some path := p.positionalArg? "path" | p.printError "error: must specify to a Lean source file" @@ -158,14 +104,17 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do if !constsSeeds.isEmpty && (p.flag? "module").isSome then p.printError "error: --consts/--consts-file and --module are mutually exclusive" return 1 - let constList ← + -- With each list, whether it is the whole import environment (the default + -- scope, as `compile-lean` without `--local`), whose compile publishes every + -- role: that compile prefetches every role's certificates. + let (constList, wholeEnv) ← if p.hasFlag "local" then do if !constsSeeds.isEmpty || (p.flag? "module").isSome || !excludeSet.isEmpty then p.printError "error: --local excludes --consts/--consts-file, --module and --exclude" return 1 let closed := localConstList fe IO.println s!"[compile] local: {closed.length} constants (the file's own and their dependencies)" - pure closed + pure (closed, false) else if !constsSeeds.isEmpty then do let mut seeds : List Lean.Name := [] let mut missing : List String := [] @@ -190,10 +139,12 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do IO.println s!"[compile] consts: {seeds.length} seed constant(s)" let closed := collectSelectedDeps leanEnv seeds IO.println s!"[compile] consts: {closed.length} constants after transitive-dep closure" - pure closed + pure (closed, false) else match p.flag? "module" with | none => - if excludeSet.isEmpty then defaultConstList fe pathStr + if excludeSet.isEmpty then + let whole ← defaultConstList fe pathStr + pure (whole, true) else -- Exclusion applies on top of the default scope (full env for classic -- files, module-visible closure for module-mode files). @@ -201,13 +152,13 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do let seeds := base.filterMap fun (n, _) => if excludeSet.contains n then none else some n IO.println s!"[compile] exclude applied: {seeds.length} seed constants" - pure (collectSelectedDeps leanEnv seeds) + pure (collectSelectedDeps leanEnv seeds, false) | some flag => let raw := flag.as! String let prefixes := parsePrefixes raw if prefixes.isEmpty then IO.println s!"[compile] warning: --module '{raw}' parsed to empty list; compiling full env" - pure leanEnv.constants.toList + pure (leanEnv.constants.toList, true) else let moduleNames := leanEnv.allImportedModuleNames let seeds := leanEnv.constants.toList.filterMap fun (n, _) => @@ -220,7 +171,7 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do IO.println s!"[compile] filter: {prefixes.length} module-prefix(es), {seeds.length} seed constants" let closed := collectSelectedDeps leanEnv seeds IO.println s!"[compile] filter: {closed.length} constants after transitive-dep closure" - pure closed + pure (closed, false) let totalConsts := constList.length println! "Total constants: {totalConsts}" @@ -238,67 +189,31 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do let allowPartial := p.hasFlag "allow-partial" let strictAnon := p.hasFlag "anon" let start ← IO.monoMsNow - -- The requested constants, whose publications and failures `finalize` keeps. - let requested := Ix.Compile.CompileInput.plain constList - let rootOf (env : Ixon.Env) : String := - toString ((Ix.Merkle.merkleRootCanonical (env.consts.toArray.map (·.1))).getD Ix.Merkle.zeroAddress) - let statusOf (original : Ixon.Env) (failures : Std.HashMap Ix.Name String) : - Ix.CompileM.CompileEnvStatus := - let ungrounded := (failures.toArray.map fun (n, r) => (n.pretty, r)).qsort (fun a b => a.1 < b.1) - { root := rootOf original, ungrounded, nonCanonical := #[], bytes := 0, named := 0, uniqueAnon := 0 } - -- Finalize (support, certificates, binding; the finalized artifact is checked - -- again), strip names under `--anon`, serialize, publish atomically. - let publish (backend : Ix.RoleBinding.Compile.Backend) (original : Ixon.Env) - (status : Ix.CompileM.CompileEnvStatus) (postStep : Ixon.Env → Except String Unit) : - IO Ix.CompileM.CompileEnvStatus := do - let artifact ← Ix.RoleBinding.Compile.finalize backend leanEnv - Ix.RoleBinding.Compile.nativeCertSource requested original Ix.Resource.typingLimits - IO.ofExcept (postStep artifact) - let artifact := if strictAnon then { artifact with - named := {}, names := {}, comms := {}, addrToName := {} } else artifact - let bytes ← IO.ofExcept (Ixon.serEnv artifact) - Ix.RoleBinding.Compile.writeAtomic (FilePath.mk outPath) bytes - return { status with - bytes := bytes.size.toUInt64 - named := artifact.named.size.toUInt64 - uniqueAnon := artifact.consts.size.toUInt64 - root := rootOf artifact } let verbose := (← IO.getEnv "IX_VERBOSE").isSome || (← IO.getEnv "IX_COMPILE_DBG").isSome - let status ← if useRust then - let input ← IO.ofExcept ((Ix.Compile.compileInputFromEnv leanEnv constList).mapError toString) - let constList ← IO.ofExcept input.prepare - if verbose then - IO.eprintln s!"[compile] source contract preparation: {((← IO.monoMsNow) - start).formatMs}" - IO.FS.withTempDir fun dir => do - let rawPath := dir / "original.ixe" - let status ← Ix.CompileM.rsCompileEnvBytesFFI constList rawPath.toString allowPartial - if !allowPartial && !status.ungrounded.isEmpty then return status - let original ← IO.ofExcept (Ixon.deEnv (← IO.FS.readBinFile rawPath)) - publish Ix.RoleBinding.Compile.rustBackend original status fun _ => pure () - else if streamed then - -- The streamed pipeline with its post-steps (`compileLeanInputValidated`): - -- uncertified, kept for comparison. - let input ← IO.ofExcept ((Ix.Compile.compileInputFromEnv leanEnv constList).mapError toString) - let out ← match ← Ix.CompileM.compileLeanInputValidated input (numWorkers := workers) (opts := opts) with - | .ok out => pure out - | .error e => throw (IO.userError s!"compile refused: {e}") - let status := statusOf out.env out.cenv.ungrounded - if !allowPartial && !status.ungrounded.isEmpty then pure status - else - publish (Ix.RoleBinding.Compile.leanBackend workers opts) out.env status fun artifact => - Ix.CompileM.resourcePostStep { out with env := artifact } - else - -- The certified compiler (`Ix.CompileM.compileCertified`: `Ix.Cert.compileReport` - -- on the canonical input), as `ix compile-lean` runs it. - let log (s : String) : IO Unit := if verbose then IO.eprintln s!"[compile] {s}" else pure () - let out ← match ← Ix.CompileM.compileCertified leanEnv constList workers log with - | .ok out => pure out - | .error e => throw (IO.userError s!"compile refused: {e}") - let status := statusOf out.env out.failures.toMap - if !allowPartial && !status.ungrounded.isEmpty then pure status - else - publish (Ix.RoleBinding.Compile.certifiedBackend workers) out.env status - (Ix.CompileM.validateFinalized out.annotated) + let log (s : String) : IO Unit := if verbose then IO.eprintln s!"[compile] {s}" else pure () + -- The certified compile, finalized as `ix compile-lean` finalizes it + -- (`Ix.CompileM.compileAndFinalize`: `Ix.Cert.compileReport` on the canonical + -- input, then the support, certificates and binding, the finalized artifact + -- checked again), with the names stripped under `--anon`, the certificates + -- prefetched for a whole-environment compile (`wholeEnv`); published + -- atomically below. + let strip (artifact : Ixon.Env) : Ixon.Env := if strictAnon then { artifact with + named := {}, names := {}, comms := {}, addrToName := {} } else artifact + let (out, finalization) ← match ← Ix.CompileM.compileAndFinalize leanEnv constList workers + allowPartial log (strip := strip) (prefetch := wholeEnv) with + | .ok r => pure r + | .error e => throw (IO.userError s!"compile refused: {e}") + let status := statusOf out.env out.failures.toMap + let status ← match finalization with + | .refused => pure status + | .failed e => throw (IO.userError e) + | .done artifact bytes => + Ix.RoleBinding.Compile.writeAtomic (FilePath.mk outPath) bytes + pure { status with + bytes := bytes.size.toUInt64 + named := artifact.named.size.toUInt64 + uniqueAnon := artifact.consts.size.toUInt64 + root := rootOf artifact } let size := status.bytes.toNat let elapsed := (← IO.monoMsNow) - start @@ -378,13 +293,11 @@ serialized the grounded subset ({status.named} named, \ def compileCmd : Cli.Cmd := `[Cli| compile VIA runCompileCmd; - "Compile a Lean file to Ixon with the certified compiler (`Ix.Cert.compileReport`, as `ix compile-lean`); `--rust` selects the uncertified Rust compiler, `--streamed` the uncertified streamed pipeline" + "Compile a Lean file to Ixon with the certified compiler (`Ix.Cert.compileReport`, as `ix compile-lean`)" FLAGS: v, verbose; "Print compiler phase timings, scheduler progress, and serialization progress. Equivalent to IX_VERBOSE=1." - rust; "Compile with the uncertified Rust compiler instead of Ix.Cert.compileReport. Shared finalization and artifact checks still run before writing the output. This backend is outside CompileCorrect; `ix compile-lean --rust-check` provides byte-comparison evidence for a selected input." - streamed; "Compile with the streamed Lean pipeline instead of the certified compiler. UNCERTIFIED: only its fold is proved to be the certified compiler's; its streamed front end is tested, not proved. Kept for comparison." - workers : Nat; "Worker count for the certified compiler's parallel phases (default 32). Ignored with --rust." + workers : Nat; "Worker count for the certified compiler's parallel phases (default 32)." "no-build"; "Skip the Lake build/cache step and reuse existing import artifacts. The input file is still elaborated. The caller must ensure all imports are built and up to date." out : String; "Output path for serialized Ixon.Env bytes; defaults to the lowercased input file stem with `.ixe` (e.g. CompileMathlib.lean -> compilemathlib.ixe)" consts : String; "Comma-separated EXACT constant names to compile (transitive deps pulled in automatically) instead of the whole import env — e.g. `Nat.add_comm`. Same flag/shape as `ix check --consts`. Mutually exclusive with --module; --exclude does not apply." @@ -397,7 +310,6 @@ def compileCmd : Cli.Cmd := `[Cli| "json-name" : String; "Row key for the --json row (default: the input file's stem, e.g. `CompileInitStd`)" "allow-partial" ; "Allow writing the successfully compiled subset despite requested compilation failures. Without this flag, those failures cause a nonzero exit and leave the output path untouched." anon ; "Strict-anonymous output: clear §4 names, §5 metadata, and §6 commitments (after hint finalization, so §3 anon hints survive). The env root is unchanged — it covers §2 only. Default keeps names: they are harmless to catalog semantics and valuable for fidelity/debugging." - "sharing-limits" : String; "Override resource limits of the canonical sharing construction: comma-separated key=value items (values: digits, 2^k or max) or `unbounded`. Keys: expr_visits, depth, nodes, states, transitions, cost_evals, output_bytes, materialize, materialize_work, knapsack_cells (Lean); input_nodes, distinct_nodes, height, candidates, states, layer_states, transitions, work, output_bytes, knapsack_cells (Rust). The defaults are far above every corpus maximum; a constant that reaches one fails to compile and names the limit. Only with --rust or --streamed: the certified compiler's theorem fixes the default limits. Sets IX_SHARING_LIMITS." report : String; "Write a machine-readable JSON compile report (versions, seed spec, requested/named/unique-anon/ungrounded counts, full ungrounded list, canonical consts merkle root, bytes, elapsed ms) to this path — written on success, fail-closed abort, and partial publish alike." ARGS: diff --git a/Ix/Cli/CompileExportCmd.lean b/Ix/Cli/CompileExportCmd.lean index 0eaa8d646..98620d3d8 100644 --- a/Ix/Cli/CompileExportCmd.lean +++ b/Ix/Cli/CompileExportCmd.lean @@ -1,4 +1,14 @@ -/- +module +public import Cli +public import Ix.Common +public import Ix.CompileM +public import Ix.CompileDriver +public import Ix.CompileCertified +public import Ix.RoleBinding.Compile +public import Ix.LeanExport +public import Ix.LeanExport.Compare + +/-! `ix compile-export [--out out.ixe]`: compile a Lean environment exported by `lean4export` (NDJSON format 3.1.0) to a bound Ixon `.ixe`, on ix's own toolchain. @@ -6,12 +16,19 @@ This is how a library elaborated by another Lean toolchain, older or newer than ix's, is compiled without building ix on that toolchain: its `lean4export` writes the declarations, and this command reads them - (`Ix.LeanExport`) and runs the same pipeline as `ix compile-lean`: - `compileLeanInputValidated`, then the role binding (`finalize`), whose - support and certificate theorems come from the export itself, so from that - toolchain's `Init` and its `IxC.Kernel.PinGen.Certs`. With - `--certificates` they come from a second export of the same toolchain - instead, for an export that does not carry them. + (`Ix.LeanExport`) and runs the same pipeline as `ix compile-lean` + (`Ix.CompileM.compileAndFinalize`): the certified compile, then the role + binding (`finalize`), whose support and certificate theorems come from the + export itself, so from that toolchain's `Init` and its + `IxC.Kernel.PinGen.Certs`. With `--certificates` the certificate theorems + come from a second export of the same toolchain instead, for an export that + does not carry them. + + The roles are bound where that toolchain's declarations put them, which can + differ from ix's default role table: 4.35.0's `Decidable` has one + constructor, so its `Int.decLt` sits elsewhere. So the compile does not check + the published role names against the default table (`defaultRoles := false`); + `finalize` checks them against the binding it generates from the export. The output matches the native compile of the same declarations in content (every constant, address and the role binding), not in metadata: an export @@ -22,22 +39,13 @@ reports the metadata differences. Exit codes: 0 success (and content exact, when compared); 1 a refused - export, a pipeline error, a block failure, a Rust divergence or a content - difference; 2 usage. + export, a pipeline error, a block failure, or a content difference; 2 usage. -/ -module -public import Cli -public import Ix.Common -public import Ix.CompileM -public import Ix.CompileDriver -public import Ix.CompileResource -public import Ix.RoleBinding.Compile -public import Ix.LeanExport -public import Ix.LeanExport.Compare -public import Ix.Cli.DriverOptions + public section open System (FilePath) +open Ix.Cli (rootCausesFirst) namespace Ix.Cli.CompileExportCmd @@ -61,11 +69,6 @@ def runCompileExportCmd (p : Cli.Parsed) : IO UInt32 := do let workers := match p.flag? "workers" with | some f => max 1 (f.as! Nat) | none => 32 - let opts ← match ← Ix.Cli.driverOptionsFromEnv (dbg := true) with - | .ok opts => pure opts - | .error e => - p.printError s!"error: {e}" - return 2 let t0 ← IO.monoMsNow let exported ← readExport "export" path let env ← Ix.LeanExport.environment exported.constants @@ -81,44 +84,26 @@ def runCompileExportCmd (p : Cli.Parsed) : IO UInt32 := do return 1 let certEnv ← Ix.LeanExport.environment certs.constants pure (pure certEnv) - let input ← IO.ofExcept ((Ix.Compile.compileInputFromEnv env env.constants.toList).mapError toString) - let out ← match ← Ix.CompileM.compileLeanInputValidated input (numWorkers := workers) (opts := opts) with + let constList := env.constants.toList + let (out, artifact, outBytes) ← match ← Ix.CompileM.compileAndFinalize env constList workers + (allowPartial := false) (certSource := certSource) (defaultRoles := false) with | .error e => IO.println s!"[compile-export] FAILED: {e}" return 1 - | .ok out => pure out - let failed := out.cenv.ungrounded.size - if failed > 0 then - IO.eprintln s!"[compile-export] error: {failed} constant(s) failed to compile; nothing written to {outPath}" - for (n, e) in out.cenv.ungrounded.toList.take 8 do - IO.eprintln s!" [ungrounded] {n.pretty}: {(e.replace "\n" " ").take 200}" - return 1 - let artifact ← Ix.RoleBinding.Compile.finalize (Ix.RoleBinding.Compile.leanBackend workers opts) - env certSource input out.env Ix.Resource.typingLimits - let out := { out with env := artifact, bytes := ← IO.ofExcept (Ixon.serEnv artifact) } - IO.ofExcept (Ix.CompileM.resourcePostStep out) - Ix.RoleBinding.Compile.writeAtomic (FilePath.mk outPath) out.bytes + | .ok (out, .refused) => + let failures := out.failures.toMap + IO.eprintln s!"[compile-export] error: {failures.size} constant(s) failed to compile; nothing written to {outPath}" + for (n, e) in (rootCausesFirst failures.toList).take 8 do + IO.eprintln s!" [ungrounded] {n.pretty}: {(e.replace "\n" " ").take 200}" + return 1 + | .ok (_, .failed e) => throw (IO.userError e) + | .ok (out, .done artifact bytes) => pure (out, artifact, bytes) + Ix.RoleBinding.Compile.writeAtomic (FilePath.mk outPath) outBytes let binding := match artifact.roleBinding with | some b => s!"role binding {b.address}" | none => "no role binding" - IO.println s!"[compile-export] wrote {out.bytes.size} bytes to {outPath} ({out.blockCount} blocks, \ + IO.println s!"[compile-export] wrote {outBytes.size} bytes to {outPath} ({out.inputs} input constants, \ {binding}) in {(← IO.monoMsNow) - t0}ms" - if p.hasFlag "rust-check" then - let tR ← IO.monoMsNow - let rustBytes ← IO.FS.withTempDir fun dir => do - let rustOut := dir / "rust-check.ixe" - let constants ← IO.ofExcept input.prepare - let _ ← Ix.CompileM.rsCompileEnvBytesFFI constants rustOut.toString true - let raw ← IO.ofExcept (Ixon.deEnv (← IO.FS.readBinFile rustOut)) - let bound ← Ix.RoleBinding.Compile.finalize Ix.RoleBinding.Compile.rustBackend env certSource - input raw Ix.Resource.typingLimits - IO.ofExcept (Ixon.serEnv bound) - if rustBytes == out.bytes then - IO.println s!"[compile-export] ALIGNED: {out.bytes.size} bytes byte-identical with Rust \ -({(← IO.monoMsNow) - tR}ms)" - else - IO.println s!"[compile-export] DIVERGED: lean {out.bytes.size}B vs rust {rustBytes.size}B" - return 1 if let some f := p.flag? "compare" then let nativePath := f.as! String let native ← IO.ofExcept (Ixon.deEnv (← IO.FS.readBinFile nativePath)) @@ -139,7 +124,6 @@ def compileExportCmd : Cli.Cmd := `[Cli| out : String; "Output path; defaults to the lowercased input file stem with `.ixe`" workers : Nat; "Worker count for the parallel phases (default 32)" certificates : String; "An export of the same toolchain carrying the certificate theorems (`IxC.Kernel.PinGen.Certs`), when the main export does not" - "rust-check" ; "Also compile through the Rust compiler and byte-compare the outputs; exit 1 on divergence" compare : String; "A native compile of the same declarations: check that the content is equal (exit 1 if not) and report the metadata differences" ARGS: diff --git a/Ix/Cli/CompileLeanCmd.lean b/Ix/Cli/CompileLeanCmd.lean index 23220ba3e..de2245f7e 100644 --- a/Ix/Cli/CompileLeanCmd.lean +++ b/Ix/Cli/CompileLeanCmd.lean @@ -1,131 +1,72 @@ -/- +module +public import Cli +public import Ix.Common +public import Ix.Meta +public import Ix.CompileM +public import Ix.CompileDriver +public import Ix.CompileCertified +public import Ix.RoleBinding.Compile +public import Ix.Cli.ValidateCmd + +/-! `ix compile-lean [--out out.ixe]`: compile a Lean file's environment to a serialized Ixon `.ixe` with the certified compiler, `Ix.Cert.compileReport`, the function `Ix.Cert.CompileCorrect` is about - (`Ix.CompileM.compileCertified`): the source contracts are prepared, the + (`Ix.CompileM.compileAndFinalize`): the source contracts are prepared, the input is canonicalized (`Ix.CanonM`), compiled and checked by `compileReport`, its accounting and resources are checked; the role support, evidence and binding are added (`Ix.RoleBinding.Compile.finalize`, which checks the finalized artifact again), and the artifact is serialized (`Ixon.serEnv`) and written atomically. - The certified compiler runs at the default sharing limits, which the theorem - fixes: `--sharing-limits` or `IX_SHARING_LIMITS` is refused, unless - `--streamed` is given. `--streamed` selects the streamed pipeline - (`Ix.CompileM.compileLeanInputValidated`), which is uncertified: its fold is - `compileReport`'s (`Ix.Cert.Hybrid`), but its streamed canonicalization, - graph, ground filter and condensation are only tested to be. It is kept for - comparison. - - `--rust-check` additionally compiles the same prepared constants through - the Rust FFI compiler (`rs_compile_env_bytes`). Lean decodes that raw - artifact, finalizes it with the Rust backend, then serializes it with - `Ixon.serEnv`. The ALIGNED gate compares these finalized bytes with the - selected Lean path's output. Rust's serializer participates in the raw - handoff; both final envelopes use Lean serialization. This is byte-parity - evidence for the selected input, not a universal refinement or - value-preservation proof. - - Exit codes: 0 success (including an explicitly allowed partial output, - and aligned when checked); 1 pipeline error, an unallowed compilation failure, - or divergence; 2 usage. The Lean output is written before `--rust-check`, - so a later divergence leaves that output on disk. + Exit codes: 0 success (including an explicitly allowed partial output); + 1 pipeline error or an unallowed compilation failure; 2 usage. -/ -module -public import Cli -public import Ix.Common -public import Ix.Meta -public import Ix.CompileM -public import Ix.CompileDriver -public import Ix.CompileResource -public import Ix.CompileCertified -public import Ix.RoleBinding.Compile -public import Ix.Cli.DriverOptions -public import Ix.Cli.ValidateCmd -public import Ix.Cli.CompileCmd + public section open System (FilePath) open Ix.EnvScope +open Ix.Cli (rootCausesFirst) namespace Ix.Cli.CompileLeanCmd -/-- Failures listed root causes first: a constant that failed only because a - dependency is missing is a cascade, listed after the ones that name the - cause (a refusal's message must be visible in the bounded listing). -/ -private def rootCausesFirst {α : Type} (xs : List (α × String)) : List (α × String) := - let cascade (e : String) := e.startsWith "missingConstant" || e.startsWith "missing constant" - xs.filter (!cascade ·.2) ++ xs.filter (cascade ·.2) - -/-- A compile's finalized, serialized artifact, the input `--rust-check` -compiles, and the summary line's counts. -/ +/-- A compile's finalized, serialized artifact and the summary line's counts. -/ private structure Compiled where bytes : ByteArray - prepared : Unit → Except String (List (Lean.Name × Lean.ConstantInfo)) failures : Std.HashMap Ix.Name String summary : String /-- Fail-closed by default, as `ix compile`: a recorded compilation failure causes a nonzero exit and leaves the output path untouched. `--allow-partial` permits writing the successfully compiled subset. -/ -private def refusePartial (allowPartial : Bool) (outPath : String) - (failures : Std.HashMap Ix.Name String) : IO Bool := do - if allowPartial || failures.isEmpty then return false +private def refusePartial (outPath : String) (failures : Std.HashMap Ix.Name String) : IO Unit := do IO.eprintln s!"[compile-lean] error: {failures.size} constant(s) \ failed to compile; nothing written to {outPath} (use --allow-partial to \ serialize the grounded subset)" for (n, e) in (rootCausesFirst failures.toList).take 8 do IO.eprintln s!" [ungrounded] {n.pretty}: {(e.replace "\n" " ").take 200}" - return true -/-- The certified compile (`Ix.CompileM.compileCertified`), finalized. -/ -private def compileCertified (fe : FileEnv) (constList : List (Lean.Name × Lean.ConstantInfo)) - (workers : Nat) (allowPartial : Bool) (outPath : String) : IO (Except UInt32 Compiled) := do - let log (s : String) : IO Unit := IO.println s!" [compile-lean] {s}" - let out ← match ← Ix.CompileM.compileCertified fe.env constList workers log with - | .ok out => pure out - | .error e => - IO.println s!"[compile-lean] FAILED: {e}" - return .error 1 - log s!"rss after the compile: {← Ix.Cli.residentSetSize}" - let failures := out.failures.toMap - if ← refusePartial allowPartial outPath failures then return .error 1 - let t0 ← IO.monoMsNow - let artifact ← Ix.RoleBinding.Compile.finalize (Ix.RoleBinding.Compile.certifiedBackend workers) - fe.env Ix.RoleBinding.Compile.nativeCertSource (.plain constList) out.env - Ix.Resource.typingLimits - IO.ofExcept (Ix.CompileM.validateFinalized out.annotated artifact) - let t1 ← IO.monoMsNow - log s!"finalize: {t1 - t0}ms, rss {← Ix.Cli.residentSetSize}" - let bytes ← IO.ofExcept (Ixon.serEnv artifact) - log s!"serialize ({bytes.size} bytes): {(← IO.monoMsNow) - t1}ms" - let prepared := out.prepared - return .ok { bytes, prepared := fun _ => .ok prepared, failures - summary := s!"{out.inputs} input constants, {failures.size} failures" } - -/-- The streamed pipeline (`Ix.CompileM.compileLeanInputValidated`), finalized: -uncertified, kept for comparison. -/ -private def compileStreamed (fe : FileEnv) (constList : List (Lean.Name × Lean.ConstantInfo)) - (workers : Nat) (opts : Ix.CompileM.DriverOptions) (allowPartial : Bool) (outPath : String) : +/-- The certified compile, finalized (`Ix.CompileM.compileAndFinalize`). A compile of +the whole import environment (not `--local`) publishes every role, so it +prefetches every role's certificates beside the source compile. -/ +private def compileFinalized (fe : FileEnv) (constList : List (Lean.Name × Lean.ConstantInfo)) + (workers : Nat) (allowPartial : Bool) (outPath : String) (wholeEnv : Bool) : IO (Except UInt32 Compiled) := do - let input ← IO.ofExcept ((Ix.Compile.compileInputFromEnv fe.env constList).mapError toString) - -- The compile, then the resource post-step (an input with semantic - -- contracts is emitted only if its resources validate). - let out ← match ← Ix.CompileM.compileLeanInputValidated input (numWorkers := workers) - (opts := opts) with - | .ok out => pure out - | .error e => - IO.println s!"[compile-lean] FAILED: {e}" - return .error 1 - let failures := out.cenv.ungrounded - if ← refusePartial allowPartial outPath failures then return .error 1 - let artifact ← Ix.RoleBinding.Compile.finalize (Ix.RoleBinding.Compile.leanBackend workers opts) - fe.env Ix.RoleBinding.Compile.nativeCertSource input out.env Ix.Resource.typingLimits - let out := { out with env := artifact, bytes := ← IO.ofExcept (Ixon.serEnv artifact) } - IO.ofExcept (Ix.CompileM.resourcePostStep out) - return .ok { bytes := out.bytes, prepared := fun _ => input.prepare, failures - summary := s!"{out.blockCount} blocks, {out.ungroundedCount} ungrounded, \ -{failures.size} block failures" } + let log (s : String) : IO Unit := IO.println s!" [compile-lean] {s}" + match ← Ix.CompileM.compileAndFinalize fe.env constList workers allowPartial log + (prefetch := wholeEnv) with + | .error e => + IO.println s!"[compile-lean] FAILED: {e}" + return .error 1 + | .ok (out, .refused) => + refusePartial outPath out.failures.toMap + return .error 1 + | .ok (_, .failed e) => throw (IO.userError e) + | .ok (out, .done _ bytes) => + let failures := out.failures.toMap + return .ok { bytes, failures + summary := s!"{out.inputs} input constants, {failures.size} failures" } def runCompileLeanCmd (p : Cli.Parsed) : IO UInt32 := do let args : Array String := p.variableArgsAs! String @@ -140,22 +81,7 @@ def runCompileLeanCmd (p : Cli.Parsed) : IO UInt32 := do let workers := match p.flag? "workers" with | some f => max 1 (f.as! Nat) | none => 32 - let streamed := p.hasFlag "streamed" let allowPartial := p.hasFlag "allow-partial" - if let some e ← applySharingLimitsFlag p then - p.printError s!"error: {e}" - return 2 - -- The streamed pipeline's options: limits (`--sharing-limits` / - -- `IX_SHARING_LIMITS`), traces (`IX_COMPILE_DBG`); phase timings always on. - let opts ← match ← Ix.Cli.driverOptionsFromEnv (dbg := true) with - | .ok opts => pure opts - | .error e => - p.printError s!"error: {e}" - return 2 - unless streamed do - if let some e ← certifiedLimitsRefusal p then - p.printError s!"error: {e}" - return 2 -- Reuse existing imports when the caller already built them: the input file -- is still elaborated; only Lake's dependency build and cache step is skipped. @@ -164,12 +90,10 @@ def runCompileLeanCmd (p : Cli.Parsed) : IO UInt32 := do buildFile pathStr let fe ← getFileEnvCore pathStr let constList ← if p.hasFlag "local" then pure (localConstList fe) else defaultConstList fe pathStr - IO.println s!"[compile-lean] {constList.length} constants, {workers} workers, \ -{if streamed then "streamed pipeline (uncertified)" else "certified compiler"}" + IO.println s!"[compile-lean] {constList.length} constants, {workers} workers, certified compiler" let t0 ← IO.monoMsNow - let compiled ← if streamed then compileStreamed fe constList workers opts allowPartial outPath - else compileCertified fe constList workers allowPartial outPath + let compiled ← compileFinalized fe constList workers allowPartial outPath (!p.hasFlag "local") let out ← match compiled with | .ok out => pure out | .error code => return code @@ -181,30 +105,6 @@ in {elapsed}ms" IO.println s!"[compile-lean] PARTIAL: {out.failures.size} constants failed to compile" for (n, e) in (rootCausesFirst out.failures.toList).take 8 do IO.println s!" {n.pretty}: {e.take 200}" - - if p.hasFlag "rust-check" then - IO.println "[compile-lean] --rust-check: compiling via Rust FFI..." - let tR ← IO.monoMsNow - let rustBytes ← IO.FS.withTempDir fun dir => do - let rustOut := dir / "rust-check.ixe" - let prepared ← IO.ofExcept (out.prepared ()) - let _ ← Ix.CompileM.rsCompileEnvBytesFFI prepared rustOut.toString true - let raw ← IO.ofExcept (Ixon.deEnv (← IO.FS.readBinFile rustOut)) - let bound ← Ix.RoleBinding.Compile.finalize Ix.RoleBinding.Compile.rustBackend fe.env - Ix.RoleBinding.Compile.nativeCertSource (.plain constList) raw Ix.Resource.typingLimits - IO.ofExcept (Ixon.serEnv bound) - let tRe := (← IO.monoMsNow) - tR - if rustBytes == out.bytes then - IO.println s!"[compile-lean] ALIGNED: {out.bytes.size} bytes byte-identical with Rust ({tRe}ms)" - else - let n := min rustBytes.size out.bytes.size - let mut firstDiff := n - for i in [0:n] do - if firstDiff == n && rustBytes.get! i != out.bytes.get! i then - firstDiff := i - IO.println s!"[compile-lean] DIVERGED: lean {out.bytes.size}B vs \ -rust {rustBytes.size}B, first difference at byte {firstDiff}" - return 1 return 0 end Ix.Cli.CompileLeanCmd @@ -218,11 +118,8 @@ def compileLeanCmd : Cli.Cmd := `[Cli| out : String; "Output path for the serialized Ixon.Env bytes; defaults to the lowercased input file stem with `.ixe`" workers : Nat; "Worker count for the parallel phases (default 32)" "no-build" ; "Skip the Lake build/cache step and reuse existing import artifacts. The input file is still elaborated. The caller must ensure all imports are built and up to date." - "rust-check" ; "Also compile via the Rust FFI compiler, finalize its artifact, and compare the final bytes (the ALIGNED gate; both final envelopes use Lean serialization). Exit 1 on divergence; the already-written Lean output remains." - "local" ; "Compile only the constants the input file itself declares, with their transitive dependencies (the selected closure: recursors, whole logical units, checker support), instead of the whole import env. --rust-check compiles the same list." + "local" ; "Compile only the constants the input file itself declares, with their transitive dependencies (the selected closure: recursors, whole logical units, checker support), instead of the whole import env." "allow-partial" ; "Allow writing the successfully compiled subset despite requested compilation failures. Without this flag, those failures cause a nonzero exit and leave the output path untouched." - streamed ; "Compile with the streamed pipeline instead of the certified compiler. UNCERTIFIED: only its fold is proved to be the certified compiler's; its streamed front end is tested, not proved. Kept for comparison." - "sharing-limits" : String; "Override resource limits of the canonical sharing construction (same format as `ix compile --sharing-limits`); only with --streamed, since the certified compiler's theorem fixes the default limits. Applies to --rust-check too. Sets IX_SHARING_LIMITS." ARGS: ...path : String; "Path to the Lean source file to compile." diff --git a/Ix/Cli/ConstsFile.lean b/Ix/Cli/ConstsFile.lean index eb8d1ee67..25ba59330 100644 --- a/Ix/Cli/ConstsFile.lean +++ b/Ix/Cli/ConstsFile.lean @@ -1,6 +1,9 @@ -/- +module +public import Cli + +/-! Shared parsing for constant-name inputs (`--consts` comma-lists and - `--consts-file` files) across every CLI that takes them: `ix check-rs`, + `--consts-file` files) across every CLI that takes them: `ix check-lean`, `ix compile --exclude-file`, and `bench-typecheck`. One grammar everywhere: one name per line, everything from a `#` to end of @@ -14,8 +17,6 @@ zkVM-style paths, where a `toName` round-trip could mangle numeric or private components). -/ -module -public import Cli public section diff --git a/Ix/Cli/DecompileCmd.lean b/Ix/Cli/DecompileCmd.lean index c6cf70149..80ff062f7 100644 --- a/Ix/Cli/DecompileCmd.lean +++ b/Ix/Cli/DecompileCmd.lean @@ -1,4 +1,11 @@ -/- +module +public import Cli +public import Ix.Common +public import Ix.DecompileDriver +public import Ix.TracingTexray +public import Ix.Benchmark.Results + +/-! `ix decompile `: decompile a serialized `.ixe` environment back to Lean constants — the inverse of `ix compile`. This is the decompile benchmark's measured tool (env-keyed row, mirroring `ix compile --json`). @@ -10,11 +17,6 @@ tests), which need the original Lean env a `.ixe` can't supply — so this performance tool does not reproduce them. -/ -module -public import Cli -public import Ix.Common -public import Ix.TracingTexray -public import Ix.Benchmark.Results public section @@ -22,11 +24,12 @@ open System (FilePath) namespace Ix.Cli.DecompileCmd -/-- Decompile a `.ixe` from disk, returning the decompiled constant - count. A malformed decompile throws (hard error). Implemented in - `crates/ffi/src/compile.rs::rs_decompile_env`. -/ -@[extern "rs_decompile_env"] -opaque rsDecompileEnvFFI : @& String → IO Nat +/-- Decompile a `.ixe` from disk in pure Lean (`Ixon.deEnvVerifiedLazy` + + `Ix.DecompileM.decompileEnvFullParallel`), returning the decompiled + `(Lean.Name × Lean.ConstantInfo)` array. A malformed decompile throws + (hard error). -/ +def decompileEnv (envPath : String) : IO (Array (Lean.Name × Lean.ConstantInfo)) := + Ix.DecompileM.decompileEnvConsts envPath def runDecompileCmd (p : Cli.Parsed) : IO UInt32 := do let some pathArg := p.positionalArg? "path" @@ -44,7 +47,8 @@ def runDecompileCmd (p : Cli.Parsed) : IO UInt32 := do IO.println s!"Decompiling {envPath}" let start ← IO.monoMsNow - let constants ← rsDecompileEnvFFI envPath + let decompiled ← decompileEnv envPath + let constants := decompiled.size let elapsed := (← IO.monoMsNow) - start IO.println s!"[decompile] {constants} constants in {elapsed.formatMs}" diff --git a/Ix/Cli/DriverOptions.lean b/Ix/Cli/DriverOptions.lean deleted file mode 100644 index 72cfafba7..000000000 --- a/Ix/Cli/DriverOptions.lean +++ /dev/null @@ -1,46 +0,0 @@ -module -public import Ix.CompileDriver - -public section - -/-! -# The Lean compile drivers' options, from the CLI's environment - -The driver (`Ix.CompileM.compileEnvFold`, and the pipeline above it) -reads no environment variable and no `/proc` file; it takes -`Ix.CompileM.DriverOptions`. `ix compile-lean` and `ix validate-lean` build -the options here: -- `IX_SHARING_LIMITS` (set by `--sharing-limits`, or directly): the sharing - construction's limits; -- `IX_COMPILE_DBG`: phase timings and the per-wave trace. - -Under `IX_COMPILE_DBG` the pipeline prints the process's resident set size -after the compile phase, read here. --/ - -namespace Ix.Cli - -/-- The process's resident set size, from `/proc/self/status` (`?` where it - cannot be read). -/ -def residentSetSize : IO String := do - try - let status ← IO.FS.readFile "/proc/self/status" - let rss := (status.splitOn "\n").findSome? fun l => - if l.startsWith "VmRSS" then (l.splitOn ":")[1]? else none - return (rss.getD "?").trimAscii.toString - catch _ => return "?" - -/-- The drivers' options from the environment; `dbg` forces the debug - traces on. An invalid `IX_SHARING_LIMITS` is an error naming it. -/ -def driverOptionsFromEnv (dbg : Bool := false) : - IO (Except String Ix.CompileM.DriverOptions) := do - match ← Ix.CompileM.compilerSharingLimitsFromEnv with - | .error e => return .error e - | .ok sharingLimits => - let dbg := dbg || (← IO.getEnv "IX_COMPILE_DBG").isSome - return .ok { sharingLimits, dbg - memory := if dbg then some residentSetSize else none } - -end Ix.Cli - -end diff --git a/Ix/Cli/IngressCmd.lean b/Ix/Cli/IngressCmd.lean deleted file mode 100644 index f98ea4bed..000000000 --- a/Ix/Cli/IngressCmd.lean +++ /dev/null @@ -1,85 +0,0 @@ -/- - `ix ingress --path `: run only the Lean → Ixon → KEnv ingress - pipeline against a Lean file's environment, stopping before the kernel - typecheck loop. Mirrors `ix check` (build the file, load its env, ship - to Rust) but pipes the env through `rs_kernel_ingress` instead of - `rs_kernel_check_consts`. - - Pipeline (Rust side, `crates/ffi/src/kernel.rs::rs_kernel_ingress`): - Lean env → compile_env → ixon_ingress → KEnv (stop) - - Use it like - `lake exe ix ingress --path Benchmarks/Compile/CompileMathlib.lean` - to time the ingress-only pipeline against a full Mathlib environment - without paying for the typecheck pass. Useful when profiling - `compile_env` / `ixon_ingress` regressions in isolation. - - Flags: - - `--path ` (required): file whose env should be ingressed. - - No `--ns` filter: ingress always processes the whole IxonEnv (the - filter on `ix check` only controls which constants we *assert* on; it - doesn't shrink the ingressed env, so it has no effect on this path). --/ -module -public import Cli -public import Ix.Common -public import Ix.CompileM -public import Ix.Meta -public import Ix.Cli.ValidateCmd - -public section - -open System (FilePath) -open Ix.EnvScope - -namespace Ix.Cli.IngressCmd - -/-- FFI: ingress a Lean environment through the compile + kernel-ingress - pipeline, stopping before typechecking. Returns the number of kernel - constants ingressed. - - Implemented in `crates/ffi/src/kernel.rs::rs_kernel_ingress`. The Rust side - prints `[rs_kernel_ingress] read env / compile / ingress` timing lines - to stderr, mirroring `rs_kernel_check_consts`. -/ -@[extern "rs_kernel_ingress"] -opaque rsKernelIngressFFI : - @& List (Lean.Name × Lean.ConstantInfo) → IO USize - -def runIngressCmd (p : Cli.Parsed) : IO UInt32 := do - let some path := p.positionalArg? "path" - | p.printError "error: must specify to a Lean source file" - return 1 - let pathStr := path.as! String - - -- `buildFile` also runs `lake exe cache get` if the target depends on - -- Mathlib, so a fresh checkout works without a prior `lake build`. - buildFile pathStr - let fe ← getFileEnvCore pathStr - let constList ← defaultConstList fe pathStr - - let totalConsts := constList.length - IO.println s!"Running Ix ingress on {pathStr}" - IO.println s!"Total constants in env: {totalConsts}" - - let start ← IO.monoMsNow - let kenvLen ← rsKernelIngressFFI constList - let elapsed := (← IO.monoMsNow) - start - - IO.println s!"[ingress] ingressed {kenvLen} kernel consts in {elapsed.formatMs}" - -- Machine-readable line: ` `. - IO.println s!"##ingress## {elapsed} {kenvLen} {totalConsts}" - return 0 - -end Ix.Cli.IngressCmd - -open Ix.Cli.IngressCmd in -def ingressCmd : Cli.Cmd := `[Cli| - ingress VIA runIngressCmd; - "Ingress a Lean file's env through the Ix kernel pipeline (compile + ingress only, no typecheck) for performance analysis" - - ARGS: - path : String; "Path to the Lean source file whose env should be ingressed." -] - -end diff --git a/Ix/Cli/ProfileCmd.lean b/Ix/Cli/ProfileCmd.lean deleted file mode 100644 index d42b428b6..000000000 --- a/Ix/Cli/ProfileCmd.lean +++ /dev/null @@ -1,71 +0,0 @@ -/- - `ix profile `: run the Ix kernel out of circuit over a serialized - `.ixe` environment, recording per-block heartbeats and the delta-unfold graph - into a `.ixprof` sidecar. This is the cost model consumed by `ix shard` - (see `plans/sharding.md`). - - Recording defaults to *cache-isolated* mode: the kernel clears its - cross-constant reduction-memo caches between constants so every delta-unfold - re-executes (sound recording) and recorded heartbeats reflect the in-circuit - (un-memoized) cost. `--keep-caches` trades fidelity for speed. --/ -module -public import Cli -public import Ix.Common -public import Ix.KernelCheck -public import Std.Internal.UV.System - -public section - -open Ix.KernelCheck - -namespace Ix.Cli.ProfileCmd - -def runProfileCmd (p : Cli.Parsed) : IO UInt32 := do - let some pathArg := p.positionalArg? "path" - | p.printError "error: must specify to a .ixe file" - return 1 - let envPath := pathArg.as! String - let outPath : String := - match p.flag? "out" with - | some flag => flag.as! String - -- Default sidecar mirrors the env's base name: `init.ixe` → `init.ixprof` - -- (not `init.ixe.ixprof`); a non-`.ixe` path just gets `.ixprof` appended. - | none => - let base := if envPath.endsWith ".ixe" then (envPath.dropEnd 4).toString else envPath - base ++ ".ixprof" - let isolate := !(p.flag? "keep-caches" |>.isSome) - let quiet := !(p.flag? "verbose" |>.isSome) - - if let some flag := p.flag? "workers" then - let n := flag.as! Nat - if n == 0 then - p.printError "error: --workers must be > 0" - return 1 - Std.Internal.UV.System.osSetenv "IX_KERNEL_CHECK_WORKERS" (toString n) - - IO.println s!"Profiling {envPath} → {outPath} (isolate={isolate})" - let start ← IO.monoMsNow - rsProfileAnonFFI envPath outPath isolate quiet - let elapsed := (← IO.monoMsNow) - start - IO.println s!"[profile] wrote {outPath} in {elapsed.formatMs}" - return 0 - -end Ix.Cli.ProfileCmd - -open Ix.Cli.ProfileCmd in -def profileCmd : Cli.Cmd := `[Cli| - "profile" VIA runProfileCmd; - "Profile a `.ixe` out of circuit → `.ixprof` (sharding cost + delta graph)" - - FLAGS: - out : String; "Output .ixprof path (default: .ixprof, e.g. init.ixe → init.ixprof)" - "keep-caches"; "Keep cross-constant caches: faster, lower-fidelity, may under-record" - workers : Nat; "Parallel kernel workers (default: available_parallelism). Plumbs IX_KERNEL_CHECK_WORKERS." - verbose; "Log every constant (default: quiet)" - - ARGS: - path : String; "Path to a serialized .ixe environment" -] - -end diff --git a/Ix/Cli/ProveCmd.lean b/Ix/Cli/ProveCmd.lean index cf571eecd..0076ace11 100644 --- a/Ix/Cli/ProveCmd.lean +++ b/Ix/Cli/ProveCmd.lean @@ -1,31 +1,3 @@ -/- - `ix prove`: generate a STARK proof against an `Ix.Claim`. Mirrors - the CLI shape of `ix check`: - - ix prove Nat.add_comm # compiled-in Lean env - ix prove --ixe arena.ixe Foo.bar # from .ixe, named target - ix prove --ixe arena.ixe # iterate every named const - ix prove --ixe arena.ixe --claim # against a persisted claim - ix prove --ixe env.ixe --ixes env.ixes # whole env, aggregated - ix prove --ixe env.ixe --ixes env.ixes --compress plonk - - Each invocation runs the same `verify_claim` Aiur witness that - `ix check` does, then drives Aiur's `prove` over it and persists - the result as an `Ixon.Proof` wrapper (claim + opaque proof - bytes). Prints the resulting proof blake3 hex on stdout — feed - that to `ix verify `. - - Per-claim mode (`--claim`) loads the claim from the store and - resolves every referenced assumption / env / contains tree - (build trees with `ix tree canonical` / `ix tree env`). - - Per-name mode builds a default `Claim.check addr none` and - persists the claim alongside the proof so `ix verify` can stand - alone with just the proof hex. - - Driven by the shared `Ix.Check.forEachClaim`: the only - prove-specific surface is `runOne = proveOne aiurSystem compiled`. --/ module public import Cli public import Ix.Aiur.Compiler @@ -37,11 +9,6 @@ public import Ix.Shard.Environment public import Ix.Cli.ShardProofIndex public import Ix.Cli.CompressCmd public import Ix.Common -public import Ix.IxVM -public import Ix.IxVM.Toplevel -public import Ix.IxVM.ClaimHarness -public import Ix.Ixon -public import Ix.Store public section @@ -602,7 +569,6 @@ def runProveCmd (p : Cli.Parsed) : IO UInt32 := do return if failed.isEmpty then 0 else 1 | _, _, _ => Ix.Check.forEachClaim ixePath claimHex names keepGoing "prove" false runOne - (rust := p.hasFlag "rust") end Ix.Cli.ProveCmd @@ -616,7 +582,6 @@ def proveCmd : Cli.Cmd := `[Cli| "keep-going"; "Continue past failures and report them at the end instead of halting on the first." "texray"; "Stream per-phase `[texray]` timing/RSS lines (execute, witness, STARK stages) to stderr as each span closes." "ixe" : String; "Path to a serialized `.ixe` env. When set, the binary reads the env from disk instead of using the compiled-in Lean env. The file must carry a role binding: its claims are relative to it." - "rust"; "Without --ixe: compile each name's closure with the Rust compiler instead of the certified Lean pipeline. The Rust compiler runs none of the compile's checks, and its output is not covered by the compiler's correctness theorem." "claim" : String; "32-byte hex address of a persisted `Ix.Claim` in `~/.ix/store/`. When set, proves the persisted claim against the `--ixe` env (single proof, skips per-const iteration)." "ixes" : String; "Path to a `.ixes` shard manifest (with --ixe). By default, prove every shard and aggregate them into one root, printing its address. With --shard K: prove shard K. With --shards-only: prove every shard without aggregating." "shard" : Nat; "0-based shard index K (with --ixes and --ixe): prove that one shard's CheckEnv claim." diff --git a/Ix/Cli/ShardCmd.lean b/Ix/Cli/ShardCmd.lean index f7f5c4bd7..bb54a5153 100644 --- a/Ix/Cli/ShardCmd.lean +++ b/Ix/Cli/ShardCmd.lean @@ -1,26 +1,24 @@ -/- - `ix shard [--profile ]`: partition an environment - into shards for the Aiur (IxVM) checking pipeline. - - Two strategies: - - **Static (default, no `--profile`)**: computed from the `.ixe` alone — - no out-of-circuit kernel run. Byte-balanced min-cut over the env's - static walk-edge nets (the relation that generates each shard's thin - frontier, i.e. its real ingress), then a global rebalance post-pass - toward equal predicted Aiur FFT cost (fitted model — constants and - provenance in `ix_kernel::shard::STATIC_OWNED_PER_BYTE`). `--shards N` - fixes the count; `--max-ram G` seeds it from the same static block-shape - score, mildly superlinear in both library scale and inverse RAM budget. - Measured against the profiled strategy on the 8-shard Init / 24-shard Std - harnesses: mean shard FFT −30%, max shard −44/−51%, stddev 17.7%→7.1% / - 30.6%→8.9%. - - **Profiled (`--profile `)**: the original pipeline over an - `ix profile` run, unchanged. Modes (precedence in `runShardCmd`): - default / `--max-ram G` / `--max-cycles C` **bin-pack to a per-shard - cycle/RAM cap** (no `--max-ram` ⇒ sized to detected system RAM); - `--shards N` forces exactly `N` balanced min-cut shards. - - Both write the same `.ixes` manifest and print a what-if report. The +module +public import Cli +public import Ix.Common +public import Ix.KernelCheck +public import Ix.Cli.ConstsFile +public import Ix.Cli.RefineCmd + +/-! + `ix shard `: partition an environment into shards for the + Aiur (IxVM) checking pipeline using the static strategy. + + Computed from the `.ixe` alone — no out-of-circuit kernel run. + Byte-balanced min-cut over the env's static walk-edge nets (the + relation that generates each shard's thin frontier, i.e. its real + ingress), then a global rebalance post-pass toward equal predicted + Aiur FFT cost (fitted model — constants and provenance in + `ixon::shard::STATIC_OWNED_PER_BYTE`). `--shards N` fixes the + count; `--max-ram G` seeds it from the same static block-shape score, + mildly superlinear in both library scale and inverse RAM budget. + + Writes a `.ixes` manifest and prints a what-if report. The partitioner is self-contained — no external graph-library dependency. `ix shard extract --consts `: the pipeline's scoping @@ -28,15 +26,8 @@ env into a standalone `.ixe`, without recompiling from source. The output carries the closure's genuine constant bytes, blobs, and reducibility hints, plus each closure constant's name→address entry, so it composes - with everything that consumes a `.ixe` (`ix profile` → `ix shard`, - `ix check-rs --consts`, the zkVM hosts, `bench-typecheck`). + with everything that consumes a `.ixe`. -/ -module -public import Cli -public import Ix.Common -public import Ix.KernelCheck -public import Ix.Cli.ConstsFile -public import Ix.Cli.RefineCmd public section @@ -72,7 +63,7 @@ def shardExtractCmd : Cli.Cmd := `[Cli| "Extract named constants + their dependency closure from a `.ixe` into a standalone `.ixe`" FLAGS: - consts : String; "Comma-separated EXACT constant names (displayed form) to extract, e.g. `Nat.add_comm,String.append`. Same flag/shape as `ix check-rs --consts`. A mutual-block member extracts its whole block." + consts : String; "Comma-separated EXACT constant names (displayed form) to extract, e.g. `Nat.add_comm,String.append`. A mutual-block member extracts its whole block." "consts-file" : String; "Additionally read names from a file (one per line; `#` comments and blank lines ignored). Unions with --consts." out : String; "Output `.ixe` path. Defaults to a slug of the first name (e.g. `nat_add_comm.ixe`)." verbose; "Print extraction details to stderr." @@ -89,7 +80,7 @@ def runShardGraphCmd (p : Cli.Parsed) : IO UInt32 := do let outPath : String := match p.flag? "out" with | some flag => flag.as! String - -- `init.ixe` → `init.graph`, mirroring the `.ixprof` default naming. + -- `init.ixe` → `init.graph`. | none => let base := if envPath.endsWith ".ixe" then (envPath.dropEnd 4).toString else envPath base ++ ".graph" @@ -113,14 +104,6 @@ def runShardCmd (p : Cli.Parsed) : IO UInt32 := do | p.printError "error: must specify to a .ixe file" return 1 let envPath := pathArg.as! String - let profileFlag : Option String := (p.flag? "profile").map (·.as! String) - -- Old CLI shape took the `.ixprof` positionally; catch it so scripts - -- fail loudly instead of parsing a profile as an env. - if profileFlag.isNone && envPath.endsWith ".ixprof" then - p.printError "error: the positional argument is now the `.ixe` env; \ - pass the profile via --profile (or drop it for the \ - static strategy)" - return 1 let balancePct : Nat := match p.flag? "balance" with | some flag => flag.as! Nat @@ -134,67 +117,28 @@ def runShardCmd (p : Cli.Parsed) : IO UInt32 := do let base := if envPath.endsWith ".ixe" then (envPath.dropEnd 4).toString else envPath base ++ ".ixes" let shardsFlag : Option Nat := (p.flag? "shards").map (·.as! Nat) - let maxCycles : Option Nat := (p.flag? "max-cycles").map (·.as! Nat) let maxRam : Option Nat := (p.flag? "max-ram").map (·.as! Nat) let execJobs : Nat := ((p.flag? "exec-jobs").map (·.as! Nat)).getD 3 - -- Provers the prove-time estimate assumes (wall clock = max(seq/P, slowest - -- shard)). Sharded proving is sequential today, so the default is 1. - let parallelism : Nat := - match p.flag? "parallelism" with - | some flag => max 1 (flag.as! Nat) - | none => 1 - - match profileFlag with - | none => - -- STATIC strategy (no out-of-circuit profiling): byte-balanced min-cut - -- over the env's walk-edge nets + predicted-FFT rebalance post-pass. - -- `--shards N` fixes the count. Under `--max-ram G`, Rust chooses a - -- seed only after building the static block profile: a CUDA build - -- scales the calibrated Mathlib cut by the blocks' serialized bytes - -- (execution records are close to linear in them); a CPU build uses the - -- fitted owned score, which also sees each block's shape. - -- Boundaries and budget fitting are still measured where execution - -- happens anyway (the prove gate splits over-budget shards inline and the - -- corrected manifest remembers the leaves). - if maxCycles.isSome then - p.printError "error: --max-cycles requires --profile (its budget \ - model is calibrated on profiled op counters)" - return 1 - let layout := if p.hasFlag "ordered" then "ordered" else "mincut" - match shardsFlag, maxRam with - | some n, _ => - if n == 0 then - p.printError "error: --shards must be positive"; return 1 - IO.println s!"Sharding {envPath} into {n} shards \ - (static strategy, {layout} layout, balance ±{balancePct}%)" - rsShardEnvStaticFFI envPath n.toUSize 0 balancePct.toUInt64 layout - outPath execJobs.toUSize - | none, some gib => - if gib == 0 then - p.printError "error: --max-ram must be positive"; return 1 - IO.println s!"Sharding {envPath} for a {gib} GiB prover budget \ - (static block-shape seed, {layout} layout, balance ±{balancePct}%)" - rsShardEnvStaticFFI envPath 0 gib.toUInt64 balancePct.toUInt64 layout - outPath execJobs.toUSize - | none, none => - p.printError "error: the static strategy (no --profile) requires \ - --shards N or --max-ram G" - return 1 - | some espPath => - -- Profiled strategy, unchanged: partition the `.ixprof`. - -- Precedence: explicit --shards (fixed count) > explicit --max-cycles/--max-ram - -- (budget) > default (size to detected system RAM). - match shardsFlag with - | some n => - IO.println s!"Sharding {espPath} into {n} shards (balance ±{balancePct}%)" - rsShardEspFFI espPath n.toUSize balancePct.toUInt64 parallelism.toUSize outPath - | none => - if maxCycles.isNone && maxRam.isNone then - IO.println s!"Sharding {espPath} to detected system RAM (balance ±{balancePct}%)" - else - IO.println s!"Sharding {espPath} to budget (max-cycles={maxCycles.getD 0}, max-ram={maxRam.getD 0} GiB, balance ±{balancePct}%)" - rsShardEspCapFFI espPath (maxCycles.getD 0).toUInt64 (maxRam.getD 0).toUInt64 - balancePct.toUInt64 parallelism.toUSize outPath + + let layout := if p.hasFlag "ordered" then "ordered" else "mincut" + match shardsFlag, maxRam with + | some n, _ => + if n == 0 then + p.printError "error: --shards must be positive"; return 1 + IO.println s!"Sharding {envPath} into {n} shards \ + (static strategy, {layout} layout, balance ±{balancePct}%)" + rsShardEnvStaticFFI envPath n.toUSize 0 balancePct.toUInt64 layout + outPath execJobs.toUSize + | none, some gib => + if gib == 0 then + p.printError "error: --max-ram must be positive"; return 1 + IO.println s!"Sharding {envPath} for a {gib} GiB prover budget \ + (static block-shape seed, {layout} layout, balance ±{balancePct}%)" + rsShardEnvStaticFFI envPath 0 gib.toUInt64 balancePct.toUInt64 layout + outPath execJobs.toUSize + | none, none => + p.printError "error: ix shard requires --shards N or --max-ram G" + return 1 if !outPath.isEmpty then IO.println s!"[shard] wrote {outPath}" return 0 @@ -204,17 +148,14 @@ end Ix.Cli.ShardCmd open Ix.Cli.ShardCmd Ix.Cli.RefineCmd in def shardCmd : Cli.Cmd := `[Cli| "shard" VIA runShardCmd; - "Partition a `.ixe` env into shards: static strategy by default (`--shards N`, no kernel run), or the profiled pipeline via `--profile `" + "Partition a `.ixe` env into shards using the static strategy (`--shards N` or `--max-ram G`, no kernel run)" FLAGS: - profile : String; "Path to a `.ixprof` from `ix profile`. When given, use the profiled strategy (cap budgeting / balanced min-cut over measured costs); when absent, the static strategy partitions the `.ixe` directly." - shards : Nat; "Fixed number of shards N (static strategy; overrides --max-ram sizing and the profiled default budget sizing)" - "max-cycles" : Nat; "Per-shard guest-cycle budget (profiled strategy only)" - "max-ram" : Nat; "Per-shard prover-RAM budget, GiB. Static strategy: seed the shard count from the `.ixe` block-shape score (serialized block size + a superlinear large-body term). A CUDA build seeds for the trace-shard prover by serialized block bytes, against one execution's share of the record budget (this budget less the prover's working set at AIUR_TRACE_SHARD_MAX_CELLS, over --exec-jobs + 2), anchored at the 78-shard Mathlib cut proven on four GPUs (execution records are close to linear in owned bytes); a CPU build seeds with the CPU prover's fitted scale and budget exponents anchored at Mathlib. The record cap or the execution gate corrects every boundary. Profiled strategy: budget from measured op counters (default: detected system RAM)." + shards : Nat; "Fixed number of shards N (overrides --max-ram sizing)" + "max-ram" : Nat; "Per-shard prover-RAM budget, GiB. Seed the shard count from the `.ixe` block-shape score (serialized block size + a superlinear large-body term). A CUDA build seeds for the trace-shard prover by serialized block bytes, against one execution's share of the record budget (this budget less the prover's working set at AIUR_TRACE_SHARD_MAX_CELLS, over --exec-jobs + 2), anchored at the 78-shard Mathlib cut proven on four GPUs (execution records are close to linear in owned bytes); a CPU build seeds with the CPU prover's fitted scale and budget exponents anchored at Mathlib. The record cap or the execution gate corrects every boundary." "exec-jobs" : Nat; "CUDA build, static strategy with --max-ram: the claim executions ahead of each prover the run will use (`ix prove --exec-jobs`), which sets the record share the seed fits (default 3, the calibration's)." balance : Nat; "Per-bisection balance tolerance, percent (default 5)" - ordered; "Static strategy: lay the shards out as contiguous ranges of a dependency order instead of a min-cut, shard 0 at the top, so each shard's reference closure lies in its own shard and later ones. The layout `ix prove --distributed` wants, where each shard is one worker's chunk: the records then commit one at a time in chunk order." - parallelism : Nat; "Provers assumed for the prove-time estimate (profiled strategy only; default 1 = sequential)" + ordered; "Lay the shards out as contiguous ranges of a dependency order instead of a min-cut, shard 0 at the top, so each shard's reference closure lies in its own shard and later ones. The layout `ix prove --distributed` wants, where each shard is one worker's chunk: the records then commit one at a time in chunk order." out : String; "Output .ixes manifest path (default: env base name + `.ixes`, e.g. init.ixe → init.ixes)" ARGS: diff --git a/Ix/Cli/ValidateLeanCmd.lean b/Ix/Cli/ValidateLeanCmd.lean index 330c43309..9f5fa726d 100644 --- a/Ix/Cli/ValidateLeanCmd.lean +++ b/Ix/Cli/ValidateLeanCmd.lean @@ -1,17 +1,30 @@ -/- +module +public import Cli +public import Ix.Common +public import Ix.CanonM +public import Ix.CompileM +public import Ix.CompileDriver +public import Ix.CompileCertified +public import Ix.DecompileM +public import Ix.DecompileDriver +public import Ix.KernelFidelity.Egress +public import Ix.KernelFidelity.Reader +public import Ix.Meta +public import Ix.Cli.ValidateCmd +public import Ix.RoleBinding.Compile +public import Ix.SemanticContract + +/-! `ix validate-lean `: run the pure-Lean Ix pipeline validation against the Lean environment for any file. Phases (all pure-Lean): - 1. Compile (Lean → Ixon) — the full pure-Lean pipeline - (canon → graph → ground → - condense → parallel - compile → serialize), its - post-steps, and the role binding - `ix compile-lean` adds: the later - phases read the bytes compile-lean - writes + 1. Compile (Lean → Ixon) — the certified pure-Lean pipeline + and the role binding `ix compile-lean` + adds (`Ix.CompileM.compileAndFinalize`): + the later phases read the bytes + compile-lean writes 2. Serde gate (pure) — verified lazy parsing, with exact writer bytes per serialized unit 3. Projection/block-order fidelity — IxC reconstructs the projections; @@ -35,22 +48,6 @@ `Benchmarks/Compile/CompileMathlib.lean`) must not become compile-time deps of the test suite. -/ -module -public import Cli -public import Ix.Common -public import Ix.CanonM -public import Ix.CompileM -public import Ix.CompileDriver -public import Ix.CompileResource -public import Ix.Cli.DriverOptions -public import Ix.DecompileM -public import Ix.DecompileDriver -public import Ix.KernelFidelity.Egress -public import Ix.KernelFidelity.Reader -public import Ix.Meta -public import Ix.Cli.ValidateCmd -public import Ix.RoleBinding.Compile -public import Ix.SemanticContract public section @@ -145,13 +142,6 @@ def runValidateLeanCmd (p : Cli.Parsed) : IO UInt32 := do if ixe?.isNone && path?.isNone then p.printError "error: must specify to a Lean source file (or --ixe )" return 1 - -- The drivers' options (sharing limits, traces), read once for the compile - -- (phase 1) and the decompile (phase 5). - let opts ← match ← Ix.Cli.driverOptionsFromEnv with - | .ok opts => pure opts - | .error e => - IO.eprintln s!"[validate-lean] error: {e}" - return 2 -- Sources: either elaborate a Lean file (full pipeline, oracle -- available) or read a pre-compiled `.ixe` (phases 2–3 only). @@ -162,12 +152,8 @@ def runValidateLeanCmd (p : Cli.Parsed) : IO UInt32 := do -- Phase 5's decompile oracle, in one of two forms. The default is a -- per-name 64-bit DIGEST of the canonicalized source (derived -- `Hashable`, same field coverage as the `BEq` used by `--full-oracle`, - -- O(1) at the hash-consed Name/Level/Expr leaves), collected by the - -- STREAMING compile driver during its transient canon pass — the - -- whole-env canon map is never materialized at all, which at - -- whole-Mathlib scale is the difference between fitting in RAM and - -- swapping. - -- `--full-oracle` keeps the old whole-env view: full structural BEq per + -- O(1) at the hash-consed Name/Level/Expr leaves). + -- `--full-oracle` keeps the whole-env view: full structural BEq per -- constant — use it to debug a digest mismatch on a filtered (`--ns`) closure. let mut canonView? : Option (Std.HashMap Ix.Name Ix.ConstantInfo) := none let mut canonDigests? : Option (Std.HashMap Ix.Name UInt64) := none @@ -207,36 +193,31 @@ def runValidateLeanCmd (p : Cli.Parsed) : IO UInt32 := do IO.println s!"Total constants: {constList.length}" IO.println "[validate-lean] phase 1: compiling (pure-Lean pipeline)..." (← IO.getStdout).flush - -- Phase 1: compile through the PURE-LEAN pipeline (canon → graph → - -- ground → condense → parallel compile → serialize), with its - -- post-steps, then add the role binding as `ix compile-lean` does - -- (`Ix.RoleBinding.Compile.finalize`), so that the later phases read the - -- bytes compile-lean writes. + -- Phase 1: compile through the certified PURE-LEAN pipeline and add the + -- role binding as `ix compile-lean` does (`Ix.CompileM.compileAndFinalize`), + -- so that the later phases read the bytes compile-lean writes. A partial + -- compile is finalized too; its failures fail the phase below. let compileWorkers := ((p.flag? "workers").map (·.as! Nat)).getD 32 let t0 ← IO.monoMsNow - let input ← IO.ofExcept ((Ix.Compile.compileInputFromEnv leanEnv constList).mapError toString) - match ← Ix.CompileM.compileLeanInputValidated input - (numWorkers := compileWorkers) (opts := opts) with + match ← Ix.CompileM.compileAndFinalize leanEnv constList compileWorkers + (allowPartial := true) with | .error e => phases ← pushPhase phases ("1. Compile (pure-Lean pipeline)", .failed s!"pure-Lean pipeline: {e}") - | .ok out => - let bound : Except String Ix.CompileM.LeanPipelineOut ← try - let artifact ← Ix.RoleBinding.Compile.finalize - (Ix.RoleBinding.Compile.leanBackend compileWorkers opts) leanEnv - Ix.RoleBinding.Compile.nativeCertSource input out.env - Ix.Resource.typingLimits - let finalized := { out with env := artifact, bytes := ← IO.ofExcept (Ixon.serEnv artifact) } - IO.ofExcept (Ix.CompileM.resourcePostStep finalized) - pure (.ok finalized) - catch e => pure (.error e.toString) + | .ok (out, finalization) => + let bound : Except String (Ixon.Env × ByteArray) := match finalization with + | .done artifact finalizedBytes => .ok (artifact, finalizedBytes) + | .failed e => .error e + | .refused => .error "the compile was not finalized" let added : Array Ix.Name := match bound with - | .ok finalized => finalized.env.named.fold (init := #[]) fun acc n _ => + | .ok (artifact, _) => artifact.named.fold (init := #[]) fun acc n _ => if out.env.named.contains n then acc else acc.push n | .error _ => #[] - bytes := (match bound with | .ok finalized => finalized.bytes | .error _ => out.bytes) + bytes := match bound with + | .ok (_, finalizedBytes) => finalizedBytes + | .error _ => (Ixon.serEnv out.env).toOption.getD .empty bindingRecords := match bound with - | .ok finalized => finalized.env.consts.fold (init := #[]) fun acc a _ => + | .ok (artifact, _) => artifact.consts.fold (init := #[]) fun acc a _ => if out.env.consts.contains a then acc else acc.push a | .error _ => #[] -- The binding's own constants (role support and certificate theorems) are @@ -249,14 +230,10 @@ def runValidateLeanCmd (p : Cli.Parsed) : IO UInt32 := do let ln := Ix.SemanticContract.toLeanName n (sources.findSome? (·.find? ln)).map (ln, ·) let addedCanon := Ix.CanonM.canonChunk addedConsts + let constArr := out.prepared.toArray + let chunkSize := max 1 ((constArr.size + 31) / 32) + let chunkArr := Ix.CanonM.chunks constArr chunkSize if fullOracle then - -- Materialize the whole canon view post-hoc (chunk-parallel). - -- The streaming compile never builds it; canon is per-constant - -- deterministic, so this equals the view the compile read - -- through its on-demand fallback. - let constArr := constList.toArray - let chunkSize := max 1 ((constArr.size + 31) / 32) - let chunkArr := Ix.CanonM.chunks constArr chunkSize let tasks := chunkArr.map fun chunk => Task.spawn fun _ => Ix.CanonM.canonChunk chunk let mut view : Std.HashMap Ix.Name Ix.ConstantInfo := {} @@ -267,27 +244,34 @@ def runValidateLeanCmd (p : Cli.Parsed) : IO UInt32 := do unless view.contains n do view := view.insert n ci canonView? := some view else - -- The streamed canon pass already digested every input - -- constant; the whole-env canon map never existed. - canonDigests? := some (addedCanon.foldl (init := out.digests) fun m (n, ci) => - if m.contains n then m else m.insert n (hash ci)) + let tasks := chunkArr.map fun chunk => + Task.spawn fun _ => (Ix.CanonM.canonChunk chunk).map fun (n, ci) => (n, hash ci) + let mut digs : Std.HashMap Ix.Name UInt64 := + Std.HashMap.emptyWithCapacity constArr.size + for task in tasks do + for (n, d) in task.get do + digs := digs.insert n d + for (n, ci) in addedCanon do + unless digs.contains n do digs := digs.insert n (hash ci) + canonDigests? := some digs let t1 ← IO.monoMsNow + let failures := out.failures.toMap match bound with | .error e => phases ← pushPhase phases ("1. Compile (pure-Lean pipeline)", .failed s!"role binding (as compile-lean adds it): {e}") - | .ok finalized => - if out.cenv.ungrounded.size > 0 then - let shown := out.cenv.ungrounded.toList.take 3 + | .ok (_, finalizedBytes) => + if !failures.isEmpty then + let shown := failures.toList.take 3 let msgs := shown.map fun (n, m) => s!" ✗ {n.pretty}: {m.take 160}" phases ← pushPhase phases ("1. Compile (pure-Lean pipeline)", - .failed (s!"{out.cenv.ungrounded.size} per-block compile \ -failure(s) ({finalized.bytes.size} bytes, {t1 - t0}ms)\n" ++ + .failed (s!"{failures.size} per-block compile \ +failure(s) ({finalizedBytes.size} bytes, {t1 - t0}ms)\n" ++ String.intercalate "\n" msgs)) else phases ← pushPhase phases ("1. Compile (pure-Lean pipeline)", .passed s!"pure-Lean pipeline with the role binding compile-lean adds: \ -{finalized.bytes.size} bytes, {out.blockCount} blocks, {out.ungroundedCount} ungrounded, \ +{finalizedBytes.size} bytes, {out.inputs} input constants, {failures.size} ungrounded, \ {added.size} names added by the binding ({addedCanon.size} with a source) ({t1 - t0}ms)") | none, none => unreachable! @@ -396,7 +380,7 @@ use IX_SKIP_PHASES=3 to skip this phase explicitly") let decompileWorkers := ((p.flag? "workers").map (·.as! Nat)).getD 16 let (decompiled, errs, _p2st) ← Ix.DecompileM.decompileEnvFullParallel ixonEnv canonView? - (numWorkers := decompileWorkers) (sharingLimits := opts.sharingLimits) + (numWorkers := decompileWorkers) let t1 ← IO.monoMsNow if !errs.isEmpty then let shown := errs.toList.take 5 diff --git a/Ix/Commit.lean b/Ix/Commit.lean index 0be1eda73..f04f697cd 100644 --- a/Ix/Commit.lean +++ b/Ix/Commit.lean @@ -1,16 +1,17 @@ -/- +module +public import Ix.Claim +public import Ix.CompileM +public import Ix.CanonM + +/-! Ix.Commit: Commitment pipeline for ZK voting and claim construction. Provides utilities for: - - Building a CompileEnv from rsCompilePhases output + - Building a CompileEnv from CompilePhases output - Incrementally compiling and committing definitions - Building evaluation, check, and reveal claims - Opening committed constants for selective field revelation -/ -module -public import Ix.Claim -public import Ix.CompileM -public import Ix.CanonM public section @@ -19,10 +20,10 @@ namespace Ix.Commit open Ixon (PutM runPut putConstant putExpr Comm) -- ============================================================================ --- mkCompileEnv: Build a compilation cache from rsCompilePhases output +-- mkCompileEnv: Build a compilation cache from CompilePhases output -- ============================================================================ -/-- Create a CompileEnv from the output of rsCompilePhases. +/-- Create a CompileEnv from a `CompilePhases` value. This allows incremental compilation of new definitions against the already-compiled base environment. -/ def mkCompileEnv (phases : Ix.CompileM.CompilePhases) : Ix.CompileM.CompileEnv := diff --git a/Ix/Compile/ExactKey.lean b/Ix/Compile/ExactKey.lean index a9812b885..bf268498d 100644 --- a/Ix/Compile/ExactKey.lean +++ b/Ix/Compile/ExactKey.lean @@ -455,7 +455,7 @@ structure PreseedKey where deriving DecidableEq instance preseedKeyBEq : BEq PreseedKey := - ⟨fun a b => exprEq a.expr b.expr && a.params.isEqv b.params nameEq && decide (a.contextHash = b.contextHash)⟩ + ⟨fun a b => decide (a.contextHash = b.contextHash) && exprEq a.expr b.expr && a.params.isEqv b.params nameEq⟩ instance preseedKeyHashable : Hashable PreseedKey := ⟨fun key => hash (key.expr.getHash, key.contextHash)⟩ @@ -466,12 +466,12 @@ instance preseedKeyLawful : LawfulBEq PreseedKey where | mk expr params contextHash => cases b with | mk other otherParams otherHash => - change (exprEq expr other && params.isEqv otherParams nameEq && decide (contextHash = otherHash)) = true at same + change (decide (contextHash = otherHash) && exprEq expr other && params.isEqv otherParams nameEq) = true at same simp only [Bool.and_eq_true, exprEq_eq, decide_eq_true_eq, paramsEq_iff] at same rcases same with ⟨⟨rfl, rfl⟩, rfl⟩ rfl rfl {a} := by - change (exprEq a.expr a.expr && a.params.isEqv a.params nameEq && decide (a.contextHash = a.contextHash)) = true + change (decide (a.contextHash = a.contextHash) && exprEq a.expr a.expr && a.params.isEqv a.params nameEq) = true simp only [Bool.and_eq_true, exprEq_eq, decide_eq_true_eq, paramsEq_iff, and_self] theorem preseedKey_fields {a b : PreseedKey} (same : (a == b) = true) : diff --git a/Ix/Compile/FrontEnd.lean b/Ix/Compile/FrontEnd.lean index 81a8efbdd..4e7e47922 100644 --- a/Ix/Compile/FrontEnd.lean +++ b/Ix/Compile/FrontEnd.lean @@ -24,14 +24,18 @@ namespace Ix.Compile.FrontEnd /-- Compute each constant's references independently, then insert them in the original map iteration order. Every graph walk starts with its own empty memo. -/ -@[expose] def graph (env : Environment) : Map Name (Set Name) := +@[expose] def graphWith (env : Environment) + (constsArr : Array (Name × ConstantInfo) := env.consts.toList.toArray) : Map Name (Set Name) := (Parallel.map taskCount (fun (name, ci) => (name, (GraphM.run env .init (graphConst ci)).1)) - env.consts.toList.toArray).foldl (fun out (name, refs) => out.insert name refs) {} + constsArr).foldl (fun out (name, refs) => out.insert name refs) {} + +@[expose] def graph (env : Environment) : Map Name (Set Name) := + graphWith env /-- Task evaluation preserves the graph's entries and insertion order. -/ @[csimp] theorem graph_eq : @GraphM.envSerial = @graph := by funext env - simp only [graph, Parallel.map_eq, Array.foldl_map] + simp only [graph, graphWith, Parallel.map_eq, Array.foldl_map] unfold GraphM.envSerial simp only [Id.run, bind_pure, Std.HashMap.forIn_eq_forIn_toList, List.forIn_pure_yield_eq_foldl, List.foldl_toArray'] @@ -48,15 +52,19 @@ original map iteration order. Every graph walk starts with its own empty memo. - /-- Check constants independently and replay refusals in the original order. This preserves both immediate errors and later propagation's worklist order. -/ -@[expose] def immediate (env : Environment) : Map Name GroundError := +@[expose] def immediateWith (env : Environment) + (constsArr : Array (Name × ConstantInfo) := env.consts.toList.toArray) : Map Name GroundError := (Parallel.map taskCount (fun (name, ci) => (name, groundConstCheck ci env)) - env.consts.toList.toArray).foldl (fun out (name, result) => + constsArr).foldl (fun out (name, result) => match result with | .error error => out.insert name error | .ok _ => out) {} +@[expose] def immediate (env : Environment) : Map Name GroundError := + immediateWith env + theorem immediate_eq (env : Environment) : immediateSerial env = immediate env := by - simp only [immediate, Parallel.map_eq, Array.foldl_map] + simp only [immediate, immediateWith, Parallel.map_eq, Array.foldl_map] unfold immediateSerial simp only [Id.run, bind_pure, Std.HashMap.forIn_eq_forIn_toList, List.foldl_toArray'] generalize env.consts.toList = constants diff --git a/Ix/Compile/ReadyCompile.lean b/Ix/Compile/ReadyCompile.lean index 2ad418a6d..4cffbf0d6 100644 --- a/Ix/Compile/ReadyCompile.lean +++ b/Ix/Compile/ReadyCompile.lean @@ -21,6 +21,26 @@ def validate (env : Ix.Environment) (blocks : Ix.CondensedBlocks) : Except Strin validateInductiveFlags env validateMutualSafety env blocks +@[expose] def validatePar (env : Ix.Environment) (blocks : Ix.CondensedBlocks) : Except String Unit := + let missingTask := Task.spawn fun _ => missingBlockRefs? blocks + let flagsTask := Task.spawn fun _ => validateInductiveFlags env + let safetyTask := Task.spawn fun _ => validateMutualSafety env blocks + match missingTask.get with + | some message => .error message + | none => + match flagsTask.get with + | .error e => .error e + | .ok () => safetyTask.get + +@[csimp] theorem validate_eq_par : @validate = @validatePar := by + funext env blocks + unfold validate validatePar + cases missingBlockRefs? blocks with + | some message => rfl + | none => + dsimp only + cases validateInductiveFlags env <;> rfl + def initialWith (env : Ix.Environment) (blocks : Ix.CondensedBlocks) (sched : FoldSchedule) (limits : Ix.Sharing.Exact.Limits) : FoldState := let view : Ix.Environment := { consts := {}, fallback? := some env.get? } @@ -55,17 +75,19 @@ def driverIO (config : ReadyQueue.Config) (env : Ix.Environment) (width : Nat := foldWaveTasks) : IO ({ result : DriverResult // result = compileEnvFold env blocks dbg limits width } × ReadyQueue.Metrics) := do - let validation ← IO.lazyPure fun _ => - (⟨validate env blocks, rfl⟩ : { result : Except String Unit // result = validate env blocks }) + let validationTask ← IO.asTask do + return (⟨validate env blocks, rfl⟩ : { result : Except String Unit // result = validate env blocks }) + let sched := FoldSchedule.of blocks + let initSt := initialWith env blocks sched limits + let validation ← IO.ofExcept validationTask.get match checked : validation.val with | .error message => return (⟨.error message, by rw [driver_decomposition, ← validation.property, checked] rfl⟩, {}) | .ok _ => - let sched := FoldSchedule.of blocks let (state, metrics) ← ReadyQueue.run config blocks sched width dbg - (blocks.blocks.size + 1) (initialWith env blocks sched limits) + (blocks.blocks.size + 1) initSt let result ← IO.lazyPure fun _ => (⟨finish env state.val, by rw [driver_decomposition, ← validation.property, checked] diff --git a/Ix/Compile/ReadyQueue.lean b/Ix/Compile/ReadyQueue.lean index 8549102a2..76ddbeb88 100644 --- a/Ix/Compile/ReadyQueue.lean +++ b/Ix/Compile/ReadyQueue.lean @@ -47,8 +47,12 @@ def makePlan (blocks : Ix.CondensedBlocks) (sched : FoldSchedule) (st : FoldStat let (nextUnmet, nextReady) := predictedNext blocks sched unmet ready unmet := nextUnmet ready := nextReady - let indices := rows.zipIdx.foldl (fun indices ((_, rep, _), i) => - indices.insert rep i) (Std.HashMap.emptyWithCapacity rows.size) + let indices := Id.run do + let mut indices : Std.HashMap Ix.Compile.ExactKey.ExactName Nat := + Std.HashMap.emptyWithCapacity rows.size + for h : i in [:rows.size] do + indices := indices.insert rows[i].2.1 i + return indices let descriptors := Ix.Compile.Parallel.map 256 (fun (key, representative, members) => Id.run do let dependencies := (sched.deps.getD representative {}).toArray let mut parents : Array Nat := #[] @@ -119,6 +123,8 @@ structure State (input : FixedInput) where ready : Array Nat := #[] active : List (Nat × Task (Nat × Candidate input)) := [] bank : Std.HashMap Nat (Candidate input) := {} + /-- Published candidates not yet released (`Controller.published`). -/ + released : Array (Candidate input) := #[] claims : AddressTable := {} sizes : Std.HashMap Nat Nat := {} edges : Nat := 0 @@ -259,7 +265,9 @@ private def collect (i : Nat) (candidate : Candidate input) (st : State input) : def create (input : FixedInput) (config : Config) (blocks : Ix.CondensedBlocks) (sched : FoldSchedule) (st : FoldState) : IO (Controller input) := do let plan := makePlan blocks sched st - let state ← IO.mkRef { claims := st.acc.cenv.nameToAddr : State input } + let initSt : State input := startReady input config plan + (admit config plan { claims := st.acc.cenv.nameToAddr : State input }) + let state ← IO.mkRef initSt return { config, plan, state } /-- Collect completed workers even when the next publication is already in @@ -270,14 +278,22 @@ def Controller.pump (controller : Controller input) : IO Unit := do if ← IO.hasFinished task then let (finished, candidate) ← IO.wait task controller.state.modify (collect finished candidate) - controller.state.modify fun st => startReady input controller.config controller.plan - (admit controller.config controller.plan st) + controller.state.modify fun st => + let canAdmit := st.next < controller.plan.descriptors.size && + st.next < st.frontier + max 1 controller.config.lookahead && + st.edges < controller.config.pendingEdges + let st := if canAdmit then admit controller.config controller.plan st else st + if st.active.length < controller.config.workers - 1 && !st.ready.isEmpty then + startReady input controller.config controller.plan st + else st def chooseLoop (controller : Controller input) (i : Nat) : Nat → IO (Option (Candidate input)) | 0 => pure none | fuel + 1 => do + let found := (← controller.state.get).bank.get? i + if found.isSome then return found controller.pump - let found ← controller.state.modifyGet fun st => (st.bank.get? i, st) + let found := (← controller.state.get).bank.get? i if found.isSome then return found let active := (← controller.state.get).active match active with @@ -310,6 +326,20 @@ def Controller.observe (controller : Controller input) | .blobMismatch => { state.metrics with blobFallbacks := state.metrics.blobFallbacks + 1 } { state with metrics } +/-- Keep `x` alive until this call, wherever it runs. -/ +@[noinline] private def keepAlive (x : α) : IO α := + pure x + +/-- Hand the published candidates, once there are enough of them, to a pool +task that drops them, so that freeing them runs off the publication thread. -/ +def Controller.release (controller : Controller input) (batch : Nat := 1024) : IO Unit := do + let released ← controller.state.modifyGet fun st => + if st.released.size < batch then (#[], st) else (st.released, { st with released := #[] }) + unless released.isEmpty do + let _ ← IO.asTask do + let _ ← keepAlive released + pure () + def Controller.published (controller : Controller input) (rep : Ix.Name) : IO Unit := do let some i := controller.plan.indices.get? rep | return () controller.state.modify fun st => Id.run do @@ -317,25 +347,41 @@ def Controller.published (controller : Controller input) (rep : Ix.Name) : IO Un let admitted := st.admitted let bank := st.bank let sizes := st.sizes + let released := st.released let bytes := st.bytes - sizes.getD i 0 let mut st := { st with published := {} admitted := {} bank := {} sizes := {} + released := #[] bytes } + -- The bank holds the last reference to a published candidate: dropping it + -- here would free its whole block state (expression caches included) on + -- this thread, which every publication waits for. + let released := match bank.get? i with + | some candidate => released.push candidate + | none => released st := { st with - published := published.insert i admitted := admitted.erase i bank := bank.erase i - sizes := sizes.erase i } + sizes := sizes.erase i + released } -- An unlaunched fallback has completed on the publication thread. -- A running speculation still owns its completion and is drained normally. if !st.active.any (·.1 == i) then st := wake i st - for _ in [:controller.plan.descriptors.size] do - if !st.published.contains st.frontier then break - st := { st with frontier := st.frontier + 1 } + if i == st.frontier then + let mut published := published + let mut frontier := i + 1 + for _ in [:controller.plan.descriptors.size] do + if !published.contains frontier then break + published := published.erase frontier + frontier := frontier + 1 + st := { st with published, frontier } + else + st := { st with published := published.insert i } return st + controller.release controller.pump /-- Consume every remaining task even on a terminal compiler refusal or IO @@ -358,6 +404,8 @@ def Controller.drain (controller : Controller input) : IO Unit := do ready := #[] bytes := 0 edges := 0 } + -- the last published candidates, fewer than a batch, go the same way + controller.release 0 def run (config : Config) (blocks : Ix.CondensedBlocks) (sched : FoldSchedule) (width : Nat) (dbg : Bool) (fuel : Nat) (st : FoldState) : diff --git a/Ix/Compile/SourceGraph.lean b/Ix/Compile/SourceGraph.lean deleted file mode 100644 index c3cff3b8d..000000000 --- a/Ix/Compile/SourceGraph.lean +++ /dev/null @@ -1,115 +0,0 @@ -module -public import Ix.GraphM -public import Ix.Compile.ExactKey -public import Ix.RoleEvidence - -@[expose] public section -namespace Ix.Compile.SourceGraph - -/-! Compact Source dependency facts, captured while a canonical record is live. -The collector is unwired. It deliberately overwrites every summary and erases -an older kept record when the new winner is streamed. The concrete row constructor -drops theorem/opaque bodies; no grounding policy is implemented here. --/ - -inductive Grouping where - | plain - | induct (all : Array Name) - | recursor (all : Array Name) - deriving Inhabited - -inductive LiteralUse where - | none | nat | string - deriving Inhabited, BEq, DecidableEq - -structure Node where - direct : Ix.Set Name - literals : LiteralUse - grouping : Grouping - -def hasStrLit : Expr → Bool - | .lit (.strVal _) _ => true - | .app f a _ => hasStrLit f || hasStrLit a - | .lam _ t b _ _ | .forallE _ t b _ _ => hasStrLit t || hasStrLit b - | .letE _ t v b _ _ => hasStrLit t || hasStrLit v || hasStrLit b - | .mdata _ e _ | .proj _ _ e _ => hasStrLit e - | _ => false - -def hasNatLit : Expr → Bool - | .lit (.natVal _) _ => true - | .app f a _ => hasNatLit f || hasNatLit a - | .lam _ t b _ _ | .forallE _ t b _ _ => hasNatLit t || hasNatLit b - | .letE _ t v b _ _ => hasNatLit t || hasNatLit v || hasNatLit b - | .mdata _ e _ | .proj _ _ e _ => hasNatLit e - | _ => false - -def terms : ConstantInfo → List Expr - | .defnInfo v => [v.cnst.type, v.value] - | .thmInfo v => [v.cnst.type, v.value] - | .opaqueInfo v => [v.cnst.type, v.value] - | .recInfo v => v.cnst.type :: v.rules.toList.map (·.rhs) - | ci => [ci.getCnst.type] - -def literalUse (ci : ConstantInfo) : LiteralUse := - if (terms ci).any hasStrLit then .string - else if (terms ci).any hasNatLit then .nat else .none - -def literalNames : LiteralUse → Array Name - | .none => #[] - | .nat => Ix.Kernel.Reader.natLitSupportNames.toArray.map fun k => - Name.fromLeanName (Ix.RoleEvidence.toLeanName (k : Ix.Kernel.Key .source)) - | .string => Ix.Kernel.Reader.strLitSupportNames.toArray.map fun k => - Name.fromLeanName (Ix.RoleEvidence.toLeanName (k : Ix.Kernel.Key .source)) - -/-- The caller may reuse its already-computed direct reference set. -/ -def captureUsing (ci : ConstantInfo) (direct : Ix.Set Name) : Node := - { direct, literals := literalUse ci, grouping := match ci with - | .inductInfo v => .induct v.all - | .recInfo v => .recursor v.all - | _ => .plain } - -def capture (ci : ConstantInfo) : Node := - captureUsing ci (GraphM.run { consts := {} } .init (graphConst ci)).1 - -/-- The recursor index must come from the complete Source input. Its lookup -uses stored hashes; the first-member comparison remains structural. -/ -def dependencies (rs : Map Name (Array (Name × Name))) (node : Node) : Array Name := - node.direct.toArray ++ literalNames node.literals ++ match node.grouping with - | .plain => #[] - | .recursor all => all - | .induct all => all ++ match all[0]? with - | none => #[] - | some first => ((rs.getD first #[]).filter (ExactKey.nameEq ·.1 first)).map (·.2) - -/-- Exactly the streamed compiler's retention rule, independent of safety. -/ -def keep : ConstantInfo → Option ConstantInfo - | .thmInfo _ | .opaqueInfo _ => none - | ci => some ci - -structure Row where - name : Name - node : Node - kept : Option ConstantInfo - -def rowUsing (n : Name) (ci : ConstantInfo) (direct : Ix.Set Name) : Row := - ⟨n, captureUsing ci direct, keep ci⟩ - -def rowOfCanonical (n : Name) (ci : ConstantInfo) : Row := ⟨n, capture ci, keep ci⟩ - -structure Collected where - nodes : Map Name Node := {} - code : Map Name ConstantInfo := {} - deriving Inhabited - -/-- Both maps describe the final row at each key, including hash aliases. -Destructure the owned accumulator before updating either map. -/ -def mergeRow (acc : Collected) (row : Row) : Collected := - let ⟨nodes, code⟩ := acc - let code := match row.kept with - | some ci => code.insert row.name ci - | none => code.erase row.name - ⟨nodes.insert row.name row.node, code⟩ - -def collect (rows : List Row) : Collected := rows.foldl mergeRow {} - -end Ix.Compile.SourceGraph diff --git a/Ix/Compile/SourceGraph/Accounting.lean b/Ix/Compile/SourceGraph/Accounting.lean deleted file mode 100644 index 11cd25934..000000000 --- a/Ix/Compile/SourceGraph/Accounting.lean +++ /dev/null @@ -1,40 +0,0 @@ -module -public import Ix.Compile.SourceGraph -public import Ix.Ixon - -@[expose] public section -namespace Ix.Compile.SourceGraph.Accounting - -inductive Issue where - | neither (name : Name) - | both (name : Name) - | outside (name : Name) - deriving Repr, DecidableEq - -def Issue.message : Issue → String - | .neither n => s!"source accounting: '{n.pretty}' is neither published nor recorded as failed" - | .both n => s!"source accounting: '{n.pretty}' is both published and recorded as failed" - | .outside n => s!"source accounting: failure name '{n.pretty}' is outside the original input" - -structure Report where - uncovered : Array Name := #[] - outside : Array Name := #[] - -/-- Collect only failing keys; no complete pair/body array is materialized. -/ -def selectKeys (m : Map Name α) (bad : Name → Bool) : Array Name := - m.fold (init := #[]) fun acc n _ => if bad n then acc.push n else acc -def scan (nodes : Map Name Node) (named : Map Name Ixon.Named) (failed : Map Name String) : Report := - ⟨selectKeys nodes (fun n => named.contains n == failed.contains n), - selectKeys failed (fun n => !nodes.contains n)⟩ -def verdict (failed : Map Name String) (report : Report) : Except Issue Unit := - match report.uncovered.toList, report.outside.toList with - | n :: _, _ => .error (if failed.contains n then .both n else .neither n) - | [], n :: _ => .error (.outside n) - | [], [] => .ok () - -/-- Final accounting: generated publications and failed-only partial -results are allowed. This neither checks types nor validates admission. -/ -def check (nodes : Map Name Node) (named : Map Name Ixon.Named) (failed : Map Name String) : Except Issue Unit := - verdict failed (scan nodes named failed) - -end Ix.Compile.SourceGraph.Accounting diff --git a/Ix/Compile/SourceGraph/Literal.lean b/Ix/Compile/SourceGraph/Literal.lean deleted file mode 100644 index 75a17c1b4..000000000 --- a/Ix/Compile/SourceGraph/Literal.lean +++ /dev/null @@ -1,172 +0,0 @@ -module -public import Ix.Compile.SourceGraph -public import Std.Data.HashMap.Lemmas - -public section -namespace Ix.Compile.SourceGraph -namespace Literal - -abbrev Bits := Bool × Bool -def tree (e : Expr) : Bits := (hasStrLit e, hasNatLit e) -def join (a b : Bits) : Bits := (a.1 || b.1, a.2 || b.2) - -/-- Exact expression keys, with a checked result for every stored entry. -/ -structure Memo where - entries : Std.HashMap ExactKey.ExprKey Bits - sound : ∀ (key : ExactKey.ExprKey) (bits : Bits), entries[key]? = some bits → bits = tree key.expr - -def empty : Memo := ⟨{}, by intro key bits h; simp at h⟩ -def Extends (a b : Memo) : Prop := - ∀ (key : ExactKey.ExprKey) (bits : Bits), a.entries[key]? = some bits → b.entries[key]? = some bits - -theorem Extends.refl (m : Memo) : Extends m m := fun _ _ h => h -theorem Extends.trans {a b c : Memo} (ab : Extends a b) (bc : Extends b c) : Extends a c := - fun key bits h => bc key bits (ab key bits h) - -def store (m : Memo) (e : Expr) (bits : Bits) (correct : bits = tree e) : Memo := - ⟨m.entries.insert ⟨e⟩ bits, by - intro key value h - rw [Std.HashMap.getElem?_insert] at h - split at h - · rename_i same - have eq : (⟨e⟩ : ExactKey.ExprKey) = key := eq_of_beq same - subst key - cases h - exact correct - · exact m.sound key value h⟩ - -theorem store_extends (m : Memo) (e : Expr) (bits : Bits) (correct : bits = tree e) : - Extends m (store m e bits correct) := by - intro key value h - simp only [store, Std.HashMap.getElem?_insert, beq_iff_eq] - split - · rename_i same - subst key - rw [m.sound _ _ h, correct] - · exact h - -structure Result (before : Memo) (e : Expr) where - bits : Bits - correct : bits = tree e - memo : Memo - grows : Extends before memo - cached : memo.entries[ExactKey.ExprKey.mk e]? = some bits - -private def finish (before : Memo) (e : Expr) (work : Memo) (more : Extends before work) - (bits : Bits) (correct : bits = tree e) : Result before e := - ⟨bits, correct, store work e bits correct, more.trans (store_extends work e bits correct), by simp [store]⟩ - -/-- The memo probe precedes every recursive descent. Proof fields erase; a -constant's terms share one memo and the exported wrapper discards it. -/ -def scan (m : Memo) (e : Expr) : Result m e := - match hit : m.entries[ExactKey.ExprKey.mk e]? with - | some bits => ⟨bits, m.sound _ _ hit, m, .refl m, hit⟩ - | none => match e with - | .app f a h => - let x := scan m f - let y := scan x.memo a - finish m (.app f a h) y.memo (x.grows.trans y.grows) (join x.bits y.bits) - (by rw [x.correct, y.correct]; rfl) - | .lam n t b bi h => - let x := scan m t - let y := scan x.memo b - finish m (.lam n t b bi h) y.memo (x.grows.trans y.grows) (join x.bits y.bits) - (by rw [x.correct, y.correct]; rfl) - | .forallE n t b bi h => - let x := scan m t - let y := scan x.memo b - finish m (.forallE n t b bi h) y.memo (x.grows.trans y.grows) (join x.bits y.bits) - (by rw [x.correct, y.correct]; rfl) - | .letE n t v b nd h => - let x := scan m t - let y := scan x.memo v - let z := scan y.memo b - finish m (.letE n t v b nd h) z.memo (x.grows.trans (y.grows.trans z.grows)) - (join (join x.bits y.bits) z.bits) - (by rw [x.correct, y.correct, z.correct]; rfl) - | .mdata d b h => - let x := scan m b - finish m (.mdata d b h) x.memo x.grows x.bits - (by simpa only [tree, hasStrLit, hasNatLit] using x.correct) - | .proj n i b h => - let x := scan m b - finish m (.proj n i b h) x.memo x.grows x.bits - (by simpa only [tree, hasStrLit, hasNatLit] using x.correct) - | .lit (.strVal s) h => finish m (.lit (.strVal s) h) m (.refl m) (true, false) rfl - | .lit (.natVal n) h => finish m (.lit (.natVal n) h) m (.refl m) (false, true) rfl - | .bvar i h => finish m (.bvar i h) m (.refl m) (false, false) rfl - | .fvar n h => finish m (.fvar n h) m (.refl m) (false, false) rfl - | .mvar n h => finish m (.mvar n h) m (.refl m) (false, false) rfl - | .sort u h => finish m (.sort u h) m (.refl m) (false, false) rfl - | .const n us h => finish m (.const n us h) m (.refl m) (false, false) rfl -termination_by structural e - -theorem scan_correct (m : Memo) (e : Expr) : (scan m e).bits = tree e := (scan m e).correct -theorem scan_extends (m : Memo) (e : Expr) : Extends m (scan m e).memo := (scan m e).grows -theorem scan_cached (m : Memo) (e : Expr) : (scan m e).memo.entries[ExactKey.ExprKey.mk e]? = some (scan m e).bits := - (scan m e).cached -theorem scan_sound (m : Memo) (e : Expr) (key : ExactKey.ExprKey) (bits : Bits) - (hit : (scan m e).memo.entries[key]? = some bits) : bits = tree key.expr := - (scan m e).memo.sound key bits hit - -/-- A hit returns unchanged storage before any child is queried. -/ -theorem scan_hit (m : Memo) (e : Expr) (bits : Bits) (hit : m.entries[ExactKey.ExprKey.mk e]? = some bits) : - (scan m e).bits = bits ∧ (scan m e).memo = m := by - have result : scan m e = ⟨bits, m.sound _ _ hit, m, .refl m, hit⟩ := by - rw [scan] - split - · rename_i value found - have same : value = bits := Option.some.inj (found.symm.trans hit) - subst value - rfl - · rename_i found - cases found.symm.trans hit - rw [result] - exact ⟨rfl, rfl⟩ - -structure TermsResult (before : Memo) (es : List Expr) where - bits : Bits - correct : bits = (es.any hasStrLit, es.any hasNatLit) - memo : Memo - grows : Extends before memo - -def scanTerms (m : Memo) : (es : List Expr) → TermsResult m es - | [] => ⟨(false, false), rfl, m, .refl m⟩ - | e :: es => - let x := scan m e - let y := scanTerms x.memo es - ⟨join x.bits y.bits, by rw [x.correct, y.correct]; rfl, - y.memo, x.grows.trans y.grows⟩ - -theorem scanTerms_correct (m : Memo) (es : List Expr) : - (scanTerms m es).bits = (es.any hasStrLit, es.any hasNatLit) := (scanTerms m es).correct -theorem scanTerms_extends (m : Memo) (es : List Expr) : Extends m (scanTerms m es).memo := (scanTerms m es).grows - -end Literal - -def literalUseShared (ci : ConstantInfo) : LiteralUse := - let bits := (Literal.scanTerms Literal.empty (terms ci)).bits - if bits.1 then .string else if bits.2 then .nat else .none - -theorem literalUseShared_eq (ci : ConstantInfo) : literalUseShared ci = literalUse ci := by - simp only [literalUseShared, (Literal.scanTerms Literal.empty (terms ci)).correct, literalUse] - -def captureUsingShared (ci : ConstantInfo) (direct : Ix.Set Name) : Node := - { direct, literals := literalUseShared ci, grouping := match ci with - | .inductInfo v => .induct v.all - | .recInfo v => .recursor v.all - | _ => .plain } - -theorem captureUsingShared_eq (ci : ConstantInfo) (direct : Ix.Set Name) : - captureUsingShared ci direct = captureUsing ci direct := by - simp only [captureUsingShared, literalUseShared_eq, captureUsing] - cases ci <;> rfl - -def rowUsingShared (n : Name) (ci : ConstantInfo) (direct : Ix.Set Name) : Row := - ⟨n, captureUsingShared ci direct, keep ci⟩ - -theorem rowUsingShared_eq (n : Name) (ci : ConstantInfo) (direct : Ix.Set Name) : - rowUsingShared n ci direct = rowUsing n ci direct := by - simp only [rowUsingShared, rowUsing, captureUsingShared_eq] - -end Ix.Compile.SourceGraph diff --git a/Ix/Compile/SourceGraph/Prepass.lean b/Ix/Compile/SourceGraph/Prepass.lean deleted file mode 100644 index 7837d0968..000000000 --- a/Ix/Compile/SourceGraph/Prepass.lean +++ /dev/null @@ -1,42 +0,0 @@ -module -public import Ix.Compile.SourceGraph.Stream - -@[expose] public section -namespace Ix.Compile.SourceGraph.Prepass - -/-! The existing three-product name prepass, as an owned pure fold. These -exact placeholders serve only the ground check, never logical admission. -The actual CompileDriver call remains a separately reviewed integration. -/ - -def placeholderHeader : ConstantVal := ⟨.mkAnon, #[], Expr.mkSort Level.mkZero⟩ -def phAxiom : ConstantInfo := .axiomInfo { cnst := placeholderHeader, isUnsafe := false } -def phCtor : ConstantInfo := .ctorInfo - { cnst := placeholderHeader, induct := .mkAnon, cidx := 0, - numParams := 0, numFields := 0, isUnsafe := false } -def placeholder : Lean.ConstantInfo → ConstantInfo - | .ctorInfo _ => phCtor - | _ => phAxiom - -structure Names where - leanByIx : Map Name (Lean.Name × Lean.ConstantInfo) := {} - thinConsts : Map Name ConstantInfo := {} - labelled : Array (Name × Lean.ConstantInfo) := #[] - -def addNamed (state : Names) (ixn : Name) (source : Lean.Name × Lean.ConstantInfo) : Names := - let ⟨leanByIx, thinConsts, labelled⟩ := state - ⟨leanByIx.insert ixn source, thinConsts.insert ixn (placeholder source.2), - labelled.push (ixn, source.2)⟩ - -def nameStep (state : Names) (source : Lean.Name × Lean.ConstantInfo) : Names := - addNamed state (Id.run ((CanonM.canonName source.1).run {})).1 source - -def run (input : Array (Lean.Name × Lean.ConstantInfo)) : Names := input.foldl nameStep {} - -/-- Names and original Lean records are prepared once; complete canonical -bodies remain transient in Stream's chunk-local producer. -/ -def produce (input : Array (Lean.Name × Lean.ConstantInfo)) - (workers : Nat) : Names × Stream.Output := - let p := run input - (p, Stream.runLabelled { consts := p.thinConsts } p.labelled workers) - -end Ix.Compile.SourceGraph.Prepass diff --git a/Ix/Compile/SourceGraph/Stream.lean b/Ix/Compile/SourceGraph/Stream.lean deleted file mode 100644 index c2bd6f759..000000000 --- a/Ix/Compile/SourceGraph/Stream.lean +++ /dev/null @@ -1,74 +0,0 @@ -module -public import Ix.CanonM -public import Ix.Compile.SourceGraph.Literal -public import Ix.Ground - -@[expose] public section -namespace Ix.Compile.SourceGraph.Stream - -/-! Pure prospective streamed input producers. A record is summarized while -its canonical form is live; CanonState is local to one chunk. Existing -ground-error collection policy is preserved. No live -CompileDriver caller is changed by this module. -/ - -structure Item where - graph : Row - immediate : Option GroundError - digest : UInt64 - -def inspect (thin : Environment) (n : Name) (ci : ConstantInfo) : Item := - let refs := (GraphM.run { consts := {} } .init (graphConst ci)).1 - let immediate := match groundConstCheck ci thin with | .ok _ => none | .error e => some e - ⟨rowUsingShared n ci refs, immediate, hash ci⟩ - -def step (thin : Environment) (p : Name × Lean.ConstantInfo) : CanonM.CanonM Item := do - let ci ← CanonM.canonConst p.2 - return inspect thin p.1 ci - -/-- Summarize each record immediately; never construct an array of complete -canonical theorem/opaque records before summarizing it. -/ -def chunk (thin : Environment) (input : Array (Name × Lean.ConstantInfo)) : Array Item := - Id.run ((input.mapM (m := CanonM.CanonM) (step thin)).run' {}) - -def chunkSize (size workers : Nat) : Nat := max 1 ((size + workers - 1) / workers) - -/-- Explicit consecutive slices; the max-1 rule covers workers = 0 too. -/ -def partition (input : Array α) (workers : Nat) : Array (Array α) := - let size := chunkSize input.size workers - (Array.range ((input.size + size - 1) / size)).map fun j => input.extract (j * size) ((j + 1) * size) - -def tasks (thin : Environment) (input : Array (Name × Lean.ConstantInfo)) (workers : Nat) : Array (Task (Array Item)) := - (partition input workers).map fun part => Task.spawn fun _ => chunk thin part - -structure Output where - facts : Collected := {} - outRefs : Map Name (Ix.Set Name) := {} - immediate : Map Name GroundError := {} - digests : Map Name UInt64 := {} - -/-- Graph/code use the coherent winner rule. Immediate errors still insert -only on error, exactly as the live collector. -/ -def merge (acc : Output) (item : Item) : Output := - let ⟨facts, outRefs, immediate, digests⟩ := acc - let n := item.graph.name - let outRefs := outRefs.insert n item.graph.node.direct - let immediate := match item.immediate with | none => immediate | some e => immediate.insert n e - let digests := digests.insert n item.digest - ⟨mergeRow facts item.graph, outRefs, immediate, digests⟩ - -/-- Join in task-array order, then row order; no flattened runtime copy. -/ -def join (pending : Array (Task (Array Item))) : Output := - pending.foldl (fun acc task => task.get.foldl merge acc) {} - -def runLabelled (thin : Environment) (input : Array (Name × Lean.ConstantInfo)) (workers : Nat) : Output := - join (tasks thin input workers) - -/-- The existing per-name conversion, from an empty name memo. The full -leanByIx/thin-map prepass is a separate integration obligation. -/ -def label (input : Array (Lean.Name × Lean.ConstantInfo)) : Array (Name × Lean.ConstantInfo) := - input.map fun (n, ci) => ((Id.run ((CanonM.canonName n).run {})).1, ci) - -def runSource (thin : Environment) (input : Array (Lean.Name × Lean.ConstantInfo)) (workers : Nat) : Output := - runLabelled thin (label input) workers - -end Ix.Compile.SourceGraph.Stream diff --git a/Ix/Compile/SourceInput.lean b/Ix/Compile/SourceInput.lean deleted file mode 100644 index 3e20931ca..000000000 --- a/Ix/Compile/SourceInput.lean +++ /dev/null @@ -1,26 +0,0 @@ -module -public import Ix.Compile.SourceGraph.Prepass - -@[expose] public section -namespace Ix.Compile.SourceInput - -abbrev Raw := Lean.Name × Lean.ConstantInfo - -/-- Only the original winner map is retained. The prepass's thin environment, -label array and transient canonical bodies are not captured here. -/ -structure Carrier where - byName : Ix.Map Ix.Name Raw - -/-- One original declaration, canonicalized alone (the singleton-canon -fallback). This lookup does not consult the transformed compile environment. -/ -def canonical? (p : Raw) : Option Ix.ConstantInfo := - ((Ix.CanonM.canonChunk #[p])[0]?).map (·.2) - -/-- A name's original declaration, canonicalized alone (`canonical?`): the -driver's fallback lookup for constants it does not keep. -/ -def Carrier.lookup? (s : Carrier) (name : Ix.Name) : Option Ix.ConstantInfo := - (s.byName.get? name).bind canonical? - -end Ix.Compile.SourceInput - -end diff --git a/Ix/CompileCertified.lean b/Ix/CompileCertified.lean index 6635a0f4d..bc5256cff 100644 --- a/Ix/CompileCertified.lean +++ b/Ix/CompileCertified.lean @@ -3,17 +3,18 @@ public import Ix.Cert.Compile public import Ix.Compile.ReadyCompile public import Ix.Resource.Validate public import Ix.RoleBinding.Compile +public import Ix.Cli.Common public section /-! # The certified compile of Lean source -`ix compile` and `ix compile-lean` compile with `Ix.Cert.compileReport`, the -function `Ix.Cert.CompileCorrect` is about, on the canonical environment of -their input: +`ix compile`, `ix compile-lean`, `ix compile-export` and `ix validate-lean` +compile with `Ix.Cert.compileReport`, the function `Ix.Cert.CompileCorrect` is +about, on the canonical environment of their input (`compileAndFinalize`): -1. **Source contracts.** The selected constants are prepared as the streamed - pipeline prepares them (`prepareCertified`). A list with no contract +1. **Source contracts.** The selected constants are prepared + (`prepareCertified`). A list with no contract registration and no contract metadata in either reserved namespace (a scan on tasks, `Ix.Compile.checkOrdinaryConstants`, which also checks that each constant is listed once, under its own name) is used as it is. Any other @@ -22,9 +23,10 @@ their input: (`Ix.CompileM.checkPreparedSource`: no binder contract left unresolved, every semantic contract inspects). 2. **Canonicalization** (`canonInput`): `Ix.CanonM.canonChunk` on consecutive - slices, on tasks. A chunk's value is `(nameS, constS)` of each of its - constants (`Ix.Cert.Canon.canonChunkCorrect`), and the slices are joined in - order, so the environment does not depend on the worker count. + slices (`Ix.CanonM.chunks`), on tasks. A chunk's value is `(nameS, constS)` + of each of its constants (`Ix.Cert.Canon.canonChunkCorrect`), and the + slices are joined in order, so the environment does not depend on the + worker count. 3. **The compile**: `Ix.Cert.compileReport`, with its checks, at the default sharing limits, which it fixes. Execution runs through the `Ix.Compile.ReadyCompile.reportIO` subtype bridge @@ -34,21 +36,15 @@ their input: hypothesis of `Ix.Cert.checkReport_sound` and `Ix.Cert.CompileCorrect` directly from the parallel ready-queue execution without re-running sequential `compileEnv`. -4. **Accounting**: every failure it reports is an input constant's. With - `compileReport`'s coverage check, every input constant is then published or - reported as failed, and never both (the streamed pipeline's source - accounting, `Ix.Compile.SourceGraph.Accounting`). +4. **Accounting**: every failure it reports is an input constant's + (`failureOutside`). With `compileReport`'s coverage check, every input + constant is then published or reported as failed, and never both. 5. **Resources**: an input with semantic contracts compiles completely, and its - artifact validates (`Ix.Resource.validate`), as the streamed pipeline's - resource post-step requires (`Ix.CompileM.resourcePostStep`). + artifact validates (`Ix.Resource.validate`), before `finalize` and after it + (`validateFinalized`). The artifact's role support and certificates are compiled the same way (`certifiedBackend`), by `Ix.RoleBinding.Compile.finalize`. - -The streamed pipeline (`Ix.CompileM.compileLeanInputValidated`) stays available -as an uncertified option of both commands: only its fold is proved to be -`compileReport`'s (`Ix.Cert.Hybrid`), and its streamed front end is tested -against it. -/ namespace Ix.CompileM @@ -85,7 +81,8 @@ private def dropAsync (α : Type) (x : α) : IO (Task (Except IO.Error Unit)) := list, canonicalized on `workers` tasks over consecutive slices, joined in order. -/ def canonInput (consts : Array (Lean.Name × Lean.ConstantInfo)) (workers : Nat) : Ix.Environment := - let tasks := (Ix.Compile.SourceGraph.Stream.partition consts workers).map fun part => + let size := max 1 ((consts.size + workers - 1) / workers) + let tasks := (Ix.CanonM.chunks consts size).map fun part => Task.spawn fun _ => Ix.CanonM.canonChunk part { consts := tasks.foldl (init := Std.HashMap.emptyWithCapacity consts.size) fun m t => t.get.foldl (fun m (n, c) => m.insert n c) m } @@ -118,7 +115,7 @@ def unboundPublishedRole (ixe : Ixon.Env) /-- The certified compile's result. -/ structure CertifiedOut where - /-- The prepared input, which `--rust-check` compiles too. -/ + /-- The prepared input. -/ prepared : List (Lean.Name × Lean.ConstantInfo) /-- The input carries semantic contracts. -/ annotated : Bool @@ -134,9 +131,20 @@ structure CertifiedOut where /-- **The certified compile of a prepared list**: its canonical environment (`canonInput`), then `Ix.Cert.compileReport`, then the accounting and, for an input with semantic contracts, the resource checks. `log` receives each -phase's time. -/ +phase's time. + +With `defaultRoles`, every published role name must sit at the default role +table's address of its role (`unboundPublishedRole`): the input is ix's own +toolchain's, whose role constants the default table binds. An export of +another toolchain is compiled without it. Its role constants reach that +toolchain's `Init`, so they sit at other addresses (4.35.0's `Decidable` has +one constructor, so its `Int.decLt` does). `finalize` binds them in a table +generated from the export, and `checkFinalized` checks every published role +name against that table (`Ix.Cert.PublishedRolesBound` at the artifact's own +binding). -/ def compileCertifiedPrepared (prepared : List (Lean.Name × Lean.ConstantInfo)) - (annotated : Bool) (workers : Nat := 32) (log : String → IO Unit := fun _ => pure ()) : + (annotated : Bool) (workers : Nat := 32) (log : String → IO Unit := fun _ => pure ()) + (defaultRoles : Bool := true) : IO (Except String CertifiedOut) := do let t0 ← IO.monoMsNow let env ← IO.lazyPure fun _ => canonInput prepared.toArray workers @@ -157,9 +165,10 @@ def compileCertifiedPrepared (prepared : List (Lean.Name × Lean.ConstantInfo)) let existing ← match Ix.RoleBinding.sourceAddresses ixe with | .ok m => pure m | .error e => return .error e - if let some (n, r, addr) := unboundPublishedRole ixe (some existing) then - return .error s!"role check failed: the published role {n.pretty} ({repr r}) is at {addr}, \ - not its bound address in the default role table" + if defaultRoles then + if let some (n, r, addr) := unboundPublishedRole ixe (some existing) then + return .error s!"role check failed: the published role {n.pretty} ({repr r}) is at {addr}, \ + not its bound address in the default role table" if annotated then unless failures.isEmpty do return .error "resource compilation requires the complete requested closure" @@ -168,9 +177,10 @@ def compileCertifiedPrepared (prepared : List (Lean.Name × Lean.ConstantInfo)) return .ok { prepared, annotated, inputs, env := ixe, failures, existing? := some existing } /-- **The certified compile** of the constants `constants` of `source`: -`prepareCertified`, then `compileCertifiedPrepared`. -/ +`prepareCertified`, then `compileCertifiedPrepared` (with `defaultRoles`). -/ def compileCertified (source : Lean.Environment) (constants : List (Lean.Name × Lean.ConstantInfo)) - (workers : Nat := 32) (log : String → IO Unit := fun _ => pure ()) : + (workers : Nat := 32) (log : String → IO Unit := fun _ => pure ()) + (defaultRoles : Bool := true) : IO (Except String CertifiedOut) := do let t0 ← IO.monoMsNow let (prepared, annotated) ← match prepareCertified source constants with @@ -178,29 +188,114 @@ def compileCertified (source : Lean.Environment) (constants : List (Lean.Name × | .error e => return .error e log s!"source contracts ({if annotated then "semantic contracts" else "none"}): \ {(← IO.monoMsNow) - t0}ms" - compileCertifiedPrepared prepared annotated workers log + compileCertifiedPrepared prepared annotated workers log defaultRoles /-- An input with semantic contracts is emitted only if its finalized artifact -validates too, as the streamed pipeline checks it after `finalize`. -/ +validates too: `compileCertifiedPrepared` validates the compiled artifact, this +the finalized one. -/ def validateFinalized (annotated : Bool) (artifact : Ixon.Env) : Except String Unit := do if annotated then let _ ← Ix.Resource.validate artifact (Ix.Resource.standardProfile artifact) +/-- Up to three recorded failures, root causes first (`Ix.Cli.rootCausesFirst`). -/ +def recordedFailures (failures : Std.HashMap Ix.Name String) : String := + "; ".intercalate ((Ix.Cli.rootCausesFirst failures.toList).take 3 |>.map fun (n, e) => + s!"{n.pretty}: {(e.replace "\n" " ").take 200}") + +/-- Compile `constList` with the certified Lean compiler and return its `CompilePhases`. -/ +def compilePhasesOf (leanEnv : Lean.Environment) + (constList : List (Lean.Name × Lean.ConstantInfo)) + (workers : Nat := 8) : IO CompilePhases := do + let certOut ← IO.ofExcept (← compileCertified leanEnv constList workers) + unless certOut.failures.isEmpty do + let fMap := certOut.failures.toMap + throw (IO.userError s!"compilePhasesOf: {fMap.size} constant(s) failed to compile: {recordedFailures fMap}") + let rawEnv := canonInput certOut.prepared.toArray workers + let condensed := Ix.Cert.blocksOf rawEnv + return { rawEnv, condensed, compileEnv := certOut.env } + end Ix.CompileM namespace Ix.RoleBinding.Compile /-- The certified compiler as `finalize`'s backend: the role support and the certificates are compiled with `Ix.Cert.compileReport`, and must compile -completely. -/ -def certifiedBackend (workers : Nat) : Backend := fun _ input => do +completely. `defaultRoles` as in `Ix.CompileM.compileCertifiedPrepared`. -/ +def certifiedBackend (workers : Nat) (defaultRoles : Bool := true) : Backend := fun _ input => do let prepared ← IO.ofExcept input.prepare let annotated ← IO.ofExcept (Ix.CompileM.checkPreparedSource prepared) - let out ← IO.ofExcept (← Ix.CompileM.compileCertifiedPrepared prepared annotated workers) + let out ← IO.ofExcept + (← Ix.CompileM.compileCertifiedPrepared prepared annotated workers (defaultRoles := defaultRoles)) unless out.failures.isEmpty do throw (IO.userError "artifact support: incomplete certified compilation") return out.env end Ix.RoleBinding.Compile +namespace Ix.CompileM + +/-- What `compileAndFinalize` made of a certified compile. -/ +inductive Finalization where + /-- Not finalized: a constant failed to compile, and a partial artifact was + not allowed. -/ + | refused + /-- `finalize`, `validateFinalized` or `Ixon.serEnv` failed. -/ + | failed (error : String) + /-- The finalized artifact, after `strip`, and its serialization. -/ + | done (artifact : Ixon.Env) (bytes : ByteArray) + +/-- **The certified compile, finalized**, as the compile commands run it: +`compileCertified`; then, unless a constant failed and `allowPartial` is false, +the role support, certificates and binding (`Ix.RoleBinding.Compile.finalize` +with `certifiedBackend`, the certificate theorems from `certSource`), +`validateFinalized`, `strip` and `Ixon.serEnv`. The outer error is the +compile's refusal. `log` receives each phase's time and the resident set size. +`defaultRoles` is off for another toolchain's export, as in +`compileCertifiedPrepared`; the finalized artifact's published role names are +then checked against its own binding (`checkFinalized`). + +With `prefetch`, every role's certificate compile starts first, beside the +source compile (`Ix.RoleBinding.Compile.prefetchCertificates`): a compile of a +whole toolchain's library publishes every role, so `finalize` needs exactly +those certificates. The prefetch is always joined before returning, also on a +refusal or a failure, or when `finalize` needed other certificates: a task +still running when `main` returns would wait for subtasks that the runtime's +shutdown no longer runs. -/ +def compileAndFinalize (source : Lean.Environment) + (constants : List (Lean.Name × Lean.ConstantInfo)) (workers : Nat) (allowPartial : Bool) + (log : String → IO Unit := fun _ => pure ()) + (certSource : IO Lean.Environment := Ix.RoleBinding.Compile.nativeCertSource) + (strip : Ixon.Env → Ixon.Env := id) (defaultRoles : Bool := true) + (prefetch : Bool := false) : + IO (Except String (CertifiedOut × Finalization)) := do + let backend := Ix.RoleBinding.Compile.certifiedBackend workers defaultRoles + let certificates? ← if prefetch then + some <$> Ix.RoleBinding.Compile.prefetchCertificates backend certSource + else pure none + let run : IO (Except String (CertifiedOut × Finalization)) := do + let out ← match ← compileCertified source constants workers log defaultRoles with + | .ok out => pure out + | .error e => return .error e + log s!"rss after the compile: {← Ix.Cli.residentSetSize}" + if !allowPartial && !out.failures.isEmpty then return .ok (out, .refused) + try + let t0 ← IO.monoMsNow + let artifact ← Ix.RoleBinding.Compile.finalize backend source certSource + (.plain constants) out.env Ix.Resource.typingLimits (existing? := out.existing?) + (prefetched? := certificates?) + IO.ofExcept (validateFinalized out.annotated artifact) + let t1 ← IO.monoMsNow + log s!"finalize: {t1 - t0}ms, rss {← Ix.Cli.residentSetSize}" + let artifact := strip artifact + let bytes ← IO.ofExcept (Ixon.serEnv artifact) + log s!"serialize ({bytes.size} bytes): {(← IO.monoMsNow) - t1}ms" + return .ok (out, .done artifact bytes) + catch e => return .ok (out, .failed e.toString) + try run + finally + if let some (_, task) := certificates? then + let _ ← IO.wait task + +end Ix.CompileM + end diff --git a/Ix/CompileDriver.lean b/Ix/CompileDriver.lean index 729e8154d..71cc08a47 100644 --- a/Ix/CompileDriver.lean +++ b/Ix/CompileDriver.lean @@ -1,24 +1,3 @@ -/- - Ix.CompileDriver: the compile-env driver. - - Port of the production scheduler semantics of - `crates/compile/src/compile/env.rs` (`compile_env_with_options`) and the - per-block dispatch of `compile_const` (compile.rs), on top of the pure - block compiler (`Ix.CompileM`). Lean's declarations compile as given: each - block of the condensation is compiled once, in Lean's order, and Lean's own - auxiliaries (`rec`, `casesOn`, `below`, …) are blocks of their own. - - Deliberate deviations from Rust, none output-visible: - - One schedule, a pure fold over waves of ready blocks applied in - block-key order (`compileEnvFold`), instead of work-stealing threads. - Output maps are keyed by name/address. Name claims (`nameToAddr`) are - insert-once: a second claim at a different address is the error Rust's - insert-once claims raise (`checkBlockClaims`, single ownership). - `Named` entries are last-wins overrides within one block's - registrations, and constants and blobs are content-keyed. So - scheduling order does not affect the result (Rust relies on the same - property for its nondeterministic work-stealing order). --/ module import Std.Sync public import Ix.Common @@ -35,9 +14,25 @@ import Std.Data.HashSet.Lemmas public import Ix.IndFlags public import Ix.Compile.Publication public import Ix.Compile.SourceContract.Transport -public import Ix.Compile.SourceGraph.Prepass -public import Ix.Compile.SourceInput public import Ix.Resource.Addressed + +/-! + Ix.CompileDriver: the compile-env driver. + + The scheduler and the per-block dispatch, on top of the pure block + compiler (`Ix.CompileM`). Lean's declarations compile as given: each + block of the condensation is compiled once, in Lean's order, and Lean's own + auxiliaries (`rec`, `casesOn`, `below`, …) are blocks of their own. + + One schedule, a pure fold over waves of ready blocks applied in block-key + order (`compileEnvFold`). Output maps are keyed by name/address. Name + claims (`nameToAddr`) are insert-once: a second claim at a different + address is an error (`checkBlockClaims`, single ownership). `Named` + entries are last-wins overrides within one block's registrations, and + constants and blobs are content-keyed. So scheduling order does not + affect the result. +-/ + public section namespace Ix.CompileM @@ -588,8 +583,8 @@ def applyAuxBlockOutcome (acc : DriverAcc) (lo : Name) (all : Set Name) /-! ## The wave fold: the one driver -`compileEnvFold` is the driver of both the pure compiler (`Ix.Cert.compile`) -and the IO pipeline (`compileDecoratedConsts`): the same function, so the +`compileEnvFold` is the driver of the pure compiler (`Ix.Cert.compile`) +and the parallel schedule: the same function, so the parallel schedule equals the pure one by definition. **Waves.** A wave is the set of blocks not yet compiled whose scheduling @@ -871,94 +866,7 @@ def compileEnvFold (env : Ix.Environment) (blocks : Ix.CondensedBlocks) let (ixe, bytes, cenv) ← finalizeEnv st.acc return (ixe, bytes, { cenv with env }) -/-- The first name, in `Ix.nameCompare` order, whose published address - differs from a reference's (the wave driver's fail-fast `rustRef` check, - now made after the fold). -/ -def rustRefMismatch? (rust : Std.HashMap Name Address) (cenv : CompileEnv) : Option String := - let bad := cenv.nameToNamed.fold (init := (#[] : Array (Name × Address × Address))) - fun acc n named => - match rust.get? n with - | some r => if named.addr != r then acc.push (n, named.addr, r) else acc - | none => acc - match (bad.qsort fun a b => Ix.nameCompare a.1 b.1 == .lt)[0]? with - | some (n, l, r) => some s!"rustRef mismatch at {n.pretty}: lean={l} rust={r}" - | none => none - -instance : Inhabited AuxBlockOutcome where - default := .failed "uninitialized" - -/-- The options of the Lean compile drivers. The drivers read no environment - variable and no `/proc` file: the CLI parses these (`ix compile-lean`: - `--sharing-limits` and `IX_SHARING_LIMITS`, - `IX_COMPILE_DBG`) and passes them in. -/ -structure DriverOptions where - /-- Resource limits of the canonical sharing construction. -/ - sharingLimits : Ix.Sharing.Exact.Limits := compilerSharingLimits - /-- Phase timings, and the fold's per-wave trace. -/ - dbg : Bool := false - /-- A memory reading for the trace after the compile phase, supplied by the - caller (the CLI reads the process's resident set size); `none` prints - none. -/ - memory : Option (IO String) := none - -/-! ## The full pure-Lean pipeline - -`Lean canon → ref graph → ground filter → condense → the wave fold → -serialize` — the Lean-side mirror of Rust -`compile_env_with_options`' setup phases (env.rs:146-260) feeding the -scheduler. This is what `ix compile-lean` and `ix validate-lean` -phase 1 run. - -The driver applies Rust's env-level domain restriction -(`validate_ind_groups`) first: an input inductive group whose stored flags -differ from the recomputed ones (`validateInductiveFlags`) fails the compile -before any block is scheduled. -/ - -/-- Result of the full pure-Lean compile pipeline. -/ -structure LeanPipelineOut where - /-- Canonical serialized environment (`Ixon.serEnv`). -/ - bytes : ByteArray - /-- The assembled environment. -/ - env : Ixon.Env - /-- Final driver state (per-block failures live in `cenv.ungrounded`). -/ - cenv : CompileEnv - /-- Constants rejected by the pre-compile groundedness scan. -/ - ungroundedCount : Nat - /-- SCC block count. -/ - blockCount : Nat - /-- Per-name content digest of every INPUT constant's canonicalized - form (derived `Hashable`), collected during the streamed canon - pass. The whole-env canon map is never materialized; this is the - phase-5 decompile oracle for `ix validate-lean`. -/ - digests : Std.HashMap Ix.Name UInt64 := {} - /-- The input carries semantic contracts: its output is emitted only after - the resource post-step (`Ix.CompileM.resourcePostStep`). -/ - annotated : Bool := false - /-- The condensation the driver compiled (the declaration groups and their - references). -/ - blocks : Ix.CondensedBlocks := default - /-- Complete original-input dependency facts, before grounding; no acceptance - verdict. `none` is unavailable, while `some {}` is an empty input. -/ - sourceNodes? : Option (Ix.Map Name Ix.Compile.SourceGraph.Node) := none - /-- Original prepared-input winners, shared with the lazy compiler fallback. - Production always supplies this; manually fabricated outputs may omit it. -/ - originalSource? : Option Ix.Compile.SourceInput.Carrier := none - -/-- Run the full pure-Lean compile pipeline over a constant list. - - STREAMING: the whole-env canonicalized map is never materialized. - The canon pass canonicalizes each constant TRANSIENTLY — extracting - its reference set, immediate groundedness error, and content digest - — then drops the canon form. The compile phase reads constants - through `Environment.fallback?` (canon-on-demand against the pinned - Lean constants), so a constant's canon form is live only while some - block actually reads it: theorem-sized proof bodies exist once - during the canon pass and once during their own block's compile, - never all at once. Canon is per-constant deterministic (chunking is - already arbitrary), so compiled output is byte-identical to the - materialized pipeline. - - See the section docstring; phase timings print when `dbg` is set. -/ +/-- Inspect semantic contracts on a source declaration before canonicalization. -/ def inspectSemanticSource (source : Lean.ConstantInfo) : Except String Unit := do if !Ix.Compile.sourceHasSemanticContracts source then return let (canonical, _) := (Ix.CanonM.canonConst source).run {} @@ -982,162 +890,6 @@ def checkPreparedSource (consts : List (Lean.Name × Lean.ConstantInfo)) : Excep inspectSemanticSource source return consts.any fun (_, source) => Ix.Compile.sourceHasSemanticContracts source -def compileDecoratedConsts (consts : List (Lean.Name × Lean.ConstantInfo)) - (rustRef : Option (Std.HashMap Name Address) := none) - (numWorkers : Nat := 32) (opts : DriverOptions := {}) - : IO (Except String LeanPipelineOut) := do - let annotated ← match checkPreparedSource consts with - | .ok annotated => pure annotated - | .error error => return .error error - let dbg := opts.dbg - let tick (label : String) (t0 : Nat) : IO Nat := do - let t1 ← IO.monoMsNow - if dbg then - IO.println s!" [compile-lean] {label}: {t1 - t0}ms" - pure t1 - let t0 ← IO.monoMsNow - let constArr := consts.toArray - -- 1a. Name pre-pass: canonical names only. Builds the lazy-lookup key - -- map (Ix name → pinned Lean constant), and the THIN ground-check env — ground - -- checks read only name-existence and is-it-a-ctor - -- (`groundExpr`/`groundConst`), so two shared placeholder - -- constants stand in for every value. - let ⟨leanByIx, thinConsts, ixArr⟩ ← IO.lazyPure fun _ => - Ix.Compile.SourceGraph.Prepass.run constArr - let t ← tick s!"canon names ({ixArr.size} consts)" t0 - -- 1b/2/3a. Streamed canon (chunk-parallel): canonicalize, take refs + - -- immediate ground error + digest. PROOF bodies (`thmInfo` / - -- `opaqueInfo` — the bulk of a Mathlib-scale env, and never read - -- by dependents' compiles) are dropped after extraction and - -- re-canonicalized on demand for their own block only. CODE kinds - -- (definitions, inductive families, recursors — what the flags check and - -- kernel ingress read from dependents, repeatedly and with - -- retention) are KEPT and materialized as the shared `consts` - -- map: one shared structure instead of a fresh unshared copy per - -- on-demand read. - -- Destructure immediately: fallback retains only leanByIx; temporary thin, - -- label and task storage must not escape through a captured aggregate. - let ⟨⟨sourceNodes, codeConsts⟩, outRefs, immediate, digests⟩ ← - IO.lazyPure fun _ => Ix.Compile.SourceGraph.Stream.runLabelled - { consts := thinConsts } ixArr numWorkers - let t ← tick s!"canon+graph+ground ({outRefs.size} nodes, \ -{codeConsts.size} code kept, {outRefs.size - codeConsts.size} proofs streamed)" t - let mut inRefs : Ix.Map Ix.Name (Ix.Set Ix.Name) := {} - for (n, refs) in outRefs do - for r in refs do - inRefs := inRefs.alter r fun - | some s => some (s.insert n) - | none => some (({} : Ix.Set Ix.Name).insert n) - let ungrounded := Ix.proliferateUngrounded immediate inRefs - let groundedOutRefs := - if ungrounded.isEmpty then outRefs - else outRefs.fold (init := {}) fun m name refs => - if ungrounded.contains name then m - else m.insert name (refs.fold (init := {}) fun s r => - if ungrounded.contains r then s else s.insert r) - if dbg && !ungrounded.isEmpty then - IO.println s!" [compile-lean] {ungrounded.size} ungrounded constants filtered" - for (n, e) in ungrounded.toList.take 5 do - IO.println s!" ungrounded: {n.pretty} ({repr e.kind})" - let t ← tick s!"ground ({ungrounded.size} ungrounded)" t - -- 4. Condense (Tarjan SCCs over the filtered graph). - let condensed := Ix.CondenseM.run groundedOutRefs - let t ← tick s!"condense ({condensed.blocks.size} blocks)" t - -- 5. The driver (`compileEnvFold`, the same function as `Ix.Cert.compile`'s) - -- against the HYBRID environment: code kinds are the materialized - -- (shared) map; proof bodies canonicalize on demand from the pinned - -- Lean constants and are dropped when their block's `CompileM.run` - -- returns. Each wave is split into `4 * numWorkers` tasks (`width`; the - -- value does not depend on it): chunks that small let the task pool - -- (`LEAN_NUM_THREADS`, else one thread per core) balance blocks of very - -- different sizes, where `numWorkers` chunks left the wave waiting on its - -- slowest chunk. At most `min width pool` blocks compile at once. - let originalSource : Ix.Compile.SourceInput.Carrier := ⟨leanByIx⟩ - let fallback : Ix.Name → Option Ix.ConstantInfo := originalSource.lookup? - let ixEnv : Ix.Environment := - { consts := codeConsts, fallback? := some fallback } - if dbg then - IO.println s!" [compile-lean] {condensed.blocks.size} blocks, {4 * numWorkers} tasks per wave" - let folded ← IO.lazyPure fun _ => - compileEnvFold ixEnv condensed (dbg := dbg) - (sharingLimits := opts.sharingLimits) (width := 4 * numWorkers) - match folded with - | .error e => return .error e - | .ok (ixonEnv, _, cenv) => - let t ← tick "compile" t - if dbg then - if let some read := opts.memory then - IO.println s!" [compile-lean] rss after the compile phase: {← read}" - if let some rust := rustRef then - if let some msg := rustRefMismatch? rust cenv then return .error msg - -- The constants the groundedness scan rejected are failures of the - -- compile, each with its named refusal (`GroundError.refusal`), as the - -- Rust compiler records them: an input constant is never dropped - -- without a record (`ix compile-lean` is fail-closed on them). - let cenv := ungrounded.fold (init := cenv) fun c n e => - if c.ungrounded.contains n then c - else { c with ungrounded := c.ungrounded.insert n (e.refusal ungrounded.contains) } - if dbg && cenv.ungrounded.size > 0 then - IO.println s!" [compile-lean] {cenv.ungrounded.size} per-block compile failures" - for (n, e) in cenv.ungrounded.toList.take 5 do - IO.println s!" failed: {n.pretty}: {e.take 200}" - -- 6. Serialize. Resource validation is a post-step of the caller's - -- (`Ix.CompileM.resourcePostStep`, `Ix.CompileResource`): semantic - -- emission is atomic only through it, so an annotated input's output - -- is emitted only after it (`annotated`). - match Ixon.serEnv ixonEnv with - | .error e => return .error s!"serEnv failed: {e}" - | .ok bytes => - let _ ← tick s!"serialize ({bytes.size} bytes)" t - return .ok { - bytes - env := ixonEnv - cenv - ungroundedCount := ungrounded.size - blockCount := condensed.blocks.size - digests - annotated - blocks := condensed - sourceNodes? := some sourceNodes - originalSource? := some originalSource } - -/-- Explicit frontend boundary: resolve occurrence contracts before -canonicalization. Resources are validated by the caller's post-step -(`Ix.CompileM.compileLeanInputValidated`). -/ -def compileLeanInput (input : Ix.Compile.CompileInput) - (rustRef : Option (Std.HashMap Name Address) := none) - (numWorkers : Nat := 32) (opts : DriverOptions := {}) : - IO (Except String LeanPipelineOut) := do - let constants ← match input.prepare with - | .ok constants => pure constants - | .error error => return .error error - compileDecoratedConsts constants rustRef numWorkers opts - -/-- Compile an isolated source list, extracting its checked occurrence records. -/ -def compileLeanConsts (consts : List (Lean.Name × Lean.ConstantInfo)) - (rustRef : Option (Std.HashMap Name Address) := none) - (numWorkers : Nat := 32) (opts : DriverOptions := {}) : - IO (Except String LeanPipelineOut) := do - let input ← match Ix.Compile.CompileInput.fromAnnotations consts with - | .ok input => pure input - | .error error => return .error (toString error) - compileLeanInput input rustRef numWorkers opts - -/-- Native compiler entrypoint with an explicitly committed resource profile. -/ -@[extern "rs_compile_env_to_ixon_profile"] -opaque rsCompileEnvProfileFFI : - @& List (Lean.Name × Lean.ConstantInfo) → @& ByteArray → IO Ixon.RawEnv - -def rsCompileInput (input : Ix.Compile.CompileInput) - (resourceProfile : Option Ix.Resource.Profile := none) : IO Ixon.Env := do - let constants ← IO.ofExcept input.prepare - let raw ← match resourceProfile with - | none => rsCompileEnvFFI constants - | some profile => - let _ ← IO.ofExcept profile.address - rsCompileEnvProfileFFI constants profile.bytes - return raw.toEnv - end Ix.CompileM diff --git a/Ix/CompileM.lean b/Ix/CompileM.lean index 8c3b8e6b8..514fde605 100644 --- a/Ix/CompileM.lean +++ b/Ix/CompileM.lean @@ -1,17 +1,3 @@ -/- - Pure per-block compilation from Lean declarations to Ixon. - - Architecture: - - CompileEnv: shared compilation snapshot (Reader) - - BlockEnv: per-block read-only context (Reader) - - BlockState: per-block writes and caches (State) - - The certified default uses these operations. CompileDriver schedules - independent blocks on tasks and merges their outcomes in key order; - Ix.Cert.compileReport checks the assembled result. The Rust compiler - remains available through --rust and for differential checks. --/ - module import Std.Sync public import Ix.Ixon @@ -30,6 +16,19 @@ public import Ix.SOrder public import Ix.Compile.ExactKey public import Ix.CanonM +/-! + Pure per-block compilation from Lean declarations to Ixon. + + Architecture: + - CompileEnv: shared compilation snapshot (Reader) + - BlockEnv: per-block read-only context (Reader) + - BlockState: per-block writes and caches (State) + + The certified compiler uses these operations. CompileDriver schedules + independent blocks on tasks and merges their outcomes in key order; + Ix.Cert.compileReport checks the assembled result. +-/ + namespace Ix.CompileM public section @@ -68,9 +67,7 @@ structure CompileEnv where blocks : Std.HashMap Name (Array (Array Name)) := {} /-- Resource limits of the canonical sharing construction (`buildBlockConstant`, `finishMutualCompilation`): `compilerSharingLimits` - unless a driver's options set others (`DriverOptions.sharingLimits`, - which the CLI parses from `IX_SHARING_LIMITS`). Limits decide only whether a block - compiles, never its bytes. -/ + by default. Limits decide only whether a block compiles, never its bytes. -/ sharingLimits : Ix.Sharing.Exact.Limits := {} /-- Initialize global state from canonicalization result. -/ @@ -221,14 +218,13 @@ structure BlockEnv where /-! ## Compilation Error -/ -/-- Compilation error type. Variant order matches Rust CompileError (tags 0–7). -/ +/-- Compilation error type. -/ inductive CompileError where | missingConstant (name : String) | missingAddress (addr : Address) | invalidMutualBlock (reason : String) | unsupportedExpr (desc : String) | unknownUnivParam (curr param : String) - | serializeError (err : Ixon.SerializeError) | resourceLimit (reason : String) /-- The sharing construction failed (other than by resource exhaustion, which is `resourceLimit`). -/ @@ -242,7 +238,6 @@ instance : ToString CompileError where | .invalidMutualBlock reason => s!"invalidMutualBlock: {reason}" | .unsupportedExpr desc => s!"unsupportedExpr: {desc}" | .unknownUnivParam curr param => s!"unknownUnivParam: compiling {curr}, param {param}" - | .serializeError err => s!"serializeError: {err}" | .resourceLimit reason => s!"resourceLimit: {reason}" | .sharingConstruction reason => s!"sharingConstruction: {reason}" @@ -957,16 +952,15 @@ def compileConstExprRaw (name : Name) (lvls : Array Level) : /-! ## Table Preseeding -Mirrors Rust `preseed_expr_tables` (crates/compile/src/compile.rs:576): -before compiling a block, walk every expression the block will compile, +Before compiling a block, walk every expression the block will compile, collect all external refs (consts, nat/str literal blobs, proj type addresses) and all universes, then intern them into the block tables in CANONICAL SORTED order — refs by address bytes, univs by their serialized -encoding (`univ_sort_key`, compile.rs:476). Table indices thereby become +encoding. Table indices thereby become traversal-order-independent; the on-the-fly interning during actual compilation then always finds the preseeded entries. Without this pass, ref/univ indices depend on compile traversal order and every nontrivial -constant's serialized form (hence address) diverges from Rust's. -/ +constant's serialized form (hence address) would depend on it. -/ /-- Blake3 key over a univ-param context: hash of the concatenated 32-byte name hashes. Mirrors Rust `univ_params_key` (compile.rs:482). -/ @@ -1461,41 +1455,22 @@ def constantInfoRootExprs : Ixon.ConstantInfo → Array Ixon.Expr /-! ## Canonical sharing The compiler builds every block Constant with the canonical sharing -construction `Ix.Sharing.Exact.canonicalSharingTiered .tagN` (Rust -`canonical_sharing_tiered`), under the limits of `CompileEnv.sharingLimits`. +construction `Ix.Sharing.Exact.canonicalSharingTiered .tagN`, under the +limits of `CompileEnv.sharingLimits`. There is no fallback: every construction failure is a compile error. -/ /-- Resource limits of the compiler's canonical sharing construction: the library defaults (`Ix.Sharing.Exact.Limits`), a safety net far above every corpus maximum. `CompileEnv.sharingLimits` carries the limits a compile - runs under. Mirrors Rust `compiler_sharing_limits`. -/ + runs under. -/ def compilerSharingLimits : Ix.Sharing.Exact.Limits := {} -/-- Environment variable carrying a sharing-limit override in the format of - `Ix.Sharing.Exact.Limits.withOverrides` (for example `states=2^44,depth=2^22` - or `unbounded`). `ix compile --sharing-limits` and `ix compile-lean - --sharing-limits` set it. `compilerSharingLimitsFromEnv` reads it, once per - run: the CLI (`Ix.Cli.driverOptionsFromEnv`), which passes the limits to - the Lean compile drivers and to `Ix.DecompileM.decompileEnvFullParallel`; - no driver reads it. Rust's `compiler_sharing_limits` reads it once per - compile or decompile entry point. -/ -def sharingLimitsEnvVar : String := "IX_SHARING_LIMITS" - -/-- `compilerSharingLimits` with the `IX_SHARING_LIMITS` override applied, or - the parse error. -/ -def compilerSharingLimitsFromEnv : IO (Except String Ix.Sharing.Exact.Limits) := do - match ← IO.getEnv sharingLimitsEnvVar with - | none => pure (.ok compilerSharingLimits) - | some spec => - pure ((compilerSharingLimits.withOverrides spec).mapError (s!"{sharingLimitsEnvVar}: " ++ ·)) - /-- A sharing-construction failure as a compile error: resource exhaustion is - `resourceLimit` (naming the limit and its override), every other kind - `sharingConstruction`. Mirrors Rust `sharing_compile_error`. -/ + `resourceLimit` (naming the limit), every other kind + `sharingConstruction`. -/ def sharingCompileError : Ix.Sharing.Exact.SharingError → CompileError | .resourceExhausted r limit => - .resourceLimit s!"canonical sharing: resource exhausted: {r.key} (limit {limit}); \ - raise it with --sharing-limits {r.key}=N ({sharingLimitsEnvVar})" + .resourceLimit s!"canonical sharing: resource exhausted: {r.key} (limit {limit})" | e => .sharingConstruction s!"canonical sharing: {e}" /-- Write `rewrittenExprs` back into the root slots of `info`, in @@ -2464,13 +2439,9 @@ def compileConstantInfo : ConstantInfo → CompileM BlockResult | .ctorInfo constructorVal => compileConstructorConstantInfo constructorVal | const => compileConstantInfoCore const -/-! ## Rust Compilation FFI -/ +/-! ## Compile Status and Phases -/ -/-- Structured result of `rs_compile_env`. Field kinds/order must match - the `LeanIxCompileEnvStatus` FFI layout in `crates/ffi/src/lean.rs` - (`{ num_obj: 3, num_64: 3 }`): boxed fields first (`root`, `ungrounded`, - `nonCanonical`), then the UInt64 scalars (`bytes`, `named`, `uniqueAnon`) - in declaration order. -/ +/-- Structured result of environment compilation. -/ structure CompileEnvStatus where /-- 64-hex canonical consts merkle root. Equals the `.ixe` header root when a file was written; still computed on a fail-closed abort. -/ @@ -2478,7 +2449,7 @@ structure CompileEnvStatus where /-- `(pretty name, reason)` for every requested constant whose block failed to compile, sorted by name. Empty ⇔ complete environment. -/ ungrounded : Array (String × String) - /-- `(pretty name, cause)` rows the Rust compiler once reported for blocks + /-- `(pretty name, cause)` rows the compiler once reported for blocks it rewrote. Always empty: the compiler compiles every block as given. -/ nonCanonical : Array (String × String) /-- Bytes written to `outPath` (0 when nothing was written). -/ @@ -2489,135 +2460,13 @@ structure CompileEnvStatus where uniqueAnon : UInt64 deriving Repr, Inhabited -/-- FFI: Compile a Lean environment and write the serialized Ixon.Env - bytes straight to `outPath` from Rust (streamed; no env-sized - ByteArray crosses the FFI). Writes to `.tmp` then renames, - so a crash cannot leave a truncated file. - - Fail-closed semantics live behind the FFI: with - `allowPartial := false`, an env with any ungrounded requested - constant writes NOTHING (the final path is never created) and the - returned status carries the full ungrounded list; with - `allowPartial := true`, the grounded subset is serialized and the - status discloses what was omitted. -/ -@[extern "rs_compile_env"] -opaque rsCompileEnvBytesFFI - : @& List (Lean.Name × Lean.ConstantInfo) → @& String → Bool - → IO CompileEnvStatus - -/-- `rsCompileEnvBytesFFI` with strict-anon output (`ix compile - --anon`): §4 names, §5 metadata, and §6 commitments are cleared — - strictly after `finalize_hints`, which derives §3 from `env.named` - — so the piece carries the anon layer only (§1–§3). The env root - is identical either way (it covers §2 keys only); the status - reports `named = 0`. -/ -@[extern "rs_compile_env_anon"] -opaque rsCompileEnvBytesAnonFFI - : @& List (Lean.Name × Lean.ConstantInfo) → @& String → Bool - → IO CompileEnvStatus - -/-- Compile a Lean environment and write the serialized Ixon.Env bytes - to `outPath` using the Rust compiler. Fail-closed by default: any - ungrounded constant throws (and nothing is written) unless - `allowPartial := true`. Returns the structured compile status. -/ -def rsCompileEnvBytes (leanEnv : Lean.Environment) (outPath : String) - (allowPartial : Bool := false) : IO CompileEnvStatus := do - let constList ← IO.ofExcept <| Ix.Compile.prepareRegisteredConstants leanEnv leanEnv.constants.toList - let status ← rsCompileEnvBytesFFI constList outPath allowPartial - if !allowPartial && !status.ungrounded.isEmpty then - throw <| IO.userError <| - s!"rsCompileEnvBytes: {status.ungrounded.size} requested constant(s) " ++ - s!"failed to compile; nothing written to {outPath}. First failure: " ++ - match status.ungrounded[0]? with - | some (n, r) => s!"{n}: {r}" - | none => "" - return status - --- Re-export RawEnv types from Ixon for backwards compatibility -export Ixon (RawConst RawNamed RawBlob RawComm RawEnv) - -/-- FFI: Compile a Lean environment to RawEnv (structured Lean objects) using Rust. -/ -@[extern "rs_compile_env_to_ixon"] -opaque rsCompileEnvFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO Ixon.RawEnv - -/-- FFI: Compute the LEON content hash of every constant in a Lean - environment. Returns `(Ix.Name, Ix.Address)` pairs where the address - is the 32-byte Blake3 digest produced by `ConstantInfo::get_hash()` - in `crates/common/src/env.rs`. This is the addressing scheme under which - `orig_kenv` stores KIds in the kernel — two constants with the same - Lean name but different content get distinct addresses. Used by - `Tests.Ix.Kernel.BuildPrimOrigs` to regenerate `PrimAddrs::new_orig` - in the Rust kernel. -/ -@[extern "rs_leon_hashes"] -opaque rsLeonHashesFFI - : @& List (Lean.Name × Lean.ConstantInfo) → IO (Array (Ix.Name × Address)) - -/-! ## Combined Compile Phases FFI -/ - -/-- Raw FFI type returned from Rust's rs_compile_phases. - Contains all compilation phases in array-based format for FFI compatibility. -/ -structure RustCompilePhases where - rawEnv : Ix.RawEnvironment -- Array-based canonicalized constants - condensed : RustCondensedBlocks -- Array-based SCC data - compileEnv : RawEnv -- Ixon raw type (RawConst, RawNamed, etc.) - deriving Inhabited, Repr - -/-- Nice Lean type with proper data structures. - Converted from RustCompilePhases for ergonomic use in Lean. -/ +/-- Bundled compilation phases (`rawEnv`, `condensed`, `compileEnv`) for + downstream incremental compilation and testing. -/ structure CompilePhases where rawEnv : Ix.Environment -- HashMap-based canonicalized constants condensed : CondensedBlocks -- Map/Set-based SCC data compileEnv : Ixon.Env -- HashMap-based Ixon environment -/-- FFI: Run all compilation phases in Rust and return structured data. -/ -@[extern "rs_compile_phases"] -opaque rsCompilePhasesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO RustCompilePhases - -/-- Run all compilation phases in Rust over an explicit constant list - and convert to Lean-friendly types. Use this for closure-scoped - compiles (e.g. `#ixeval` compiles only a term's reference closure); - `rsCompilePhases` covers the whole-environment case. -/ -def rsCompilePhasesOf (constList : List (Lean.Name × Lean.ConstantInfo)) : - IO CompilePhases := do - let constList ← IO.ofExcept <| Ix.Compile.prepareSourceConstants constList - let raw ← rsCompilePhasesFFI constList - - -- Convert RawEnvironment to Environment - let rawEnv := raw.rawEnv.toEnvironment - - -- Convert RustCondensedBlocks to CondensedBlocks - let condensed := raw.condensed.toCondensedBlocks - - -- Convert RawEnv to Ixon.Env - let compileEnv := raw.compileEnv.toEnv - - pure { rawEnv, condensed, compileEnv } - -/-- Run all compilation phases using Rust and convert to Lean-friendly types. - This is the main entry point for getting Rust compilation results. -/ -def rsCompilePhases (leanEnv : Lean.Environment) : IO CompilePhases := do - let constants ← IO.ofExcept <| Ix.Compile.prepareRegisteredConstants leanEnv leanEnv.constants.toList - let raw ← rsCompilePhasesFFI constants - return { rawEnv := raw.rawEnv.toEnvironment - condensed := raw.condensed.toCondensedBlocks - compileEnv := raw.compileEnv.toEnv } - -/-- Compile an explicit constant list to Ixon.Env using the Rust - compiler. Use for compiles over constructed environments (e.g. the - catalog `--audit` per-library comparison). -/ -def rsCompileEnvOf (constList : List (Lean.Name × Lean.ConstantInfo)) : - IO Ixon.Env := do - let constList ← IO.ofExcept <| Ix.Compile.prepareSourceConstants constList - let rawEnv ← rsCompileEnvFFI constList - pure rawEnv.toEnv - -/-- Compile a Lean environment to Ixon.Env using the Rust compiler. - Uses the direct FFI that returns structured Lean objects. -/ -def rsCompileEnv (leanEnv : Lean.Environment) : IO Ixon.Env := do - let constants ← IO.ofExcept <| Ix.Compile.prepareRegisteredConstants leanEnv leanEnv.constants.toList - let raw ← rsCompileEnvFFI constants - return raw.toEnv - end end Ix.CompileM diff --git a/Ix/CompileResource.lean b/Ix/CompileResource.lean deleted file mode 100644 index d7676aa55..000000000 --- a/Ix/CompileResource.lean +++ /dev/null @@ -1,144 +0,0 @@ -module -public import Ix.CompileDriver -public import Ix.Compile.SourceGraph.Accounting -public import Ix.Resource.Validate -public import Ix.Cert.Compile -public section - -/-! -# The Lean pipeline's validated post-steps - -`Ix.CompileM.compileLeanInput` compiles and serializes; it does not validate -resources, so the compiler does not import `Ix.Resource.Validate` (and with it -the validator's typing half, `Ix.Resource.typeCheck`). An input that carries semantic contracts, or -a compile against a resource profile, is emitted only after this post-step: -the requested closure must be complete (no recorded failure), and the -compiled environment must validate (`Ix.Resource.validate`, against the given -profile or the environment's standard one). Every result then checks original -input publication/failure accounting from the compiler's retained facts, and -the compile's report as the pure compiler checks its own (`reportPostStep`). -`ix compile-lean`, `ix validate-lean` and the Lean backend of artifact support -use the validated wrappers before emission. Bare compileLeanInput/compileLeanConsts -remain unvalidated. -This post-step does not authenticate caller-supplied Node maps. --/ - -namespace Ix.CompileM - -/-- Validate resources when required, then original-input accounting for -every result. Existing resource errors retain precedence. -/ -def resourcePostStep (out : LeanPipelineOut) - (resourceProfile : Option Ix.Resource.Profile := none) : Except String Unit := do - if out.annotated || resourceProfile.isSome then - if !out.cenv.ungrounded.isEmpty then - throw "resource compilation requires the complete requested closure" - let _ ← Ix.Resource.validate out.env - (resourceProfile.getD (Ix.Resource.standardProfile out.env)) - let some nodes := out.sourceNodes? - | throw "source accounting: original input facts are unavailable" - (Ix.Compile.SourceGraph.Accounting.check nodes out.env.named out.cenv.ungrounded).mapError - Ix.Compile.SourceGraph.Accounting.Issue.message - -/-- An input constant's header: a theorem's or opaque's value replaced by -`Sort 0`, any other constant as it is. -/ -def headerRaw : Ix.Compile.SourceInput.Raw → Ix.Compile.SourceInput.Raw - | (n, .thmInfo v) => (n, .thmInfo { v with value := .sort .zero }) - | (n, .opaqueInfo v) => (n, .opaqueInfo { v with value := .sort .zero }) - | p => p - -/-- **The input the report checks read**: every original input constant (the -retained source facts' names), canonical, as the pure compiler reads its input, -except that a theorem or opaque is read as its header (`headerRaw`): its value -is not canonicalized. A code constant is the materialized one, read through the -compile's lazy lookup; so is every constant when the output retains no original -input (`LeanPipelineOut.originalSource?`). - -The headers leave the verdict unchanged: `Ix.Cert.checkReport` reads its input -environment only through its keys (`uncovered`, `nameHashCollision`, -`unresolvedPublished`, `unreadablePublished`), a constant's kind and own name -(`misnamedInput`, `unhintedDefinition`), an inductive's constructors -and a constructor's inductive (`strayConstructor`), and an inductive family's -shape: the names it lists, constructor indices, level-parameter names, `all` lists, -nested counts and recursor rules' constructor names (`unstoredFamilyName`, -`misplacedConstructor`, `strayLevelParams`, -`strayFamilyMember`, `recursorShapeError`), and never a type or a value. Reading the -values would hold every proof body of the input at once. The family check reads the -condensation the driver compiled (`LeanPipelineOut.blocks`), not one recomputed from -the headers. -/ -def reportInput (out : LeanPipelineOut) : Except String Ix.Environment := do - let some nodes := out.sourceNodes? - | throw "report check: original input facts are unavailable" - let read (n : Ix.Name) : Option Ix.ConstantInfo := - match out.originalSource?.bind (·.byName.get? n) with - | some raw => - match raw.2 with - | .thmInfo _ | .opaqueInfo _ => Ix.Compile.SourceInput.canonical? (headerRaw raw) - | _ => out.cenv.env.get? n - | none => out.cenv.env.get? n - let consts ← nodes.foldM (init := ({} : Std.HashMap Ix.Name Ix.ConstantInfo)) fun m n _ => - match read n with - | some ci => pure (m.insert n ci) - | none => throw s!"report check: the input constant {n.pretty} could not be read" - pure { consts } - -/-- Up to three recorded failures, root causes first: a failure that only names a -missing constant is a dependent's, listed after the ones that name a cause. -/ -def recordedFailures (failures : Std.HashMap Ix.Name String) : String := - let cascade (e : String) := e.startsWith "missingConstant" || e.startsWith "missing constant" - let all := failures.toList - let ordered := all.filter (!cascade ·.2) ++ all.filter (cascade ·.2) - "; ".intercalate (ordered.take 3 |>.map fun (n, e) => s!"{n.pretty}: {(e.replace "\n" " ").take 200}") - -/-- **The compile's report, checked as the pure compiler checks its own** -(`Ix.Cert.checkReport`, the checks behind `Ix.Cert.compileReport`): on the -whole input (`reportInput`), the driver's final environment and the artifact with -every recorded failure (the ground filter's refusals are among the driver's). A -refusal names the compile's recorded failures too, root causes first: a check can -fail because of a block that failed to compile. -Coverage, authentic storage, an acyclic reference order, distinct name hashes, -blocks that agree with their `Muts` entries, published input names at readable -records (`Ix.Cert.unreadablePublished`), and the resolution and naming invariants -hold of the emitted artifact, and the input's inductives have Lean's shapes over the -condensation the driver compiled (`out.blocks`), or the compile is refused. -/ -def reportPostStep (out : LeanPipelineOut) : Except String Unit := do - let env ← reportInput out - (Ix.Cert.checkReport env out.cenv (out.env, { blocks := out.cenv.ungrounded }) - out.blocks).mapError fun e => - match recordedFailures out.cenv.ungrounded with - | "" => e - | causes => s!"{e}; the compile recorded {out.cenv.ungrounded.size} failure(s), first {causes}" - -/-- `compileLeanInput` followed by the post-steps: an output that fails one is -an error, never a result to emit. -/ -def compileLeanInputValidated (input : Ix.Compile.CompileInput) - (rustRef : Option (Std.HashMap Name Address) := none) - (numWorkers : Nat := 32) (opts : DriverOptions := {}) - (resourceProfile : Option Ix.Resource.Profile := none) : - IO (Except String LeanPipelineOut) := do - match ← compileLeanInput input rustRef numWorkers opts with - | .error e => return .error e - | .ok out => - let t0 ← IO.monoMsNow - let checked ← IO.lazyPure fun _ => - resourcePostStep out resourceProfile >>= fun _ => reportPostStep out - if opts.dbg then - IO.println s!" [compile-lean] post-steps (resources, accounting, report checks): \ -{(← IO.monoMsNow) - t0}ms" - if let some read := opts.memory then - IO.println s!" [compile-lean] rss after the post-steps: {← read}" - return checked.map fun _ => out - -/-- `compileLeanConsts` followed by the post-steps. -/ -def compileLeanConstsValidated (consts : List (Lean.Name × Lean.ConstantInfo)) - (rustRef : Option (Std.HashMap Name Address) := none) - (numWorkers : Nat := 32) (opts : DriverOptions := {}) - (resourceProfile : Option Ix.Resource.Profile := none) : - IO (Except String LeanPipelineOut) := do - match ← compileLeanConsts consts rustRef numWorkers opts with - | .error e => return .error e - | .ok out => - return (resourcePostStep out resourceProfile >>= fun _ => reportPostStep out).map fun _ => out - -end Ix.CompileM - -end diff --git a/Ix/CondenseM.lean b/Ix/CondenseM.lean index 726811bc7..200601923 100644 --- a/Ix/CondenseM.lean +++ b/Ix/CondenseM.lean @@ -1,5 +1,10 @@ module -/- +public import Ix.Common +public import Ix.Environment +public import Ix.Compile.Canon.Graph +import Ix.CompileCert.Canon.SccRange + +/-! # CondenseM: strongly connected components of the reference graph Condenses a dependency graph of `Ix.Name`s into its strongly connected @@ -12,24 +17,14 @@ module - `blocks`: maps each SCC representative to all members - `blockRefs`: maps each SCC representative to its external references - **Presentation, kept byte for byte.** The graph is presented in the - iteration order of the reference map (roots) and of each reference set - (successors), as the former `partial` Tarjan visited it, and the three maps - are filled in the order that implementation filled them (members by - discovery number through a `UInt64`-keyed map, whose iteration order - depends only on its keys and the order they were first inserted). So the - representative of a block (the Tarjan root, its first-discovered member), - the maps' contents, their iteration orders and every member set's - iteration order are what the former implementation produced. The - partition does not depend on the presentation; the representatives and - the iteration orders do. + **Presentation.** The graph is presented in the iteration order of the + reference map (roots) and of each reference set (successors), as the former + `partial` Tarjan visited it, so each component's representative (the Tarjan + root, its first-discovered member) and the three maps' contents match what + the former implementation produced. Each component's member set and + `lowLinks` entries are populated directly from `c.comps.zip c.roots`. -/ -public import Ix.Common -public import Ix.Environment -public import Ix.Compile.Canon.Graph -import Ix.CompileCert.Canon.SccRange - public section namespace Ix @@ -70,59 +65,31 @@ def CondenseM.run (refs : Map Ix.Name (Set Ix.Name)) let c := (Ix.Compile.Canon.condensation adj).get (Ix.CompileCert.Canon.condensation_isSome adj) have hc : Ix.Compile.Canon.condensation adj = some c := (Option.some_get _).symm - -- Each node's representative: the name of its component's root. - let mut rootName : Vector Ix.Name names.size := names.toVector + -- Each component's representative and member set. + let mut blocks : Map Ix.Name (Set Ix.Name) := {} for h : p in c.comps.zip c.roots do have hr : p.2 < names.size := by rw [hsize] exact Ix.CompileCert.Canon.condensation_roots_lt hc (Array.of_mem_zip (a := p.1) (b := p.2) h).2 let rn := names[p.2] + let mut s : Set Ix.Name := {rn} for v in p.1 do - rootName := rootName.setIfInBounds v rn - -- The nodes in the former `lowLink` map's iteration order: discovery - -- numbers `0, 1, …` inserted in that order into a `UInt64`-keyed map. - let mut byDiscovery : Std.HashMap UInt64 (Ix.Name × Ix.Name) := {} - let mut d : Nat := 0 - for h : v in c.order do - have hv : v < names.size := by - rw [hsize]; exact Ix.CompileCert.Canon.condensation_order_lt hc h - byDiscovery := byDiscovery.insert d.toUInt64 (names[v], rootName[v]) - d := d + 1 - let mut blocks : Map Ix.Name (Set Ix.Name) := {} + s := if hv : v < names.size then s.insert names[v] else s + blocks := blocks.insert rn s let mut lowLinks : Map Ix.Name Ix.Name := {} - for (_, (name, lowName)) in byDiscovery do - lowLinks := lowLinks.insert name lowName - blocks := blocks.alter lowName fun x => match x with - | .some s => .some (s.insert name) - | .none => .some {name} + for (rn, s) in blocks do + for nm in s do + lowLinks := lowLinks.insert nm rn let mut blockRefs : Map Ix.Name (Set Ix.Name) := {} for (lo, all) in blocks do let mut rs : Set Ix.Name := {} for a in all do -- every member is a key of `refs` rs := rs.union (refs.getD a {}) - rs := rs.filter (!all.contains ·) - blockRefs := blockRefs.insert lo rs + blockRefs := blockRefs.insert lo (rs.filter (!all.contains ·)) return ⟨lowLinks, blocks, blockRefs⟩ -/-- Rust's CondensedBlocks structure (mirroring Rust's output format). - Used for FFI round-tripping with array-based representation. -/ -structure RustCondensedBlocks where - lowLinks : Array (Ix.Name × Ix.Name) - blocks : Array (Ix.Name × Array Ix.Name) - blockRefs : Array (Ix.Name × Array Ix.Name) - deriving Inhabited, Nonempty, Repr - -/-- Convert Rust's array-based format to Lean's map-based CondensedBlocks. -/ -def RustCondensedBlocks.toCondensedBlocks (rust : RustCondensedBlocks) : CondensedBlocks := - let lowLinks := rust.lowLinks.foldl (init := {}) fun m (k, v) => m.insert k v - let blocks := rust.blocks.foldl (init := {}) fun m (k, v) => - m.insert k (v.foldl (init := {}) fun s n => s.insert n) - let blockRefs := rust.blockRefs.foldl (init := {}) fun m (k, v) => - m.insert k (v.foldl (init := {}) fun s n => s.insert n) - { lowLinks, blocks, blockRefs } - end Ix end diff --git a/Ix/DecompileDriver.lean b/Ix/DecompileDriver.lean index 3d2e6e927..08c86915a 100644 --- a/Ix/DecompileDriver.lean +++ b/Ix/DecompileDriver.lean @@ -1,4 +1,10 @@ -/- +module +public import Ix.DecompileM +public import Ix.IndFlags +public import Ix.CanonM +public import Ix.GraphM + +/-! Ix.DecompileDriver: kernel-free decompilation of stored source records. The stored source declarations are selected (original addresses, metadata @@ -6,9 +12,7 @@ recomputed from the decoded declarations. No auxiliary is regenerated or recompiled. -/ -module -public import Ix.DecompileM -public import Ix.IndFlags + public section namespace Ix.DecompileM @@ -17,13 +21,12 @@ namespace Ix.DecompileM does not store `numNested`/`isRec`/`isReflexive` (they are derivable), so `decompileInductive` stubs them; this pass recomputes them per original mutual group FROM THE DECOMPILED CONSTANTS - (`Ix.IndFlags.computeLeanIndFlags`), mirroring Rust `decompile_env`, which - calls the same `compute_lean_ind_flags`. + (`Ix.IndFlags.computeLeanIndFlags`). Groups key on `all[0]` (every member carries the same `all`, so first-seen insertion order is irrelevant). The flags computation runs in `CompileM` over a synthetic `CompileEnv` whose source env IS the - decompiled map — the same self-referential view Rust passes. -/ + decompiled map. -/ def fixupInductiveFlags (decompiled : Std.HashMap Ix.Name Ix.ConstantInfo) : Except String (Std.HashMap Ix.Name Ix.ConstantInfo) := Id.run do let mut groups : Std.HashMap Ix.Name (Array Ix.Name) := {} @@ -83,4 +86,72 @@ def decompileEnvFullParallel (ixonEnv : Ixon.Env) let numWorkers := ((← IO.getEnv "IX_DECOMPILE_WORKERS").bind (·.toNat?)).getD numWorkers return decompileOriginals ixonEnv (max 1 numWorkers) +/-- Select the source view of a decompiled environment (`import_ixe`). + `only` empty selects every name; otherwise the requested names, which must + be present in `decompiled`, and their reference closure. The reference + closure is checked for a full selection too, so a broken source + reconstruction is refused: every reference must be a present name. -/ +def materializedSourceNames (decompiled : Std.HashMap Ix.Name Ix.ConstantInfo) + (only : Array Lean.Name := #[]) : Except String (Array Ix.Name) := do + let roots : Array Ix.Name := + if only.isEmpty then decompiled.toArray.map (·.1) + else only.map Ix.Name.fromLeanName + let mut seen : Ix.Set Ix.Name := {} + let mut worklist : Array Ix.Name := #[] + for root in roots do + unless decompiled.contains root do + throw s!"requested source constant '{root.pretty}' not found" + unless seen.contains root do + seen := seen.insert root + worklist := worklist.push root + while !worklist.isEmpty do + let name := worklist.back! + worklist := worklist.pop + let some decl := decompiled.get? name + | throw s!"source constant '{name.pretty}' disappeared during selection" + let (refs, _) := Ix.GraphM.run { consts := {} } .init (Ix.graphConst decl) + for ref in refs do + unless seen.contains ref do + unless decompiled.contains ref do + throw s!"source constant '{name.pretty}' references missing source constant '{ref.pretty}' — broken closure" + seen := seen.insert ref + worklist := worklist.push ref + return seen.toArray + +/-- Read, verify, and decompile a `.ixe` from disk in pure Lean, returning the + selected source constants as `Lean.ConstantInfo` pairs sorted by name. + Empty `only` returns every decompiled constant; nonempty returns the + requested source constants plus their reference closure. Fails on anonymous + envs, thin bundles, broken source closures, and absent requested names. -/ +def decompileEnvConsts (path : String) (only : Array Lean.Name := #[]) + : IO (Array (Lean.Name × Lean.ConstantInfo)) := do + let bytes ← IO.FS.readBinFile path + let parts ← match Ixon.deEnvVerifiedLazy bytes with + | .ok p => pure p + | .error e => throw <| IO.userError s!"decompile {path}: {e}" + if parts.namedRows.isEmpty then + throw <| IO.userError + s!"decompile {path}: anonymous env (empty named section); decompile needs names" + if !parts.env.assumptions.isEmpty then + throw <| IO.userError + s!"decompile {path}: thin bundle ({parts.env.assumptions.size} assumptions); decompile needs a self-contained env" + let ixonEnv ← match parts.materializeAll with + | .ok env => pure env + | .error e => throw <| IO.userError s!"decompile {path}: {e}" + let (decompiled, errors, _) ← decompileEnvFullParallel ixonEnv + if !errors.isEmpty then + let (firstName, firstErr) := errors[0]! + throw <| IO.userError s!"decompile {path}: {firstName.pretty}: {firstErr}" + let selected ← match materializedSourceNames decompiled only with + | .ok names => pure names + | .error e => throw <| IO.userError s!"decompile {path}: {e}" + let uncanoned : Array (Lean.Name × Lean.ConstantInfo) := + ((selected.mapM fun name => do + let some info := decompiled.get? name + | unreachable! + let name' ← Ix.CanonM.uncanonName name + let info' ← Ix.CanonM.uncanonConst info + pure (name', info')).run {}).1 + return uncanoned.qsort (fun a b => Lean.Name.lt a.1 b.1) + end Ix.DecompileM diff --git a/Ix/DecompileM.lean b/Ix/DecompileM.lean index 14640373e..109d1ccd6 100644 --- a/Ix/DecompileM.lean +++ b/Ix/DecompileM.lean @@ -37,7 +37,7 @@ def resolveIxName (names : Std.HashMap Address Ix.Name) (addr : Address) : Optio /-! ## Error Type -/ -/-- Decompilation error type. Variant order matches Rust DecompileError (tags 0–11). -/ +/-- Decompilation error type. -/ inductive DecompileError where | invalidRefIndex (idx : UInt64) (refsLen : Nat) (constant : String) | invalidUnivIndex (idx : UInt64) (univsLen : Nat) (constant : String) @@ -49,7 +49,6 @@ inductive DecompileError where | blobNotFound (addr : Address) | badBlobFormat (addr : Address) (expected : String) | badConstantFormat (msg : String) - | serializeError (err : Ixon.SerializeError) /-- `share idx` occurring in `metaSharing[entry]` outside that entry's index space (`ConstantMeta.metaSharing`): with `primaryLen` primary and `metaLen` metadata entries, the valid indices are @@ -70,7 +69,6 @@ def DecompileError.toString : DecompileError → String | .blobNotFound addr => s!"Blob not found at: {addr}" | .badBlobFormat addr expected => s!"Bad blob format at {addr}, expected {expected}" | .badConstantFormat msg => s!"Bad constant format: {msg}" - | .serializeError err => s!"Serialization error: {err}" | .invalidMetaShareIndex idx entry p q c => if idx.toNat ≥ p + q then s!"Invalid metadata share index {idx} in metaSharing[{entry}] of '{c}': \ diff --git a/Ix/Environment.lean b/Ix/Environment.lean index 6ce5319d8..3b1119f59 100644 --- a/Ix/Environment.lean +++ b/Ix/Environment.lean @@ -1,9 +1,3 @@ -/- - Canonical Lean types with embedded content-addressed hashes. - - Ix types mirror Lean's core types but include a Blake3 hash at each node, - enabling O(1) equality checks and content-addressed storage. --/ module public import Blake3.Rust @@ -11,6 +5,13 @@ public import Std.Data.HashMap public import Std.Data.TreeMap public import Ix.Address +/-! + Canonical Lean types with embedded content-addressed hashes. + + Ix types mirror Lean's core types but include a Blake3 hash at each node, + enabling O(1) equality checks and content-addressed storage. +-/ + public section namespace Ix @@ -134,8 +135,7 @@ end Name /-- Synthetic name for a mutual block's `Muts` named entry: `Ix..` (numeric vs string components preserved). Kernel ingress discovers mutual blocks only - through entries under these names. Mirrors Rust `Address::muts_name` - (crates/common/src/address.rs:99). -/ + through entries under these names. -/ partial def _root_.Address.mutsName (addr : Address) (firstMember : Name) : Name := let base := Name.mkStr (Name.mkStr .mkAnon "Ix") (toString addr) go base firstMember @@ -689,21 +689,6 @@ def Environment.get? (env : Environment) (n : Name) : Option ConstantInfo := | some f => f n | none => none -/-- Raw environment data as arrays (returned from Rust FFI). - Use `toEnvironment` to convert to Environment with HashMaps. -/ -structure RawEnvironment where - consts : Array (Name × ConstantInfo) - deriving Repr, Inhabited - -/-- Convert raw arrays to Environment with HashMaps. - This is done on the Lean side for correct hash function usage. -/ -def RawEnvironment.toEnvironment (raw : RawEnvironment) : Environment := - { consts := raw.consts.foldl (init := {}) fun m (k, v) => m.insert k v } - -/-- Convert Environment to raw arrays for FFI usage. -/ -def Environment.toRaw (env : Environment) : RawEnvironment := - { consts := env.consts.toArray } - /-! ## Context Types for Compilation -/ /-- Mutual context mapping Name to index within block. Keys are compared by diff --git a/Ix/FileDigest.lean b/Ix/FileDigest.lean new file mode 100644 index 000000000..23a3f8742 --- /dev/null +++ b/Ix/FileDigest.lean @@ -0,0 +1,38 @@ +module + +public import Blake3.Rust + +/-! # File digests for tests + +The Blake3 digest of a file, for tests that pin or compare an artifact's +bytes: Ix addresses are Blake3, and so are the file digests the tests record. +The file is read in chunks through the streaming hasher (`Blake3.HasherOps`: +`init`, `update`, `finalize`), in process, so no `b3sum` or other external +tool is needed. -/ + +public section + +namespace Ix.FileDigest + +/-- Lowercase hexadecimal, two digits per byte. -/ +def lowerHex (bytes : ByteArray) : String := + let digit (n : UInt8) : Char := "0123456789abcdef".toList[n.toNat]! + bytes.foldl (fun s b => (s.push (digit (b >>> 4))).push (digit (b &&& 0xf))) "" + +/-- The Blake3 digest of the file at `path`, as 64 lowercase hexadecimal +digits, read `chunk` bytes at a time (at least one: a read of zero bytes would +end the loop at once, with the empty input's digest). -/ +def blake3File (path : System.FilePath) (chunk : USize := 1048576) : IO String := do + let handle ← IO.FS.Handle.mk path .read + let mut hasher := Blake3.HasherOps.init (H := Blake3.Rust.Hasher) () + repeat + let bytes ← handle.read (max chunk 1) + if bytes.isEmpty then break + hasher := Blake3.HasherOps.update hasher bytes + return lowerHex (Blake3.HasherOps.finalize hasher 32).val + +#guard lowerHex ⟨#[0xaf, 0x13, 0x09, 0x00]⟩ == "af130900" + +end Ix.FileDigest + +end diff --git a/Ix/GraphM.lean b/Ix/GraphM.lean index b41374525..586e9bdd2 100644 --- a/Ix/GraphM.lean +++ b/Ix/GraphM.lean @@ -17,38 +17,53 @@ namespace Ix The canonicalization should be done first (ideally via fast Rust FFI). -/ -/-- The graph walk's memo: each expression's references, keyed exactly -(`Ix.Compile.ExactKey.ExprKey`: complete structural equality confirms a hit, as in -the compiler's caches). A stored hash is not a content hash on every input, so a -key compared by stored hashes alone could return another expression's references. -/ +/-- The graph walk's visited set: shared expression sub-DAGs already walked in +the current constant, keyed exactly (`Ix.Compile.ExactKey.ExprKey`: complete +structural equality confirms a hit, as in the compiler's caches). A stored hash +is not a content hash on every input, so a key compared by stored hashes alone +could skip another expression's references. -/ structure GraphState where - exprCache : Std.HashMap Ix.Compile.ExactKey.ExprKey (Set Ix.Name) + exprSeen : Std.HashSet Ix.Compile.ExactKey.ExprKey := {} -def GraphState.init : GraphState := ⟨{}⟩ +def GraphState.init : GraphState := {} abbrev GraphM := ReaderT Ix.Environment <| StateT GraphState Id -/-- Extract constant references from an Ix.Expr. - NOTE: Aligned with Rust's get_expr_references for cross-impl testing. -/ -def graphExpr (expr: Ix.Expr) : GraphM (Set Ix.Name) := do - match (<- get).exprCache.get? ⟨expr⟩ with - | some x => pure x - | none => - let refs <- go expr +/-- Extract constant references from an `Ix.Expr` into a single accumulator `acc`, +skipping shared sub-DAGs already recorded in `exprSeen`. -/ +def graphExprAcc (expr : Ix.Expr) (acc : Set Ix.Name) : GraphM (Set Ix.Name) := do + if (← get).exprSeen.contains ⟨expr⟩ then + pure acc + else + let refs ← go expr acc modifyGet fun stt => (refs, { stt with - exprCache := stt.exprCache.insert ⟨expr⟩ refs + exprSeen := stt.exprSeen.insert ⟨expr⟩ }) where - go : Ix.Expr -> GraphM (Set Ix.Name) - | .mdata _ x _ => graphExpr x - | .const name _ _ => pure {name} - | .app f a _ => .union <$> graphExpr f <*> graphExpr a - | .lam _ t b _ _ => .union <$> graphExpr t <*> graphExpr b - | .forallE _ t b _ _ => .union <$> graphExpr t <*> graphExpr b - | .letE _ t v b _ _ => - .union <$> graphExpr t <*> (.union <$> graphExpr v <*> graphExpr b) - | .proj typeName _ s _ => (.insert · typeName) <$> graphExpr s - | _ => pure {} + go : Ix.Expr → Set Ix.Name → GraphM (Set Ix.Name) + | .mdata _ x _, acc => graphExprAcc x acc + | .const name _ _, acc => pure (acc.insert name) + | .app f a _, acc => do + let acc ← graphExprAcc f acc + graphExprAcc a acc + | .lam _ t b _ _, acc => do + let acc ← graphExprAcc t acc + graphExprAcc b acc + | .forallE _ t b _ _, acc => do + let acc ← graphExprAcc t acc + graphExprAcc b acc + | .letE _ t v b _ _, acc => do + let acc ← graphExprAcc t acc + let acc ← graphExprAcc v acc + graphExprAcc b acc + | .proj typeName _ s _, acc => do + let acc ← graphExprAcc s acc + pure (acc.insert typeName) + | _, acc => pure acc + +/-- Extract constant references from an Ix.Expr. -/ +def graphExpr (expr : Ix.Expr) : GraphM (Set Ix.Name) := + graphExprAcc expr {} /-- The recursors of an inductive block, in the family's declared order: `X.rec` for each member `X` of `all`, then the nested auxiliary recursors `all[0].rec_N`, @@ -61,7 +76,6 @@ def recursorFamilyNames (all : Array Ix.Name) (numMotives : Nat) : Array Ix.Name return out /-- Extract constant references from an Ix.ConstantInfo. - NOTE: Aligned with Rust's get_constant_info_references for cross-impl testing. An inductive refers to the other members of its Lean block (`all`) and a recursor to the other recursors of its family (`recursorFamilyNames`): the @@ -70,9 +84,15 @@ def recursorFamilyNames (all : Array Ix.Name) (numMotives : Nat) : Array Ix.Name not strongly connected through their types. -/ def graphConst: Ix.ConstantInfo -> GraphM (Set Ix.Name) | .axiomInfo val => graphExpr val.cnst.type -| .defnInfo val => .union <$> graphExpr val.cnst.type <*> graphExpr val.value -| .thmInfo val => .union <$> graphExpr val.cnst.type <*> graphExpr val.value -| .opaqueInfo val => .union <$> graphExpr val.cnst.type <*> graphExpr val.value +| .defnInfo val => do + let acc ← graphExprAcc val.cnst.type {} + graphExprAcc val.value acc +| .thmInfo val => do + let acc ← graphExprAcc val.cnst.type {} + graphExprAcc val.value acc +| .opaqueInfo val => do + let acc ← graphExprAcc val.cnst.type {} + graphExprAcc val.value acc | .quotInfo val => graphExpr val.cnst.type | .inductInfo val => do -- Rust: type refs + constructor names (NOT constructor type refs) + block siblings @@ -91,8 +111,7 @@ def graphConst: Ix.ConstantInfo -> GraphM (Set Ix.Name) let mut rs := t for rule in val.rules do rs := rs.insert rule.ctor - let rhsRefs <- graphExpr rule.rhs - rs := rs.union rhsRefs + rs <- graphExprAcc rule.rhs rs return (recursorFamilyNames val.all val.numMotives).foldl (fun s r => if r != val.cnst.name then s.insert r else s) rs @@ -100,31 +119,6 @@ def GraphM.run (env: Ix.Environment) (stt: GraphState) (g: GraphM α) : α × GraphState := StateT.run (ReaderT.run (Id.run g env)) stt -/-- Build dependency graph from Ix.Environment. - Returns a map from Ix.Name to the set of Ix.Names it references. - Pass `dbg := true` and `total` (constant count) to enable progress tracing. -/ -def GraphM.env (env: Ix.Environment) (dbg : Bool := false) (total : Nat := 0) - : Map Ix.Name (Set Ix.Name) := Id.run do - let mut stt : GraphState := .init - let mut refs: Map Ix.Name (Set Ix.Name) := Std.HashMap.emptyWithCapacity env.consts.size - let mut i : Nat := 0 - let mut lastPct : Nat := 0 - for (name, const) in env.consts do - let (rs, stt') := GraphM.run env stt (graphConst const) - refs := refs.insert name rs - stt := stt' - i := i + 1 - if dbg && total > 0 then - let pct := (i * 100) / total - if pct >= lastPct + 10 then - dbg_trace s!" [Graph] {pct}% ({i}/{total})" - lastPct := pct - return refs - -def GraphM.envParallel (env: Ix.Environment) : Map Ix.Name (Set Ix.Name) := - (Ix.Compile.Parallel.map 256 (fun (name, ci) => (name, (GraphM.run { consts := {} } .init (graphConst ci)).1)) - env.consts.toList.toArray).foldl (fun out (name, refs) => out.insert name refs) {} - def GraphM.envSerial (env: Ix.Environment) : Map Ix.Name (Set Ix.Name) := Id.run do let mut refs: Map Ix.Name (Set Ix.Name) := {} for (name, const) in env.consts do @@ -181,50 +175,65 @@ private theorem EnvFree.forInArray {α σ : Type} (f : α → σ → GraphM (For rw [← Array.forIn_toList] exact EnvFree.forInList f hf xs.toList init -private theorem graphExpr_envFree (e : Ix.Expr) : EnvFree (graphExpr e) := by +private theorem graphExprAcc_envFree (e : Ix.Expr) : ∀ acc : Set Ix.Name, EnvFree (graphExprAcc e acc) := by induction e with | bvar | fvar | mvar | sort | const | lit => - rw [graphExpr] + intro acc + rw [graphExprAcc] refine EnvFree.bind (fun _ _ => rfl) fun stt => ?_ split · exact EnvFree.pure _ - · rw [graphExpr.go.eq_def] + · rw [graphExprAcc.go.eq_def] exact (EnvFree.pure _).bind fun _ _ _ => rfl | app _ _ _ ihf iha => - rw [graphExpr] + intro acc + rw [graphExprAcc] refine EnvFree.bind (fun _ _ => rfl) fun stt => ?_ split · exact EnvFree.pure _ - · rw [graphExpr.go.eq_def] - exact ((ihf.map _).seq iha).bind fun _ _ _ => rfl + · rw [graphExprAcc.go.eq_def] + exact ((ihf acc).bind iha).bind fun _ _ _ => rfl | lam _ _ _ _ _ iht ihb | forallE _ _ _ _ _ iht ihb => - rw [graphExpr] + intro acc + rw [graphExprAcc] refine EnvFree.bind (fun _ _ => rfl) fun stt => ?_ split · exact EnvFree.pure _ - · rw [graphExpr.go.eq_def] - exact ((iht.map _).seq ihb).bind fun _ _ _ => rfl + · rw [graphExprAcc.go.eq_def] + exact ((iht acc).bind ihb).bind fun _ _ _ => rfl | letE _ _ _ _ _ _ iht ihv ihb => - rw [graphExpr] + intro acc + rw [graphExprAcc] refine EnvFree.bind (fun _ _ => rfl) fun stt => ?_ split · exact EnvFree.pure _ - · rw [graphExpr.go.eq_def] - exact ((iht.map _).seq ((ihv.map _).seq ihb)).bind fun _ _ _ => rfl + · rw [graphExprAcc.go.eq_def] + exact ((iht acc).bind fun acc₁ => (ihv acc₁).bind ihb).bind fun _ _ _ => rfl | mdata _ _ _ ih => - rw [graphExpr] + intro acc + rw [graphExprAcc] refine EnvFree.bind (fun _ _ => rfl) fun stt => ?_ split · exact EnvFree.pure _ - · rw [graphExpr.go.eq_def] - exact ih.bind fun _ _ _ => rfl + · rw [graphExprAcc.go.eq_def] + exact (ih acc).bind fun _ _ _ => rfl | proj _ _ _ _ ih => - rw [graphExpr] + intro acc + rw [graphExprAcc] refine EnvFree.bind (fun _ _ => rfl) fun stt => ?_ split · exact EnvFree.pure _ - · rw [graphExpr.go.eq_def] - exact (ih.map _).bind fun _ _ _ => rfl + · rw [graphExprAcc.go.eq_def] + exact ((ih acc).bind fun _ => EnvFree.pure _).bind fun _ _ _ => rfl + +private theorem graphExpr_envFree (e : Ix.Expr) : EnvFree (graphExpr e) := + graphExprAcc_envFree e {} + +/-- **`graphExprAcc` does not read the environment it runs in.** -/ +theorem graphExprAcc_env (e : Ix.Expr) (acc : Set Ix.Name) (env₁ env₂ : Ix.Environment) (s : GraphState) : + GraphM.run env₁ s (graphExprAcc e acc) = GraphM.run env₂ s (graphExprAcc e acc) := by + show (graphExprAcc e acc env₁).run s = (graphExprAcc e acc env₂).run s + rw [graphExprAcc_envFree e acc env₁ env₂] /-- **`graphExpr` does not read the environment it runs in.** -/ theorem graphExpr_env (e : Ix.Expr) (env₁ env₂ : Ix.Environment) : @@ -239,14 +248,14 @@ theorem graphConst_env (ci : Ix.ConstantInfo) (env₁ env₂ : Ix.Environment) ( cases ci with | axiomInfo v | quotInfo v => exact graphExpr_envFree _ | defnInfo v | thmInfo v | opaqueInfo v => - exact ((graphExpr_envFree _).map _).seq (graphExpr_envFree _) + exact (graphExprAcc_envFree _ _).bind fun acc => graphExprAcc_envFree _ acc | inductInfo v => exact (graphExpr_envFree _).bind fun _ => EnvFree.pure _ | ctorInfo v => exact (graphExpr_envFree _).bind fun _ => EnvFree.pure _ | recInfo v => refine (graphExpr_envFree _).bind fun t => ?_ dsimp only refine (EnvFree.forInArray _ (fun rule r => ?_) _ _).bind fun _ => EnvFree.pure _ - exact (graphExpr_envFree _).bind fun _ => EnvFree.pure _ + exact (graphExprAcc_envFree _ _).bind fun _ => EnvFree.pure _ show (graphConst ci env₁).run s = (graphConst ci env₂).run s rw [h env₁ env₂] diff --git a/Ix/Ground.lean b/Ix/Ground.lean index a962af096..6e399942e 100644 --- a/Ix/Ground.lean +++ b/Ix/Ground.lean @@ -1,25 +1,21 @@ /- Ix.Ground: groundedness checking for Lean environment constants. - Pure-Lean port of Rust `crates/compile/src/ground.rs` (compiler - alignment). A constant is "grounded" if all its references resolve to + A constant is "grounded" if all its references resolve to known constants, all bound variables are in scope, and no metavariables remain. Ungroundedness propagates through the reference graph: if A references ungrounded B, then A is also ungrounded. Map types: the reverse-reference graph parameter uses `Map Name (Set Name)` — the `Ix.Common` aliases for `Std.HashMap` / `Std.HashSet` — - which is exactly the shape `Ix.GraphM` produces (`GraphM.env : ... → - Map Ix.Name (Set Ix.Name)`) and mirrors Rust `RefMap = - FxHashMap>` (crates/compile/src/graph.rs:30). + which is the shape `Ix.GraphM.envSerial` produces (`GraphM.envSerial : + Environment → Map Ix.Name (Set Ix.Name)`). - Determinism contract (vs Rust rayon parallelism): the immediate scan - checks each constant independently, so the RESULT SET of ungrounded - names (and the error kind of each immediately-ungrounded constant) is - independent of iteration order — in Rust (par_iter) and here - (sequential) alike. Transitive `ref` attribution payloads depend on - worklist pop order and are NOT contract-stable; see - `proliferateUngrounded`. + Determinism contract: the immediate scan checks each constant + independently, so the RESULT SET of ungrounded names (and the error kind + of each immediately-ungrounded constant) is independent of iteration + order. Transitive `ref` attribution payloads depend on worklist pop order + and are NOT contract-stable; see `proliferateUngrounded`. -/ module diff --git a/Ix/HostAdmission/Audit.lean b/Ix/HostAdmission/Audit.lean index d20762603..9c3673fff 100644 --- a/Ix/HostAdmission/Audit.lean +++ b/Ix/HostAdmission/Audit.lean @@ -31,7 +31,7 @@ helpers or their specializations; every execution allowance is unchanged. -/ -- The complete extern, unsafe, direct and reached implementation, csimp, -- replacement and opaque entry sets are unchanged, as are all runtime rulings. /-- info: runtime closure of [Ix.HostAdmission.certify, - Ix.HostAdmission.verifyAddressed]: 7176 compiled functions; inherited externs 125, implemented_by 0, unsafe 26, csimp 8; ruled assumed 4, computed_field 21, csimp 28 -/ + Ix.HostAdmission.verifyAddressed]: 7173 compiled functions; inherited externs 125, implemented_by 0, unsafe 26, csimp 8; ruled assumed 4, computed_field 21, csimp 28 -/ #guard_msgs (whitespace := lax) in run_cmd do Ix.Kernel.Audit.checkRuntimeWith #[``Ix.HostAdmission.certify, ``Ix.HostAdmission.verifyAddressed] diff --git a/Ix/ImportIxe.lean b/Ix/ImportIxe.lean index eddc9b939..a3a3ca851 100644 --- a/Ix/ImportIxe.lean +++ b/Ix/ImportIxe.lean @@ -1,17 +1,21 @@ -/- +module + +public import Lean +public import Ix.DecompileDriver +public meta import Ix.DecompileDriver +public meta import Ix.Replay + +/-! `import_ixe`: materialize a `.ixe`'s constants into the current elaboration environment. The `.ixe` is ix's fundamental artifact (plan D5 — no olean layer anywhere): a consumer file writes `import_ixe "catalog.ixe"` and the catalog's qualified constants become referenceable source-level - names, kernel-checked on the way in. The heavy lifting happens in - Rust: `rs_decompile_env_consts` decompiles the artifact and hands - back real `Lean.ConstantInfo` object graphs (constructed through the - toolchain's exported `lean_expr_mk_*` constructors, so hashes and - cached flags are Lean's own); the Lean side reconstructs kernel - `Declaration`s with the shared `Ix.Replay.planDeclarations` planner - and replays them with `Lean.addDecl`. + names, kernel-checked on the way in. `Ix.DecompileM.decompileEnvConsts` + decodes and decompiles the artifact in pure Lean and uncanonicalizes + each source constant to a `Lean.ConstantInfo`; `Ix.Replay.planDeclarations` + reconstructs kernel `Declaration`s and replays them with `Lean.addDecl`. Semantics: - Import restores the original Lean source declarations. @@ -23,7 +27,7 @@ bodies stay transparent even when the consumer file is in module mode — Ix punches through the Lean module system (plan D6). - `import_ixe "path" only [n₁, n₂]` materializes just the named - constants plus their full reference closure (computed Rust-side) — + constants plus their full reference closure — the bounded-cost import for large catalogs (plan DQ2). - Kernel-level only: instances, attributes, and native code do not transfer. Instance constants arrive as ordinary definitions; apps @@ -42,37 +46,19 @@ namespace resolve to ix's own mirror types, so Lean's are `Lean.`-qualified explicitly throughout (repo convention). -/ -module - -public import Lean -public meta import Ix.Replay public section namespace Ix.ImportIxe -/-- Decompile a `.ixe` and materialize constants as real - `Lean.ConstantInfo`s, in Rust - (`crates/ffi/src/compile.rs::rs_decompile_env_consts`, construction - in `crates/ffi/src/lean_build.rs`). Empty `only` ⇒ every - decompiled constant; nonempty ⇒ the requested source constants plus - their reference closure. Fails on thin bundles, broken source closures - and absent requested names. Pairs return sorted by name. -/ -@[extern "rs_decompile_env_consts"] -opaque rsDecompileEnvConstsFFI : @& String → @& Array Lean.Name → - IO (Array (Lean.Name × Lean.ConstantInfo)) - -/-- Meta twin of `rsDecompileEnvConstsFFI`, binding the same symbol: - the `import_ixe` elaborator chain is `meta` and may only reference - `meta` declarations, while compiled programs and tests use the - plain twin. -/ -@[extern "rs_decompile_env_consts"] -meta opaque rsDecompileEnvConstsMetaFFI : @& String → @& Array Lean.Name → - IO (Array (Lean.Name × Lean.ConstantInfo)) - +/-- Decompile a `.ixe` in pure Lean and materialize constants as + `Lean.ConstantInfo`s. Empty `only` ⇒ every decompiled constant; + nonempty ⇒ the requested source constants plus their reference + closure. Fails on thin bundles, broken source closures, and absent + requested names. Pairs return sorted by name. -/ def materializeIxe (path : String) (only : Array Lean.Name := #[]) : IO (Array (Lean.Name × Lean.ConstantInfo)) := - rsDecompileEnvConstsFFI path only + Ix.DecompileM.decompileEnvConsts path only /-- Add materialized constants to the environment by checked kernel replay in dependency order, skipping names already present. @@ -102,7 +88,7 @@ meta def addMaterialized (consts : Array (Lean.Name × Lean.ConstantInfo)) : meta def runImportIxe (path : String) (only : Array Lean.Name := #[]) : Lean.Elab.Command.CommandElabM Unit := do - let consts ← rsDecompileEnvConstsMetaFFI path only + let consts ← Ix.DecompileM.decompileEnvConsts path only let (replayed, skipped) ← Lean.Elab.Command.liftCoreM (addMaterialized consts) Lean.logInfo m!"import_ixe {path}: {consts.size} constants \ diff --git a/Ix/IxEval.lean b/Ix/IxEval.lean index 40b46384b..f221085f2 100644 --- a/Ix/IxEval.lean +++ b/Ix/IxEval.lean @@ -1,9 +1,17 @@ -/- +module + +public import Lean +public meta import Ix.Replay +public meta import Ix.CompileCertified +public meta import Ix.Commit +public meta import Ix.ClaimContext + +/-! `#ixeval`: evaluate a term through the ix pipeline and surface the content-addressed evaluation claim. `#ixeval t` elaborates `t`, computes a candidate normal form with `Meta.reduceAll`, - compiles the term's reference closure with the Rust compiler, and + compiles the term's reference closure with the certified Lean compiler, and builds an `Ix.Claim.eval` from the input and output content addresses (`Ix.Commit.evalClaim`). The reduced value, the claim, and its digest (blake3 of the serialized claim — the public input @@ -24,13 +32,6 @@ resolve to ix's own mirror types, so Lean's are `Lean.`-qualified explicitly throughout (repo convention). -/ -module - -public import Lean -public meta import Ix.Replay -public meta import Ix.CompileM -public meta import Ix.Commit -public meta import Ix.ClaimContext public section @@ -76,7 +77,7 @@ meta def elabIxEval : CommandElab := fun stx => do for r in Ix.Replay.constantInfoReferences ci do unless seen.contains r do work := work.push r - let phases ← Ix.CompileM.rsCompilePhasesOf closure + let phases ← Ix.CompileM.compilePhasesOf env closure let compileEnv := Ix.Commit.mkCompileEnv phases let ctx ← match Ix.ClaimContext.ofDefaultTable with | .ok ctx => pure ctx diff --git a/Ix/IxVM/ClaimHarness.lean b/Ix/IxVM/ClaimHarness.lean index 0af401222..e9c3a1148 100644 --- a/Ix/IxVM/ClaimHarness.lean +++ b/Ix/IxVM/ClaimHarness.lean @@ -3,7 +3,7 @@ public import Ix.Aiur.Protocol public import Ix.Ixon public import Ix.Claim public import Ix.AssumptionTree -public import Ix.CompileM +public import Ix.CompileCertified public import Ix.Common public import Ix.PrimAddrs public import Ix.ClaimContext @@ -19,7 +19,7 @@ Lean-side glue between `Ix.Claim` and the Aiur `verify_claim` / The harness handles every step the Aiur side cannot: 1. **Lean → Ixon compile**: `loadIxonEnv` / `loadSharedIxonEnv` walk - the Lean environment, run `Ix.CompileM.rsCompileEnvFFI`, and + the Lean environment, run `Ix.CompileM.compileCertified`, and return an `Ixon.Env` representing the constants' transitive closure. @@ -157,23 +157,62 @@ def withDefaultRoleBinding (env : Ixon.Env) : Except String Ixon.Env := do if env.roleBinding.isSome then return env withRoleTable env (← Ix.Kernel.Reader.defaultTableBytes) +/-- Close `roots` under `Lean.collectDependenciesMany` and every collected + inductive's recursors (`.rec` and nested `.rec_N`) so + `Ix.Cert.compileReport`'s `unownedRecursor` check sees every inductive + family's recursors when two families share an anonymous Ixon block + (e.g., `Lean.Doc.MathMode` and `Bool`). -/ +private def dedupConstList (entries : List (Lean.Name × Lean.ConstantInfo)) : + List (Lean.Name × Lean.ConstantInfo) := Id.run do + let mut seen : Lean.NameSet := {} + let mut out : Array (Lean.Name × Lean.ConstantInfo) := #[] + for entry in entries do + if !seen.contains entry.1 then + seen := seen.insert entry.1 + out := out.push entry + return out.toList + +private def collectCertifiedClosure (roots : Array Lean.Name) + (leanEnv : Lean.Environment) : List (Lean.Name × Lean.ConstantInfo) := Id.run do + let mut seeds := roots.filter leanEnv.constants.contains + let mut deps := dedupConstList (Lean.collectDependenciesMany seeds leanEnv.constants) + for _ in [0:4] do + let mut seen : Lean.NameSet := {} + for (n, _) in deps do seen := seen.insert n + let recs := deps.foldl (init := #[]) fun acc (_, ci) => + match ci with + | .inductInfo v => + let names := (Lean.mkRecName v.name) :: + (List.range v.numNested).map fun i => + Lean.Name.mkStr (v.all.headD v.name) s!"rec_{i + 1}" + acc ++ (names.filter fun r => + leanEnv.constants.contains r && !seen.contains r && !acc.contains r).toArray + | _ => acc + if recs.isEmpty then break + seeds := seeds ++ recs + deps := dedupConstList (Lean.collectDependenciesMany seeds leanEnv.constants) + return deps + /-- Variant of loadIxonEnv: also seeds the constants the kernel's kernel fabricates during reduction (Bool.true/false + Nat.zero/succ), so their bytes are always in ch 2 even when the target's proof body doesn't textually reference them. -/ def loadIxonEnv (name : Lean.Name) (leanEnv : Lean.Environment) : IO Ixon.Env := do - let mut seen : Lean.NameSet := {} - let mut deduped : Array (Lean.Name × Lean.ConstantInfo) := #[] - let names : Array Lean.Name := #[name] ++ synthesizedPrimNames ++ roleNames - for n in names do - if leanEnv.constants.contains n then - for entry in Lean.collectDependencies n leanEnv.constants do - if !seen.contains entry.fst then - seen := seen.insert entry.fst - deduped := deduped.push entry - let rawEnv ← Ix.CompileM.rsCompileEnvFFI deduped.toList - IO.ofExcept (withDefaultRoleBinding rawEnv.toEnv) + let roots := (#[name] ++ synthesizedPrimNames ++ roleNames).filter leanEnv.constants.contains + let initial := dedupConstList (Lean.collectDependenciesMany roots leanEnv.constants) + let res ← match ← Ix.CompileM.compileCertified leanEnv initial 32 with + | .ok out => pure (.ok out) + | .error _ => Ix.CompileM.compileCertified leanEnv (collectCertifiedClosure roots leanEnv) 32 + match res with + | .error e => throw (IO.userError s!"the certified Lean compile of {name}'s closure failed: {e}") + | .ok out => + unless out.failures.isEmpty do + let fMap := out.failures.toMap + throw (IO.userError s!"the certified Lean compile of {name}'s closure: \ + {fMap.size} constant(s) failed to compile: \ + {Ix.CompileM.recordedFailures fMap}") + IO.ofExcept (withDefaultRoleBinding out.env) /-- Compile the union of `names`'s transitive closures into one shared Ixon env. Drivers like `KernelArena.lean` use this to pay the @@ -181,15 +220,20 @@ def loadIxonEnv (name : Lean.Name) (leanEnv : Lean.Environment) : `buildClaimWitness` / `buildDbgCheckConst`. -/ def loadSharedIxonEnv (names : Array Lean.Name) (leanEnv : Lean.Environment) : IO Ixon.Env := do - let mut seen : Lean.NameSet := {} - let mut deduped : Array (Lean.Name × Lean.ConstantInfo) := #[] - for n in names ++ roleNames.filter leanEnv.constants.contains do - for entry in Lean.collectDependencies n leanEnv.constants do - if !seen.contains entry.fst then - seen := seen.insert entry.fst - deduped := deduped.push entry - let rawEnv ← Ix.CompileM.rsCompileEnvFFI deduped.toList - IO.ofExcept (withDefaultRoleBinding rawEnv.toEnv) + let roots := (names ++ synthesizedPrimNames ++ roleNames).filter leanEnv.constants.contains + let initial := dedupConstList (Lean.collectDependenciesMany roots leanEnv.constants) + let res ← match ← Ix.CompileM.compileCertified leanEnv initial 32 with + | .ok out => pure (.ok out) + | .error _ => Ix.CompileM.compileCertified leanEnv (collectCertifiedClosure roots leanEnv) 32 + match res with + | .error e => throw (IO.userError s!"the certified Lean compile of shared closure failed: {e}") + | .ok out => + unless out.failures.isEmpty do + let fMap := out.failures.toMap + throw (IO.userError s!"the certified Lean compile of shared closure: \ + {fMap.size} constant(s) failed to compile: \ + {Ix.CompileM.recordedFailures fMap}") + IO.ofExcept (withDefaultRoleBinding out.env) /-- The `refs` indices `e` uses as a CONSTANT: `Ref i` and the type index of `Prj i`. `Str`/`Nat` index blob payloads and `Rec` resolves through diff --git a/Ix/IxVM/Ingress.lean b/Ix/IxVM/Ingress.lean index 75a9336a2..e15ce9d04 100644 --- a/Ix/IxVM/Ingress.lean +++ b/Ix/IxVM/Ingress.lean @@ -231,11 +231,10 @@ def ingress := ⟦ -- wherever addresses are compared; `canon_addr_cmp` orders a -- block's members by their external refs. -- - -- The reference does not do this either: faulting a block + -- The retired Rust kernel did not do this either: faulting a block -- address ingresses the members under their PROJECTION KIds and -- the block under `kenv.blocks`, leaving nothing at the block - -- address itself, so asking for it errors - -- (`crates/kernel/src/ingress.rs:4539-4544`). + -- address itself, so asking for it errored. -- -- Nothing in the kernel needed the fallback: `is_muts_block` -- and `check_block_peer_param_agreement` read blocks through diff --git a/Ix/IxVM/Kernel/Check.lean b/Ix/IxVM/Kernel/Check.lean index e189e3d7b..8803ad932 100644 --- a/Ix/IxVM/Kernel/Check.lean +++ b/Ix/IxVM/Kernel/Check.lean @@ -33,9 +33,9 @@ def check := ⟦ -- Mirror is_unsafe_ci: 1 if a SAFE constant may not reference it, 0 -- otherwise. Thm/Quot always safe. -- - -- `Partial` counts alongside `Unsafe`, matching the reference's + -- `Partial` counts alongside `Unsafe`, matching the retired Rust kernel's -- "safe definition references partial definition" rejection - -- (crates/kernel/src/check.rs). This is load-bearing rather than + -- check. This is load-bearing rather than -- cosmetic: a partial definition may reference ITSELF (see the recur -- slot in Ingress), and `k_infer` discharges a `Const` against its -- declared type without checking it, so `partial def bad : False := bad` @@ -93,7 +93,7 @@ def check := ⟦ } } - -- Mirror crates/kernel/src/check.rs fn validate_univ_params_seen. + -- After the retired Rust kernel's `validate_univ_params_seen`. -- Walks a KLevel asserting `Param(i)` has `i < bound`. Aiur's `store`/ -- `load` deduplication subsumes Rust's seen-set. fn validate_univ_params_seen(u: KLevel, bound: G) { @@ -263,7 +263,7 @@ def check := ⟦ } } - -- Mirror crates/kernel/src/inductive.rs check_ctor_return_type. + -- After the retired Rust kernel's `check_ctor_return_type`. -- Validates that a ctor's declared type, after peeling -- `n_params + n_fields` Foralls, is a syntactic `Indc(params, indices)` -- application: @@ -667,7 +667,7 @@ def check := ⟦ -- `Inner α | mk : (α → False) → Inner α` used as `Inner Host`, the -- negative occurrence of `Host` only appears once `α := Host`. -- - -- Mirrors `check_nested_ctor_fields` (crates/kernel/src/inductive.rs): + -- As the retired Rust kernel's `check_nested_ctor_fields` did: -- strip `n_params` foralls, then simultaneously substitute at depth 0. -- After stripping, `BVar 0` is the LAST (innermost) parameter, so the -- argument list is reversed — `expr_inst_many` maps `substs[i]` to @@ -708,7 +708,7 @@ def check := ⟦ } } - -- Mirror crates/kernel/src/inductive.rs check_positivity. + -- After the retired Rust kernel's `check_positivity`. -- Strict positivity: each ctor field's domain must not have any inductive -- of `ind_idx`'s mutual block in a negative position (left of an arrow). -- For mutual blocks, the initial positivity context is the full set of @@ -928,7 +928,7 @@ def check := ⟦ } } - -- Mirror crates/kernel/src/inductive.rs large-elim check on Prop + -- After the retired Rust kernel's large-elim check on a Prop -- single-ctor inductive. Walk past `n_params` Foralls (skipping params), -- then walk `n_fields` Foralls collecting de Bruijn indices of data fields -- (those whose domain has sort != 0). Body after walk is the ctor's return @@ -1809,7 +1809,7 @@ def check := ⟦ } } - -- Mirror crates/kernel/src/inductive.rs (build_motive_type_flat, the + -- After the retired Rust kernel's `build_motive_type_flat` (the -- `for _ in 0..n_indices { whnf; All => push; _ => break }` loop): -- whnf before each peel, stop tolerantly when no binder remains. -- Index binders can hide under definitional wrappers (e.g. a result type @@ -3352,8 +3352,7 @@ def check := ⟦ -- For each rec field j, append IH_j applied to `head`. -- IH_j = `Const(target_rec, rec_lvls) (params...) (motives...) (minors...) (idx_args) field_j`. -- target_rec = peer_recs[mem_idx] — the recursor for the field's own type. - -- Mirror crates/kernel/src/inductive.rs fn build_rule_ih. - -- Mirror crates/kernel/src/inductive.rs fn build_rule_ih: WHNF the + -- After the retired Rust kernel's `build_rule_ih`: WHNF the -- field's lifted dom and the inner body so the head/args reflect the -- true inductive occurrence (after reducing wrappers like -- `constType (n α) (n α)` → `n α`). diff --git a/Ix/IxVM/Kernel/Claim.lean b/Ix/IxVM/Kernel/Claim.lean index a0af6e22a..671ae50f1 100644 --- a/Ix/IxVM/Kernel/Claim.lean +++ b/Ix/IxVM/Kernel/Claim.lean @@ -306,11 +306,9 @@ def claim := ⟦ } -- ============================================================================ - -- Assumption / env tree: leaf list serialization (ch 1). The format - -- matches the Rust implementation in crates/kernel/src, so the same - -- witness buffers feed either checker. The tree is bound to its ch 1 - -- key by recomputing the merkle root, not by hashing the bytes — see - -- the channel table in Ix/IxVM/ClaimHarness.lean. + -- Assumption / env tree: leaf list serialization (ch 1). The tree is + -- bound to its ch 1 key by recomputing the merkle root, not by hashing + -- the bytes — see the channel table in Ix/IxVM/ClaimHarness.lean. -- ============================================================================ fn leaf_hash(addr: Addr) -> Addr { let tail = put_address(addr, store(ListNode.Nil)); @@ -388,10 +386,13 @@ def claim := ⟦ -- Aiur memoizes per (addr, owned, asm, strict) — the list pointers -- are per-run constants, so shared subgraphs walk once. -- Addr-set membership via RBTreeMap keyed on the interned store - -- pointer: content-addressed interning makes ptr equality ⇔ 32-byte - -- equality in BOTH directions (each content has exactly one store - -- slot), so a u32 ptr key is an exact membership test. O(log n) per - -- query vs the O(n) list scan — the frontier list is the biggest + -- pointer: under honest host/VM content-addressed interning, each 32-byte + -- value has one store slot so pointer equality matches 32-byte equality + -- (while in the STARK constraint system a `Store` pointer is an auxiliary + -- column without value-deduplication constraints, so pointer-inequality + -- guards are honest-prover checks whereas positive membership still checks + -- a pointer loaded from the authenticated assumption/owned tree). O(log n) + -- per query vs the O(n) list scan — the frontier list is the biggest -- per-visit cost on fat-frontier shards (e.g. 1977 asm leaves). fn addr_set_build(leaves: List‹Addr›, acc: RBTreeMap‹G›) -> RBTreeMap‹G› { match load(leaves) { @@ -416,14 +417,17 @@ def claim := ⟦ -- exclude an owned leaf from both while the claim's merkle root still -- commits to it. match addr_set_member(addr, asm) { - 1 => (), + 1 => + assert_eq!(eq_zero(strict - 2), 0, + "Check: target is in the assumption set"); + (), _ => - match strict { + let next_strict = match strict { 1 => assert_eq!(addr_set_member(addr, owned), 1, "CheckEnv: walk reached a constant outside the owned set"); - (), - _ => (), + 1, + _ => 0, }; let c = load_verified_constant(addr); match c { @@ -435,29 +439,29 @@ def claim := ⟦ match dprj { DefinitionProj.Mk(_, block_addr) => run_check(rc, addr); - env_walk(rc, block_addr, owned, asm, strict), + env_walk(rc, block_addr, owned, asm, next_strict), }, ConstantInfo.IPrj(iprj) => match iprj { InductiveProj.Mk(_, block_addr) => run_check(rc, addr); - env_walk(rc, block_addr, owned, asm, strict), + env_walk(rc, block_addr, owned, asm, next_strict), }, ConstantInfo.CPrj(cprj) => match cprj { ConstructorProj.Mk(_, _, block_addr) => run_check(rc, addr); - env_walk(rc, block_addr, owned, asm, strict), + env_walk(rc, block_addr, owned, asm, next_strict), }, ConstantInfo.RPrj(rprj) => match rprj { RecursorProj.Mk(_, block_addr) => run_check(rc, addr); - env_walk(rc, block_addr, owned, asm, strict), + env_walk(rc, block_addr, owned, asm, next_strict), }, _ => run_check(rc, addr), }; - env_walk_refs(rc, refs, const_idxs_of(c), 0, owned, asm, strict), + env_walk_refs(rc, refs, const_idxs_of(c), 0, owned, asm, next_strict), }, } } @@ -486,6 +490,10 @@ def claim := ⟦ match load(leaves) { ListNode.Nil => (), ListNode.Cons(a, rest) => + -- Assumptions waive out-of-shard dependency checks, never the + -- check of an owned subject itself. + assert_eq!(addr_set_member(a, asm), 0, + "CheckEnv: owned constant is in the assumption set"); env_walk(rc, a, owned, asm, 1); env_walk_leaves(rc, rest, owned, asm), } @@ -498,6 +506,13 @@ def claim := ⟦ -- being well-typed, so every constant the claim covers must be -- reached by this walk or assumed via the frontier. fn run_check_env(rc: RoleCtx, env_root: Addr, asm: Option‹Addr›) { + let owned = load_assumption_tree(env_root); + match load(owned) { + ListNode.Nil => + assert_eq!(0, 1, "CheckEnv: owned tree has no leaves"); + (), + ListNode.Cons(_, _) => (), + }; match asm { Option.None => -- The whole environment: every constant under `env_root` is @@ -514,13 +529,11 @@ def claim := ⟦ -- (the sharded walk below aborts instead), so a claim without -- assumptions proves every constant its tree reaches, not only -- those it lists. For an environment's tree the two coincide. - let leaves = load_assumption_tree(env_root); match role_ctx_axioms(rc) { - RBTreeMap.Nil => walk_leaves_owned(rc, leaves), - RBTreeMap.Node(_, _, _, _, _) => walk_leaves_transitive(rc, leaves), + RBTreeMap.Nil => walk_leaves_owned(rc, owned), + RBTreeMap.Node(_, _, _, _, _) => walk_leaves_transitive(rc, owned), }, Option.Some(r) => - let owned = load_assumption_tree(env_root); let owned_set = addr_set_build(owned, RBTreeMap.Nil); let asm_set = addr_set_build(load_assumption_tree(r), RBTreeMap.Nil); env_walk_leaves(rc, owned, owned_set, asm_set), @@ -646,8 +659,7 @@ def claim := ⟦ } -- ============================================================================ - -- Reveal claim (variant 6). Mirrors the Rust implementation in - -- crates/kernel/src; the machinery is Ixon-level rather than + -- Reveal claim (variant 6). The machinery is Ixon-level rather than -- typechecker-level: parse a `RevealConstantInfo` (per-field Option -- mirror of `ConstantInfo`, Expr fields as content hashes), load the -- committed constant, assert every Some(field) equals the real one. @@ -1410,7 +1422,9 @@ def claim := ⟦ Option.Some(r) => let asm_leaves = load_assumption_tree(r); let asm_set = addr_set_build(asm_leaves, RBTreeMap.Nil); - env_walk(rc, target, RBTreeMap.Nil, asm_set, 0), + -- strict = 2 rejects `target ∈ asm_set` at the root without + -- enforcing owned-set membership on transitive dependencies. + env_walk(rc, target, RBTreeMap.Nil, asm_set, 2), }, 5 => let (binding, axioms, s) = get_claim_context(s); diff --git a/Ix/IxVM/Kernel/DefEq.lean b/Ix/IxVM/Kernel/DefEq.lean index 3e76960c9..f06cc0631 100644 --- a/Ix/IxVM/Kernel/DefEq.lean +++ b/Ix/IxVM/Kernel/DefEq.lean @@ -393,8 +393,8 @@ def defEq := ⟦ -- Takes the no-delta forms slow_nd already computed and NEVER calls -- full whnf: full WHNF here would delta-unfold stuck open primitives -- such as `Nat.ble` into their recursive logical models and walk - -- enormous Nat offsets layer by layer (mirror the Tier 4c whnf-core - -- discipline in crates/kernel/src/def_eq.rs). Delta happens only + -- enormous Nat offsets layer by layer (the retired Rust kernel's Tier 4c + -- whnf-core discipline). Delta happens only -- inside lazy_delta_loop, one definitional step at a time, with the -- cheap tiers re-consulted between steps; the loop hands back its -- final forms for the structural fallback tiers. @@ -656,7 +656,7 @@ def defEq := ⟦ -- Struct-eta: if `s` is a fully-applied ctor of a single-ctor -- 0-indices inductive (struct-like) and `t` is any expr, -- accept when `Proj(induct, i, t) ≡ s_args[nparams + i]` for every - -- field. Mirror crates/kernel/src. + -- field. fn try_eta_struct(rc: RoleCtx, t: KExpr, s: KExpr, types: List‹KExpr›) -> G { match collect_spine(s) { (s_head, s_args) => @@ -696,9 +696,7 @@ def defEq := ⟦ -- synthesized inside def-eq and never -- inferred, so `k_infer_proj`'s own -- head-address gate never sees them. - -- Mirrors the reference's - -- "Types must be def-eq" check - -- (crates/kernel/src/def_eq.rs), itself + -- The types must be def-eq, as in -- lean4lean's `tryEtaStructCore`. let t_ty = k_infer_only(rc, t, types); let s_ty = k_infer_only(rc, s, types); @@ -830,7 +828,7 @@ def defEq := ⟦ } } - -- Mirror crates/kernel/src: if `a`'s type is a Prop, + -- Proof irrelevance: if `a`'s type is a Prop, -- and `b`'s type is def-eq to `a`'s type, then a ≡ b as proof-terms. -- Uses k_infer_only to avoid the def_eq ↔ check cycle. fn try_proof_irrel(rc: RoleCtx, a: KExpr, b: KExpr, types: List‹KExpr›) -> G { diff --git a/Ix/IxVM/Kernel/Infer.lean b/Ix/IxVM/Kernel/Infer.lean index d0ad85fd6..3d6dc9bce 100644 --- a/Ix/IxVM/Kernel/Infer.lean +++ b/Ix/IxVM/Kernel/Infer.lean @@ -59,7 +59,7 @@ def infer := ⟦ } -- k_infer - -- Mirror of `crates/kernel/src/infer.rs::infer`. Per-variant dispatch. + -- Per-variant dispatch. -- Lvls placed at outermost; level-params instantiation handled where -- the constant's declared type is fetched. -- Context-trimmed memo wrapper (mirror Rust `infer_key`): key inference on @@ -465,7 +465,7 @@ def infer := ⟦ } } - -- Mirror crates/kernel/src/infer.rs dependent-data-field guard. + -- Dependent-data-field guard. -- For Prop structures: a preceding data field (sort != 0) whose body -- has any loose bvar (`body.lbr() > 0`) makes projection past it unsound. -- Matches Rust's `body.lbr() > 0` check exactly. @@ -486,7 +486,7 @@ def infer := ⟦ } } - -- Mirror crates/kernel/src/infer.rs ctor-field walk loop. + -- Ctor-field walk loop. -- For each preceding field i < target_field, substitute body[Var(0) -- := Proj(struct_idx, i, val)] before recursing. At i == target_field, -- return the field's domain type. @@ -515,7 +515,6 @@ def infer := ⟦ -- Cold-extracted Proj arm: the widest arm of `k_infer_core` by far -- (~10 call sites) on a rare node kind. - -- Mirror crates/kernel/src/infer.rs infer_proj. fn k_infer_proj(rc: RoleCtx, struct_addr: Addr, fidx: G, e1: KExpr, types: List‹KExpr›) -> KExpr { let val_ty = k_infer(rc, e1, types); diff --git a/Ix/IxVM/Kernel/InferOnly.lean b/Ix/IxVM/Kernel/InferOnly.lean index 21c42f425..4eb72f1bd 100644 --- a/Ix/IxVM/Kernel/InferOnly.lean +++ b/Ix/IxVM/Kernel/InferOnly.lean @@ -12,7 +12,7 @@ namespace IxVM /-! ## Infer-Only -Mirror `with_infer_only` (crates/kernel/src/infer.rs). +Fast type-synthesis pass (`with_infer_only`). Type synthesis ONLY. Skips inner validations: App drops `k_check(rc, a, dom)`, Lam drops `k_ensure_sort(rc, ty)`, Let drops val/ty diff --git a/Ix/IxVM/Kernel/Klimbs.lean b/Ix/IxVM/Kernel/Klimbs.lean index 63327da30..9f7ac6696 100644 --- a/Ix/IxVM/Kernel/Klimbs.lean +++ b/Ix/IxVM/Kernel/Klimbs.lean @@ -9,7 +9,7 @@ namespace IxVM /-! ## KLimbs bignum arithmetic -Bignum gadgets backing the Nat primitives (see crates/kernel/src/primitive.rs). +Bignum gadgets backing the Nat primitives (`Ix/IxVM/Kernel/NatPrim.lean`). Kept in a module of its own, separate from the primitive dispatch that consumes it, so the arithmetic can be read and tested without the diff --git a/Ix/IxVM/Kernel/Levels.lean b/Ix/IxVM/Kernel/Levels.lean index a01a780f1..282c57d6a 100644 --- a/Ix/IxVM/Kernel/Levels.lean +++ b/Ix/IxVM/Kernel/Levels.lean @@ -8,9 +8,8 @@ namespace IxVM /-! ## Level operations + literal equality + expr_inst_levels -Mirror: crates/kernel/src/level.rs + literal-equality helpers from -crates/kernel/src/expr.rs. Self-contained; new kernel modules -(Subst/Whnf/Infer/DefEq/Check) import from here. +Universe level operations and literal-equality helpers. Self-contained; +kernel modules (`Subst`/`Whnf`/`Infer`/`DefEq`/`Check`) import from here. -/ def levels := ⟦ @@ -58,10 +57,9 @@ def levels := ⟦ } -- ============================================================================ - -- Canonical level normalization (mirror: crates/kernel/src/level.rs - -- normalize_level / norm_level_eq / norm_level_le, itself a port of - -- Lean4Lean's Level.Normalize with the documented subsumption and - -- covers-split fixes). + -- Canonical level normalization (normalize_level / norm_level_eq / + -- norm_level_le, a port of Lean4Lean's Level.Normalize with the documented + -- subsumption and covers-split fixes). -- -- Semantic level comparison via the recursive `Level.leq` with its -- two-way param-substitution split per Max/IMax-with-param is diff --git a/Ix/IxVM/Kernel/NatPrim.lean b/Ix/IxVM/Kernel/NatPrim.lean index 03e948599..4b4fb135b 100644 --- a/Ix/IxVM/Kernel/NatPrim.lean +++ b/Ix/IxVM/Kernel/NatPrim.lean @@ -11,8 +11,7 @@ namespace IxVM /-! ## Nat primitive dispatch -Nat/Str/BitVec/decidable reduction, mirroring -crates/kernel/src/primitive.rs. Recognizes Const-headed applications of +Nat/Str/BitVec/decidable reduction. Recognizes Const-headed applications of Nat.succ / Nat.pred / Nat.add / Nat.sub / Nat.mul / Nat.div / Nat.mod / Nat.pow / Nat.gcd / Nat.land / Nat.lor / Nat.xor / Nat.shl / Nat.shr / Nat.beq / Nat.ble on Nat literals (or on Nat.zero / App(Nat.succ, ...) @@ -175,9 +174,9 @@ def natPrim := ⟦ } } - -- Native string-value fast paths (mirror crates/kernel/src/primitive.rs - -- `string_append` / `string_dec_eq`): literal append and literal - -- decidable equality reduce natively in whnf, keeping evaluator-grade + -- Native string-value fast paths (`string_append` / `string_dec_eq`): + -- literal append and literal decidable equality reduce natively in + -- whnf, keeping evaluator-grade -- string values out of the structural ByteArray/UTF-8 model. -- String primitive dispatch (`op`: 1 ofList, 2 utf8ByteSize, 3 back, diff --git a/Ix/IxVM/Kernel/Subst.lean b/Ix/IxVM/Kernel/Subst.lean index 2dcfb8a32..bdd5ec3f2 100644 --- a/Ix/IxVM/Kernel/Subst.lean +++ b/Ix/IxVM/Kernel/Subst.lean @@ -8,8 +8,6 @@ namespace IxVM /-! ## Substitution primitives over `KExpr` -Mirror: crates/kernel/src/subst.rs (full file). - Two core operations: * `expr_lift(e, shift, cutoff)` — shift `BVar(i)` to `BVar(i+shift)` when @@ -42,8 +40,8 @@ def subst := ⟦ -- O(1). First traversal cost matches a single subst walk; the payoff is -- short-circuiting subst that would otherwise re-walk closed subtrees. -- - -- Mirror: crates/kernel/src/expr.rs::lbr field on KExprNode (precomputed at - -- intern time in Rust; computed on demand here, memoized via `store`). + -- The retired Rust kernel precomputed this (`lbr` on `KExprNode`) at + -- intern time; here it is computed on demand, memoized via `store`. -- ============================================================================ fn expr_lbr(e: KExpr) -> G { match load(e) { @@ -104,7 +102,7 @@ def subst := ⟦ -- Used by the nested-occurrence spec_params validity check in -- `Kernel/Inductive.lean`: at extraction depth `d`, a spec_param with -- a loose BVar in `[0, d)` depends on a field/domain-local binder. - -- Mirror: `sp.has_fvars()` in `crates/kernel/src/inductive.rs:723-730` + -- The retired Rust kernel tested `sp.has_fvars()` instead -- (Rust opens field/domain binders as fvars; Aiur keeps de Bruijn, so -- the same references appear as loose BVars below the depth). -- ============================================================================ @@ -300,7 +298,7 @@ def subst := ⟦ -- Shift `BVar(i)` → `BVar(i + shift)` when `i ≥ cutoff`. Recursion bumps -- `cutoff` by 1 when crossing a binder (Lam/Forall/Let). -- - -- Mirrors `crates/kernel/src/subst.rs::lift_no_intern` (line 364-415). + -- As the retired Rust kernel's `lift_no_intern`. -- Fast path: when `expr_lbr(e) <= cutoff`, no loose BVar at or above the -- cutoff exists, so `e` is unchanged. -- ============================================================================ @@ -432,7 +430,7 @@ def subst := ⟦ -- Substitute `BVar(depth)` with `expr_lift(arg, depth, 0)` and decrement -- `BVar(i)` for `i > depth`. Crossing a binder bumps `depth`. -- - -- Mirrors the single-arg form of `crates/kernel/src/subst.rs::instantiate_rev` + -- The single-arg form of `instantiate_rev`, -- with a list of one element. For the lambda-eta case in def_eq we want -- `body[BVar(0) := arg]` which is `expr_inst1(body, arg, 0)`. -- ============================================================================ @@ -539,7 +537,7 @@ def subst := ⟦ -- ============================================================================ -- expr_inst_many — simultaneous substitution of `substs` for the `n` binders - -- at `depth` in ONE walk (mirror `crates/kernel/src/subst.rs::simul_subst`). Used + -- at `depth` in ONE walk (as the retired Rust kernel's `simul_subst`). Used -- for multi-arg beta: `(λλλ. body) x y z` substitutes all three at once -- instead of three separate `expr_inst1` re-walks of the body. -- diff --git a/Ix/IxVM/Kernel/Whnf.lean b/Ix/IxVM/Kernel/Whnf.lean index 9b8ce85c9..83140d649 100644 --- a/Ix/IxVM/Kernel/Whnf.lean +++ b/Ix/IxVM/Kernel/Whnf.lean @@ -53,7 +53,7 @@ def whnf := ⟦ } } - -- Multi-arg beta (mirror crates/kernel/src/whnf.rs): peel the whole + -- Multi-arg beta: peel the whole -- telescope and substitute all consumed args in ONE `expr_inst_many` walk, -- instead of one `expr_inst1` re-walk of the body per arg. Single-arg betas -- stay on the cheap `expr_inst1` path (no list overhead). @@ -421,8 +421,7 @@ def whnf := ⟦ } -- Recognize `λ x_1 ... x_n → Prj S i (BVar k)` and rewrite an - -- application of the constant to a direct Prj. Mirrors the Rust - -- implementation in crates/kernel/src. Pure perf — standard + -- application of the constant to a direct Prj. Pure perf — standard -- delta+beta produces the same result — but keeps binder-type WHNF -- inside a proj scrutinee stuck at Const-inductive form rather than -- descending into the body and getting stuck at a partially-applied @@ -464,12 +463,11 @@ def whnf := ⟦ } } - -- Canonical Nat.zero / Nat.succ addrs. Mirror - -- crates/kernel/src, 490). + -- Canonical Nat.zero / Nat.succ addrs. -- Convert `Lit(Nat n)` to `Nat.zero` or `App(Nat.succ, Lit(n-1))`. -- Non-Lit inputs pass through unchanged. Enables iota to fire on - -- Lit majors. Mirror crates/kernel/src. + -- Lit majors. fn nat_lit_to_ctor_or_self(rc: RoleCtx, e: KExpr) -> KExpr { match load(e) { KExprNode.Lit(lit) => @@ -492,8 +490,7 @@ def whnf := ⟦ } -- K-recursor synth: fire rule[0] assuming the nullary K-ctor, but only - -- when the toCtorWhenK gate holds (mirror try_synth_k_ctor, - -- crates/kernel/src: infer the major's type, require its + -- when the toCtorWhenK gate holds: infer the major's type, require its -- head to be this recursor's inductive, synthesize the ctor app from -- the type's params, and require def_eq(major_ty, ctor_ty). Without -- the gate, an Eq.rec transport whose endpoints differ symbolically @@ -546,8 +543,7 @@ def whnf := ⟦ -- The toCtorWhenK gate: 1 iff whnf(rc, infer(major)) is `I params indices` -- for this recursor's own inductive AND the synthesized ctor app - -- `I.ctor params` has type def-eq to the major's. Mirror - -- try_synth_k_ctor's verification step. + -- `I.ctor params` has type def-eq to the major's. fn k_synth_gate(rc: RoleCtx, raw_major: KExpr, cidx: G, nparams: G, rec_parent: Addr, types: List‹KExpr›) -> G { let major_ty = k_infer(rc, raw_major, types); @@ -587,8 +583,7 @@ def whnf := ⟦ -- major argument. If it's a Ctor application matching one of the -- rules, apply the rule's rhs (Lam-wrapped) to -- params ++ motives ++ minors ++ ctor_fields ++ post_major via - -- apply_spine so that outer whnf beta-reduces. Mirror - -- crates/kernel/src. + -- apply_spine so that outer whnf beta-reduces. -- Rewrite spine with major (at major_idx) replaced by given whnf'd major. fn replace_spine_major(spine: List‹KExpr›, major_idx: G, major_w: KExpr) -> List‹KExpr› { @@ -1063,8 +1058,7 @@ def whnf := ⟦ } -- ============================================================================ - -- whnf_nd: WHNF without delta-unfolding. Mirror whnf_nd - -- crates/kernel/src / Rust whnf.rs::whnf_no_delta. + -- whnf_nd: WHNF without delta-unfolding. -- -- Same dispatch tree as whnf, but the Defn/Thm arms of the const -- head return the stuck application instead of unfolding. Beta / zeta / diff --git a/Ix/Ixon.lean b/Ix/Ixon.lean index c0a462bb1..df7fa9031 100644 --- a/Ix/Ixon.lean +++ b/Ix/Ixon.lean @@ -657,28 +657,24 @@ def exprMetaSlotCount : Nat → Option Nat | 8 | 9 => some 1 | _ => none -/-- The structural child slots of a node, in field order. -/ -def ExprMetaData.structuralSlots : ExprMetaData → Array UInt64 - | .app f a => #[f, a] - | .binder _ _ t b => #[t, b] - | .letBinder _ t v b => #[t, v, b] - | .prj _ c => #[c] - | .mdata _ c => #[c] - | _ => #[] - /-- The implicit-slot mask of node `i` and its block start `lo[i]`, given the block starts `lo[0..i]` of the preceding nodes. -/ def exprMetaImplicitMask (node : ExprMetaData) (i : Nat) (lo : Array Nat) : - UInt8 × Nat := Id.run do - let slots := node.structuralSlots - let mut top := i - let mut mask : UInt8 := 0 - for k in [0:slots.size] do - let s := slots.size - 1 - k - if top > 0 && slots[s]!.toNat == top - 1 then - mask := mask ||| ((1 : UInt8) <<< s.toUInt8) - top := lo[top - 1]! - return (mask, top) + UInt8 × Nat := + match node with + | .leaf | .ref _ => (0, i) + | .prj _ c | .mdata _ c => + if i > 0 && c.toNat == i - 1 then (1, lo[i - 1]!) else (0, i) + | .app c0 c1 | .binder _ _ c0 c1 => + let (mask, top) := + if i > 0 && c1.toNat == i - 1 then ((2 : UInt8), lo[i - 1]!) else (0, i) + if top > 0 && c0.toNat == top - 1 then (mask ||| 1, lo[top - 1]!) else (mask, top) + | .letBinder _ c0 c1 c2 => + let (mask, top) := + if i > 0 && c2.toNat == i - 1 then ((4 : UInt8), lo[i - 1]!) else (0, i) + let (mask, top) := + if top > 0 && c1.toNat == top - 1 then (mask ||| 2, lo[top - 1]!) else (mask, top) + if top > 0 && c0.toNat == top - 1 then (mask ||| 1, lo[top - 1]!) else (mask, top) /-- Explicit reference from node `i` to node `c`: TagN of `i - 1 - c` (wrapping). -/ @@ -964,18 +960,6 @@ def exprMetaNodeByteSize (em : ExprMetaData) (i : Nat) (mask : UInt8) (idx : Nam | .prj structName child => 1 + idxByteSize structName idx + slot 0 child | .mdata mdata child => 1 + mdataStackIndexedByteSize mdata idx + slot 0 child -/-- Exact byte length written by `putExprMetaArenaIndexed arena idx`. -/ -def exprMetaArenaIndexedByteSize (arena : ExprMetaArena) (idx : NameIndex) : Nat := Id.run do - let mut total := tag0ByteSize arena.nodes.size.toUInt64 - let mut lo : Array Nat := Array.emptyWithCapacity arena.nodes.size - let mut i := 0 - for node in arena.nodes do - let (mask, top) := exprMetaImplicitMask node i lo - total := total + exprMetaNodeByteSize node i mask idx - lo := lo.push top - i := i + 1 - return total - /-- Exact byte length written by `putUniv u`. -/ def univByteSize : Univ → Nat | .zero => tagNByteWidth 2 0 @@ -993,50 +977,94 @@ decreasing_by have hbase := Univ.succBase_sizeOf_le inner omega -/-- Exact byte length written by `putConstantMetaInfoIndexed cm idx`. -/ -def constantMetaInfoIndexedByteSize (cm : ConstantMetaInfo) (idx : NameIndex) : Nat := +/-- Compute the per-node implicit-slot masks and exact byte length of +`putExprMetaArenaIndexed arena idx` in a single pass over `arena.nodes`. -/ +def exprMetaArenaMasksAndByteSize (arena : ExprMetaArena) (idx : NameIndex) : + ByteArray × Nat := Id.run do + let n := arena.nodes.size + if n == 0 then + return (ByteArray.empty, 1) + let mut total := tag0ByteSize n.toUInt64 + let mut lo : Array Nat := Array.emptyWithCapacity n + let mut masks : ByteArray := ByteArray.emptyWithCapacity n + for i in [0:n] do + let node := arena.nodes[i]! + let (mask, top) := exprMetaImplicitMask node i lo + total := total + exprMetaNodeByteSize node i mask idx + lo := lo.push top + masks := masks.push mask + return (masks, total) + +/-- Serialize `ExprMetaArena` using precomputed per-node implicit-slot `masks` +from `exprMetaArenaMasksAndByteSize`. -/ +def putExprMetaArenaWithMasks (arena : ExprMetaArena) (masks : ByteArray) (idx : NameIndex) : + PutM Unit := do + let n := arena.nodes.size + putTagN 0 0 n.toUInt64 + for i in [0:n] do + putExprMetaNode arena.nodes[i]! i (masks.get! i) idx + +/-- Compute the arena implicit-slot masks and exact byte length of +`putConstantMetaInfoIndexed cm idx` in a single arena pass. -/ +def constantMetaInfoMasksAndByteSize (cm : ConstantMetaInfo) (idx : NameIndex) : + ByteArray × Nat := match cm with - | .empty => 1 + | .empty => (ByteArray.empty, 1) | .defn name lvls all ctx arena typeRoot valueRoot => - 1 + idxByteSize name idx + idxVecByteSize lvls idx + idxVecByteSize all idx + - idxVecByteSize ctx idx + exprMetaArenaIndexedByteSize arena idx + - tag0ByteSize typeRoot + tag0ByteSize valueRoot + let (masks, arenaBytes) := exprMetaArenaMasksAndByteSize arena idx + (masks, + 1 + idxByteSize name idx + idxVecByteSize lvls idx + idxVecByteSize all idx + + idxVecByteSize ctx idx + arenaBytes + + tag0ByteSize typeRoot + tag0ByteSize valueRoot) | .axio name lvls arena typeRoot => - 1 + idxByteSize name idx + idxVecByteSize lvls idx + - exprMetaArenaIndexedByteSize arena idx + tag0ByteSize typeRoot + let (masks, arenaBytes) := exprMetaArenaMasksAndByteSize arena idx + (masks, + 1 + idxByteSize name idx + idxVecByteSize lvls idx + + arenaBytes + tag0ByteSize typeRoot) | .quot name lvls arena typeRoot => - 1 + idxByteSize name idx + idxVecByteSize lvls idx + - exprMetaArenaIndexedByteSize arena idx + tag0ByteSize typeRoot + let (masks, arenaBytes) := exprMetaArenaMasksAndByteSize arena idx + (masks, + 1 + idxByteSize name idx + idxVecByteSize lvls idx + + arenaBytes + tag0ByteSize typeRoot) | .indc name lvls ctors all ctx arena typeRoot => - 1 + idxByteSize name idx + idxVecByteSize lvls idx + idxVecByteSize ctors idx + - idxVecByteSize all idx + idxVecByteSize ctx idx + - exprMetaArenaIndexedByteSize arena idx + tag0ByteSize typeRoot + let (masks, arenaBytes) := exprMetaArenaMasksAndByteSize arena idx + (masks, + 1 + idxByteSize name idx + idxVecByteSize lvls idx + idxVecByteSize ctors idx + + idxVecByteSize all idx + idxVecByteSize ctx idx + + arenaBytes + tag0ByteSize typeRoot) | .ctor name lvls induct arena typeRoot => - 1 + idxByteSize name idx + idxVecByteSize lvls idx + idxByteSize induct idx + - exprMetaArenaIndexedByteSize arena idx + tag0ByteSize typeRoot + let (masks, arenaBytes) := exprMetaArenaMasksAndByteSize arena idx + (masks, + 1 + idxByteSize name idx + idxVecByteSize lvls idx + idxByteSize induct idx + + arenaBytes + tag0ByteSize typeRoot) | .recr name lvls rules all ctx arena typeRoot ruleRoots => - 1 + idxByteSize name idx + idxVecByteSize lvls idx + idxVecByteSize rules idx + - idxVecByteSize all idx + idxVecByteSize ctx idx + - exprMetaArenaIndexedByteSize arena idx + tag0ByteSize typeRoot + - ruleRoots.foldl (fun acc r => acc + tag0ByteSize r) (tag0ByteSize ruleRoots.size.toUInt64) + let (masks, arenaBytes) := exprMetaArenaMasksAndByteSize arena idx + (masks, + 1 + idxByteSize name idx + idxVecByteSize lvls idx + idxVecByteSize rules idx + + idxVecByteSize all idx + idxVecByteSize ctx idx + + arenaBytes + tag0ByteSize typeRoot + + ruleRoots.foldl (fun acc r => acc + tag0ByteSize r) (tag0ByteSize ruleRoots.size.toUInt64)) | .muts all auxLayout => let allBytes := all.foldl (fun acc cls => acc + idxVecByteSize cls idx) (1 + tag0ByteSize all.size.toUInt64) - match auxLayout with - | none => allBytes + 1 - | some layout => - let permBytes := layout.perm.foldl (fun acc p => acc + tag0ByteSize p) - (tag0ByteSize layout.perm.size.toUInt64) - let countsBytes := layout.sourceCtorCounts.foldl (fun acc c => acc + tag0ByteSize c) - (tag0ByteSize layout.sourceCtorCounts.size.toUInt64) - let evapLen := if layout.evaporated.isEmpty && !layout.perm.isEmpty - then layout.perm.size else layout.evaporated.size - let evapBytes := tag0ByteSize evapLen.toUInt64 + evapLen - allBytes + 1 + permBytes + countsBytes + evapBytes - -/-- Exact byte length written by `putConstantMetaIndexed cm idx`. -/ -def constantMetaIndexedByteSize (cm : ConstantMeta) (idx : NameIndex) : Nat := - let infoBytes := constantMetaInfoIndexedByteSize cm.info idx + let total := match auxLayout with + | none => allBytes + 1 + | some layout => + let permBytes := layout.perm.foldl (fun acc p => acc + tag0ByteSize p) + (tag0ByteSize layout.perm.size.toUInt64) + let countsBytes := layout.sourceCtorCounts.foldl (fun acc c => acc + tag0ByteSize c) + (tag0ByteSize layout.sourceCtorCounts.size.toUInt64) + let evapLen := if layout.evaporated.isEmpty && !layout.perm.isEmpty + then layout.perm.size else layout.evaporated.size + let evapBytes := tag0ByteSize evapLen.toUInt64 + evapLen + allBytes + 1 + permBytes + countsBytes + evapBytes + (ByteArray.empty, total) + +/-- Compute the arena implicit-slot masks and exact byte length of +`putConstantMetaIndexed cm idx` in a single arena pass. -/ +def constantMetaMasksAndByteSize (cm : ConstantMeta) (idx : NameIndex) : + ByteArray × Nat := + let (masks, infoBytes) := constantMetaInfoMasksAndByteSize cm.info idx let sharingBytes := cm.metaSharing.foldl (fun acc e => acc + (runPut (putExpr e)).size) (tag0ByteSize cm.metaSharing.size.toUInt64) let refsBytes := tag0ByteSize cm.metaRefs.size.toUInt64 + cm.metaRefs.size * 32 @@ -1046,7 +1074,94 @@ def constantMetaIndexedByteSize (cm : ConstantMeta) (idx : NameIndex) : Nat := (fun acc p => acc + tag0ByteSize p.arenaIdx + p.univIdxs.foldl (fun a i => a + tag0ByteSize i) (tag0ByteSize p.univIdxs.size.toUInt64)) (tag0ByteSize cm.univPatches.size.toUInt64) - infoBytes + sharingBytes + refsBytes + univsBytes + patchesBytes + (masks, infoBytes + sharingBytes + refsBytes + univsBytes + patchesBytes) + +/-- Serialize `ConstantMetaInfo` using precomputed arena implicit-slot `masks`. -/ +def putConstantMetaInfoWithMasks (cm : ConstantMetaInfo) (masks : ByteArray) (idx : NameIndex) : + PutM Unit := do + match cm with + | .empty => putU8 255 + | .defn name lvls all ctx arena typeRoot valueRoot => + putU8 0 + putIdx name idx + putIdxVec lvls idx + putIdxVec all idx + putIdxVec ctx idx + putExprMetaArenaWithMasks arena masks idx + putTagN 0 0 typeRoot + putTagN 0 0 valueRoot + | .axio name lvls arena typeRoot => + putU8 1 + putIdx name idx + putIdxVec lvls idx + putExprMetaArenaWithMasks arena masks idx + putTagN 0 0 typeRoot + | .quot name lvls arena typeRoot => + putU8 2 + putIdx name idx + putIdxVec lvls idx + putExprMetaArenaWithMasks arena masks idx + putTagN 0 0 typeRoot + | .indc name lvls ctors all ctx arena typeRoot => + putU8 3 + putIdx name idx + putIdxVec lvls idx + putIdxVec ctors idx + putIdxVec all idx + putIdxVec ctx idx + putExprMetaArenaWithMasks arena masks idx + putTagN 0 0 typeRoot + | .ctor name lvls induct arena typeRoot => + putU8 4 + putIdx name idx + putIdxVec lvls idx + putIdx induct idx + putExprMetaArenaWithMasks arena masks idx + putTagN 0 0 typeRoot + | .recr name lvls rules all ctx arena typeRoot ruleRoots => + putU8 5 + putIdx name idx + putIdxVec lvls idx + putIdxVec rules idx + putIdxVec all idx + putIdxVec ctx idx + putExprMetaArenaWithMasks arena masks idx + putTagN 0 0 typeRoot + putTagN 0 0 ruleRoots.size.toUInt64 + for r in ruleRoots do putTagN 0 0 r + | .muts all auxLayout => + putU8 6 + putTagN 0 0 all.size.toUInt64 + for cls in all do + putIdxVec cls idx + match auxLayout with + | none => putU8 0 + | some layout => + putU8 1 + putTagN 0 0 layout.perm.size.toUInt64 + for p in layout.perm do putTagN 0 0 p + putTagN 0 0 layout.sourceCtorCounts.size.toUInt64 + for c in layout.sourceCtorCounts do putTagN 0 0 c + let evaporated := if layout.evaporated.isEmpty && !layout.perm.isEmpty + then Array.replicate layout.perm.size 0 else layout.evaporated + putTagN 0 0 evaporated.size.toUInt64 + for b in evaporated do putU8 (if b != 0 then 1 else 0) + +/-- Serialize `ConstantMeta` using precomputed arena implicit-slot `masks`. -/ +def putConstantMetaWithMasks (cm : ConstantMeta) (masks : ByteArray) (idx : NameIndex) : + PutM Unit := do + putConstantMetaInfoWithMasks cm.info masks idx + putTagN 0 0 cm.metaSharing.size.toUInt64 + for e in cm.metaSharing do putExpr e + putTagN 0 0 cm.metaRefs.size.toUInt64 + for a in cm.metaRefs do Serialize.put a + putTagN 0 0 cm.metaUnivs.size.toUInt64 + for u in cm.metaUnivs do putUniv u + putTagN 0 0 cm.univPatches.size.toUInt64 + for p in cm.univPatches do + putTagN 0 0 p.arenaIdx + putTagN 0 0 p.univIdxs.size.toUInt64 + for i in p.univIdxs do putTagN 0 0 i def getConstantMetaInfoIndexed (rev : NameReverseIndex) : GetM ConstantMetaInfo := do let cm ← match ← getU8 with @@ -1776,13 +1891,6 @@ partial def topologicalSortNames (names : Std.HashMap Address Ix.Name) : @[inline] def runPutWithCapacity (cap : Nat) (p : PutM Unit) : ByteArray := (p.run (ByteArray.emptyWithCapacity cap)).2 -/-- Exact byte length of the §5 metadata blob for `namedEntry`. -/ -def namedMetaBlobByteSize (namedEntry : Named) (nameIdx : NameIndex) : Nat := - constantMetaIndexedByteSize namedEntry.constMeta nameIdx + - match namedEntry.original with - | none => 1 - | some (_, origMeta) => 1 + 32 + constantMetaIndexedByteSize origMeta nameIdx - /-- §5 entries `named[lo:hi]`, exactly as `putEnv` writes them one after the other: the entry's §4 name index, its §2 constant rank, its exact hint, and its length-prefixed metadata blob. -/ @@ -1803,16 +1911,23 @@ def putNamedEntries (named : Array (Ix.Name × Named)) (lo hi : Nat) putFusedOptHint namedEntry.hints -- Metadata blob: ConstantMeta + original as Option (0 = none, -- 1 = some(addr, meta)), length-prefixed, written directly without - -- allocating a temporary ByteArray per entry. - let blobLen := namedMetaBlobByteSize namedEntry nameIdx - putTagN 0 0 blobLen.toUInt64 - putConstantMetaIndexed namedEntry.constMeta nameIdx + -- allocating a temporary ByteArray per entry and computing the arena + -- implicit-slot masks once for both byte-size accounting and writing. + let (constMasks, constBytes) := constantMetaMasksAndByteSize namedEntry.constMeta nameIdx match namedEntry.original with - | none => putU8 0 + | none => + let blobLen := constBytes + 1 + putTagN 0 0 blobLen.toUInt64 + putConstantMetaWithMasks namedEntry.constMeta constMasks nameIdx + putU8 0 | some (origAddr, origMeta) => + let (origMasks, origBytes) := constantMetaMasksAndByteSize origMeta nameIdx + let blobLen := constBytes + 1 + 32 + origBytes + putTagN 0 0 blobLen.toUInt64 + putConstantMetaWithMasks namedEntry.constMeta constMasks nameIdx putU8 1 Serialize.put origAddr - putConstantMetaIndexed origMeta nameIdx + putConstantMetaWithMasks origMeta origMasks nameIdx /-- Entries per task when §5 is written in parallel (`putEnv`). -/ def namedChunk : Nat := 2048 @@ -2767,11 +2882,17 @@ structure RawEnvLazy where that share `buf`. `buf` MUST be the exact buffer passed to `rs_de_env_lazy` (offsets are relative to it). -/ def RawEnvLazy.toEnv (raw : RawEnvLazy) (buf : ByteArray) : Env := Id.run do - let mut env : Env := { main := raw.main + let mut env : Env := { consts := Std.HashMap.emptyWithCapacity raw.consts.size + named := Std.HashMap.emptyWithCapacity raw.named.size + blobs := Std.HashMap.emptyWithCapacity raw.blobs.size + addrToName := Std.HashMap.emptyWithCapacity raw.named.size + main := raw.main roleBinding := raw.roleBinding - assumptions := raw.assumptions.foldl (·.insert ·) {} + assumptions := raw.assumptions.foldl (·.insert ·) + (Std.HashSet.emptyWithCapacity raw.assumptions.size) anonHints := raw.anonHints.foldl - (fun m (a, h) => m.insert a h) {} } + (fun m (a, h) => m.insert a h) + (Std.HashMap.emptyWithCapacity raw.anonHints.size) } for ⟨addr, offset, len⟩ in raw.consts do env := { env with consts := env.consts.insert addr diff --git a/Ix/Ixon/Authenticated.lean b/Ix/Ixon/Authenticated.lean index f32559482..16aa8e981 100644 --- a/Ix/Ixon/Authenticated.lean +++ b/Ix/Ixon/Authenticated.lean @@ -21,8 +21,8 @@ An Ixon address is BLAKE3 of exact bytes, with no tag or length prefix: a record's is BLAKE3 of its complete serialization (`Ixon.serConstant`, the bytes an `.ixe` stores for it after its length prefix), a projection record's likewise, and a blob's is BLAKE3 of its payload. This is how the -compiler addresses them (`crates/compile`, `Address::hash` over -`Constant::put`), how the `.ixe` loaders verify them (`Ixon.getEnv`, +compiler addresses them (`Ix.CompileM`, `Address.blake3` over +`Ixon.serConstant`), how the `.ixe` loaders verify them (`Ixon.getEnv`, `Ixon.getEnvVerifiedLazy`), and how the resource adapter commits them (`Ix.Resource.Addressed.commit`). diff --git a/Ix/Ixon/CheckOrder.lean b/Ix/Ixon/CheckOrder.lean index a4d88e79a..e8efa6083 100644 --- a/Ix/Ixon/CheckOrder.lean +++ b/Ix/Ixon/CheckOrder.lean @@ -41,10 +41,12 @@ over table references (and the `extra` edges), with projections replaced by their owners; `first` comes first, in its own order. -/ def order (store : RecordStore) (extra : Std.HashMap Address (Array Address)) (first : Array Address) : Array Address := Id.run do - let mut done : Std.HashSet Address := {} - let mut active : Std.HashSet Address := {} - let mut out : Array Address := #[] - let roots := first ++ (store.toArray.qsort (fun a b => a.1.cmpBytes b.1 == .lt)).map (·.1) + let mut done : Std.HashSet Address := Std.HashSet.emptyWithCapacity store.size + let mut active : Std.HashSet Address := Std.HashSet.emptyWithCapacity 256 + let mut out : Array Address := Array.mkEmpty store.size + let mut keys : Array Address := Array.mkEmpty store.size + for (a, _) in store do keys := keys.push a + let roots := first ++ keys.qsort (fun a b => a.cmpBytes b == .lt) for address in roots do let some source := store[address]? | continue let root := owner address source @@ -62,11 +64,17 @@ def order (store : RecordStore) (extra : Std.HashMap Address (Array Address)) active := active.insert node stack := stack.push (node, true) let refs := (store[node]?.map (·.refs)).getD #[] - for ref in refs ++ extra.getD node #[] do + for ref in refs do if let some target := store[ref]? then let dependency := owner ref target unless dependency == node || done.contains dependency || active.contains dependency do stack := stack.push (dependency, false) + if let some ex := extra[node]? then + for ref in ex do + if let some target := store[ref]? then + let dependency := owner ref target + unless dependency == node || done.contains dependency || active.contains dependency do + stack := stack.push (dependency, false) return out /-- Only the records reachable from `roots` (with projections replaced by @@ -85,8 +93,11 @@ def closure (store : RecordStore) (extra : Std.HashMap Address (Array Address)) let o := owner a c if reach.contains o then continue reach := reach.insert o - for r in ((store[o]?.map (·.refs)).getD #[]) ++ extra.getD o #[] do + for r in (store[o]?.map (·.refs)).getD #[] do if store.contains r then todo := todo.push r + if let some ex := extra[o]? then + for r in ex do + if store.contains r then todo := todo.push r return sub.filter reach.contains @@ -102,12 +113,20 @@ def dependencies (store : RecordStore) (index : RecIndex) (extra : Std.HashMap A (address : Address) (source : Ixon.Constant) : Array Address := Std.HashSet.toArray <| Id.run do let recs := recursorRecords index address let mut out : Std.HashSet Address := {} - for r in #[address] ++ recs do + let addRef (out : Std.HashSet Address) (ref : Address) : Std.HashSet Address := + match store[ref]? with + | some target => + let o := owner ref target + if o == address || recs.contains o then out else out.insert o + | none => out + for ref in source.refs do out := addRef out ref + if let some ex := extra[address]? then + for ref in ex do out := addRef out ref + for r in recs do let refs := if r == address then source.refs else ((store[r]?.map (·.refs)).getD #[]) - for ref in refs ++ extra.getD r #[] do - if let some target := store[ref]? then - let o := owner ref target - unless o == address || recs.contains o do out := out.insert o + for ref in refs do out := addRef out ref + if let some ex := extra[r]? then + for ref in ex do out := addRef out ref return out /-- The certified `Nat` operations' certificate ground as extra edges diff --git a/Ix/IxonUniv.lean b/Ix/IxonUniv.lean index 1c7f12f38..4171d0bc1 100644 --- a/Ix/IxonUniv.lean +++ b/Ix/IxonUniv.lean @@ -7,8 +7,6 @@ public import Batteries.Recycling.RBTree.Basic /-! Universe-level canonicalization (canonicity §10.6). -Mirror: crates/ixon/src/canon_univ.rs - `canonUniv = linearize ∘ subsumption ∘ normalizeAux` — a total, deterministic canonical representative of a stored level's Géran semantic-equality class, kernel-free and next to the wire type so diff --git a/Ix/KernelCheck.lean b/Ix/KernelCheck.lean index 9d4840263..f5f819349 100644 --- a/Ix/KernelCheck.lean +++ b/Ix/KernelCheck.lean @@ -1,188 +1,32 @@ -/- - Kernel typechecking FFI bindings. - - Exposes `rsCheckConstsFFI` and the `CheckError` ADT shared by: - - `Ix.Cli.CheckCmd` — the `lake exe ix check` CLI entry point. - - `Tests.Ix.Kernel.Tutorial` — the targeted-batch test harness. - - `Tests.Ix.Kernel.CheckEnv` — the full-environment test runner. - - Centralising the binding means the FFI symbol (`rs_kernel_check_consts`, - defined in `src/ffi/kernel.rs`) has a single Lean-side `@[extern]` - declaration, and every caller agrees on the `CheckError` constructor - layout (tag 0 = `kernelException`, tag 1 = `compileError`). --/ module public import Lean.Data.Name public import Lean.Declaration public import Ix.Address +/-! + Environment extraction and static sharding FFI bindings. +-/ + public section namespace Ix.KernelCheck -/-- Type-check errors returned from the Rust kernel FFI. - - Two variants: - - `kernelException msg` — rejection during kernel typechecking (tag 0). - - `compileError msg` — rejection during `compile_env` (tag 1), emitted - when `compile_env`'s tolerant scheduler records a block as ungrounded - (e.g. `inductBadNonSort` failing `compute_is_large_and_k`). - - **Important**: keep at least two constructors so Lean's LCNF trivial - structure optimization does NOT elide the enum to just `String`. With - only one ctor + one field, `hasTrivialStructure?` fires and the runtime - representation becomes identical to `String`, which breaks any FFI that - allocates a heap ctor. See - `refs/lean4/src/Lean/Compiler/LCNF/MonoTypes.lean:20-28`. - - Tags are stable across the Rust FFI — see `KERNEL_EXCEPTION_TAG` and - `COMPILE_ERROR_TAG` in `src/ffi/kernel.rs`. -/ +/-- Kernel or compiler check error returned by test and CLI check helpers. -/ inductive CheckError where | kernelException (msg : String) - | compileError (msg : String) - deriving Repr + | compileError (msg : String) + deriving Repr, BEq -/-- Render a `CheckError` as a single-line, prefixed message suitable for - log lines. Pulls the message string out of either ctor without going - through `repr` — derived `Repr` for long multi-line kernel diagnostics - is seconds-slow per call and can make a check appear to hang. -/ def CheckError.message : CheckError → String | .kernelException m => s!"kernel: {m}" - | .compileError m => s!"compile: {m}" - -/-- FFI: type-check a batch of constants through the full pipeline - (Lean env → Ixon compile → kernel ingress → typecheck). - - Implemented in `src/ffi/kernel.rs::rs_kernel_check_consts`. Note: this - used to be gated behind the `test-ffi` Cargo feature. It is now part - of the production build so `lake exe ix check` can drive it directly. - - The trailing `Bool` toggles ephemeral progress printing on the Rust - side: - - `false` (verbose): every constant is logged on its own line with - elapsed time and `def_eq` depth — ideal for small, targeted batches - where every result matters. - - `true` (quiet / ephemeral): the current `[i/N] name ...` label is - rewritten in place, and only slow constants (>=7s by default), unexpected - passes/failures, and ungrounded compile errors are promoted to - persistent lines. Ideal for full-env runs where thousands of fast - constants would otherwise swamp the log. Parallel quiet mode also - prints periodic done/total, rate, ETA, and oldest in-flight - constants. Tune with `IX_KERNEL_CHECK_PROGRESS_MS`, - `IX_KERNEL_CHECK_SLOW_MS`, `IX_KERNEL_CHECK_ACTIVE_SLOW_MS`, and - `IX_KERNEL_CHECK_INFLIGHT`. - - Results come back in input-array order — the caller pairs each - `results[i]` with its `names[i]`. We pass `Lean.Name` structurally - (rather than shipping `name.toString` strings) because Lean's - default `toString` wraps non-identifier components in `«…»`, and - round-tripping that through a Rust string parser was brittle: - names like `Lean.Order.«term_⊑_»` failed lookup against the - kernel's unescaped `Lean.Order.term_⊑_` key. Rust decodes each - `Lean.Name` structurally via `decode_name_array`, so the kernel - lookup is an exact structural match. -/ -@[extern "rs_kernel_check_consts"] -opaque rsCheckConstsFFI : - @& List (Lean.Name × Lean.ConstantInfo) → - @& Array Lean.Name → - @& Array Bool → - @& Bool → - IO (Array (Option CheckError)) - -/-- FFI: type-check constants from a serialized Ixon env file produced by - `ix compile --out`. If the name array is empty, Rust checks every - checkable named constant in the file. - - The trailing `String` is the `--fail-out` path. An empty string means - "no streaming"; any other value is a filesystem path that Rust opens - truncate-create and incrementally appends one record per failing - constant to (with an immediate flush per record), capping with a - `# total failures: N` footer once all checks finish. The format is the - same one `Ix.Cli.CheckIxonCmd.readNamesFile` reads, so the same file - is round-trippable as a `--consts-file` input. Streaming from Rust is - what makes a long full-env run visible to a `tail -f` observer instead - of dumping every failure only at the very end. -/ -@[extern "rs_kernel_check_ixon"] -opaque rsCheckIxonFFI : - @& String → - @& Array Lean.Name → - @& Array Bool → - @& Bool → - @& String → - IO (Array (Option CheckError)) - -/-- FFI: list checkable names from a serialized Ixon env file. Used by the - `ix check` CLI to support `--ns` filtering without rebuilding Lean. -/ -@[extern "rs_kernel_ixon_names"] -opaque rsIxonNamesFFI : @& String → IO (Array Lean.Name) - -/-- FFI: metadata-free anonymous-mode type-check of an entire `.ixe`. - - Loads the env via `IxonEnv::get_anon_mmap` (which mmaps the file - and discards the `named`/`names`/`comms` sections during - deserialization), enumerates every kernel-checkable target by - iterating `consts` (skipping projection constants — they're - covered by their parent Muts block work item, with projection - addresses reconstructed deterministically via `Constant::commit`), - and runs `TypeChecker::check_const` on each. The kernel's - typechecking logic structurally cannot read metadata: every - `M::MField` is `()` in Anon mode. - - `fail_out` is a streaming failure log path; pass `""` to disable. - - Returns an array of `(hex_address, Option CheckError)` pairs — one - per kernel-checkable target. The kernel has no name to associate - with each slot, so the address travels back so the Lean CLI can - print `#` failure labels matching the Rust progress and - fail-out output. -/ -@[extern "rs_kernel_check_anon"] -opaque rsCheckAnonFFI : - @& String → -- .ixe path - @& Bool → -- quiet - @& String → -- fail-out path ("" = none) - IO (Array (String × Option CheckError)) - -/-- FFI: anon-mode type-check of named constants with (by default) their full - dependency closures. The `Bool` after the names is `skip-deps`: `true` - checks only each name's own work item (subject-only; deps trusted). - - Names are the constants' displayed forms (e.g. `"Nat.add_comm"`, - `"_private.Init.….instRxcHasSize_eq"`), resolved through the env's `named` - metadata by string match, after - which the check runs on the anon view (the kernel never sees names). A - member of a mutual block selects the whole block's work item. Multiple - names union their closures into one check set. - - The trailing `String` is the results-rows JSON path (`""` = off). When - set, each name is instead checked as its own independent closure run — - the zkVM hosts' per-constant scope — with the env still loaded once, and - the row `{ name: { status, constants, check-time, throughput, peak-rss } }` - flushed per name (see `Ix.Benchmark.Results` for the contract). The - per-name `check-time`/`peak-rss` window wraps only that name's closure - selection + check, so rows aren't distorted by the env load; meaningful - `peak-rss` needs `TracingTexray.startSampler`. Not combinable with a - fail-out path. - - Returns `(hex_address, Option CheckError)` pairs, one per checked target, - exactly like `rsCheckAnonFFI` (concatenated across the per-name runs when - rows are on). Errors (rather than returning) when a name doesn't resolve, - so a typo can't silently produce an empty check. -/ -@[extern "rs_kernel_check_anon_consts"] -opaque rsCheckAnonConstsFFI : - @& String → -- .ixe path - @& Array String → -- constant names (displayed form) - @& Bool → -- skip-deps (subject-only) - @& Bool → -- quiet - @& String → -- fail-out path ("" = none) - @& String → -- benchmark results JSON path ("" = off) - IO (Array (String × Option CheckError)) + | .compileError m => s!"compile: {m}" /-- FFI: extract the named constants' dependency closure from a serialized env into a standalone `.ixe` — genuine constant bytes, blobs, and reducibility hints, plus the closure constants' Named entries so names - still resolve — without recompiling from source. Names resolve like - `rsCheckAnonConstsFFI` (displayed form); a mutual-block member pulls its - whole block. Errors on an unresolvable name. -/ + still resolve — without recompiling from source. Names resolve by + displayed form; a mutual-block member pulls its whole block. Errors on an + unresolvable name. -/ @[extern "rs_env_extract"] opaque rsEnvExtractFFI : @& String → -- source .ixe path @@ -194,10 +38,10 @@ opaque rsEnvExtractFFI : /-- FFI: partition a `.ixe` with the STATIC strategy (no out-of-circuit profiling). Layout `mincut`: byte-balanced min-cut over the static walk-edge nets + a predicted-FFT-cost rebalance post-pass - (`ix_kernel::shard::shard_static`; model constants documented there). + (`ixon::shard::shard_static`; model constants documented there). Layout `ordered`: contiguous ranges of a dependency order, shard 0 at the top, so a shard's reference closure lies in its own shard and later - ones (`ix_kernel::shard::shard_static_ordered`; what the distributed + ones (`ixon::shard::shard_static_ordered`; what the distributed prover wants). A nonzero `numShards` fixes the count; otherwise `ramGib` selects the static block-shape seed after the `.ixe` has been profiled. Writes a `.ixes` manifest; prints the report to stderr. -/ @@ -224,45 +68,6 @@ opaque rsShardStaticGraphFFI : @& String → -- output text path IO Unit -/-- FFI: profile a `.ixe` out of circuit, writing a `.ixprof` sidecar with - per-block heartbeats + the delta-unfold graph (the sharding cost model). - Runs the anon kernel over every checkable target. - `isolate` clears the kernel's reduction-memo caches between constants for - sound/faithful recording; `quiet` suppresses per-constant progress. -/ -@[extern "rs_kernel_profile_anon"] -opaque rsProfileAnonFFI : - @& String → -- .ixe path - @& String → -- .ixprof output path - @& Bool → -- isolate caches - @& Bool → -- quiet - IO Unit - -/-- FFI: partition a `.ixprof` into `numShards` shards, writing a `.ixes` - manifest. Empty `outPath` skips the manifest. Prints a what-if report to - stderr. -/ -@[extern "rs_shard_esp"] -opaque rsShardEspFFI : - @& String → -- .ixprof path - USize → -- num_shards (N) - UInt64 → -- balance percent - USize → -- parallelism (provers for prove-time est) - @& String → -- .ixes output path ("" = skip) - IO Unit - -/-- FFI: partition a `.ixprof` to a per-shard cycle/RAM budget, writing a - `.ixes` manifest. `maxCycles` is a guest-STEP cap; if `ramGib` > 0 it is - converted via the measured prover RAM model and overrides `maxCycles`. Pass - 0 for whichever is unused. -/ -@[extern "rs_shard_esp_cap"] -opaque rsShardEspCapFFI : - @& String → -- .ixprof path - UInt64 → -- max_cycles (0 = unset) - UInt64 → -- max_ram GiB (0 = unset) - UInt64 → -- balance percent - USize → -- parallelism (provers for prove-time est) - @& String → -- .ixes output path ("" = skip) - IO Unit - end Ix.KernelCheck end diff --git a/Ix/KernelCheck/Binding.lean b/Ix/KernelCheck/Binding.lean index ba0d8cb49..1d9180d47 100644 --- a/Ix/KernelCheck/Binding.lean +++ b/Ix/KernelCheck/Binding.lean @@ -11,10 +11,14 @@ namespace Ix.KernelCheck.Binding def limits : Ix.Kernel.Admission.Limits := ⟨1 <<< 22, 1 <<< 22, 1 <<< 34, 1 <<< 28, 1 <<< 26⟩ +/-- The role table and prelude a harness reads an artifact under. A bound +artifact is admitted by `check-lean`'s preparation (`prepareRecords`), run +with its decoding in parallel (`prepareRecordsPar`, equal to it by +`prepareRecordsPar_eq`). -/ def context (env : Ixon.Env) : Except String (Ix.Kernel.RoleTable × Ix.Kernel.Reader.Prelude .anon) := do if env.roleBinding.isSome then - let input ← (Ix.RoleBinding.Artifact.prepareRecords env limits).mapError toString + let input ← (Ix.RoleBinding.Artifact.prepareRecordsPar env limits).mapError toString return (input.prepared.table, input.prepared.pre) let table ← Ix.Kernel.Reader.defaultTable let pre ← Ix.Kernel.Reader.preludeFor table diff --git a/Ix/KernelCheck/Pool.lean b/Ix/KernelCheck/Pool.lean index 487d000ba..58df19f95 100644 --- a/Ix/KernelCheck/Pool.lean +++ b/Ix/KernelCheck/Pool.lean @@ -44,9 +44,18 @@ open Benchmarks.Kernel.CheckIxeStep /-! ## Phase A -/ +/-- What one declaration installed during Phase A: the constants it prepended +to `fe.env.consts`, and the slice `[pendStart, pendStop)` of `pend` it recorded. -/ +structure InstalledDecl (md : Ix.Kernel.Mode := .anon) where + consts : List (Ix.Kernel.ConstantInfo md) := [] + pendStart : Nat := 0 + pendStop : Nat := 0 + /-- Phase A's step state: the installed environment, the memo state and the fold position (as `Checker`'s), every recorded check with the record it -belongs to, and the records whose install failed. -/ +belongs to, the records whose install failed, the number of records stepped, +and (for the typing tier) the per-declaration constants and check slices each +record installed, in install order. -/ structure Installer (md : Ix.Kernel.Mode := .anon) where fe : Ix.Kernel.FEnv md := Ix.Kernel.mkFEnv Ix.Kernel.Env.empty cs : Ix.Kernel.Cached.CState md := {} @@ -54,37 +63,93 @@ structure Installer (md : Ix.Kernel.Mode := .anon) where pend : Array (Ix.Kernel.Cached.PendingCheck md) := #[] owners : Array Address := #[] installErrors : Std.HashMap Address (Ix.Kernel.CheckError md) := {} + installedCount : Nat := 0 + installedLog : Array (Address × Array (InstalledDecl md)) := #[] instance : Inhabited (Installer md) := ⟨{}⟩ /-- A record's declarations installed in order from a fresh accumulator of recorded checks; the first failure ends it, rolled back as `Checker.step` rolls back (the index rebuilt from the constants before the declaration, -the memo state reset), with the checks the declarations before it recorded. -/ -def installDecls (ev : Ix.Kernel.Evidences md) : - Nat → Ix.Kernel.FEnv md → Array (Ix.Kernel.Cached.PendingCheck md) → Ix.Kernel.Cached.CState md → +the memo state reset), with the checks the declarations before it recorded. +`pendOffset` is the number of checks recorded before the record, so that the +slices in `InstalledDecl` index the global accumulator. -/ +def installDecls (ev : Ix.Kernel.Evidences md) (pendOffset : Nat) (recordDecls : Bool := true) + (allowUserAxioms : Bool := false) : + Nat → Ix.Kernel.FEnv md → Array (Ix.Kernel.Cached.PendingCheck md) → + Array (InstalledDecl md) → Ix.Kernel.Cached.CState md → List (Ix.Kernel.Declaration md) → - Nat × Ix.Kernel.FEnv md × Array (Ix.Kernel.Cached.PendingCheck md) × Ix.Kernel.Cached.CState md × + Nat × Ix.Kernel.FEnv md × Array (Ix.Kernel.Cached.PendingCheck md) × + Array (InstalledDecl md) × Ix.Kernel.Cached.CState md × Option (Ix.Kernel.CheckError md) - | pos, fe, pend, cs, [] => (pos, fe, pend, cs, none) - | pos, fe, pend, cs, d :: rest => + | pos, fe, pend, decls, cs, [] => (pos, fe, pend, decls, cs, none) + | pos, fe, pend, decls, cs, d :: rest => let before := fe.env let recorded := pend - match Ix.Kernel.Cached.annotDeclStep .verified ev (pos, fe, pend) d cs with - | .ok ((pos', fe', pend'), cs') => installDecls ev pos' fe' pend' cs' rest - | .error (e, _) => (pos + 1, Ix.Kernel.mkFEnv before, recorded, {}, some e) - -/-- Phase A's step: install a record's declarations, recording their checks. -/ -def Installer.stepRecord (ev : Ix.Kernel.Evidences md) (c : Installer md) (address : Address) - (rd : Read md) : Installer md × Option (Ix.Kernel.CheckError md) := - let ⟨fe, cs, pos, pend, owners, installErrors⟩ := c - let (pos, fe, recorded, cs, err) := installDecls ev pos fe #[] cs rd.decls.toList - let owners := owners ++ Array.replicate recorded.size address + let oldLen := fe.visibleBelow + let pStart := pendOffset + recorded.size + let stepFn := if allowUserAxioms then + Ix.Kernel.Cached.annotDeclStepWithUserAxioms .verified ev + else + Ix.Kernel.Cached.annotDeclStep .verified ev + match stepFn (pos, fe, pend) d cs with + | .ok ((pos', fe', pend'), cs') => + let decls := if recordDecls then + let added := fe'.env.consts.take (fe'.visibleBelow - oldLen) + let pStop := pendOffset + pend'.size + decls.push { consts := added, pendStart := pStart, pendStop := pStop } + else decls + installDecls ev pendOffset recordDecls allowUserAxioms pos' fe' pend' decls cs' rest + | .error (e, _) => (pos + 1, Ix.Kernel.mkFEnv before, recorded, decls, {}, some e) + +/-- Phase A's step: install a record's declarations, recording their checks +(and, with `recordDecls`, what each declaration installed). -/ +def Installer.stepRecord (ev : Ix.Kernel.Evidences md) (recordDecls : Bool := true) + (allowUserAxioms : Bool := false) + (c : Installer md) (address : Address) (rd : Read md) : + Installer md × Option (Ix.Kernel.CheckError md) := + let ⟨fe, cs, pos, pend, owners, installErrors, installedCount, installedLog⟩ := c + -- the record's checks go to a fresh accumulator, appended afterwards: + -- `installDecls` keeps the accumulator from before each declaration alive + -- across its step (its rollback), so a step pushing onto the global + -- accumulator would copy all of it once per declaration + let (pos, fe, recorded, decls, cs, err) := + installDecls ev pend.size recordDecls allowUserAxioms pos fe #[] #[] cs rd.decls.toList + let owners := Id.run do + let mut o := owners + for _ in [0:recorded.size] do o := o.push address + return o let pend := pend ++ recorded let installErrors := match err with | some e => installErrors.insert address e | none => installErrors - (⟨fe, cs, pos, pend, owners, installErrors⟩, err) + let installedLog := if recordDecls then installedLog.push (address, decls) else installedLog + (⟨fe, cs, pos, pend, owners, installErrors, installedCount + 1, installedLog⟩, err) + +/-- Reconstruct the checked logical index after Phase B: if no check failed +and no installed record was later blocked, `fe` is already exact; otherwise +keep only the declarations of non-blocked records (`ran`, in execution order) +up to the first declaration in each record whose Phase B checks failed. -/ +def checkedLogicalEnv (fe : Ix.Kernel.FEnv md) (failures : Nat) + (installedLog : Array (Address × Array (InstalledDecl md))) (installedCount : Nat) + (table : Array (Option (Ix.Kernel.CheckError md) × Nat)) + (ran : Array Address) : Ix.Kernel.FEnv md := Id.run do + if failures == 0 && ran.size == installedCount then + return fe + let installedDecls : Std.HashMap Address (Array (InstalledDecl md)) := + installedLog.foldl (init := Std.HashMap.emptyWithCapacity installedLog.size) + fun m (a, ds) => m.insert a ds + let mut consts : List (Ix.Kernel.ConstantInfo md) := [] + for a in ran do + for d in installedDecls.getD a #[] do + let mut failed := false + for k in [d.pendStart : d.pendStop] do + if (table[k]?.bind (·.1)).isSome then + failed := true + if failed then + break + consts := d.consts ++ consts + return Ix.Kernel.mkFEnv ⟨consts⟩ /-! ## Phase B -/ @@ -139,47 +204,89 @@ def pool (fe : Ix.Kernel.FEnv md) (pend : Array (Ix.Kernel.Cached.PendingCheck m /-- The environment check over `addresses` with phase B on one worker per slot (`slots`, the watchdog's; at least one). Rows go to `emit`, in the -per-record check's order; stage lines go to `log`. Returns the actual logical-record outcome -and failed-root map. Tier records are not enabled by this logical-only pool; -the normal runner uses the serial tier loop when requested. -/ +per-record check's order; stage lines go to `log`. Returns the actual record outcome +and failed-root map. When `tier` is supplied, tier records are deferred during +Phase A and checked in staged order against the checked logical environment +after Phase B. -/ def runOutcome {σ : Type} (src : LoopSource σ md) (ev : Ix.Kernel.Evidences md) (names : Address → Array String) (addresses : Array Address) (skip : Std.HashSet String) (slots : Array Slot) (emit : Row → IO Unit) (log : String → IO Unit) (progress : Nat → Std.HashMap String Nat → IO Unit := fun _ _ => pure ()) (onRead : Address → Except ReadError (Read md) → Nat → IO Unit := fun _ _ _ => pure ()) - (spell : Ix.Kernel.Key md → String := toString) : + (spell : Ix.Kernel.Key md → String := toString) + (tier : Option (TierHook σ (Checker md) md) := none) + (allowUserAxioms : Bool := false) : IO (LoopOutcome Unit) := do let some slot0 := slots[0]? | throw (IO.userError "check-ixe: the pool needs a worker") -- phase A: what each record's rows will carry besides the verdict - let times ← IO.mkRef ({} : Std.HashMap Address (Nat × Nat)) + let times ← IO.mkRef (Std.HashMap.emptyWithCapacity addresses.size : Std.HashMap Address (Nat × Nat)) let readErrors ← IO.mkRef ({} : Std.HashMap Address ReadError) let recOwner ← IO.mkRef ({} : Std.HashMap Address Address) + let views ← IO.mkRef (Std.HashMap.emptyWithCapacity addresses.size : Std.HashMap Address RecordView) let tA ← IO.monoMsNow let st ← src.init - let installed ← checkLoopWith src.view src.read src.commit st (Installer.stepRecord ev) {} + let mut tierReads : Std.HashMap Address (Except ReadError (Ix.Kernel.Tier.TierDecl md) × Nat) := {} + if let some th := tier then + let knownTier := th.tierAddresses <|> src.tierAddresses + let scanTier : Bool := match knownTier with + | some ta => !ta.isEmpty + | none => true + if scanTier then + for address in addresses do + if let some ta := knownTier then + unless ta.contains address do continue + let r0 ← IO.monoNanosNow + let res? ← IO.lazyPure fun _ => th.read st address + if let some res := res? then + let readMicros := ((← IO.monoNanosNow) - r0) / 1000 + tierReads := tierReads.insert address (res, readMicros) + if let some v := src.view address then + let v' := if res.isOk then let d := v.deps (); { v with deps := fun _ => d } else v + views.modify (·.insert address v') + let tierAddrSet : Std.HashSet Address := + tierReads.fold (fun s a _ => s.insert a) {} + let tierHookA : Option (TierHook σ (Installer md) md) := + tier.map fun _ => + { read := fun _ a => (tierReads[a]?).map fun (res, _) => res.map fun _ => .defs .part [] + index := fun _ => Ix.Kernel.mkFEnv Ix.Kernel.Env.empty + tierAddresses := some tierAddrSet } + let needDecls := tier.isSome && !tierReads.isEmpty + let startInstaller : Installer md := + { pend := Array.mkEmpty addresses.size + owners := Array.mkEmpty addresses.size + installedLog := if needDecls then Array.mkEmpty addresses.size else #[] } + let installed ← checkLoopWith src.view src.read src.commit st + (Installer.stepRecord ev needDecls allowUserAxioms) startInstaller names addresses skip - (emit := fun row => times.modify (·.insert row.address (row.micros, row.readMicros))) + (emit := fun row => + unless tierReads.contains row.address do + times.modify (·.insert row.address (row.micros, row.readMicros))) (before := fun a => do slot0.set (some (a, ← IO.monoMsNow))) (after := slot0.set none) (progress := fun i o => progress i o.counts) (onRead := fun a reading micros => do if let .error e := reading then readErrors.modify (·.insert a e) - if let some v := src.view a then - for r in v.recs do recOwner.modify (·.insert r a) onRead a reading micros) + (tier := tierHookA) + (onView := fun a v => do + for r in v.recs do recOwner.modify (·.insert r a) + views.modify (·.insert a v)) let inst := installed.checker log s!"phase A: {installed.counts.toList} (accept: installed), {inst.pend.size} checks recorded, \ - {inst.fe.env.consts.length} constants, in {(← IO.monoMsNow) - tA} ms" + {inst.fe.visibleBelow} constants, in {(← IO.monoMsNow) - tA} ms" -- phase B: the workers share the installed environment and the checks let tM ← IO.monoMsNow - -- the memo state is only a cache; the rest is used to the end of the run - -- (`markPersistent` is `unsafe` because a persistent object is never freed) - let inst : Installer md := { inst with cs := {} } - let _ ← unsafe Runtime.markPersistent inst + -- the memo state is only a cache; only the worker-shared inputs are marked + -- persistent (`markPersistent` is `unsafe` because a persistent object is never freed) let fe := inst.fe let pend := inst.pend let owners := inst.owners let installErrors := inst.installErrors + let installedCount := inst.installedCount + let installedLog := inst.installedLog + let _ ← unsafe Runtime.markPersistent fe + let _ ← unsafe Runtime.markPersistent pend + let _ ← unsafe Runtime.markPersistent owners log s!"installed environment marked persistent in {(← IO.monoMsNow) - tM} ms" let tB ← IO.monoMsNow let table ← pool fe pend owners slots @@ -190,42 +297,59 @@ def runOutcome {σ : Type} (src : LoopSource σ md) (ev : Ix.Kernel.Evidences md -- the verdicts: a record's first failed check (in install order), else its -- install failure; and its checks' time let mut verdicts : Std.HashMap Address (Ix.Kernel.CheckError md) := {} - let mut checkMicros : Std.HashMap Address Nat := {} - for ((r, micros), k) in table.zipIdx do + let mut checkMicros : Std.HashMap Address Nat := Std.HashMap.emptyWithCapacity addresses.size + for h : k in [0:table.size] do + let (r, micros) := table[k] let some a := owners[k]? | continue checkMicros := checkMicros.insert a (checkMicros.getD a 0 + micros) if let some e := r then unless verdicts.contains a do verdicts := verdicts.insert a e - for (a, e) in installErrors.toList do + for (a, e) in installErrors do unless verdicts.contains a do verdicts := verdicts.insert a e -- the rows: the loop again, over phase A's readings and the verdicts let times ← times.get let readErrors ← readErrors.get let recOwner ← recOwner.get + let cachedViews ← views.get let rowMicros (row : Row) : Nat := if row.outcome == "blocked" then 0 else (times.getD row.address (0, 0)).1 + checkMicros.getD (recOwner.getD row.address row.address) 0 - let out ← checkLoopWith src.view + let tierHookB : Option (TierHook Unit (Array Address) md) := + tier.map fun _ => + { read := fun _ a => (tierReads[a]?).map (·.1) + index := fun ran => + checkedLogicalEnv fe failures installedLog installedCount table ran + tierAddresses := some tierAddrSet } + let out ← checkLoopWith (fun a => cachedViews[a]? <|> src.view a) (fun (_ : Unit) a => match readErrors[a]? with | some e => .error e | none => .ok {}) (fun _ _ _ => ()) () - (fun (_ : Unit) a _ => ((), verdicts[a]?)) () + (fun (ran : Array Address) a _ => (ran.push a, verdicts[a]?)) (Array.mkEmpty addresses.size) names addresses skip - (emit := fun row => emit { row with micros := rowMicros row, - readMicros := (times.getD row.address (0, 0)).2 }) + (emit := fun row => + match tierReads[row.address]? with + | some (_, readMicros) => + emit { row with micros := if row.outcome == "blocked" then 0 else row.micros, + readMicros } + | none => + emit { row with micros := rowMicros row, + readMicros := (times.getD row.address (0, 0)).2 }) (spell := spell) - return out + (tier := tierHookB) + return { checker := (), counts := out.counts, reasons := out.reasons, failed := out.failed } -/-- Compatibility count-pair API over the mode-generic logical pool. -/ +/-- Compatibility count-pair API over the mode-generic pool. -/ def run {σ : Type} (src : LoopSource σ md) (ev : Ix.Kernel.Evidences md) (names : Address → Array String) (addresses : Array Address) (skip : Std.HashSet String) (slots : Array Slot) (emit : Row → IO Unit) (log : String → IO Unit) (progress : Nat → Std.HashMap String Nat → IO Unit := fun _ _ => pure ()) (onRead : Address → Except ReadError (Read md) → Nat → IO Unit := fun _ _ _ => pure ()) - (spell : Ix.Kernel.Key md → String := toString) : + (spell : Ix.Kernel.Key md → String := toString) + (tier : Option (TierHook σ (Checker md) md) := src.tierHook) + (allowUserAxioms : Bool := false) : IO (Std.HashMap String Nat × Std.HashMap String Nat) := do - let out ← runOutcome src ev names addresses skip slots emit log progress onRead spell + let out ← runOutcome src ev names addresses skip slots emit log progress onRead spell tier allowUserAxioms return (out.counts, out.reasons) end Benchmarks.Kernel.CheckIxePool diff --git a/Ix/KernelCheck/Run.lean b/Ix/KernelCheck/Run.lean index 390157e60..9edd700fa 100644 --- a/Ix/KernelCheck/Run.lean +++ b/Ix/KernelCheck/Run.lean @@ -15,10 +15,12 @@ namespace Benchmarks.Kernel.CheckIxeStep open Ix.Kernel.Reader /-- Requested execution: `none` uses the incremental checker, `some n` the -logical worker pool. Tier support always uses the incremental checker. -/ +worker pool. With tier support, the incremental checker runs whatever +`workers` asks for. -/ structure RunConfig where workers : Option Nat := none tier : Bool := false + allowUserAxioms : Bool := false skip : Std.HashSet String := {} /-- The path actually taken. Tier fallback records the original request; @@ -44,10 +46,12 @@ structure RunReport where failed : Std.HashMap Address Address execution : Execution -/-- Run the supplied record source at its mode. The logical-only pool keeps -its install/check/replay protocol. Enabling tier support uses the existing -serial loop so tier checks see the checked logical state, never phase A's -unchecked installation environment. The returned execution path is explicit, +/-- Run the supplied record source at its mode. Without tier support, `some n` +workers run the pool (`CheckIxePool.runOutcome`) with its install/check/replay +protocol. With tier support this harness always runs the serial loop, whose +tier checks see the per-record checked state; the pool's own tier path +(rebuilding the checked logical index after phase B) is used only by +`kernel-check-ixe --jobs`. The returned execution path is explicit, even when a source happens to contain no tier records. Pool progress reports provisional phase-A install counts; only the returned report has the replayed verdicts. The report retains every emitted row, so its memory is linear in @@ -62,13 +66,17 @@ def runRecords (src : LoopSource σ md) (ev : Ix.Kernel.Evidences md) let rows ← IO.mkRef (#[] : Array Row) let note := fun row => do rows.modify (·.push row); emit row let execution := if cfg.tier then Execution.serialTier cfg.workers else - match cfg.workers with | none => .serial | some n => .pool n + match cfg.workers with + | none => .serial + | some n => .pool n let out : LoopOutcome Unit ← match execution with | .pool n => do let slots ← (Array.range n).mapM fun _ => IO.mkRef (none : Option (Address × Nat)) CheckIxePool.runOutcome src ev names addresses cfg.skip slots note log progress onRead spell + (allowUserAxioms := cfg.allowUserAxioms) | .serial | .serialTier _ => do - let out ← checkLoopWith src.view src.read src.commit (← src.init) (Checker.stepRecord ev) {} + let out ← checkLoopWith src.view src.read src.commit (← src.init) + (Checker.stepRecordWithUserAxioms ev cfg.allowUserAxioms) {} names addresses cfg.skip note (progress := fun i o => progress i o.counts) (onRead := onRead) (spell := spell) (tier := if cfg.tier then src.tierHook else none) pure { checker := (), counts := out.counts, reasons := out.reasons, failed := out.failed } diff --git a/Ix/KernelCheck/Step.lean b/Ix/KernelCheck/Step.lean index 96c61fd7b..1cd1e042c 100644 --- a/Ix/KernelCheck/Step.lean +++ b/Ix/KernelCheck/Step.lean @@ -53,11 +53,16 @@ structure Checker (md : Ix.Kernel.Mode) where instance : Inhabited (Checker md) := ⟨{}⟩ /-- Install and check one declaration; on failure, the state before it. -/ -def Checker.step (ev : Ix.Kernel.Evidences md) (c : Checker md) (d : Ix.Kernel.Declaration md) : +def Checker.step (ev : Ix.Kernel.Evidences md) (c : Checker md) (d : Ix.Kernel.Declaration md) + (allowUserAxioms : Bool := false) : Checker md × Option (Ix.Kernel.CheckError md) := let ⟨fe, cs, pos⟩ := c let before := fe.env - match Ix.Kernel.Cached.annotDeclStep .verified ev (pos, fe, #[]) d cs with + let stepFn := if allowUserAxioms then + Ix.Kernel.Cached.annotDeclStepWithUserAxioms .verified ev + else + Ix.Kernel.Cached.annotDeclStep .verified ev + match stepFn (pos, fe, #[]) d cs with | .error (e, _) => (⟨Ix.Kernel.mkFEnv before, {}, pos + 1⟩, some e) | .ok ((pos', fe', pend), cs') => match Ix.Kernel.Cached.checkPendingList .verified fe' pend.toList with @@ -66,10 +71,11 @@ def Checker.step (ev : Ix.Kernel.Evidences md) (c : Checker md) (d : Ix.Kernel.D /-- One record's declarations, in order; the first failure ends it. -/ def Checker.steps (ev : Ix.Kernel.Evidences md) (c : Checker md) - (ds : Array (Ix.Kernel.Declaration md)) : Checker md × Option (Ix.Kernel.CheckError md) := Id.run do + (ds : Array (Ix.Kernel.Declaration md)) (allowUserAxioms : Bool := false) : + Checker md × Option (Ix.Kernel.CheckError md) := Id.run do let mut c := c for d in ds do - let (c', e) := c.step ev d + let (c', e) := c.step ev d allowUserAxioms c := c' if e.isSome then return (c, e) return (c, none) @@ -120,8 +126,9 @@ structure Hints where def Hints.ofStore (store : RecordStore) (hints : Std.HashMap Address Lean.ReducibilityHints) : Hints := Id.run do - let mut projAt : Std.HashMap (ConstRef Address) Address := {} - for (a, c) in store.toList do + let mut projAt : Std.HashMap (ConstRef Address) Address := + Std.HashMap.emptyWithCapacity (store.size / 2) + for (a, c) in store do if let .dPrj p := c.info then projAt := projAt.insert (.member p.block p.idx.toNat) a return { projAt, hints } @@ -149,6 +156,18 @@ def Row.json (row : Row) : Lean.Json := Lean.Json.mkObj [ ("reason", Lean.toJson row.reason), ("micros", Lean.toJson row.micros), ("readMicros", Lean.toJson row.readMicros)] +/-- `row.json.compress`, written out without building the `Json` value (keys +in `Json`'s sorted order). `kernel-record-check` checks the two agree. -/ +def Row.jsonLine (row : Row) : String := + let namesStr := String.intercalate "," (row.names.toList.map Lean.Json.renderString) + "{\"address\":\"" ++ toString row.address ++ + "\",\"kind\":" ++ Lean.Json.renderString row.kind ++ + ",\"micros\":" ++ toString row.micros ++ + ",\"names\":[" ++ namesStr ++ + "],\"outcome\":" ++ Lean.Json.renderString row.outcome ++ + ",\"readMicros\":" ++ toString row.readMicros ++ + ",\"reason\":" ++ Lean.Json.renderString row.reason ++ "}" + /-- A field of `/proc/self/status` in kB (Linux only; 0 elsewhere). -/ def statusKb (field : String) : IO Nat := do let status ← (IO.FS.readFile "/proc/self/status" |>.toBaseIO) @@ -158,6 +177,44 @@ def statusKb (field : String) : IO Nat := do /-! ## The run -/ +/-- Pre-sized `keysOf` for `.anon` mode that resolves each record address once. -/ +def fastKeysOf (store : Store) (roles : Bindings .anon) + (records : Array (Address × Ixon.Constant)) : Keys .anon store roles := + let init : Keys .anon store roles := + { byAddress := Std.HashMap.emptyWithCapacity records.size + address := Std.HashMap.emptyWithCapacity records.size + byAddress_sound := by intro a k hk; simp at hk + address_sound := by intro r a hr; simp at hr } + records.foldl (init := init) fun k (a, _) => + match h : resolve store a with + | none => k + | some r => + let k := k.withAddress r a h + let key : CName .anon := match roles.names[r]? with + | some k => k + | none => .id a + have hKey : keyOfAddress store roles a = some key := by + dsimp only [key] + simp only [keyOfAddress, h, bind, Option.bind, Ix.Kernel.Mode.addressId?, Option.map] + cases roles.names[r]? <;> rfl + k.withKey a key hKey + +/-- Dependency edges from pre-computed `(address, nat, str)` literal-kind flags. -/ +def literalEdgesOfFlags (roles : Std.HashMap (ConstRef Address) (CName md)) + (flags : Array (Address × Bool × Bool)) : Std.HashMap Address (Array Address) := Id.run do + let byName : Std.HashMap (CName md) Address := roles.fold (fun m r n => m.insert n r.block) {} + let targets (names : List (CName md)) : Array Address := + names.foldl (fun acc n => match byName[n]? with + | some a => if acc.contains a then acc else acc.push a + | none => acc) #[] + let natTargets := targets natLitSupportNames + let strTargets := targets strLitSupportNames + let mut out : Std.HashMap Address (Array Address) := {} + for (a, nat, str) in flags do + let ts := (if str then strTargets else if nat then natTargets else #[]).filter (· != a) + unless ts.isEmpty do out := out.insert a ts + return out + /-- What an environment-check run reads: the store, the reader context and the order. -/ structure Setup where store : RecordStore @@ -179,7 +236,8 @@ are computed instead of from the store. -/ def setup (store : RecordStore) (blobs : Address → Option ByteArray) (t : Ix.Kernel.RoleTable) (pre : Prelude .anon) (hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint) - (literals : Option (Array (Address × Ixon.Constant)) := none) : Setup := Id.run do + (literals : Option (Array (Address × Ixon.Constant)) := none) + (literalFlags : Option (Array (Address × Bool × Bool)) := none) : Setup := Id.run do let mut store := store let mut added : Array (Address × Ixon.Constant) := #[] for (a, c) in pre.records do @@ -194,13 +252,24 @@ def setup (store : RecordStore) (blobs : Address → Option ByteArray) let evidence : Std.HashMap Address Ixon.Constant := pre.evidence.foldl (fun m (a, c) => if store.contains a then m else m.insert a c) {} let look : Address → Option Ixon.Constant := fun a => store[a]? <|> evidence[a]? - let cx : Ctx .anon := contextWith look blobs' t records hint - (Ix.Kernel.Admission.projectionAddress Address.blake3 look) - let roles := cx.roles - let literalRecords := match literals with - | some ls => ls ++ added - | none => records - let extra := mergeEdges (groundEdges roles.names) (literalEdges roles.names literalRecords) + let indexed := records.filter fun (_, c) => !(recursorMembers c).isEmpty + let roles := bindingsOf look t + let cx : Ctx .anon := + { store := look, blob := blobs', roles, index := buildIndex look indexed, hint, + reconstruct := Ix.Kernel.Admission.projectionAddress Address.blake3 look, + keys := fastKeysOf look roles records } + let litEdges := match literalFlags with + | some fs => + let allFlags := added.foldl (fun acc (a, c) => + let (nat, str) := literalKinds c + if nat || str then acc.push (a, nat, str) else acc) fs + literalEdgesOfFlags roles.names allFlags + | none => + let literalRecords := match literals with + | some ls => ls ++ added + | none => records + literalEdges roles.names literalRecords + let extra := mergeEdges (groundEdges roles.names) litEdges let first := pre.records.foldl (fun acc (a, c) => let o := owner a c if acc.contains o then acc else acc.push o) #[] @@ -252,6 +321,9 @@ the step state's environment. -/ structure TierHook (σ κ : Type) (md : Ix.Kernel.Mode := .anon) where read : σ → Address → Option (Except ReadError (Ix.Kernel.Tier.TierDecl md)) index : κ → Ix.Kernel.FEnv md + /-- optional known set of tier record addresses; when `some`, only these + addresses are queried via `read` -/ + tierAddresses : Option (Std.HashSet Address) := none /-- A tier record's declaration, read against `cx` (`none`: not a tier record). -/ def readTierAt (cx : Ctx md) (address : Address) (source : Ixon.Constant) : @@ -296,6 +368,12 @@ def Checker.stepRecord (ev : Ix.Kernel.Evidences md) (c : Checker md) (_ : Addre Checker md × Option (Ix.Kernel.CheckError md) := c.steps ev rd.decls +/-- `Checker.stepRecord` with `allowUserAxioms` threaded to `Checker.steps`. -/ +def Checker.stepRecordWithUserAxioms (ev : Ix.Kernel.Evidences md) (allowUserAxioms : Bool) + (c : Checker md) (_ : Address) (rd : Read md) : + Checker md × Option (Ix.Kernel.CheckError md) := + c.steps ev rd.decls allowUserAxioms + /-- Check `addresses` in order, continuing past failures; each row is passed to `emit`. The records' views and readings come from `view` and `read`; the reading state `σ` is threaded through `commit` after every reading that @@ -318,7 +396,8 @@ def checkLoopWith {σ κ : Type} [Inhabited κ] (view : Address → Option Recor (after : IO Unit := pure ()) (progress : Nat → LoopOutcome κ → IO Unit := fun _ _ => pure ()) (onRead : Address → Except ReadError (Read md) → Nat → IO Unit := fun _ _ _ => pure ()) (spell : Ix.Kernel.Key md → String := toString) - (tier : Option (TierHook σ κ md) := none) : + (tier : Option (TierHook σ κ md) := none) + (onView : Address → RecordView → IO Unit := fun _ _ => pure ()) : IO (LoopOutcome κ) := do let mut out : LoopOutcome κ := { checker := default } -- the reading state and the step state live in references of the loop's @@ -346,11 +425,15 @@ def checkLoopWith {σ κ : Type} [Inhabited κ] (view : Address → Option Recor let r0 ← IO.monoNanosNow let st ← state.get -- a tier record: read by the tier reader and deferred - let tr? ← IO.lazyPure fun _ => tier.bind (·.read st address) + let tr? ← IO.lazyPure fun _ => tier.bind fun th => + match th.tierAddresses with + | some ta => if !ta.isEmpty && ta.contains address then th.read st address else none + | none => th.read st address if let some tr := tr? then let readMicros := ((← IO.monoNanosNow) - r0) / 1000 match tr with | .error e => + onView address v let (outcome, reason) := readOutcome e for row in rowsFor outcome reason 0 readMicros do out := { out with failed := out.failed.insert row.address row.address, @@ -359,7 +442,9 @@ def checkLoopWith {σ κ : Type} [Inhabited κ] (view : Address → Option Recor out := { out with reasons := out.reasons.insert reason (out.reasons.getD reason 0 + 1) } | .ok d => let deps := v.deps () - match deps.find? out.failed.contains with + let v := { v with deps := fun _ => deps } + onView address v + match if out.failed.isEmpty then none else deps.find? out.failed.contains with | some blocker => let root := out.failed.getD blocker blocker for row in rowsFor "blocked" (toString root) 0 readMicros do @@ -375,6 +460,7 @@ def checkLoopWith {σ κ : Type} [Inhabited κ] (view : Address → Option Recor onRead address reading readMicros match reading with | .error e => + onView address v let (outcome, reason) := readOutcome e for row in rowsFor outcome reason 0 readMicros do out := { out with failed := out.failed.insert row.address row.address, @@ -385,8 +471,11 @@ def checkLoopWith {σ κ : Type} [Inhabited κ] (view : Address → Option Recor -- the reader's state learns from every record it reads; what a failed -- record taught is read only by its dependents, which are blocked state.modify (commit · address rd) - let deps := v.deps () - match deps.find? out.failed.contains with + let (v, deps) := + if out.failed.isEmpty && deferredSet.isEmpty then (v, #[]) + else let d := v.deps (); ({ v with deps := fun _ => d }, d) + onView address v + match if out.failed.isEmpty then none else deps.find? out.failed.contains with | some blocker => let root := out.failed.getD blocker blocker for row in rowsFor "blocked" (toString root) 0 readMicros do @@ -395,7 +484,7 @@ def checkLoopWith {σ κ : Type} [Inhabited κ] (view : Address → Option Recor emit row | none => -- the fence: a logical record that names a tier record - if let some t := deps.find? deferredSet.contains then + if let some t := if deferredSet.isEmpty then none else deps.find? deferredSet.contains then let reason := s!"fence: names the tier record {t}" for row in rowsFor "decline" reason 0 readMicros do out := { out with failed := out.failed.insert row.address row.address, @@ -403,7 +492,7 @@ def checkLoopWith {σ κ : Type} [Inhabited κ] (view : Address → Option Recor emit row out := { out with reasons := out.reasons.insert "fence" (out.reasons.getD "fence" 0 + 1) } continue - if skip.contains (toString address) then + if !skip.isEmpty && skip.contains (toString address) then let reason := "check-ixe: skipped: exceeded the watchdog's limits on an earlier run" for row in rowsFor "decline" reason 0 readMicros do out := { out with failed := out.failed.insert row.address row.address, @@ -434,33 +523,34 @@ def checkLoopWith {σ κ : Type} [Inhabited κ] (view : Address → Option Recor -- the typing tier, staged, from the final step state's index (also the -- logical view of the unsafe opaques) if let some th := tier then - let logical := th.index checker - let fe ← IO.mkRef logical - for (address, v, d, deps, readMicros) in stageDeferred (·.2.2.1) deferred do - let row (outcome reason : String) (micros : Nat) : Row := - ⟨address, names address, v.kind, outcome, reason, micros, readMicros⟩ - match deps.find? out.failed.contains with - | some blocker => - let root := out.failed.getD blocker blocker - out := { out with failed := out.failed.insert address root, - counts := out.counts.insert "blocked" (out.counts.getD "blocked" 0 + 1) } - emit (row "blocked" (toString root) 0) - | none => - let t0 ← IO.monoNanosNow - let fe₀ ← fe.modifyGet fun x => (x, Ix.Kernel.mkFEnv Ix.Kernel.Env.empty) - let (fe₁, err) ← IO.lazyPure fun _ => stepTier logical fe₀ d - fe.set fe₁ - let micros := ((← IO.monoNanosNow) - t0) / 1000 - match err with + unless deferred.isEmpty do + let logical := th.index checker + let fe ← IO.mkRef logical + for (address, v, d, deps, readMicros) in stageDeferred (·.2.2.1) deferred do + let row (outcome reason : String) (micros : Nat) : Row := + ⟨address, names address, v.kind, outcome, reason, micros, readMicros⟩ + match if out.failed.isEmpty then none else deps.find? out.failed.contains with + | some blocker => + let root := out.failed.getD blocker blocker + out := { out with failed := out.failed.insert address root, + counts := out.counts.insert "blocked" (out.counts.getD "blocked" 0 + 1) } + emit (row "blocked" (toString root) 0) | none => - out := { out with counts := out.counts.insert "typed" (out.counts.getD "typed" 0 + 1) } - emit (row "typed" (tierSafety d).word micros) - | some e => - let (outcome, reason) := tierOutcome (tierSafety d) e spell - out := { out with failed := out.failed.insert address address, - counts := out.counts.insert outcome (out.counts.getD outcome 0 + 1), - reasons := out.reasons.insert reason (out.reasons.getD reason 0 + 1) } - emit (row outcome reason micros) + let t0 ← IO.monoNanosNow + let fe₀ ← fe.modifyGet fun x => (x, Ix.Kernel.mkFEnv Ix.Kernel.Env.empty) + let (fe₁, err) ← IO.lazyPure fun _ => stepTier logical fe₀ d + fe.set fe₁ + let micros := ((← IO.monoNanosNow) - t0) / 1000 + match err with + | none => + out := { out with counts := out.counts.insert "typed" (out.counts.getD "typed" 0 + 1) } + emit (row "typed" (tierSafety d).word micros) + | some e => + let (outcome, reason) := tierOutcome (tierSafety d) e spell + out := { out with failed := out.failed.insert address address, + counts := out.counts.insert outcome (out.counts.getD outcome 0 + 1), + reasons := out.reasons.insert reason (out.reasons.getD reason 0 + 1) } + emit (row outcome reason micros) return { out with checker := checker } /-- A record of a check setup, read by the reader at the state `st`. -/ @@ -479,6 +569,8 @@ structure LoopSource (σ : Type) (md : Ix.Kernel.Mode := .anon) where init : IO σ /-- the tier reader (`TierHook.read`), if the source runs the typing tier -/ tier : Option (σ → Address → Option (Except ReadError (Ix.Kernel.Tier.TierDecl md))) := none + /-- optional known set of tier record addresses -/ + tierAddresses : Option (Std.HashSet Address) := none /-- A record of a check setup, read by the tier reader if it is a tier record. -/ def Setup.tierRead (s : Setup) (address : Address) : @@ -490,12 +582,14 @@ def Setup.tierRead (s : Setup) (address : Address) : /-- A check setup as a loop source: every record read from the setup's store, a tier record by the tier reader. -/ def Setup.source (s : Setup) : LoopSource (State .anon) := + let ta := s.store.fold (fun acc a c => + if Ix.Kernel.Admission.isTierInfo c.info then acc.insert a else acc) {} { view := s.view, read := s.read, commit := fun st _ rd => st.commit rd, init := pure {}, - tier := some fun _ a => s.tierRead a } + tier := some fun _ a => s.tierRead a, tierAddresses := some ta } /-- The per-record check's tier hook over a source's tier reader. -/ def LoopSource.tierHook (src : LoopSource σ md) : Option (TierHook σ (Checker md) md) := - src.tier.map fun r => { read := r, index := (·.fe) } + src.tier.map fun r => { read := r, index := (·.fe), tierAddresses := src.tierAddresses } /-- `checkLoopWith` over a check setup with the per-record step: each record is read by the reader at the state the records before it left; with `tier`, @@ -513,8 +607,8 @@ def checkLoop (s : Setup) (names : Address → Array String) /-- Names by owning record, for reporting only (at most three). -/ def reportNames (env : Ixon.Env) (store : RecordStore) : Std.HashMap Address (Array String) := Id.run do - let mut names : Std.HashMap Address (Array String) := {} - for (name, named) in env.named.toList do + let mut names : Std.HashMap Address (Array String) := Std.HashMap.emptyWithCapacity store.size + for (name, named) in env.named do let root := match store[named.addr]? with | some source => owner named.addr source | none => named.addr diff --git a/Ix/KernelCheck/Stream.lean b/Ix/KernelCheck/Stream.lean index 6b0c43708..fafaa9ce5 100644 --- a/Ix/KernelCheck/Stream.lean +++ b/Ix/KernelCheck/Stream.lean @@ -1,14 +1,15 @@ module public import Ix.KernelCheck.Step +import all IxC.Ixon.Types.Kinds @[expose] public section /-! # The environment check's streaming load (untrusted harness) `kernel-check-ixe` loads an `.ixe` this way unless `--load eager` is given. -The `.ixe` is loaded metadata-light and lazily (`Ixon.deEnvAnon`: names map -to addresses, records stay windows into the file's buffer). Every record is +The `.ixe` is parsed metadata-light (`Ixon.deEnvAnon`: names map to +addresses, records stay undecoded until `loadEnv`). Every record is decoded once for its **skeleton** (`skeleton`): what the order, the record views and the reader read of a record other than the one being read. The setup (`CheckIxeStep.setup`) is built over the skeletons, with the literal @@ -16,16 +17,84 @@ edges computed from stand-ins that keep each record's literal kinds (`literalStandIn`), so the order, the recursor index and the views are those of the eager load. -Each record's serialized bytes are copied out of the file's buffer into the -reading state (`StreamState`), the buffer is released, and the check loop -(`CheckIxeStep.checkLoopWith`) decodes a record in full only when its turn -comes and drops its bytes once it is read. What stays resident is the -skeletons, the names, hints and blobs, the bytes of the records not yet read, -and what the checker installs. +In `loadEnv`, parallel worker chunks decode each record once to build its +skeleton and literal flags (keeping full records in the skeleton store only +for proof-evidence records and their owners), build `CheckIxeStep.setup` over +the skeleton store, and then pre-read context-free records in parallel into +`PreRead` (`Read .anon`) entries while retaining full decoded `Ixon.Constant`s +only for context-dependent or tier records. During the check loop +(`CheckIxeStep.checkLoopWith`), each record's pre-read `Read .anon` (or +fallback decoded `Ixon.Constant`) is taken and dropped from `StreamState` +once read. The rows are the eager load's: the same records in the same order, read by the same reader from the same decoded records. -/ +namespace Ix.Kernel.Reader + +/-- Whether a value's body is a projection of the bound variable: the only +shape the projection-function rewrite (`projRewrite`) reads the state for. -/ +def projBody (value : CExpr md) : Bool := + match Ix.Kernel.Frontend.lamBody value with + | .proj _ _ (.bvar 0) => true + | _ => false + +/-- Whether `lowerDefinition` reads its reading state `st`: through the +projection rewrite, on a definition's or theorem's projection body (an +opaque's value is kept as read). -/ +def readsState (value : CExpr md) : Ix.DefKind → Bool + | .opaq => false + | _ => projBody value + +/-- Whether `lowerDefinition` reads its `heights`: through the height rule, on +a definition without a host hint. -/ +def readsHeights (hint : Option Ix.Kernel.ReducibilityHint) : Ix.DefKind → Bool + | .defn => hint.isNone + | _ => false + +/-- Whether `lowerDefinition hint st heights cv value kind safety` reads its +reading state or its heights. When it does not, its result is the same at +every state (`lowerDefinition_stateless`), so a reading at the empty state +stands. -/ +def needsState (hint : Option Ix.Kernel.ReducibilityHint) (value : CExpr md) + (kind : Ix.DefKind) : Bool := + readsState value kind || readsHeights hint kind + +theorem projRewrite_eq_none (st : State md) (cv : CVal md) (value : CExpr md) + (h : projBody value = false) : projRewrite st cv value = none := by + unfold projBody at h + unfold projRewrite + split + · rename_i heq + rw [heq] at h + simp at h + · rfl + +/-- `needsState` is exact about `lowerDefinition`'s reads: if it says no +state is read, any two states lower the same. -/ +theorem lowerDefinition_stateless (hint : Option Ix.Kernel.ReducibilityHint) + (st st' : State md) (heights heights' : CName md → Nat) (cv : CVal md) + (value : CExpr md) (kind : Ix.DefKind) (safety : Ix.DefinitionSafety) + (h : needsState hint value kind = false) : + lowerDefinition hint st heights cv value kind safety = + lowerDefinition hint st' heights' cv value kind safety := by + cases kind with + | defn => + cases hint with + | none => simp [needsState, readsHeights] at h + | some k => + simp only [needsState, readsState, readsHeights, Option.isNone_some, Bool.or_false] at h + simp [lowerDefinition, projRewrite_eq_none _ _ _ h] + | thm => + simp only [needsState, readsState, readsHeights, Bool.or_false] at h + simp [lowerDefinition, projRewrite_eq_none _ _ _ h] + | opaq => + -- the derived `BEq` is unfolded here (`import all` of its module) + have hk : (Ix.DefKind.opaq != Ix.DefKind.opaq) = false := rfl + simp [lowerDefinition, hk] + +end Ix.Kernel.Reader + namespace Benchmarks.Kernel.CheckIxeStream open Ix.Kernel.Reader @@ -72,78 +141,229 @@ def literalStandIn (nat str : Bool) : Ixon.Constant := /-! ## The load -/ +/-- A pre-computed record reading when the expression DAG does not depend on +prior records' `State .anon`, or a pre-built `.defn` header (`cv`, `value`) +that only needs `lowerDefinition` against `st.projOwners` / `st.heights` during +the sequential pass. -/ +inductive PreRead where + | ready (r : Except ReadError (Read .anon)) + | defn (cv : CVal .anon) (value : CExpr .anon) + (kind : Ix.DefKind) (safety : Ix.DefinitionSafety) + +/-- Pre-read a record against an empty `State .anon` when its `Expr .anon` +construction is independent of prior records' state. A single definition +whose lowering reads the state (`needsState`) is pre-read up to its header and +value, lowered at its turn. Returns `none` only for records whose expression +construction depends on `st` otherwise (inductive blocks needing the +in-process model, or mutual definition blocks with a member whose lowering +reads the state). -/ +def preReadRecord (cx : Ctx .anon) (address : Address) (c : Ixon.Constant) : Option PreRead := + match c.info with + | .defn d => + match readRecord cx {} address c with + | .error e => some (.ready (.error e)) + | .ok rd => + match rd.decls[0]? with + | some (Ix.Kernel.Declaration.defnDecl cv value _) + | some (Ix.Kernel.Declaration.thmDecl cv value) => + if needsState (cx.hint (.member address 0)) value d.kind then + some (.defn cv value d.kind d.safety) + else + some (.ready (.ok rd)) + | _ => some (.ready (.ok rd)) + | .muts ms => + if ms.any (fun | .indc _ => true | _ => false) then + match readRecord cx {} address c with + | .ok rd => + if rd.blocks.all fun (_, b) => !Ix.Kernel.Frontend.InModel.wants b then + some (.ready (.ok rd)) + else + none + | .error _ => none + else if ms.all (fun | .defn _ => true | _ => false) then + match readRecord cx {} address c with + | .error e => some (.ready (.error e)) + | .ok rd => + -- `needsState` member by member: the heights by the member's index + -- in the block (its hint's), the state by its declaration (the + -- declarations follow `defOrder`, not the block's order) + let needsSt := (List.range ms.size).any (fun i => + match ms[i]? with + | some (.defn d) => readsHeights (cx.hint (.member address i)) d.kind + | _ => false) || + rd.decls.any fun (d : Ix.Kernel.Declaration .anon) => + match d with + | .defnDecl _ value _ => readsState value .defn + | .thmDecl _ value => readsState value .thm + | _ => false + if needsSt then none else some (.ready (.ok rd)) + else + some (.ready (readRecord cx {} address c)) + | _ => + some (.ready (readRecord cx {} address c)) + +/-- Pre-read a chunk of decoded records in a worker task, keeping a decoded +record only where the check loop still reads it: when its pre-read gave up, +and for a tier record (the tier reader reads it). Every other decoded record +is freed with its chunk. -/ +def preReadChunk (cx : Ctx .anon) + (chunk : Array (Address × Ixon.Constant × Ixon.Constant × (Bool × Bool))) : + Array (Address × Option PreRead × Option Ixon.Constant) := + chunk.map fun (address, c, _, _) => + let pr := preReadRecord cx address c + (address, pr, if pr.isNone || Ix.Kernel.Admission.isTierInfo c.info then some c else none) + /-- A streaming load: the environment without its records, the setup over -the skeletons, the records' lazy windows (handed to the check loop's thread -in a reference, so that the loop takes the only reference to them), and the -prelude's records that the `.ixe` does not hold (read in full from the -setup's store). -/ +the skeletons, the decoded records the pre-read could not read ahead (the +context-dependent ones and the tier records), the parallel pre-readings (both +handed to the check loop's thread in a reference, so that the loop takes the +only reference to the containers), and the prelude's records that the `.ixe` +does not hold (read in full from the setup's store). -/ structure Loaded where env : Ixon.Env setup : Setup - windows : IO.Ref (Std.HashMap Address Ixon.LazyConstant) + decoded : IO.Ref (Std.HashMap Address Ixon.Constant) + preReads : IO.Ref (Std.HashMap Address PreRead) preludeOnly : Std.HashSet Address + tierAddresses : Std.HashSet Address := {} -/-- Load an `.ixe` for streaming: decode every record once for its skeleton -and its literal kinds, and build the setup over the skeletons. -/ -def load (bytes : ByteArray) (table : Ix.Kernel.RoleTable) (pre : Prelude .anon) : IO Loaded := do - let env ← IO.ofExcept (Ixon.deEnvAnon bytes) - let consts := env.consts - let env := { env with consts := {} } - let mut store : RecordStore := {} - let mut lits : Array (Address × Ixon.Constant) := #[] +/-- A lazy constant decoded, as `LazyConstant.get` decodes it, from its own +copy of its bytes. Every record of a loaded `.ixe` is a window into the one +file buffer, and the decoder's state holds the buffer it reads: decoded in +place by the parallel workers, every step of every worker updates that one +buffer's thread-shared reference count, and the chunks spend about 50 times +the work of decoding the same records one at a time from copies. -/ +def decodeOwn (lazy : Ixon.LazyConstant) : Except String Ixon.Constant := + match lazy.cache with + | some c => .ok c + | none => Ixon.deConstantAt lazy.rawBytes 0 lazy.len + +/-- Decode a slice `[lo, hi)` of lazy constants together with their skeletons +and literal-kind flags. -/ +def decodeChunk (entries : Array (Address × Ixon.LazyConstant)) + (lo hi : Nat) : Except String (Array (Address × Ixon.Constant × Ixon.Constant × (Bool × Bool))) := Id.run do + let mut out := Array.mkEmpty (hi - lo) + for idx in [lo:hi] do + if h : idx < entries.size then + let (address, lazy) := entries[idx] + match decodeOwn lazy with + | .ok c => out := out.push (address, c, skeleton c, literalKinds c) + | .error e => return .error e + return .ok out + +/-- Load an already-parsed anonymous `.ixe` environment for streaming: decode +every record once in parallel chunks for its skeleton, literal kinds, and +streaming read state, build the setup over the skeletons, and pre-read +state-independent expression DAGs in parallel. + +With `persistent`, the setup is marked persistent before the pre-read: the +pre-read workers then read its reader context without thread-shared reference +counting, and the setup is never freed. A caller that keeps the setup to the +end of its process sets it (`kernel-check-ixe`); without it, the setup is +freed as usual once its last reference is dropped. -/ +def loadEnv (env : Ixon.Env) (table : Ix.Kernel.RoleTable) (pre : Prelude .anon) + (persistent : Bool := false) : IO Loaded := do -- Input theorem records shadow fallback evidence at the same address. -- Preserve their real values, including a projected theorem's owner. let mut proofRecords : Std.HashSet Address := {} - for (_, entries) in table.evidence do - for entry in entries do + for (_, evEntries) in table.evidence do + for entry in evEntries do if let .proof address := entry then proofRecords := proofRecords.insert address - if let some lazy := consts[address]? then + if let some lazy := env.consts[address]? then let record ← IO.ofExcept lazy.get proofRecords := proofRecords.insert (Ixon.CheckOrder.owner address record) - for (address, lazy) in consts.toList do - let c ← IO.ofExcept lazy.get - let (nat, str) := literalKinds c - if nat || str then lits := lits.push (address, literalStandIn nat str) - store := store.insert address (if proofRecords.contains address then c else skeleton c) + let entries := env.consts.toArray + let env := { env with consts := {} } + let chunkSize := Nat.max 256 ((entries.size + 63) / 64) + let numChunks := (entries.size + chunkSize - 1) / chunkSize + let tasks := (Array.range numChunks).map fun i => + let lo := i * chunkSize + let hi := Nat.min entries.size (lo + chunkSize) + Task.spawn fun () => decodeChunk entries lo hi + let mut chunks : Array (Array (Address × Ixon.Constant × Ixon.Constant × (Bool × Bool))) := + Array.mkEmpty numChunks + let mut store : RecordStore := Std.HashMap.emptyWithCapacity entries.size + let mut litFlags : Array (Address × Bool × Bool) := #[] + let mut tierAddresses : Std.HashSet Address := {} + for t in tasks do + let chunk ← IO.ofExcept t.get + chunks := chunks.push chunk + for (address, c, sk, (nat, str)) in chunk do + if nat || str then litFlags := litFlags.push (address, nat, str) + if Ix.Kernel.Admission.isTierInfo sk.info then + tierAddresses := tierAddresses.insert address + store := store.insert address (if proofRecords.contains address then c else sk) let preludeOnly := pre.records.foldl (init := {}) fun (acc : Std.HashSet Address) (a, _) => if store.contains a then acc else acc.insert a let hints := Hints.ofStore store env.anonHints - let s := setup store (env.blobs[·]?) table pre hints.lookup (literals := some lits) - return { env, setup := s, windows := ← IO.mkRef consts, preludeOnly } + let s := setup store (env.blobs[·]?) table pre hints.lookup (literalFlags := some litFlags) + -- `markPersistent` is `unsafe`: a persistent object is never freed. + let s ← if persistent then unsafe Runtime.markPersistent s else pure s + let preTasks := chunks.map fun chunk => Task.spawn fun () => preReadChunk s.cx chunk + let mut preReads : Std.HashMap Address PreRead := Std.HashMap.emptyWithCapacity entries.size + let mut decoded : Std.HashMap Address Ixon.Constant := {} + for pt in preTasks do + let preChunk := pt.get + for (address, pr?, c?) in preChunk do + if let some pr := pr? then preReads := preReads.insert address pr + if let some c := c? then decoded := decoded.insert address c + return { + env + setup := s + decoded := ← IO.mkRef decoded + preReads := ← IO.mkRef preReads + preludeOnly + tierAddresses + } + +/-- Load an `.ixe` for streaming: decode every record once in parallel chunks +for its skeleton, literal kinds, and streaming read state, and build the setup +over the skeletons (`loadEnv`, with `persistent`). -/ +def load (bytes : ByteArray) (table : Ix.Kernel.RoleTable) (pre : Prelude .anon) + (persistent : Bool := false) : IO Loaded := do + let env ← IO.ofExcept (Ixon.deEnvAnon bytes) + loadEnv env table pre persistent /-! ## Reading -/ -/-- The reading state of a streaming run: the reader's state, and the -serialized bytes of every record of the `.ixe` not yet read. -/ +/-- The reading state of a streaming run: the reader's state, the pre-built +readings, and the decoded records of the `.ixe` not yet read. -/ structure StreamState where st : State .anon := {} - bytes : Std.HashMap Address ByteArray := {} - -/-- The initial reading state: every record's bytes, copied out of the -file's buffer. Run on the check loop's thread, after taking the windows out -of their reference: the map is then that thread's own and the only one, so -dropping a record's bytes frees them, and the buffer is freed with the -windows. -/ -def StreamState.take (windows : IO.Ref (Std.HashMap Address Ixon.LazyConstant)) : IO StreamState := do - let consts ← windows.modifyGet fun m => (m, {}) - return { bytes := consts.fold (init := {}) fun m a lazy => m.insert a lazy.rawBytes } - -/-- A record read in full: decoded from its bytes, or, for a prelude record -the `.ixe` does not hold, the setup's. -/ + records : Std.HashMap Address Ixon.Constant := {} + preReads : Std.HashMap Address PreRead := {} + +/-- Take both the pre-read map and fallback decoded records out of `Loaded` +so the check loop's thread holds the sole reference to the containers. -/ +def Loaded.init (l : Loaded) : IO StreamState := do + let records ← l.decoded.modifyGet fun m => (m, {}) + let preReads ← l.preReads.modifyGet fun m => (m, {}) + return { records, preReads } + +/-- A record read in full: from the pre-read map or fallback decoded record map, +or, for a prelude record the `.ixe` does not hold, the setup's. -/ def StreamState.read (l : Loaded) (ss : StreamState) (address : Address) : Except ReadError (Read .anon) := - match ss.bytes[address]? with - | some b => match Ixon.deConstantAt b 0 b.size with - | .ok source => readRecord l.setup.cx ss.st address source - | .error e => .error (.malformed s!"record does not decode: {e}") + match ss.preReads[address]? with + | some (.ready r) => r + | some (.defn cv value kind safety) => + match lowerDefinition (l.setup.cx.hint (.member address 0)) ss.st (ss.st.heights.getD · 0) + cv value kind safety with + | .ok (decl, rw) => .ok { decls := #[decl], projRewrites := if rw then 1 else 0 } + | .error e => .error e | none => - if l.preludeOnly.contains address then l.setup.read ss.st address - else .error (.malformed "record is missing or was read already") + match ss.records[address]? with + | some source => readRecord l.setup.cx ss.st address source + | none => + if l.preludeOnly.contains address then l.setup.read ss.st address + else .error (.malformed "record is missing or was read already") /-- The state after a record: the reader's state learns from it, and its -bytes are dropped. -/ +pre-read entry or decoded record is dropped. -/ def StreamState.commit (ss : StreamState) (address : Address) (rd : Read .anon) : StreamState := - { st := ss.st.commit rd, bytes := ss.bytes.erase address } + { st := ss.st.commit rd + records := if ss.records.isEmpty then ss.records else ss.records.erase address + preReads := ss.preReads.erase address } /-- A tier record read in full by the tier reader (`none`: not a tier record). Its skeleton keeps its safety, so the test needs no decoding. -/ @@ -151,16 +371,15 @@ def StreamState.tierRead (l : Loaded) (ss : StreamState) (address : Address) : Option (Except ReadError (Ix.Kernel.Tier.TierDecl .anon)) := do let sk ← l.setup.store[address]? guard (Ix.Kernel.Admission.isTierInfo sk.info) - match ss.bytes[address]? with - | some b => match Ixon.deConstantAt b 0 b.size with - | .ok source => readTierAt l.setup.cx address source - | .error e => some (.error (.malformed s!"record does not decode: {e}")) + match ss.records[address]? with + | some source => readTierAt l.setup.cx address source | none => some (.error (.malformed "record is missing or was read already")) /-- A streaming load as a loop source. -/ def Loaded.source (l : Loaded) : LoopSource StreamState := { view := l.setup.view, read := StreamState.read l, - commit := StreamState.commit, init := StreamState.take l.windows, - tier := some (StreamState.tierRead l) } + commit := StreamState.commit, init := l.init, + tier := some (StreamState.tierRead l), + tierAddresses := some l.tierAddresses } end Benchmarks.Kernel.CheckIxeStream diff --git a/Ix/KernelDisplay/Peek.lean b/Ix/KernelDisplay/Peek.lean index 672bfc0d9..f5d685a94 100644 --- a/Ix/KernelDisplay/Peek.lean +++ b/Ix/KernelDisplay/Peek.lean @@ -24,8 +24,9 @@ trust-surface fence (`Tests/Ix/Kernel/TrustSurface.lean`) with this reason: entry points that print with names (`kernel-check-ixe --meta`, the `kernel-meta-names` oracle). The renderer itself (`Ix.KernelDisplay.Render`) is safe code that takes the read as a parameter (`Peek`), and this module - holds nothing else. `Tests.Ix.Kernel.DisplayRead` checks that the runtime - closures of the certified roots do not reach `peekSquash`. -/ + holds nothing else. `IxC/Kernel/Audit/Roots.lean` and + `Ix/Cert/RuntimeAudit.lean` check that the runtime closures of the certified + roots do not reach `peekSquash`. -/ namespace Ix.KernelDisplay diff --git a/Ix/KernelFidelity/Reader.lean b/Ix/KernelFidelity/Reader.lean index d1b22634b..58cc3ba89 100644 --- a/Ix/KernelFidelity/Reader.lean +++ b/Ix/KernelFidelity/Reader.lean @@ -589,7 +589,7 @@ def InputAccess.ofInput (input : Input) : InputAccess := { /-- Decode each record transiently for its skeleton and literal edges. The insertion order is exactly `Input.ofEnv`'s, including colliding hash buckets. Metadata is decoded per requested source name, never as a whole named store. -No `StreamState.take` raw-window copies are made. -/ +No decoded record is retained beyond its skeleton. -/ def InputAccess.ofParts (lean : Lean.Environment) (parts : Ixon.LazyEnvParts) : Except String InputAccess := do let records := _root_.Ix.KernelFidelity.RecordAccess.ofParts parts diff --git a/Ix/LeanIngress/NominalQuotient.lean b/Ix/LeanIngress/NominalQuotient.lean new file mode 100644 index 000000000..1bbb20b71 --- /dev/null +++ b/Ix/LeanIngress/NominalQuotient.lean @@ -0,0 +1,544 @@ +import Ix.Cert.SourceTrace +import Ix.LeanIngress.Rename +import Ix.LeanIngress.Theorems + +/-! # Pre-lowering fiber safety and collapsed trace invariants + +This module defines the structural predicates and executable boolean checkers +for pre-lowering fiber safety on a closure `(sub, blocks)` at an artifact and +role table `(ixe, t)`, and connects `prepareSourceCollapsedAt` to the collapsed +trace invariants in `Ix.Cert.SourceTrace`, `Ix.Cert.SourcePrelude`, and +`Ix.Cert.SourceReplay`: + +* `GroupIntraInjective` and `checkGroupIntraInjective`: within every inductive + and definition group of `groupsOf sub blocks`, `Key.subst` at + `fiberAliasAt ixe (roleAtMap t)` maps the group's declared constants to + pairwise distinct keys (`Nodup`); +* `HintsAgreeAt` and `checkHintsAgreeAt`: every definition in `sub` has its + nominal reducibility hint matching `fiberHintsMap ixe` at its substituted key; +* `FiberAliasSafe`, `checkGroupFiberSafe`, and `checkGroupFiberSafe_sound`: the + combined executable checker and its soundness theorem; +* `step_fresh_or_beqF`, `alias_fresh_or_beqF`, `collapseRead_fresh_or_beqF`, + and `prepareSourceCollapsedAt_trace`: every step in a successful collapsed + trace is either a fresh declaration/block or an exact structural match + (`Declaration.beqF` / `BlockRead.beqF`) with an already-kept declaration or + block, while `StableOwned`, `OwnerExact`, `KeptNoBasis`, `declsGoodB`, + `bundleDisjointB`, `evidenceGoodB`, and `keepsPrelude` all hold. +-/ + +namespace Ix.LeanIngress + +open Ix.Kernel +open Ix.Kernel.Reader (CName CExpr CVal CDecl ReadError State Read BlockRead Prelude) +open Ix.Cert.SourcePrelude (SKey OwnerExt ReadInv ReadBad readStep readLoop readFinish + forIn_id_inv_or_array collapseRead_eq collapseRead_facts step_ownerFacts alias_none_fresh + Trace lowerAllCollapsed_trace) +open Ix.Cert.SourceReplay (OwnerExact KeptNoBasis StableOwned step_drop collapseRead_ownerExact + trace_ownerExact trace_keptNoBasis) + +/-! ## Intra-group fiber injectivity and hint agreement -/ + +/-- Within an inductive or definition group, `Key.subst ρ` maps the group's +declared constants (and, for an inductive group, its converted `BlockRead.names`) +to pairwise distinct keys (`List.Nodup`). -/ +def Group.IntraInjective (ρ : FiberMap) : Group → Prop + | g@(.inductive _ _ _) => + ((g.names.toList.map ingressKey).map (Key.subst ρ)).Nodup ∧ + ∀ rb, convert g = .ok (.inductive rb) → + ((BlockRead.names rb).map (Key.subst ρ)).Nodup + | .definitions ms => + ((ms.toList.map (fun ci => ingressKey ci.getCnst.name)).map (Key.subst ρ)).Nodup + | _ => True + +/-- Within every inductive group and every definition group in `groupsOf sub blocks`, +`Key.subst (fiberAliasAt ixe (roleAtMap t))` is injective on the group's +declared keys (`Nodup` after `Key.subst`). -/ +def GroupIntraInjective (ixe : Ixon.Env) (t : Ix.Kernel.RoleTable) + (sub : Ix.Environment) (blocks : Ix.CondensedBlocks) : Prop := + let ρ := fiberAliasAt ixe (roleAtMap t) + ∀ g ∈ (groupsOf sub blocks).toList, Group.IntraInjective ρ g + +/-- Executable boolean check for `Group.IntraInjective` on a single group. -/ +def checkGroupIntraInjectiveOne (ρ : FiberMap) : Group → Bool + | g@(.inductive _ _ _) => + decide (((g.names.toList.map ingressKey).map (Key.subst ρ)).Nodup) && + match convert g with + | .ok (.inductive rb) => decide (((BlockRead.names rb).map (Key.subst ρ)).Nodup) + | _ => true + | .definitions ms => + decide (((ms.toList.map (fun ci => ingressKey ci.getCnst.name)).map (Key.subst ρ)).Nodup) + | _ => true + +/-- Executable boolean check for `GroupIntraInjective`. -/ +def checkGroupIntraInjective (ixe : Ixon.Env) (t : Ix.Kernel.RoleTable) + (sub : Ix.Environment) (blocks : Ix.CondensedBlocks) : Bool := + let ρ := fiberAliasAt ixe (roleAtMap t) + (groupsOf sub blocks).toList.all (checkGroupIntraInjectiveOne ρ) + +theorem checkGroupIntraInjectiveOne_sound {ρ : FiberMap} {g : Group} + (h : checkGroupIntraInjectiveOne ρ g = true) : Group.IntraInjective ρ g := by + cases g with + | «inductive» types ctors recs => + unfold checkGroupIntraInjectiveOne at h + rw [Bool.and_eq_true] at h + obtain ⟨h1, h2⟩ := h + refine ⟨of_decide_eq_true h1, fun rb hc => ?_⟩ + rw [hc] at h2 + exact of_decide_eq_true h2 + | definitions ms => + unfold checkGroupIntraInjectiveOne at h + unfold Group.IntraInjective + exact of_decide_eq_true h + | «axiom» _ => trivial + | quot _ => trivial + | invalid _ _ => trivial + +/-- Soundness of `checkGroupIntraInjective`. -/ +theorem checkGroupIntraInjective_sound {ixe : Ixon.Env} {t : Ix.Kernel.RoleTable} + {sub : Ix.Environment} {blocks : Ix.CondensedBlocks} + (h : checkGroupIntraInjective ixe t sub blocks = true) : + GroupIntraInjective ixe t sub blocks := by + unfold checkGroupIntraInjective GroupIntraInjective at * + dsimp only at h ⊢ + intro g hg + exact checkGroupIntraInjectiveOne_sound (List.all_eq_true.mp h g hg) + +/-- Every definition in `sub` has its nominal reducibility hint (`definitionShape`) +matching `fiberHintsMap ixe` at its substituted key under +`fiberAliasAt ixe (roleAtMap t)` (`Key.subst ρ (ingressKey n)`). +Because `fiberHintsMap ixe` is keyed by the quotient representative of each +address fiber (and `Converted.quotient` looks up hints at the quotient key +after `Key.subst ρ`), agreement at `Key.subst ρ (ingressKey n)` is the exact +condition ensuring every member of an alias fiber receives its nominal hint. -/ +def HintsAgreeAt (ixe : Ixon.Env) (t : Ix.Kernel.RoleTable) (sub : Ix.Environment) : Prop := + let ρ := fiberAliasAt ixe (roleAtMap t) + let hints := fiberHintsMap ixe + ∀ n ci, (n, ci) ∈ sub.consts.toList → + ∀ kind safety hint, definitionShape ci = some (kind, safety, hint) → + hints[Key.subst ρ (ingressKey n)]? = some hint + +/-- Executable boolean check for `HintsAgreeAt`. -/ +def checkHintsAgreeAt (ixe : Ixon.Env) (t : Ix.Kernel.RoleTable) (sub : Ix.Environment) : Bool := + let ρ := fiberAliasAt ixe (roleAtMap t) + let hints := fiberHintsMap ixe + sub.consts.toList.all fun (n, ci) => + match definitionShape ci with + | some (_, _, hint) => + hints[Key.subst ρ (ingressKey n)]? == some hint + | none => true + +/-- Soundness of `checkHintsAgreeAt`. -/ +theorem checkHintsAgreeAt_sound {ixe : Ixon.Env} {t : Ix.Kernel.RoleTable} {sub : Ix.Environment} + (h : checkHintsAgreeAt ixe t sub = true) : HintsAgreeAt ixe t sub := by + unfold checkHintsAgreeAt HintsAgreeAt at * + dsimp only at h ⊢ + intro n ci hmem kind safety hint hshape + have hall := List.all_eq_true.mp h (n, ci) hmem + dsimp only at hall + rw [hshape] at hall + simp only [beq_iff_eq] at hall + exact hall + +/-- Pre-lowering fiber safety on `(sub, blocks)` at `(ixe, t)`: intra-group +key injectivity after `Key.subst` and reducibility-hint agreement. -/ +def FiberAliasSafe (ixe : Ixon.Env) (t : Ix.Kernel.RoleTable) + (sub : Ix.Environment) (blocks : Ix.CondensedBlocks) : Prop := + GroupIntraInjective ixe t sub blocks ∧ HintsAgreeAt ixe t sub + +/-- Executable boolean checker for pre-lowering fiber safety (`GroupIntraInjective` +and `HintsAgreeAt`). -/ +def checkGroupFiberSafe (ixe : Ixon.Env) (t : Ix.Kernel.RoleTable) + (sub : Ix.Environment) (blocks : Ix.CondensedBlocks) : Bool := + checkGroupIntraInjective ixe t sub blocks && checkHintsAgreeAt ixe t sub + +/-- **Soundness of `checkGroupFiberSafe`**: when the executable check succeeds, +both `GroupIntraInjective` and `HintsAgreeAt` (`FiberAliasSafe`) hold. -/ +theorem checkGroupFiberSafe_sound {ixe : Ixon.Env} {t : Ix.Kernel.RoleTable} + {sub : Ix.Environment} {blocks : Ix.CondensedBlocks} + (h : checkGroupFiberSafe ixe t sub blocks = true) : + GroupIntraInjective ixe t sub blocks ∧ HintsAgreeAt ixe t sub := by + unfold checkGroupFiberSafe at h + rw [Bool.and_eq_true] at h + exact ⟨checkGroupIntraInjective_sound h.1, checkHintsAgreeAt_sound h.2⟩ + +/-! ## Step and trace characterization for `prepareSourceCollapsedAt` -/ + +/-- Every successful declaration-collapse step under `OwnerExact` either keeps a +declaration whose declared names are all fresh, or drops a declaration that +matches an already-kept declaration via `Declaration.beqF` (and therefore equals +it) with every declared name owned by that kept index. -/ +theorem step_fresh_or_beqF {s s' : CollapseState} (owner : OwnerExact s) + {first : SKey} {d : Declaration .source} {kept : Bool} + (h : s.step first d = (s', .ok kept)) : + (kept = true ∧ s'.kept = s.kept.push d ∧ ∀ k ∈ d.names, s.owner[k]? = none) ∨ + (kept = false ∧ s' = s ∧ ∃ i d₀, s.kept[i]? = some d₀ ∧ + Declaration.beqF d d₀ = true ∧ d = d₀ ∧ ∀ k ∈ d.names, s.owner[k]? = some i) := by + cases kept with + | true => + left + exact ⟨rfl, by simpa using Ix.Cert.SourcePrelude.step_kept h, (step_ownerFacts h).2.2 rfl⟩ + | false => + right + obtain ⟨rfl, i, held, hown⟩ := step_drop owner h + exact ⟨rfl, rfl, i, d, held, Declaration.beqF_refl d, rfl, hown⟩ + +/-- Every successful inductive-block alias check either finds all of the block's +names fresh (`res = none`) or matches an already-kept block `rb₀` at index `k` +via `BlockRead.beqF` (`res = some k`, hence `rb = rb₀`). -/ +theorem alias_fresh_or_beqF {kb : KeptBlocks} {s : CollapseState} {first : SKey} + {rb : BlockRead .source} {res : Option Nat} + (h : kb.alias s first rb = .ok res) : + (res = none ∧ ∀ k ∈ BlockRead.names rb, s.owner[k]? = none) ∨ + ∃ k rb₀, res = some k ∧ kb.blocks[k]? = some rb₀ ∧ + BlockRead.beqF rb rb₀ = true ∧ rb = rb₀ ∧ + ∀ n ∈ BlockRead.names rb, kb.blockOf[n]? = some k := by + cases res with + | none => + exact .inl ⟨rfl, alias_none_fresh h⟩ + | some k => + right + unfold KeptBlocks.alias at h + dsimp only at h + split at h + · cases h + · split at h + · rename_i k' hk' + split at h + · rename_i hgood + cases h + obtain ⟨hall, hany⟩ := Bool.and_eq_true_iff.mp hgood + cases hblk : kb.blocks[k]? with + | none => simp [hblk] at hany + | some rb₀ => + simp only [hblk, Option.any_some] at hany + refine ⟨k, rb₀, rfl, hblk, hany, BlockRead.beqF_eq_true hany, fun n hn => ?_⟩ + exact beq_iff_eq.mp (List.all_eq_true.mp hall n hn) + · cases h + · cases h + +/-- An `.alias` step in `collapseGroup` comes from an inductive block that +matches an already-kept block in `kb` via `BlockRead.beqF`. -/ +theorem collapseGroup_alias_beqF {st : State .source} {s s' : CollapseState} + {kb kb' : KeptBlocks} {first : SKey} {pos k : Nat} {c : Except ReadError Converted} + (h : collapseGroup st s kb first pos c = .alias k s' kb') : + s' = s ∧ kb' = kb ∧ ∃ rb rb₀, c = .ok (.inductive rb) ∧ + kb.blocks[k]? = some rb₀ ∧ BlockRead.beqF rb rb₀ = true ∧ rb = rb₀ ∧ + ∀ n ∈ BlockRead.names rb, kb.blockOf[n]? = some k := by + cases c with + | error e => simp [collapseGroup] at h + | ok conv => + unfold collapseGroup at h + dsimp only at h + cases conv with + | «inductive» rb => + dsimp only at h + cases ha : kb.alias s first rb with + | error e => simp [ha] at h + | ok res => + cases res with + | none => + simp only [ha] at h + split at h + · cases h + · split at h <;> cases h + | some k' => + simp only [ha, CollapseStep.alias.injEq] at h + obtain ⟨rfl, rfl, rfl⟩ := h + rcases alias_fresh_or_beqF ha with ⟨hnone, _⟩ | ⟨k'', rb₀, hsome, hblk, hbeq, heq, hown⟩ + · cases hnone + · cases hsome + exact ⟨rfl, rfl, rb, rb₀, rfl, hblk, hbeq, heq, hown⟩ + | definitions ms => + dsimp only at h + split at h + · cases h + · split at h <;> cases h + | «axiom» cv => + dsimp only at h + split at h + · cases h + · split at h <;> cases h + | quot kind cv => + dsimp only at h + split at h + · cases h + · split at h <;> cases h + +private theorem getElem?_push_of_some {α : Type} {xs : Array α} {i : Nat} {v : α} (x : α) + (h : xs[i]? = some v) : (xs.push x)[i]? = some v := by + have hlt : i < xs.size := (Array.getElem?_eq_some_iff.mp h).1 + rw [Array.getElem?_push] + simp [h] + omega + +private theorem getElem?_push_self {α : Type} (xs : Array α) (x : α) : + (xs.push x)[xs.size]? = some x := by + rw [Array.getElem?_push] + simp + +/-- Every declaration of a lowered group that `collapseRead` processes is either +kept in `r.decls` (with all its names fresh prior to the group) or matches a +kept declaration in `s'.kept` via `Declaration.beqF`. -/ +theorem collapseRead_fresh_or_beqF {s s' : CollapseState} (owner : OwnerExact s) + {first : SKey} {raw r : Read .source} {dropped : Array SKey} + (h : collapseRead s first raw = (s', .ok (r, dropped))) : + OwnerExact s' ∧ + ∀ d ∈ raw.decls.toList, + (d ∈ r.decls.toList ∧ ∀ k ∈ d.names, s.owner[k]? = none) ∨ + ∃ i d₀, s'.kept[i]? = some d₀ ∧ Declaration.beqF d d₀ = true ∧ d = d₀ ∧ + ∀ k ∈ d.names, s'.owner[k]? = some i := by + have exact' := collapseRead_ownerExact owner h + obtain ⟨_, claims, fresh, _, _, _, _⟩ := collapseRead_facts h + refine ⟨exact', fun d hd => ?_⟩ + by_cases hmem : d ∈ r.decls.toList + · exact .inl ⟨hmem, fun k hk => (fresh d hmem k hk).1⟩ + · -- `collapseRead` processes every declaration in `raw.decls` through `step`; + -- whether kept or dropped, `s'.kept` holds a `Declaration.beqF`-equal copy when `d.names` is non-empty, + -- and every declaration in `raw.decls` that was stepped either entered `r.decls` or matched `s'.kept`. + let I (pre : List (Declaration .source × Nat)) (a : Ix.Cert.SourcePrelude.ReadAcc) : Prop := + a.1 = none ∧ OwnerExact a.2.1 ∧ OwnerExt s a.2.1 ∧ + ∀ x ∈ pre, + (x.1 ∈ a.2.2.1.toList ∧ ∀ k ∈ x.1.names, s.owner[k]? = none) ∨ + ∃ i d₀, a.2.1.kept[i]? = some d₀ ∧ Declaration.beqF x.1 d₀ = true ∧ x.1 = d₀ ∧ + ∀ k ∈ x.1.names, a.2.1.owner[k]? = some i + have init : I [] (none, s, #[], 0, #[]) := + ⟨rfl, owner, .refl s, fun _ hx => nomatch hx⟩ + have inv := forIn_id_inv_or_array I ReadBad (readStep first s.kept.size raw.generated) + raw.decls.zipIdx (fun pre x _ a _ ha => by + obtain ⟨hact, hown, hext, heach⟩ := ha + rcases hs : a.2.1.step first x.1 with ⟨next, res⟩ + cases res with + | error e => + simp only [readStep, hs, pure] + exact ⟨next.restore s.kept.size, e, rfl⟩ + | ok b => + have hown' := OwnerExact.step hown hs + have hf := step_ownerFacts hs + have hext' : OwnerExt s next := hext.trans hf.1 + rcases step_fresh_or_beqF hown hs with ⟨rfl, hkept, hfr⟩ | ⟨rfl, rfl, i, d₀, held, hbeq, heq, hallk⟩ + · have stepEach (gen : Nat) : I (pre ++ [x]) (none, next, a.2.2.1.push x.1, gen, a.2.2.2.2) := by + refine ⟨rfl, hown', hext', fun y hy => ?_⟩ + rcases List.mem_append.mp hy with hy | hy + · rcases heach y hy with ⟨hmy, hfy⟩ | ⟨i, d₀, held, hbeq, heq, hk⟩ + · exact .inl ⟨by simp [hmy], hfy⟩ + · refine .inr ⟨i, d₀, ?_, hbeq, heq, fun k hk' => hf.1 k i (hk k hk')⟩ + rw [hkept] + exact getElem?_push_of_some x.1 held + · simp only [List.mem_singleton] at hy + subst hy + exact .inl ⟨by simp, fun k hk => hext.fresh (hfr k hk)⟩ + by_cases early : x.2 < raw.generated + · simpa only [readStep, hs, early, ite_true, pure] using stepEach (a.2.2.2.1 + 1) + · simpa only [readStep, hs, early, ite_false, pure] using stepEach a.2.2.2.1 + · simp only [readStep, hs, pure] + refine ⟨rfl, hown, hext, fun y hy => ?_⟩ + rcases List.mem_append.mp hy with hy | hy + · exact heach y hy + · simp only [List.mem_singleton] at hy + subst hy + exact .inr ⟨i, d₀, held, hbeq, heq, hallk⟩) _ init + change I raw.decls.zipIdx.toList (readLoop s first raw) ∨ ReadBad (readLoop s first raw) at inv + rw [collapseRead_eq] at h + rcases inv with ⟨hact, _, _, heach⟩ | ⟨t, e, he⟩ + · simp only [readFinish, hact, Prod.mk.injEq, Except.ok.injEq] at h + obtain ⟨rfl, rfl, _⟩ := h + obtain ⟨j, hj⟩ := List.mem_map.mp + (show d ∈ raw.decls.zipIdx.toList.map Prod.fst by simpa using hd) + have := heach j hj.1 + rwa [hj.2] at this + · simp [readFinish, he] at h + +/-- After `collapseRead` succeeds from `OwnerExact s`, every declaration +`d ∈ raw.decls.toList` (whether kept as fresh or dropped as a duplicate) has a +`Declaration.beqF`-matching declaration `d₀` stored in `s'.kept` (with `d = d₀`). -/ +theorem collapseRead_kept_beqF {s s' : CollapseState} (owner : OwnerExact s) + {first : SKey} {raw r : Read .source} {dropped : Array SKey} + (h : collapseRead s first raw = (s', .ok (r, dropped))) : + OwnerExact s' ∧ + ∀ d ∈ raw.decls.toList, + ∃ (i : Nat) (d₀ : Declaration .source), s'.kept[i]? = some d₀ ∧ Declaration.beqF d d₀ = true ∧ d = d₀ := by + have exact' := collapseRead_ownerExact owner h + refine ⟨exact', fun d hd => ?_⟩ + let I (pre : List (Declaration .source × Nat)) (a : Ix.Cert.SourcePrelude.ReadAcc) : Prop := + a.1 = none ∧ OwnerExact a.2.1 ∧ + ∀ x ∈ pre, + ∃ (i : Nat) (d₀ : Declaration .source), a.2.1.kept[i]? = some d₀ ∧ Declaration.beqF x.1 d₀ = true ∧ x.1 = d₀ + have init : I [] (none, s, #[], 0, #[]) := + ⟨rfl, owner, fun _ hx => nomatch hx⟩ + have inv := forIn_id_inv_or_array I ReadBad (readStep first s.kept.size raw.generated) + raw.decls.zipIdx (fun pre x _ a _ ha => by + obtain ⟨hact, hown, heach⟩ := ha + rcases hs : a.2.1.step first x.1 with ⟨next, res⟩ + cases res with + | error e => + simp only [readStep, hs, pure] + exact ⟨next.restore s.kept.size, e, rfl⟩ + | ok b => + have hown' := OwnerExact.step hown hs + rcases step_fresh_or_beqF hown hs with ⟨rfl, hkept, _⟩ | ⟨rfl, rfl, i, d₀, held, hbeq, heq, _⟩ + · have stepEach (gen : Nat) : I (pre ++ [x]) (none, next, a.2.2.1.push x.1, gen, a.2.2.2.2) := by + refine ⟨rfl, hown', fun y hy => ?_⟩ + rcases List.mem_append.mp hy with hy | hy + · obtain ⟨i, d₀, held, hbeq, heq⟩ := heach y hy + refine ⟨i, d₀, ?_, hbeq, heq⟩ + rw [hkept] + exact getElem?_push_of_some x.1 held + · simp only [List.mem_singleton] at hy + subst y + refine ⟨a.2.1.kept.size, x.1, ?_, Declaration.beqF_refl x.1, rfl⟩ + rw [hkept] + exact getElem?_push_self a.2.1.kept x.1 + by_cases early : x.2 < raw.generated + · simpa only [readStep, hs, early, ite_true, pure] using stepEach (a.2.2.2.1 + 1) + · simpa only [readStep, hs, early, ite_false, pure] using stepEach a.2.2.2.1 + · simp only [readStep, hs, pure] + refine ⟨rfl, hown, fun y hy => ?_⟩ + rcases List.mem_append.mp hy with hy | hy + · exact heach y hy + · simp only [List.mem_singleton] at hy + subst hy + exact ⟨i, d₀, held, hbeq, heq⟩) _ init + change I raw.decls.zipIdx.toList (readLoop s first raw) ∨ ReadBad (readLoop s first raw) at inv + rw [collapseRead_eq] at h + rcases inv with ⟨hact, _, heach⟩ | ⟨t, e, he⟩ + · simp only [readFinish, hact, Prod.mk.injEq, Except.ok.injEq] at h + obtain ⟨rfl, rfl, _⟩ := h + obtain ⟨j, hj⟩ := List.mem_map.mp + (show d ∈ raw.decls.zipIdx.toList.map Prod.fst by simpa using hd) + have := heach j hj.1 + rwa [hj.2] at this + · simp [readFinish, he] at h + +/-- **Connecting `Converted.quotient_eq_substKeys_canon` to `collapseRead`**: +for any converted group `c`, lowering the canonicalized key-substituted group +`Converted.substKeys ρ (Converted.canon c)` is identical to lowering +`Converted.quotient ρ c`, and when `collapseRead` succeeds from `OwnerExact s`, +every declaration `d ∈ raw.decls.toList` has a `Declaration.beqF`-matching +declaration `d₀` stored in `s'.kept` (with `d = d₀`). -/ +theorem collapseRead_substKeys_canon_beqF {ρ : FiberMap} {st : State .source} + {c : Converted} {raw r : Read .source} + {s s' : CollapseState} {first : SKey} {dropped : Array SKey} + (owner : OwnerExact s) + (hlow : lower st (Converted.substKeys ρ (Converted.canon c)) = .ok raw) + (hcol : collapseRead s first raw = (s', .ok (r, dropped))) : + lower st (Converted.quotient ρ c) = .ok raw ∧ + OwnerExact s' ∧ + ∀ d ∈ raw.decls.toList, + ∃ (i : Nat) (d₀ : Declaration .source), s'.kept[i]? = some d₀ ∧ Declaration.beqF d d₀ = true ∧ d = d₀ := by + rw [← Converted.quotient_eq_substKeys_canon] at hlow + exact ⟨hlow, collapseRead_kept_beqF owner hcol⟩ + +/-- When `collapsedChecksWith ixe t b ev ds = none`, all three executable +stream/bundle/evidence checks return `true`. -/ +theorem collapsedChecksWith_booleans {ixe : Ixon.Env} {t : Ix.Kernel.RoleTable} + {b : Ix.RoleEvidence.Bundle} {ev : Ix.Kernel.Evidences .source} + {ds : Array (Declaration .source)} + (h : collapsedChecksWith ixe t b ev ds = none) : + let ρ := fiberAliasAt ixe (roleAtMap t) + let src := sourceAddrs ixe + declsGoodB (qRepAtB src ρ) ds = true ∧ + bundleDisjointB ixe b = true ∧ + evidenceGoodB (qEvB src ρ (bundleAddrs b)) (evidenceSubst ρ ev) = true := by + unfold collapsedChecksWith at h + dsimp only at h ⊢ + split at h + · cases h + rename_i h1 + split at h + · cases h + rename_i h2 + split at h + · cases h + rename_i h3 + simp only [Bool.not_eq_true', Bool.not_eq_false] at h1 h2 h3 + exact ⟨h1, h2, h3⟩ + +/-- **Connection between `prepareSourceCollapsedAtUsing` and the trace invariants**, +over explicit bundle and evidence sources. -/ +theorem prepareSourceCollapsedAtUsing_trace {ixe : Ixon.Env} {t : Ix.Kernel.RoleTable} + {bundle : Except String Ix.RoleEvidence.Bundle} {ev : Ix.Kernel.Evidences .source} + {hints : CName .source → Option Ix.Kernel.ReducibilityHint} + {pre : Prelude .source} {sub : Ix.Environment} {blocks : Ix.CondensedBlocks} + {ds : Array (Declaration .source)} + (h : prepareSourceCollapsedAtUsing bundle ev ixe t hints pre sub blocks = .ok ds) : + let ρ := fiberAliasAt ixe (roleAtMap t) + let src := sourceAddrs ixe + ∃ b rs fin decls s kb, + bundle = .ok b ∧ + Trace (graphCollapsed ρ hints sub blocks) pre.state + (scheduleOf (graphCollapsed ρ hints sub blocks)).zipIdx.toList rs fin s kb ∧ + s.kept = decls ∧ + fin = rs.foldl State.commit pre.state ∧ + decls.toList = rs.flatMap (·.decls.toList) ∧ + ds = Ix.Kernel.Frontend.preparePrelude pre.ix decls ∧ + Ix.Kernel.Reader.State.keepsPrelude pre.state fin = true ∧ + StableOwned s fin ∧ + OwnerExact s ∧ + KeptNoBasis s ∧ + declsGoodB (qRepAtB src ρ) ds = true ∧ + bundleDisjointB ixe b = true ∧ + evidenceGoodB (qEvB src ρ (bundleAddrs b)) (evidenceSubst ρ ev) = true ∧ + (∀ d ∈ ds, d.Good (KeyScope.ofId (QRepAt ixe (roleAtMap t)))) ∧ + (evidenceSubst ρ ev).Good + (fun n => QRepAt ixe (roleAtMap t) n ∨ BundleOnly ixe b n) := by + dsimp only + unfold prepareSourceCollapsedAtUsing at h + cases hp : prepareSourceCollapsed (fiberAliasAt ixe (roleAtMap t)) hints pre sub blocks with + | error e => rw [hp] at h; cases h + | ok ds' => + rw [hp] at h + dsimp only at h + cases hc : collapsedChecksUsing bundle ev ixe t ds' with + | error e => rw [hc] at h; cases h + | ok u => + rw [hc] at h + cases h + obtain ⟨b, hbun, hcw⟩ := collapsedChecksUsing_spec hc + obtain ⟨hdB, hbdB, hevB⟩ := collapsedChecksWith_booleans hcw + obtain ⟨hdGood, _, _, hevGood⟩ := collapsedChecksWith_spec hcw + obtain ⟨fin, decls, hl, hkeeps, hprep⟩ := prepareSourceCollapsed_keepsPrelude hp + obtain ⟨rs, s, kb, htrace, hkept, hfin, hflat, hstable⟩ := + Ix.Cert.SourceTrace.lowerAllCollapsed_stable hl + exact ⟨b, rs, fin, decls, s, kb, hbun, htrace, hkept, hfin, hflat, hprep, hkeeps, + hstable, trace_ownerExact htrace, trace_keptNoBasis htrace, + hdB, hbdB, hevB, hdGood, hevGood⟩ + +/-- **Connection between `prepareSourceCollapsedAt` and the trace invariants**: +whenever `prepareSourceCollapsedAt ixe t hints pre sub blocks = .ok ds`, there is +a committed quotient bundle `b` and a collapsed trace `(rs, fin, decls, s, kb)` +such that: +* `Trace` holds on `graphCollapsed (fiberAliasAt ixe (roleAtMap t)) hints sub blocks`, + and `StableOwned s fin`, `OwnerExact s`, and `KeptNoBasis s` all hold; +* `State.keepsPrelude pre.state fin = true`, `declsGoodB ... ds = true`, + `bundleDisjointB ixe b = true`, and `evidenceGoodB ... = true` all hold; +* every declaration and evidence item satisfies `Good` on representatives (and + bundle-only names). -/ +theorem prepareSourceCollapsedAt_trace {ixe : Ixon.Env} {t : Ix.Kernel.RoleTable} + {hints : CName .source → Option Ix.Kernel.ReducibilityHint} + {pre : Prelude .source} {sub : Ix.Environment} {blocks : Ix.CondensedBlocks} + {ds : Array (Declaration .source)} + (h : prepareSourceCollapsedAt ixe t hints pre sub blocks = .ok ds) : + let ρ := fiberAliasAt ixe (roleAtMap t) + let src := sourceAddrs ixe + ∃ b rs fin decls s kb, + Ix.RoleEvidence.quotientBundle () = .ok b ∧ + Trace (graphCollapsed ρ hints sub blocks) pre.state + (scheduleOf (graphCollapsed ρ hints sub blocks)).zipIdx.toList rs fin s kb ∧ + s.kept = decls ∧ + fin = rs.foldl State.commit pre.state ∧ + decls.toList = rs.flatMap (·.decls.toList) ∧ + ds = Ix.Kernel.Frontend.preparePrelude pre.ix decls ∧ + Ix.Kernel.Reader.State.keepsPrelude pre.state fin = true ∧ + StableOwned s fin ∧ + OwnerExact s ∧ + KeptNoBasis s ∧ + declsGoodB (qRepAtB src ρ) ds = true ∧ + bundleDisjointB ixe b = true ∧ + evidenceGoodB (qEvB src ρ (bundleAddrs b)) + (evidenceSubst ρ (Ix.RoleEvidence.quotientEvidence ())) = true ∧ + (∀ d ∈ ds, d.Good (KeyScope.ofId (QRepAt ixe (roleAtMap t)))) ∧ + (evidenceSubst ρ (Ix.RoleEvidence.quotientEvidence ())).Good + (fun n => QRepAt ixe (roleAtMap t) n ∨ BundleOnly ixe b n) := + prepareSourceCollapsedAtUsing_trace h + +end Ix.LeanIngress diff --git a/Ix/LeanIngress/Rename.lean b/Ix/LeanIngress/Rename.lean index 45d1345ca..e8125c308 100644 --- a/Ix/LeanIngress/Rename.lean +++ b/Ix/LeanIngress/Rename.lean @@ -142,6 +142,60 @@ theorem Key.subst_good (hroles : FibersRoleFree ixe) : | .role _, _ => trivial | .aux k _, hk => Key.subst_good hroles (k := k) hk +@[simp] theorem ConstantVal.substKeys_name (ρ : FiberMap) + (cv : Ix.Kernel.ConstantVal .source) : + (ConstantVal.substKeys ρ cv).name = Key.subst ρ cv.name := rfl + +@[simp] theorem ConstantVal.substKeys_levelParams (ρ : FiberMap) + (cv : Ix.Kernel.ConstantVal .source) : + (ConstantVal.substKeys ρ cv).levelParams = cv.levelParams := rfl + +@[simp] theorem ConstantVal.substKeys_type (ρ : FiberMap) + (cv : Ix.Kernel.ConstantVal .source) : + (ConstantVal.substKeys ρ cv).type = Expr.substKeys ρ cv.type := rfl + +@[simp] theorem ConstantInfo.substKeys_toConstantVal (ρ : FiberMap) + (c : Ix.Kernel.ConstantInfo .source) : + (ConstantInfo.substKeys ρ c).toConstantVal = + ConstantVal.substKeys ρ c.toConstantVal := by + cases c <;> rfl + +@[simp] theorem ConstantInfo.substKeys_name (ρ : FiberMap) + (c : Ix.Kernel.ConstantInfo .source) : + (ConstantInfo.substKeys ρ c).name = Key.subst ρ c.name := by + cases c <;> rfl + +@[simp] theorem ConstantInfo.substKeys_levelParams (ρ : FiberMap) + (c : Ix.Kernel.ConstantInfo .source) : + (ConstantInfo.substKeys ρ c).toConstantVal.levelParams = + c.toConstantVal.levelParams := by + cases c <;> rfl + +@[simp] theorem ConstantInfo.substKeys_type (ρ : FiberMap) + (c : Ix.Kernel.ConstantInfo .source) : + (ConstantInfo.substKeys ρ c).type = Expr.substKeys ρ c.type := by + cases c <;> rfl + +@[simp] theorem RecRule.substKeys_ctor (ρ : FiberMap) + (r : Ix.Kernel.RecRule .source) : + (RecRule.substKeys ρ r).ctor = Key.subst ρ r.ctor := rfl + +@[simp] theorem RecRule.substKeys_nfields (ρ : FiberMap) + (r : Ix.Kernel.RecRule .source) : + (RecRule.substKeys ρ r).nfields = r.nfields := rfl + +@[simp] theorem RecRule.substKeys_rhs (ρ : FiberMap) + (r : Ix.Kernel.RecRule .source) : + (RecRule.substKeys ρ r).rhs = Expr.substKeys ρ r.rhs := rfl + +@[simp] theorem BlockRead.substKeys_names (ρ : FiberMap) + (rb : Ix.Kernel.Reader.BlockRead .source) : + BlockRead.names (BlockRead.substKeys ρ rb) = + (BlockRead.names rb).map (Key.subst ρ) := by + simp only [BlockRead.names, BlockRead.substKeys, Array.toList_map, + List.map_append, List.map_map, Function.comp_def] + rfl + section Structural variable {ρ : FiberMap} {P Q : Ix.Kernel.Mode.Id .source → Prop} diff --git a/Ix/LeanIngress/Theorems.lean b/Ix/LeanIngress/Theorems.lean index ba5756062..cb63674ba 100644 --- a/Ix/LeanIngress/Theorems.lean +++ b/Ix/LeanIngress/Theorems.lean @@ -330,7 +330,7 @@ theorem checkSourceTiered_typed (V : Type u) [Ix.Kernel.SetTheory V] {env : Ix.E obtain ⟨_, _, tier, _, _, _, hwf, hfresh, htyped⟩ := checkSourceTieredUsing_ok V h exact ⟨hwf, hfresh, _, htyped⟩ -open Ix.Kernel.Reader (CDecl ReadError State) +open Ix.Kernel.Reader (CDecl CExpr CName CVal BlockRead RecCounts ReadError State) /-! ## Coverage: every group is scheduled @@ -457,6 +457,700 @@ theorem checkSource_converts {env : Ix.Environment} {blocks : Ix.CondensedBlocks rfl exact ⟨conv, same ▸ hc⟩ +/-! ## Level canonicalization and key substitution + +Pure characterizations of `canonExprM`, `canonValM`, `DefMember.canon`, +`canonInfo`, `BlockRead.canon`, `Converted.canon`, and `canonDecl`, their +key-preservation and conditional idempotence properties (conditioned on the +level-canonicalizer idempotence hypothesis `hf`), and their commutation with +`substKeys` (`Converted.quotient`). -/ + +theorem LevelMemoInv.empty (f : KLevel → KLevel) : LevelMemoInv f {} := by + intro k v h + simp at h + +@[local simp] theorem mapLevels_id (e : CExpr .source) : mapLevels id e = e := by + induction e <;> simp_all [mapLevels] + +theorem mapLevels_comp (g f : KLevel → KLevel) (e : CExpr .source) : + mapLevels g (mapLevels f e) = mapLevels (g ∘ f) e := by + induction e <;> simp_all [mapLevels, List.map_map] + +theorem mapLevels_congr {f g : KLevel → KLevel} (h : ∀ l, f l = g l) (e : CExpr .source) : + mapLevels f e = mapLevels g e := by + have hfg : f = g := funext h + subst hfg + rfl + +theorem mapLevels_idem {f : KLevel → KLevel} (hf : ∀ l, f (f l) = f l) (e : CExpr .source) : + mapLevels f (mapLevels f e) = mapLevels f e := by + rw [mapLevels_comp] + exact mapLevels_congr hf e + +theorem mapLevels_substKeys (f : KLevel → KLevel) (ρ : FiberMap) (e : CExpr .source) : + mapLevels f (Expr.substKeys ρ e) = Expr.substKeys ρ (mapLevels f e) := by + induction e <;> simp_all [Expr.substKeys, mapLevels] + +@[local simp] theorem lamBody_mapLevels (f : KLevel → KLevel) (e : CExpr .source) : + Ix.Kernel.Frontend.lamBody (mapLevels f e) = mapLevels f (Ix.Kernel.Frontend.lamBody e) := by + induction e with + | lam t b m _ ih => simpa only [Ix.Kernel.Frontend.lamBody, mapLevels] using ih + | _ => rfl + +@[local simp] theorem lamBody_substKeys (ρ : FiberMap) (e : CExpr .source) : + Ix.Kernel.Frontend.lamBody (Expr.substKeys ρ e) = + Expr.substKeys ρ (Ix.Kernel.Frontend.lamBody e) := by + induction e with + | lam t b m _ ih => simpa only [Ix.Kernel.Frontend.lamBody, Expr.substKeys] using ih + | _ => rfl + +@[local simp] theorem piTeleLen_mapLevels (f : KLevel → KLevel) (e : CExpr .source) : + Ix.Kernel.Reader.piTeleLen (mapLevels f e) = Ix.Kernel.Reader.piTeleLen e := by + induction e with + | forallE t b m _ ih => simp only [Ix.Kernel.Reader.piTeleLen, mapLevels, ih] + | _ => rfl + +@[local simp] theorem piTeleLen_substKeys (ρ : FiberMap) (e : CExpr .source) : + Ix.Kernel.Reader.piTeleLen (Expr.substKeys ρ e) = Ix.Kernel.Reader.piTeleLen e := by + induction e with + | forallE t b m _ ih => simp only [Ix.Kernel.Reader.piTeleLen, Expr.substKeys, ih] + | _ => rfl + +/-- Pure level canonicalization of a `ConstantVal` against its own +`levelParams`. -/ +def canonVal (cv : Ix.Kernel.ConstantVal .source) : Ix.Kernel.ConstantVal .source := + { cv with type := mapLevels (canonLevelIn cv.levelParams) cv.type } + +@[local simp] theorem canonVal_name (cv : Ix.Kernel.ConstantVal .source) : + (canonVal cv).name = cv.name := rfl + +@[local simp] theorem canonVal_levelParams (cv : Ix.Kernel.ConstantVal .source) : + (canonVal cv).levelParams = cv.levelParams := rfl + +@[local simp] theorem canonVal_type (cv : Ix.Kernel.ConstantVal .source) : + (canonVal cv).type = mapLevels (canonLevelIn cv.levelParams) cv.type := rfl + +/-- Pure level canonicalization of a recursor rule's firing discipline against +`lps`. -/ +def canonRecRuleFire (lps : List Ix.Kernel.LevelParam) : + Ix.Kernel.RecRuleFire .source → Ix.Kernel.RecRuleFire .source + | .inert => .inert + | .plain => .plain + | .nested lvls pins => + .nested (lvls.map (canonLevelIn lps)) (pins.map (mapLevels (canonLevelIn lps))) + +/-- Pure level canonicalization of a recursor rule against `lps`. -/ +def canonRecRule (lps : List Ix.Kernel.LevelParam) + (r : Ix.Kernel.RecRule .source) : Ix.Kernel.RecRule .source := + { r with + fire := canonRecRuleFire lps r.fire + rhs := mapLevels (canonLevelIn lps) r.rhs } + +/-- Pure level canonicalization of a projection table against +`tbl.levelParams`. -/ +def canonProjTable (tbl : Ix.Kernel.ProjTable .source) : Ix.Kernel.ProjTable .source := + let f := canonLevelIn tbl.levelParams + { tbl with + structSort := f tbl.structSort + guards := tbl.guards.map f + bodies := tbl.bodies.map (mapLevels f) } + +@[local simp] private theorem id_bind {α β : Type} (x : Id α) (f : α → Id β) : (x >>= f) = f x := rfl +@[local simp] private theorem id_pure {α : Type} (x : α) : (pure x : Id α) = x := rfl +@[local simp] private theorem id_map {α β : Type} (f : α → β) (x : Id α) : (f <$> x) = f x := rfl + +theorem canonExprM_run (lps : List Ix.Kernel.LevelParam) (e : CExpr .source) + (memo : Std.HashMap (CExpr .source) (CExpr .source)) + (hm : LevelMemoInv (canonLevelIn lps) memo) : + ((canonExprM lps e).run memo).1 = mapLevels (canonLevelIn lps) e ∧ + LevelMemoInv (canonLevelIn lps) ((canonExprM lps e).run memo).2 := + mapLevelsGo_spec e hm + +@[local simp] theorem canonExprM_run' (lps : List Ix.Kernel.LevelParam) (e : CExpr .source) : + (canonExprM lps e).run' {} = mapLevels (canonLevelIn lps) e := + (canonExprM_run lps e {} (LevelMemoInv.empty _)).1 + +@[local simp] theorem canonExprM_run_empty_fst (lps : List Ix.Kernel.LevelParam) (e : CExpr .source) : + ((canonExprM lps e).run {}).1 = mapLevels (canonLevelIn lps) e := + (canonExprM_run lps e {} (LevelMemoInv.empty _)).1 + +theorem canonValM_run (cv : Ix.Kernel.ConstantVal .source) + (memo : Std.HashMap (CExpr .source) (CExpr .source)) + (hm : LevelMemoInv (canonLevelIn cv.levelParams) memo) : + ((canonValM cv).run memo).1 = canonVal cv ∧ + LevelMemoInv (canonLevelIn cv.levelParams) ((canonValM cv).run memo).2 := by + unfold canonValM + obtain ⟨h1, h2⟩ := canonExprM_run cv.levelParams cv.type memo hm + simp only [StateT.run_bind, id_bind, StateT.run_pure, id_pure, h1] + exact ⟨rfl, h2⟩ + +@[local simp] theorem canonValM_run' (cv : Ix.Kernel.ConstantVal .source) : + (canonValM cv).run' {} = canonVal cv := + (canonValM_run cv {} (LevelMemoInv.empty _)).1 + +@[local simp] theorem canonValM_run_empty_fst (cv : Ix.Kernel.ConstantVal .source) : + ((canonValM cv).run {}).1 = canonVal cv := + (canonValM_run cv {} (LevelMemoInv.empty _)).1 + +@[local simp] theorem canonValM_name (cv : Ix.Kernel.ConstantVal .source) : + ((canonValM cv).run' {}).name = cv.name := by + simp + +@[local simp] theorem canonValM_levelParams (cv : Ix.Kernel.ConstantVal .source) : + ((canonValM cv).run' {}).levelParams = cv.levelParams := by + simp + +theorem list_mapM_run_inv {σ α β : Type} (I : σ → Prop) (f : α → StateM σ β) (g : α → β) + (hf : ∀ x s, I s → ((f x).run s).1 = g x ∧ I ((f x).run s).2) : + ∀ (xs : List α) (s : σ), I s → + ((xs.mapM f).run s).1 = xs.map g ∧ I ((xs.mapM f).run s).2 + | [], s, hs => ⟨rfl, hs⟩ + | x :: xs, s, hs => by + obtain ⟨h1, hs1⟩ := hf x s hs + obtain ⟨h2, hs2⟩ := list_mapM_run_inv I f g hf xs ((f x).run s).2 hs1 + simp only [List.mapM_cons, StateT.run_bind, id_bind, StateT.run_pure, id_pure, + h1, h2, List.map_cons] + exact ⟨trivial, hs2⟩ + +theorem array_mapM_run_inv {σ α β : Type} (I : σ → Prop) (f : α → StateM σ β) (g : α → β) + (hf : ∀ x s, I s → ((f x).run s).1 = g x ∧ I ((f x).run s).2) + (xs : Array α) (s : σ) (hs : I s) : + ((xs.mapM f).run s).1 = xs.map g ∧ I ((xs.mapM f).run s).2 := by + rw [Array.mapM_eq_mapM_toList] + obtain ⟨h1, h2⟩ := list_mapM_run_inv I f g hf xs.toList s hs + simp only [StateT.run_map, id_map, h1] + refine ⟨?_, h2⟩ + rcases xs with ⟨l⟩ + simp + +@[local simp] theorem DefMember.canon_eq (m : DefMember) : + DefMember.canon m = + { m with + cv := canonVal m.cv + value := mapLevels (canonLevelIn m.cv.levelParams) m.value } := by + unfold DefMember.canon + obtain ⟨hcv, hm₁⟩ := canonValM_run m.cv {} (LevelMemoInv.empty _) + obtain ⟨hval, _⟩ := canonExprM_run m.cv.levelParams m.value _ hm₁ + simp only [StateT.run'_eq, StateT.run_bind, id_bind, StateT.run_pure, id_pure, + id_map, hcv, hval] + +@[local simp] theorem DefMember.canon_cv_name (m : DefMember) : + (DefMember.canon m).cv.name = m.cv.name := by + simp + +@[local simp] theorem DefMember.canon_cv_levelParams (m : DefMember) : + (DefMember.canon m).cv.levelParams = m.cv.levelParams := by + simp + +theorem canonRecRuleM_run (lps : List Ix.Kernel.LevelParam) (r : Ix.Kernel.RecRule .source) + (memo : Std.HashMap (CExpr .source) (CExpr .source)) + (hm : LevelMemoInv (canonLevelIn lps) memo) : + (((do + let fire ← match r.fire with + | .nested lvls pins => do + let ps ← pins.mapM (canonExprM lps) + pure (Ix.Kernel.RecRuleFire.nested (lvls.map (canonLevelIn lps)) ps) + | f => pure f + return { r with rhs := ← canonExprM lps r.rhs, fire } : + StateM (Std.HashMap (CExpr .source) (CExpr .source)) + (Ix.Kernel.RecRule .source)).run memo)).1 = canonRecRule lps r ∧ + LevelMemoInv (canonLevelIn lps) + (((do + let fire ← match r.fire with + | .nested lvls pins => do + let ps ← pins.mapM (canonExprM lps) + pure (Ix.Kernel.RecRuleFire.nested (lvls.map (canonLevelIn lps)) ps) + | f => pure f + return { r with rhs := ← canonExprM lps r.rhs, fire } : + StateM (Std.HashMap (CExpr .source) (CExpr .source)) + (Ix.Kernel.RecRule .source)).run memo)).2 := by + rcases r with ⟨ctor, nfields, rparams, fire, rhs, k, p1, p2⟩ + cases fire with + | inert => + obtain ⟨hrhs, hm'⟩ := canonExprM_run lps rhs memo hm + simp only [StateT.run_bind, id_bind, StateT.run_pure, id_pure, + canonRecRule, canonRecRuleFire, hrhs] + exact ⟨trivial, hm'⟩ + | plain => + obtain ⟨hrhs, hm'⟩ := canonExprM_run lps rhs memo hm + simp only [StateT.run_bind, id_bind, StateT.run_pure, id_pure, + canonRecRule, canonRecRuleFire, hrhs] + exact ⟨trivial, hm'⟩ + | nested lvls pins => + obtain ⟨hpins, hm₁⟩ := list_mapM_run_inv (LevelMemoInv (canonLevelIn lps)) + (canonExprM lps) (mapLevels (canonLevelIn lps)) (canonExprM_run lps) pins memo hm + obtain ⟨hrhs, hm₂⟩ := canonExprM_run lps rhs _ hm₁ + simp only [StateT.run_bind, id_bind, StateT.run_pure, id_pure, + canonRecRule, canonRecRuleFire, hpins, hrhs] + exact ⟨trivial, hm₂⟩ + +@[local simp] theorem canonInfo_axiomInfo (cv : Ix.Kernel.ConstantVal .source) : + canonInfo (.axiomInfo cv) = .axiomInfo (canonVal cv) := by + unfold canonInfo canonInfo.go + obtain ⟨hcv, _⟩ := canonValM_run cv {} (LevelMemoInv.empty _) + simp only [StateT.run'_eq, StateT.run_bind, id_bind, StateT.run_pure, id_pure, + id_map, hcv] + +@[local simp] theorem canonInfo_defnInfo (cv : Ix.Kernel.ConstantVal .source) + (v : CExpr .source) (h : Ix.Kernel.ReducibilityHint) : + canonInfo (.defnInfo cv v h) = + .defnInfo (canonVal cv) (mapLevels (canonLevelIn cv.levelParams) v) h := by + unfold canonInfo canonInfo.go + obtain ⟨hcv, hm₁⟩ := canonValM_run cv {} (LevelMemoInv.empty _) + obtain ⟨hv, _⟩ := canonExprM_run cv.levelParams v _ hm₁ + simp only [StateT.run'_eq, StateT.run_bind, id_bind, StateT.run_pure, id_pure, + id_map, hcv, hv] + +@[local simp] theorem canonInfo_thmInfo (cv : Ix.Kernel.ConstantVal .source) (v : CExpr .source) : + canonInfo (.thmInfo cv v) = + .thmInfo (canonVal cv) (mapLevels (canonLevelIn cv.levelParams) v) := by + unfold canonInfo canonInfo.go + obtain ⟨hcv, hm₁⟩ := canonValM_run cv {} (LevelMemoInv.empty _) + obtain ⟨hv, _⟩ := canonExprM_run cv.levelParams v _ hm₁ + simp only [StateT.run'_eq, StateT.run_bind, id_bind, StateT.run_pure, id_pure, + id_map, hcv, hv] + +@[local simp] theorem canonInfo_indInfo (cv : Ix.Kernel.ConstantVal .source) + (caps : Ix.Kernel.IndCaps .source) : + canonInfo (.indInfo cv caps) = .indInfo (canonVal cv) caps := by + unfold canonInfo canonInfo.go + obtain ⟨hcv, _⟩ := canonValM_run cv {} (LevelMemoInv.empty _) + simp only [StateT.run'_eq, StateT.run_bind, id_bind, StateT.run_pure, id_pure, + id_map, hcv] + +@[local simp] theorem canonInfo_ctorInfo (cv : Ix.Kernel.ConstantVal .source) (nP nF : Nat) : + canonInfo (.ctorInfo cv nP nF) = .ctorInfo (canonVal cv) nP nF := by + unfold canonInfo canonInfo.go + obtain ⟨hcv, _⟩ := canonValM_run cv {} (LevelMemoInv.empty _) + simp only [StateT.run'_eq, StateT.run_bind, id_bind, StateT.run_pure, id_pure, + id_map, hcv] + +@[local simp] theorem canonInfo_recInfo (cv : Ix.Kernel.ConstantVal .source) (m p : Nat) + (rules : List (Ix.Kernel.RecRule .source)) : + canonInfo (.recInfo cv m p rules) = + .recInfo (canonVal cv) m p (rules.map (canonRecRule cv.levelParams)) := by + unfold canonInfo canonInfo.go + obtain ⟨hcv, hm₁⟩ := canonValM_run cv {} (LevelMemoInv.empty _) + obtain ⟨hrules, _⟩ := list_mapM_run_inv (LevelMemoInv (canonLevelIn cv.levelParams)) + _ (canonRecRule cv.levelParams) (canonRecRuleM_run cv.levelParams) rules _ hm₁ + simp only [StateT.run'_eq, StateT.run_bind, id_bind, StateT.run_pure, id_pure, + id_map, hcv] + exact congrArg (Ix.Kernel.ConstantInfo.recInfo (canonVal cv) m p) hrules + +@[local simp] theorem canonInfo_projInfo (tbl : Ix.Kernel.ProjTable .source) : + canonInfo (.projInfo tbl) = .projInfo (canonProjTable tbl) := by + unfold canonInfo canonInfo.go + obtain ⟨hbodies, _⟩ := array_mapM_run_inv (LevelMemoInv (canonLevelIn tbl.levelParams)) + (canonExprM tbl.levelParams) (mapLevels (canonLevelIn tbl.levelParams)) + (canonExprM_run tbl.levelParams) tbl.bodies {} (LevelMemoInv.empty _) + simp only [StateT.run'_eq, StateT.run_bind, id_bind, StateT.run_pure, id_pure, + id_map, hbodies, canonProjTable] + +@[local simp] theorem BlockRead.canon_eq (rb : BlockRead .source) : + BlockRead.canon rb = + { rb with + types := rb.types.map fun (c, p, i) => (canonVal c, p, i) + ctors := rb.ctors.map fun (c, p, f) => (canonVal c, p, f) + recs := rb.recs.map fun (c, rc, rules) => + (canonVal c, rc, rules.map (canonRecRule c.levelParams)) } := by + simp only [BlockRead.canon, canonValM_run', canonInfo_recInfo] + +@[local simp] theorem canonDecl_axiomDecl (cv : Ix.Kernel.ConstantVal .source) : + canonDecl (.axiomDecl cv) = .axiomDecl (canonVal cv) := by + simp [canonDecl] + +@[local simp] theorem canonDecl_defnDecl (cv : Ix.Kernel.ConstantVal .source) (v : CExpr .source) + (h : Ix.Kernel.ReducibilityHint) : + canonDecl (.defnDecl cv v h) = + .defnDecl (canonVal cv) (mapLevels (canonLevelIn cv.levelParams) v) h := by + unfold canonDecl + obtain ⟨hcv, hm₁⟩ := canonValM_run cv {} (LevelMemoInv.empty _) + obtain ⟨hv, _⟩ := canonExprM_run cv.levelParams v _ hm₁ + simp only [StateT.run'_eq, StateT.run_bind, id_bind, StateT.run_pure, id_pure, + id_map, hcv, hv] + +@[local simp] theorem canonDecl_thmDecl (cv : Ix.Kernel.ConstantVal .source) (v : CExpr .source) : + canonDecl (.thmDecl cv v) = + .thmDecl (canonVal cv) (mapLevels (canonLevelIn cv.levelParams) v) := by + unfold canonDecl + obtain ⟨hcv, hm₁⟩ := canonValM_run cv {} (LevelMemoInv.empty _) + obtain ⟨hv, _⟩ := canonExprM_run cv.levelParams v _ hm₁ + simp only [StateT.run'_eq, StateT.run_bind, id_bind, StateT.run_pure, id_pure, + id_map, hcv, hv] + +@[local simp] theorem canonDecl_opaqueDecl (cv : Ix.Kernel.ConstantVal .source) (v : CExpr .source) : + canonDecl (.opaqueDecl cv v) = + .opaqueDecl (canonVal cv) (mapLevels (canonLevelIn cv.levelParams) v) := by + unfold canonDecl + obtain ⟨hcv, hm₁⟩ := canonValM_run cv {} (LevelMemoInv.empty _) + obtain ⟨hv, _⟩ := canonExprM_run cv.levelParams v _ hm₁ + simp only [StateT.run'_eq, StateT.run_bind, id_bind, StateT.run_pure, id_pure, + id_map, hcv, hv] + +@[local simp] theorem canonDecl_basisDecl (k : Ix.Kernel.BasisKind) : + canonDecl (.basisDecl k) = .basisDecl k := rfl + +@[local simp] theorem canonDecl_indDecl (block : List (Ix.Kernel.ConstantInfo .source)) (nP : Nat) : + canonDecl (.indDecl block nP) = .indDecl (block.map canonInfo) nP := rfl + +@[local simp] theorem canonDecl_quotDecl (k : Ix.Kernel.QuotKind) + (cv : Ix.Kernel.ConstantVal .source) : + canonDecl (.quotDecl k cv) = .quotDecl k (canonVal cv) := by + simp [canonDecl] + +/-! ### Key and name preservation -/ + +/-- The names a converted group declares, in order. -/ +def Converted.names : Converted → List (CName .source) + | .inductive rb => BlockRead.names rb + | .definitions ms => ms.toList.map (·.cv.name) + | .axiom cv => [cv.name] + | .quot _ cv => [cv.name] + +@[local simp] theorem BlockRead.canon_names (rb : BlockRead .source) : + BlockRead.names (BlockRead.canon rb) = BlockRead.names rb := by + simp only [BlockRead.names, BlockRead.canon_eq, Array.toList_map, List.map_map, + Function.comp_def, canonVal_name] + +@[local simp] theorem Converted.canon_names (c : Converted) : + Converted.names (Converted.canon c) = Converted.names c := by + cases c with + | «inductive» rb => exact BlockRead.canon_names rb + | definitions ms => + simp only [Converted.canon, Converted.names, Array.toList_map, List.map_map] + have h : ((fun m : DefMember => m.cv.name) ∘ DefMember.canon) = (·.cv.name) := by + funext m; simp + rw [h] + | «axiom» cv => simp [Converted.canon, Converted.names] + | quot k cv => simp [Converted.canon, Converted.names] + +@[local simp] theorem Converted.substKeys_names (ρ : FiberMap) (c : Converted) : + Converted.names (Converted.substKeys ρ c) = (Converted.names c).map (Key.subst ρ) := by + cases c with + | «inductive» rb => exact BlockRead.substKeys_names ρ rb + | definitions ms => + simp only [Converted.substKeys, Converted.names, Array.toList_map, List.map_map] + rfl + | «axiom» cv => rfl + | quot k cv => rfl + +@[local simp] theorem Converted.quotient_names (ρ : FiberMap) (c : Converted) : + Converted.names (Converted.quotient ρ c) = (Converted.names c).map (Key.subst ρ) := by + rw [Converted.quotient, Converted.canon_names, Converted.substKeys_names] + +/-! ### Idempotence under level-canonicalizer idempotence -/ + +theorem canonExprM_idem {lps : List Ix.Kernel.LevelParam} + (hf : ∀ l, canonLevelIn lps (canonLevelIn lps l) = canonLevelIn lps l) + (e : CExpr .source) : + (canonExprM lps ((canonExprM lps e).run' {})).run' {} = (canonExprM lps e).run' {} := by + simp only [canonExprM_run', mapLevels_idem hf] + +theorem canonVal_idem {cv : Ix.Kernel.ConstantVal .source} + (hf : ∀ l, canonLevelIn cv.levelParams (canonLevelIn cv.levelParams l) = + canonLevelIn cv.levelParams l) : + canonVal (canonVal cv) = canonVal cv := by + simp only [canonVal, mapLevels_idem hf] + +theorem canonValM_idem {cv : Ix.Kernel.ConstantVal .source} + (hf : ∀ l, canonLevelIn cv.levelParams (canonLevelIn cv.levelParams l) = + canonLevelIn cv.levelParams l) : + (canonValM ((canonValM cv).run' {})).run' {} = (canonValM cv).run' {} := by + simp only [canonValM_run', canonVal_idem hf] + +theorem DefMember.canon_idem {m : DefMember} + (hf : ∀ l, canonLevelIn m.cv.levelParams (canonLevelIn m.cv.levelParams l) = + canonLevelIn m.cv.levelParams l) : + DefMember.canon (DefMember.canon m) = DefMember.canon m := by + simp only [DefMember.canon_eq, canonVal_levelParams, canonVal_idem hf, mapLevels_idem hf] + +theorem canonRecRuleFire_idem {lps : List Ix.Kernel.LevelParam} + (hf : ∀ l, canonLevelIn lps (canonLevelIn lps l) = canonLevelIn lps l) + (f : Ix.Kernel.RecRuleFire .source) : + canonRecRuleFire lps (canonRecRuleFire lps f) = canonRecRuleFire lps f := by + cases f with + | inert => rfl + | plain => rfl + | nested lvls pins => + simp only [canonRecRuleFire, List.map_map] + congr 1 + · exact List.map_congr_left fun l _ => hf l + · exact List.map_congr_left fun e _ => mapLevels_idem hf e + +theorem canonRecRule_idem {lps : List Ix.Kernel.LevelParam} + (hf : ∀ l, canonLevelIn lps (canonLevelIn lps l) = canonLevelIn lps l) + (r : Ix.Kernel.RecRule .source) : + canonRecRule lps (canonRecRule lps r) = canonRecRule lps r := by + rcases r with ⟨ctor, nfields, rparams, fire, rhs, k, p1, p2⟩ + simp only [canonRecRule, canonRecRuleFire_idem hf, mapLevels_idem hf] + +theorem canonProjTable_idem {tbl : Ix.Kernel.ProjTable .source} + (hf : ∀ l, canonLevelIn tbl.levelParams (canonLevelIn tbl.levelParams l) = + canonLevelIn tbl.levelParams l) : + canonProjTable (canonProjTable tbl) = canonProjTable tbl := by + have hss : canonLevelIn tbl.levelParams (canonLevelIn tbl.levelParams tbl.structSort) = + canonLevelIn tbl.levelParams tbl.structSort := hf tbl.structSort + have hgs : (tbl.guards.map (canonLevelIn tbl.levelParams)).map (canonLevelIn tbl.levelParams) = + tbl.guards.map (canonLevelIn tbl.levelParams) := by + rw [List.map_map] + exact List.map_congr_left fun l _ => hf l + have hbs : (tbl.bodies.map (mapLevels (canonLevelIn tbl.levelParams))).map + (mapLevels (canonLevelIn tbl.levelParams)) = + tbl.bodies.map (mapLevels (canonLevelIn tbl.levelParams)) := by + rw [← Array.toList_inj] + simp only [Array.toList_map, List.map_map] + exact List.map_congr_left fun e _ => mapLevels_idem hf e + simp only [canonProjTable, hss, hgs, hbs] + +/-- Conditional idempotence of `canonInfo`, given level-canonicalizer +idempotence `hf`. -/ +theorem canonInfo_idem + (hf : ∀ lps l, canonLevelIn lps (canonLevelIn lps l) = canonLevelIn lps l) + (ci : Ix.Kernel.ConstantInfo .source) : + canonInfo (canonInfo ci) = canonInfo ci := by + cases ci with + | axiomInfo cv => + simp only [canonInfo_axiomInfo, canonVal_idem (hf cv.levelParams)] + | defnInfo cv v h => + simp only [canonInfo_defnInfo, canonVal_levelParams, + canonVal_idem (hf cv.levelParams), mapLevels_idem (hf cv.levelParams)] + | thmInfo cv v => + simp only [canonInfo_thmInfo, canonVal_levelParams, + canonVal_idem (hf cv.levelParams), mapLevels_idem (hf cv.levelParams)] + | indInfo cv caps => + simp only [canonInfo_indInfo, canonVal_idem (hf cv.levelParams)] + | ctorInfo cv nP nF => + simp only [canonInfo_ctorInfo, canonVal_idem (hf cv.levelParams)] + | recInfo cv m p rules => + simp only [canonInfo_recInfo, canonVal_levelParams, + canonVal_idem (hf cv.levelParams), List.map_map] + congr 1 + exact List.map_congr_left fun r _ => canonRecRule_idem (hf cv.levelParams) r + | projInfo tbl => + simp only [canonInfo_projInfo, canonProjTable_idem (hf tbl.levelParams)] + +/-- Conditional idempotence of `BlockRead.canon`, given level-canonicalizer +idempotence `hf`. -/ +theorem BlockRead.canon_idem + (hf : ∀ lps l, canonLevelIn lps (canonLevelIn lps l) = canonLevelIn lps l) + (rb : BlockRead .source) : + BlockRead.canon (BlockRead.canon rb) = BlockRead.canon rb := by + have htys : (rb.types.map fun (c, p, i) => (canonVal c, p, i)).map + (fun (c, p, i) => (canonVal c, p, i)) = + rb.types.map fun (c, p, i) => (canonVal c, p, i) := by + rw [← Array.toList_inj] + simp only [Array.toList_map, List.map_map] + refine List.map_congr_left fun ⟨c, p, i⟩ _ => ?_ + simp only [Function.comp_apply, canonVal_idem (hf c.levelParams)] + have hcts : (rb.ctors.map fun (c, p, f) => (canonVal c, p, f)).map + (fun (c, p, f) => (canonVal c, p, f)) = + rb.ctors.map fun (c, p, f) => (canonVal c, p, f) := by + rw [← Array.toList_inj] + simp only [Array.toList_map, List.map_map] + refine List.map_congr_left fun ⟨c, p, f⟩ _ => ?_ + simp only [Function.comp_apply, canonVal_idem (hf c.levelParams)] + have hrs : (rb.recs.map fun (c, rc, rules) => + (canonVal c, rc, rules.map (canonRecRule c.levelParams))).map + (fun (c, rc, rules) => (canonVal c, rc, rules.map (canonRecRule c.levelParams))) = + rb.recs.map fun (c, rc, rules) => + (canonVal c, rc, rules.map (canonRecRule c.levelParams)) := by + rw [← Array.toList_inj] + simp only [Array.toList_map, List.map_map] + refine List.map_congr_left fun ⟨c, rc, rules⟩ _ => ?_ + simp only [Function.comp_apply, canonVal_levelParams, + canonVal_idem (hf c.levelParams), List.map_map] + congr 2 + exact List.map_congr_left fun r _ => canonRecRule_idem (hf c.levelParams) r + simp only [BlockRead.canon_eq, htys, hcts, hrs] + +/-- Conditional idempotence of `Converted.canon`, given the level-canonicalizer +idempotence hypothesis `hf : ∀ lps l, canonLevelIn lps (canonLevelIn lps l) = canonLevelIn lps l`. -/ +theorem Converted.canon_idem + (hf : ∀ lps l, canonLevelIn lps (canonLevelIn lps l) = canonLevelIn lps l) + (c : Converted) : + Converted.canon (Converted.canon c) = Converted.canon c := by + cases c with + | «inductive» rb => + simp only [Converted.canon, BlockRead.canon_idem hf] + | definitions ms => + simp only [Converted.canon, Array.map_map] + congr 1 + rw [← Array.toList_inj] + simp only [Array.toList_map] + exact List.map_congr_left fun m _ => DefMember.canon_idem (hf m.cv.levelParams) + | «axiom» cv => + simp only [Converted.canon, canonValM_run', canonVal_idem (hf cv.levelParams)] + | quot k cv => + simp only [Converted.canon, canonValM_run', canonVal_idem (hf cv.levelParams)] + +/-- Conditional idempotence of `canonDecl`, given the level-canonicalizer +idempotence hypothesis `hf : ∀ lps l, canonLevelIn lps (canonLevelIn lps l) = canonLevelIn lps l`. -/ +theorem canonDecl_idem + (hf : ∀ lps l, canonLevelIn lps (canonLevelIn lps l) = canonLevelIn lps l) + (d : CDecl .source) : + canonDecl (canonDecl d) = canonDecl d := by + cases d with + | axiomDecl cv => + simp only [canonDecl_axiomDecl, canonVal_idem (hf cv.levelParams)] + | defnDecl cv v h => + simp only [canonDecl_defnDecl, canonVal_levelParams, + canonVal_idem (hf cv.levelParams), mapLevels_idem (hf cv.levelParams)] + | thmDecl cv v => + simp only [canonDecl_thmDecl, canonVal_levelParams, + canonVal_idem (hf cv.levelParams), mapLevels_idem (hf cv.levelParams)] + | opaqueDecl cv v => + simp only [canonDecl_opaqueDecl, canonVal_levelParams, + canonVal_idem (hf cv.levelParams), mapLevels_idem (hf cv.levelParams)] + | basisDecl k => rfl + | indDecl block nP => + simp only [canonDecl_indDecl, List.map_map] + congr 1 + exact List.map_congr_left fun ci _ => canonInfo_idem hf ci + | quotDecl k cv => + simp only [canonDecl_quotDecl, canonVal_idem (hf cv.levelParams)] + +/-! ### Commutation of level canonicalization with key substitution -/ + +@[local simp] theorem canonExprM_substKeys (ρ : FiberMap) (lps : List Ix.Kernel.LevelParam) + (e : CExpr .source) : + (canonExprM lps (Expr.substKeys ρ e)).run' {} = + Expr.substKeys ρ ((canonExprM lps e).run' {}) := by + simp only [canonExprM_run', mapLevels_substKeys] + +@[local simp] theorem canonVal_substKeys (ρ : FiberMap) (cv : Ix.Kernel.ConstantVal .source) : + canonVal (ConstantVal.substKeys ρ cv) = ConstantVal.substKeys ρ (canonVal cv) := by + simp only [canonVal, ConstantVal.substKeys, mapLevels_substKeys] + +@[local simp] theorem canonValM_substKeys (ρ : FiberMap) (cv : Ix.Kernel.ConstantVal .source) : + (canonValM (ConstantVal.substKeys ρ cv)).run' {} = + ConstantVal.substKeys ρ ((canonValM cv).run' {}) := by + simp only [canonValM_run', canonVal_substKeys] + +@[local simp] theorem DefMember.canon_substKeys (ρ : FiberMap) (m : DefMember) : + DefMember.canon (DefMember.substKeys ρ m) = DefMember.substKeys ρ (DefMember.canon m) := by + simp only [DefMember.canon_eq, DefMember.substKeys, ConstantVal.substKeys_levelParams, + canonVal_substKeys, mapLevels_substKeys] + +@[local simp] theorem canonRecRuleFire_substKeys (ρ : FiberMap) (lps : List Ix.Kernel.LevelParam) + (f : Ix.Kernel.RecRuleFire .source) : + canonRecRuleFire lps (RecRuleFire.substKeys ρ f) = + RecRuleFire.substKeys ρ (canonRecRuleFire lps f) := by + cases f with + | inert => rfl + | plain => rfl + | nested lvls pins => + simp only [canonRecRuleFire, RecRuleFire.substKeys, List.map_map, Function.comp_def, + mapLevels_substKeys] + +@[local simp] theorem canonRecRule_substKeys (ρ : FiberMap) (lps : List Ix.Kernel.LevelParam) + (r : Ix.Kernel.RecRule .source) : + canonRecRule lps (RecRule.substKeys ρ r) = RecRule.substKeys ρ (canonRecRule lps r) := by + rcases r with ⟨ctor, nfields, rparams, fire, rhs, k, p1, p2⟩ + simp only [canonRecRule, RecRule.substKeys, canonRecRuleFire_substKeys, mapLevels_substKeys] + +@[local simp] theorem canonProjTable_substKeys (ρ : FiberMap) (tbl : Ix.Kernel.ProjTable .source) : + canonProjTable (ProjTable.substKeys ρ tbl) = ProjTable.substKeys ρ (canonProjTable tbl) := by + have hbs : (tbl.bodies.map (Expr.substKeys ρ)).map (mapLevels (canonLevelIn tbl.levelParams)) = + (tbl.bodies.map (mapLevels (canonLevelIn tbl.levelParams))).map (Expr.substKeys ρ) := by + rw [← Array.toList_inj] + simp only [Array.toList_map, List.map_map, Function.comp_def, mapLevels_substKeys] + simp only [canonProjTable, ProjTable.substKeys, hbs] + +@[local simp] theorem canonInfo_substKeys (ρ : FiberMap) (ci : Ix.Kernel.ConstantInfo .source) : + canonInfo (ConstantInfo.substKeys ρ ci) = ConstantInfo.substKeys ρ (canonInfo ci) := by + cases ci with + | axiomInfo cv => + simp only [ConstantInfo.substKeys, canonInfo_axiomInfo, canonVal_substKeys] + | defnInfo cv v h => + simp only [ConstantInfo.substKeys, canonInfo_defnInfo, ConstantVal.substKeys_levelParams, + canonVal_substKeys, mapLevels_substKeys] + | thmInfo cv v => + simp only [ConstantInfo.substKeys, canonInfo_thmInfo, ConstantVal.substKeys_levelParams, + canonVal_substKeys, mapLevels_substKeys] + | indInfo cv caps => + simp only [ConstantInfo.substKeys, canonInfo_indInfo, canonVal_substKeys] + | ctorInfo cv nP nF => + simp only [ConstantInfo.substKeys, canonInfo_ctorInfo, canonVal_substKeys] + | recInfo cv mI rP rules => + simp only [ConstantInfo.substKeys, canonInfo_recInfo, ConstantVal.substKeys_levelParams, + canonVal_substKeys, List.map_map, Function.comp_def, canonRecRule_substKeys] + | projInfo tbl => + simp only [ConstantInfo.substKeys, canonInfo_projInfo, canonProjTable_substKeys] + +@[local simp] theorem BlockRead.canon_substKeys (ρ : FiberMap) (rb : BlockRead .source) : + BlockRead.canon (BlockRead.substKeys ρ rb) = BlockRead.substKeys ρ (BlockRead.canon rb) := by + have htys : (rb.types.map fun (cv, p, i) => (ConstantVal.substKeys ρ cv, p, i)).map + (fun (c, p, i) => (canonVal c, p, i)) = + (rb.types.map fun (c, p, i) => (canonVal c, p, i)).map + fun (cv, p, i) => (ConstantVal.substKeys ρ cv, p, i) := by + rw [← Array.toList_inj] + simp only [Array.toList_map, List.map_map] + refine List.map_congr_left fun ⟨cv, p, i⟩ _ => ?_ + simp only [Function.comp_apply, canonVal_substKeys] + have hcts : (rb.ctors.map fun (cv, p, f) => (ConstantVal.substKeys ρ cv, p, f)).map + (fun (c, p, f) => (canonVal c, p, f)) = + (rb.ctors.map fun (c, p, f) => (canonVal c, p, f)).map + fun (cv, p, f) => (ConstantVal.substKeys ρ cv, p, f) := by + rw [← Array.toList_inj] + simp only [Array.toList_map, List.map_map] + refine List.map_congr_left fun ⟨cv, p, f⟩ _ => ?_ + simp only [Function.comp_apply, canonVal_substKeys] + have hrs : (rb.recs.map fun (cv, rc, rules) => + (ConstantVal.substKeys ρ cv, rc, rules.map (RecRule.substKeys ρ))).map + (fun (c, rc, rules) => (canonVal c, rc, rules.map (canonRecRule c.levelParams))) = + (rb.recs.map fun (c, rc, rules) => + (canonVal c, rc, rules.map (canonRecRule c.levelParams))).map + fun (cv, rc, rules) => (ConstantVal.substKeys ρ cv, rc, rules.map (RecRule.substKeys ρ)) := by + rw [← Array.toList_inj] + simp only [Array.toList_map, List.map_map] + refine List.map_congr_left fun ⟨cv, rc, rules⟩ _ => ?_ + simp only [Function.comp_apply, ConstantVal.substKeys_levelParams, canonVal_substKeys, + List.map_map, Function.comp_def, canonRecRule_substKeys] + simp only [BlockRead.canon_eq, BlockRead.substKeys, htys, hcts, hrs] + +@[local simp] theorem Converted.canon_substKeys (ρ : FiberMap) (c : Converted) : + Converted.canon (Converted.substKeys ρ c) = Converted.substKeys ρ (Converted.canon c) := by + cases c with + | «inductive» rb => + simp only [Converted.canon, Converted.substKeys, BlockRead.canon_substKeys] + | definitions ms => + simp only [Converted.canon, Converted.substKeys, Array.map_map, Function.comp_def, + DefMember.canon_substKeys] + | «axiom» cv => + simp only [Converted.canon, Converted.substKeys, canonValM_run', canonVal_substKeys] + | quot k cv => + simp only [Converted.canon, Converted.substKeys, canonValM_run', canonVal_substKeys] + +theorem Converted.quotient_eq_substKeys_canon (ρ : FiberMap) (c : Converted) : + Converted.quotient ρ c = Converted.substKeys ρ (Converted.canon c) := + Converted.canon_substKeys ρ c + +@[local simp] theorem canonDecl_substKeys (ρ : FiberMap) (d : CDecl .source) : + canonDecl (Declaration.substKeys ρ d) = Declaration.substKeys ρ (canonDecl d) := by + cases d with + | axiomDecl cv => + simp only [Declaration.substKeys, canonDecl_axiomDecl, canonVal_substKeys] + | defnDecl cv v h => + simp only [Declaration.substKeys, canonDecl_defnDecl, ConstantVal.substKeys_levelParams, + canonVal_substKeys, mapLevels_substKeys] + | thmDecl cv v => + simp only [Declaration.substKeys, canonDecl_thmDecl, ConstantVal.substKeys_levelParams, + canonVal_substKeys, mapLevels_substKeys] + | opaqueDecl cv v => + simp only [Declaration.substKeys, canonDecl_opaqueDecl, ConstantVal.substKeys_levelParams, + canonVal_substKeys, mapLevels_substKeys] + | basisDecl k => rfl + | indDecl block nP => + simp only [Declaration.substKeys, canonDecl_indDecl, List.map_map, Function.comp_def, + canonInfo_substKeys] + | quotDecl k cv => + simp only [Declaration.substKeys, canonDecl_quotDecl, canonVal_substKeys] + +@[simp] theorem Declaration.beqF_refl (d : CDecl .source) : + Declaration.beqF d d = true := by + rw [Declaration.beqF_eq] + exact decide_eq_true rfl + end Ix.LeanIngress /-! ## Axioms @@ -530,3 +1224,18 @@ the build. -/ /-- info: 'Ix.LeanIngress.mapLevelsList_eq_fast' depends on axioms: [propext, Classical.choice, Quot.sound] -/ #guard_msgs in #print axioms Ix.LeanIngress.mapLevelsList_eq_fast + +/-- info: 'Ix.LeanIngress.canonDecl_idem' depends on axioms: [propext, Classical.choice, Quot.sound] -/ +#guard_msgs in #print axioms Ix.LeanIngress.canonDecl_idem + +/-- info: 'Ix.LeanIngress.Converted.canon_idem' depends on axioms: [propext, Classical.choice, Quot.sound] -/ +#guard_msgs in #print axioms Ix.LeanIngress.Converted.canon_idem + +/-- info: 'Ix.LeanIngress.Converted.quotient_eq_substKeys_canon' depends on axioms: [propext, Classical.choice, Quot.sound] -/ +#guard_msgs in #print axioms Ix.LeanIngress.Converted.quotient_eq_substKeys_canon + +/-- info: 'Ix.LeanIngress.canonDecl_substKeys' depends on axioms: [propext, Classical.choice, Quot.sound] -/ +#guard_msgs in #print axioms Ix.LeanIngress.canonDecl_substKeys + +/-- info: 'Ix.LeanIngress.Declaration.beqF_refl' depends on axioms: [propext, Quot.sound] -/ +#guard_msgs in #print axioms Ix.LeanIngress.Declaration.beqF_refl diff --git a/Ix/Meta.lean b/Ix/Meta.lean index f513fc95f..201ca7d44 100644 --- a/Ix/Meta.lean +++ b/Ix/Meta.lean @@ -71,7 +71,7 @@ partial def findLakeRoot (start : FilePath) : IO (Option FilePath) := do exactly as before. -/ def initLeanSearchPathFor (file : FilePath) : IO Unit := do let parent := file.parent.getD ⟨"."⟩ - initLeanSearchPath (some ((← findLakeRoot parent).getD parent)) + initLeanSearchPath (← findLakeRoot parent) /-- A file's elaborated environment plus whether its header declares `module`. -/ structure FileEnv where diff --git a/Ix/PrimAddrs.lean b/Ix/PrimAddrs.lean index 2992a0798..dee89883e 100644 --- a/Ix/PrimAddrs.lean +++ b/Ix/PrimAddrs.lean @@ -6,15 +6,17 @@ public import Std.Data.HashSet /-! # The canonical primitive addresses (Address-only) -Mirror: crates/kernel/src/primitive.rs (`PrimAddrs::new()`). +Mirror: crates/common/src/prim_addrs.rs (`PrimAddrs::new()`). The primitive and reserved-marker content addresses, the Lean parity table, and their membership set are shared by the resource validator and IxVM claim harness. They require only the address representation, independently of the kernel typechecker. -The addresses are hardcoded BLAKE3 hashes matching Rust's `PrimAddrs::new()`; -regenerate with `lake test -- --ignored rust-kernel-build-primitives` and -update both tables. The ordered live comparison is +The addresses are hardcoded BLAKE3 hashes matching `PrimAddrs::new()` in +`ix-common`; regenerate them as `docs/kernel.md` describes (`lake exe +ixon-v4-primitives`; `lake test -- --ignored rust-kernel-build-primitives` +prints the table in `PrimAddrs::new` form) and update both tables. The ordered +live comparison is `Tests.Ix.Kernel.PrimitiveTable` in the `prim-addrs` entry. `Lean.reduceBool` and `Lean.reduceNat` dispatch by their real content @@ -228,18 +230,10 @@ def canonical : PrimAddrs where stringAppend := h "0eae69f3f8b198d1ffac67b9e88d39526b1b039ff519cd87eec106f63974860c" stringDecEq := h "da7bb331e098f9bf5cdabcb9609a7e953dffc609ad9d90871a14c016c52a3011" -/-- The synthetic kernel-only marker address used by the *original* - (LEON-addressed) environment's `PrimAddrs::new_orig()`. Only the marker is - ported — the full orig table belongs to the Lean→kernel ingress half, - which is out of scope. -/ -def origEagerReduce : Address := - h "ff00000000000000000000000000000000000000000000000000000000000013" - /-- Addresses reserved for kernel-only reduction markers. These are not Lean constants and must never be accepted as user environment entries. -/ def reservedMarkerAddrs : Array (String × Address) := - #[("eager_reduce", canonical.eagerReduce), - ("orig.eager_reduce", origEagerReduce)] + #[("eager_reduce", canonical.eagerReduce)] /-- `(lean_name, canonical_address_hex)` pairs in the same order as Rust's `PrimAddrs::lean_parity_table()` / the `kernelPrimitives` list. Used by @@ -352,18 +346,12 @@ def reservedMarkerName (addr : Address) : Option String := address (built once at module init from `leanParityTable` + `reservedMarkerAddrs`). - Soundness note: the kernel substitutes native/GMP semantics for the - declarations at these addresses (`tryReduceNat*`/`tryReduceDecidable`/ - …), so its verdicts are sound only if address = content holds exactly - here — which the blake3 integrity check at materialization - establishes. Ingress therefore verifies prim-addressed constants - UNCONDITIONALLY, even under `--no-verify` (`getConstVerified`): for - every other constant, skipping verification merely risks checking a - mislabeled-but-still-checked declaration; a mislabeled primitive - would be silently trusted with the wrong semantics. The Rust mirror - has no analogous hole — its integrity check is unconditional at - deserialize (`crates/ixon/src/serialize.rs` `Env::get`/`get_anon`, - plus the anon merkle-root check). -/ + The IxVM claim harness (`IxVM.ClaimHarness.closureFrom` and its + siblings) seeds a claim's byte scope with the members an environment + carries: the kernel can reference them without any `Constant.refs` + naming them. Membership grants no semantics: every constant a native + rule recognizes or builds is a role, read through the claim's role + binding and validated against its specification. -/ def primAddrSet : Std.HashSet Address := Id.run do let mut s : Std.HashSet Address := Std.HashSet.emptyWithCapacity (PrimAddrs.leanParityTable.size + 4) diff --git a/Ix/RoleBinding/Artifact.lean b/Ix/RoleBinding/Artifact.lean index a0c273b0b..b9d696444 100644 --- a/Ix/RoleBinding/Artifact.lean +++ b/Ix/RoleBinding/Artifact.lean @@ -3,6 +3,7 @@ module public import Ix.RoleBinding.Publication public import Ix.Ixon.Authenticated public import Ix.KernelCheck.Step +public import IxC.Kernel.Admission.UserAxioms /-! # Certified checking of an artifact's own binding and records @@ -102,6 +103,111 @@ def materializeOrder (artifact : Ixon.Env) (store : RecordStore) (order : Array decoded := decoded.push (address, constant) return (ordered.toList,decoded.toList) +/-! ## Decoding in parallel + +`decodeRecords` is one loop over every record. `decodeRecordsPar` decodes +chunks of the records on parallel tasks, each from its first record's +position, and joins them in order; it is `decodeRecords`, the same value and +the same first failure (`decodeRecordsPar_eq`). -/ + +section DecodePar + +open Ix.Kernel.Admission + +theorem decodeLoop_acc (limits : Limits) (position : Nat) (records : Records) + (reversed : Ix.Kernel.Ingress.Constants) : + decodeLoop limits position records reversed = + (decodeLoop limits position records []).map (reversed.reverse ++ ·) := by + induction records generalizing position reversed with + | nil => simp [decodeLoop, Except.map] + | cons pair rest ih => + rcases pair with ⟨address, bytes⟩ + cases decoded : Ixon.Canonical.deConstant limits.maxRecordBytes limits.maxRecordUnivNodes bytes with + | error reason => simp [decodeLoop, decoded, Except.mapError, bind, Except.bind, Except.map] + | ok constant => + simp only [decodeLoop, decoded, Except.mapError, bind, Except.bind] + rw [ih (position + 1) ((address, constant) :: reversed), ih (position + 1) [(address, constant)]] + cases decodeLoop limits (position + 1) rest [] <;> simp [Except.map, List.append_assoc] + +theorem decodeLoop_append (limits : Limits) (position : Nat) (front back : Records) : + decodeLoop limits position (front ++ back) [] = + (decodeLoop limits position front []).bind fun first => + (decodeLoop limits (position + front.length) back []).map (first ++ ·) := by + induction front generalizing position with + | nil => + simp only [List.nil_append, List.length_nil, Nat.add_zero, decodeLoop, List.reverse_nil, Except.bind] + cases decodeLoop limits position back [] <;> rfl + | cons pair rest ih => + rcases pair with ⟨address, bytes⟩ + cases decoded : Ixon.Canonical.deConstant limits.maxRecordBytes limits.maxRecordUnivNodes bytes with + | error reason => simp [decodeLoop, decoded, Except.mapError, bind, Except.bind] + | ok constant => + simp only [List.cons_append, decodeLoop, decoded, Except.mapError, bind, Except.bind] + rw [decodeLoop_acc limits (position + 1) (rest ++ back) [(address, constant)], ih (position + 1), + decodeLoop_acc limits (position + 1) rest [(address, constant)]] + have shift : position + (rest.length + 1) = position + 1 + rest.length := by omega + simp only [List.length_cons, shift] + cases decodeLoop limits (position + 1) rest [] with + | error e => simp [Except.bind, Except.map] + | ok first => + cases decodeLoop limits (position + 1 + rest.length) back [] <;> + simp [Except.bind, Except.map] + +/-- One decoding task per chunk of `size + 1` records, each with its first +record's position, all spawned before any is joined. -/ +def spawnDecodes (limits : Limits) (size : Nat) (position : Nat) : + Records → List (Task (Except ByteError Ix.Kernel.Ingress.Constants)) + | [] => [] + | r :: rs => + (Task.spawn fun _ => decodeLoop limits position ((r :: rs).take (size + 1)) []) :: + spawnDecodes limits size (position + (size + 1)) ((r :: rs).drop (size + 1)) +termination_by records => records.length +decreasing_by simp; omega + +/-- The chunks' decodings joined in order: the first failure, else every +chunk's constants in order. -/ +def joinDecodes : List (Task (Except ByteError Ix.Kernel.Ingress.Constants)) → + Except ByteError Ix.Kernel.Ingress.Constants + | [] => .ok [] + | t :: ts => + match t.get with + | .error e => .error e + | .ok first => (joinDecodes ts).map (first ++ ·) + +theorem joinDecodes_spawnDecodes (limits : Limits) (size : Nat) : + (position : Nat) → (records : Records) → + joinDecodes (spawnDecodes limits size position records) = decodeLoop limits position records [] + | position, [] => by simp [spawnDecodes, joinDecodes, decodeLoop] + | position, r :: rs => by + rw [spawnDecodes, joinDecodes] + rw [joinDecodes_spawnDecodes limits size (position + (size + 1)) ((r :: rs).drop (size + 1))] + conv => rhs; rw [← List.take_append_drop (size + 1) (r :: rs), decodeLoop_append] + show (match decodeLoop limits position ((r :: rs).take (size + 1)) [] with + | .error e => (Except.error e : Except ByteError Ix.Kernel.Ingress.Constants) + | .ok first => (decodeLoop limits (position + (size + 1)) ((r :: rs).drop (size + 1)) []).map + (first ++ ·)) = _ + cases decodeLoop limits position ((r :: rs).take (size + 1)) [] with + | error e => rfl + | ok first => + by_cases fits : size + 1 ≤ (r :: rs).length + · have least : min size rs.length = size := Nat.min_eq_left (by simp at fits; omega) + simp [Except.bind, least] + · have rest : (r :: rs).drop (size + 1) = [] := List.drop_eq_nil_of_le (by omega) + simp [rest, decodeLoop, Except.bind, Except.map] +termination_by _ records => records.length +decreasing_by simp; omega + +/-- `decodeRecords` on chunks of at least 256 records, decoded in parallel. -/ +def decodeRecordsPar (limits : Limits) (records : Records) : + Except ByteError Ix.Kernel.Ingress.Constants := + joinDecodes (spawnDecodes limits (Nat.max 255 (records.length / 64)) 0 records) + +theorem decodeRecordsPar_eq (limits : Limits) (records : Records) : + decodeRecordsPar limits records = decodeRecords limits records := by + simp only [decodeRecordsPar, decodeRecords, joinDecodes_spawnDecodes] + +end DecodePar + /-- The order is selected before reading/checking, then subjected to the same complete artifact coverage, ownership and uniqueness guards. -/ def prepareRecordsFor (artifact : Ixon.Env) (limits : Ix.Kernel.Admission.Limits) @@ -140,6 +246,54 @@ def prepareRecordsAt (artifact : Ixon.Env) (limits : Ix.Kernel.Admission.Limits) (order : Array Address) : Except Error PreparedRecords := prepareRecordsFor artifact limits (some order) +/-- `prepareRecordsFor` with the records decoded in parallel +(`decodeRecordsPar`): the same preparation, the same value and the same +first refusal (`prepareRecordsForPar_eq`), for hosts that run it outside +a certified entry. -/ +def prepareRecordsForPar (artifact : Ixon.Env) (limits : Ix.Kernel.Admission.Limits) + (requested : Option (Array Address)) : + Except Error PreparedRecords := do + let some raw := artifact.roleBinding | throw .missingBinding + let binding : Ix.Kernel.Admission.Binding := ⟨raw.address, raw.bytes⟩ + let rawRecords := (artifact.consts.toArray.map fun (a, c) => (a, c.rawBytes)).qsort + fun x y => x.1.cmpBytes y.1 == .lt + let blobs := (artifact.blobs.toArray.qsort fun x y => x.1.cmpBytes y.1 == .lt).toList + (Ix.Kernel.Admission.preflight limits rawRecords.toList blobs).mapError .bytes + (Ix.Kernel.Admission.uniqueKeys rawRecords.toList blobs).mapError .bytes + (Ix.Kernel.Admission.authenticate Address.admissionHash rawRecords.toList blobs).mapError .bytes + let table ← (Ix.Kernel.Admission.admitBinding Address.admissionHash binding).mapError .binding + let constants ← (decodeRecordsPar limits rawRecords.toList).mapError .bytes + let store : RecordStore := constants.foldl (fun m (a, c) => m.insert a c) {} + checkBindings table store + let preRecords ← (Ix.RoleBinding.preludeRecords table (store[·]?) Ix.Kernel.preludeShape).mapError .prelude + let pre : Prelude .anon ← (readPrelude table preRecords #[] blobs).mapError .prelude + (checkPreludeShape pre).mapError .prelude + (checkFallbackRecursors pre).mapError .prelude + let order := match requested with + | none => Ixon.CheckOrder.wholeEnv table pre constants.toArray + | some order => order + unless order.size == rawRecords.size do throw (.coverage "record order does not cover the artifact") + let (ordered,decoded) ← materializeOrder artifact store order + let hints := Hints.ofStore store artifact.anonHints + return { prepared := { binding, table, pre, records := ordered, blobs, hints } + constants := decoded } + +theorem prepareRecordsForPar_eq (artifact : Ixon.Env) (limits : Ix.Kernel.Admission.Limits) + (requested : Option (Array Address)) : + prepareRecordsForPar artifact limits requested = prepareRecordsFor artifact limits requested := by + unfold prepareRecordsForPar prepareRecordsFor + simp only [decodeRecordsPar_eq] + +/-- `prepareRecords` with the records decoded in parallel +(`prepareRecordsPar_eq`). -/ +def prepareRecordsPar (artifact : Ixon.Env) (limits : Ix.Kernel.Admission.Limits) : + Except Error PreparedRecords := + prepareRecordsForPar artifact limits none + +theorem prepareRecordsPar_eq (artifact : Ixon.Env) (limits : Ix.Kernel.Admission.Limits) : + prepareRecordsPar artifact limits = prepareRecords artifact limits := + prepareRecordsForPar_eq artifact limits none + def prepareFor (artifact : Ixon.Env) (limits : Ix.Kernel.Admission.Limits) (requested : Option (Array Address)) : Except Error Prepared := do let input ← prepareRecordsFor artifact limits requested @@ -195,4 +349,43 @@ def checkAt (artifact : Ixon.Env) (limits : Ix.Kernel.Admission.Limits) (order : | .error e => .error (.admission e) | .ok target => .ok ⟨prepared,target,fromArtifact,checked⟩ +/-- A receipt of preparation and of the check that admits user axioms +(`Admission.checkBytesUnder .allowUser`), with the kernel's list of the user +axioms among the declarations it ran over (`axioms`). Its accept holds only +under them: no theorem covers it and no host statement is issued for it, +unless the list is empty, when it is a receipt of the certified entry +(`ReceiptConditional.toReceipt`). -/ +structure ReceiptConditional (artifact : Ixon.Env) (limits : Ix.Kernel.Admission.Limits) where + prepared : Prepared + target : Ix.Kernel.Env .anon + axioms : List (Ix.Kernel.ConstantVal .anon) + preparedFrom : prepare artifact limits = .ok prepared + checked : Ix.Kernel.Admission.checkBytesUnder .allowUser Address.admissionHash prepared.binding + prepared.pre limits prepared.records prepared.blobs prepared.hints.lookup = .ok (target, axioms) + +def checkConditional (artifact : Ixon.Env) (limits : Ix.Kernel.Admission.Limits) : + Except Error (ReceiptConditional artifact limits) := + match fromArtifact : prepare artifact limits with + | .error e => .error e + | .ok prepared => + match checked : Ix.Kernel.Admission.checkBytesUnder .allowUser Address.admissionHash prepared.binding + prepared.pre limits prepared.records prepared.blobs prepared.hints.lookup with + | .error e => .error (.admission e) + | .ok (target, axioms) => .ok ⟨prepared, target, axioms, fromArtifact, checked⟩ + +/-- **A conditional receipt that lists no user axiom is a receipt of the +certified entry**, with the same preparation and environment (T1, +`Admission.checkBytesUnder_strict_of_nil`). One run of the check that admits +user axioms therefore also gives the certified receipt, and the host statement, +of an artifact that declares none. -/ +def ReceiptConditional.toReceipt {artifact : Ixon.Env} {limits : Ix.Kernel.Admission.Limits} + (receipt : ReceiptConditional artifact limits) (noAxioms : receipt.axioms = []) : + Receipt artifact limits where + prepared := receipt.prepared + target := receipt.target + preparedFrom := receipt.preparedFrom + checked := Ix.Kernel.Admission.checkBytesUnder_strict_of_nil (policy := .allowUser) (by + rw [← noAxioms] + exact receipt.checked) + end Ix.RoleBinding.Artifact diff --git a/Ix/RoleBinding/ArtifactAudit.lean b/Ix/RoleBinding/ArtifactAudit.lean index 6fcffda59..85aaf5d2c 100644 --- a/Ix/RoleBinding/ArtifactAudit.lean +++ b/Ix/RoleBinding/ArtifactAudit.lean @@ -46,22 +46,40 @@ replace only their entry/preparation wrappers. No runtime ruling changes. -/ -- These entries use the supplied binding, so they gain no committed-fixture -- helpers. All direct and reached implementation, extern, unsafe, csimp, -- replacement and opaque entries were compared exactly and are unchanged. -/-- info: runtime closure of [Ix.RoleBinding.Artifact.check]: 6780 compiled functions; inherited externs 115, implemented_by 0, unsafe 26, csimp 8; ruled assumed 4, computed_field 21, csimp 28 -/ +/-- info: runtime closure of [Ix.RoleBinding.Artifact.check]: 6777 compiled functions; inherited externs 115, implemented_by 0, unsafe 26, csimp 8; ruled assumed 4, computed_field 21, csimp 28 -/ #guard_msgs (whitespace := lax) in run_cmd Ix.Kernel.Audit.checkRuntimeWith #[``Ix.RoleBinding.Artifact.check] #[`Init, `Std] rulings -/-- info: runtime closure of [Ix.RoleBinding.Artifact.checkTiered]: 6914 compiled functions; inherited externs 115, implemented_by 0, unsafe 26, csimp 8; ruled assumed 4, computed_field 21, csimp 28 -/ +/-- info: runtime closure of [Ix.RoleBinding.Artifact.checkTiered]: 6911 compiled functions; inherited externs 115, implemented_by 0, unsafe 26, csimp 8; ruled assumed 4, computed_field 21, csimp 28 -/ #guard_msgs (whitespace := lax) in run_cmd Ix.Kernel.Audit.checkRuntimeWith #[``Ix.RoleBinding.Artifact.checkTiered] #[`Init, `Std] rulings -/-- info: runtime closure of [Ix.RoleBinding.Artifact.checkAt]: 6780 compiled functions; inherited externs 115, implemented_by 0, unsafe 26, csimp 8; ruled assumed 4, computed_field 21, csimp 28 -/ +/-- info: runtime closure of [Ix.RoleBinding.Artifact.checkAt]: 6777 compiled functions; inherited externs 115, implemented_by 0, unsafe 26, csimp 8; ruled assumed 4, computed_field 21, csimp 28 -/ #guard_msgs (whitespace := lax) in run_cmd Ix.Kernel.Audit.checkRuntimeWith #[``Ix.RoleBinding.Artifact.checkAt] #[`Init, `Std] rulings -/-- info: runtime closure of [Ix.RoleBinding.Artifact.checkTieredAt]: 6914 compiled functions; inherited externs 115, implemented_by 0, unsafe 26, csimp 8; ruled assumed 4, computed_field 21, csimp 28 -/ +/-- info: runtime closure of [Ix.RoleBinding.Artifact.checkTieredAt]: 6911 compiled functions; inherited externs 115, implemented_by 0, unsafe 26, csimp 8; ruled assumed 4, computed_field 21, csimp 28 -/ #guard_msgs (whitespace := lax) in run_cmd Ix.Kernel.Audit.checkRuntimeWith #[``Ix.RoleBinding.Artifact.checkTieredAt] #[`Init, `Std] rulings +/- `ix check-lean --allow-user-axioms` runs the entries that admit user axioms +in place of `check` and `checkTiered`. When the kernel lists no user axiom, +their receipt is the certified one (`ReceiptConditional.toReceipt`, +`TierReceiptConditional.toTierReceipt`, T1) and a host statement is derived +from it, so their closure is frozen here with the same rulings. It has 61 +more compiled functions than `checkTiered`'s (the fold under the axiom policy +and the list); the extern, implemented_by, unsafe and csimp counts and every +ruling are those of `check` and `checkTiered`. Their kernel entries (`Admission.checkBytesUnder`, +`checkBytesTieredUsingUnder`) are frozen apart in +`IxC/Kernel/Audit/Roots.lean` (`userAxiomOperations`). -/ +/-- info: runtime closure of [Ix.RoleBinding.Artifact.checkConditional, + Ix.RoleBinding.Artifact.checkTieredConditional]: 6972 compiled functions; inherited externs 115, implemented_by 0, unsafe 26, csimp 8; ruled assumed 4, computed_field 21, csimp 28 -/ +#guard_msgs (whitespace := lax) in +run_cmd do + Ix.Kernel.Audit.checkRuntimeWith + #[``Ix.RoleBinding.Artifact.checkConditional, ``Ix.RoleBinding.Artifact.checkTieredConditional] + #[`Init, `Std] rulings + #guard_kernel_axioms Ixon.putTagN_eq_I [propext, Quot.sound] #guard_kernel_axioms Ix.RoleBinding.Artifact.Receipt.strongModel [propext, Classical.choice, Quot.sound] #guard_kernel_axioms Ix.RoleBinding.Artifact.Receipt.authentic [propext, Classical.choice, Quot.sound] @@ -73,6 +91,9 @@ run_cmd Ix.Kernel.Audit.checkRuntimeWith #[``Ix.RoleBinding.Artifact.checkTiered #guard_kernel_axioms Ix.RoleBinding.Artifact.Materialized.complete [propext, Classical.choice, Quot.sound] #guard_kernel_axioms Ix.RoleBinding.Artifact.prepareRecordsAt_reading [propext, Classical.choice, Quot.sound] #guard_kernel_axioms Ix.RoleBinding.Artifact.prepareRecordsAt_complete [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.RoleBinding.Artifact.decodeRecordsPar_eq [propext, Quot.sound] +#guard_kernel_axioms Ix.RoleBinding.Artifact.prepareRecordsForPar_eq [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.RoleBinding.Artifact.prepareRecordsPar_eq [propext, Classical.choice, Quot.sound] #guard_kernel_axioms Ix.RoleBinding.Artifact.prepareAt_obligations [propext, Classical.choice, Quot.sound] #guard_kernel_axioms Ix.RoleBinding.Artifact.prepareTieredAt_obligations [propext, Classical.choice, Quot.sound] #guard_kernel_axioms Ix.RoleBinding.Artifact.OrderedReceipt.complete [propext, Classical.choice, Quot.sound] @@ -81,5 +102,9 @@ run_cmd Ix.Kernel.Audit.checkRuntimeWith #[``Ix.RoleBinding.Artifact.checkTiered #guard_kernel_axioms Ix.RoleBinding.Artifact.OrderedTierReceipt.target_unique [propext, Classical.choice, Quot.sound] #guard_kernel_axioms Ix.RoleBinding.Artifact.OrderedReceipt.authentic [propext, Classical.choice, Quot.sound] #guard_kernel_axioms Ix.RoleBinding.Artifact.OrderedTierReceipt.authentic [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.RoleBinding.Artifact.ReceiptConditional.toReceipt [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.RoleBinding.Artifact.TierReceiptConditional.toTierReceipt [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.RoleBinding.Artifact.ReceiptConditional.strongModel_of_nil [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.RoleBinding.Artifact.TierReceiptConditional.strongModel_of_nil [propext, Classical.choice, Quot.sound] end Ix.RoleBinding.Artifact.Audit diff --git a/Ix/RoleBinding/ArtifactTheorems.lean b/Ix/RoleBinding/ArtifactTheorems.lean index 6eb5411c9..1410f3e67 100644 --- a/Ix/RoleBinding/ArtifactTheorems.lean +++ b/Ix/RoleBinding/ArtifactTheorems.lean @@ -17,4 +17,13 @@ theorem Receipt.authentic {artifact : Ixon.Env} {limits : Ix.Kernel.Admission.Li Address.admissionHash receipt.prepared.binding.bytes = receipt.prepared.binding.address := Ix.Kernel.Admission.checkBytesWith_authentic receipt.checked +/-- A conditional receipt that lists no user axiom has the certified receipt's +model: it is that receipt (`ReceiptConditional.toReceipt`, T1). One that lists +some has no model claimed. -/ +theorem ReceiptConditional.strongModel_of_nil (V : Type u) [Ix.Kernel.SetTheory V] + {artifact : Ixon.Env} {limits : Ix.Kernel.Admission.Limits} + (receipt : ReceiptConditional artifact limits) (noAxioms : receipt.axioms = []) : + Nonempty (Ix.Kernel.Cached.StrongModel V receipt.target) := + (receipt.toReceipt noAxioms).strongModel V + end Ix.RoleBinding.Artifact diff --git a/Ix/RoleBinding/ArtifactTiered.lean b/Ix/RoleBinding/ArtifactTiered.lean index cd6485f82..88c56b0a1 100644 --- a/Ix/RoleBinding/ArtifactTiered.lean +++ b/Ix/RoleBinding/ArtifactTiered.lean @@ -96,4 +96,44 @@ def checkTieredAt (artifact : Ixon.Env) (limits : Ix.Kernel.Admission.Limits) (o | .error e => .error (.admission e) | .ok (logical,tier) => .ok ⟨prepared,logical,tier,fromArtifact,checked⟩ +/-- A receipt of preparation and of the tiered check that admits user axioms on +the logical part (`Admission.checkBytesTieredUsingUnder .allowUser`), with the +kernel's list of the user axioms among the logical declarations (`axioms`). Its +accept holds only under them, unless the list is empty, when it is a receipt of +the certified tiered entry (`TierReceiptConditional.toTierReceipt`). -/ +structure TierReceiptConditional (artifact : Ixon.Env) (limits : Ix.Kernel.Admission.Limits) where + prepared : Prepared + logical : Ix.Kernel.Env .anon + tier : List (Ix.Kernel.Tier.TierConst .anon) + axioms : List (Ix.Kernel.ConstantVal .anon) + preparedFrom : prepareTiered artifact limits = .ok prepared + checked : Ix.Kernel.Admission.checkBytesTieredUsingUnder .allowUser prepared.preludeFor + Address.admissionHash prepared.binding limits prepared.records prepared.blobs + prepared.hints.lookup = .ok ((logical, tier), axioms) + +def checkTieredConditional (artifact : Ixon.Env) (limits : Ix.Kernel.Admission.Limits) : + Except TierError (TierReceiptConditional artifact limits) := + match fromArtifact : prepareTiered artifact limits with + | .error e => .error e + | .ok prepared => + match checked : Ix.Kernel.Admission.checkBytesTieredUsingUnder .allowUser prepared.preludeFor + Address.admissionHash prepared.binding limits prepared.records prepared.blobs + prepared.hints.lookup with + | .error e => .error (.admission e) + | .ok ((logical, tier), axioms) => .ok ⟨prepared, logical, tier, axioms, fromArtifact, checked⟩ + +/-- **A tiered conditional receipt that lists no user axiom is a receipt of the +certified tiered entry**, with the same preparation, logical environment and +tier (T1, `Admission.checkBytesTieredUsingUnder_strict_of_nil`). -/ +def TierReceiptConditional.toTierReceipt {artifact : Ixon.Env} {limits : Ix.Kernel.Admission.Limits} + (receipt : TierReceiptConditional artifact limits) (noAxioms : receipt.axioms = []) : + TierReceipt artifact limits where + prepared := receipt.prepared + logical := receipt.logical + tier := receipt.tier + preparedFrom := receipt.preparedFrom + checked := Ix.Kernel.Admission.checkBytesTieredUsingUnder_strict_of_nil (policy := .allowUser) (by + rw [← noAxioms] + exact receipt.checked) + end Ix.RoleBinding.Artifact diff --git a/Ix/RoleBinding/ArtifactTieredTheorems.lean b/Ix/RoleBinding/ArtifactTieredTheorems.lean index 006bccd97..7e70b9bd8 100644 --- a/Ix/RoleBinding/ArtifactTieredTheorems.lean +++ b/Ix/RoleBinding/ArtifactTieredTheorems.lean @@ -29,4 +29,13 @@ theorem TierReceipt.typed (V : Type u) [Ix.Kernel.SetTheory V] obtain ⟨_, wf, fresh, _, declarations, _, typed⟩ := Ix.Kernel.Admission.checkConstantsTiered_ok V checked exact ⟨wf, fresh, declarations.toList.map (·.2), typed⟩ +/-- A tiered conditional receipt that lists no user axiom has the certified +tiered receipt's model of its logical part: it is that receipt +(`TierReceiptConditional.toTierReceipt`, T1). -/ +theorem TierReceiptConditional.strongModel_of_nil (V : Type u) [Ix.Kernel.SetTheory V] + {artifact : Ixon.Env} {limits : Ix.Kernel.Admission.Limits} + (receipt : TierReceiptConditional artifact limits) (noAxioms : receipt.axioms = []) : + Nonempty (Ix.Kernel.Cached.StrongModel V receipt.logical) := + (receipt.toTierReceipt noAxioms).strongModel V + end Ix.RoleBinding.Artifact diff --git a/Ix/RoleBinding/Compile.lean b/Ix/RoleBinding/Compile.lean index dce71e4fb..0a51a290c 100644 --- a/Ix/RoleBinding/Compile.lean +++ b/Ix/RoleBinding/Compile.lean @@ -1,7 +1,7 @@ module public import Ix.RoleBinding.Generate -public import Ix.CompileResource +public import Ix.Cert.Compile public import Ix.EnvScope public import Ix.LeanExport public import Ix.Meta @@ -25,20 +25,6 @@ open Ix.Kernel (Role) The finalizer also checks every requested support/certificate publication. -/ abbrev Backend := Lean.Environment → Ix.Compile.CompileInput → IO Ixon.Env -def leanBackend (workers : Nat) (opts : Ix.CompileM.DriverOptions) : Backend := fun _ input => do - let out ← IO.ofExcept (← Ix.CompileM.compileLeanInputValidated input (numWorkers := workers) (opts := opts)) - unless out.cenv.ungrounded.isEmpty && out.ungroundedCount == 0 do - throw (IO.userError "artifact support: incomplete Lean compilation") - return out.env - -def rustBackend : Backend := fun _ input => IO.FS.withTempDir fun dir => do - let path := dir / "support.ixe" - let constants ← IO.ofExcept input.prepare - let status ← Ix.CompileM.rsCompileEnvBytesFFI constants path.toString false - unless status.ungrounded.isEmpty do - throw (IO.userError "artifact support: incomplete Rust compilation") - IO.ofExcept (Ixon.deEnv (← IO.FS.readBinFile path)) - def roleName (role : Role) : Lean.Name := Ix.RoleEvidence.toLeanName (Ix.Kernel.roleKey (md := .source) role) @@ -167,18 +153,20 @@ def isFamilyRecordInfo : Ixon.ConstantInfo → Bool /-- The first published name whose address holds no stored record, or whose record header disagrees with its entry kind (a block record for a non-`Muts` entry, or a non-block record for a `Muts` entry), if any. -/ -def unreadablePublished (ixe : Ixon.Env) : Option Ix.Name := +def unreadablePublished (ixe : Ixon.Env) + (namedArr : Array (Ix.Name × Ixon.Named) := ixe.named.toArray) : Option Ix.Name := (Ix.Cert.parMap (fun (n, named) => match ixe.consts.get? named.addr with | some lc => match named.constMeta.info with | .muts .. => if Ix.Cert.blockHeader lc.rawBytes then none else some n | _ => if Ix.Cert.blockHeader lc.rawBytes then some n else none - | none => some n) ixe.named.toArray).findSome? id + | none => some n) namedArr).findSome? id /-- The first non-`Muts` published name whose address does not resolve to a constant or constructor reference in the artifact's record store, if any. -/ -def unresolvedPublished (ixe : Ixon.Env) : Option Ix.Name := +def unresolvedPublished (ixe : Ixon.Env) + (namedArr : Array (Ix.Name × Ixon.Named) := ixe.named.toArray) : Option Ix.Name := (Ix.Cert.parMap (fun (n, named) => match named.constMeta.info with | .muts .. => none @@ -188,11 +176,12 @@ def unresolvedPublished (ixe : Ixon.Env) : Option Ix.Name := if lc.cache.isNone && standaloneHeader lc.rawBytes then none else if (Ix.Kernel.Reader.resolve (Ix.Cert.storedRecord ixe) named.addr).isSome then none else some n - | none => some n) ixe.named.toArray).findSome? id + | none => some n) namedArr).findSome? id /-- The first published definition, theorem or opaque whose address has no reducibility hint in `ixe.anonHints`, if any. -/ -def unhintedDefinition (ixe : Ixon.Env) : Option Ix.Name := +def unhintedDefinition (ixe : Ixon.Env) + (namedArr : Array (Ix.Name × Ixon.Named) := ixe.named.toArray) : Option Ix.Name := (Ix.Cert.parMap (fun (n, named) => match named.constMeta.info with | .muts .. => none @@ -202,23 +191,37 @@ def unhintedDefinition (ixe : Ixon.Env) : Option Ix.Name := match ixe.consts.get? named.addr with | some lc => definitionHeader lc.rawBytes | none => false - if isDef && !ixe.anonHints.contains named.addr then some n else none) ixe.named.toArray).findSome? id - -/-- Fast block owner lookup for `unownedRecursorWith`: a standalone constant is -its own block without scanning its expressions; projections and blocks use -`Ix.Cert.recordBlock`. -/ -def fastRecordBlock (ixe : Ixon.Env) (a : Address) : Address := - match ixe.consts.get? a with - | some lc => - if lc.cache.isNone && standaloneHeader lc.rawBytes then a - else Ix.Cert.recordBlock ixe a - | none => a + if isDef && !ixe.anonHints.contains named.addr then some n else none) namedArr).findSome? id + +/-- If `lc` is a projection record (`cPrj`, `rPrj`, `iPrj`, or `dPrj`), its +owning block address, read from the projection header without decoding any +constant body. -/ +def projectionBlock? (lc : Ixon.LazyConstant) : Option Address := + match lc.cache with + | some c => + match c.info with + | .dPrj p | .iPrj p | .rPrj p | .cPrj p => some p.block + | _ => none + | none => + let getProjBlock : Ixon.GetM (Option Address) := do + let tag ← Ixon.getTagN 4 + if tag.flag != Ixon.Constant.FLAG then return none + match tag.value with + | 4 => (·.block) <$> Ixon.getConstructorProj + | 5 => (·.block) <$> Ixon.getRecursorProj + | 6 => (·.block) <$> Ixon.getInductiveProj + | 7 => (·.block) <$> Ixon.getDefinitionProj + | _ => return none + match getProjBlock.run { idx := lc.off, bytes := lc.buf } with + | .ok block? _ => block? + | .error _ _ => none /-- `unownedRecursor` given the artifact's stored recursor records (`Ix.Cert.storedRecursorRecords ixe`), so callers that also run `recursorSlotErrorWith` compute the recursor record array once. -/ def unownedRecursorWith (ixe : Ixon.Env) - (recRecords : Array (Address × Ixon.Constant)) : + (recRecords : Array (Address × Ixon.Constant)) + (namedArr : Array (Ix.Name × Ixon.Named) := ixe.named.toArray) : Option (Address × Ix.Name × Option (Ix.Kernel.ConstRef Address)) := let idx := Ix.Kernel.Reader.buildIndex (Ix.Cert.storedRecord ixe) recRecords if idx.blocks.isEmpty then none @@ -230,13 +233,13 @@ def unownedRecursorWith (ixe : Ixon.Env) if recursorHeader lc.rawBytes && (Ix.Cert.publishedRecursor? ixe n).isSome then Ix.Cert.publishedRecords ixe n else [] - | none => []) ixe.named.toArray).foldl + | none => []) namedArr).foldl (fun acc as => as.foldl (fun s a => s.insert a) acc) {} (Ix.Cert.parMap (fun (x, named) => match named.constMeta.info with | .muts .. => none | _ => - let b := fastRecordBlock ixe named.addr + let b := Ix.Cert.recordBlockFast ixe named.addr match idx.blocks[b]? with | none => none | some shape => @@ -246,7 +249,7 @@ def unownedRecursorWith (ixe : Ixon.Env) (shape.recs.find? fun r => !publishedRecBlocks.contains r.block).map fun r => (b, x, some r) else some (b, x, none) - | none => some (b, x, none)) ixe.named.toArray).findSome? id + | none => some (b, x, none)) namedArr).findSome? id /-- An inductive block the recursor index (`Ix.Kernel.Reader.buildIndex`) attaches recursors to and a non-`Muts` name published in that block whose stored @@ -276,7 +279,8 @@ every bound role's stored record has its role's kind (`Ix.Cert.RoleRecordKinds`) and every stored literal-support role used by literals is published at its bound address. -/ def roleBindingError (ixe : Ixon.Env) - (sourceAddrs? : Option (Except String (Std.HashMap Ix.Kernel.Name Address)) := none) : + (sourceAddrs? : Option (Except String (Std.HashMap Ix.Kernel.Name Address)) := none) + (namedArr? : Option (Array (Ix.Name × Ixon.Named)) := none) : Option String := match sourceAddrs?.getD (sourceAddresses ixe) with | .error e => some e @@ -297,7 +301,7 @@ def roleBindingError (ixe : Ixon.Env) | some r => if table.addressOf? r == some named.addr then none else some s!"the published role {n.pretty} ({repr r}) is at {named.addr}, not its bound address" - | none => none) ixe.named.toArray).findSome? id + | none => none) (namedArr?.getD ixe.named.toArray)).findSome? id if let some msg := roleMismatch then some msg else let kindMismatch := table.bound.toList.findSome? fun (r, p) => @@ -440,23 +444,35 @@ def FinalizedCheckTasks.spawn (final : Ixon.Env) FinalizedCheckTasks := match added? with | none => + let finalConstsList := final.consts.toList + let finalConstsArr := finalConstsList.toArray + let finalNamedList := final.named.toList + let finalNamedArr := finalNamedList.toArray + let finalNamedDirectArr := final.named.toArray + let finalBlobsList := final.blobs.toList + let finalBlobsArr := finalBlobsList.toArray let recRecords := Task.spawn fun _ => Ix.Cert.storedRecursorRecords final - { unauthentic := Task.spawn fun _ => Ix.Cert.unauthenticPar final - referenceCycle := Task.spawn fun _ => Ix.Cert.referenceCyclePar final - nameHashCollision := Task.spawn fun _ => Ix.Cert.nameHashCollision { consts := {} } final - mutsMismatch := Task.spawn fun _ => Ix.Cert.mutsMismatchPar final - unresolvedPublished := Task.spawn fun _ => Ix.RoleBinding.Compile.unresolvedPublished final - projectionCollision := Task.spawn fun _ => Ix.Cert.projectionCollisionPar final - unreadablePublished := Task.spawn fun _ => Ix.RoleBinding.Compile.unreadablePublished final - blobRecordCollision := Task.spawn fun _ => Ix.Cert.blobRecordCollisionPar final + { unauthentic := Task.spawn fun _ => Ix.Cert.unauthenticPar final finalConstsArr finalBlobsArr + referenceCycle := Task.spawn fun _ => Ix.Cert.referenceCyclePar final finalConstsArr + nameHashCollision := Task.spawn fun _ => + Ix.Cert.nameHashCollisionFast { consts := {} } final + mutsMismatch := Task.spawn fun _ => Ix.Cert.mutsMismatchPar final finalNamedArr + unresolvedPublished := Task.spawn fun _ => + Ix.RoleBinding.Compile.unresolvedPublished final finalNamedDirectArr + projectionCollision := Task.spawn fun _ => Ix.Cert.projectionCollisionPar final finalConstsArr + unreadablePublished := Task.spawn fun _ => + Ix.RoleBinding.Compile.unreadablePublished final finalNamedDirectArr + blobRecordCollision := Task.spawn fun _ => Ix.Cert.blobRecordCollisionPar final finalBlobsArr unownedRecursor := Task.spawn fun _ => - Ix.RoleBinding.Compile.unownedRecursorWith final recRecords.get + Ix.RoleBinding.Compile.unownedRecursorWith final recRecords.get finalNamedDirectArr roleRecordKind := Task.spawn fun _ => Ix.RoleBinding.Compile.fastRoleRecordKind final literalRoleUnnamed := Task.spawn fun _ => Ix.RoleBinding.Compile.literalRoleUnnamed final recursorSlotError := Task.spawn fun _ => Ix.Cert.recursorSlotErrorWith final recRecords.get - unhintedDefinition := Task.spawn fun _ => Ix.RoleBinding.Compile.unhintedDefinition final - roleBindingError := Task.spawn fun _ => Ix.RoleBinding.Compile.roleBindingError final sourceAddrs? } + unhintedDefinition := Task.spawn fun _ => + Ix.RoleBinding.Compile.unhintedDefinition final finalNamedDirectArr + roleBindingError := Task.spawn fun _ => + Ix.RoleBinding.Compile.roleBindingError final sourceAddrs? (some finalNamedDirectArr) } | some (addedConsts, addedBlobs, _) => let hasNewRec := addedConsts.any fun (_, c) => match c.info with | .recr _ | .rPrj _ => true | _ => false @@ -499,73 +515,82 @@ def FinalizedCheckTasks.spawn (final : Ixon.Env) unhintedDefinition := Task.pure none roleBindingError := Task.spawn fun _ => Ix.RoleBinding.Compile.roleBindingError final sourceAddrs? } -@[noinline] def checkFinalizedOf (ts : FinalizedCheckTasks) : Except String Unit := do - if let some a := ts.unauthentic.get.head? then - throw s!"artifact finalization: the record or blob at {a} is not stored at the hash of its bytes" - if let some cycle := ts.referenceCycle.get then - throw s!"artifact finalization: the records {cycle.take 4} are on a reference cycle" - if let some (n, m) := ts.nameHashCollision.get then - throw s!"artifact finalization: the published names {n.pretty} and {m.pretty} share a stored hash" - if let some (entry, i, _) := ts.mutsMismatch.get then - throw s!"artifact finalization: class {i} of the Muts entry {entry.pretty} lists a name not \ - published at its member's projection" - if let some n := ts.unresolvedPublished.get then - throw s!"artifact finalization: the published name {n.pretty} does not resolve in the record store" - if let some (block, i) := ts.projectionCollision.get then - throw s!"artifact finalization: member {i} of the block at {block} has more than one stored \ - definition projection record" - if let some n := ts.unreadablePublished.get then - throw s!"artifact finalization: the published name {n.pretty} is at an address with no readable \ - record of its entry's kind" - if let some a := ts.blobRecordCollision.get then - throw s!"artifact finalization: the address {a} holds both a stored blob and a stored record" - if let some (b, x, r?) := ts.unownedRecursor.get then - match r? with - | none => - throw s!"artifact finalization: the published name {x.pretty} in the inductive block at {b} \ - is not an inductive, constructor or recursor" - | some r => - throw s!"artifact finalization: the recursor at {r.block}, indexed under the inductive block \ - at {b} for {x.pretty}, is not published" - if let some (role, address) := ts.roleRecordKind.get then - throw s!"artifact finalization: the record at {address}, the default role table's address of \ - {repr role}, is not of its role's kind" - if let some (role, address) := ts.literalRoleUnnamed.get then - throw s!"artifact finalization: the record at {address}, the default role table's address of \ - the literal-support role {repr role}, is not published under {(Ix.Cert.literalRoleIxName role).pretty}" - if let some e := ts.recursorSlotError.get then - throw s!"artifact finalization: {e.message}" - if let some n := ts.unhintedDefinition.get then - throw s!"artifact finalization: the published definition {n.pretty} has no reducibility hint \ - at its address" - if let some msg := ts.roleBindingError.get then - throw s!"artifact finalization: {msg}" +/-- The first failure of the finalized artifact's checks, in their listed order: +`preserved` (the original's publications and hints) first, then each task's. +Every task is waited for before a failure is reported: a check left running +when `main` returns could start subtasks that the runtime's shutdown no longer +runs (`roleBindingError` starts its `parMap` only after admitting the binding). -/ +@[noinline] def checkFinalizedOf (preserved : List (Option String)) (ts : FinalizedCheckTasks) : + Except String Unit := + let failures := preserved ++ [ + ts.unauthentic.get.head?.map fun a => + s!"artifact finalization: the record or blob at {a} is not stored at the hash of its bytes", + ts.referenceCycle.get.map fun cycle => + s!"artifact finalization: the records {cycle.take 4} are on a reference cycle", + ts.nameHashCollision.get.map fun (n, m) => + s!"artifact finalization: the published names {n.pretty} and {m.pretty} share a stored hash", + ts.mutsMismatch.get.map fun (entry, i, _) => + s!"artifact finalization: class {i} of the Muts entry {entry.pretty} lists a name not \ + published at its member's projection", + ts.unresolvedPublished.get.map fun n => + s!"artifact finalization: the published name {n.pretty} does not resolve in the record store", + ts.projectionCollision.get.map fun (block, i) => + s!"artifact finalization: member {i} of the block at {block} has more than one stored \ + definition projection record", + ts.unreadablePublished.get.map fun n => + s!"artifact finalization: the published name {n.pretty} is at an address with no readable \ + record of its entry's kind", + ts.blobRecordCollision.get.map fun a => + s!"artifact finalization: the address {a} holds both a stored blob and a stored record", + ts.unownedRecursor.get.map fun + | (b, x, none) => + s!"artifact finalization: the published name {x.pretty} in the inductive block at {b} \ + is not an inductive, constructor or recursor" + | (b, x, some r) => + s!"artifact finalization: the recursor at {r.block}, indexed under the inductive block \ + at {b} for {x.pretty}, is not published", + ts.roleRecordKind.get.map fun (role, address) => + s!"artifact finalization: the record at {address}, the default role table's address of \ + {repr role}, is not of its role's kind", + ts.literalRoleUnnamed.get.map fun (role, address) => + s!"artifact finalization: the record at {address}, the default role table's address of \ + the literal-support role {repr role}, is not published under {(Ix.Cert.literalRoleIxName role).pretty}", + ts.recursorSlotError.get.map fun e => s!"artifact finalization: {e.message}", + ts.unhintedDefinition.get.map fun n => + s!"artifact finalization: the published definition {n.pretty} has no reducibility hint \ + at its address", + ts.roleBindingError.get.map fun msg => s!"artifact finalization: {msg}"] + match failures.findSome? id with + | some e => .error e + | none => .ok () def checkFinalized (original final : Ixon.Env) (sourceAddrs? : Option (Except String (Std.HashMap Ix.Kernel.Name Address)) := none) (added? : Option (Array (Address × Ixon.Constant) × Array (Address × ByteArray) × Array (Address × Lean.ReducibilityHints)) := none) : - Except String Unit := do + Except String Unit := let tasks := FinalizedCheckTasks.spawn final sourceAddrs? added? - match added? with - | none => - let namedCheck := Task.spawn fun _ => - (Ix.Cert.parMap (fun (name, named) => - if (final.named.get? name).map (·.addr) == some named.addr then none else some name) - original.named.toArray).findSome? id - let hintsCheck := Task.spawn fun _ => - (Ix.Cert.parMap (fun (address, hint) => - if final.anonHints[address]? == some hint then none else some address) - original.anonHints.toArray).findSome? id - if let some name := namedCheck.get then - throw s!"artifact finalization: the published address of {name.pretty} changed" - if let some address := hintsCheck.get then - throw s!"artifact finalization: the reducibility hint at {address} changed" - | some (_, _, addedHints) => - for (address, _) in addedHints do - if let some hint := original.anonHints[address]? then - unless final.anonHints[address]? == some hint do - throw s!"artifact finalization: the reducibility hint at {address} changed" - checkFinalizedOf tasks + let preserved := match added? with + | none => + let namedCheck := Task.spawn fun _ => + (Ix.Cert.parMap (fun (name, named) => + if (final.named.get? name).map (·.addr) == some named.addr then none else some name) + original.named.toArray).findSome? id + let hintsCheck := Task.spawn fun _ => + (Ix.Cert.parMap (fun (address, hint) => + if final.anonHints[address]? == some hint then none else some address) + original.anonHints.toArray).findSome? id + [namedCheck.get.map fun name => + s!"artifact finalization: the published address of {name.pretty} changed", + hintsCheck.get.map fun address => + s!"artifact finalization: the reducibility hint at {address} changed"] + | some (_, _, addedHints) => + [addedHints.findSome? fun (address, _) => + match original.anonHints[address]? with + | some hint => + if final.anonHints[address]? == some hint then none + else some s!"artifact finalization: the reducibility hint at {address} changed" + | none => none] + checkFinalizedOf preserved tasks /-- Adding internal support cannot hide a failed original request, change an original address, or replace its source contract input. -/ @@ -648,29 +673,6 @@ emission has finished reading it. -/ (store : CheckedStore limits env) : IO Nat := IO.lazyPure fun _ => store.records.size -/-- If `lc` is a projection record (`cPrj`, `rPrj`, `iPrj`, or `dPrj`), its -owning block address, read from the projection header without decoding any -constant body. -/ -def projectionBlock? (lc : Ixon.LazyConstant) : Option Address := - match lc.cache with - | some c => - match c.info with - | .dPrj p | .iPrj p | .rPrj p | .cPrj p => some p.block - | _ => none - | none => - let getProjBlock : Ixon.GetM (Option Address) := do - let tag ← Ixon.getTagN 4 - if tag.flag != Ixon.Constant.FLAG then return none - match tag.value with - | 4 => (·.block) <$> Ixon.getConstructorProj - | 5 => (·.block) <$> Ixon.getRecursorProj - | 6 => (·.block) <$> Ixon.getInductiveProj - | 7 => (·.block) <$> Ixon.getDefinitionProj - | _ => return none - match getProjBlock.run { idx := lc.off, bytes := lc.buf } with - | .ok block? _ => block? - | .error _ _ => none - /-- All member and constructor projection addresses of a `.muts` block at `b`. -/ def blockProjectionAddrs (b : Address) (lc : Ixon.LazyConstant) : Array Address := if !Ix.Cert.blockHeader lc.rawBytes then #[] @@ -695,20 +697,15 @@ def blockProjectionAddrs (b : Address) (lc : Ixon.LazyConstant) : Array Address return out | _ => #[] -/-- Extract the compiled support closure of `selected` from `artifact` when -every constant in `selected.constants` is already published in `artifact`. -/ -def sliceSupport? (artifact : Ixon.Env) - (existing : Std.HashMap Ix.Kernel.Name Address) - (selected : Ix.Compile.CompileInput) : Option Ixon.Env := do - let mut todo : Array Address := #[] - let mut queued : Std.HashSet Address := {} - for (name, _) in selected.constants do - let key := Ix.Kernel.Name.ofLeanName name - let addr ← existing[key]? - guard (artifact.consts.contains addr) - unless queued.contains addr do - queued := queued.insert addr - todo := todo.push addr +/-- Expand `todo` (seeded with `queued`) through `artifact`'s reference graph +and `.muts` block projections, collecting the visited records, their blobs, and +their reducibility hints. -/ +def sliceSupportClosure (artifact : Ixon.Env) + (todo : Array Address) (queued : Std.HashSet Address) : + Option (Std.HashSet Address × Std.HashMap Address Ixon.LazyConstant × + Std.HashMap Address ByteArray × Std.HashMap Address Lean.ReducibilityHints) := do + let mut todo := todo + let mut queued := queued let mut visited : Std.HashSet Address := {} let mut consts : Std.HashMap Address Ixon.LazyConstant := {} let mut blobs : Std.HashMap Address ByteArray := {} @@ -741,6 +738,25 @@ def sliceSupport? (artifact : Ixon.Env) consts := consts.insert pAddr pLc if let some hint := artifact.anonHints[pAddr]? then anonHints := anonHints.insert pAddr hint + some (visited, consts, blobs, anonHints) + +/-- Extract the compiled support closure of `selected` from `artifact` when +every constant in `selected.constants` is already published in `artifact`. -/ +def sliceSupport? (artifact : Ixon.Env) + (existing : Std.HashMap Ix.Kernel.Name Address) + (selected : Ix.Compile.CompileInput) : Option Ixon.Env := do + let mut todo : Array Address := #[] + let mut queued : Std.HashSet Address := {} + for (name, _) in selected.constants do + let key := Ix.Kernel.Name.ofLeanName name + let addr ← existing[key]? + guard (artifact.consts.contains addr) + unless queued.contains addr do + queued := queued.insert addr + todo := todo.push addr + let (_, consts, blobs, anonHints) ← sliceSupportClosure artifact todo queued + let mut consts := consts + let mut anonHints := anonHints let mut named : Std.HashMap Ix.Name Ixon.Named := {} for (name, _) in selected.constants do let ixName := Ix.Name.fromLeanName name @@ -779,38 +795,9 @@ def sliceSupportRoles? (source : Lean.Environment) (artifact : Ixon.Env) unless queued.contains addr do queued := queued.insert addr todo := todo.push addr - let mut visited : Std.HashSet Address := {} - let mut consts : Std.HashMap Address Ixon.LazyConstant := {} - let mut blobs : Std.HashMap Address ByteArray := {} - let mut anonHints : Std.HashMap Address Lean.ReducibilityHints := {} - for _ in [:artifact.consts.size + 1] do - if h : todo.size > 0 then - let addr := todo[todo.size - 1] - todo := todo.pop - unless visited.contains addr do - let lc ← artifact.consts[addr]? - let (refs, own) ← Ix.Cert.RecordRefs.lazyRefs addr lc - visited := visited.insert addr - consts := consts.insert addr lc - if let some hint := artifact.anonHints[addr]? then - anonHints := anonHints.insert addr hint - let deps := if own == addr then refs else refs.push own - for dep in deps do - if artifact.consts.contains dep then - unless queued.contains dep do - queued := queued.insert dep - todo := todo.push dep - else if let some bytes := artifact.blobs[dep]? then - blobs := blobs.insert dep bytes - else - break - guard todo.isEmpty - for (b, lc) in consts.toList do - for pAddr in blockProjectionAddrs b lc do - if let some pLc := artifact.consts[pAddr]? then - consts := consts.insert pAddr pLc - if let some hint := artifact.anonHints[pAddr]? then - anonHints := anonHints.insert pAddr hint + let (visited, consts, blobs, anonHints) ← sliceSupportClosure artifact todo queued + let mut consts := consts + let mut anonHints := anonHints let mut named : Std.HashMap Ix.Name Ixon.Named := {} let mut foundRoles : Std.HashSet Role := {} for r in Role.all do @@ -838,6 +825,38 @@ def sliceSupportRoles? (source : Lean.Environment) (artifact : Ixon.Env) guard (closeRoles (roles ++ Role.all.filter foundRoles.contains) == roles) some { consts, blobs, named, anonHints } +/-- The certificate theorems of the roles `roles`, in the evidence order. -/ +def certTheoremsOf (roles : List Role) : List Lean.Name := + (Ix.RoleEvidence.allCertTheorems.filter (fun (role, _) => roles.contains role)).flatMap (·.2) + +/-- The certificate closure of `theorems`, read from `certSource` and compiled +by `backend` (empty for no theorems). -/ +def compileCertificates (backend : Backend) (certSource : IO Lean.Environment) + (theorems : List Lean.Name) : IO Ixon.Env := do + if theorems.isEmpty then pure ({} : Ixon.Env) else do + let certSource ← certSource + for name in theorems do + match certSource.find? name with + | some (.thmInfo _) => pure () + | _ => throw (IO.userError s!"artifact evidence: source theorem unavailable: {name}") + let constants := Ix.EnvScope.collectSelectedDeps certSource theorems + let selected ← IO.ofExcept ((Ix.Compile.compileInputFromEnv certSource constants).mapError toString) + compileRequired backend certSource selected + +/-- A certificate compile started ahead of `finalize`: its theorems, and the +task compiling their closure (`compileCertificates`). -/ +abbrev Prefetched := List Lean.Name × Task (Except IO.Error Ixon.Env) + +/-- Start the certificate compile of every role's theorems now, on its own +task, so that it runs beside the source compile. `finalize` uses it when its +roles need exactly these theorems (as a compile of a whole toolchain's +library does), and compiles its own otherwise. `backend` and `certSource` +must be the ones `finalize` is given. -/ +def prefetchCertificates (backend : Backend) (certSource : IO Lean.Environment) : + IO Prefetched := do + let theorems := certTheoremsOf Role.all + return (theorems, ← IO.asTask (compileCertificates backend certSource theorems)) + /-- Generate from real certificate source, compiled by the same raw backend. No committed/default role table or certificate store supplies missing inputs. @@ -846,86 +865,98 @@ toolchain's environment as `source`: `nativeCertSource` for a compile of ix's ow toolchain, and the imported environment itself for a compile of another toolchain's export (`Ix.LeanExport`). It is only run when a role with certificate evidence (a well-founded operation, or a definition specified by -equations that hold by a certificate) is published. -/ +equations that hold by a certificate) is published. A `prefetched` compile +(`prefetchCertificates`, from the same `backend` and `certSource`) stands in +for it when its theorems are the ones needed. + +`finalize` starts at most one certificate compile of its own, and only once its +roles are known, so it never starts one it does not use; it joins that compile +before it returns or throws. A compile left running when `main` returns would +wait for subtasks that the runtime's shutdown no longer runs. A prefetched +compile is its caller's to join. -/ def finalize (backend : Backend) (source : Lean.Environment) (certSource : IO Lean.Environment) (input : Ix.Compile.CompileInput) (artifact : Ixon.Env) (limits : Ix.Kernel.Admission.Limits) - (existing? : Option (Std.HashMap Ix.Kernel.Name Address) := none) : IO Ixon.Env := do + (existing? : Option (Std.HashMap Ix.Kernel.Name Address) := none) + (prefetched? : Option Prefetched := none) : IO Ixon.Env := do let existing ← match existing? with | some m => pure m | none => IO.ofExcept (sourceAddresses artifact) let initialRoles := publishedRolesFrom existing let initRoles := closeRoles (Ix.Kernel.preludeShape.flatten ++ initialRoles) - let spawnCertificatesTask (roles : List Role) : IO (Task (Except IO.Error Ixon.Env)) := do - let theorems := (Ix.RoleEvidence.allCertTheorems.filter (fun (role, _) => roles.contains role)).flatMap (·.2) - IO.asTask do - if theorems.isEmpty then pure ({} : Ixon.Env) else do - let certSource ← certSource - for name in theorems do - match certSource.find? name with - | some (.thmInfo _) => pure () - | _ => throw (IO.userError s!"artifact evidence: source theorem unavailable: {name}") - let constants := Ix.EnvScope.collectSelectedDeps certSource theorems - let selected ← IO.ofExcept ((Ix.Compile.compileInputFromEnv certSource constants).mapError toString) - compileRequired backend certSource selected - let initCertTask? ← if initialRoles == initRoles then - some <$> spawnCertificatesTask initRoles - else - pure none - let (required, covered, merged, mergedNames, support, certificatesTask) ← - match (if initialRoles == initRoles then sliceSupportRoles? source artifact existing initRoles else none), initCertTask? with - | some sliced, some certTask => - pure (initRoles, true, artifact, existing, sliced, certTask) - | _, _ => + -- The roles to support, and how: `initRoles`, sliced from the artifact when + -- it can be; otherwise `supportInput`'s closure, from its selected input. + let sliced? := if initialRoles == initRoles then + sliceSupportRoles? source artifact existing initRoles + else none + let (required, plan) ← match sliced? with + | some sliced => pure (initRoles, Sum.inl sliced) + | none => do let (required, selected) ← IO.ofExcept (supportInput source artifact (some initialRoles)) - let certificatesTask ← match initCertTask? with - | some certTask => - if required == initRoles then pure certTask else spawnCertificatesTask required - | none => spawnCertificatesTask required - let covered := selected.constants.all fun (name, _) => - existing.contains (Ix.Kernel.Name.ofLeanName name) - let (merged, mergedNames, compiledSupport?) ← if covered then - pure (artifact, existing, none) - else do - let support ← compileRequired backend source selected - let merged ← IO.ofExcept (mergeSupport limits artifact support) - let mergedNames ← IO.ofExcept (sourceAddresses merged) - IO.ofExcept (preserveRequestedWith input existing mergedNames) - pure (merged, mergedNames, some support) - let support ← match compiledSupport? with - | some support => pure support - | none => - match sliceSupport? merged existing selected with + pure (required, Sum.inr selected) + -- Their certificates: the prefetched compile when it compiles exactly these + -- theorems, otherwise our own, joined in the `finally` below. + let theorems := certTheoremsOf required + let (certificatesTask, own?) ← match prefetched? with + | some (ahead, task) => + if ahead == theorems then pure (task, none) else do + let task ← IO.asTask (compileCertificates backend certSource theorems) + pure (task, some task) + | none => do + let task ← IO.asTask (compileCertificates backend certSource theorems) + pure (task, some task) + try + let (covered, merged, mergedNames, support) ← match plan with + | .inl sliced => pure (true, artifact, existing, sliced) + | .inr selected => do + let covered := selected.constants.all fun (name, _) => + existing.contains (Ix.Kernel.Name.ofLeanName name) + let (merged, mergedNames, compiledSupport?) ← if covered then + pure (artifact, existing, none) + else do + let support ← compileRequired backend source selected + let merged ← IO.ofExcept (mergeSupport limits artifact support) + let mergedNames ← IO.ofExcept (sourceAddresses merged) + IO.ofExcept (preserveRequestedWith input existing mergedNames) + pure (merged, mergedNames, some support) + let support ← match compiledSupport? with | some support => pure support - | none => compileRequired backend source selected - pure (required, covered, merged, mergedNames, support, certificatesTask) - let supportStore ← IO.ofExcept (checkedStore limits support) - let actual := if covered then initialRoles else publishedRolesFrom mergedNames - unless actual == required do - throw (IO.userError "artifact support: compiled role publications differ from the selected closure") - let certificates ← IO.ofExcept certificatesTask.get - IO.ofExcept (checkCertificateContent merged certificates) - -- The support already contains every selected role and its source closure. - -- Evidence generation needs only that closure and the certificate closure; - -- emission below checks its binding against the complete merged artifact. - let generated ← IO.ofExcept (generate limits support certificates required Ix.Kernel.preludeShape - (store? := some supportStore)) - let emitted ← IO.ofExcept (generated.emit limits merged - (roleStore? := some (supportStore.records[·]?)) - (sourceAddrs? := some mergedNames)) - let _ ← IO.asTask (prio := .dedicated) (dropCheckedStore supportStore) - unless emitted.roleBinding.isSome do - throw (IO.userError "artifact finalization: missing emitted role binding") - -- `Generated.emit` does not modify `named`, so `mergedNames` remains current - -- and `preserveRequestedWith` (checked above when `!covered`) still holds. - let added? := if covered then - some (generated.support ++ generated.evidence, - generated.supportBlobs ++ generated.blobs, - generated.supportHints) - else none - IO.ofExcept (checkFinalized artifact emitted (some (.ok mergedNames)) added?) - return emitted + | none => + match sliceSupport? merged existing selected with + | some support => pure support + | none => compileRequired backend source selected + pure (covered, merged, mergedNames, support) + let supportStore ← IO.ofExcept (checkedStore limits support) + let actual := if covered then initialRoles else publishedRolesFrom mergedNames + unless actual == required do + throw (IO.userError "artifact support: compiled role publications differ from the selected closure") + let certificates ← IO.ofExcept certificatesTask.get + IO.ofExcept (checkCertificateContent merged certificates) + -- The support already contains every selected role and its source closure. + -- Evidence generation needs only that closure and the certificate closure; + -- emission below checks its binding against the complete merged artifact. + let generated ← IO.ofExcept (generate limits support certificates required Ix.Kernel.preludeShape + (store? := some supportStore)) + let emitted ← IO.ofExcept (generated.emit limits merged + (roleStore? := some (supportStore.records[·]?)) + (sourceAddrs? := some mergedNames)) + -- A dedicated task that only drops the store: it waits on no other task. + let _ ← IO.asTask (prio := .dedicated) (dropCheckedStore supportStore) + unless emitted.roleBinding.isSome do + throw (IO.userError "artifact finalization: missing emitted role binding") + -- `Generated.emit` does not modify `named`, so `mergedNames` remains current + -- and `preserveRequestedWith` (checked above when `!covered`) still holds. + let added? := if covered then + some ((generated.support ++ generated.evidence).filter (fun (a, _) => !artifact.consts.contains a), + (generated.supportBlobs ++ generated.blobs).filter (fun (a, _) => !artifact.blobs.contains a), + generated.supportHints.filter (fun (a, _) => !artifact.anonHints.contains a)) + else none + IO.ofExcept (checkFinalized artifact emitted (some (.ok mergedNames)) added?) + return emitted + finally + if let some task := own? then + let _ ← IO.wait task /-- Publish only after finalization and serialization succeed. The temporary file is beside the destination so rename is atomic on that filesystem. -/ diff --git a/Ix/RoleBinding/Generate.lean b/Ix/RoleBinding/Generate.lean index 4fced8b7a..4edfa0c3a 100644 --- a/Ix/RoleBinding/Generate.lean +++ b/Ix/RoleBinding/Generate.lean @@ -144,6 +144,8 @@ def generate (limits : Ix.Kernel.Admission.Limits) (env certificates : Ixon.Env) let store ← match store? with | some store => pure store.records | none => recordStore limits env + let certRecordsTask := Task.spawn fun _ => recordStore limits certificates + let certNamesTask := Task.spawn fun _ => sourceAddresses certificates let published ← publication env (store[·]?) required let boundOnly := RoleTable.ofEntries Ix.Kernel.specVersion published.bound.toList [] let preRecords ← preludeRecords boundOnly (store[·]?) groups @@ -153,8 +155,8 @@ def generate (limits : Ix.Kernel.Admission.Limits) (env certificates : Ixon.Env) unless (base.cx.memberKey ref).toOption == some (Ix.Kernel.roleKey role) do throw s!"role generation: {repr role} is not read under its role key" - let certRecords ← recordStore limits certificates - let certNames ← sourceAddresses certificates + let certRecords ← certRecordsTask.get + let certNames ← certNamesTask.get let certStore ← mergeRecords base.store certRecords for (role, address) in published.bound do if let some certAddress := certNames[role.leanName]? then @@ -203,44 +205,56 @@ def generate (limits : Ix.Kernel.Admission.Limits) (env certificates : Ixon.Env) let mut records : Array (Address × Ixon.Constant) := #[] let mut blobs : Array (Address × ByteArray) := #[] let mut closed : Std.HashMap Address (CExpr .anon) := {} - for (role, theorems) in selected do - -- The same closure operations as RoleEvidence.closedProofs, with a - -- fallible certificate-key lookup instead of its total key function. - let operation := Ix.Kernel.roleKey (md := .anon) role - let some operationValue := Ix.RoleEvidence.valueOf declarations operation - | throw s!"role generation: {repr role} is not a definition here" - let isOperation := Ix.RoleEvidence.certTheorems.any (·.1 == role) - let allowed := Ix.RoleEvidence.allowedFor (lookup[·]?) operation operationValue - -- Capture the computed set: a function-valued conditional causes closure - -- conversion to repeat the graph walk for every visited proof node. - let keep : CName .anon → Bool := allowed.contains - let proofs ← theorems.mapM fun name => do - let some key := keys[name]? | throw s!"role generation: missing certificate key for {name}" - let some value := Ix.RoleEvidence.theoremOf declarations key - | throw s!"role generation: {name} is not a theorem here" - let proof ← (if isOperation then Ix.RoleEvidence.closeProof values keep value - else .ok value).mapError - fun e => s!"role generation: {name}: {e}" - pure (key, proof) - let mut entries : Array (Ix.Kernel.Evidence Address) := #[] - for (key, proof) in proofs do - let some type := declarations.findSome? (fun - | .thmDecl cv _ => if cv.name == key then some cv.type else none - | _ => none) - | throw s!"role generation: no theorem statement for {key}" - let (record, extraBlobs) ← Evidence.theoremRecord (addrOfName[·]?) type proof - let bytes := Ixon.serConstant record - let decoded ← Ixon.Canonical.deConstant limits.maxRecordBytes limits.maxRecordUnivNodes bytes - unless Ixon.serConstant decoded == bytes do throw "role generation: noncanonical proof record" - let address := Address.admissionHash bytes + let roleTasks := selected.map fun (role, theorems) => + Task.spawn fun _ => (do + -- The same closure operations as RoleEvidence.closedProofs, with a + -- fallible certificate-key lookup instead of its total key function. + let operation := Ix.Kernel.roleKey (md := .anon) role + let some operationValue := Ix.RoleEvidence.valueOf declarations operation + | throw s!"role generation: {repr role} is not a definition here" + let isOperation := Ix.RoleEvidence.certTheorems.any (·.1 == role) + let allowed := Ix.RoleEvidence.allowedFor (lookup[·]?) operation operationValue + -- Capture the computed set: a function-valued conditional causes closure + -- conversion to repeat the graph walk for every visited proof node. + let keep : CName .anon → Bool := allowed.contains + let thmTasks := theorems.map fun name => + Task.spawn fun _ => (do + let some key := keys[name]? | throw s!"role generation: missing certificate key for {name}" + let some value := Ix.RoleEvidence.theoremOf declarations key + | throw s!"role generation: {name} is not a theorem here" + let proof ← (if isOperation then Ix.RoleEvidence.closeProof values keep value + else .ok value).mapError + fun e => s!"role generation: {name}: {e}" + let some type := declarations.findSome? (fun + | Kernel.Declaration.thmDecl cv _ => if cv.name == key then some cv.type else none + | _ => none) + | throw s!"role generation: no theorem statement for {key}" + let (record, extraBlobs) ← Evidence.theoremRecord (addrOfName[·]?) type proof + let bytes := Ixon.serConstant record + let decoded ← Ixon.Canonical.deConstant limits.maxRecordBytes limits.maxRecordUnivNodes bytes + unless Ixon.serConstant decoded == bytes do throw "role generation: noncanonical proof record" + let address := Address.admissionHash bytes + pure (address, record, proof, extraBlobs) : + Except String (Address × Ixon.Constant × CExpr .anon × Array (Address × ByteArray))) + let mut entries : Array (Ix.Kernel.Evidence Address) := #[] + let mut thmOut : Array (Address × Ixon.Constant × CExpr .anon × Array (Address × ByteArray)) := #[] + for t in thmTasks do + let res ← t.get + entries := entries.push (.proof res.1) + thmOut := thmOut.push res + pure (role, Ix.RoleEvidence.evidenceEntries role entries, thmOut) : + Except String (Role × Array (Ix.Kernel.Evidence Address) × + Array (Address × Ixon.Constant × CExpr .anon × Array (Address × ByteArray)))) + for rt in roleTasks do + let (role, roleEv, thmOut) ← rt.get + for (address, record, proof, extraBlobs) in thmOut do records := records.push (address, record) closed := closed.insert address proof - entries := entries.push (.proof address) for (a, b) in extraBlobs do if let some (_, old) := blobs.find? (·.1 == a) then unless old == b do throw s!"role generation: conflicting generated blob at {a}" else blobs := blobs.push (a, b) - evidence := evidence.push (role, Ix.RoleEvidence.evidenceEntries role entries) + evidence := evidence.push (role, roleEv) let table := RoleTable.ofEntries Ix.Kernel.specVersion published.bound.toList evidence.toList table.check unless table.wireWF do throw "role generation: table exceeds the canonical wire domain" @@ -321,11 +335,12 @@ def Generated.emit (generated : Generated) (limits : Ix.Kernel.Admission.Limits) for (address, record) in generated.support ++ generated.evidence do let bytes := Ixon.serConstant record unless Address.admissionHash bytes == address do throw s!"role emission: bad record address {address}" - let decoded ← Ixon.Canonical.deConstant limits.maxRecordBytes limits.maxRecordUnivNodes bytes - unless Ixon.serConstant decoded == bytes do throw s!"role emission: noncanonical record at {address}" if let some old := out.consts[address]? then unless old.rawBytes == bytes do throw s!"role emission: conflicting record at {address}" - else out := { out with consts := out.consts.insert address { buf := bytes, off := 0, len := bytes.size, cache := some record } } + else + let decoded ← Ixon.Canonical.deConstant limits.maxRecordBytes limits.maxRecordUnivNodes bytes + unless Ixon.serConstant decoded == bytes do throw s!"role emission: noncanonical record at {address}" + out := { out with consts := out.consts.insert address { buf := bytes, off := 0, len := bytes.size, cache := some record } } for (address, bytes) in generated.supportBlobs ++ generated.blobs do unless Address.admissionHash bytes == address do throw s!"role emission: bad blob address {address}" if let some old := out.blobs[address]? then diff --git a/Ix/RoleBinding/UserAxioms.lean b/Ix/RoleBinding/UserAxioms.lean new file mode 100644 index 000000000..5d8713b8e --- /dev/null +++ b/Ix/RoleBinding/UserAxioms.lean @@ -0,0 +1,67 @@ +module + +public import Ix.RoleBinding.ArtifactTiered + +/-! # User axioms in the host drivers (uncertified) + +`ix check-lean --allow-user-axioms` and `kernel-check-ixe --allow-user-axioms` +admit safe axioms beyond Lean's standard three: an `.axiomDecl` whose key is an +input constant's (`Ix.Kernel.Key.isCustomAxiomEligible`). Without the flag both +are strict, and such an axiom declines. + +The kernel decides what is admitted and lists it. Its entries under +`AxiomPolicy.allowUser` (`Admission.checkBytesUnder`, +`checkBytesTieredUsingUnder`, run by `Artifact.checkConditional` and +`checkTieredConditional`) refuse a role table that leaves an axiom role +unbound (`Admission.admitAxiomPolicy`) and return the user axioms of the +declarations they ran over. What is left here is the host's part: + +* `requireAxiomRoles`: the kernel's role-table check, `admitAxiomPolicy + .allowUser`, on the artifact's binding, with the host's message, before a + driver checks any declaration. The record-by-record drivers + (`Ix/KernelCheck/{Step,Pool}.lean`, through `annotDeclStepWithUserAxioms`) + never reach admission, so every driver runs it first + (`CheckLeanCmd.prepareInput`, `CheckIxe.run`). +* `addresses`, `ofDecls`: the input keys of the kernel's list, for the + drivers' `CONDITIONAL` listings; names come from the artifact's metadata. + +Nothing here is trusted; the kernel's own checks are the boundary. -/ + +@[expose] public section + +namespace Ix.RoleBinding.UserAxioms + +/-- Refuse user axioms unless the artifact's binding passes the kernel's +check (`Admission.admitAxiomPolicy .allowUser`: `sorryAx`, `propext` and +`Classical.choice` bound), the binding admitted as the entries admit it; an +artifact with no binding is refused too. A binding that leaves an axiom role +out keys Lean's own constant as an input constant, which the user-axiom checks +would admit. -/ +def requireAxiomRoles (binding? : Option Ixon.RoleBinding) : Except String Unit := do + let names := ", ".intercalate (Ix.Kernel.Admission.userAxiomRoles.map (toString ·.leanName)) + let some binding := binding? + | throw s!"user axioms require a role binding that binds {names}; the artifact carries none" + let table ← (Ix.Kernel.Admission.admitBinding Address.admissionHash ⟨binding.address, binding.bytes⟩).mapError + fun e => toString (Ix.Kernel.Admission.Error.binding e) + match Ix.Kernel.Admission.admitAxiomPolicy .allowUser table with + | .ok () => pure () + | .error (.axiomRoleUnbound role) => + throw s!"user axioms require a role binding that binds {names}; this binding leaves \ + {role.leanName} unbound" + | .error e => throw (toString e) + +/-- The input keys of the kernel's list of user axioms, in order. -/ +def addresses (axioms : List (Ix.Kernel.ConstantVal .anon)) : Array Address := + axioms.toArray.filterMap fun cv => + match cv.name with + | .id address => some address + | _ => none + +/-- The addresses of the user axioms among `decls` (`userAxiomsOfDecls`), in +order. -/ +def ofDecls (decls : List (Ix.Kernel.Declaration .anon)) : Array Address := + addresses (Ix.Kernel.userAxiomsOfDecls decls) + +end Ix.RoleBinding.UserAxioms + +end diff --git a/Ix/Runtime/Thread.lean b/Ix/Runtime/Thread.lean index a574da2da..5d9fe56ae 100644 --- a/Ix/Runtime/Thread.lean +++ b/Ix/Runtime/Thread.lean @@ -9,7 +9,7 @@ namespace Ix.Runtime /-- Set the stack size, in bytes, for subsequently spawned Lean runtime threads (`.dedicated` task threads and not-yet-spawned pool workers) — the knob behind `lean --tstack`. Already-running threads keep their - stacks. The Rust shim (`crates/ffi/src/kernel.rs`) adapts the bare + stacks. The Rust shim (`crates/ffi/src/lib.rs`) adapts the bare `void(size_t)` runtime call to the IO ABI. -/ @[extern "rs_lean_set_thread_stack_size"] opaque setLeanThreadStackSize : USize → BaseIO Unit diff --git a/Ix/Shard/Check.lean b/Ix/Shard/Check.lean index 772907c52..61ce9648e 100644 --- a/Ix/Shard/Check.lean +++ b/Ix/Shard/Check.lean @@ -278,7 +278,7 @@ def runShardBatchNative (manifestPath ixePath : String) (jobs? : Option Nat) IO UInt32 := do -- The row's peak-rss needs the process-tree RSS sampler running -- (`peakTreeRssBytes` reports 0 otherwise); started before the env - -- load so the peak covers the whole run, like `check-rs`. + -- load so the peak covers the whole run, like `check-lean`. if json?.isSome then TracingTexray.startSampler match (← loadEnvAndShards manifestPath ixePath) with | .error e => IO.eprintln e; return 1 @@ -350,7 +350,7 @@ def runShardBatchNative (manifestPath ixePath : String) (jobs? : Option Nat) | .ok rs => if waveNum == 0 then -- The bench row's measured window: the planned partition's - -- batch call, matching `check-rs`. Split waves are audit + -- batch call, matching `check-lean`. Split waves are audit -- extras outside the benchmarked engine window. elapsedMs := (← IO.monoMsNow) - start totalConsts := ownedPer.foldl (· + ·.size) 0 @@ -533,7 +533,7 @@ def runShardBatchNative (manifestPath ixePath : String) (jobs? : Option Nat) -- `ix shard refine` still wrote its manifest (failed leaves unchanged): -- exit 2 tells the operator to read the report. Under `--json` a kernel -- rejection is the benchmark's `rejected` exit (the row is already - -- written), same contract as `check-rs`. + -- written), same contract as `check-lean`. if opts.emitOnFailure then return 2 return if json?.isSome then Ix.Benchmark.Results.exitRejected else 1 diff --git a/Ix/Shard/Manifest.lean b/Ix/Shard/Manifest.lean index a30ecc5ac..f1a8f729d 100644 --- a/Ix/Shard/Manifest.lean +++ b/Ix/Shard/Manifest.lean @@ -108,7 +108,7 @@ structure IxesManifestView where /-- The deepest aggregation tree a manifest may carry, in internal nodes on a path. A planner tree comes from recursive bisection, so its depth stays far below this; the bound keeps every recursive walk of a parsed tree shallow. The Rust -reader (`AGG_TREE_MAX_DEPTH`, `crates/kernel/src/shard.rs`) uses the same. -/ +reader (`AGG_TREE_MAX_DEPTH`, `crates/ixon/src/shard.rs`) uses the same. -/ def ixesTreeMaxDepth : Nat := 4096 /-- A preorder aggregation tree with at most `depth` internal nodes on any diff --git a/Ix/ShardMap.lean b/Ix/ShardMap.lean deleted file mode 100644 index e3e5dd5f7..000000000 --- a/Ix/ShardMap.lean +++ /dev/null @@ -1,453 +0,0 @@ -/- - ShardMap: A concurrent hashmap with sharded locks for parallel access. - - This is a Lean equivalent of Rust's DashMap, providing O(1) concurrent - read/write access with reduced lock contention through sharding. - - ## Thread Safety - - - All single-key operations (`insert`, `get?`, `remove`, `modify`, etc.) are atomic - and thread-safe. - - Aggregate operations (`size`, `toArray`, `fold`, `toList`, `toHashMap`) iterate - over shards non-atomically - they may observe an inconsistent view during - concurrent modifications. - - `clear` is not atomic - shards are cleared sequentially. - - ## Performance Characteristics - - - Uses power-of-2 sharding with bit masking for O(1) shard selection. - - Each shard uses SharedMutex for reader-writer semantics (concurrent reads, - exclusive writes). - - Cache line padding prevents false sharing between adjacent shards. - - Hash mixing distributes keys more evenly across shards. - - Default 256 shards provides good parallelism for most workloads. - - `insertMany` parallelizes updates across shards for 10-20x speedup on bulk inserts. - - ## Usage - - let map ← ShardMap.new (shardBits := 6) -- 64 shards - map.insert key value - let val? ← map.get? key - - -- Bulk insert for better performance - map.insertMany #[(k1, v1), (k2, v2), ...] - - -- Atomic read-modify-write - let result? ← map.modifyGet key fun v => (computeResult v, updateValue v) --/ -module - -public import Std.Data.HashMap -public import Std.Sync.SharedMutex - -public section - -namespace Ix - -/-- Wrapper to prevent false sharing between shards. - Adds padding to push each shard to separate cache lines (typically 64 bytes). - The SharedMutex pointer is ~8 bytes, padding adds 56 bytes for 64-byte alignment. -/ -structure PaddedShard (α : Type) (β : Type) [BEq α] [Hashable α] where - shard : Std.SharedMutex (Std.HashMap α β) - -- Padding fields to reach ~64 bytes total (cache line size) - -- Each UInt64 is 8 bytes - private _pad0 : UInt64 := 0 - private _pad1 : UInt64 := 0 - private _pad2 : UInt64 := 0 - private _pad3 : UInt64 := 0 - private _pad4 : UInt64 := 0 - private _pad5 : UInt64 := 0 - private _pad6 : UInt64 := 0 - -/-- A concurrent hashmap with sharded locks using reader-writer semantics. - - Each shard is protected by a SharedMutex, allowing concurrent reads - while writes are exclusive. Uses 2^shardBits shards with bit-masking for - fast shard selection. - - Shards are padded to separate cache lines to prevent false sharing. -/ -structure ShardMap (α : Type) (β : Type) [BEq α] [Hashable α] where - shards : Array (PaddedShard α β) - shardMask : USize -- 2^k - 1 for fast bitwise AND - h_pos : shards.size > 0 - -namespace ShardMap - -variable {α β : Type} [BEq α] [Hashable α] - -/-- Build an array of n padded shards with given capacity per shard. -/ -private def mkShardArrayWithCapacity (n : Nat) (capacity : Nat) - : BaseIO { arr : Array (PaddedShard α β) // arr.size = n } := do - let rec go (remaining : Nat) (acc : Array (PaddedShard α β)) (hacc : acc.size + remaining = n) : - BaseIO { arr : Array (PaddedShard α β) // arr.size = n } := do - -- `dependent := true`: the proof arms need `hacc` refined by the - -- scrutinee, which `do match` stopped doing by default in v4.32. - match (dependent := true) remaining with - | 0 => pure ⟨acc, by omega⟩ - | r + 1 => - let mutex ← Std.SharedMutex.new (Std.HashMap.emptyWithCapacity capacity) - let paddedShard : PaddedShard α β := { shard := mutex } - go r (acc.push paddedShard) (by simp [Array.size_push]; omega) - go n #[] (by simp) - -/-- Build an array of n empty padded shards. -/ -private def mkShardArray (n : Nat) : BaseIO { arr : Array (PaddedShard α β) // arr.size = n } := - mkShardArrayWithCapacity n 0 - -/-- Create a new ShardMap with 2^shardBits shards. - Default is 8 bits = 256 shards, which provides good parallelism for most workloads. -/ -def new (shardBits : Nat := 8) : BaseIO (ShardMap α β) := do - let numShards := 2 ^ shardBits - let shardMask : USize := (numShards - 1).toUSize - let ⟨shards, hsize⟩ ← mkShardArray numShards - have h : shards.size > 0 := by simp [hsize]; exact Nat.one_le_two_pow - pure ⟨shards, shardMask, h⟩ - -/-- Compute the shard index for a given key using fast bit masking. - Mixes high and low bits of the hash for better distribution across shards, - which helps when hash functions produce correlated low bits. -/ -@[inline] -def shardIdx (m : ShardMap α β) (key : α) : Nat := - let h : UInt64 := hash key - -- Mix high and low bits for better distribution - let mixed := h ^^^ (h >>> 32) - (mixed.toUSize &&& m.shardMask).toNat - -/-- Get the shard for a key. -/ -@[inline] -def getShard (m : ShardMap α β) (key : α) : Std.SharedMutex (Std.HashMap α β) := - let idx := m.shardIdx key - (m.shards[idx % m.shards.size]'(Nat.mod_lt _ m.h_pos)).shard - -/-- Contention threshold in nanoseconds (100ms) -/ -def contentionThresholdNs : Nat := 100_000_000 - -/-- Insert a key-value pair into the map. - If the key already exists, its value is replaced. -/ -@[inline] -def insert (m : ShardMap α β) (key : α) (val : β) : BaseIO Unit := do - m.getShard key |>.atomically fun ref => do - let map ← ST.Ref.get ref - ST.Ref.set ref (map.insert key val) - -/-- Insert with contention detection. -/ -def insertTimed (m : ShardMap α β) (key : α) (val : β) - (label : String := "ShardMap") : IO Unit := do - let start ← IO.monoNanosNow - let shardIdx := m.shardIdx key - m.getShard key |>.atomically fun ref => do - let map ← ST.Ref.get ref - ST.Ref.set ref (map.insert key val) - let elapsed := (← IO.monoNanosNow) - start - if elapsed > contentionThresholdNs then - IO.eprintln s!"[CONTENTION] {label} shard {shardIdx}: insert took {elapsed / 1_000_000}ms" - -/-- Look up a key in the map, returning `none` if not found. - Uses shared read lock for concurrent access. -/ -@[inline] -def get? (m : ShardMap α β) (key : α) : BaseIO (Option β) := do - m.getShard key |>.atomicallyRead fun map => pure (map.get? key) - -/-- Look up a key in the map, returning a default value if not found. -/ -@[inline] -def getD (m : ShardMap α β) (key : α) (default : β) : BaseIO β := do - match ← m.get? key with - | some v => pure v - | none => pure default - -/-- Get a value or insert a new one if the key doesn't exist. - The `mkVal` function is only called if the key is not present. - Returns the value (either existing or newly inserted). - Uses double-checked locking: tries read lock first, only takes write lock on miss. -/ -def getOrInsert (m : ShardMap α β) (key : α) (mkVal : Unit → BaseIO β) : BaseIO β := do - let shard := m.getShard key - -- Fast path: try read lock first - let cached? ← shard.atomicallyRead fun map => pure (map.get? key) - match cached? with - | some v => pure v - | none => - -- Slow path: take write lock and double-check - shard.atomically fun ref => do - let map ← ST.Ref.get ref - match map.get? key with - | some v => pure v -- Another thread inserted while we waited - | none => do - let v ← mkVal () - ST.Ref.set ref (map.insert key v) - pure v - -/-- Get a value or insert a new one if the key doesn't exist (IO version). - The `mkVal` function is only called if the key is not present. - Returns the value (either existing or newly inserted). - Uses double-checked locking: tries read lock first, only takes write lock on miss. -/ -def getOrInsertIO (m : ShardMap α β) (key : α) (mkVal : Unit → IO β) : IO β := do - let shard := m.getShard key - -- Fast path: try read lock first - let cached? ← shard.atomicallyRead fun map => pure (map.get? key) - match cached? with - | some v => pure v - | none => - -- Slow path: take write lock and double-check - shard.atomically fun ref => do - let map ← ST.Ref.get ref - match map.get? key with - | some v => pure v -- Another thread inserted while we waited - | none => do - let v ← mkVal () - ST.Ref.set ref (map.insert key v) - pure v - -/-- Get a value or insert a new one (pure version). - The default value is evaluated only if needed. - Uses double-checked locking: tries read lock first, only takes write lock on miss. -/ -def getOrInsertLazy (m : ShardMap α β) (key : α) (mkVal : Unit → β) : BaseIO β := do - let shard := m.getShard key - -- Fast path: try read lock first - let cached? ← shard.atomicallyRead fun map => pure (map.get? key) - match cached? with - | some v => pure v - | none => - -- Slow path: take write lock and double-check - shard.atomically fun ref => do - let map ← ST.Ref.get ref - match map.get? key with - | some v => pure v - | none => - let v := mkVal () - ST.Ref.set ref (map.insert key v) - pure v - -/-- Get a value or insert a new one, with contention detection. - Prints a warning if the operation takes longer than the threshold. - Uses double-checked locking: tries read lock first, only takes write lock on miss. -/ -def getOrInsertLazyTimed (m : ShardMap α β) (key : α) (mkVal : Unit → β) - (label : String := "ShardMap") : IO β := do - let start ← IO.monoNanosNow - let shardIdx := m.shardIdx key - let shard := m.getShard key - -- Fast path: try read lock first - let cached? ← shard.atomicallyRead fun map => pure (map.get? key) - let result ← match cached? with - | some v => pure v - | none => - -- Slow path: take write lock and double-check - shard.atomically fun ref => do - let map ← ST.Ref.get ref - match map.get? key with - | some v => pure v - | none => - let v := mkVal () - ST.Ref.set ref (map.insert key v) - pure v - let elapsed := (← IO.monoNanosNow) - start - if elapsed > contentionThresholdNs then - IO.eprintln s!"[CONTENTION] {label} shard {shardIdx}: getOrInsertLazy took {elapsed / 1_000_000}ms" - pure result - -/-- Check if a key exists in the map. - Uses shared read lock for concurrent access. -/ -@[inline] -def contains (m : ShardMap α β) (key : α) : BaseIO Bool := do - m.getShard key |>.atomicallyRead fun map => pure (map.contains key) - -/-- Remove a key from the map, returning the removed value if it existed. -/ -def remove (m : ShardMap α β) (key : α) : BaseIO (Option β) := do - m.getShard key |>.atomically fun ref => do - let map ← ST.Ref.get ref - match map.get? key with - | some v => - ST.Ref.set ref (map.erase key) - pure (some v) - | none => pure none - -/-- Modify the value associated with a key, if it exists. - Returns `true` if the key was found and modified. -/ -def modify (m : ShardMap α β) (key : α) (f : β → β) : BaseIO Bool := do - m.getShard key |>.atomically fun ref => do - let map ← ST.Ref.get ref - match map.get? key with - | some v => - ST.Ref.set ref (map.insert key (f v)) - pure true - | none => pure false - -/-- Modify a value and return a result, atomically. - The function `f` receives the current value and returns both a result and - a new value. Returns `none` if the key doesn't exist. - - This is useful for patterns that need to read and modify in a single - lock acquisition, e.g., incrementing a counter and returning the old value. -/ -def modifyGet (m : ShardMap α β) (key : α) (f : β → (γ × β)) : BaseIO (Option γ) := do - m.getShard key |>.atomically fun ref => do - let map ← ST.Ref.get ref - match map.get? key with - | some v => - let (result, newV) := f v - ST.Ref.set ref (map.insert key newV) - pure (some result) - | none => pure none - -/-! ## Bulk Operations -/ - -/-- Insert multiple key-value pairs, grouping by shard for efficiency. - This reduces lock acquisition overhead compared to inserting items one by one. - - Items are grouped by their target shard, then each shard is updated in parallel - using IO.asTask for better multi-core utilization. -/ -def insertMany (m : ShardMap α β) (items : Array (α × β)) : IO Unit := do - -- Group items by shard index - let numShards := m.shards.size - let mut shardGroups : Array (Array (α × β)) := Array.replicate numShards #[] - for (k, v) in items do - let idx := m.shardIdx k - shardGroups := shardGroups.modify idx (·.push (k, v)) - -- Insert each group in parallel across shards - let finalGroups := shardGroups - let mut tasks : Array (Task (Except IO.Error Unit)) := #[] - for i in [:numShards] do - if let some group := finalGroups[i]? then - if group.size > 0 then - let paddedShard := m.shards[i % numShards]'(Nat.mod_lt _ m.h_pos) - let task ← IO.asTask do - paddedShard.shard.atomically fun ref => do - let map ← ST.Ref.get ref - ST.Ref.set ref (group.foldl (fun m (k, v) => m.insert k v) map) - tasks := tasks.push task - -- Wait for all tasks to complete - for task in tasks do - let _ ← IO.ofExcept task.get - -/-- Get the approximate size of the map. - Note: This is not atomic and may not be exact during concurrent modifications. -/ -def size (m : ShardMap α β) : BaseIO Nat := do - let mut total := 0 - for paddedShard in m.shards do - let sz ← paddedShard.shard.atomicallyRead fun map => pure map.size - total := total + sz - pure total - -/-- Convert the map to an array of key-value pairs. O(n) time complexity. - Single-pass collection to avoid stale size between passes. - Not atomic; may be inconsistent during concurrent modifications. -/ -def toArray (m : ShardMap α β) : BaseIO (Array (α × β)) := do - let mut result : Array (α × β) := #[] - for paddedShard in m.shards do - let shardMap ← paddedShard.shard.atomicallyRead fun map => pure map - for pair in shardMap do - result := result.push pair - pure result - -/-- Fold over all key-value pairs in the map. - Not atomic; may be inconsistent during concurrent modifications. -/ -def fold (m : ShardMap α β) (init : γ) (f : γ -> α -> β -> γ) : BaseIO γ := do - let mut acc := init - for paddedShard in m.shards do - let shardMap ← paddedShard.shard.atomicallyRead fun map => pure map - for (k, v) in shardMap do - acc := f acc k v - pure acc - -/-- Convert the map to a list of key-value pairs. O(n) time complexity. - Note: This is not atomic and may not be consistent during concurrent modifications. -/ -def toList (m : ShardMap α β) : BaseIO (List (α × β)) := do - let arr ← m.toArray - pure arr.toList - -/-- Convert to a regular HashMap. O(n) time complexity using bulk construction. - Note: This is not atomic and may not be consistent during concurrent modifications. -/ -def toHashMap (m : ShardMap α β) : BaseIO (Std.HashMap α β) := do - let pairs ← m.toArray - pure (Std.HashMap.emptyWithCapacity pairs.size |>.insertMany pairs) - -/-- Clear all entries from the map. - Note: This is not atomic - shards are cleared sequentially. -/ -def clear (m : ShardMap α β) : BaseIO Unit := do - for paddedShard in m.shards do - paddedShard.shard.atomically fun ref => ST.Ref.set ref {} - -/-! ## Non-blocking operations -/ - -/-- Get a value with try-lock fast path. - Attempts non-blocking read first, falls back to blocking if shard is contended. - Useful for read-heavy workloads where you want to avoid blocking on contended shards. -/ -@[inline] -def get?Fast (m : ShardMap α β) (key : α) : BaseIO (Option β) := do - -- Try non-blocking first - match ← m.getShard key |>.tryAtomicallyRead fun map => pure (map.get? key) with - | some result => pure result - | none => m.get? key -- Fall back to blocking - -/-- Check if a key exists with try-lock fast path. - Attempts non-blocking read first, falls back to blocking if shard is contended. - Useful for read-heavy workloads where you want to avoid blocking on contended shards. -/ -@[inline] -def containsFast (m : ShardMap α β) (key : α) : BaseIO Bool := do - -- Try non-blocking first - match ← m.getShard key |>.tryAtomicallyRead fun map => pure (map.contains key) with - | some result => pure result - | none => m.contains key -- Fall back to blocking - -/-- Try to get a value without blocking. - Returns `some (some v)` if key exists, `some none` if key doesn't exist, - or `none` if the shard is currently locked. -/ -def tryGet? (m : ShardMap α β) (key : α) : BaseIO (Option (Option β)) := do - m.getShard key |>.tryAtomicallyRead fun map => pure (map.get? key) - -/-- Try to insert without blocking. - Returns `true` if insert succeeded, `false` if shard was locked. -/ -def tryInsert (m : ShardMap α β) (key : α) (val : β) : BaseIO Bool := do - let shard := m.getShard key - match ← shard.tryAtomically fun ref => do - let map ← ST.Ref.get ref - ST.Ref.set ref (map.insert key val) - with - | some () => pure true - | none => pure false - -/-- Try to get or insert without blocking. - Returns `some v` with the value (existing or new), or `none` if shard was locked. -/ -def tryGetOrInsertLazy (m : ShardMap α β) (key : α) (mkVal : Unit → β) : BaseIO (Option β) := do - let shard := m.getShard key - -- First try read lock - match ← shard.tryAtomicallyRead fun map => pure (map.get? key) with - | some (some v) => pure (some v) -- Found it - | some none => - -- Not found, try write lock - match ← shard.tryAtomically fun ref => do - let map ← ST.Ref.get ref - match map.get? key with - | some v => pure v - | none => - let v := mkVal () - ST.Ref.set ref (map.insert key v) - pure v - with - | some v => pure (some v) - | none => pure none -- Write lock failed - | none => pure none -- Read lock failed - -/-! ## Capacity hints -/ - -/-- Create a new ShardMap with pre-sized shards. - - **Note:** Benchmarks show that pre-allocation generally hurts performance due to - HashMap allocation overhead. In most cases, `new` with natural growth performs - better than `newWithCapacity`. This function is retained for cases where you've - profiled and determined pre-allocation helps your specific workload. - - Capacity is capped at 64 entries per shard to limit allocation overhead. -/ -def newWithCapacity (shardBits : Nat := 8) (capacityPerShard : Nat := 64) - : BaseIO (ShardMap α β) := do - let numShards := 2 ^ shardBits - let shardMask : USize := (numShards - 1).toUSize - -- Cap capacity to avoid allocation overhead; let HashMap grow naturally beyond this - let effectiveCapacity := min capacityPerShard 64 - let ⟨shards, hsize⟩ ← mkShardArrayWithCapacity numShards effectiveCapacity - have h : shards.size > 0 := by simp [hsize]; exact Nat.one_le_two_pow - pure ⟨shards, shardMask, h⟩ - -end ShardMap - -end Ix - -end diff --git a/Ix/Sharing/Exact.lean b/Ix/Sharing/Exact.lean index 19d9023fb..3f2de4e38 100644 --- a/Ix/Sharing/Exact.lean +++ b/Ix/Sharing/Exact.lean @@ -3,8 +3,7 @@ its reference searches (`docs/sharing-minimum.md`). The compiler shares every block with the tiered canonical construction - `canonicalSharingTiered` (rule `sharing-k16-greedy-v1`; Rust - `ixon::sharing_exact::canonical_sharing_tiered`): phase 1 uses the fixed + `canonicalSharingTiered` (rule `sharing-k16-greedy-v1`): phase 1 uses the fixed K16 initial-component families at widths 1, 2 and 3, phase 2 allocates the table slots, phase 3 re-materializes every part under the real TagN widths, and the candidate with the fewest bytes wins. @@ -155,7 +154,7 @@ def normalizeConstantSharing (c : Constant) (limits : Limits := {}) : let info ← withRoots c.info r.roots return { c with info, sharing := r.sharing } -/-- Outcome of `checkExactMinimum` (Rust `ExactMinimumCheck`). -/ +/-- Outcome of `checkExactMinimum`. -/ inductive ExactMinimumCheck where /-- The Constant is its exact-minimum encoding. -/ | minimum @@ -165,8 +164,7 @@ inductive ExactMinimumCheck where /-- Whether `c` is byte-identical to its exact-minimum sharing: a check against the test oracle `normalizeConstantSharing`, not against the -compiler's canonical construction (`canonicalSharingTiered`). Rust -`check_exact_minimum`. -/ +compiler's canonical construction (`canonicalSharingTiered`). -/ def checkExactMinimum (c : Constant) (limits : Limits := {}) : Except SharingError ExactMinimumCheck := do let n ← normalizeConstantSharing c limits diff --git a/Ix/Sharing/Exact/Basic.lean b/Ix/Sharing/Exact/Basic.lean index a64675057..bed970d39 100644 --- a/Ix/Sharing/Exact/Basic.lean +++ b/Ix/Sharing/Exact/Basic.lean @@ -339,7 +339,7 @@ inductive Resource where deriving BEq, Repr, Inhabited /-- The name of a resource in error messages and in limit overrides -(`Limits.withOverrides`, `ix compile --sharing-limits`). -/ +(`Limits.withOverrides`). -/ def Resource.key : Resource → String | .exprVisits => "expr_visits" | .depth => "depth" @@ -400,7 +400,7 @@ structure Limits where /-- Width states inserted into the search frontier. -/ maxStates : Nat := 1 <<< 40 /-- Cells (components × capacity) of the uniform optimizer's count-bracket - knapsack table; matches the Rust `max_knapsack_cells`. -/ + knapsack table. -/ maxKnapsackCells : Nat := 1 <<< 28 /-- Transitions (state, appended term) examined. -/ maxTransitions : Nat := 1 <<< 40 @@ -429,17 +429,9 @@ structure Limits where uniformSubsetSearch : Bool := false deriving Repr, Inhabited -/-- The value `max` in a limit override: `2^64 − 1`, the largest value Rust's -`u64` limits hold. -/ +/-- The value `max` in a limit override: `2^64 − 1`, the largest `UInt64`. -/ def limitMax : Nat := 2 ^ 64 - 1 -/-- Limit keys of the Rust implementation (`ExactSharingLimits`) that the Lean -limits do not have. `Limits.withOverrides` accepts and ignores them, so one -override string serves both compilers; `states`, `transitions` and -`output_bytes` name a limit in both. -/ -def rustOnlyLimitKeys : List String := - ["input_nodes", "distinct_nodes", "height", "candidates", "layer_states", "work"] - /-- Set the limit named `key` (`Resource.key`, oracle limits excluded). -/ def Limits.set? (l : Limits) (key : String) (v : Nat) : Option Limits := match key with @@ -462,8 +454,7 @@ def Limits.setAll (l : Limits) (v : Nat) : Limits := maxMaterialize := v, maxMaterializeWork := v, maxKnapsackCells := v } /-- A limit value: ASCII decimal digits, `2^k` (`k ≤ 63`) or `max`. No sign or -`_` separator (`String.toNat?` alone would accept `1_000`), so the grammar is -Rust's `parse_limit_value`. -/ +`_` separator (`String.toNat?` alone would accept `1_000`). -/ def parseLimitValue (raw : String) : Except String Nat := let s := raw.trimAscii.toString if s.any (· == '_') then .error s!"sharing limit value {s}: expected digits, 2^k or max" @@ -479,11 +470,8 @@ def parseLimitValue (raw : String) : Except String Nat := /-- Apply a limit override: comma-separated items, each `key=value` (`Resource.key` names; values as `parseLimitValue`) or `unbounded` (every -production limit at `max`), applied left to right. Rust-only keys -(`rustOnlyLimitKeys`) are accepted and ignored; any other key is an error. -This is the format of `ix compile --sharing-limits` and of the -`IX_SHARING_LIMITS` environment variable, which Rust's -`compiler_sharing_limits` parses the same way. -/ +production limit at `max`), applied left to right. Any other key is an +error. -/ def Limits.withOverrides (l : Limits) (spec : String) : Except String Limits := do let mut l := l for raw in spec.splitOn "," do @@ -499,10 +487,8 @@ def Limits.withOverrides (l : Limits) (spec : String) : Except String Limits := match l.set? key v with | some l' => l := l' | none => - unless rustOnlyLimitKeys.contains key do - throw s!"unknown sharing limit {key} (expected expr_visits, depth, nodes, states, \ - transitions, cost_evals, output_bytes, materialize, materialize_work, knapsack_cells, or a Rust key: \ - {", ".intercalate rustOnlyLimitKeys})" + throw s!"unknown sharing limit {key} (expected expr_visits, depth, nodes, states, \ + transitions, cost_evals, output_bytes, materialize, materialize_work or knapsack_cells)" | _ => throw s!"sharing limit item {item}: expected key=value or unbounded" return l diff --git a/Ix/Sharing/Exact/KnapsackFast.lean b/Ix/Sharing/Exact/KnapsackFast.lean index 645a144eb..f1c278f33 100644 --- a/Ix/Sharing/Exact/KnapsackFast.lean +++ b/Ix/Sharing/Exact/KnapsackFast.lean @@ -13,7 +13,7 @@ symmetric difference: the smaller of that of `S_a, S_b` (the least first difference of the rank-adjacent sets between them) and that of `X, Y`; the set without it precedes. The chosen cell's set is rebuilt from - the entries taken. (Rust: `Knapsack`, `sharing_exact/uniform.rs`.) + the entries taken. `uniformKnapsack_eq_fast : @uniformKnapsack = @uniformKnapsackFast` (`@[csimp]`) holds for every input; the fast knapsack runs when every diff --git a/Ix/Sharing/Exact/Tiered.lean b/Ix/Sharing/Exact/Tiered.lean index 97b5dbcb8..b5cf2b6da 100644 --- a/Ix/Sharing/Exact/Tiered.lean +++ b/Ix/Sharing/Exact/Tiered.lean @@ -127,8 +127,7 @@ def tagNWidth (i : Nat) : Nat := Ixon.tagNByteWidth 4 i /-! ## Layouts -/ /-- A Share width layout. TagN is the only Share code, so this is the only -layout; the type is kept so that layouts stay explicit in the API (and match -the Rust `ShareLayout::TagN`). -/ +layout; the type is kept so that layouts stay explicit in the API. -/ inductive ShareLayout where /-- The TagN Share code (`tagNWidth`). -/ | tagN diff --git a/IxC/Ixon/Codec.lean b/IxC/Ixon/Codec.lean index d2e5edf98..522ce5cb9 100644 --- a/IxC/Ixon/Codec.lean +++ b/IxC/Ixon/Codec.lean @@ -58,30 +58,6 @@ def ser [Serialize α] (a : α) : ByteArray := runPut (Serialize.put a) def de [Serialize α] (bytes : ByteArray) : Except String α := runGet Serialize.get bytes -/-! ## Serialization Error Type -/ - -/-- Serialization/deserialization error. Variant order matches Rust SerializeError (tags 0–6). -/ -inductive SerializeError where - | unexpectedEof (expected : String) - | invalidTag (tag : UInt8) (context : String) - | invalidFlag (flag : UInt8) (context : String) - | invalidVariant (variant : UInt64) (context : String) - | invalidBool (value : UInt8) - | addressError - | invalidShareIndex (idx : UInt64) (max : Nat) - deriving Repr, BEq - -def SerializeError.toString : SerializeError → String - | .unexpectedEof expected => s!"unexpected EOF, expected {expected}" - | .invalidTag tag context => s!"invalid tag 0x{String.ofList <| tag.toNat.toDigits 16} in {context}" - | .invalidFlag flag context => s!"invalid flag {flag} in {context}" - | .invalidVariant variant context => s!"invalid variant {variant} in {context}" - | .invalidBool value => s!"invalid bool value {value}" - | .addressError => "address parsing error" - | .invalidShareIndex idx max => s!"invalid Share index {idx}, max is {max}" - -instance : ToString SerializeError := ⟨SerializeError.toString⟩ - /-! ## Primitive Serialization -/ def putU8 (x : UInt8) : PutM Unit := diff --git a/IxC/Kernel/Admission.lean b/IxC/Kernel/Admission.lean index 2490f78ad..2b3be18a4 100644 --- a/IxC/Kernel/Admission.lean +++ b/IxC/Kernel/Admission.lean @@ -2,7 +2,7 @@ module public import IxC.Kernel.Admission.Bytes public import IxC.Kernel.Ixon.Prelude -public import IxC.Kernel.Cached.Installed +public import IxC.Kernel.Cached.Policy public import IxC.Kernel.Egress.Projection /-! # Admission from ordered Ixon record bytes @@ -106,6 +106,10 @@ inductive Error where prelude record does not read to what the prelude read -/ | preludeCopy | kernel (error : Ix.Kernel.CheckError .anon) (position : Nat) + /-- the check admits user axioms (`AxiomPolicy.allowUser`), and the role table + leaves one of `userAxiomRoles` unbound: that role's own axiom would read under + an input key and be admitted as a user axiom (`admitAxiomPolicy`) -/ + | axiomRoleUnbound (role : Ix.Kernel.Role) instance : ToString Error where toString @@ -120,6 +124,8 @@ instance : ToString Error where | .read p e => s!"read at {p}: {e}" | .preludeCopy => "prelude copy: the stream's reading leaves other entries than the prelude's at a prelude key" | .kernel e p => s!"kernel at {p}: {e}" + | .axiomRoleUnbound r => s!"user axioms: the role table leaves {repr r} unbound; a check that \ + admits user axioms requires sorryAx, propext and Classical.choice bound" /-- The byte stage's failures, unchanged. -/ def Error.ofBytes : ByteError → Error @@ -295,6 +301,7 @@ def Error.outcome : Error → Outcome | .read _ (.declined _) => .declined | .preludeCopy => .declined | .kernel _ _ => .declined + | .axiomRoleUnbound _ => .declined /-- The classification of a byte-stage failure, as `Error.outcome` makes it: a batch limit declines; a key used twice in one table, a key that is not the @@ -306,15 +313,94 @@ def ByteError.outcome : ByteError → Outcome | .mismatch .. => .rejected | .decode .. => .rejected -/-- Extract the custom user axioms from a prepared declaration array. -/ +/-! ## Checks that admit user axioms + +The fold runs under an axiom policy (`Ix.Kernel.Cached.AxiomPolicy`, +`IxC/Kernel/Cached/Policy.lean`). `strict` is the certified entry's: at it, +`checkConstantsUnder` and `checkBytesUnder` are `checkConstantsWith` and +`checkBytesWith` (`checkConstantsUnder_strict`, `checkBytesUnder_strict`, +`Ix.Kernel.Admission.UserAxioms`). `allowUser` also admits user axioms, and an +accept is then conditional on them: the entries return the environment +together with the user axioms of the declarations the fold ran over +(`preparedUserAxioms`; the environment cannot list them, since an opaque is +installed as `.axiomInfo` too). With none, the accept is the strict entry's +(`checkBytesUnder_strict_of_nil`). Under `allowUser` the role table must bind +the axiom roles (`admitAxiomPolicy`), and the fold declines a user axiom that +restates an axiom role's specification (`Cached.checkUserAxiomDeclC`). -/ + +/-- The user axioms of a prepared declaration array (`userAxiomsOfDecls`): its +`.axiomDecl`s under eligible keys, in order. -/ def preparedUserAxioms (decls : Array (Ix.Kernel.Declaration .anon)) : List (Ix.Kernel.ConstantVal .anon) := Ix.Kernel.userAxiomsOfDecls decls.toList -/-- Extract the custom user axioms stored in an admitted environment. -/ -def admittedUserAxioms (env : Ix.Kernel.Env .anon) : - List (Ix.Kernel.ConstantVal .anon) := - env.customAxiomInfos +/-- **The roles a check that admits user axioms requires bound**: `sorryAx`, +`propext` and `Classical.choice`. Left unbound, the role's own axiom (Lean's +`sorryAx`, say) reads under an input key, which is eligible, and would be +admitted as a user axiom. -/ +def userAxiomRoles : List Ix.Kernel.Role := [.sorryAx, .propext, .choice] + +/-- **The role table's admission under an axiom policy**: nothing more under +`strict`; under `allowUser`, every role of `userAxiomRoles` bound, else the +first unbound one is refused (`Error.axiomRoleUnbound`). -/ +def admitAxiomPolicy (policy : Ix.Kernel.Cached.AxiomPolicy) (t : Ix.Kernel.RoleTable) : + Except Error Unit := + match policy with + | .strict => pure () + | .allowUser => + match userAxiomRoles.find? (fun r => (t.addressOf? r).isNone) with + | some r => throw (.axiomRoleUnbound r) + | none => pure () + +/-- **The fold over decoded records under an axiom policy**: the policy's +admission of the table (`admitAxiomPolicy`), the reader, the prelude, and the +fold under the policy (`Cached.checkDeclsUnder`). The result is the environment +and the user axioms it is conditional on (`preparedUserAxioms`, empty under +`strict`). -/ +def checkConstantsUnder (policy : Ix.Kernel.Cached.AxiomPolicy) (hash : ByteArray → Address) + (t : Ix.Kernel.RoleTable) (pre : Prelude .anon) + (constants : List (Address × Ixon.Constant)) (blobs : Ix.Kernel.Ingress.Blobs) + (hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint := fun _ => none) : + Except Error (Ix.Kernel.Env .anon × List (Ix.Kernel.ConstantVal .anon)) := do + admitAxiomPolicy policy t + let (ev, decls) ← readStream hash t pre constants blobs hint + let ds := Ix.Kernel.Frontend.preparePrelude pre.ix decls + let env ← (Ix.Kernel.Cached.checkDeclsUnder .verified policy ev ds).mapError + fun (e, i) => .kernel e i + pure (env, preparedUserAxioms ds) + +/-- **The bytes entry under an axiom policy**: `checkBytesWith`'s byte +admission (`checkBytesWith_eq`: the batch limits, key uniqueness, +authentication, the role table's admission, canonical decoding), then +`checkConstantsUnder`. -/ +def checkBytesUnder (policy : Ix.Kernel.Cached.AxiomPolicy) (hash : ByteArray → Address) + (binding : Binding) (pre : Prelude .anon) + (limits : Limits) (records : Records) (blobs : Ix.Kernel.Ingress.Blobs) + (hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint := fun _ => none) : + Except Error (Ix.Kernel.Env .anon × List (Ix.Kernel.ConstantVal .anon)) := do + (preflight limits records blobs).mapError Error.ofBytes + (uniqueKeys records blobs).mapError Error.ofBytes + (authenticate hash records blobs).mapError Error.ofBytes + let t ← (admitBinding hash binding).mapError .binding + let constants ← (decodeRecords limits records).mapError Error.ofBytes + checkConstantsUnder policy hash t pre constants blobs hint + +/-- `checkBytesWith` with user axioms admitted: the environment of +`checkBytesUnder .allowUser`, which also lists the user axioms the accept is +conditional on. -/ +def checkBytesWithUserAxioms (hash : ByteArray → Address) (binding : Binding) (pre : Prelude .anon) + (limits : Limits) (records : Records) (blobs : Ix.Kernel.Ingress.Blobs) + (hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint := fun _ => none) : + Except Error (Ix.Kernel.Env .anon) := + Prod.fst <$> checkBytesUnder .allowUser hash binding pre limits records blobs hint + +/-- `checkConstantsWith` with user axioms admitted: the environment of +`checkConstantsUnder .allowUser`. -/ +def checkConstantsWithUserAxioms (hash : ByteArray → Address) (t : Ix.Kernel.RoleTable) (pre : Prelude .anon) + (constants : List (Address × Ixon.Constant)) (blobs : Ix.Kernel.Ingress.Blobs) + (hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint := fun _ => none) : + Except Error (Ix.Kernel.Env .anon) := + Prod.fst <$> checkConstantsUnder .allowUser hash t pre constants blobs hint end Ix.Kernel.Admission diff --git a/IxC/Kernel/Admission/Tier.lean b/IxC/Kernel/Admission/Tier.lean index 3cef6659c..340cfecf9 100644 --- a/IxC/Kernel/Admission/Tier.lean +++ b/IxC/Kernel/Admission/Tier.lean @@ -241,6 +241,63 @@ def checkBytesTiered (hash : ByteArray → Address) (binding : Binding) (limits Except TierError (Ix.Kernel.Env .anon × List (TierConst .anon)) := checkBytesTieredUsing preludeFor hash binding limits records blobs hint +/-- **The tiered entry over decoded records under an axiom policy**: +`checkConstantsTiered` with the logical part checked by `checkConstantsUnder` +at the policy; the user axioms the logical accept is conditional on come with +the result. The tier itself has no axioms of its own (an unsafe axiom is a +tier record, not a user axiom). -/ +def checkConstantsTieredUnder (policy : Ix.Kernel.Cached.AxiomPolicy) (hash : ByteArray → Address) + (t : Ix.Kernel.RoleTable) (pre : Prelude .anon) + (constants : List (Address × Ixon.Constant)) (blobs : Ix.Kernel.Ingress.Blobs) + (hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint := fun _ => none) : + Except TierError ((Ix.Kernel.Env .anon × List (TierConst .anon)) × + List (Ix.Kernel.ConstantVal .anon)) := do + let (logical, tier) := partitionTier constants + if let some (a, r) := fenceOf logical tier then throw (.fence a r) + let (env, axs) ← (checkConstantsUnder policy hash t pre logical blobs hint).mapError .entry + let tds ← (readTier (streamContext hash t pre constants blobs hint) tier).mapError + fun (i, e) => .read i e + match Ix.Kernel.Tier.checkTierC (Ix.Kernel.mkFEnv env) (tds.toList.map (·.2)) with + | .ok (_, ts) => pure ((env, ts), axs) + | .error (e, k) => throw (.tier e ((tds[k]?.map (·.1)).getD k)) + +/-- **The tiered Ixon entry under an axiom policy**: `checkBytesTieredUsing`'s +byte stage, the role table's admission, the prelude and canonical decoding, +then `checkConstantsTieredUnder`. -/ +def checkBytesTieredUsingUnder (policy : Ix.Kernel.Cached.AxiomPolicy) + (prelude : Ix.Kernel.RoleTable → Except String (Prelude .anon)) + (hash : ByteArray → Address) (binding : Binding) (limits : Limits) + (records : Records) (blobs : Ix.Kernel.Ingress.Blobs) + (hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint := fun _ => none) : + Except TierError ((Ix.Kernel.Env .anon × List (TierConst .anon)) × + List (Ix.Kernel.ConstantVal .anon)) := do + (preflight limits records blobs).mapError (.entry ∘ Error.ofBytes) + (uniqueKeys records blobs).mapError (.entry ∘ Error.ofBytes) + (authenticate hash records blobs).mapError (.entry ∘ Error.ofBytes) + let t ← (admitBinding hash binding).mapError (.entry ∘ .binding) + let pre ← (prelude t).mapError (.entry ∘ .prelude) + let constants ← (decodeRecords limits records).mapError (.entry ∘ Error.ofBytes) + checkConstantsTieredUnder policy hash t pre constants blobs hint + +/-- `checkConstantsTiered` with user axioms admitted on the logical part: the +result of `checkConstantsTieredUnder .allowUser`, without its list of user +axioms. -/ +def checkConstantsTieredWithUserAxioms (hash : ByteArray → Address) (t : Ix.Kernel.RoleTable) (pre : Prelude .anon) + (constants : List (Address × Ixon.Constant)) (blobs : Ix.Kernel.Ingress.Blobs) + (hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint := fun _ => none) : + Except TierError (Ix.Kernel.Env .anon × List (TierConst .anon)) := + Prod.fst <$> checkConstantsTieredUnder .allowUser hash t pre constants blobs hint + +/-- `checkBytesTieredUsing` with user axioms admitted on the logical part: the +result of `checkBytesTieredUsingUnder .allowUser`, without its list of user +axioms. -/ +def checkBytesTieredUsingWithUserAxioms (prelude : Ix.Kernel.RoleTable → Except String (Prelude .anon)) + (hash : ByteArray → Address) (binding : Binding) (limits : Limits) + (records : Records) (blobs : Ix.Kernel.Ingress.Blobs) + (hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint := fun _ => none) : + Except TierError (Ix.Kernel.Env .anon × List (TierConst .anon)) := + Prod.fst <$> checkBytesTieredUsingUnder .allowUser prelude hash binding limits records blobs hint + end Ix.Kernel.Admission end diff --git a/IxC/Kernel/Admission/UserAxioms.lean b/IxC/Kernel/Admission/UserAxioms.lean new file mode 100644 index 000000000..9446cf6d0 --- /dev/null +++ b/IxC/Kernel/Admission/UserAxioms.lean @@ -0,0 +1,329 @@ +module + +public import IxC.Kernel.Admission.Tier + +/-! # The theorems of the entries that admit user axioms + +The entries under an axiom policy (`checkConstantsUnder`, `checkBytesUnder`, +`checkConstantsTieredUnder`, `checkBytesTieredUsingUnder`; +`IxC/Kernel/Admission.lean`, `IxC/Kernel/Admission/Tier.lean`) return the +environment together with the user axioms the accept is conditional on. The +user-axiom entries (`checkBytesWithUserAxioms`, `checkConstantsWithUserAxioms`, +`checkConstantsTieredWithUserAxioms`, `checkBytesTieredUsingWithUserAxioms`) +are their environments at `allowUser`. + +* **At `strict` they are the certified entries** (`checkConstantsUnder_strict`, + `checkBytesUnder_strict`, `checkConstantsTieredUnder_strict`, + `checkBytesTieredUsingUnder_strict`), so every theorem of + `Ix.Kernel.Admission.Theorems` and `.TierTheorems` is a theorem about them. +* **T1: an accept with no user axioms is the certified entry's accept** + (`checkConstantsUnder_strict_of_nil`, `checkBytesUnder_strict_of_nil`, + `checkConstantsTieredUnder_strict_of_nil`, + `checkBytesTieredUsingUnder_strict_of_nil`), at every policy: over records + that declare no user axiom the fold is `checkDecls` + (`Cached.checkDeclsUnder_eq_checkDecls`). In particular such an accept has a + model (`checkBytesUnder_has_model_of_nil`, `UserAxioms/Model.lean`), and a + receipt of the user-axiom entry whose list is empty is a receipt of the + certified entry (`Ix.RoleBinding.Artifact.ReceiptConditional.toReceipt`, + `TierReceiptConditional.toTierReceipt`). +* An accept of a user-axiom entry is exactly an accept under `allowUser` with + some list (`checkBytesWithUserAxioms_ok_iff`, ...). + +This file is a `module`, so that a host's compiled code can build the certified +entry's receipt from an accept that lists no user axiom (a `module` imports +only modules). It imports no theorem module: the theorem that needs one, the +model of such an accept, is `UserAxioms/Model.lean`. + +Every statement holds at every content hash `hash`. An accept that lists user +axioms is conditional on them; no model is claimed for it here. -/ + +public section + +namespace Ix.Kernel.Admission + +open Ix.Kernel (ConstRef) +open Ix.Kernel.Reader +open Ix.Kernel.Tier (TierConst) + +/-- The environment of an accept that also returns a list. -/ +theorem map_fst_ok_iff {ε α β : Type} {x : Except ε (α × β)} {a : α} : + Prod.fst <$> x = .ok a ↔ ∃ b, x = .ok (a, b) := by + cases x with + | error e => exact ⟨fun h => (by cases h), fun ⟨_, h⟩ => (by cases h)⟩ + | ok p => + obtain ⟨a', b⟩ := p + constructor + · intro h + have : a' = a := Except.ok.inj h + subst this + exact ⟨b, rfl⟩ + · rintro ⟨b', h⟩ + have : (a', b) = (a, b') := Except.ok.inj h + cases this + rfl + +/-! ## Over decoded records -/ + +/-- **At `strict`, the fold over decoded records is `checkConstantsWith`.** -/ +theorem checkConstantsUnder_strict (hash : ByteArray → Address) (t : Ix.Kernel.RoleTable) + (pre : Prelude .anon) (constants : List (Address × Ixon.Constant)) + (blobs : Ix.Kernel.Ingress.Blobs) (hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint) : + Prod.fst <$> checkConstantsUnder .strict hash t pre constants blobs hint = + checkConstantsWith hash t pre constants blobs hint := by + unfold checkConstantsUnder checkConstantsWith + cases hr : readStream hash t pre constants blobs hint with + | error e => rfl + | ok p => + obtain ⟨ev, decls⟩ := p + simp only [admitAxiomPolicy, bind, Except.bind, Ix.Kernel.Cached.checkDeclsUnder_strict] + cases Ix.Kernel.Cached.checkDecls .verified ev (Ix.Kernel.Frontend.preparePrelude pre.ix decls) <;> rfl + +/-- **T1 over decoded records**: an accept under any policy that lists no user +axiom is `checkConstantsWith`'s accept, with the same environment. -/ +theorem checkConstantsUnder_strict_of_nil {policy : Ix.Kernel.Cached.AxiomPolicy} + {hash : ByteArray → Address} {t : Ix.Kernel.RoleTable} {pre : Prelude .anon} + {constants : List (Address × Ixon.Constant)} {blobs : Ix.Kernel.Ingress.Blobs} + {hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint} {env : Ix.Kernel.Env .anon} + (h : checkConstantsUnder policy hash t pre constants blobs hint = .ok (env, [])) : + checkConstantsWith hash t pre constants blobs hint = .ok env := by + unfold checkConstantsUnder at h + unfold checkConstantsWith + cases hp : admitAxiomPolicy policy t with + | error e => simp [hp, bind, Except.bind] at h + | ok u => + cases hr : readStream hash t pre constants blobs hint with + | error e => simp [hp, hr, bind, Except.bind] at h + | ok p => + obtain ⟨ev, decls⟩ := p + simp only [hp, hr, bind, Except.bind] at h + simp only [bind, Except.bind] + cases hc : Ix.Kernel.Cached.checkDeclsUnder .verified policy ev + (Ix.Kernel.Frontend.preparePrelude pre.ix decls) with + | error e => simp [hc, Except.mapError] at h + | ok env' => + simp only [hc, Except.mapError, pure, Except.pure, Except.ok.injEq, Prod.mk.injEq] at h + obtain ⟨rfl, hnil⟩ := h + rw [← Ix.Kernel.Cached.checkDeclsUnder_eq_checkDecls .verified policy ev _ hnil, hc] + rfl + +/-- An accept of `checkConstantsWithUserAxioms` is an accept under `allowUser`. -/ +theorem checkConstantsWithUserAxioms_ok_iff {hash : ByteArray → Address} {t : Ix.Kernel.RoleTable} + {pre : Prelude .anon} {constants : List (Address × Ixon.Constant)} + {blobs : Ix.Kernel.Ingress.Blobs} {hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint} + {env : Ix.Kernel.Env .anon} : + checkConstantsWithUserAxioms hash t pre constants blobs hint = .ok env ↔ + ∃ axs, checkConstantsUnder .allowUser hash t pre constants blobs hint = .ok (env, axs) := + map_fst_ok_iff + +/-! ## Over record bytes -/ + +/-- **At `strict`, the bytes entry is `checkBytesWith`.** -/ +theorem checkBytesUnder_strict (hash : ByteArray → Address) (binding : Binding) (pre : Prelude .anon) + (limits : Limits) (records : Records) (blobs : Ix.Kernel.Ingress.Blobs) + (hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint) : + Prod.fst <$> checkBytesUnder .strict hash binding pre limits records blobs hint = + checkBytesWith hash binding pre limits records blobs hint := by + unfold checkBytesUnder checkBytesWith prepareWith + cases preflight limits records blobs with + | error e => rfl + | ok _ => + cases uniqueKeys records blobs with + | error e => rfl + | ok _ => + cases authenticate hash records blobs with + | error e => rfl + | ok _ => + cases admitBinding hash binding with + | error e => rfl + | ok t => + cases decodeRecords limits records with + | error e => rfl + | ok constants => + refine (checkConstantsUnder_strict hash t pre constants blobs hint).trans ?_ + unfold checkConstantsWith + show Except.bind (readStream hash t pre constants blobs hint) _ = + Except.bind (Except.bind (readStream hash t pre constants blobs hint) _) _ + cases readStream hash t pre constants blobs hint <;> rfl + +/-- **T1 over record bytes**: an accept of the bytes entry under any policy that +lists no user axiom is `checkBytesWith`'s accept, with the same environment. -/ +theorem checkBytesUnder_strict_of_nil {policy : Ix.Kernel.Cached.AxiomPolicy} + {hash : ByteArray → Address} {binding : Binding} {pre : Prelude .anon} + {limits : Limits} {records : Records} {blobs : Ix.Kernel.Ingress.Blobs} + {hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint} {env : Ix.Kernel.Env .anon} + (h : checkBytesUnder policy hash binding pre limits records blobs hint = .ok (env, [])) : + checkBytesWith hash binding pre limits records blobs hint = .ok env := by + unfold checkBytesWith prepareWith + unfold checkBytesUnder at h + cases h1 : preflight limits records blobs with + | error e => simp [h1, bind, Except.bind, Except.mapError] at h + | ok _ => + cases h2 : uniqueKeys records blobs with + | error e => simp [h1, h2, bind, Except.bind, Except.mapError] at h + | ok _ => + cases h3 : authenticate hash records blobs with + | error e => simp [h1, h2, h3, bind, Except.bind, Except.mapError] at h + | ok _ => + cases h4 : admitBinding hash binding with + | error e => simp [h1, h2, h3, h4, bind, Except.bind, Except.mapError] at h + | ok t => + cases h5 : decodeRecords limits records with + | error e => simp [h1, h2, h3, h4, h5, bind, Except.bind, Except.mapError] at h + | ok constants => + simp only [h1, h2, h3, h4, h5, bind, Except.bind, Except.mapError] at h + refine Eq.trans ?_ (checkConstantsUnder_strict_of_nil h) + unfold checkConstantsWith + show Except.bind (Except.bind (readStream hash t pre constants blobs hint) _) _ = + Except.bind (readStream hash t pre constants blobs hint) _ + cases readStream hash t pre constants blobs hint <;> rfl + +/-- An accept of `checkBytesWithUserAxioms` is an accept under `allowUser`. -/ +theorem checkBytesWithUserAxioms_ok_iff {hash : ByteArray → Address} {binding : Binding} + {pre : Prelude .anon} {limits : Limits} {records : Records} {blobs : Ix.Kernel.Ingress.Blobs} + {hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint} {env : Ix.Kernel.Env .anon} : + checkBytesWithUserAxioms hash binding pre limits records blobs hint = .ok env ↔ + ∃ axs, checkBytesUnder .allowUser hash binding pre limits records blobs hint = .ok (env, axs) := + map_fst_ok_iff + +/-! ## The tiered entries -/ + +/-- A bind whose first action accepts. -/ +theorem bind_ok {ε α β : Type} {m : Except ε α} {k : α → Except ε β} {r : β} + (h : (m >>= k) = .ok r) : ∃ a, m = .ok a ∧ k a = .ok r := by + cases m with + | error e => exact nomatch h + | ok a => exact ⟨a, rfl, h⟩ + +/-- A bind after an accepting action is its continuation. -/ +theorem bind_of_ok {ε α β : Type} {m : Except ε α} {a : α} (h : m = .ok a) (k : α → Except ε β) : + (m >>= k) = k a := by + rw [h] + rfl + +/-- A map through a bind, continuation by continuation. -/ +theorem map_bind_eq {ε α β γ : Type} (f : β → γ) (m : Except ε α) {k₁ : α → Except ε β} + {k₂ : α → Except ε γ} (h : ∀ a, f <$> k₁ a = k₂ a) : f <$> (m >>= k₁) = m >>= k₂ := by + cases m with + | error e => rfl + | ok a => exact h a + +/-- **At `strict`, the tiered entry over decoded records is +`checkConstantsTiered`.** -/ +theorem checkConstantsTieredUnder_strict (hash : ByteArray → Address) (t : Ix.Kernel.RoleTable) + (pre : Prelude .anon) (constants : List (Address × Ixon.Constant)) + (blobs : Ix.Kernel.Ingress.Blobs) (hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint) : + Prod.fst <$> checkConstantsTieredUnder .strict hash t pre constants blobs hint = + checkConstantsTiered hash t pre constants blobs hint := by + unfold checkConstantsTieredUnder checkConstantsTiered + generalize partitionTier constants = p + obtain ⟨logical, tier⟩ := p + dsimp only + cases fenceOf logical tier with + | some x => + obtain ⟨a, r⟩ := x + rfl + | none => + simp only [bind, Except.bind, pure, Except.pure] + rw [← checkConstantsUnder_strict hash t pre logical blobs hint] + cases checkConstantsUnder .strict hash t pre logical blobs hint with + | error e => rfl + | ok q => + obtain ⟨env, axs⟩ := q + simp only [Except.mapError, Functor.map, Except.map] + cases readTier (streamContext hash t pre constants blobs hint) tier with + | error e => rfl + | ok tds => + try simp only [] + cases Ix.Kernel.Tier.checkTierC (Ix.Kernel.mkFEnv env) (tds.toList.map (·.2)) with + | ok r => obtain ⟨_, ts⟩ := r; rfl + | error r => obtain ⟨e, k⟩ := r; rfl + +/-- **T1 for the tiered entry over decoded records**: an accept under any policy +that lists no user axiom is `checkConstantsTiered`'s accept. -/ +theorem checkConstantsTieredUnder_strict_of_nil {policy : Ix.Kernel.Cached.AxiomPolicy} + {hash : ByteArray → Address} {t : Ix.Kernel.RoleTable} {pre : Prelude .anon} + {constants : List (Address × Ixon.Constant)} {blobs : Ix.Kernel.Ingress.Blobs} + {hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint} + {r : Ix.Kernel.Env .anon × List (TierConst .anon)} + (h : checkConstantsTieredUnder policy hash t pre constants blobs hint = .ok (r, [])) : + checkConstantsTiered hash t pre constants blobs hint = .ok r := by + unfold checkConstantsTieredUnder at h + unfold checkConstantsTiered + generalize partitionTier constants = p at h ⊢ + obtain ⟨logical, tier⟩ := p + cases hf : fenceOf logical tier with + | some x => simp [hf, bind, Except.bind, throw, throwThe, MonadExceptOf.throw] at h + | none => + simp only [hf, bind, Except.bind, pure, Except.pure] at h ⊢ + cases hc : checkConstantsUnder policy hash t pre logical blobs hint with + | error e => simp [hc, Except.mapError] at h + | ok q => + obtain ⟨env, axs⟩ := q + simp only [hc, Except.mapError] at h + cases hr : readTier (streamContext hash t pre constants blobs hint) tier with + | error e => simp [hr] at h + | ok tds => + simp only [hr] at h + cases ht : Ix.Kernel.Tier.checkTierC (Ix.Kernel.mkFEnv env) (tds.toList.map (·.2)) with + | error e => simp [ht, throw, throwThe, MonadExceptOf.throw] at h + | ok q' => + obtain ⟨fe', ts'⟩ := q' + simp only [ht, Except.ok.injEq, Prod.mk.injEq] at h + obtain ⟨rfl, rfl⟩ := h + have hw := checkConstantsUnder_strict_of_nil hc + simp only [hw, Except.mapError, ht] + +/-- **At `strict`, the tiered Ixon entry is `checkBytesTieredUsing`.** -/ +theorem checkBytesTieredUsingUnder_strict (prelude : Ix.Kernel.RoleTable → Except String (Prelude .anon)) + (hash : ByteArray → Address) (binding : Binding) (limits : Limits) + (records : Records) (blobs : Ix.Kernel.Ingress.Blobs) + (hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint) : + Prod.fst <$> checkBytesTieredUsingUnder .strict prelude hash binding limits records blobs hint = + checkBytesTieredUsing prelude hash binding limits records blobs hint := by + unfold checkBytesTieredUsingUnder checkBytesTieredUsing + refine map_bind_eq _ _ fun _ => ?_ + refine map_bind_eq _ _ fun _ => ?_ + refine map_bind_eq _ _ fun _ => ?_ + refine map_bind_eq _ _ fun t => ?_ + refine map_bind_eq _ _ fun pre => ?_ + refine map_bind_eq _ _ fun constants => ?_ + exact checkConstantsTieredUnder_strict hash t pre constants blobs hint + +/-- **T1 for the tiered Ixon entry**: an accept under any policy that lists no +user axiom is `checkBytesTieredUsing`'s accept. -/ +theorem checkBytesTieredUsingUnder_strict_of_nil {policy : Ix.Kernel.Cached.AxiomPolicy} + {prelude : Ix.Kernel.RoleTable → Except String (Prelude .anon)} + {hash : ByteArray → Address} {binding : Binding} {limits : Limits} + {records : Records} {blobs : Ix.Kernel.Ingress.Blobs} + {hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint} + {r : Ix.Kernel.Env .anon × List (TierConst .anon)} + (h : checkBytesTieredUsingUnder policy prelude hash binding limits records blobs hint = .ok (r, [])) : + checkBytesTieredUsing prelude hash binding limits records blobs hint = .ok r := by + unfold checkBytesTieredUsingUnder at h + unfold checkBytesTieredUsing + obtain ⟨_, h1, h⟩ := bind_ok h + obtain ⟨_, h2, h⟩ := bind_ok h + obtain ⟨_, h3, h⟩ := bind_ok h + obtain ⟨t, h4, h⟩ := bind_ok h + obtain ⟨pre, h5, h⟩ := bind_ok h + obtain ⟨constants, h6, h⟩ := bind_ok h + simp only [bind_of_ok h1, bind_of_ok h2, bind_of_ok h3, bind_of_ok h4, bind_of_ok h5, + bind_of_ok h6] + exact checkConstantsTieredUnder_strict_of_nil h + +/-- An accept of `checkBytesTieredUsingWithUserAxioms` is an accept under +`allowUser`. -/ +theorem checkBytesTieredUsingWithUserAxioms_ok_iff + {prelude : Ix.Kernel.RoleTable → Except String (Prelude .anon)} + {hash : ByteArray → Address} {binding : Binding} {limits : Limits} + {records : Records} {blobs : Ix.Kernel.Ingress.Blobs} + {hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint} + {r : Ix.Kernel.Env .anon × List (TierConst .anon)} : + checkBytesTieredUsingWithUserAxioms prelude hash binding limits records blobs hint = .ok r ↔ + ∃ axs, checkBytesTieredUsingUnder .allowUser prelude hash binding limits records blobs hint = + .ok (r, axs) := + map_fst_ok_iff + +end Ix.Kernel.Admission + +end diff --git a/IxC/Kernel/Admission/UserAxioms/Model.lean b/IxC/Kernel/Admission/UserAxioms/Model.lean new file mode 100644 index 000000000..78fd3a7d3 --- /dev/null +++ b/IxC/Kernel/Admission/UserAxioms/Model.lean @@ -0,0 +1,29 @@ +import IxC.Kernel.Admission.UserAxioms +import IxC.Kernel.Admission.Theorems + +/-! # The model of an accept that lists no user axiom + +T1 (`checkBytesUnder_strict_of_nil`, `IxC/Kernel/Admission/UserAxioms.lean`) +makes such an accept the certified entry's, and `checkBytesWith_has_model` +gives that one a model. This theorem needs `Ix.Kernel.Admission.Theorems`, +which is not a `module`, so it is not in `UserAxioms.lean`, which is. -/ + +namespace Ix.Kernel.Admission + +open Ix.Kernel (ConstRef) +open Ix.Kernel.Reader + +universe u + +/-- **Model existence without user axioms**: an accept of the bytes entry under +any policy that lists no user axiom has a model in every set theory. -/ +theorem checkBytesUnder_has_model_of_nil (V : Type u) [Ix.Kernel.SetTheory V] + {policy : Ix.Kernel.Cached.AxiomPolicy} + {hash : ByteArray → Address} {binding : Binding} {pre : Prelude .anon} + {limits : Limits} {records : Records} {blobs : Ix.Kernel.Ingress.Blobs} + {hint : ConstRef Address → Option Ix.Kernel.ReducibilityHint} {env : Ix.Kernel.Env .anon} + (h : checkBytesUnder policy hash binding pre limits records blobs hint = .ok (env, [])) : + Nonempty (Ix.Kernel.Model V env) := + checkBytesWith_has_model V (checkBytesUnder_strict_of_nil h) + +end Ix.Kernel.Admission diff --git a/IxC/Kernel/Audit/Roots.lean b/IxC/Kernel/Audit/Roots.lean index 0fabc8d69..b171bcc3c 100644 --- a/IxC/Kernel/Audit/Roots.lean +++ b/IxC/Kernel/Audit/Roots.lean @@ -3,6 +3,9 @@ import IxC.Ixon.Types import IxC.Kernel.Admission.Theorems import IxC.Kernel.Admission.TierTheorems import IxC.Kernel.Admission.Reference +import IxC.Kernel.Admission.UserAxioms +import IxC.Kernel.Admission.UserAxioms.Model +import IxC.Kernel.Verify.Cached.PolicyC import IxC.Kernel.Verify.Cached.PerBlock import IxC.Kernel.Audit.Axioms import IxC.Kernel.Audit.Imports @@ -56,7 +59,12 @@ entries (`checkBytesTiered`, `checkConstantsTiered`: the logical check, then the typing tier) are frozen apart: 6963 compiled functions (the entry's closure and the tier's functions), with 21 computed-field overrides, 28 proved csimp rewrites, and nothing new to rule; the closures above do not reach the -tier. Indexing the shape guard's former membership and constructor uniqueness +tier. The checks that admit user axioms (`userAxiomOperations`: the fold, the +entries and the tiered entries under `AxiomPolicy.allowUser`, whose accepts are +conditional on the user axioms they list) are frozen apart too: 6676 compiled +functions, 112 inherited externs, under the same rulings; the certified +closures above do not reach them, and their theorems (`userAxiomRoots`) are +guarded and their statements frozen below. Indexing the shape guard's former membership and constructor uniqueness (`indShapeOkFast`, `indShapeKeysNodup`) adds 14 compiled functions net to each checker closure and one proved csimp, `indShapeOk_eq_indShapeOkFast`; the reader, externs, unsafe declarations and computed-field counts are unchanged. @@ -342,6 +350,43 @@ def tierRoots : Array Lean.Name := ``Ix.Kernel.Admission.checkBytesTieredUsing_ok, ``Ix.Kernel.Admission.checkBytesTiered_ok, ``Ix.Kernel.Admission.checkBytesTiered_has_model, ``Ix.Kernel.Admission.checkBytesTiered_typed] +/-- The checks that admit user axioms (`Ix.Kernel.Cached.AxiomPolicy.allowUser`, +`IxC/Kernel/Cached/Policy.lean`): the bytes, decoded-records and tiered +user-axiom entries, the policy-parameterised entries they are the environments +of, and the fold and the step a host's record-by-record driver runs. An accept +is conditional on the user axioms the entry lists. Their closure is frozen +apart, so the certified entries' closures are unchanged. -/ +def userAxiomOperations : Array Lean.Name := + #[``Ix.Kernel.Admission.checkBytesWithUserAxioms, ``Ix.Kernel.Admission.checkConstantsWithUserAxioms, + ``Ix.Kernel.Admission.checkBytesTieredUsingWithUserAxioms, + ``Ix.Kernel.Admission.checkConstantsTieredWithUserAxioms, + ``Ix.Kernel.Admission.checkBytesUnder, ``Ix.Kernel.Admission.checkConstantsUnder, + ``Ix.Kernel.Admission.checkBytesTieredUsingUnder, ``Ix.Kernel.Admission.checkConstantsTieredUnder, + ``Ix.Kernel.Cached.checkDeclsWithUserAxioms, ``Ix.Kernel.Cached.annotDeclStepWithUserAxioms] + +/-- The theorems of the checks that admit user axioms: at `strict` the +policy-parameterised fold and entries are the certified ones; without user +axioms (T1) every policy's accept is the certified accept, and conversely the +certified fold accepts no user axiom (`checkDecls_iff_userAxioms`); hence the +model, and no proof of `False`, for such an accept. -/ +def userAxiomRoots : Array Lean.Name := + #[``Ix.Kernel.Cached.checkDeclsUnder_strict, ``Ix.Kernel.Cached.checkDeclsUnder_eq_checkDecls, + ``Ix.Kernel.Cached.checkDeclsWithUserAxioms_eq_checkDecls, + ``Ix.Kernel.Cached.checkDecls_userAxioms_nil, ``Ix.Kernel.Cached.checkDecls_iff_userAxioms, + ``Ix.Kernel.Cached.checkDeclsWithUserAxioms_sound, + ``Ix.Kernel.Cached.no_proof_of_False_cached_userAxioms, ``Ix.Kernel.model_exists_of_no_user_axioms, + ``Ix.Kernel.Admission.checkConstantsUnder_strict, + ``Ix.Kernel.Admission.checkConstantsUnder_strict_of_nil, + ``Ix.Kernel.Admission.checkConstantsWithUserAxioms_ok_iff, + ``Ix.Kernel.Admission.checkBytesUnder_strict, ``Ix.Kernel.Admission.checkBytesUnder_strict_of_nil, + ``Ix.Kernel.Admission.checkBytesWithUserAxioms_ok_iff, + ``Ix.Kernel.Admission.checkBytesUnder_has_model_of_nil, + ``Ix.Kernel.Admission.checkConstantsTieredUnder_strict, + ``Ix.Kernel.Admission.checkConstantsTieredUnder_strict_of_nil, + ``Ix.Kernel.Admission.checkBytesTieredUsingUnder_strict, + ``Ix.Kernel.Admission.checkBytesTieredUsingUnder_strict_of_nil, + ``Ix.Kernel.Admission.checkBytesTieredUsingWithUserAxioms_ok_iff] + /-- The certified entry's module, whose import closure is audited. -/ def publicModules : Array Lean.Name := #[`IxC.Kernel.Admission] @@ -387,7 +432,7 @@ def importAllowlist : Array Lean.Name := closure may not use: the entry's theorems and audit. -/ def importDenylist : Array Lean.Name := #[`IxC.Kernel.Admission.Theorems, `IxC.Kernel.Admission.Bytes.Theorems, `IxC.Kernel.Admission.Audit, - `IxC.Kernel.Audit] + `IxC.Kernel.Admission.UserAxioms, `IxC.Kernel.Audit] /-- The proofs of the public theorems may additionally use the Ixon codec's proof modules (`Ix.Ixon.Verify`, `Ix.Ixon.Bounded.Size`, with their Lean @@ -568,6 +613,55 @@ the executed fold and the certified entry. -/ #guard_kernel_axioms Ix.Kernel.Admission.checkBytes_strongModel [propext, Classical.choice, Quot.sound] #guard_kernel_axioms Ix.Kernel.Admission.checkBytes_model_values [propext, Classical.choice, Quot.sound] +/-! ### Checks that admit user axioms + +The fold and the entries under an axiom policy (`AxiomPolicy`, +`IxC/Kernel/Cached/Policy.lean`, `IxC/Kernel/Admission/UserAxioms.lean`) and +their theorems (`IxC/Kernel/Verify/Cached/PolicyC.lean`, +`IxC/Kernel/Admission/UserAxioms{,/Model}.lean`) stand on the same three +axioms. `UserAxioms.lean` is a `module`, so that the host's compiled code can +use T1 (`checkBytesUnder_strict_of_nil` and its tiered form) to make the +certified entry's receipt from an accept that lists no user axiom; it is +denied to the certified import closure like the other theorem modules +(`importDenylist`, which also covers `UserAxioms.Model`). Their statements are +frozen below; their runtime closure is frozen with the others. -/ + +run_cmd do + let env ← Lean.getEnv + for root in Ix.Kernel.Audit.userAxiomRoots ++ Ix.Kernel.Audit.userAxiomOperations do + unless env.contains root do throwError m!"required root is missing: {root}" + +#guard_kernel_axioms Ix.Kernel.Cached.checkDeclsUnder_strict [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Cached.checkDeclsUnder_eq_checkDecls [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Cached.checkDeclsWithUserAxioms_eq_checkDecls [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Cached.checkDecls_userAxioms_nil [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Cached.checkDecls_iff_userAxioms [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Cached.checkDeclsWithUserAxioms_sound [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Cached.no_proof_of_False_cached_userAxioms [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.model_exists_of_no_user_axioms [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkConstantsUnder_strict [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkConstantsUnder_strict_of_nil [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkConstantsWithUserAxioms_ok_iff [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkBytesUnder_strict [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkBytesUnder_strict_of_nil [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkBytesWithUserAxioms_ok_iff [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkBytesUnder_has_model_of_nil [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkConstantsTieredUnder_strict [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkConstantsTieredUnder_strict_of_nil [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkBytesTieredUsingUnder_strict [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkBytesTieredUsingUnder_strict_of_nil [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkBytesTieredUsingWithUserAxioms_ok_iff [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkBytesWithUserAxioms [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkConstantsWithUserAxioms [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkBytesTieredUsingWithUserAxioms [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkConstantsTieredWithUserAxioms [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkBytesUnder [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkConstantsUnder [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkBytesTieredUsingUnder [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Admission.checkConstantsTieredUnder [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Cached.checkDeclsWithUserAxioms [propext, Classical.choice, Quot.sound] +#guard_kernel_axioms Ix.Kernel.Cached.annotDeclStepWithUserAxioms [propext, Classical.choice, Quot.sound] + /-! ### Import and runtime closures The entry's import closure stays inside `importAllowlist`, below the ruled @@ -591,6 +685,13 @@ run_cmd Ix.Kernel.Audit.checkImportsWith Ix.Kernel.Audit.publicModules Ix.Kernel #guard_msgs (drop info) in run_cmd Ix.Kernel.Audit.checkImportsWith #[`IxC.Kernel.Admission.Theorems] Ix.Kernel.Audit.proofImportAllowlist Ix.Kernel.Audit.elaborationImports +#guard_msgs (drop info) in +run_cmd Ix.Kernel.Audit.checkImportsWith #[`IxC.Kernel.Admission.UserAxioms] Ix.Kernel.Audit.proofImportAllowlist Ix.Kernel.Audit.elaborationImports + +#guard_msgs (drop info) in +run_cmd Ix.Kernel.Audit.checkImportsWith #[`IxC.Kernel.Admission.UserAxioms.Model] Ix.Kernel.Audit.proofImportAllowlist Ix.Kernel.Audit.elaborationImports +#guard !Ix.Kernel.Audit.allowed Ix.Kernel.Audit.importAllowlist `IxC.Kernel.Admission.UserAxioms.Model Ix.Kernel.Audit.importDenylist + -- The entry's byte stage is admitted; projection hashing, block order, the -- codec proofs and `kernelImportAllowlist` are not widened. #guard Ix.Kernel.Audit.allowed Ix.Kernel.Audit.importAllowlist `IxC.Ixon.Canonical Ix.Kernel.Audit.importDenylist @@ -724,6 +825,28 @@ run_cmd Ix.Kernel.Audit.checkRuntimeWith Ix.Kernel.Audit.tierOperations Ix.Kerne #guard_msgs (drop info) in run_cmd Ix.Kernel.Audit.checkImportsWith #[`IxC.Kernel.Admission.Tier] Ix.Kernel.Audit.importAllowlist Ix.Kernel.Audit.elaborationImports Ix.Kernel.Audit.importDenylist +-- The checks that admit user axioms (`userAxiomOperations`, `AxiomPolicy.allowUser`): the +-- logical and tiered entries' closures with the fold under a policy (`Cached.checkDeclsUnder`, +-- the axiom arm `checkAxiomDeclC` with the user arm's refusal of an axiom role's +-- specification, `axiomRolePins`, `axiomRoleSpecHit?`), the table's admission under the +-- policy (`admitAxiomPolicy`) and the listed user axioms (`preparedUserAxioms`). Frozen apart: +-- the certified closures above do not reach them. Measured at 6676 compiled functions, fewer +-- than the tiered entries' 6963 because none of these reaches the committed default table and +-- prelude (each takes its prelude or table as an argument); inherited externs 112, and the +-- same rulings admit it with the same unsafe, csimp and ruled counts. +/-- info: runtime closure of [Ix.Kernel.Admission.checkBytesWithUserAxioms, + Ix.Kernel.Admission.checkConstantsWithUserAxioms, + Ix.Kernel.Admission.checkBytesTieredUsingWithUserAxioms, + Ix.Kernel.Admission.checkConstantsTieredWithUserAxioms, + Ix.Kernel.Admission.checkBytesUnder, + Ix.Kernel.Admission.checkConstantsUnder, + Ix.Kernel.Admission.checkBytesTieredUsingUnder, + Ix.Kernel.Admission.checkConstantsTieredUnder, + Ix.Kernel.Cached.checkDeclsWithUserAxioms, + Ix.Kernel.Cached.annotDeclStepWithUserAxioms]: 6676 compiled functions; inherited externs 112, implemented_by 0, unsafe 26, csimp 7; ruled computed_field 21, csimp 28 -/ +#guard_msgs (whitespace := lax) in +run_cmd Ix.Kernel.Audit.checkRuntimeWith Ix.Kernel.Audit.userAxiomOperations Ix.Kernel.Audit.runtimeAllowlist Ix.Kernel.Audit.runtimeRulings + -- Without the rulings the entry's closure fails: they are what admits it. /-- error: project-level execution replacements reached from [Ix.Kernel.Cached.checkDecls] -/ #guard_msgs (substring := true) in @@ -954,6 +1077,131 @@ constructor: #guard_msgs (whitespace := lax) in #check @Ix.Kernel.model_exists +/-! ### Frozen statements: the checks that admit user axioms + +The entries' signatures, the bytes and tiered Ixon entries at `strict`, T1 at +the fold and at the bytes and tiered entries, the converse, and the model +without user axioms. -/ + +/-- info: @Ix.Kernel.Admission.checkBytesWithUserAxioms : (ByteArray → Address) → + Ix.Kernel.Admission.Binding → + Ix.Kernel.Reader.Prelude Ix.Kernel.Mode.anon → + Ix.Kernel.Admission.Limits → + Ix.Kernel.Admission.Records → + Ix.Kernel.Ingress.Blobs → + (optParam (Ix.Kernel.ConstRef Address → Option Ix.Kernel.ReducibilityHint) fun x => none) → + Except Ix.Kernel.Admission.Error (Ix.Kernel.Env Ix.Kernel.Mode.anon) -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Kernel.Admission.checkBytesWithUserAxioms + +/-- info: @Ix.Kernel.Admission.checkConstantsWithUserAxioms : (ByteArray → Address) → + Ix.Kernel.RoleTable → + Ix.Kernel.Reader.Prelude Ix.Kernel.Mode.anon → + List (Address × Ixon.Constant) → + Ix.Kernel.Ingress.Blobs → + (optParam (Ix.Kernel.ConstRef Address → Option Ix.Kernel.ReducibilityHint) fun x => none) → + Except Ix.Kernel.Admission.Error (Ix.Kernel.Env Ix.Kernel.Mode.anon) -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Kernel.Admission.checkConstantsWithUserAxioms + +/-- info: @Ix.Kernel.Admission.checkBytesTieredUsingWithUserAxioms : (Ix.Kernel.RoleTable → + Except String (Ix.Kernel.Reader.Prelude Ix.Kernel.Mode.anon)) → + (ByteArray → Address) → + Ix.Kernel.Admission.Binding → + Ix.Kernel.Admission.Limits → + Ix.Kernel.Admission.Records → + Ix.Kernel.Ingress.Blobs → + (optParam (Ix.Kernel.ConstRef Address → Option Ix.Kernel.ReducibilityHint) fun x => none) → + Except Ix.Kernel.Admission.TierError + (Ix.Kernel.Env Ix.Kernel.Mode.anon × List (Ix.Kernel.Tier.TierConst Ix.Kernel.Mode.anon)) -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Kernel.Admission.checkBytesTieredUsingWithUserAxioms + +/-- info: @Ix.Kernel.Admission.checkBytesUnder : Ix.Kernel.Cached.AxiomPolicy → + (ByteArray → Address) → + Ix.Kernel.Admission.Binding → + Ix.Kernel.Reader.Prelude Ix.Kernel.Mode.anon → + Ix.Kernel.Admission.Limits → + Ix.Kernel.Admission.Records → + Ix.Kernel.Ingress.Blobs → + (optParam (Ix.Kernel.ConstRef Address → Option Ix.Kernel.ReducibilityHint) fun x => none) → + Except Ix.Kernel.Admission.Error + (Ix.Kernel.Env Ix.Kernel.Mode.anon × List (Ix.Kernel.ConstantVal Ix.Kernel.Mode.anon)) -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Kernel.Admission.checkBytesUnder + +/-- info: Ix.Kernel.Admission.admitAxiomPolicy : Ix.Kernel.Cached.AxiomPolicy → + Ix.Kernel.RoleTable → Except Ix.Kernel.Admission.Error Unit -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Kernel.Admission.admitAxiomPolicy + +/-- info: Ix.Kernel.Cached.checkDeclsUnder_eq_checkDecls : ∀ (mode : Ix.Kernel.CheckMode) {md : Ix.Kernel.Mode} + (policy : Ix.Kernel.Cached.AxiomPolicy) (ev : Ix.Kernel.Evidences md) (ds : Array (Ix.Kernel.Declaration md)), + Ix.Kernel.userAxiomsOfDecls ds.toList = [] → + Ix.Kernel.Cached.checkDeclsUnder mode policy ev ds = Ix.Kernel.Cached.checkDecls mode ev ds -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Kernel.Cached.checkDeclsUnder_eq_checkDecls + +/-- info: @Ix.Kernel.Cached.checkDecls_iff_userAxioms : ∀ {md : Ix.Kernel.Mode} {ev : Ix.Kernel.Evidences md} + (mode : Ix.Kernel.CheckMode) (ds : Array (Ix.Kernel.Declaration md)) (env : Ix.Kernel.Env md), + Ix.Kernel.Cached.checkDecls mode ev ds = Except.ok env ↔ + Ix.Kernel.Cached.checkDeclsWithUserAxioms mode ev ds = Except.ok env ∧ Ix.Kernel.userAxiomsOfDecls ds.toList = [] -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Kernel.Cached.checkDecls_iff_userAxioms + +/-- info: Ix.Kernel.Admission.checkBytesUnder_strict : ∀ (hash : ByteArray → Address) (binding : Ix.Kernel.Admission.Binding) + (pre : Ix.Kernel.Reader.Prelude Ix.Kernel.Mode.anon) (limits : Ix.Kernel.Admission.Limits) + (records : Ix.Kernel.Admission.Records) (blobs : Ix.Kernel.Ingress.Blobs) + (hint : Ix.Kernel.ConstRef Address → Option Ix.Kernel.ReducibilityHint), + Prod.fst <$> + Ix.Kernel.Admission.checkBytesUnder Ix.Kernel.Cached.AxiomPolicy.strict hash binding pre limits records blobs + hint = + Ix.Kernel.Admission.checkBytesWith hash binding pre limits records blobs hint -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Kernel.Admission.checkBytesUnder_strict + +/-- info: @Ix.Kernel.Admission.checkBytesUnder_strict_of_nil : ∀ {policy : Ix.Kernel.Cached.AxiomPolicy} + {hash : ByteArray → Address} {binding : Ix.Kernel.Admission.Binding} + {pre : Ix.Kernel.Reader.Prelude Ix.Kernel.Mode.anon} {limits : Ix.Kernel.Admission.Limits} + {records : Ix.Kernel.Admission.Records} {blobs : Ix.Kernel.Ingress.Blobs} + {hint : Ix.Kernel.ConstRef Address → Option Ix.Kernel.ReducibilityHint} {env : Ix.Kernel.Env Ix.Kernel.Mode.anon}, + Ix.Kernel.Admission.checkBytesUnder policy hash binding pre limits records blobs hint = Except.ok (env, []) → + Ix.Kernel.Admission.checkBytesWith hash binding pre limits records blobs hint = Except.ok env -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Kernel.Admission.checkBytesUnder_strict_of_nil + +/-- info: Ix.Kernel.Admission.checkBytesTieredUsingUnder_strict : ∀ + (prelude : Ix.Kernel.RoleTable → Except String (Ix.Kernel.Reader.Prelude Ix.Kernel.Mode.anon)) + (hash : ByteArray → Address) (binding : Ix.Kernel.Admission.Binding) (limits : Ix.Kernel.Admission.Limits) + (records : Ix.Kernel.Admission.Records) (blobs : Ix.Kernel.Ingress.Blobs) + (hint : Ix.Kernel.ConstRef Address → Option Ix.Kernel.ReducibilityHint), + Prod.fst <$> + Ix.Kernel.Admission.checkBytesTieredUsingUnder Ix.Kernel.Cached.AxiomPolicy.strict prelude hash binding limits + records blobs hint = + Ix.Kernel.Admission.checkBytesTieredUsing prelude hash binding limits records blobs hint -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Kernel.Admission.checkBytesTieredUsingUnder_strict + +/-- info: @Ix.Kernel.Admission.checkBytesTieredUsingUnder_strict_of_nil : ∀ {policy : Ix.Kernel.Cached.AxiomPolicy} + {prelude : Ix.Kernel.RoleTable → Except String (Ix.Kernel.Reader.Prelude Ix.Kernel.Mode.anon)} + {hash : ByteArray → Address} {binding : Ix.Kernel.Admission.Binding} {limits : Ix.Kernel.Admission.Limits} + {records : Ix.Kernel.Admission.Records} {blobs : Ix.Kernel.Ingress.Blobs} + {hint : Ix.Kernel.ConstRef Address → Option Ix.Kernel.ReducibilityHint} + {r : Ix.Kernel.Env Ix.Kernel.Mode.anon × List (Ix.Kernel.Tier.TierConst Ix.Kernel.Mode.anon)}, + Ix.Kernel.Admission.checkBytesTieredUsingUnder policy prelude hash binding limits records blobs hint = + Except.ok (r, []) → + Ix.Kernel.Admission.checkBytesTieredUsing prelude hash binding limits records blobs hint = Except.ok r -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Kernel.Admission.checkBytesTieredUsingUnder_strict_of_nil + +/-- info: @Ix.Kernel.model_exists_of_no_user_axioms : ∀ {md : Ix.Kernel.Mode} [md.Certified] (V : Type u_1) + [inst : Ix.Kernel.SetTheory V] (ev : Ix.Kernel.Evidences md) (ds : Array (Ix.Kernel.Declaration md)) + (env : Ix.Kernel.Env md), + Ix.Kernel.Cached.checkDeclsWithUserAxioms Ix.Kernel.CheckMode.verified ev ds = Except.ok env → + Ix.Kernel.userAxiomsOfDecls ds.toList = [] → Nonempty (Ix.Kernel.Model V env) -/ +#guard_msgs (whitespace := lax) in +#check @Ix.Kernel.model_exists_of_no_user_axioms + /-! ## The kernel-side modules and the import allowlists The `Ix.Kernel` umbrella (the kernel-side boundary, including the committed diff --git a/IxC/Kernel/Cached/Policy.lean b/IxC/Kernel/Cached/Policy.lean new file mode 100644 index 000000000..d45220013 --- /dev/null +++ b/IxC/Kernel/Cached/Policy.lean @@ -0,0 +1,328 @@ +module + +public import IxC.Kernel.Cached.Installed + +@[expose] public section + +/-! +# The axiom policy: the strict fold and the fold that admits user axioms + +The certified fold `checkDecls` (`IxC/Kernel/Cached/Installed.lean`) admits an +axiom only as one of the pinned standard axioms (`propext`, +`Classical.choice`, `Quot.sound`) or the tolerated `sorryAx`; any other axiom +declines. The **axiom policy** (`AxiomPolicy`) is the one parameter that +tells this fold apart from the fold that also admits *user axioms*: + +* `strict` is `checkDecls` itself (`checkDeclsUnder_strict`); +* `allowUser` also admits an `.axiomDecl` under an eligible key + (`Key.isCustomAxiomEligible`: an input key that names no role), with its + header and type checked and installed as `.axiomInfo`, unless its type + restates an axiom role's pinned specification (`axiomRoleSpecHit?`): that + is a role's own axiom read under an input key because the role table does + not bind the role, and it declines. + +An accept under `allowUser` is conditional on the user axioms of its records, +`userAxiomsOfDecls` (`IxC/Kernel/Roles.lean`), which are read off the +declarations, not the environment: the fold installs an opaque as +`.axiomInfo` too. The main theorem's model (`Ix.Kernel.model_exists`) is the +strict fold's; under `allowUser` this module proves that **without user +axioms the two folds are the same function** +(`checkDeclsUnder_eq_checkDecls`), so an accept that admitted none carries +every theorem of the strict fold (`IxC/Kernel/Verify/Cached/PolicyC.lean` +proves the converse and `MainC`/`MainTheorem` the soundness letters). + +The policy enters at exactly one place, the axiom arm (`checkAxiomDeclC`). +Everything else of a step is the strict fold's: `annotStepUnderC` overrides +`annotStepC` at `.axiomDecl` only, and `annotDeclStepUnder` and +`checkDeclsUnder` are `annotDeclStep` and `checkDecls` over it. The strict +definitions keep their own bodies, which the frozen runtime closures and the +proof towers read; that `strict` is them is proved here +(`checkDeclC_axiomDecl`, `annotStepUnderC_strict`, `annotDeclStepUnder_strict`, +`checkDeclsUnder_strict`), so the two can never drift apart. Like the fold's +own lemmas these use nothing but the definitions, so they live beside them. +-/ + +namespace Ix.Kernel.Cached + +open Ix.Kernel + +/-! ## The policy and the axiom roles' specifications -/ + +/-- **The axiom policy** a fold runs under: `strict` admits only the pinned +standard axioms and the tolerated `sorryAx` (`checkDecls`); `allowUser` also +admits user axioms under eligible keys (`AxiomPolicy.admits`). -/ +inductive AxiomPolicy where + | strict + | allowUser + deriving DecidableEq, Repr, Inhabited + +/-- Whether `policy` admits an axiom under the key `k` as a user axiom: +never under `strict`; under `allowUser`, exactly at an eligible key +(`Key.isCustomAxiomEligible`). -/ +def AxiomPolicy.admits (policy : AxiomPolicy) (k : Key md) : Bool := + match policy with + | .strict => false + | .allowUser => k.isCustomAxiomEligible + +@[simp] theorem AxiomPolicy.admits_strict (k : Key md) : + AxiomPolicy.strict.admits k = false := rfl + +@[simp] theorem AxiomPolicy.admits_allowUser (k : Key md) : + AxiomPolicy.allowUser.admits k = k.isCustomAxiomEligible := rfl + +/-- No policy admits an ineligible key. -/ +theorem AxiomPolicy.admits_of_not_eligible (policy : AxiomPolicy) {k : Key md} + (h : k.isCustomAxiomEligible = false) : policy.admits k = false := by + cases policy + · rfl + · exact h + +/-- **The axiom roles' pinned specifications**, each under its role's key: +the roles whose specification is a typed axiom (`Role.spec`: `propext`, +`Classical.choice`, `sorryAx`), at the role's level count, and `Quot.sound`, +the quotient block's axiom (`quotSoundRaw`). -/ +def axiomRolePins : List (Role × ConstantVal md) := + ([Role.propext, .choice, .sorryAx].filterMap fun r => + match (Role.spec (md := md)) r with + | .axiom n (some ty) => some (r, { name := .role r, levelParams := List.range n, type := ty }) + | _ => none) ++ + [(.quotSound, quotSoundRaw.toConstantVal)] + +/-- The axiom role whose pinned specification `cv` restates under another key, +if any: the same level count and, up to binder data, the same type +(`ConstantVal.matchesPin` at the role's key). -/ +def axiomRoleSpecHit? (cv : ConstantVal md) : Option Role := + (axiomRolePins.find? fun p => ConstantVal.matchesPin { cv with name := p.2.name } p.2).map (·.1) + +/-! ## The axiom arm, under a policy -/ + +variable (mode : CheckMode) + +/-- **The strict axiom arm**: `checkDeclC`'s `.axiomDecl` arm, verbatim +(`checkDeclC_axiomDecl`). `Quot.sound` is compared with the pinned quotient +block's member and installs nothing; `propext` and `Classical.choice` install +when their environment and type are the pins' (`stdAxiomOkF`) and decline +otherwise; `sorryAx` is tolerated and installs nothing; any other axiom +declines. -/ +def checkStdAxiomDeclC (fe : FEnv md) (cv : ConstantVal md) : (CheckCM md) (FEnv md) := + if cv.name = quotSoundName then + (if ConstantInfo.canonEq (.axiomInfo cv) (quotBasis.getD 4 (.axiomInfo default)) then + pure fe + else + throw (.notImplemented "quotient soundness axiom mismatch")) + else do + let (cvA, _) ← checkConstantValC mode fe cv + if stdAxiomOkF fe cvA then + pure (fe.push (.axiomInfo cvA)) + else if cvA.name = propextName ∨ cvA.name = choiceName then + throw (.notImplemented msg!"standard axiom shape mismatch ({cv.name})") + else if cvA.name = sorryAxName then + pure fe + else + throw (.notImplemented msg!"non-standard axiom ({cv.name})") + +/-- **The user axiom arm**: the header and type checked (`checkConstantValC`) +and the axiom installed as `.axiomInfo`, unless its type restates an axiom +role's pinned specification (`axiomRoleSpecHit?`), which declines: such an +axiom is the role's own (Lean's `sorryAx`, say) read under an input key +because the role table leaves the role unbound. -/ +def checkUserAxiomDeclC (fe : FEnv md) (cv : ConstantVal md) : (CheckCM md) (FEnv md) := do + let (cvA, _) ← checkConstantValC mode fe cv + match axiomRoleSpecHit? cvA with + | some r => throw (.notImplemented + msg!"user axiom {cv.name} restates the specification of {(roleKey r : Key md)}, which the role table does not bind") + | none => pure (fe.push (.axiomInfo cvA)) + +/-- **The axiom arm under a policy**: the user arm at a key the policy admits +(`AxiomPolicy.admits`), the strict arm otherwise. This is the only place the +policy is read. -/ +def checkAxiomDeclC (policy : AxiomPolicy) (fe : FEnv md) (cv : ConstantVal md) : + (CheckCM md) (FEnv md) := + if policy.admits cv.name then checkUserAxiomDeclC mode fe cv else checkStdAxiomDeclC mode fe cv + +/-- `checkDeclStepC` at an axiom, under a policy: from the empty memo state +(`resetC`), the axiom arm. -/ +def checkAxiomDeclStepC (policy : AxiomPolicy) (fe : FEnv md) (cv : ConstantVal md) : + (CheckCM md) (FEnv md) := do + resetC + checkAxiomDeclC mode policy fe cv + +/-- **Phase A's step body under a policy**: `annotStepC`, with the axiom arm +under the policy (`annotStepUnderC_strict`). -/ +def annotStepUnderC (policy : AxiomPolicy) (ev : Evidences md) (i : Nat) (fe : FEnv md) + (pend : Array (PendingCheck md)) : + (Declaration md) → (CheckCM md) (FEnv md × Array (PendingCheck md)) + | .axiomDecl cv => do + pure (← checkAxiomDeclStepC mode policy fe cv, pend) + | pd => annotStepC mode ev i fe pend pd + +/-- **Phase A's step under a policy**: `annotDeclStep` over `annotStepUnderC` +(the anchors validated first, the position carried and the error tagged). -/ +def annotDeclStepUnder (policy : AxiomPolicy) (ev : Evidences md) + (p : Nat × FEnv md × Array (PendingCheck md)) (pd : Declaration md) : + StateT (CState md) (Except (CheckError md × Nat)) (Nat × FEnv md × Array (PendingCheck md)) := + fun s => + match pd.anchorMismatch? with + | some k => .error (.notImplemented msg!"role specification mismatch ({k})", p.1) + | none => + match annotStepUnderC mode policy ev p.1 p.2.1 p.2.2 pd s with + | .ok ((fe', pend'), s') => .ok ((p.1 + 1, fe', pend'), s') + | .error e => .error (e, p.1) + +/-- **The declaration fold under a policy**: `checkDecls` over +`annotDeclStepUnder` (install every record, check every recorded declaration +on tasks, check units, return the environment). At `strict` it is +`checkDecls` (`checkDeclsUnder_strict`). -/ +def checkDeclsUnder (mode : CheckMode) (policy : AxiomPolicy) (ev : Evidences md) + (ds : Array (Declaration md)) : + Except (CheckError md × Nat) (Env md) := do + let (p, _) ← (ds.foldlM (annotDeclStepUnder mode policy ev) (0, mkFEnv Env.empty, #[])) {} + checkPendingTasks mode p.2.1 p.2.2 + checkUnits ds + pure p.2.1.env + +/-- **Phase A's step with user axioms admitted** (`allowUser`): the step the +host's record-by-record driver runs. -/ +def annotDeclStepWithUserAxioms (ev : Evidences md) + (p : Nat × FEnv md × Array (PendingCheck md)) (pd : Declaration md) : + StateT (CState md) (Except (CheckError md × Nat)) (Nat × FEnv md × Array (PendingCheck md)) := + annotDeclStepUnder mode .allowUser ev p pd + +/-- **The declaration fold with user axioms admitted**: `checkDeclsUnder` at +`allowUser`. An accept is conditional on `userAxiomsOfDecls ds.toList`; with +none it is `checkDecls`'s (`checkDeclsWithUserAxioms_eq_checkDecls`). -/ +def checkDeclsWithUserAxioms (mode : CheckMode) (ev : Evidences md) + (ds : Array (Declaration md)) : + Except (CheckError md × Nat) (Env md) := + checkDeclsUnder mode .allowUser ev ds + +/-! ## `strict` is the certified fold -/ + +/-- `checkDeclC`'s axiom arm is the strict arm. -/ +theorem checkDeclC_axiomDecl (ev : Evidences md) (fe : FEnv md) (cv : ConstantVal md) : + checkDeclC mode ev fe (.axiomDecl cv) = checkStdAxiomDeclC mode fe cv := rfl + +/-- The axiom arm at `strict` is the strict arm. -/ +theorem checkAxiomDeclC_strict (fe : FEnv md) (cv : ConstantVal md) : + checkAxiomDeclC mode .strict fe cv = checkStdAxiomDeclC mode fe cv := rfl + +/-- At `strict`, the step body is `annotStepC`. -/ +theorem annotStepUnderC_strict (ev : Evidences md) (i : Nat) (fe : FEnv md) + (pend : Array (PendingCheck md)) (pd : Declaration md) : + annotStepUnderC mode .strict ev i fe pend pd = annotStepC mode ev i fe pend pd := by + cases pd <;> rfl + +/-- At `strict`, the step is `annotDeclStep`. -/ +theorem annotDeclStepUnder_strict (ev : Evidences md) : + annotDeclStepUnder mode .strict ev = annotDeclStep mode ev := by + funext p pd s + unfold annotDeclStepUnder annotDeclStep + simp only [annotStepUnderC_strict] + rfl + +/-- **At `strict`, the fold is `checkDecls`**, so every theorem about the +certified fold is a theorem about `checkDeclsUnder _ .strict`. -/ +theorem checkDeclsUnder_strict (ev : Evidences md) (ds : Array (Declaration md)) : + checkDeclsUnder mode .strict ev ds = checkDecls mode ev ds := by + unfold checkDeclsUnder checkDecls + rw [annotDeclStepUnder_strict] + +/-! ## Without user axioms, the policies agree + +A policy is read only at an axiom's key. Two policies that admit the same +there take the same step (`annotDeclStepUnder_congr`), so over records none of +which is a user axiom (`userAxiomsOfDecls`, an eligible `.axiomDecl`) every +policy's fold is the strict one: `checkDeclsUnder_eq_checkDecls`. -/ + +/-- Two policies that admit the same at an axiom's key take the same step +body there, and every policy takes `annotStepC`'s at any other record. -/ +theorem annotStepUnderC_congr {p₁ p₂ : AxiomPolicy} (ev : Evidences md) (i : Nat) + (fe : FEnv md) (pend : Array (PendingCheck md)) (pd : Declaration md) + (h : ∀ cv, pd = .axiomDecl cv → p₁.admits cv.name = p₂.admits cv.name) : + annotStepUnderC mode p₁ ev i fe pend pd = annotStepUnderC mode p₂ ev i fe pend pd := by + cases pd with + | axiomDecl cv => + have hc : checkAxiomDeclStepC mode p₁ fe cv = checkAxiomDeclStepC mode p₂ fe cv := by + unfold checkAxiomDeclStepC checkAxiomDeclC + rw [h cv rfl] + show (do pure (← checkAxiomDeclStepC mode p₁ fe cv, pend) : + (CheckCM md) (FEnv md × Array (PendingCheck md))) = + (do pure (← checkAxiomDeclStepC mode p₂ fe cv, pend)) + rw [hc] + | _ => rfl + +/-- The step, likewise. -/ +theorem annotDeclStepUnder_congr {p₁ p₂ : AxiomPolicy} (ev : Evidences md) + (p : Nat × FEnv md × Array (PendingCheck md)) (pd : Declaration md) + (h : ∀ cv, pd = .axiomDecl cv → p₁.admits cv.name = p₂.admits cv.name) : + annotDeclStepUnder mode p₁ ev p pd = annotDeclStepUnder mode p₂ ev p pd := by + funext s + unfold annotDeclStepUnder + rw [annotStepUnderC_congr mode ev p.1 p.2.1 p.2.2 pd h] + +/-- A record that is not a user axiom is admitted alike by every policy. -/ +theorem admits_eq_of_userAxiom?_none {p₁ p₂ : AxiomPolicy} {pd : Declaration md} + (h : pd.userAxiom? = none) : + ∀ cv, pd = .axiomDecl cv → p₁.admits cv.name = p₂.admits cv.name := by + intro cv hcv + subst hcv + have helig : cv.name.isCustomAxiomEligible = false := by + cases he : cv.name.isCustomAxiomEligible with + | false => rfl + | true => + have hsome : (Declaration.axiomDecl cv).userAxiom? = some cv := by + simp only [Declaration.userAxiom?, he, ↓reduceIte] + rw [hsome] at h + exact nomatch h + rw [AxiomPolicy.admits_of_not_eligible p₁ helig, AxiomPolicy.admits_of_not_eligible p₂ helig] + +/-- Folds over a list whose steps agree at every element agree. -/ +theorem foldlM_stateT_congr {σ ε α β : Type} {f g : α → β → StateT σ (Except ε) α} : + ∀ (l : List β), (∀ b ∈ l, ∀ a s, f a b s = g a b s) → + ∀ (a : α) (s : σ), (l.foldlM f a) s = (l.foldlM g a) s + | [], _, _, _ => rfl + | b :: l, h, a, s => by + have ih := foldlM_stateT_congr l (fun b' hb' => h b' (List.mem_cons_of_mem _ hb')) + simp only [List.foldlM_cons, Bind.bind, StateT.bind, h b List.mem_cons_self a s] + cases g a b s with + | error e => rfl + | ok v => + obtain ⟨a', s'⟩ := v + exact ih a' s' + +/-- **T1: without user axioms, every policy's fold is the certified fold.** +When no record is a user axiom (`userAxiomsOfDecls ds.toList = []`), the fold +under any policy, `allowUser` included, is `checkDecls`: the same verdict, the +same environment, the same failure and position. -/ +theorem checkDeclsUnder_eq_checkDecls (policy : AxiomPolicy) (ev : Evidences md) + (ds : Array (Declaration md)) (hnone : userAxiomsOfDecls ds.toList = []) : + checkDeclsUnder mode policy ev ds = checkDecls mode ev ds := by + rw [← checkDeclsUnder_strict] + have hstep : ∀ pd ∈ ds.toList, ∀ p s, + annotDeclStepUnder mode policy ev p pd s = annotDeclStepUnder mode .strict ev p pd s := by + intro pd hpd p s + have hnot : pd.userAxiom? = none := by + cases hu : pd.userAxiom? with + | none => rfl + | some cv => + have hmem : cv ∈ userAxiomsOfDecls ds.toList := + List.mem_filterMap.mpr ⟨pd, hpd, hu⟩ + rw [hnone] at hmem + exact (List.not_mem_nil hmem).elim + rw [annotDeclStepUnder_congr mode ev p pd (admits_eq_of_userAxiom?_none hnot)] + have hfold : ∀ s, (ds.foldlM (annotDeclStepUnder mode policy ev) (0, mkFEnv Env.empty, #[])) s = + (ds.foldlM (annotDeclStepUnder mode .strict ev) (0, mkFEnv Env.empty, #[])) s := by + intro s + rw [← Array.foldlM_toList, ← Array.foldlM_toList] + exact foldlM_stateT_congr ds.toList hstep _ s + unfold checkDeclsUnder + rw [hfold] + +/-- **T1 at the user-axiom fold**: with no user axiom among the records, +`checkDeclsWithUserAxioms` is `checkDecls`. -/ +theorem checkDeclsWithUserAxioms_eq_checkDecls (ev : Evidences md) + (ds : Array (Declaration md)) (hnone : userAxiomsOfDecls ds.toList = []) : + checkDeclsWithUserAxioms mode ev ds = checkDecls mode ev ds := + checkDeclsUnder_eq_checkDecls mode .allowUser ev ds hnone + +end Ix.Kernel.Cached diff --git a/IxC/Kernel/Checker.lean b/IxC/Kernel/Checker.lean index 1414fc792..2f51a0d31 100644 --- a/IxC/Kernel/Checker.lean +++ b/IxC/Kernel/Checker.lean @@ -424,41 +424,4 @@ def checkDeclsPure (ops : (CheckerOps md) m) (ev : Evidences md) (ds : List (Declaration md)) : m (Env md) := ds.foldlM (checkDecl mode ops ev) Env.empty -/-- Check an `.axiomDecl` declaration when custom user axioms are permitted: -in addition to the pinned standard axioms (`propext`, `Classical.choice`, -`Quot.sound`) and the tolerated `sorryAx`, any well-formed axiom whose key -satisfies `Key.isCustomAxiomEligible` is admitted and installed as `.axiomInfo`. -/ -def checkAxiomDeclWithUserAxioms (ops : (CheckerOps md) m) (env : Env md) - (cv : ConstantVal md) : m (Env md) := do - if cv.name = quotSoundName then - if ConstantInfo.canonEq (.axiomInfo cv) (quotBasis.getD 4 (.axiomInfo default)) then - pure env - else - throw (.notImplemented "quotient soundness axiom mismatch") - else do - let cvA ← checkConstantVal ops env cv - if stdAxiomOk env cvA then - pure ⟨.axiomInfo cvA :: env.consts⟩ - else if cvA.name = propextName ∨ cvA.name = choiceName then - throw (.notImplemented msg!"standard axiom shape mismatch ({cv.name})") - else if cvA.name = sorryAxName then - pure env - else if cvA.name.isCustomAxiomEligible then - pure ⟨.axiomInfo cvA :: env.consts⟩ - else - throw (.notImplemented msg!"non-standard axiom ({cv.name})") - -/-- Check a single declaration, admitting custom user axioms (`Key.isCustomAxiomEligible`) -at `.axiomDecl` and delegating all other declaration kinds to `checkDecl`. -/ -def checkDeclWithUserAxioms (ops : (CheckerOps md) m) (ev : Evidences md) (env : Env md) - (d : Declaration md) : m (Env md) := - match d with - | .axiomDecl cv => checkAxiomDeclWithUserAxioms ops env cv - | _ => checkDecl mode ops ev env d - -/-- Check a list of declarations in order from `Env.empty`, admitting custom user axioms. -/ -def checkDeclsPureWithUserAxioms (ops : (CheckerOps md) m) (ev : Evidences md) - (ds : List (Declaration md)) : m (Env md) := - ds.foldlM (checkDeclWithUserAxioms mode ops ev) Env.empty - end Ix.Kernel diff --git a/IxC/Kernel/DeclCheck.lean b/IxC/Kernel/DeclCheck.lean index 870c0b283..e8820b6c5 100644 --- a/IxC/Kernel/DeclCheck.lean +++ b/IxC/Kernel/DeclCheck.lean @@ -849,27 +849,6 @@ def checkDivModEvidenceF (ops : (CheckerOps md) m) (ev : Evidences md) else throw (.notImplemented msg!"unsupported Nat.div/mod environment ({c})") -/-- `checkAxiomDeclWithUserAxioms` through the `FEnv` index, returning the pushed index. -/ -def checkAxiomDeclWithUserAxiomsF (ops : (CheckerOps md) m) (fe : FEnv md) - (cv : ConstantVal md) : m (FEnv md) := do - if cv.name = quotSoundName then - if ConstantInfo.canonEq (.axiomInfo cv) (quotBasis.getD 4 (.axiomInfo default)) then - pure fe - else - throw (.notImplemented "quotient soundness axiom mismatch") - else do - let cvA ← checkConstantValF ops fe cv - if stdAxiomOkF fe cvA then - pure (fe.push (.axiomInfo cvA)) - else if cvA.name = propextName ∨ cvA.name = choiceName then - throw (.notImplemented msg!"standard axiom shape mismatch ({cv.name})") - else if cvA.name = sorryAxName then - pure fe - else if cvA.name.isCustomAxiomEligible then - pure (fe.push (.axiomInfo cvA)) - else - throw (.notImplemented msg!"non-standard axiom ({cv.name})") - end Mirrors end Ix.Kernel diff --git a/IxC/Kernel/Denotes.lean b/IxC/Kernel/Denotes.lean index cae8cd403..f15fc7b70 100644 --- a/IxC/Kernel/Denotes.lean +++ b/IxC/Kernel/Denotes.lean @@ -286,57 +286,7 @@ structure Model (V : Type w) [SetTheory V] (env : Env md) where A ∈ˢ univ (Level.eval φ u) → a ∈ˢ A → b ∈ˢ A → app (app (app E A) a) b = eqv a b -/-! ## Custom user axiom realization, value models, and semantic consistency -/ - -/-- A constant specification `cv` (such as a custom user axiom) is realized under -`(cval, env)` in `V` when at every universe-parameter valuation `φ` and variable -assignment `ρ`, `cv.type` denotes some set `T` containing `cval cv.name φ`. -/ -def AxiomRealized (V : Type w) [SetTheory V] - (cval : Key md → (LevelParam → Nat) → V) (env : Env md) (cv : ConstantVal md) : Prop := - ∀ (φ : LevelParam → Nat) (ρ : BVarIdx → V), - ∃ T, Denotes cval env φ ρ cv.type T ∧ cval cv.name φ ∈ˢ T - -/-- A list of axiom specifications `axs` is realized under `(cval, env)` in `V` -when every `cv ∈ axs` satisfies `AxiomRealized V cval env cv`. -/ -def AxiomSetRealized (V : Type w) [SetTheory V] - (cval : Key md → (LevelParam → Nat) → V) (env : Env md) - (axs : List (ConstantVal md)) : Prop := - ∀ cv ∈ axs, AxiomRealized V cval env cv - -@[simp] theorem AxiomSetRealized_nil (V : Type w) [SetTheory V] - (cval : Key md → (LevelParam → Nat) → V) (env : Env md) : - AxiomSetRealized V cval env [] := by - intro cv hcv; exact nomatch hcv - -/-- Every `.axiomInfo cv` stored in a modeled environment `env` is realized by `M`. -/ -theorem Model.realizes_of_mem_consts {env : Env md} (M : Model V env) {cv : ConstantVal md} - (h : .axiomInfo cv ∈ env.consts) : - AxiomRealized V M.cval env cv := - fun φ ρ => M.mem (.axiomInfo cv) h φ ρ - -/-- Construct a `Model V env` from a constant interpretation `cval` that satisfies -`false_empty` and `eq_equality`, models all non-axiom constants of `env`, and -realizes all `.axiomInfo` constants of `env` (`AxiomRealized`). -/ -def Model.ofUserAxioms {env : Env md} - (cval : Key md → (LevelParam → Nat) → V) - (hNonAxiom : ∀ c ∈ env.consts, (∀ cv, c ≠ .axiomInfo cv) → - ∀ (φ : LevelParam → Nat) (ρ : BVarIdx → V), - ∃ T, Denotes cval env φ ρ c.toConstantVal.type T ∧ cval c.name φ ∈ˢ T) - (hAxioms : ∀ cv, .axiomInfo cv ∈ env.consts → AxiomRealized V cval env cv) - (hFalse : ∀ (φ : LevelParam → Nat) (ρ : BVarIdx → V) (F : V), - Denotes cval env φ ρ (.const falseName []) F → F = empty) - (hEq : ∀ (u : Level) (φ : LevelParam → Nat) (ρ : BVarIdx → V) (E A a b : V), - Denotes cval env φ ρ (.const eqName [u]) E → - A ∈ˢ univ (Level.eval φ u) → a ∈ˢ A → b ∈ˢ A → - app (app (app E A) a) b = eqv a b) : - Model V env where - cval := cval - mem c hc φ ρ := by - cases c with - | axiomInfo cv => exact hAxioms cv hc φ ρ - | _ => exact hNonAxiom _ hc (fun _ h => nomatch h) φ ρ - false_empty := hFalse - eq_equality := hEq +/-! ## Semantic consistency -/ /-- Semantic consistency of an environment `env`: no accepted constant in `env.consts` has type `False` (`.const falseName []`). -/ @@ -358,32 +308,4 @@ theorem consistent_of_model (V : Type w) [SetTheory V] {env : Env md} theorem Model.consistent {env : Env md} (M : Model V env) : Consistent env := consistent_of_model V ⟨M⟩ -/-- A model `M : Model V env` together with the definitional value-denotation -property (every stored `.defnInfo cv value hint` has `value` denoting -`M.cval cv.name φ`) and realization of all custom user axioms in `axs`. -/ -structure ConditionalValueModel (V : Type w) [SetTheory V] (env : Env md) - (axs : List (ConstantVal md) := []) extends Model V env where - defn_values : ∀ (cv : ConstantVal md) (value : Expr md) (hint : ReducibilityHint), - ConstantInfo.defnInfo cv value hint ∈ env.consts → - ∀ (φ : LevelParam → Nat) (ρ : BVarIdx → V), - Denotes cval env φ ρ value (cval cv.name φ) - axioms_realized : AxiomSetRealized V cval env axs - -/-- Construct a `ConditionalValueModel` from a `Model V env` that satisfies `defn_values` -and realizes the user axioms `axs`. -/ -def ConditionalValueModel.ofUserAxioms {env : Env md} {axs : List (ConstantVal md)} - (M : Model V env) - (hDefn : ∀ (cv : ConstantVal md) (value : Expr md) (hint : ReducibilityHint), - ConstantInfo.defnInfo cv value hint ∈ env.consts → - ∀ (φ : LevelParam → Nat) (ρ : BVarIdx → V), - Denotes M.cval env φ ρ value (M.cval cv.name φ)) - (hAx : AxiomSetRealized V M.cval env axs) : - ConditionalValueModel V env axs := - { toModel := M, defn_values := hDefn, axioms_realized := hAx } - -/-- Every `ConditionalValueModel` of `env` witnesses semantic consistency (`Consistent env`). -/ -theorem ConditionalValueModel.consistent {env : Env md} {axs : List (ConstantVal md)} - (vm : ConditionalValueModel V env axs) : Consistent env := - consistent_of_model V ⟨vm.toModel⟩ - end Ix.Kernel diff --git a/IxC/Kernel/Expr.lean b/IxC/Kernel/Expr.lean index be2a3fce1..59d5a476c 100644 --- a/IxC/Kernel/Expr.lean +++ b/IxC/Kernel/Expr.lean @@ -378,8 +378,11 @@ layout is | 0 | `hasLP` | 1 | The two ranges **saturate** at `satRange = 2^15 - 1`: a node whose -bound would not fit stores `satRange`, which reads as "*at least* -`satRange`". Saturation is a *representation* decision and costs +raw stored field `bvarBRaw`/`fvarBRaw` reaches `satRange` marks the +bound as saturated, so `bvarB`/`fvarB` recomputes the exact bound on +demand (for leaf/constructor nodes it is at least `satRange`, while +for binder nodes lowering a saturated body bound by 1 may drop below +`satRange`). Saturation is a *representation* decision and costs nothing logically — `Expr.bvarB`/`Expr.fvarB` remain the exact functions `Expr.bvarBound`/`Expr.fvarRange` (`bvarB_eq`, `fvarB_eq`), because the accessors fall back to a memoized exact walk on the @@ -395,7 +398,7 @@ LLVM emits is the same shift-and-mask, and every roundtrip lemma below is then `omega` after `UInt64.toNat`. -/ /-- Saturation value of the two 15-bit range fields: a stored -`satRange` reads as "at least `satRange`". -/ +`satRange` triggers an on-demand exact recomputation in `bvarB`/`fvarB`. -/ def satRange : Nat := 32767 /-- Assemble the packed word from a 32-bit hash, two 15-bit ranges and diff --git a/IxC/Kernel/ExprOps.lean b/IxC/Kernel/ExprOps.lean index 03e79bf67..b28f6f5c4 100644 --- a/IxC/Kernel/ExprOps.lean +++ b/IxC/Kernel/ExprOps.lean @@ -26,6 +26,420 @@ instantiated binder bodies: it counts expression nodes but gives every namespace Ix.Kernel.Expr +/-- The least `k` with `looseBVarsBounded k` (the spec function of the +eager `bvarBs` entries). -/ +def _root_.Ix.Kernel.Expr.bvarBound : Expr md → Nat + | .bvar i => i + 1 + | .fvar _ _ | .sort _ | .const _ _ | .lit _ => 0 + | .app f a => max f.bvarBound a.bvarBound + | .lam ty body _ | .forallE ty body _ => + max ty.bvarBound (body.bvarBound - 1) + | .letE ty val body _ => + max (max ty.bvarBound val.bvarBound) (body.bvarBound - 1) + | .proj _ _ e => e.bvarBound + +/-- Memoized `bvarBound` (the saturated branch's exact recomputation). -/ +def bvarBoundGo (memo : Std.HashMap (Expr md) Nat) (e : Expr md) : + Nat × Std.HashMap (Expr md) Nat := + match memo[e]? with + | some r => (r, memo) + | none => + let (r, memo) : Nat × Std.HashMap (Expr md) Nat := + match e with + | .bvar i => (i + 1, memo) + | .fvar _ _ | .sort _ | .const _ _ | .lit _ => (0, memo) + | .app f a => + let (rf, memo) := bvarBoundGo memo f + let (ra, memo) := bvarBoundGo memo a + (max rf ra, memo) + | .lam ty body _ | .forallE ty body _ => + let (rt, memo) := bvarBoundGo memo ty + let (rb, memo) := bvarBoundGo memo body + (max rt (rb - 1), memo) + | .letE ty val body _ => + let (rt, memo) := bvarBoundGo memo ty + let (rv, memo) := bvarBoundGo memo val + let (rb, memo) := bvarBoundGo memo body + (max (max rt rv) (rb - 1), memo) + | .proj _ _ sub => bvarBoundGo memo sub + (r, memo.insert e r) + +@[inherit_doc bvarBoundGo] +def bvarBoundMemo (e : Expr md) : Nat := (bvarBoundGo {} e).1 + +/-- **The loose-bvar bound the checker reads**: the packed field, or — +on the saturated branch alone — the exact memoized recomputation. +Equal to `Expr.bvarBound` unconditionally (`bvarB_eq`). -/ +@[inline] def bvarB (e : Expr md) : Nat := + let r := e.bvarBRaw + if r == satRange then bvarBoundMemo e else r + +/-- The packed loose-bvar field is `bvarBound` wherever it did not +saturate. -/ +theorem bvarBRaw_exact : ∀ e : Expr md, e.bvarBRaw < satRange → + e.bvarBRaw = bvarBound e := by + intro e + induction e with + | bvar i => intro h; simp_all [satRange, bvarBound]; omega + | fvar _ _ _ | sort _ | const _ _ | lit _ => + intro _; simp [bvarBound] + | app f a ihf iha => + intro h + rw [bvarBRaw_app] at h ⊢ + rw [ihf (by omega), iha (by omega), bvarBound] + | lam ty b m iht ihb => + intro h + rw [bvarBRaw_lam] at h ⊢ + have hb : b.bvarBRaw ≠ satRange := by + intro hb'; rw [hb'] at h; simp at h; omega + have hb2 : b.bvarBRaw < satRange := by + have := bvarBRaw_lt b; simp [satRange] at *; omega + rw [if_neg hb, iht (by omega), ihb hb2, bvarBound] + | forallE ty b m iht ihb => + intro h + rw [bvarBRaw_forallE] at h ⊢ + have hb : b.bvarBRaw ≠ satRange := by + intro hb'; rw [hb'] at h; simp at h; omega + have hb2 : b.bvarBRaw < satRange := by + have := bvarBRaw_lt b; simp [satRange] at *; omega + rw [if_neg hb, iht (by omega), ihb hb2, bvarBound] + | letE ty v b _ iht ihv ihb => + intro h + rw [bvarBRaw_letE] at h ⊢ + have hb : b.bvarBRaw ≠ satRange := by + intro hb'; rw [hb'] at h; simp at h; omega + have hb2 : b.bvarBRaw < satRange := by + have := bvarBRaw_lt b; simp [satRange] at *; omega + rw [if_neg hb, iht (by omega), ihv (by omega), ihb hb2, bvarBound] + | proj s i sub ih => + intro h + rw [bvarBRaw_proj] at h ⊢ + rw [ih h, bvarBound] + +/-- The `bvarBound` walk's memo invariant. -/ +def MemoBInv (memo : Std.HashMap (Expr md) Nat) : Prop := + ∀ (e : Expr md) (r : Nat), memo[e]? = some r → r = bvarBound e + +theorem MemoBInv.empty : (MemoBInv (md := md)) {} := by + intro e r h; simp at h + +theorem MemoBInv.insert {memo : Std.HashMap (Expr md) Nat} (hm : MemoBInv memo) + {e : Expr md} {r : Nat} (heq : r = bvarBound e) : + MemoBInv (memo.insert e r) := by + intro e' r' hk + rw [Std.HashMap.getElem?_insert] at hk + split at hk + · rename_i hbeq + cases hk + rw [← (beq_iff_eq ..).mp hbeq] + exact heq + · exact hm e' r' hk + +/-- **The memoized `bvarBound` walk agrees with `bvarBound`.** -/ +theorem bvarBoundGo_spec : ∀ (e : Expr md) {memo : Std.HashMap (Expr md) Nat}, + MemoBInv memo → + (bvarBoundGo memo e).1 = bvarBound e ∧ + MemoBInv (bvarBoundGo memo e).2 := by + intro e + induction e with + | bvar i => + intro memo hm + rw [bvarBoundGo.eq_def] + split + · rename_i r hhit; exact ⟨hm _ _ hhit, hm⟩ + · exact ⟨rfl, hm.insert rfl⟩ + | fvar idx ty _ | sort u | const n us | lit l => + intro memo hm + rw [bvarBoundGo.eq_def] + split + · rename_i r hhit; exact ⟨hm _ _ hhit, hm⟩ + · exact ⟨rfl, hm.insert rfl⟩ + | app f a ihf iha => + intro memo hm + rw [bvarBoundGo.eq_def] + split + · rename_i r hhit; exact ⟨hm _ _ hhit, hm⟩ + · obtain ⟨h1, hm1⟩ := ihf hm + obtain ⟨h2, hm2⟩ := iha hm1 + refine ⟨by simp [h1, h2, bvarBound], hm2.insert ?_⟩ + simp [h1, h2, bvarBound] + | lam ty b m iht ihb | forallE ty b m iht ihb => + intro memo hm + rw [bvarBoundGo.eq_def] + split + · rename_i r hhit; exact ⟨hm _ _ hhit, hm⟩ + · obtain ⟨h1, hm1⟩ := iht hm + obtain ⟨h2, hm2⟩ := ihb hm1 + refine ⟨by simp [h1, h2, bvarBound], hm2.insert ?_⟩ + simp [h1, h2, bvarBound] + | letE ty v b _ iht ihv ihb => + intro memo hm + rw [bvarBoundGo.eq_def] + split + · rename_i r hhit; exact ⟨hm _ _ hhit, hm⟩ + · obtain ⟨h1, hm1⟩ := iht hm + obtain ⟨h2, hm2⟩ := ihv hm1 + obtain ⟨h3, hm3⟩ := ihb hm2 + refine ⟨by simp [h1, h2, h3, bvarBound], hm3.insert ?_⟩ + simp [h1, h2, h3, bvarBound] + | proj s i sub ih => + intro memo hm + rw [bvarBoundGo.eq_def] + split + · rename_i r hhit; exact ⟨hm _ _ hhit, hm⟩ + · obtain ⟨h1, hm1⟩ := ih hm + refine ⟨by simp [h1, bvarBound], hm1.insert ?_⟩ + simp [h1, bvarBound] + +@[inherit_doc bvarBoundGo_spec] +theorem bvarBoundMemo_eq (e : Expr md) : + bvarBoundMemo e = bvarBound e := + (bvarBoundGo_spec e MemoBInv.empty).1 + +/-- The `bvarB` field is `bvarBound`. -/ +theorem bvarB_eq : ∀ e : Expr md, e.bvarB = bvarBound e := by + intro e + show (if e.bvarBRaw == satRange then bvarBoundMemo e + else e.bvarBRaw) = _ + split + · rename_i h; exact bvarBoundMemo_eq e + · rename_i h + have hne : e.bvarBRaw ≠ satRange := by simpa using h + have := bvarBRaw_lt e + exact bvarBRaw_exact e (by simp [satRange] at *; omega) + +/-- Bump every loose bound variable `≥ cutoff` by `amount`. Used to +transport a constructor-telescope field domain (parameters, then prior +fields) into a recursor-rule telescope (parameters, motive, minors, +then prior fields): parameter references must skip the extra motive +and minor binders, and by the zeta expansion's substitution to carry +open let-values under binders. -/ +def liftLooseBVars (amount : Nat) : (cutoff : Nat) → Expr md → Expr md + | c, .bvar i => if i ≥ c then .bvar (i + amount) else .bvar i + | _, .fvar i ty => .fvar i ty + | _, .sort u => .sort u + | _, .const n us => .const n us + | c, .app a b => .app (liftLooseBVars amount c a) (liftLooseBVars amount c b) + | c, .lam ty body m => + .lam (liftLooseBVars amount c ty) (liftLooseBVars amount (c + 1) body) m + | c, .forallE ty body m => + .forallE (liftLooseBVars amount c ty) (liftLooseBVars amount (c + 1) body) m + | c, .letE ty v body lmeta => + .letE (liftLooseBVars amount c ty) (liftLooseBVars amount c v) + (liftLooseBVars amount (c + 1) body) lmeta + | _, .lit l => .lit l + | c, .proj s i e => .proj s i (liftLooseBVars amount c e) + +/-! ### `liftLooseBVars`, memoized (task #210 Part B) + +The recursor generators lift every constructor field domain into the +rule and recursor telescopes; on a DAG-shared field type the tree walk +does not finish (task #215's `tower_struct`). The same `@[csimp]` +arrangement as `instantiate1` below; keyed by the node and the cutoff, +dropped after each call (the answer depends on `amount`). -/ + +/-- Lifting a term whose loose variables all sit below the cutoff is +the identity. -/ +theorem liftLooseBVars_of_bvarBound_le : + ∀ (e : Expr md) (amount c : Nat), e.bvarBound ≤ c → + liftLooseBVars amount c e = e := by + intro e + induction e with + | bvar i => + intro amount c h + rw [Expr.bvarBound] at h + rw [liftLooseBVars, if_neg (by omega)] + | fvar i ty _ => intro amount c _; rfl + | sort u => intro amount c _; rfl + | const n us => intro amount c _; rfl + | lit l => intro amount c _; rfl + | app f a ihf iha => + intro amount c h + rw [Expr.bvarBound, Nat.max_le] at h + rw [liftLooseBVars, ihf amount c h.1, iha amount c h.2] + | lam ty body m iht ihb => + intro amount c h + rw [Expr.bvarBound, Nat.max_le] at h + rw [liftLooseBVars, iht amount c h.1, ihb amount (c + 1) (by omega)] + | forallE ty body m iht ihb => + intro amount c h + rw [Expr.bvarBound, Nat.max_le] at h + rw [liftLooseBVars, iht amount c h.1, ihb amount (c + 1) (by omega)] + | letE ty val body _ iht ihv ihb => + intro amount c h + rw [Expr.bvarBound, Nat.max_le, Nat.max_le] at h + rw [liftLooseBVars, iht amount c h.1.1, ihv amount c h.1.2, + ihb amount (c + 1) (by omega)] + | proj sn i sub ih => + intro amount c h + rw [Expr.bvarBound] at h + rw [liftLooseBVars, ih amount c h] + +/-- The memo's invariant: every recorded answer is the real one. -/ +def LiftMemoInv (amount : Nat) (memo : Std.HashMap (Expr md × Nat) (Expr md)) : Prop := + ∀ (k : Expr md × Nat) (r : Expr md), memo[k]? = some r → r = liftLooseBVars amount k.2 k.1 + +theorem LiftMemoInv.empty {amount : Nat} : (LiftMemoInv (md := md)) amount {} := by + intro k r h; simp at h + +theorem LiftMemoInv.insert {amount : Nat} {memo : Std.HashMap (Expr md × Nat) (Expr md)} + (hm : LiftMemoInv amount memo) {e : Expr md} {c : Nat} {r : Expr md} + (heq : r = liftLooseBVars amount c e) : + LiftMemoInv amount (memo.insert (e, c) r) := by + intro k r' hk + rw [Std.HashMap.getElem?_insert] at hk + split at hk + · rename_i hbeq + cases hk + rw [← eq_of_beq hbeq] + exact heq + · exact hm k r' hk + +/-- Memoized `liftLooseBVars`. -/ +def liftLooseBVarsGo (amount : Nat) (memo : Std.HashMap (Expr md × Nat) (Expr md)) + (e : Expr md) (c : Nat) : Expr md × Std.HashMap (Expr md × Nat) (Expr md) := + if e.bvarB ≤ c then (e, memo) else + match e with + | .bvar i => (if i ≥ c then .bvar (i + amount) else .bvar i, memo) + | .fvar i ty => (.fvar i ty, memo) + | .sort u => (.sort u, memo) + | .const n us => (.const n us, memo) + | .lit l => (.lit l, memo) + | e => + match memo[(e, c)]? with + | some r => (r, memo) + | none => + let (r, memo) : Expr md × Std.HashMap (Expr md × Nat) (Expr md) := + match e with + | .app a b => + let (a', memo) := liftLooseBVarsGo amount memo a c + let (b', memo) := liftLooseBVarsGo amount memo b c + (.app a' b', memo) + | .lam ty body m => + let (t, memo) := liftLooseBVarsGo amount memo ty c + let (b, memo) := liftLooseBVarsGo amount memo body (c + 1) + (.lam t b m, memo) + | .forallE ty body m => + let (t, memo) := liftLooseBVarsGo amount memo ty c + let (b, memo) := liftLooseBVarsGo amount memo body (c + 1) + (.forallE t b m, memo) + | .letE ty v body lmeta => + let (t, memo) := liftLooseBVarsGo amount memo ty c + let (w, memo) := liftLooseBVarsGo amount memo v c + let (b, memo) := liftLooseBVarsGo amount memo body (c + 1) + (.letE t w b lmeta, memo) + | .proj s i sub => + let (u, memo) := liftLooseBVarsGo amount memo sub c + (.proj s i u, memo) + | e => (e, memo) + (r, memo.insert (e, c) r) + +/-- **The memoized walk is `liftLooseBVars`.** -/ +theorem liftLooseBVarsGo_spec {amount : Nat} : + ∀ (e : Expr md) (c : Nat) (memo : Std.HashMap (Expr md × Nat) (Expr md)), + LiftMemoInv amount memo → + (liftLooseBVarsGo amount memo e c).1 = liftLooseBVars amount c e ∧ + LiftMemoInv amount (liftLooseBVarsGo amount memo e c).2 := by + intro e + induction e with + | bvar i => + intro c memo hm + rw [liftLooseBVarsGo] + split + · rename_i h + exact ⟨(liftLooseBVars_of_bvarBound_le _ amount c (by rwa [← bvarB_eq])).symm, hm⟩ + · exact ⟨rfl, hm⟩ + | fvar i ty _ => + intro c memo hm + rw [liftLooseBVarsGo]; split <;> exact ⟨rfl, hm⟩ + | sort u => + intro c memo hm + rw [liftLooseBVarsGo]; split <;> exact ⟨rfl, hm⟩ + | const n us => + intro c memo hm + rw [liftLooseBVarsGo]; split <;> exact ⟨rfl, hm⟩ + | lit l => + intro c memo hm + rw [liftLooseBVarsGo]; split <;> exact ⟨rfl, hm⟩ + | app a b iha ihb => + intro c memo hm + rw [liftLooseBVarsGo] + split + · rename_i h + exact ⟨(liftLooseBVars_of_bvarBound_le _ amount c (by rwa [← bvarB_eq])).symm, hm⟩ + · split + · rename_i r hhit + exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ + · obtain ⟨h1, h2⟩ := iha c memo hm + obtain ⟨h3, h4⟩ := ihb c _ h2 + refine ⟨by simp [liftLooseBVars, h1, h3], ?_⟩ + exact h4.insert (by simp [liftLooseBVars, h1, h3]) + | lam ty body bi iht ihb => + intro c memo hm + rw [liftLooseBVarsGo] + split + · rename_i h + exact ⟨(liftLooseBVars_of_bvarBound_le _ amount c (by rwa [← bvarB_eq])).symm, hm⟩ + · split + · rename_i r hhit + exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ + · obtain ⟨h1, h2⟩ := iht c memo hm + obtain ⟨h3, h4⟩ := ihb (c + 1) _ h2 + refine ⟨by simp [liftLooseBVars, h1, h3], ?_⟩ + exact h4.insert (by simp [liftLooseBVars, h1, h3]) + | forallE ty body bi iht ihb => + intro c memo hm + rw [liftLooseBVarsGo] + split + · rename_i h + exact ⟨(liftLooseBVars_of_bvarBound_le _ amount c (by rwa [← bvarB_eq])).symm, hm⟩ + · split + · rename_i r hhit + exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ + · obtain ⟨h1, h2⟩ := iht c memo hm + obtain ⟨h3, h4⟩ := ihb (c + 1) _ h2 + refine ⟨by simp [liftLooseBVars, h1, h3], ?_⟩ + exact h4.insert (by simp [liftLooseBVars, h1, h3]) + | letE ty val body _ iht ihv ihb => + intro c memo hm + rw [liftLooseBVarsGo] + split + · rename_i h + exact ⟨(liftLooseBVars_of_bvarBound_le _ amount c (by rwa [← bvarB_eq])).symm, hm⟩ + · split + · rename_i r hhit + exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ + · obtain ⟨h1, h2⟩ := iht c memo hm + obtain ⟨h3, h4⟩ := ihv c _ h2 + obtain ⟨h5, h6⟩ := ihb (c + 1) _ h4 + refine ⟨by simp [liftLooseBVars, h1, h3, h5], ?_⟩ + exact h6.insert (by simp [liftLooseBVars, h1, h3, h5]) + | proj s i sub ih => + intro c memo hm + rw [liftLooseBVarsGo] + split + · rename_i h + exact ⟨(liftLooseBVars_of_bvarBound_le _ amount c (by rwa [← bvarB_eq])).symm, hm⟩ + · split + · rename_i r hhit + exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ + · obtain ⟨h1, h2⟩ := ih c memo hm + refine ⟨by simp [liftLooseBVars, h1], ?_⟩ + exact h2.insert (by simp [liftLooseBVars, h1]) + +/-- The executed `liftLooseBVars` (one memoized DAG walk). -/ +def liftLooseBVarsFast (amount c : Nat) (e : Expr md) : Expr md := + if e.bvarB ≤ c then e else (liftLooseBVarsGo amount {} e c).1 + +@[csimp] theorem liftLooseBVars_eq_liftLooseBVarsFast : + @liftLooseBVars = @liftLooseBVarsFast := by + funext md amount c e + unfold liftLooseBVarsFast + split + · rename_i h + exact liftLooseBVars_of_bvarBound_le e amount c (by rwa [← bvarB_eq]) + · exact (liftLooseBVarsGo_spec e c {} LiftMemoInv.empty).1.symm + /-- Replace `bvar d` by `v` in `e`, where `d` counts the binders passed on the way (callers start at the default `d = 0`). `v` must be closed with respect to bound variables (an `fvar`, a constant, …); it is not shifted. @@ -57,6 +471,43 @@ stays what every proof consumes. The memo is keyed by the *node and the cursor* (`instantiate1`'s answer depends on both) and dropped after each call, since it also depends on `v`. -/ +/-- Substituting for a variable no loose variable reaches is the +identity. -/ +theorem instantiate1_of_bvarBound_le : + ∀ (e : Expr md) (v : Expr md) (d : Nat), e.bvarBound ≤ d → + instantiate1 e v d = e := by + intro e + induction e with + | bvar i => + intro v d h + rw [Expr.bvarBound] at h + rw [instantiate1, if_neg (by omega), if_neg (by omega)] + | fvar i ty _ => intro v d _; rfl + | sort u => intro v d _; rfl + | const n us => intro v d _; rfl + | lit l => intro v d _; rfl + | app f a ihf iha => + intro v d h + rw [Expr.bvarBound, Nat.max_le] at h + rw [instantiate1, ihf v d h.1, iha v d h.2] + | lam ty body m iht ihb => + intro v d h + rw [Expr.bvarBound, Nat.max_le] at h + rw [instantiate1, iht v d h.1, ihb v (d + 1) (by omega)] + | forallE ty body m iht ihb => + intro v d h + rw [Expr.bvarBound, Nat.max_le] at h + rw [instantiate1, iht v d h.1, ihb v (d + 1) (by omega)] + | letE ty val body _ iht ihv ihb => + intro v d h + rw [Expr.bvarBound, Nat.max_le, Nat.max_le] at h + rw [instantiate1, iht v d h.1.1, ihv v d h.1.2, + ihb v (d + 1) (by omega)] + | proj sn i sub ih => + intro v d h + rw [Expr.bvarBound] at h + rw [instantiate1, ih v d h] + /-- The memo's invariant: every recorded answer is the real one. -/ def Inst1MemoInv (v : Expr md) (memo : Std.HashMap (Expr md × Nat) (Expr md)) : Prop := ∀ (k : Expr md × Nat) (r : Expr md), memo[k]? = some r → r = instantiate1 k.1 v k.2 @@ -80,6 +531,7 @@ theorem Inst1MemoInv.insert {v : Expr md} {memo : Std.HashMap (Expr md × Nat) ( /-- Memoized `instantiate1`. -/ def instantiate1Go (v : Expr md) (memo : Std.HashMap (Expr md × Nat) (Expr md)) (e : Expr md) (d : Nat) : Expr md × Std.HashMap (Expr md × Nat) (Expr md) := + if e.bvarB ≤ d then (e, memo) else match e with | .bvar i => (if i = d then v else if i > d then .bvar (i - 1) else .bvar i, memo) | .fvar idx ty => (.fvar idx ty, memo) @@ -123,70 +575,103 @@ theorem instantiate1Go_spec {v : Expr md} : Inst1MemoInv v (instantiate1Go v memo e d).2 := by intro e induction e with - | bvar i => intro d memo hm; exact ⟨rfl, hm⟩ - | fvar i ty _ => intro d memo hm; exact ⟨rfl, hm⟩ - | sort u => intro d memo hm; exact ⟨rfl, hm⟩ - | const n us => intro d memo hm; exact ⟨rfl, hm⟩ - | lit l => intro d memo hm; exact ⟨rfl, hm⟩ + | bvar i => + intro d memo hm + rw [instantiate1Go] + split + · rename_i h + exact ⟨(instantiate1_of_bvarBound_le _ v d (by rwa [← bvarB_eq])).symm, hm⟩ + · exact ⟨rfl, hm⟩ + | fvar i ty _ => + intro d memo hm + rw [instantiate1Go]; split <;> exact ⟨rfl, hm⟩ + | sort u => + intro d memo hm + rw [instantiate1Go]; split <;> exact ⟨rfl, hm⟩ + | const n us => + intro d memo hm + rw [instantiate1Go]; split <;> exact ⟨rfl, hm⟩ + | lit l => + intro d memo hm + rw [instantiate1Go]; split <;> exact ⟨rfl, hm⟩ | app a b iha ihb => intro d memo hm rw [instantiate1Go] split - · rename_i r hhit - exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ - · obtain ⟨h1, h2⟩ := iha d memo hm - obtain ⟨h3, h4⟩ := ihb d _ h2 - refine ⟨by simp [instantiate1, h1, h3], ?_⟩ - exact h4.insert (by simp [instantiate1, h1, h3]) + · rename_i h + exact ⟨(instantiate1_of_bvarBound_le _ v d (by rwa [← bvarB_eq])).symm, hm⟩ + · split + · rename_i r hhit + exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ + · obtain ⟨h1, h2⟩ := iha d memo hm + obtain ⟨h3, h4⟩ := ihb d _ h2 + refine ⟨by simp [instantiate1, h1, h3], ?_⟩ + exact h4.insert (by simp [instantiate1, h1, h3]) | lam ty body bi iht ihb => intro d memo hm rw [instantiate1Go] split - · rename_i r hhit - exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ - · obtain ⟨h1, h2⟩ := iht d memo hm - obtain ⟨h3, h4⟩ := ihb (d + 1) _ h2 - refine ⟨by simp [instantiate1, h1, h3], ?_⟩ - exact h4.insert (by simp [instantiate1, h1, h3]) + · rename_i h + exact ⟨(instantiate1_of_bvarBound_le _ v d (by rwa [← bvarB_eq])).symm, hm⟩ + · split + · rename_i r hhit + exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ + · obtain ⟨h1, h2⟩ := iht d memo hm + obtain ⟨h3, h4⟩ := ihb (d + 1) _ h2 + refine ⟨by simp [instantiate1, h1, h3], ?_⟩ + exact h4.insert (by simp [instantiate1, h1, h3]) | forallE ty body bi iht ihb => intro d memo hm rw [instantiate1Go] split - · rename_i r hhit - exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ - · obtain ⟨h1, h2⟩ := iht d memo hm - obtain ⟨h3, h4⟩ := ihb (d + 1) _ h2 - refine ⟨by simp [instantiate1, h1, h3], ?_⟩ - exact h4.insert (by simp [instantiate1, h1, h3]) + · rename_i h + exact ⟨(instantiate1_of_bvarBound_le _ v d (by rwa [← bvarB_eq])).symm, hm⟩ + · split + · rename_i r hhit + exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ + · obtain ⟨h1, h2⟩ := iht d memo hm + obtain ⟨h3, h4⟩ := ihb (d + 1) _ h2 + refine ⟨by simp [instantiate1, h1, h3], ?_⟩ + exact h4.insert (by simp [instantiate1, h1, h3]) | letE ty val body _ iht ihv ihb => intro d memo hm rw [instantiate1Go] split - · rename_i r hhit - exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ - · obtain ⟨h1, h2⟩ := iht d memo hm - obtain ⟨h3, h4⟩ := ihv d _ h2 - obtain ⟨h5, h6⟩ := ihb (d + 1) _ h4 - refine ⟨by simp [instantiate1, h1, h3, h5], ?_⟩ - exact h6.insert (by simp [instantiate1, h1, h3, h5]) + · rename_i h + exact ⟨(instantiate1_of_bvarBound_le _ v d (by rwa [← bvarB_eq])).symm, hm⟩ + · split + · rename_i r hhit + exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ + · obtain ⟨h1, h2⟩ := iht d memo hm + obtain ⟨h3, h4⟩ := ihv d _ h2 + obtain ⟨h5, h6⟩ := ihb (d + 1) _ h4 + refine ⟨by simp [instantiate1, h1, h3, h5], ?_⟩ + exact h6.insert (by simp [instantiate1, h1, h3, h5]) | proj s i sub ih => intro d memo hm rw [instantiate1Go] split - · rename_i r hhit - exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ - · obtain ⟨h1, h2⟩ := ih d memo hm - refine ⟨by simp [instantiate1, h1], ?_⟩ - exact h2.insert (by simp [instantiate1, h1]) + · rename_i h + exact ⟨(instantiate1_of_bvarBound_le _ v d (by rwa [← bvarB_eq])).symm, hm⟩ + · split + · rename_i r hhit + exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ + · obtain ⟨h1, h2⟩ := ih d memo hm + refine ⟨by simp [instantiate1, h1], ?_⟩ + exact h2.insert (by simp [instantiate1, h1]) /-- The executed `instantiate1` (one memoized DAG walk). -/ def instantiate1Fast (e v : Expr md) (d : Nat := 0) : Expr md := - (instantiate1Go v {} e d).1 + if e.bvarB ≤ d then e else (instantiate1Go v {} e d).1 @[csimp] theorem instantiate1_eq_instantiate1Fast : @instantiate1 = @instantiate1Fast := by funext md e v d - exact (instantiate1Go_spec e d {} Inst1MemoInv.empty).1.symm + unfold instantiate1Fast + split + · rename_i h + exact instantiate1_of_bvarBound_le e v d (by rwa [← bvarB_eq]) + · exact (instantiate1Go_spec e d {} Inst1MemoInv.empty).1.symm /-- Bulk instantiation (task #50): substitute the replacement list `vs` for the bound variables `bvar d, bvar (d + 1), …` in one traversal — @@ -244,6 +729,48 @@ above, keyed by the node and the cursor with the replacement list fixed; the `bvar` case (the recursion into a replacement, the identity at every checker site) is the pure function's. -/ +/-- Bulk instantiation at or above the loose-bvar bound is the +identity. -/ +theorem instantiateList_of_bvarBound_le : + ∀ (e : Expr md) (vs : List (Expr md)) (d : Nat), e.bvarBound ≤ d → + instantiateList e vs d = e := by + intro e + induction e with + | bvar i => + intro vs d h + rw [Expr.bvarBound] at h + rw [instantiateList] + simp [show i < d by omega] + | fvar i ty _ => intro vs d _; rw [instantiateList] + | sort u => intro vs d _; rw [instantiateList] + | const n us => intro vs d _; rw [instantiateList] + | lit l => intro vs d _; rw [instantiateList] + | app f a ihf iha => + intro vs d h + rw [Expr.bvarBound, Nat.max_le] at h + rw [instantiateList] + simp [ihf vs d h.1, iha vs d h.2] + | lam ty body m iht ihb => + intro vs d h + rw [Expr.bvarBound, Nat.max_le] at h + rw [instantiateList] + simp [iht vs d h.1, ihb vs (d + 1) (by omega)] + | forallE ty body m iht ihb => + intro vs d h + rw [Expr.bvarBound, Nat.max_le] at h + rw [instantiateList] + simp [iht vs d h.1, ihb vs (d + 1) (by omega)] + | letE ty val body _ iht ihv ihb => + intro vs d h + rw [Expr.bvarBound, Nat.max_le, Nat.max_le] at h + rw [instantiateList] + simp [iht vs d h.1.1, ihv vs d h.1.2, ihb vs (d + 1) (by omega)] + | proj sn i sub ih => + intro vs d h + rw [Expr.bvarBound] at h + rw [instantiateList] + simp [ih vs d h] + /-- The memo's invariant: every recorded answer is the real one. -/ def InstLMemoInv (vs : List (Expr md)) (memo : Std.HashMap (Expr md × Nat) (Expr md)) : Prop := ∀ (k : Expr md × Nat) (r : Expr md), memo[k]? = some r → r = instantiateList k.1 vs k.2 @@ -267,6 +794,7 @@ theorem InstLMemoInv.insert {vs : List (Expr md)} {memo : Std.HashMap (Expr md /-- Memoized `instantiateList`. -/ def instantiateListGo (vs : List (Expr md)) (memo : Std.HashMap (Expr md × Nat) (Expr md)) (e : Expr md) (d : Nat) : Expr md × Std.HashMap (Expr md × Nat) (Expr md) := + if e.bvarB ≤ d then (e, memo) else match e with | .bvar j => (instantiateList (.bvar j) vs d, memo) | .fvar idx ty => (.fvar idx ty, memo) @@ -310,233 +838,107 @@ theorem instantiateListGo_spec {vs : List (Expr md)} : InstLMemoInv vs (instantiateListGo vs memo e d).2 := by intro e induction e with - | bvar i => intro d memo hm; exact ⟨rfl, hm⟩ - | fvar i ty _ => - intro d memo hm; exact ⟨by show (Expr.fvar i ty, memo).1 = _; rw [instantiateList], hm⟩ - | sort u => intro d memo hm; exact ⟨by show (Expr.sort u, memo).1 = _; rw [instantiateList], hm⟩ - | const n us => - intro d memo hm; exact ⟨by show (Expr.const n us, memo).1 = _; rw [instantiateList], hm⟩ - | lit l => intro d memo hm; exact ⟨by show (Expr.lit l, memo).1 = _; rw [instantiateList], hm⟩ - | app a b iha ihb => + | bvar i => intro d memo hm rw [instantiateListGo] split - · rename_i r hhit - exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ - · obtain ⟨h1, h2⟩ := iha d memo hm - obtain ⟨h3, h4⟩ := ihb d _ h2 - refine ⟨by rw [instantiateList]; simp [h1, h3], ?_⟩ - exact h4.insert (by rw [instantiateList]; simp [h1, h3]) - | lam ty body bi iht ihb => + · rename_i h + exact ⟨(instantiateList_of_bvarBound_le _ vs d (by rwa [← bvarB_eq])).symm, hm⟩ + · exact ⟨rfl, hm⟩ + | fvar i ty _ => intro d memo hm rw [instantiateListGo] - split - · rename_i r hhit - exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ - · obtain ⟨h1, h2⟩ := iht d memo hm - obtain ⟨h3, h4⟩ := ihb (d + 1) _ h2 - refine ⟨by rw [instantiateList]; simp [h1, h3], ?_⟩ - exact h4.insert (by rw [instantiateList]; simp [h1, h3]) - | forallE ty body bi iht ihb => + split <;> exact ⟨by show (Expr.fvar i ty, memo).1 = _; rw [instantiateList], hm⟩ + | sort u => intro d memo hm rw [instantiateListGo] - split - · rename_i r hhit - exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ - · obtain ⟨h1, h2⟩ := iht d memo hm - obtain ⟨h3, h4⟩ := ihb (d + 1) _ h2 - refine ⟨by rw [instantiateList]; simp [h1, h3], ?_⟩ - exact h4.insert (by rw [instantiateList]; simp [h1, h3]) - | letE ty val body _ iht ihv ihb => + split <;> exact ⟨by show (Expr.sort u, memo).1 = _; rw [instantiateList], hm⟩ + | const n us => intro d memo hm rw [instantiateListGo] - split - · rename_i r hhit - exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ - · obtain ⟨h1, h2⟩ := iht d memo hm - obtain ⟨h3, h4⟩ := ihv d _ h2 - obtain ⟨h5, h6⟩ := ihb (d + 1) _ h4 - refine ⟨by rw [instantiateList]; simp [h1, h3, h5], ?_⟩ - exact h6.insert (by rw [instantiateList]; simp [h1, h3, h5]) - | proj s i sub ih => + split <;> exact ⟨by show (Expr.const n us, memo).1 = _; rw [instantiateList], hm⟩ + | lit l => intro d memo hm rw [instantiateListGo] - split - · rename_i r hhit - exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ - · obtain ⟨h1, h2⟩ := ih d memo hm - refine ⟨by rw [instantiateList]; simp [h1], ?_⟩ - exact h2.insert (by rw [instantiateList]; simp [h1]) - -/-- The executed `instantiateList` (one memoized DAG walk). -/ -def instantiateListFast (e : Expr md) (vs : List (Expr md)) (d : Nat := 0) : Expr md := - (instantiateListGo vs {} e d).1 - -@[csimp] theorem instantiateList_eq_instantiateListFast : - @instantiateList = @instantiateListFast := by - funext md e vs d - exact (instantiateListGo_spec e d {} InstLMemoInv.empty).1.symm - -/-- Bump every loose bound variable `≥ cutoff` by `amount`. Used to -transport a constructor-telescope field domain (parameters, then prior -fields) into a recursor-rule telescope (parameters, motive, minors, -then prior fields): parameter references must skip the extra motive -and minor binders, and by the zeta expansion's substitution to carry -open let-values under binders. -/ -def liftLooseBVars (amount : Nat) : (cutoff : Nat) → Expr md → Expr md - | c, .bvar i => if i ≥ c then .bvar (i + amount) else .bvar i - | _, .fvar i ty => .fvar i ty - | _, .sort u => .sort u - | _, .const n us => .const n us - | c, .app a b => .app (liftLooseBVars amount c a) (liftLooseBVars amount c b) - | c, .lam ty body m => - .lam (liftLooseBVars amount c ty) (liftLooseBVars amount (c + 1) body) m - | c, .forallE ty body m => - .forallE (liftLooseBVars amount c ty) (liftLooseBVars amount (c + 1) body) m - | c, .letE ty v body lmeta => - .letE (liftLooseBVars amount c ty) (liftLooseBVars amount c v) - (liftLooseBVars amount (c + 1) body) lmeta - | _, .lit l => .lit l - | c, .proj s i e => .proj s i (liftLooseBVars amount c e) - -/-! ### `liftLooseBVars`, memoized (task #210 Part B) - -The recursor generators lift every constructor field domain into the -rule and recursor telescopes; on a DAG-shared field type the tree walk -does not finish (task #215's `tower_struct`). The same `@[csimp]` -arrangement as `instantiate1` above; keyed by the node and the cutoff, -dropped after each call (the answer depends on `amount`). -/ - -/-- The memo's invariant: every recorded answer is the real one. -/ -def LiftMemoInv (amount : Nat) (memo : Std.HashMap (Expr md × Nat) (Expr md)) : Prop := - ∀ (k : Expr md × Nat) (r : Expr md), memo[k]? = some r → r = liftLooseBVars amount k.2 k.1 - -theorem LiftMemoInv.empty {amount : Nat} : (LiftMemoInv (md := md)) amount {} := by - intro k r h; simp at h - -theorem LiftMemoInv.insert {amount : Nat} {memo : Std.HashMap (Expr md × Nat) (Expr md)} - (hm : LiftMemoInv amount memo) {e : Expr md} {c : Nat} {r : Expr md} - (heq : r = liftLooseBVars amount c e) : - LiftMemoInv amount (memo.insert (e, c) r) := by - intro k r' hk - rw [Std.HashMap.getElem?_insert] at hk - split at hk - · rename_i hbeq - cases hk - rw [← eq_of_beq hbeq] - exact heq - · exact hm k r' hk - -/-- Memoized `liftLooseBVars`. -/ -def liftLooseBVarsGo (amount : Nat) (memo : Std.HashMap (Expr md × Nat) (Expr md)) - (e : Expr md) (c : Nat) : Expr md × Std.HashMap (Expr md × Nat) (Expr md) := - match e with - | .bvar i => (if i ≥ c then .bvar (i + amount) else .bvar i, memo) - | .fvar i ty => (.fvar i ty, memo) - | .sort u => (.sort u, memo) - | .const n us => (.const n us, memo) - | .lit l => (.lit l, memo) - | e => - match memo[(e, c)]? with - | some r => (r, memo) - | none => - let (r, memo) : Expr md × Std.HashMap (Expr md × Nat) (Expr md) := - match e with - | .app a b => - let (a', memo) := liftLooseBVarsGo amount memo a c - let (b', memo) := liftLooseBVarsGo amount memo b c - (.app a' b', memo) - | .lam ty body m => - let (t, memo) := liftLooseBVarsGo amount memo ty c - let (b, memo) := liftLooseBVarsGo amount memo body (c + 1) - (.lam t b m, memo) - | .forallE ty body m => - let (t, memo) := liftLooseBVarsGo amount memo ty c - let (b, memo) := liftLooseBVarsGo amount memo body (c + 1) - (.forallE t b m, memo) - | .letE ty v body lmeta => - let (t, memo) := liftLooseBVarsGo amount memo ty c - let (w, memo) := liftLooseBVarsGo amount memo v c - let (b, memo) := liftLooseBVarsGo amount memo body (c + 1) - (.letE t w b lmeta, memo) - | .proj s i sub => - let (u, memo) := liftLooseBVarsGo amount memo sub c - (.proj s i u, memo) - | e => (e, memo) - (r, memo.insert (e, c) r) - -/-- **The memoized walk is `liftLooseBVars`.** -/ -theorem liftLooseBVarsGo_spec {amount : Nat} : - ∀ (e : Expr md) (c : Nat) (memo : Std.HashMap (Expr md × Nat) (Expr md)), - LiftMemoInv amount memo → - (liftLooseBVarsGo amount memo e c).1 = liftLooseBVars amount c e ∧ - LiftMemoInv amount (liftLooseBVarsGo amount memo e c).2 := by - intro e - induction e with - | bvar i => intro c memo hm; exact ⟨rfl, hm⟩ - | fvar i ty _ => intro c memo hm; exact ⟨rfl, hm⟩ - | sort u => intro c memo hm; exact ⟨rfl, hm⟩ - | const n us => intro c memo hm; exact ⟨rfl, hm⟩ - | lit l => intro c memo hm; exact ⟨rfl, hm⟩ + split <;> exact ⟨by show (Expr.lit l, memo).1 = _; rw [instantiateList], hm⟩ | app a b iha ihb => - intro c memo hm - rw [liftLooseBVarsGo] + intro d memo hm + rw [instantiateListGo] split - · rename_i r hhit - exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ - · obtain ⟨h1, h2⟩ := iha c memo hm - obtain ⟨h3, h4⟩ := ihb c _ h2 - refine ⟨by simp [liftLooseBVars, h1, h3], ?_⟩ - exact h4.insert (by simp [liftLooseBVars, h1, h3]) + · rename_i h + exact ⟨(instantiateList_of_bvarBound_le _ vs d (by rwa [← bvarB_eq])).symm, hm⟩ + · split + · rename_i r hhit + exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ + · obtain ⟨h1, h2⟩ := iha d memo hm + obtain ⟨h3, h4⟩ := ihb d _ h2 + refine ⟨by rw [instantiateList]; simp [h1, h3], ?_⟩ + exact h4.insert (by rw [instantiateList]; simp [h1, h3]) | lam ty body bi iht ihb => - intro c memo hm - rw [liftLooseBVarsGo] + intro d memo hm + rw [instantiateListGo] split - · rename_i r hhit - exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ - · obtain ⟨h1, h2⟩ := iht c memo hm - obtain ⟨h3, h4⟩ := ihb (c + 1) _ h2 - refine ⟨by simp [liftLooseBVars, h1, h3], ?_⟩ - exact h4.insert (by simp [liftLooseBVars, h1, h3]) + · rename_i h + exact ⟨(instantiateList_of_bvarBound_le _ vs d (by rwa [← bvarB_eq])).symm, hm⟩ + · split + · rename_i r hhit + exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ + · obtain ⟨h1, h2⟩ := iht d memo hm + obtain ⟨h3, h4⟩ := ihb (d + 1) _ h2 + refine ⟨by rw [instantiateList]; simp [h1, h3], ?_⟩ + exact h4.insert (by rw [instantiateList]; simp [h1, h3]) | forallE ty body bi iht ihb => - intro c memo hm - rw [liftLooseBVarsGo] + intro d memo hm + rw [instantiateListGo] split - · rename_i r hhit - exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ - · obtain ⟨h1, h2⟩ := iht c memo hm - obtain ⟨h3, h4⟩ := ihb (c + 1) _ h2 - refine ⟨by simp [liftLooseBVars, h1, h3], ?_⟩ - exact h4.insert (by simp [liftLooseBVars, h1, h3]) + · rename_i h + exact ⟨(instantiateList_of_bvarBound_le _ vs d (by rwa [← bvarB_eq])).symm, hm⟩ + · split + · rename_i r hhit + exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ + · obtain ⟨h1, h2⟩ := iht d memo hm + obtain ⟨h3, h4⟩ := ihb (d + 1) _ h2 + refine ⟨by rw [instantiateList]; simp [h1, h3], ?_⟩ + exact h4.insert (by rw [instantiateList]; simp [h1, h3]) | letE ty val body _ iht ihv ihb => - intro c memo hm - rw [liftLooseBVarsGo] + intro d memo hm + rw [instantiateListGo] split - · rename_i r hhit - exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ - · obtain ⟨h1, h2⟩ := iht c memo hm - obtain ⟨h3, h4⟩ := ihv c _ h2 - obtain ⟨h5, h6⟩ := ihb (c + 1) _ h4 - refine ⟨by simp [liftLooseBVars, h1, h3, h5], ?_⟩ - exact h6.insert (by simp [liftLooseBVars, h1, h3, h5]) + · rename_i h + exact ⟨(instantiateList_of_bvarBound_le _ vs d (by rwa [← bvarB_eq])).symm, hm⟩ + · split + · rename_i r hhit + exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ + · obtain ⟨h1, h2⟩ := iht d memo hm + obtain ⟨h3, h4⟩ := ihv d _ h2 + obtain ⟨h5, h6⟩ := ihb (d + 1) _ h4 + refine ⟨by rw [instantiateList]; simp [h1, h3, h5], ?_⟩ + exact h6.insert (by rw [instantiateList]; simp [h1, h3, h5]) | proj s i sub ih => - intro c memo hm - rw [liftLooseBVarsGo] + intro d memo hm + rw [instantiateListGo] split - · rename_i r hhit - exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ - · obtain ⟨h1, h2⟩ := ih c memo hm - refine ⟨by simp [liftLooseBVars, h1], ?_⟩ - exact h2.insert (by simp [liftLooseBVars, h1]) + · rename_i h + exact ⟨(instantiateList_of_bvarBound_le _ vs d (by rwa [← bvarB_eq])).symm, hm⟩ + · split + · rename_i r hhit + exact ⟨(hm _ _ hhit).symm ▸ rfl, hm⟩ + · obtain ⟨h1, h2⟩ := ih d memo hm + refine ⟨by rw [instantiateList]; simp [h1], ?_⟩ + exact h2.insert (by rw [instantiateList]; simp [h1]) -/-- The executed `liftLooseBVars` (one memoized DAG walk). -/ -def liftLooseBVarsFast (amount c : Nat) (e : Expr md) : Expr md := - (liftLooseBVarsGo amount {} e c).1 +/-- The executed `instantiateList` (one memoized DAG walk). -/ +def instantiateListFast (e : Expr md) (vs : List (Expr md)) (d : Nat := 0) : Expr md := + if e.bvarB ≤ d then e else (instantiateListGo vs {} e d).1 -@[csimp] theorem liftLooseBVars_eq_liftLooseBVarsFast : - @liftLooseBVars = @liftLooseBVarsFast := by - funext md amount c e - exact (liftLooseBVarsGo_spec e c {} LiftMemoInv.empty).1.symm +@[csimp] theorem instantiateList_eq_instantiateListFast : + @instantiateList = @instantiateListFast := by + funext md e vs d + unfold instantiateListFast + split + · rename_i h + exact instantiateList_of_bvarBound_le e vs d (by rwa [← bvarB_eq]) + · exact (instantiateListGo_spec e d {} InstLMemoInv.empty).1.symm /-! ### `resetMeta` (task #210 Part D) @@ -1292,18 +1694,6 @@ exactness proofs) and `IxC/Kernel/Verify/IExpr.lean` until task #172's interned removal; the cached engine's field facts (`IxC/Kernel/Verify/Cached/Erase.lean`) are stated against them. -/ -/-- The least `k` with `looseBVarsBounded k` (the spec function of the -eager `bvarBs` entries). -/ -def _root_.Ix.Kernel.Expr.bvarBound : Expr md → Nat - | .bvar i => i + 1 - | .fvar _ _ | .sort _ | .const _ _ | .lit _ => 0 - | .app f a => max f.bvarBound a.bvarBound - | .lam ty body _ | .forallE ty body _ => - max ty.bvarBound (body.bvarBound - 1) - | .letE ty val body _ => - max (max ty.bvarBound val.bvarBound) (body.bvarBound - 1) - | .proj _ _ e => e.bvarBound - /-- `bvarBound` is exact for `looseBVarsBounded`. -/ theorem looseBVarsBounded_iff {x : Expr md} : ∀ {k : Nat}, x.looseBVarsBounded k = true ↔ x.bvarBound ≤ k := by @@ -1367,35 +1757,6 @@ that even the fallback stays linear in the DAG rather than the unfolded tree — the standing "no unmemoized traversals in executable paths" rule applies to the saturated branch too. -/ -/-- Memoized `bvarBound` (the saturated branch's exact recomputation). -/ -def bvarBoundGo (memo : Std.HashMap (Expr md) Nat) (e : Expr md) : - Nat × Std.HashMap (Expr md) Nat := - match memo[e]? with - | some r => (r, memo) - | none => - let (r, memo) : Nat × Std.HashMap (Expr md) Nat := - match e with - | .bvar i => (i + 1, memo) - | .fvar _ _ | .sort _ | .const _ _ | .lit _ => (0, memo) - | .app f a => - let (rf, memo) := bvarBoundGo memo f - let (ra, memo) := bvarBoundGo memo a - (max rf ra, memo) - | .lam ty body _ | .forallE ty body _ => - let (rt, memo) := bvarBoundGo memo ty - let (rb, memo) := bvarBoundGo memo body - (max rt (rb - 1), memo) - | .letE ty val body _ => - let (rt, memo) := bvarBoundGo memo ty - let (rv, memo) := bvarBoundGo memo val - let (rb, memo) := bvarBoundGo memo body - (max (max rt rv) (rb - 1), memo) - | .proj _ _ sub => bvarBoundGo memo sub - (r, memo.insert e r) - -@[inherit_doc bvarBoundGo] -def bvarBoundMemo (e : Expr md) : Nat := (bvarBoundGo {} e).1 - /-- Memoized `fvarRange` (the saturated branch's exact recomputation). -/ def fvarRangeGo (memo : Std.HashMap (Expr md) Nat) (e : Expr md) : @@ -1426,13 +1787,6 @@ def fvarRangeGo (memo : Std.HashMap (Expr md) Nat) (e : Expr md) : @[inherit_doc fvarRangeGo] def fvarRangeMemo (e : Expr md) : Nat := (fvarRangeGo {} e).1 -/-- **The loose-bvar bound the checker reads**: the packed field, or — -on the saturated branch alone — the exact memoized recomputation. -Equal to `Expr.bvarBound` unconditionally (`bvarB_eq`). -/ -@[inline] def bvarB (e : Expr md) : Nat := - let r := e.bvarBRaw - if r == satRange then bvarBoundMemo e else r - /-- **The fvar range the checker reads**: the packed field, or — on the saturated branch alone — the exact memoized recomputation. Equal to `Expr.fvarRange` unconditionally (`fvarB_eq`). -/ @@ -1451,140 +1805,6 @@ checks ask them of the block's types. Swapped in by `@[csimp]`, as the memos above: kernel-checked, no trust point, the pure walks stay the specs. -/ -/-- The packed loose-bvar field is `bvarBound` wherever it did not -saturate. -/ -theorem bvarBRaw_exact : ∀ e : Expr md, e.bvarBRaw < satRange → - e.bvarBRaw = bvarBound e := by - intro e - induction e with - | bvar i => intro h; simp_all [satRange, bvarBound]; omega - | fvar _ _ _ | sort _ | const _ _ | lit _ => - intro _; simp [bvarBound] - | app f a ihf iha => - intro h - rw [bvarBRaw_app] at h ⊢ - rw [ihf (by omega), iha (by omega), bvarBound] - | lam ty b m iht ihb => - intro h - rw [bvarBRaw_lam] at h ⊢ - have hb : b.bvarBRaw ≠ satRange := by - intro hb'; rw [hb'] at h; simp at h; omega - have hb2 : b.bvarBRaw < satRange := by - have := bvarBRaw_lt b; simp [satRange] at *; omega - rw [if_neg hb, iht (by omega), ihb hb2, bvarBound] - | forallE ty b m iht ihb => - intro h - rw [bvarBRaw_forallE] at h ⊢ - have hb : b.bvarBRaw ≠ satRange := by - intro hb'; rw [hb'] at h; simp at h; omega - have hb2 : b.bvarBRaw < satRange := by - have := bvarBRaw_lt b; simp [satRange] at *; omega - rw [if_neg hb, iht (by omega), ihb hb2, bvarBound] - | letE ty v b _ iht ihv ihb => - intro h - rw [bvarBRaw_letE] at h ⊢ - have hb : b.bvarBRaw ≠ satRange := by - intro hb'; rw [hb'] at h; simp at h; omega - have hb2 : b.bvarBRaw < satRange := by - have := bvarBRaw_lt b; simp [satRange] at *; omega - rw [if_neg hb, iht (by omega), ihv (by omega), ihb hb2, bvarBound] - | proj s i sub ih => - intro h - rw [bvarBRaw_proj] at h ⊢ - rw [ih h, bvarBound] - -/-- The `bvarBound` walk's memo invariant. -/ -def MemoBInv (memo : Std.HashMap (Expr md) Nat) : Prop := - ∀ (e : Expr md) (r : Nat), memo[e]? = some r → r = bvarBound e - -theorem MemoBInv.empty : (MemoBInv (md := md)) {} := by - intro e r h; simp at h - -theorem MemoBInv.insert {memo : Std.HashMap (Expr md) Nat} (hm : MemoBInv memo) - {e : Expr md} {r : Nat} (heq : r = bvarBound e) : - MemoBInv (memo.insert e r) := by - intro e' r' hk - rw [Std.HashMap.getElem?_insert] at hk - split at hk - · rename_i hbeq - cases hk - rw [← (beq_iff_eq ..).mp hbeq] - exact heq - · exact hm e' r' hk - -/-- **The memoized `bvarBound` walk agrees with `bvarBound`.** -/ -theorem bvarBoundGo_spec : ∀ (e : Expr md) {memo : Std.HashMap (Expr md) Nat}, - MemoBInv memo → - (bvarBoundGo memo e).1 = bvarBound e ∧ - MemoBInv (bvarBoundGo memo e).2 := by - intro e - induction e with - | bvar i => - intro memo hm - rw [bvarBoundGo.eq_def] - split - · rename_i r hhit; exact ⟨hm _ _ hhit, hm⟩ - · exact ⟨rfl, hm.insert rfl⟩ - | fvar idx ty _ | sort u | const n us | lit l => - intro memo hm - rw [bvarBoundGo.eq_def] - split - · rename_i r hhit; exact ⟨hm _ _ hhit, hm⟩ - · exact ⟨rfl, hm.insert rfl⟩ - | app f a ihf iha => - intro memo hm - rw [bvarBoundGo.eq_def] - split - · rename_i r hhit; exact ⟨hm _ _ hhit, hm⟩ - · obtain ⟨h1, hm1⟩ := ihf hm - obtain ⟨h2, hm2⟩ := iha hm1 - refine ⟨by simp [h1, h2, bvarBound], hm2.insert ?_⟩ - simp [h1, h2, bvarBound] - | lam ty b m iht ihb | forallE ty b m iht ihb => - intro memo hm - rw [bvarBoundGo.eq_def] - split - · rename_i r hhit; exact ⟨hm _ _ hhit, hm⟩ - · obtain ⟨h1, hm1⟩ := iht hm - obtain ⟨h2, hm2⟩ := ihb hm1 - refine ⟨by simp [h1, h2, bvarBound], hm2.insert ?_⟩ - simp [h1, h2, bvarBound] - | letE ty v b _ iht ihv ihb => - intro memo hm - rw [bvarBoundGo.eq_def] - split - · rename_i r hhit; exact ⟨hm _ _ hhit, hm⟩ - · obtain ⟨h1, hm1⟩ := iht hm - obtain ⟨h2, hm2⟩ := ihv hm1 - obtain ⟨h3, hm3⟩ := ihb hm2 - refine ⟨by simp [h1, h2, h3, bvarBound], hm3.insert ?_⟩ - simp [h1, h2, h3, bvarBound] - | proj s i sub ih => - intro memo hm - rw [bvarBoundGo.eq_def] - split - · rename_i r hhit; exact ⟨hm _ _ hhit, hm⟩ - · obtain ⟨h1, hm1⟩ := ih hm - refine ⟨by simp [h1, bvarBound], hm1.insert ?_⟩ - simp [h1, bvarBound] - -@[inherit_doc bvarBoundGo_spec] -theorem bvarBoundMemo_eq (e : Expr md) : - bvarBoundMemo e = bvarBound e := - (bvarBoundGo_spec e MemoBInv.empty).1 - -/-- The `bvarB` field is `bvarBound`. -/ -theorem bvarB_eq : ∀ e : Expr md, e.bvarB = bvarBound e := by - intro e - show (if e.bvarBRaw == satRange then bvarBoundMemo e - else e.bvarBRaw) = _ - split - · rename_i h; exact bvarBoundMemo_eq e - · rename_i h - have hne : e.bvarBRaw ≠ satRange := by simpa using h - have := bvarBRaw_lt e - exact bvarBRaw_exact e (by simp [satRange] at *; omega) - /-- The packed fvar-range field is `fvarRange` wherever it did not saturate. -/ theorem fvarBRaw_exact : ∀ e : Expr md, e.fvarBRaw < satRange → @@ -1928,12 +2148,16 @@ theorem abstract1Go_spec {d : Nat} : @[inherit_doc abstract1Go] def abstract1Fast (e : Expr md) (d : Nat) (k : Nat := 0) : Expr md := - (abstract1Go d {} e k).1 + if e.fvarB ≤ d then e else (abstract1Go d {} e k).1 @[csimp] theorem abstract1_eq_abstract1Fast : @abstract1 = @abstract1Fast := by funext md e d k - exact (abstract1Go_spec e k {} Abs1MemoInv.empty).1.symm + unfold abstract1Fast + split + · rename_i h + exact abstract1_of_fvarRange_le e d k (by rwa [← fvarB_eq]) + · exact (abstract1Go_spec e k {} Abs1MemoInv.empty).1.symm /-! ### `lowerBVars` reads the loose-bvar bound, and memoizes @@ -2145,12 +2369,16 @@ theorem lowerBVarsGo_spec {amount : Nat} : @[inherit_doc lowerBVarsGo] def lowerBVarsFast (amount : Nat) (c : Nat) (e : Expr md) : Expr md := - (lowerBVarsGo amount {} e c).1 + if e.bvarB ≤ c + amount then e else (lowerBVarsGo amount {} e c).1 @[csimp] theorem lowerBVars_eq_lowerBVarsFast : @lowerBVars = @lowerBVarsFast := by funext md amount c e - exact (lowerBVarsGo_spec e c {} LowerMemoInv.empty).1.symm + unfold lowerBVarsFast + split + · rename_i h + exact lowerBVars_of_bvarBound_le e amount c (by rwa [← bvarB_eq]) + · exact (lowerBVarsGo_spec e c {} LowerMemoInv.empty).1.symm /-! ### `instantiate1Lift` reads the loose-bvar bound, and memoizes @@ -2357,12 +2585,16 @@ theorem instantiate1LiftGo_spec {v : Expr md} : @[inherit_doc instantiate1LiftGo] def instantiate1LiftFast (e : Expr md) (v : Expr md) (d : Nat := 0) : Expr md := - (instantiate1LiftGo v {} e d).1 + if e.bvarB ≤ d then e else (instantiate1LiftGo v {} e d).1 @[csimp] theorem instantiate1Lift_eq_instantiate1LiftFast : @instantiate1Lift = @instantiate1LiftFast := by funext md e v d - exact (instantiate1LiftGo_spec e d {} Inst1LMemoInv.empty).1.symm + unfold instantiate1LiftFast + split + · rename_i h + exact instantiate1Lift_of_bvarBound_le e v d (by rwa [← bvarB_eq]) + · exact (instantiate1LiftGo_spec e d {} Inst1LMemoInv.empty).1.symm /-- Instantiate the leading `∀`-binders at *open* arguments, returning the residual. Unlike `instPisAt` this uses the general @@ -2719,12 +2951,16 @@ theorem Expr.instLPGo_spec {ks : List LevelParam} {us : List Level} : @[inherit_doc Expr.instLPGo] def Expr.instLPFast (ks : List LevelParam) (us : List Level) (e : Expr md) : Expr md := - (Expr.instLPGo ks us {} e).1 + if !e.hasLP then e else (Expr.instLPGo ks us {} e).1 @[csimp] theorem Expr.instantiateLevelParams_eq_instLPFast : @Expr.instantiateLevelParams = @Expr.instLPFast := by funext md ks us e - exact (instLPGo_spec e {} ILPMemoInv.empty).1.symm + unfold Expr.instLPFast + split + · rename_i h + exact instantiateLevelParams_eq_self (by rw [← Expr.hasLP_eq]; simpa using h) + · exact (instLPGo_spec e {} ILPMemoInv.empty).1.symm /-- Level-parameter definedness is trivial on level-param-free expressions. -/ diff --git a/IxC/Kernel/Ixon/RoleData.lean b/IxC/Kernel/Ixon/RoleData.lean index 5befe2262..d2c4b7d4a 100644 --- a/IxC/Kernel/Ixon/RoleData.lean +++ b/IxC/Kernel/Ixon/RoleData.lean @@ -14,9 +14,9 @@ To regenerate: lake exe IxTests cert-statement # then: RoleEvidenceChecks on the committed file The default table binds every role (`Ix.Kernel.Role`) to the constant of the -compiled Init (sha256 c0b73f5c850fc6732a75e0abb50c2692229ebc7a9f098c0c5940d6bce48983d7) under the role's Lean name, with the +compiled Init (blake3 a24fd146fcf3c5ed6e6a4821f5f122a6d397270f7eb3106bd7b75ff23eeca9a3) under the role's Lean name, with the structural `Nat` operations' `defeq` evidence and, for each well-founded one, -the theorems of `IxC/Kernel/PinGen/Certs.lean` (compiled, sha256 e6601a09cb062ef2869f22abc39b6f53e86501baab5356dfd04fc815c023294c) +the theorems of `IxC/Kernel/PinGen/Certs.lean` (compiled, blake3 4660542d43e8e7de1b9143fbfec4573ae17e3cbedfa11e365cb9280d90ef6a43) closed over the operation's dependency closure and written as theorem records. Every bound constant's record, the evidence and both literal capabilities were checked by the verified fold through the Ixon reader under @@ -63,7 +63,7 @@ every regeneration. -/ namespace Ix.Kernel.Reader.RoleData -def source : String := "sha256:c0b73f5c850fc6732a75e0abb50c2692229ebc7a9f098c0c5940d6bce48983d7 sha256:e6601a09cb062ef2869f22abc39b6f53e86501baab5356dfd04fc815c023294c" +def source : String := "blake3:a24fd146fcf3c5ed6e6a4821f5f122a6d397270f7eb3106bd7b75ff23eeca9a3 blake3:4660542d43e8e7de1b9143fbfec4573ae17e3cbedfa11e365cb9280d90ef6a43" def table : String := "e80303670035e1cc809f6f076521a43f85068d5592220407c0532b6a08952f40d8523d04bc01eeb2e6268ff6d0e6b2e9764d9940b81bb64b9f8f4b1358d294ff9817e6941a460242d6ed31536f0958176f1dc973383808ac8583223746ebca78dbe389b65321a603c8322e03d558f85a8edc7fe68524013880ad0bbcb7dc890136d247154b23099204e6eba3c8b4d19f6a1076b39fa89aec61dccbb960f83d9a62e6acf35a69c9a0a405dda12bcb330727f6dfb816bc9752aabd0520e6515b79fc8a5a9e713866f4c63e06a29a636176cf1135d077eb074798f9007c78e7801383e9cff363bae5edf0576207b0b10db287012d15b4694e76b789c12ec5e41efc9db8a663cfd754bf2fc4971708c37d6290e00fb865fb5db7bce18fd69e6d1674ad573724b85466ea9f7578a20709d494dc601386ba8e8f0b711fe62a2d60ab60c6488c8cf6ce97796ebc3ad5fefb0afb03f5a7e141d84025b3a503dedc53ce86f297bde36bbc1b98687aeab43599680b2dfc16af01b82b3b91c2ff704409d76236a83f956c0c6e6659a64fe21d76695b0cdd9285ac45d4ac85d17fa0d3243d7909c782a2a276ee3fb0cffefa9afbdb0de10d2def8c9dc22f62e43f0f7c5936e2418d476e0fcbbcab780ef1cda642a15bf45f0e0ac506ba734d180963584489282a1cc047c14b04e9c5634e741d04b3780ab82a0fde82a8328a72c2c3727fb715ce2cc81e3583d83a71e7f0d0250f5318f9ff801e10a1236045140ac2abbe152958348434fb84496596f9fc5dcbbd585b8174c4392711adde8852949530b0f56369b0e53e223abd2f23a55833bdfd994104bde6686ae912e35ab17358fae7a81efb3d94a164fd3f85ab8b0c888aaa071cfe0d60ffbc1cac13f4bc2a9e8e268eb352f719657d8806b072a555a29f2eb85e72805fddc9cd019114b288d9e0242cff29788b9eb72236dea50f70905947a92325e7a27b25f0a9ce8615cf32039cedb19bb4aa85e696d12585bb0792e9e55a6fcf53b6bb0e24f6198cc816ce844ac2a7e94ecda2c456f143f6fee0eda191421777e19fd29ef20822f1f5bb170455da832c67f841d30b299a79e693f343abf0ddbf471c4abe76037b2c5b79b618694a19361f0bca20ed2852807e6b3eb8322afa727a6781f9e9f875869e14b69f192e29819d1b018d97d034e2c108fe5a5ba5a256afef2287c4db1e216de2fc5f501a5e7aced05ef9d9aae03d24d221ba8a1ed44309d8dd290179f64f75a310fc01e21b4d370b804541dc206ed10ed1f1bdf6d24e34938794c8a551f18aee6c77366e021cd293dfc5e466be1692b75bc5f77b62bc8d823314e8b514f4d6168989a8699b5c1d4fb6b41c30532e6ab4acb4eefe01da1314ee7f2129af8989fd63f7ec277aaf011e3994bc51e3686f30a1d483d69d0a835a7b484d892393c084f87a5849de54f3021fb3a7d80249fd823100ee03626232d1412a0e8335275d5df53b92943887b650f72045040b3126855169dad919a970a9c72cee5875658aedb3bd1f89fcdf8c88edfa21811600dfa59f3612cb03c0f0475af152399defff9226c6e19fd648041a6899a92286b365e75bb4230f04c6e4f3a7ca6988adf574616d79a3d7477ad65cac102110231745491c0d651de0bea589d5fffbc9bc4d7fbe897c1d805b1f7f59ca9696a6ef249b2874e754d995db8d27868f8100eb4d99c07cc2c8bfd099bb1e2986d0a6784a25221625a00ee2d6b96297e34d7e05bf7e4fd38d8d99d3e1d64a7cd2e06ca0752d2645dcb3c5e5ada3bc0ca52d62a66f7f9dda3507df71061a5c200adea0cf24101427065fc8b41393f525c1e541b9d4bc97c20c13bf23a7209a2093b6f4527330114a28cff875914f3f8b4f0014d1eaa223c6b9da7201239bc5976e140b69d99cd1f357293244687301d779ec757274761d903def3eb3135177327b709bcbd854ef496f792a3e8aadcc611c4d8677cadf00c78506561d33738ee3d571f7a4196e260580c9b82b03b3a041285b0fe202d90b227d405c4cec252df18ba7616acd6dae2792e672bb2c373b7d74f6b40da11b7ed79603a45ec7011a8831b720b57d2efe92e82a3d57f02d5baaf05c969d2cf377312111be51bb64c600c13fa164ee722e92243b0f771bab2ed3d8ff924af9dc4b5cd869760615b3e7ee80916bb617259d45890c69f41283372f9e445b9d5f8252772347872e909b5db4e7eca7086cedf4fec6de840f6b7b5f9230824d1c53d8c1c3cf0a27f24639ec2be1ecc13d4a01c993171595edccf69497373111abbf986481c53a21c8cb21ee2581ae6238aedbec15f972959d086bef1a198532aba94bfb41351d4db36172c379a512f04219ef21dc5454d71590967d4e8f913f33de8c100c22f82a5c3533c67e4370671813434a6fec5d023998ded090df4a49343493afc1cd298c63606e5f468d1e5989ab4680d256e95d07a8642a88b5c6b858e13577dd40d7b6ca310cc59466d1f24df4f180ef50e1b00fd850698458cd26901c5d3605bb657e8b9d5dc224e7ef3d53e54153ce3ac26da8d1f1b1d61069a3726566cd3725019ff485fcf5709597cd1e07fcc3ce796776ef3f715a6d187eb5831c63b4be38209c96511648d09459019701f6e1005409320a710a3cfd1895e5672134161e3439cfa39364a87c7561ae41e5093915e97ccced20c5907ef7f67c02ac7029423c403a16bba1a1afac7ff7048ba993de3e369f415ff0f1387e418a74e8bb940b2589943bf0bef055f03b4aa6c39b798181fc69ff5e3213515cb8ded34a2f16f6a23ea45f3c363d071182be414e1a58599b83f5e19f9b873723054f4287ba69705aafef27da3d8cc0e1360c1b29108dab3d6172e7fce8a9aa9640daa4bbe99ba1305d9623624b3e03618c4fb6cae9e740ca1998d0e8638600c8c25d169c96dae4e1bb46c9a8b27a3fbd7e9c85d35b4c18d6960d8a717ac38f7ebdca29cb79348fbfcbf268dd5f2cd840c683fbd7b8b2fcc5eb09ff5e16f9a4a76f0c5d04cd5d8df23db49702d5597bc34111157bc3898f06e3e6f2ca83411efd0d43b8846b57d2f6088e18f3447ddc3b28428f70b4a0ae9874c133f4b3d32433111988ab4b3dc1a47a6be6f6c341a7dbb0af43f1de7103802ebc5309041bdaadea4a65652d54af37e66aaaf41787b1d8919557447bb659876671e03d0731c456e894ce9e6f7de8250ab4ae70113dc08e2453212745af586b07546e1fca9ab0780d1c236af8d360c4c6bb32bb0cd03ec9ca63d3da9646c60c95e3cdbf4ed010d999b3277f4e1fd3695e8c30260876102905d7b36241a2470a99f49cc73ab9f42783092e7120613225670bab89b9d63533d1adefc876182f48f7130d5864e1ba77b0f6a7338f92159c35bd5147e0febcf1ebed456e513538cc499a3720bdd0efea88c565c704c00e99b5d6616a75a1b67e4be47b12a774045e324af908f29917a2bc16df858bac311926b4c75ca206239295bf41f999c6e3294aef4b19c87649f9a17809f248beff7bc37f569f548e24a98d8a7cddd5b82eb808e5a64cb666925b10473fbe1a7630ec496fe07d799c5248667db1e2302fca5fe4c50cb64d7c60043d6016e2f0ef5d764e1ab7923968d43d5c388186ddf6c83c28f024017a4e9dff949e4d0282393213042a36a69af4b71a58709096eaa3de5130bae0af84f34f01e7c724508897e01e5d0b8f72e2cfa2607cea5ff0d352d008d0e7512aab2f465053e404f5dd3c53693bc3b5bfae582b0496ec3c05af0cee0a46c38ab662c57fdf5113c97e84fb8ac3ee8120e923d61fca6f7e6bb9241ec3aa5d359ba5dce542aa1152a28958d747e87b5a60e4fa07ef7ed8d949f69adcbc44afe2958482d3ee9b33655390022ae4c5d52ad28acd80051dd41f27f51c9c4afbab1bb391773c19ef0dc1e95467160b97679306d2820445383a6d740e6fe9839c2545ea48680ff2a77b0be98655fade2dbfb1d7f77c30c247d52df128bf516eedce01f817d363ce88da13cfac2b562f9c9c369045036d9a35ff7b38285dca3b976426c81a8f366fce1161b090d60d57da7bb331e098f9bf5cdabcb9609a7e953dffc609ad9d90871a14c016c52a3011589ade18f9475f50bbdad1c3d3e206c7b116a985264bec2323df1151e6c6353c05594da661917a58152f5ca974d2256d98efa9d722c4f16382e9cdf016df4c5743685ade013024c598e5f0c3dcd067f506e1d2e32749573522acb539b61400627cf8975bbb94d9f9475ba141a037dddbc665e907506b87bfc7267d9e0f7ec173a21709df5c0eae69f3f8b198d1ffac67b9e88d39526b1b039ff519cd87eec106f63974860c5d717463825c552702ba1908b9fa5d8f3f7786d7730c633e41d8d380939feeb7485e96bb3eb63527bce3a69fe0813a131e4742c92d956035b73fab2e76a6cb95c7c65f35a9ed7202e3c43857cdc4063eac123fe7b77f0631154ac540dba2c997bdf224601710034a517f249f5c8265e8e2783be60619f0c8e85f4eab625e85983abd722e615318877ae61e1da37980d80c11e3e47a36d850c3e43e99716122f1e6eff01e9f62d0380dc9b63afeeb3235a77adeb355a8b52b0730971595093741ec2c6d57cd4963209aca62cc1f9dd2c0a928cd84d72bb41570aa97c1fd833e5fffc567c1c6401f648713fb91441f3f93ea786dc4cfb7064cd78ea940f47c4f89ee9dba9e6164d146654c086826cc679df3b6aa57a29f3d093a8a92d0de4edecef46a908d8398733dc0666a47d4c02a7bd096323488f1c115081fb0dfe8fc9651751de02e00ca6e0a68f12c290200002a0200002b0200002c0200002d0200002e04000000002f030000003003011c417f7daf28f407c13f6dcfa9d703af5e89d512f47160296d98e7b792f3ffb00148b5cf19fedc4ab4f8bd95c44b3c434ea7dc597edb7865aee243e3a7d4e49ae00108b25ddc5a2df7f551bb82886298733668381bd4e331cdc719ade4f1dc4479c83103016f2da8037e3dfeab14c687cc1933875c721db63d6cb23614dfc9b52b084b54bb01fedea646d23ce5317c487b1f82eab0eb957612fd47ea882975ec579bd3a06bc2017d721749d3ac4ed8ecd7c0c853b677e3276db34cee04604ff1120e34daa210dd32020150c1f2dc08b785b68df76180ef96679b16fb8806abda514c3e6e4de22442cc1c014219f7fa9b5efb83f045b3f5aa6f7f96eb5621fa0684043424a5a619c610c9d3330201d523e3a028ff5f3971212c72468425d133f984a9c2900d0677ad781a79bfc025012e8a179005270d9dec9bd454cb617b47c60ee17983aaecdc82e3b2bfd5b40a5a340201797dd6e0af6cce6585c7fcc480533422ff27febde1d9544504ef5fed8e616dac014683c3596dfe3937e234ab117bcfd04b1933753837982281970bdf2097080c483502016980e7c19ce8aa2773124048a230b4d7788d228cd453d20a331c971c73b16c2b011d39e42530c2b49ada166e790e8b6ab4aca8f6f96a80347d29281edd8a3c1cc53602018abca5cde56513d57ebaf0f26cedcc0ce5eb1be4be902e8e7645d75378073ab101f6e7b9827b2b76e9836ce4d5bd95813207607f1a22d31c961cd386110e53f036370201a3e90cbb497a884a906ffc99a830f94c19f17f1223e14fd65e5b1634ba740a6001351abae316c51d280f76ca5d7cdbb09238338616dbd1085d0bcfc2591ee42ad44103000001890022f84c9925b9e9e645fa2163a83fce44c29fe2831b9b6eddbda0c91559fb4301010f17985002d4da85cad8c34280a8c3e1d81d199a9db5f9c7b526ac7875503372440101df5cb72ba577d59191b81468b214b4d43e74c787669e308c84d476782ce7113b450100490200004a02014a52d4a68c46c89fb2e1019a029512f27116277bc93fc583bb4f09eef1cd858801d9a232456141ccb6ceafc734d2e65551391b6ad818087eda87935df9a1b387cc4b0200004c04000000004d01004e04000000004f0200005005000000000051040000000052030001feed63f507026cc73fe45a29fb841911294a403142603e9157cd79cbb75ed0c40152e9d0238da796eed701c67955e2dae21bb1ea4270f4fdb91446a3d3b6a8ab99550100560101ae85634619fe2c9aeb475385452893aec8c58cf1c4f204f9cfe0ef6c829def1b570101997ec1247b679671aeb61a20422b468c4c0f5db64330ddd612560326b884168c5a01005b0200005c01017a3ed903486f2fb69859716f43f901c35bb1f405f6d543203b67df76afe00b165d0200005f040102def122161cae500f77f00d8a800e4e9c484b6f03a90dbb09e34a213b65f04d000001a9598c0804d07cb918b00917054796f64c5c02c399fbf73bb12805b537b54eb8600401d1ac828e910547a3e3d00481ea9113932a006ea5559889236947b79a8d1d12630141d8ede500c60393f696de42cd6fb2b24d34976074f07c790d4f47b8cbf966cd0001cdafc2a66f6f9097c973e7d394ab317e67ef6758ba50b60491134a4c5cf43aac6101006201006301006405010f5af18bb41907b91da24fe965bc4201c165894386231cdc23db50cb246e2fd601452646430af20d12eab3a4a3f972b6fa76028e1c92e132f3e783e8bf18c13545017658f1f764f3c745cdc9f9ccde1f608f7c012842d1e271e105f126b9c09ac60001fc9ef6f6e30ec70b06366c0df928eef7c097c526e986f01364a87946e2d00ed301905b30b547d4464a4a8b66b81314acccb92759406e2e744946ef52854ac94ddd65020001619d9161a7a7ff3e8e981e41b0ccf5fed0936c3caf2dd31737f7761c54955a9266030001551dd19d30cce62a588116107b1dfd3be35a8a05619b8248cce9b53726c7fcd001a18c84b6d67730221c34f5b169c4aab7b82032509ab9b2b494b97af22b5fd1a1" @@ -10014,7 +10014,7 @@ def closureHintsChunk2 : Array (String × String) := #[ ("bf13f759fec573674ad41b5c45e3ccddfc503cfd9c102432912a86cb22dc8203", "opaque"), ("b069ff754e2c31591cbd2e3c1fd062620d35a8dba86eb2169f88b2bdab117b64", "opaque"), ("b6c1a2ed611e94322161fc6eeabc6634102ecca98c614d5bac652b51d8a0c021", "opaque"), - ("f064c5ea422cbe6f7547539a9f597a8a7aaa2ed1ea254548541bb08f9409614a", "1"), + ("f064c5ea422cbe6f7547539a9f597a8a7aaa2ed1ea254548541bb08f9409614a", "abbrev"), ("e19026d0d2eb838a5ade3d8e2622cc23f2a63e5c0627db923361313354e1ddf6", "abbrev"), ("ef62adaad6d02b9f512d24ec1716c73bff7ae08d8ae45fe330e09925a5ab93a5", "abbrev"), ("f2d8066e64d58fda0af26afb16251d1d96c06ee02e14381ec275c755a00db278", "opaque"), @@ -10943,7 +10943,7 @@ def closureHintsChunk16 : Array (String × String) := #[ ("62c98c1b45d2c37d87671a13098c8ec4a07afe103472e2184ec0816f932671c0", "7"), ("88b3c58629661cf33ddd043adf0fa6d9cf752281adde18239575fc79c5c7a2f4", "8"), ("60a7b973c687468fbeb9c529aa2ad51712f65ae97e67f2c1638552c919a8f43c", "abbrev"), - ("5d991cb8747fcd5f255118eac9411f2ff033a4e5e24cabc60b6533feb9fbdb8c", "1"), + ("5d991cb8747fcd5f255118eac9411f2ff033a4e5e24cabc60b6533feb9fbdb8c", "abbrev"), ("2e06d5a9bc88a8d2232791bf0498e4c2709ab6dfec772d02a2d52c4b7ef563d1", "1"), ("ee4debf2602b04f9e7edf578056d5cc311cacf0b9214a74fca7d9c38f617993a", "opaque"), ("aa835909ff4818c774c8b342fde9ffc05b35b86ffa5ae4f26011b2ae3388f976", "abbrev"), @@ -11873,7 +11873,7 @@ def closureHintsChunk30 : Array (String × String) := #[ ("7ebabc68fa1c6a0beac2d8a0ce228cd635c9deacb5cf42ec75081160935c7021", "opaque"), ("389a4c4f988904de13ae564de433a55b54f41da0d16cc0ec05476bb785601ad1", "opaque"), ("a85db711fbb273431f46476419cb9ae51d9f45227fe3daaa3ca90e118f90bbae", "opaque"), - ("bfbc76ae4ebfa93fd56ae0627f0d48a6b780f8fe45ad5d59922117ba8237c8c3", "1"), + ("bfbc76ae4ebfa93fd56ae0627f0d48a6b780f8fe45ad5d59922117ba8237c8c3", "abbrev"), ("138244a5b9f149228979894c5e0070ceacf99409590846d83c989603f14c1d6f", "5"), ("f32bbef91fc1d5914daaba9cab479b415516e754e51ad0078871c013565d7b75", "opaque"), ("a70beeced7cf9cb088941c5c25a5c176f5a5e671a5b1ca5bb97d8f8f93f00bab", "opaque"), diff --git a/IxC/Kernel/MainTheorem.lean b/IxC/Kernel/MainTheorem.lean index f1b2934c1..b0dc664fd 100644 --- a/IxC/Kernel/MainTheorem.lean +++ b/IxC/Kernel/MainTheorem.lean @@ -2,7 +2,7 @@ module import IxC.Kernel.Verify.Cached.MainC public import IxC.Kernel.Denotes -public import IxC.Kernel.Cached.Installed +public import IxC.Kernel.Cached.Policy import IxC.Kernel.Model.Denotes public section @@ -64,15 +64,19 @@ theorem model_exists {md : Mode} [md.Certified] (V : Type w) [SetTheory V] obtain ⟨m⟩ := Cached.checkDecls_sound (V := V) rfl accepted exact ⟨Model.Model.ofEnvModelM m⟩ -/-- **Conditional model existence**: every environment accepted by `checkDecls` -has a model in `V` realizing all custom user axioms in `env.customAxiomInfos`. -/ -theorem model_exists_conditional {md : Mode} [md.Certified] (V : Type w) [SetTheory V] +/-- **The main theorem for the fold that admits user axioms, without them.** +An accept of `checkDeclsWithUserAxioms` from records that declare no user axiom +(`userAxiomsOfDecls`, the `.axiomDecl`s under eligible keys) has a model in +every set theory: over such records the fold is `checkDecls` +(`Cached.checkDeclsWithUserAxioms_eq_checkDecls`). An accept that admitted user +axioms is conditional on them, and no model is claimed for it here. -/ +theorem model_exists_of_no_user_axioms {md : Mode} [md.Certified] (V : Type w) [SetTheory V] (ev : Evidences md) (ds : Array (Declaration md)) (env : Env md) - (accepted : checkDecls .verified ev ds = .ok env) : - ∃ M : Model V env, AxiomSetRealized V M.cval env env.customAxiomInfos := by - obtain ⟨M⟩ := model_exists V ev ds env accepted - refine ⟨M, fun cv hcv => ?_⟩ - exact M.realizes_of_mem_consts ((Env.mem_customAxiomInfos_iff.mp hcv).1) + (accepted : Cached.checkDeclsWithUserAxioms .verified ev ds = .ok env) + (hnone : userAxiomsOfDecls ds.toList = []) : + Nonempty (Model V env) := by + rw [Cached.checkDeclsWithUserAxioms_eq_checkDecls .verified ev ds hnone] at accepted + exact model_exists V ev ds env accepted /-- **Consistency of accepted environments**: every environment accepted by `checkDecls` is semantically consistent (`Consistent env`, i.e., contains no @@ -83,14 +87,4 @@ theorem checkDecls_consistent {md : Mode} [md.Certified] (V : Type w) [SetTheory Consistent env := consistent_of_model V (model_exists V ev ds env accepted) -/-- **Conditional consistency under custom user axioms**: any environment `env` -with a model `M : Model V env` realizing its custom user axioms -(`AxiomSetRealized V M.cval env env.customAxiomInfos`) is semantically -consistent (`Consistent env`). -/ -theorem consistent_of_userAxioms_realized {md : Mode} (V : Type w) [SetTheory V] - {env : Env md} (M : Model V env) - (_hax : AxiomSetRealized V M.cval env env.customAxiomInfos) : - Consistent env := - consistent_of_model V ⟨M⟩ - end Ix.Kernel diff --git a/IxC/Kernel/Model/AxiomPin.lean b/IxC/Kernel/Model/AxiomPin.lean index 0910d2a5c..3c4e977c5 100644 --- a/IxC/Kernel/Model/AxiomPin.lean +++ b/IxC/Kernel/Model/AxiomPin.lean @@ -183,37 +183,4 @@ theorem axiomStd (hμ : μ.verifiedChecks = true) rw [if_neg hn, if_neg hn2] at hok exact nomatch hok -/-! ## Custom user axioms (conditional on semantic realization) -/ - -/-- **Semantic realization of a custom user axiom at the P model tier.** -A custom axiom `cv` with annotated type `type'` is realized over `mp : EnvModelM V μ env` -when its installation `.axiomInfo ⟨cv.name, cv.levelParams, type'⟩` extends `mp` to a -valid model `EnvModelM V μ ⟨.axiomInfo ⟨cv.name, cv.levelParams, type'⟩ :: env.consts⟩`. -/ -@[expose] def CustomAxiomRealizedM (_mp : EnvModelM V μ env) (cv : ConstantVal md) (type' : Expr md) : Prop := - Nonempty (EnvModelM V μ ⟨.axiomInfo ⟨cv.name, cv.levelParams, type'⟩ :: env.consts⟩) - -/-- **Custom propositional/point-realized user axiom branch**, discharged via `harvestAxiom`. -/ -theorem axiomCustomPt (hμ : μ.verifiedChecks = true) - (mp : EnvModelM V μ env) {cv : ConstantVal md} {type' : Expr md} - (hcv : ConstantValRun μ F env cv type') - (hmem : ∀ (ψ : LevelParam → Nat) (ta : AnnotTerm), - denoteMeta mp.base2.acval env ψ 0 type' = some ta → - ∀ (ρ : Nat → V), interp V ρ (.const .propext []) ∈ˢ interp V ρ ta) : - CustomAxiomRealizedM mp cv type' := by - refine harvestAxiom (V := V) hμ mp hcv - (A := fun _ => .const .propext []) (fun _ => trivial) - (fun _ _ => rfl) (fun _ _ _ => rfl) (fun _ _ => by simp) - (fun _ _ => by simp) ?_ - intro ψ ta hta ρ - exact hmem ψ ta hta ρ - -/-- **The custom user axiom branch, discharged conditionally on `CustomAxiomRealizedM`.** -/ -theorem axiomCustom (_hμ : μ.verifiedChecks = true) - (mp : EnvModelM V μ env) {cv : ConstantVal md} {type' : Expr md} - (_hcv : ConstantValRun μ F env cv type') - (hreal : CustomAxiomRealizedM mp cv type') : - Nonempty (EnvModelM V μ - ⟨.axiomInfo ⟨cv.name, cv.levelParams, type'⟩ :: env.consts⟩) := - hreal - end Ix.Kernel.Model diff --git a/IxC/Kernel/Model/Fold.lean b/IxC/Kernel/Model/Fold.lean index 523f2a94b..0d371fff8 100644 --- a/IxC/Kernel/Model/Fold.lean +++ b/IxC/Kernel/Model/Fold.lean @@ -87,28 +87,6 @@ theorem axiomStepPB_of (hμ : μ.verifiedChecks = true) : (AxiomStepPB (md := md · exact axiomStd hμ mp hcv hok · exact axiomSkip mp -/-- Conditional axiom-step hypothesis: every custom user axiom encountered in the -stream is semantically realized over the current prefix model `mp`. -/ -def CustomAxiomsRealizedPB (V : Type w) [SetTheory V] (μ : CheckMode) : Prop := - ∀ {F : Nat} {env : Env md} (mp : EnvModelM V μ env) - {cv : ConstantVal md} {type' : Expr md}, - ConstantValRun μ F env cv type' → - cv.name.isCustomAxiomEligible = true → - CustomAxiomRealizedM mp cv type' - -/-- Conditional `DeclAxiomRunCond` step: given `CustomAxiomsRealizedPB`, every -standard, tolerated, or custom user axiom step preserves `EnvModelM`. -/ -theorem axiomStepCond_of (hμ : μ.verifiedChecks = true) - (hax : (CustomAxiomsRealizedPB (md := md)) V μ) - {F : Nat} {env : Env md} (mp : EnvModelM V μ env) - {cv : ConstantVal md} {env₂ : Env md} - (hR : DeclAxiomRunCond μ F env cv env₂) : - Nonempty (EnvModelM V μ env₂) := by - rcases hR with hStd | hCust - · exact axiomStepPB_of hμ mp hStd - · obtain ⟨type', hcv, _, _, _, _, helig, rfl⟩ := hCust - exact axiomCustom hμ mp hcv (hax mp hcv helig) - /-- The basis kind's whole step, routed (basis tier). -/ def BasisStepPB (V : Type w) [SetTheory V] (μ : CheckMode) : Prop := ∀ {env : Env md}, EnvModelM V μ env → diff --git a/IxC/Kernel/Roles.lean b/IxC/Kernel/Roles.lean index 266efc63e..0f4b70c01 100644 --- a/IxC/Kernel/Roles.lean +++ b/IxC/Kernel/Roles.lean @@ -1331,11 +1331,6 @@ def Declaration.anchorMismatch? : Declaration md → Option (Key md) /-! ## Custom user axiom eligibility and extraction -/ -/-- Whether `k` is one of the standard axioms (`propext`, `Classical.choice`, -`Quot.sound`) or the tolerated `sorryAx` anchor. -/ -def Key.isStdOrAnchorAxiom (k : Key md) : Bool := - k == propextName || k == choiceName || k == quotSoundName || k == sorryAxName - /-- Whether `k` is eligible as a custom user axiom name: an `.id` key that is not in `reservedBasisNames`. Role keys (`.role`) and compiler/model auxiliary keys (`.aux`) are never eligible as custom user axioms. -/ @@ -1427,31 +1422,4 @@ theorem mem_userAxiomsOfDecls_iff {ds : List (Declaration md)} {cv : ConstantVal refine ⟨.axiomDecl cv, hd, ?_⟩ simp only [Declaration.userAxiom?, helig, ↓reduceIte] -/-- The custom user axioms stored as `.axiomInfo` entries in `env`. -/ -def Env.customAxiomInfos (env : Env md) : List (ConstantVal md) := - env.consts.filterMap fun - | .axiomInfo cv => if cv.name.isCustomAxiomEligible then some cv else none - | _ => none - -theorem Env.mem_customAxiomInfos_iff {env : Env md} {cv : ConstantVal md} : - cv ∈ env.customAxiomInfos ↔ - .axiomInfo cv ∈ env.consts ∧ cv.name.isCustomAxiomEligible = true := by - unfold Env.customAxiomInfos - rw [List.mem_filterMap] - constructor - · rintro ⟨ci, hci, hcv⟩ - cases ci with - | axiomInfo cv' => - dsimp only at hcv - split at hcv - · next helig => - injection hcv with hcv' - subst hcv' - exact ⟨hci, helig⟩ - · exact nomatch hcv - | _ => exact nomatch hcv - · rintro ⟨hci, helig⟩ - refine ⟨.axiomInfo cv, hci, ?_⟩ - simp only [helig, ↓reduceIte] - end Ix.Kernel diff --git a/IxC/Kernel/Semantics/DeclRun.lean b/IxC/Kernel/Semantics/DeclRun.lean index e6249cf8a..ff5c8810f 100644 --- a/IxC/Kernel/Semantics/DeclRun.lean +++ b/IxC/Kernel/Semantics/DeclRun.lean @@ -225,41 +225,6 @@ def DeclRun (μ : CheckMode) (F : Nat) | .type => DeclBasisRun env .quotK env₂ | _ => env₂ = env -/-- Run/guard specification of a custom user axiom declaration: the constant's -type passes `ConstantValRun`, its name is eligible as a custom user axiom -(`Key.isCustomAxiomEligible`), it is not a standard or anchor axiom, and the -annotated constant is installed as `.axiomInfo`. -/ -def DeclCustomAxiomRun (μ : CheckMode) (F : Nat) (env : Env md) - (cv : ConstantVal md) (env₂ : Env md) : Prop := - ∃ type', - ConstantValRun μ F env cv type' ∧ - let cvA : ConstantVal md := ⟨cv.name, cv.levelParams, type'⟩ - stdAxiomOk env cvA = false ∧ - cvA.name ≠ propextName ∧ cvA.name ≠ choiceName ∧ - cvA.name ≠ sorryAxName ∧ - cvA.name.isCustomAxiomEligible = true ∧ - env₂ = ⟨.axiomInfo cvA :: env.consts⟩ - -/-- Run/guard specification of `.axiomDecl` when custom user axioms are permitted: -either the standard `DeclAxiomRun` holds, or `DeclCustomAxiomRun` holds. -/ -def DeclAxiomRunCond (μ : CheckMode) (F : Nat) (env : Env md) - (cv : ConstantVal md) (env₂ : Env md) : Prop := - DeclAxiomRun μ F env cv env₂ ∨ DeclCustomAxiomRun μ F env cv env₂ - -/-- Per-declaration run relation when custom user axioms are permitted. -/ -def DeclRunCond (μ : CheckMode) (F : Nat) - (Ind : List (ConstantInfo md) → Nat → Env md → Prop) (env : Env md) : - (Declaration md) → Env md → Prop - | .axiomDecl cv, env₂ => DeclAxiomRunCond μ F env cv env₂ - | d, env₂ => DeclRun μ F Ind env d env₂ - -theorem DeclRun.toCond {μ : CheckMode} {F : Nat} - {Ind : List (ConstantInfo md) → Nat → Env md → Prop} - {env env₂ : Env md} {d : Declaration md} - (h : DeclRun μ F Ind env d env₂) : - DeclRunCond μ F Ind env d env₂ := by - cases d <;> first | exact h | exact Or.inl h - /-! ## The projections, retired (2026-09-05) `ConstantValR.toRun`, `ValueFrontR.toRun`, `DivModPinR.toRun`, diff --git a/IxC/Kernel/Verify/Cached/MainC.lean b/IxC/Kernel/Verify/Cached/MainC.lean index bd37eacac..66a690d13 100644 --- a/IxC/Kernel/Verify/Cached/MainC.lean +++ b/IxC/Kernel/Verify/Cached/MainC.lean @@ -1,6 +1,7 @@ module public import IxC.Kernel.Verify.Cached.InstalledC +public import IxC.Kernel.Cached.Policy public section /-! @@ -78,25 +79,38 @@ theorem no_proof_of_False_cached (V : Type w) [SetTheory V] obtain ⟨mp⟩ := checkDecls_sound (V := V) hμ h exact fun c hc hty => no_constant_of_False mp c hc hty -/-- **Conditional acceptance**: what the fold accepts carries the model -whenever all custom user axioms in `ds` are realized. -/ -theorem checkDecls_sound_conditional (hμ : μ.verifiedChecks = true) +/-! ## The fold that admits user axioms + +`checkDeclsWithUserAxioms` (`IxC/Kernel/Cached/Policy.lean`) also admits user +axioms, and an accept is conditional on them. These letters are what holds +unconditionally: over records that declare no user axiom +(`userAxiomsOfDecls`), the fold is `checkDecls` +(`checkDeclsWithUserAxioms_eq_checkDecls`), so its accept carries the model. +(The letters these replace, `checkDecls_sound_conditional` and +`no_proof_of_False_cached_conditional`, were about `checkDecls`, with a +premise of type `… → True`.) -/ + +/-- **Acceptance without user axioms**: what the fold that admits user axioms +accepts from records that declare none carries the model. -/ +theorem checkDeclsWithUserAxioms_sound (hμ : μ.verifiedChecks = true) {ds : Array (Declaration md)} {env' : Env md} - (h : checkDecls μ ev ds = .ok env') - (_hax : ∀ cv ∈ userAxiomsOfDecls ds.toList, .axiomInfo cv ∈ env'.consts → True := fun _ _ _ => trivial) : - Nonempty (EnvModelM V μ env') := - checkDecls_sound hμ h - -/-- **Conditional letter about `False`**: no constant of type `False` is stored -in an accepted environment when custom user axioms are realized. -/ -theorem no_proof_of_False_cached_conditional (V : Type w) [SetTheory V] + (h : checkDeclsWithUserAxioms μ ev ds = .ok env') + (hnone : userAxiomsOfDecls ds.toList = []) : + Nonempty (EnvModelM V μ env') := by + rw [checkDeclsWithUserAxioms_eq_checkDecls μ ev ds hnone] at h + exact checkDecls_sound hμ h + +/-- **The letter about `False`, without user axioms**: the fold that admits +user axioms, running a validating mode, never accepts records that declare +none in which some stored constant has type `False`. -/ +theorem no_proof_of_False_cached_userAxioms (V : Type w) [SetTheory V] {μ : CheckMode} (hμ : μ.verifiedChecks = true) {ds : Array (Declaration md)} {env' : Env md} - (h : checkDecls μ ev ds = .ok env') - (hax : ∀ cv ∈ userAxiomsOfDecls ds.toList, .axiomInfo cv ∈ env'.consts → True := fun _ _ _ => trivial) : + (h : checkDeclsWithUserAxioms μ ev ds = .ok env') + (hnone : userAxiomsOfDecls ds.toList = []) : ∀ c ∈ env'.consts, c.toConstantVal.type = .const falseName [] → False := by - obtain ⟨mp⟩ := checkDecls_sound_conditional (V := V) hμ h hax + obtain ⟨mp⟩ := checkDeclsWithUserAxioms_sound (V := V) hμ h hnone exact fun c hc hty => no_constant_of_False mp c hc hty end Ix.Kernel.Cached diff --git a/IxC/Kernel/Verify/Cached/PolicyC.lean b/IxC/Kernel/Verify/Cached/PolicyC.lean new file mode 100644 index 000000000..339b6dd9f --- /dev/null +++ b/IxC/Kernel/Verify/Cached/PolicyC.lean @@ -0,0 +1,103 @@ +module + +public import IxC.Kernel.Cached.Policy +public import IxC.Kernel.Verify.Cached.AgreeFloor + +public section + +/-! +# The strict fold is the user-axiom fold without user axioms + +`IxC/Kernel/Cached/Policy.lean` proves that, over records none of which is a +user axiom, the fold under any axiom policy is the certified fold `checkDecls` +(`checkDeclsUnder_eq_checkDecls`). This module proves the converse: the +certified fold accepts no user axiom, because its axiom arm admits only +role-keyed axioms (`checkStdAxiomDeclC_eligible`). Together, +`checkDecls_iff_userAxioms`: the certified fold accepts exactly what the fold +that admits user axioms accepts without any. So a host may run the +user-axiom fold alone: an accept whose user axioms are none is the certified +fold's accept, and nothing the certified fold accepts is lost. +-/ + +namespace Ix.Kernel.Cached + +open Ix.Kernel + +variable {ev : Evidences md} + +/-- **The strict axiom arm declines every eligible key**: an eligible key is no +role key, so it is not `Quot.sound`, `propext`, `Classical.choice` or `sorryAx`, +and every other axiom declines. -/ +theorem checkStdAxiomDeclC_eligible (mode : CheckMode) (fe : FEnv md) (cv : ConstantVal md) + (h : cv.name.isCustomAxiomEligible = true) : + Yields (checkStdAxiomDeclC mode fe cv) (fun _ => False) := by + unfold checkStdAxiomDeclC + rw [if_neg (Key.isCustomAxiomEligible_ne_quotSound h)] + refine Yields.bind' (checkConstantValC_name mode fe cv) fun p hp => ?_ + obtain ⟨cvA, jty⟩ := p + have hn : cvA.name = cv.name := hp + have hpx : ¬cvA.name = propextName := by rw [hn]; exact Key.isCustomAxiomEligible_ne_propext h + have hch : ¬cvA.name = choiceName := by rw [hn]; exact Key.isCustomAxiomEligible_ne_choice h + have hso : ¬cvA.name = sorryAxName := by rw [hn]; exact Key.isCustomAxiomEligible_ne_sorryAx h + have hstd : ¬stdAxiomOkF fe cvA = true := by + unfold stdAxiomOkF + rw [if_neg hpx, if_neg hch] + exact Bool.false_ne_true + dsimp only + rw [if_neg hstd, if_neg (fun h' => h'.elim hpx hch), if_neg hso] + exact Yields.ofThrow + +/-- The certified fold's step declines a user axiom. -/ +theorem annotDeclStep_userAxiom (mode : CheckMode) (p : Nat × FEnv md × Array (PendingCheck md)) + {cv : ConstantVal md} (h : cv.name.isCustomAxiomEligible = true) (s : CState md) + (q : Nat × FEnv md × Array (PendingCheck md)) (s' : CState md) : + annotDeclStep mode ev p (.axiomDecl cv) s ≠ .ok (q, s') := by + intro hok + obtain ⟨fe', pend', -, hstep⟩ := annotDeclStep_ok hok + have hy : Yields (checkDeclStepC mode ev p.2.1 (.axiomDecl cv)) (fun _ => False) := by + unfold checkDeclStepC + ybind + rw [checkDeclC_axiomDecl] + exact checkStdAxiomDeclC_eligible mode p.2.1 cv h + have hy' : Yields (annotStepC mode ev p.1 p.2.1 p.2.2 (.axiomDecl cv)) (fun _ => False) := + Yields.bind' hy fun _ hf => hf.elim + exact hy' s (fe', pend') s' hstep + +/-- An accepting phase-A run accepted a step at each of its records. -/ +theorem InstallRun.step_of_mem {mode : CheckMode} {ds : List (Declaration md)} + {p p' : Nat × FEnv md × Array (PendingCheck md)} {s s' : CState md} + (r : InstallRun mode ev ds p s p' s') : + ∀ pd ∈ ds, ∃ q s₀ q' s₀', annotDeclStep mode ev q pd s₀ = .ok (q', s₀') := by + induction r with + | nil => intro pd hpd; exact absurd hpd List.not_mem_nil + | cons hstep _ ih => + intro pd hpd + rcases List.mem_cons.mp hpd with rfl | hpd + · exact ⟨_, _, _, _, hstep⟩ + · exact ih pd hpd + +/-- **The certified fold accepts no user axiom.** -/ +theorem checkDecls_userAxioms_nil {mode : CheckMode} {ds : Array (Declaration md)} {env : Env md} + (h : checkDecls mode ev ds = .ok env) : userAxiomsOfDecls ds.toList = [] := by + obtain ⟨fc, -⟩ := checkDecls_fullyChecked mode h + obtain ⟨n, s, r⟩ := fc.1.run + refine List.eq_nil_iff_forall_not_mem.mpr fun cv hcv => ?_ + obtain ⟨hmem, helig⟩ := mem_userAxiomsOfDecls_iff.mp hcv + obtain ⟨q, s₀, q', s₀', hok⟩ := r.step_of_mem _ hmem + exact annotDeclStep_userAxiom mode q helig s₀ q' s₀' hok + +/-- **The certified fold is the user-axiom fold without user axioms**: +`checkDecls` accepts exactly when `checkDeclsWithUserAxioms` accepts with the +same environment and the records declare no user axiom. -/ +theorem checkDecls_iff_userAxioms (mode : CheckMode) (ds : Array (Declaration md)) (env : Env md) : + checkDecls mode ev ds = .ok env ↔ + checkDeclsWithUserAxioms mode ev ds = .ok env ∧ userAxiomsOfDecls ds.toList = [] := by + constructor + · intro h + have hnone := checkDecls_userAxioms_nil h + exact ⟨by rw [checkDeclsWithUserAxioms_eq_checkDecls mode ev ds hnone]; exact h, hnone⟩ + · rintro ⟨h, hnone⟩ + rw [checkDeclsWithUserAxioms_eq_checkDecls mode ev ds hnone] at h + exact h + +end Ix.Kernel.Cached diff --git a/IxSharingVerify/Audit/Statements.lean b/IxSharingVerify/Audit/Statements.lean index 3994c7524..f05737b95 100644 --- a/IxSharingVerify/Audit/Statements.lean +++ b/IxSharingVerify/Audit/Statements.lean @@ -7,7 +7,6 @@ import IxSharingVerify.FamilyFast import IxSharingVerify.FamilyNormalize import IxSharingVerify.FamilyNormalizeSpec import IxSharingVerify.FamilyWritingBound -import IxSharingVerify.UniformCappedSize import Ix.LeanIngress.Expr import Ix.Sharing.FamilyContext @@ -156,8 +155,7 @@ def roots : Array RootAllowance := #[ { root := ``Ix.Sharing.Family.familyUniform_eq_reference, standardAxioms := standard }, { root := ``Ix.Sharing.Family.familyAtWidth_eq_reference, standardAxioms := standard }, -- Groundwork for an output-size bound (`FamilyFullWitness`, `FamilyResidual`, - -- `FamilyLocalWriting`, `FamilyWritingBound`) and the arithmetic contract of - -- Rust's capped classification cost (`UniformCappedSize`). + -- `FamilyLocalWriting`, `FamilyWritingBound`). { root := ``Ix.Sharing.Family.fullChoices_member, standardAxioms := standard }, { root := ``Ix.Sharing.Family.fullChoices_selected_mem, standardAxioms := noChoice }, { root := ``Ix.Sharing.Family.initial_full_member, standardAxioms := standard }, @@ -194,19 +192,6 @@ def roots : Array RootAllowance := #[ { root := ``Ix.Sharing.Family.residual_semantic_row, standardAxioms := standard }, { root := ``Ix.Sharing.Family.fullStored_ulen_le_residual, standardAxioms := standard }, { root := ``Ix.Sharing.Family.fullStored_encoding_bound, standardAxioms := standard }, - { root := ``Ix.Sharing.Verify.CappedSize.excluded_div, standardAxioms := standard }, - { root := ``Ix.Sharing.Verify.CappedSize.excluded_fast, standardAxioms := standard }, - { root := ``Ix.Sharing.Verify.CappedSize.excluded_size_lt_two, standardAxioms := standard }, - { root := ``Ix.Sharing.Verify.CappedSize.cap_size, standardAxioms := standard }, - { root := ``Ix.Sharing.Verify.CappedSize.cap_occ, standardAxioms := standard }, - { root := ``Ix.Sharing.Verify.CappedSize.classification_capped, standardAxioms := standard }, - { root := ``Ix.Sharing.Verify.CappedSize.cap_add_u128_safe, standardAxioms := noChoice }, - { root := ``Ix.Sharing.Verify.CappedSize.cap_add, standardAxioms := noChoice }, - { root := ``Ix.Sharing.Verify.CappedSize.cap_fold, standardAxioms := noChoice }, - { root := ``Ix.Sharing.Verify.CappedSize.cap_empty_telescope, standardAxioms := noChoice }, - { root := ``Ix.Sharing.Verify.CappedSize.empty_no_cuts, standardAxioms := noChoice }, - { root := ``Ix.Sharing.Verify.CappedSize.empty_telescope_natural, standardAxioms := noChoice }, - { root := ``Ix.Sharing.Verify.CappedSize.certainExcludedTest_contract, standardAxioms := #[] }, -- The meaning of the canonical construction: the pointer cache, ingestion -- of the table and the roots, and canonical numbering preserve what each -- input expression denotes. diff --git a/IxSharingVerify/UniformCappedSize.lean b/IxSharingVerify/UniformCappedSize.lean deleted file mode 100644 index 1b0db08f9..000000000 --- a/IxSharingVerify/UniformCappedSize.lean +++ /dev/null @@ -1,168 +0,0 @@ -import IxC.Kernel.Audit.Axioms -import IxSharingVerify.UniformClasses - -/-! Arithmetic contract of Rust's classification-only empty-cost cap. -The cap covers every public UInt64 width. It is not used for general phi, -gains, signed deltas or wire lengths. The sum/empty-telescope lemmas justify -the no-cut capped recurrence; native Rust correspondence is separately -checked against an arbitrary-precision reference. -/ - -namespace Ix.Sharing.Verify.CappedSize - -abbrev Excluded (occ size width : Nat) : Prop := (occ - 1) * size < occ * width - -theorem excluded_div {occ size width : Nat} (positive : 0 < occ) (larger : width < size) : - Excluded occ size width ↔ occ ≤ (size - 1) / (size - width) := by - have left : (occ - 1) * size + size = occ * size := by - rw [← Nat.succ_mul] - congr 1 - omega - have right : occ * size = occ * (size - width) + occ * width := by - rw [← Nat.mul_add, Nat.sub_add_cancel (Nat.le_of_lt larger)] - rw [Nat.le_div_iff_mul_le (by omega)] - unfold Excluded - omega - -/-- Product-free comparison, including occurrence zero and width zero. -/ -theorem excluded_fast (occ size width : Nat) : - Excluded occ size width ↔ occ ≠ 0 ∧ width ≠ 0 ∧ - (size ≤ width ∨ occ ≤ (size - 1) / (size - width)) := by - by_cases zero : occ = 0 - · simp [Excluded, zero] - by_cases noWidth : width = 0 - · simp [Excluded, noWidth] - by_cases small : size ≤ width - · have scaled := Nat.mul_le_mul_left (occ - 1) small - have split : occ * width = (occ - 1) * width + width := by - rw [← Nat.succ_mul] - congr 1 - omega - have yes : Excluded occ size width := by unfold Excluded; omega - simp only [ne_eq, zero, noWidth, not_false_eq_true, small, true_or, and_self, yes] - · rw [excluded_div (by omega) (by omega)] - simp only [ne_eq, zero, noWidth, not_false_eq_true, small, false_or, true_and] - -theorem excluded_size_lt_two {occ size width : Nat} (repeated : 2 ≤ occ) - (excluded : Excluded occ size width) : size < 2 * width := by - apply Classical.byContradiction - intro large - have doubled : occ ≤ (occ - 1) * 2 := by omega - have first := Nat.mul_le_mul_right width doubled - have second := Nat.mul_le_mul_left (occ - 1) (show 2 * width ≤ size by omega) - rw [Nat.mul_assoc] at first - exact Nat.not_lt_of_ge (Nat.le_trans first second) excluded - -/-- Large empty costs need not be represented exactly for exclusion. -/ -theorem cap_size (occ size width bound : Nat) (covers : 2 * width ≤ bound) : - Excluded occ (min size bound) width ↔ Excluded occ size width := by - by_cases fits : size ≤ bound - · rw [Nat.min_eq_left fits] - rw [Nat.min_eq_right (Nat.le_of_lt (Nat.lt_of_not_ge fits))] - by_cases zero : occ = 0 - · simp [Excluded, zero] - by_cases once : occ = 1 - · simp [Excluded, once] - have repeated : 2 ≤ occ := by omega - have left : ¬ Excluded occ bound width := by - intro yes - have := excluded_size_lt_two repeated yes - omega - have right : ¬ Excluded occ size width := by - intro yes - have := excluded_size_lt_two repeated yes - omega - simp only [left, right] - -/-- Saturated occurrence counts preserve the same predicate when the -represented size is below the saturation threshold. -/ -theorem cap_occ (occ size width bound : Nat) (above : size < bound) : - Excluded (min occ bound) size width ↔ Excluded occ size width := by - by_cases fits : occ ≤ bound - · rw [Nat.min_eq_left fits] - rw [Nat.min_eq_right (Nat.le_of_lt (Nat.lt_of_not_ge fits)), - excluded_fast, excluded_fast] - have positive : bound ≠ 0 := by omega - have occPositive : occ ≠ 0 := by omega - have quotient := Nat.div_le_self (size - 1) (size - width) - have boundLarge : ¬ bound ≤ (size - 1) / (size - width) := by omega - have occLarge : ¬ occ ≤ (size - 1) / (size - width) := by omega - simp only [ne_eq, positive, occPositive, not_false_eq_true, boundLarge, occLarge, - or_false, true_and] - -def maxWidth : Nat := 2 ^ 64 - 1 -def emptyCap : Nat := 2 * maxWidth -def occurrenceCap : Nat := 2 ^ 128 - 1 - -/-- Every public u64 width is covered, including zero; occurrence saturation -and empty-size capping together preserve the uncapped natural predicate. -/ -theorem classification_capped (occ size width : Nat) (publicWidth : width ≤ maxWidth) : - Excluded (min occ occurrenceCap) (min size emptyCap) width ↔ Excluded occ size width := by - have gap : emptyCap < occurrenceCap := by decide - have above : min size emptyCap < occurrenceCap := Nat.lt_of_le_of_lt (Nat.min_le_right _ _) gap - exact (cap_occ occ (min size emptyCap) width occurrenceCap above).trans - (cap_size occ size width emptyCap (Nat.mul_le_mul_left 2 publicWidth)) - -/-- The capped additions use inputs at most 2^65-2; even their sum fits -u128, so native addition needs no overflowing intermediate. -/ -theorem cap_add_u128_safe (a b : Nat) : min a emptyCap + min b emptyCap < 2 ^ 128 := by - have bound : 2 * emptyCap < 2 ^ 128 := by decide - have ha := Nat.min_le_right a emptyCap - have hb := Nat.min_le_right b emptyCap - omega - -theorem cap_add (bound a b : Nat) : - min (min a bound + min b bound) bound = min (a + b) bound := by omega - -/-- Own-payload vectors and non-telescope children are folds of capped sums. -Every scalar/vector element participates; no u64 Len sentinel is substituted. -/ -theorem cap_fold (bound : Nat) (xs : List Nat) (start : Nat) : - xs.foldl (fun acc x => min (acc + min x bound) bound) (min start bound) = - min (start + xs.sum) bound := by - induction xs generalizing start with - | nil => simp - | cons x xs ih => - rw [List.foldl_cons, cap_add, ih] - simp only [List.sum_cons, Nat.add_assoc] - -/-- Empty telescope evaluation has only side sums and a natural tail; -there is no available cut and therefore no subtraction of capped values. -/ -theorem cap_empty_telescope (bound header extra side previous tail : Nat) : - min (min (min header bound + - min (min (min extra bound + min side bound) bound + min previous bound) bound) bound + - min tail bound) bound = min (header + (extra + side + previous) + tail) bound := by - rw [cap_add bound extra side, cap_add bound (extra + side) previous, - cap_add bound header (extra + side + previous), cap_add] - -/-- The actual empty Lean evaluator has no available telescope cut. -/ -theorem empty_no_cuts (p : Ix.Sharing.Exact.Prep) (width : Nat) (value : Nat → Nat) (term : Nat) : - Ix.Sharing.Verify.UniformModel.cutCosts p width (fun _ => false) value term = [] := by - simp [Ix.Sharing.Verify.UniformModel.cutCosts] - -theorem empty_telescope_natural (p : Ix.Sharing.Exact.Prep) (width : Nat) (value : Nat → Nat) - (term : Nat) (tele : p.family[term]! ≠ .none) : - Ix.Sharing.Verify.UniformModel.inlOf p width (fun _ => false) value term = - Ix.Sharing.Verify.UniformModel.naturalCost p value term := by - rw [Ix.Sharing.Verify.UniformModel.inl_tele width _ _ tele, empty_no_cuts] - rfl - -/-- Link the arithmetic contract to the actual Lean classifier definition. -/ -theorem certainExcludedTest_contract (p : Ix.Sharing.Exact.Prep) - (f : Ix.Sharing.Exact.GraphFacts) (width term : Nat) : - Ix.Sharing.Exact.certainExcludedTest p f width term = - decide (Excluded f.occ[term]! p.base[term]! width) := rfl - -end Ix.Sharing.Verify.CappedSize - -#guard_kernel_axioms Ix.Sharing.Verify.CappedSize.excluded_div [propext, Classical.choice, Quot.sound] -#guard_kernel_axioms Ix.Sharing.Verify.CappedSize.excluded_fast [propext, Classical.choice, Quot.sound] -#guard_kernel_axioms Ix.Sharing.Verify.CappedSize.excluded_size_lt_two [propext, Classical.choice, Quot.sound] -#guard_kernel_axioms Ix.Sharing.Verify.CappedSize.cap_size [propext, Classical.choice, Quot.sound] -#guard_kernel_axioms Ix.Sharing.Verify.CappedSize.cap_occ [propext, Classical.choice, Quot.sound] -#guard_kernel_axioms Ix.Sharing.Verify.CappedSize.classification_capped [propext, Classical.choice, Quot.sound] -#guard_kernel_axioms Ix.Sharing.Verify.CappedSize.cap_add_u128_safe [propext, Quot.sound] -#guard_kernel_axioms Ix.Sharing.Verify.CappedSize.cap_add [propext, Quot.sound] -#guard_kernel_axioms Ix.Sharing.Verify.CappedSize.cap_fold [propext, Quot.sound] -#guard_kernel_axioms Ix.Sharing.Verify.CappedSize.cap_empty_telescope [propext, Quot.sound] -#guard_kernel_axioms Ix.Sharing.Verify.CappedSize.empty_no_cuts [propext, Quot.sound] -#guard_kernel_axioms Ix.Sharing.Verify.CappedSize.empty_telescope_natural [propext, Quot.sound] -#guard_kernel_axioms Ix.Sharing.Verify.CappedSize.certainExcludedTest_contract [] diff --git a/Main.lean b/Main.lean index bfb2ffb12..429b536f1 100644 --- a/Main.lean +++ b/Main.lean @@ -3,7 +3,6 @@ import Ix.Cli.AggregateCmd import Ix.Cli.BenchReport import Ix.Cli.CheckCmd import Ix.Cli.CodegenCmd -import Ix.Cli.CheckRsCmd import Ix.Cli.CheckLeanCmd import Ix.Cli.CompileLeanCmd import Ix.Cli.CompileExportCmd @@ -13,11 +12,9 @@ import Ix.Cli.CatalogCmd import Ix.Cli.CompileCmd import Ix.Cli.DecompileCmd import Ix.Cli.DiffCmd -import Ix.Cli.IngressCmd import Ix.Cli.MergeCmd import Ix.Cli.NameOfCmd import Ix.Cli.PackCmd -import Ix.Cli.ProfileCmd import Ix.Cli.CompressCmd import Ix.Cli.ProveCmd import Ix.Cli.ProveBindingCmd @@ -29,6 +26,8 @@ import Ix.Cli.ServeCmd import Ix.Cli.ConnectCmd import Ix +/-! CLI entry point for the `ix` executable. -/ + def VERSION : String := Ix.versionString def ixCmd : Cli.Cmd := `[Cli| @@ -43,7 +42,6 @@ def ixCmd : Cli.Cmd := `[Cli| decompileCmd; mergeCmd; checkCmd; - checkRsCmd; checkLeanCmd; compileLeanCmd; compileExportCmd; @@ -52,7 +50,6 @@ def ixCmd : Cli.Cmd := `[Cli| diffCmd; packCmd; treeCmd; - profileCmd; proveCmd; proveBindingCmd; compressCmd; @@ -62,7 +59,6 @@ def ixCmd : Cli.Cmd := `[Cli| verifyHostCmd; addrOfCmd; nameOfCmd; - ingressCmd; serveCmd; connectCmd ] diff --git a/README.md b/README.md index 3593fd0e4..3c714d2a0 100644 --- a/README.md +++ b/README.md @@ -179,8 +179,10 @@ consistency. It is Ix's own kernel, derived from the verified checker of [con-leche](https://github.com/leanprover/con-leche). Its entry, `Ix.Kernel.Admission.checkBytes`, takes canonical `ixon` bytes, and for every environment it accepts the theorems give a set-theoretic model and no proof of -`False`, on the standard axioms only. It does not certify the compiler, the -Rust kernel or IxVM. [docs/kernel.md](docs/kernel.md) states what is +`False`, on the standard axioms only. This theorem does not by itself certify +the compiler or IxVM: the compiler has its own theorem, +`Ix.Cert.CompileCorrect` (see [Compiling Lean to Ixon](#compiling-lean-to-ixon)), +and IxVM is outside both. [docs/kernel.md](docs/kernel.md) states what is proved, what is trusted and what the gate checks; `--with-model` also builds the Mathlib-based model package `Models/SetTheory`: @@ -235,12 +237,9 @@ measurements belong in [BENCHMARKS.md](BENCHMARKS.md) and `ix compile .lean --out .ixe` (and `ix compile-lean`) runs the certified compiler, `Ix.Cert.compileReport`, which is proved correct (`Ix.Cert.CompileCorrect`), at the default sharing limits. `ix prove`, `ix check` -and `ix addr-of` given a name and no `--ixe` compile its closure with the -streamed Lean pipeline, which runs the same report checks but whose front end is -tested, not proved; `--streamed` selects it for `ix compile`. `--rust` selects the -Rust compiler instead: faster and lighter, but **uncertified**. It runs none -of the report checks, and its output is held to the Lean compiler's only by -byte equality on the reference corpora (`ix compile-lean --rust-check`). See +and `ix addr-of` given a name and no `--ixe` compile its closure through the +same certified compiler (`Ix.CompileM.compileCertified`). The uncertified +streamed and Rust compilers have been retired. See [docs/kernel.md](docs/kernel.md) for what the theorem covers on these paths. ### Testing @@ -249,13 +248,10 @@ byte equality on the reference corpora (`ix compile-lean --rust-check`). See Every test entry is registered once, with its tier, in `Tests/Main.lean` (the typed registry, `Tests/Registry.lean`). The tiers: `primary` (run by a -bare `lake test`), `ci:` (a CI partition: `cli` in the CI job, -`compile-pipeline`, `compiler-closures`, `compile`, `decompile`, `misc`, -`landing-compilers` and `landing-initstd` in the merge queue) and `manual` -(with a reason, e.g. a Mathlib-sized input, or an entry expected to retire -with the compiler's canonical rewriting). The two landing partitions hold -the end-to-end checks of the compilers and of the certification, on the -whole test environment and Init+Std; `--tier landing` selects both. +bare `lake test`), `ci:` (a CI partition: `cli` in the CI job, and +`compiler-closures`, `misc` and `landing-initstd-cli` in the merge queue) and +`manual` (with a reason, e.g. a Mathlib-sized input). The landing partition +holds the end-to-end checks on Init+Std; `--tier landing` selects it. - `lake test` runs every primary entry. Every selected entry runs, even after a failure, with a result line each; the exit code is the first non-zero one. - `lake test -- ...` runs exactly the named entries, whatever their tier diff --git a/Scripts/Performance/CompilerChecks.lean b/Scripts/Performance/CompilerChecks.lean deleted file mode 100644 index 6748fb8d9..000000000 --- a/Scripts/Performance/CompilerChecks.lean +++ /dev/null @@ -1,45 +0,0 @@ -import Tests.Ix.Compile.Performance - -/-! Native entry for the source-unit and family-check cache regressions and -scale fixtures. Arguments: `test`, `unit COUNT`, or `family COUNT`. -Construction is outside each measurement. Family timing compares the cached -entry to the direct, unchanged `familyBlockOk` reference helper. --/ - -open Tests.Ix.Compile.Performance - -private def benchMeasure (label : String) (action : Unit → α) : IO α := do - let start ← IO.monoNanosNow - let result ← IO.lazyPure action - let elapsed := (← IO.monoNanosNow) - start - IO.println s!"{label} ns={elapsed}" - return result - -private def unitScale (count : Nat) : IO Unit := do - let data ← unitFixture count - let indexed ← benchMeasure s!"unit-index auxiliaries={count}" fun _ => - (Ix.EnvScope.unitIndex data.env.constants).getD data.root #[] - unless indexed.size == count do throw (IO.userError "unit index omitted auxiliaries") - let reference ← benchMeasure s!"raw-closure auxiliaries={count}" fun _ => - (Ix.EnvScope.collectDeps data.env data.expected).map (·.1) - let selected ← benchMeasure s!"selected-closure auxiliaries={count}" fun _ => - (Ix.EnvScope.collectSelectedDeps data.env [data.root]).map (·.1) - unless selected == reference && selected.length == 2 * count + 1 do - throw (IO.userError "selected closure lost auxiliary dependencies or gained unrelated input") - -private def familyScale (count : Nat) : IO Unit := do - let data ← IO.lazyPure fun _ => familyFixture count - let reference ← benchMeasure s!"family-reference constructors={count}" fun _ => - familyReference data.env data.blocks {} - let cached ← benchMeasure s!"family-cached constructors={count}" fun _ => - Ix.Cert.strayFamilyMemberWithCached data.env data.blocks {} - unless reference.isNone && decide (cached = reference) do - throw (IO.userError "valid family was rejected or cached result differs") - -def main (args : List String) : IO UInt32 := do - match args with - | ["test"] => Tests.Ix.Compile.Performance.run - | ["unit", count] => unitScale count.toNat! - | ["family", count] => familyScale count.toNat! - | _ => throw (IO.userError "expected test, unit COUNT, or family COUNT") - return 0 diff --git a/Scripts/SourceHygiene.lean b/Scripts/SourceHygiene.lean index 47202f7d5..3ca3c487c 100644 --- a/Scripts/SourceHygiene.lean +++ b/Scripts/SourceHygiene.lean @@ -11,7 +11,10 @@ def has (text pattern : String) : Bool := (text.splitOn pattern).length > 1 def retiredNames : List String := ["sourceClasses", "source_classes", "compileExprNoSurgery", "compileEquivalentMutConsts", - "compileMutConstClass", "Ix.Tc.PrimAddrs", "Ix.Tc.primAddrSet", "Ix.Tc.reservedMarkerName"] + "compileMutConstClass", "Ix.Tc.PrimAddrs", "Ix.Tc.primAddrSet", "Ix.Tc.reservedMarkerName", + "compileEnvStreamed", "compileEnvWithRust", "compileLeanWithRust", "checkRsCmd", "validateRsCmd", + "crates/kernel/", "crates/compile/", "check-rs", "ix_compile::", "rs_compile_env", + "resourcePostStep"] def privatePlans : List String := ["IXC-PLAN", "PRIMITIVES-DESIGN"] @@ -34,24 +37,57 @@ def hasRetiredOpen (text : String) : Bool := let opened := ((word.dropPrefix? "_root_.").map (·.toString)).getD word opened == "Ix.Tc" || opened.startsWith "Ix.Tc." -def violations (text : String) : List String := +def violationsWith (retired : Bool) (text : String) : List String := (if hasQueueLabel text then ["numbered private work queue"] else []) ++ (if hasRetiredOpen text then ["open of retired typechecker namespace"] else []) ++ - (privatePlans ++ bannedSubstrings ++ retiredNames).filter (has text) + (privatePlans ++ bannedSubstrings ++ (if retired then retiredNames else [])).filter (has text) + +def violations (text : String) : List String := violationsWith true text + +/-- Files exempt from `retiredNames` alone, as path components from the repository root: +the history of the retired Rust implementations, which must name their paths. Every +other rule still applies to them. -/ +def retiredNamesExempt : List (List String) := [["docs", "retired-rust.md"]] + +def exemptFromRetiredNames (path : System.FilePath) : Bool := + retiredNamesExempt.contains (path.components.filter (· != ".")) + +def violationsAt (path : System.FilePath) (text : String) : List String := + violationsWith (!exemptFromRetiredNames path) text + +/-- The `retiredNames` exemption covers exactly its listed files and only that rule. -/ +def exemptionCheck : IO Unit := do + let retired := "crates/kernel/src/lib.rs, crates/compile/src/compile.rs, ix check-rs" + for path in ["docs/retired-rust.md", "./docs/retired-rust.md"] do + unless (violationsAt path retired).isEmpty do + throw <| IO.userError s!"retired-name exemption missing for {path}" + for bad in ["-- CQ2.8 exact cache", "IXC-PLAN §12", "owner decision", "open LSpec Ix.Tc"] do + if (violationsAt path bad).isEmpty then + throw <| IO.userError s!"retired-name exemption also waived another rule at {path}: {bad}" + for path in ["docs/kernel.md", "docs/retired-rust.lean", "docs/retired-rust.md.orig", + "docs/retired-rust2.md", "docs/old/retired-rust.md", "Tests/docs/retired-rust.md", + "Docs/retired-rust.md", "retired-rust.md", "Ix/retired-rust.md"] do + if (violationsAt path retired).isEmpty then + throw <| IO.userError s!"retired-name exemption leaked to {path}" def selfCheck : IO Unit := do + exemptionCheck for bad in ["-- CQ2.8 exact cache", "-- CQP.3 wrapper", "-- CQX.8 field test", "IXC-PLAN §12", "PRIMITIVES-DESIGN", "ALPHA-COLLAPSE.md §8.4", "LOCALITY.md", "TRUST-CLOSURE", "AUX-CERT", "AuxGen helper", "SurgAlias", "HANDOFF-2026", "ix-certified-v3", "coordinator decision, 2026-10-20", "owner decision", "Ix.Tc.PrimAddrs.canonical", + "compileEnvStreamed", "compileEnvWithRust", "checkRsCmd", + "Mirror crates/kernel/src/inductive.rs", "crates/compile/src/compile.rs", "ix check-rs --anon", + "ix_compile::decompile", "[rs_compile_env]", "Ix.CompileM.resourcePostStep", "sourceClasses members", "compileEquivalentMutConsts sources", "open LSpec Ix.Tc", "open LSpec _root_.Ix.Tc", "open _root_.Ix.Tc.PrimAddrs"] do if (violations bad).isEmpty then throw <| IO.userError s!"scanner missed control: {bad}" for good in ["S3 object storage", "R1 and R2 are relations", "2026-10-28", "CQRS architecture", "sourceMembers members", "compileMutConstMembers sources", "fae62ca5", "#guard !allowedData `Ix.Tc", - "open LSpec _root_.Ix", "open _root_.Ix.CompileM"] do + "open LSpec _root_.Ix", "open _root_.Ix.CompileM", "crates/ixon/src/shard.rs", + "ix check-lean --anon", "crates/common/src/prim_addrs.rs"] do unless (violations good).isEmpty do throw <| IO.userError s!"scanner rejected ordinary text: {good}" def sourceFile (path : System.FilePath) : Bool := @@ -70,9 +106,9 @@ def check (paths : Array System.FilePath) : IO UInt32 := do let mut failures := 0 for path in paths do let source ← IO.FS.readFile path - if (violations source).isEmpty then continue + if (violationsAt path source).isEmpty then continue for (line, index) in (source.splitOn "\n").zipIdx do - for problem in violations line do + for problem in violationsAt path line do IO.eprintln s!"{path}:{index + 1}: {problem}" failures := failures + 1 if failures == 0 then diff --git a/Tests/Aggr.lean b/Tests/Aggr.lean index b365c9007..026b09eba 100644 --- a/Tests/Aggr.lean +++ b/Tests/Aggr.lean @@ -42,6 +42,16 @@ namespace Tests.Aggr open Tests.ProofHelpers (expectOk expectErr recCommitParams innerFri) +private def expectErrContains [ToString ε] (descr : String) (needle : String) + (e : Except ε α) : TestSeq := + match e with + | .error msg => + if (toString msg).contains needle then + test descr true + else + test s!"{descr} — rejected with unexpected error: {msg} (expected '{needle}')" false + | .ok _ => test s!"{descr} — expected a rejection but it was accepted" false + /-- Cheap stand-in children. `fake_verify_claim` mirrors the IxVM `verify_claim` claim shape (10 words), `fake_aggr` mirrors the `ix_aggr` claim shape (18 words). Bodies touch memory so the proofs have a non-trivial trace. -/ @@ -791,6 +801,156 @@ def smokeSuite : IO UInt32 := do fullRange.claimsBytes ByteArray.empty leftClaimBytes allowed (Aggr.preimagesBlob #[fullStmt]) (Aggr.treesBlob #[]) (Aggr.pathsBlob #[]) + -- ── R01 self-assumption and cross-discharge regressions ───────────────── + -- 1. Duplicated self-assuming child `q = CheckEnv(ctx, {a}, {a})` in flat + -- and structural joins attempting `join(q, q) -> CheckEnv(ctx, {a}, none)`. + let selfAssumingA : Aggr.CheckEnvTrees := + { ctx := testCtx, subjects := canonicalTree #[a] + assumptions := some (canonicalTree #[a]) } + let selfAssumingABytes := Ix.Claim.ser selfAssumingA.claim + let selfAssumingAIxvm ← IO.ofExcept (mkIxvmChild selfAssumingABytes) + let selfDischargedFlatOut : Aggr.CheckEnvTrees := + { ctx := testCtx, subjects := canonicalTree #[a], assumptions := none } + let selfDischargedFlatBytes := Ix.Claim.ser selfDischargedFlatOut.claim + let selfAssumingFlatDuplicate := run (pairShape .ixvm .ixvm) + #[selfAssumingAIxvm, selfAssumingAIxvm] selfDischargedFlatBytes + (outClaim? := some selfDischargedFlatBytes) + (preimages := #[selfAssumingABytes]) + (trees := #[selfAssumingA.subjects]) + -- 2. Flat pair where left child's subjects and right child's subjects are + -- disjoint (`{a}` vs `{c}`), but left child assumes its own subject `a`. + let selfDischargedCrossFlatOut : Aggr.CheckEnvTrees := + { ctx := testCtx, subjects := canonicalTree #[a, c] + assumptions := none } + let selfDischargedCrossFlatBytes := Ix.Claim.ser selfDischargedCrossFlatOut.claim + let selfAssumingFlatDisjointSubjects := run (pairShape .ixvm .ixvm) + #[selfAssumingAIxvm, rightIxvm] selfDischargedCrossFlatBytes + (outClaim? := some selfDischargedCrossFlatBytes) + (preimages := #[selfAssumingABytes, rightClaimBytes]) + (trees := Aggr.CheckEnvTrees.adviceTrees + selfAssumingA rightStatement selfDischargedCrossFlatOut) + -- 3. Flat pair where each child has `subjects ∩ assumptions = ∅`, but the + -- two children have overlapping subject sets (`leftStatement` with itself). + let overlappingFlatSubjects := run (pairShape .ixvm .ixvm) + #[leftIxvm, leftIxvm] leftClaimBytes + (outClaim? := some leftClaimBytes) + (preimages := #[leftClaimBytes]) + (trees := #[leftStatement.subjects, leftStatement.assumptions.get!]) + -- 4. Structural pair with duplicated self-assuming child `joinStructural(q, q)`. + let selfDischargedStructOut : Aggr.CheckEnvTrees := + { ctx := testCtx + subjects := Ix.AssumptionTree.join selfAssumingA.subjects selfAssumingA.subjects + assumptions := none } + let selfDischargedStructBytes := Ix.Claim.ser selfDischargedStructOut.claim + let selfAssumingStructuralDuplicate := run (Aggr.structuralShapeCode .ixvm .ixvm) + #[selfAssumingAIxvm, selfAssumingAIxvm] selfDischargedStructBytes + (outClaim? := some selfDischargedStructBytes) + (preimages := #[selfAssumingABytes]) + (trees := #[selfAssumingA.assumptions.get!]) + (paths := #[(a, selfDischargedStructOut.subjects.merkleProof a)]) + -- 5. Structural pair where a left-only assumption (`b`) is discharged by a + -- valid Merkle path into `left_root` instead of `right_root`. + let leftSelfAssumingB : Aggr.CheckEnvTrees := + { ctx := testCtx, subjects := canonicalTree #[a, b] + assumptions := some (canonicalTree #[b, c]) } + let leftSelfAssumingBBytes := Ix.Claim.ser leftSelfAssumingB.claim + let leftSelfAssumingBIxvm ← IO.ofExcept (mkIxvmChild leftSelfAssumingBBytes) + let selfDischargeLeftStructOut : Aggr.CheckEnvTrees := + { ctx := testCtx + subjects := Ix.AssumptionTree.join leftSelfAssumingB.subjects rightStatement.subjects + assumptions := none } + let selfDischargeLeftStructBytes := Ix.Claim.ser selfDischargeLeftStructOut.claim + let selfAssumingStructuralSelfDischarge := run (Aggr.structuralShapeCode .ixvm .ixvm) + #[leftSelfAssumingBIxvm, rightIxvm] selfDischargeLeftStructBytes + (outClaim? := some selfDischargeLeftStructBytes) + (preimages := #[leftSelfAssumingBBytes, rightClaimBytes]) + (trees := Aggr.CheckEnvTrees.structuralAdviceTrees + leftSelfAssumingB rightStatement selfDischargeLeftStructOut) + (paths := #[ + (a, selfDischargeLeftStructOut.subjects.merkleProof a), + (b, selfDischargeLeftStructOut.subjects.merkleProof b), + (c, selfDischargeLeftStructOut.subjects.merkleProof c), + (d, none)]) + -- 6. Structural pair where a right-only assumption (`c`) is discharged by a + -- valid Merkle path into `right_root` instead of `left_root`. + let leftOnlyAssumingD : Aggr.CheckEnvTrees := + { ctx := testCtx, subjects := canonicalTree #[a, b] + assumptions := some (canonicalTree #[d]) } + let leftOnlyAssumingDBytes := Ix.Claim.ser leftOnlyAssumingD.claim + let leftOnlyAssumingDIxvm ← IO.ofExcept (mkIxvmChild leftOnlyAssumingDBytes) + let rightSelfAssumingC : Aggr.CheckEnvTrees := + { ctx := testCtx, subjects := canonicalTree #[c] + assumptions := some (canonicalTree #[a, c]) } + let rightSelfAssumingCBytes := Ix.Claim.ser rightSelfAssumingC.claim + let rightSelfAssumingCIxvm ← IO.ofExcept (mkIxvmChild rightSelfAssumingCBytes) + let selfDischargeRightStructOut : Aggr.CheckEnvTrees := + { ctx := testCtx + subjects := Ix.AssumptionTree.join leftOnlyAssumingD.subjects rightSelfAssumingC.subjects + assumptions := some (canonicalTree #[d]) } + let selfDischargeRightStructBytes := Ix.Claim.ser selfDischargeRightStructOut.claim + let selfAssumingStructuralRightSelfDischarge := run (Aggr.structuralShapeCode .ixvm .ixvm) + #[leftOnlyAssumingDIxvm, rightSelfAssumingCIxvm] selfDischargeRightStructBytes + (outClaim? := some selfDischargeRightStructBytes) + (preimages := #[leftOnlyAssumingDBytes, rightSelfAssumingCBytes]) + (trees := Aggr.CheckEnvTrees.structuralAdviceTrees + leftOnlyAssumingD rightSelfAssumingC selfDischargeRightStructOut) + (paths := #[ + (a, selfDischargeRightStructOut.subjects.merkleProof a), + (c, (rightSelfAssumingC.subjects.merkleProof c).map fun p => + p.push (leftOnlyAssumingD.subjects.root, true)), + (d, none)]) + -- 7. Structural pair where an assumption shared by both children (`d`) is + -- discharged instead of carried. + let rightOwningD : Aggr.CheckEnvTrees := + { ctx := testCtx, subjects := canonicalTree #[c, d] + assumptions := some (canonicalTree #[a, d]) } + let rightOwningDBytes := Ix.Claim.ser rightOwningD.claim + let rightOwningDIxvm ← IO.ofExcept (mkIxvmChild rightOwningDBytes) + let sharedDischargedStructOut : Aggr.CheckEnvTrees := + { ctx := testCtx + subjects := Ix.AssumptionTree.join leftStatement.subjects rightOwningD.subjects + assumptions := none } + let sharedDischargedStructBytes := Ix.Claim.ser sharedDischargedStructOut.claim + let sharedStructuralAssumptionDischarged := run (Aggr.structuralShapeCode .ixvm .ixvm) + #[leftIxvm, rightOwningDIxvm] sharedDischargedStructBytes + (outClaim? := some sharedDischargedStructBytes) + (preimages := #[leftClaimBytes, rightOwningDBytes]) + (trees := Aggr.CheckEnvTrees.structuralAdviceTrees + leftStatement rightOwningD sharedDischargedStructOut) + (paths := #[ + (a, sharedDischargedStructOut.subjects.merkleProof a), + (c, sharedDischargedStructOut.subjects.merkleProof c), + (d, sharedDischargedStructOut.subjects.merkleProof d)]) + -- 8. Cross-binding structural pair (shape 15) rejecting duplicated subject + -- roots and self-discharging assumptions. + let crossDuplicateOut := memberA.joinCrossBinding klass + (underTable tableB { leftStatement with assumptions := none }) + let crossDuplicateBytes := Ix.Claim.ser crossDuplicateOut.claim + let (childBLeftSubjects, bytesBLeftSubjects) ← + selfOf (underTable tableB { leftStatement with assumptions := none }) + let selfAssumingCrossBindingDuplicate := runCross childA childBLeftSubjects + #[bytesA, bytesBLeftSubjects, tableA, tableB] crossDuplicateBytes + let crossSelfLeft := underTable tableA leftSelfAssumingB + let crossRightHonest := underTable tableB rightStatement + let (childCrossSelfLeft, bytesCrossSelfLeft) ← selfOf crossSelfLeft + let (childCrossRightHonest, bytesCrossRightHonest) ← selfOf crossRightHonest + let crossSelfDischargeOut : Aggr.CheckEnvTrees := + { ctx := { testCtx with binding := klass, bindingAssumed := false, literalClass := true } + subjects := Ix.AssumptionTree.join crossSelfLeft.subjects crossRightHonest.subjects + assumptions := none } + let crossSelfDischargeBytes := Ix.Claim.ser crossSelfDischargeOut.claim + let selfAssumingCrossBindingSelfDischarge := run Aggr.crossBindingShapeCode + #[childCrossSelfLeft, childCrossRightHonest] crossSelfDischargeBytes + (outClaim? := some crossSelfDischargeBytes) + (preimages := #[bytesCrossSelfLeft, bytesCrossRightHonest, tableA, tableB]) + (trees := Aggr.CheckEnvTrees.structuralAdviceTrees + crossSelfLeft crossRightHonest crossSelfDischargeOut) + (paths := #[ + (a, crossSelfDischargeOut.subjects.merkleProof a), + (b, crossSelfDischargeOut.subjects.merkleProof b), + (c, crossSelfDischargeOut.subjects.merkleProof c), + (d, none)]) + lspecIO (.ofList [("ix-aggr", [ test "ixAggr prunes the Ix-agnostic lift entrypoint" liftPruned, test "pair fold leaves exactly one outstanding assumption" @@ -912,6 +1072,33 @@ def smokeSuite : IO UInt32 := do expectErr "catalog join rejects a binding claim as a member" catalogOfBinding, expectErr "catalog join rejects a catalog child without its members tree" catalogTampered, + expectErrContains "flat pair rejects duplicated self-assuming children join(q, q)" + "aggr: subject set overlaps assumption set" + selfAssumingFlatDuplicate, + expectErrContains "flat pair rejects a child whose subject set overlaps its own assumptions" + "aggr: subject set overlaps assumption set" + selfAssumingFlatDisjointSubjects, + expectErrContains "flat pair rejects children with overlapping subject sets" + "aggr: child subject sets overlap" + overlappingFlatSubjects, + expectErrContains "structural pair rejects duplicated self-assuming children join(q, q)" + "aggr structural: child subject roots must be distinct" + selfAssumingStructuralDuplicate, + expectErrContains "structural pair rejects discharging a left assumption against the left subject tree" + "aggr structural: left assumption must be discharged by right child" + selfAssumingStructuralSelfDischarge, + expectErrContains "structural pair rejects discharging a right assumption against the right subject tree" + "aggr structural: right assumption must be discharged by left child" + selfAssumingStructuralRightSelfDischarge, + expectErrContains "structural pair rejects discharging an assumption shared by both children" + "aggr structural: shared assumption cannot be discharged" + sharedStructuralAssumptionDischarged, + expectErrContains "cross-binding pair rejects children with identical subject roots" + "aggr cross-binding: child subject roots must be distinct" + selfAssumingCrossBindingDuplicate, + expectErrContains "cross-binding pair rejects discharging a left assumption against the left subject tree" + "aggr structural: left assumption must be discharged by right child" + selfAssumingCrossBindingSelfDischarge, ])]) [] end Tests.Aggr diff --git a/Tests/AggrBindingReuse.lean b/Tests/AggrBindingReuse.lean index dd78d2e6c..2ddefe81d 100644 --- a/Tests/AggrBindingReuse.lean +++ b/Tests/AggrBindingReuse.lean @@ -2,6 +2,7 @@ module public import LSpec public import Ix.IxVM.ClaimHarness +public import Ix.CompileCertified public import Ix.Meta /-! @@ -51,10 +52,10 @@ private def boundClosure (leanEnv : Lean.Environment) (names : Array Lean.Name) if !seen.contains entry.fst then seen := seen.insert entry.fst deduped := deduped.push entry - let raw ← Ix.CompileM.rsCompileEnvFFI deduped.toList + let out ← IO.ofExcept (← Ix.CompileM.compileCertified leanEnv deduped.toList) let bytes ← IO.ofExcept Ix.Kernel.Reader.defaultTableBytes let address ← IO.ofExcept Ix.Kernel.Reader.defaultTableAddress - pure { raw.toEnv with roleBinding := some { address, bytes } } + pure { out.env with roleBinding := some { address, bytes } } private def isAddressHex (s : String) : Bool := s.length == 64 && s.all fun c => c.isDigit || ('a' ≤ c && c ≤ 'f') diff --git a/Tests/AggrSemantics.lean b/Tests/AggrSemantics.lean index cacd6b791..0c211383b 100644 --- a/Tests/AggrSemantics.lean +++ b/Tests/AggrSemantics.lean @@ -242,6 +242,18 @@ def semanticSuite : IO UInt32 := do structuralOutput.subjects.root == Ix.Merkle.nodeHash left.subjects.root right.subjects.root && structuralOutput.assumptions.map (·.leaves) == some #[d] + let selfAssumingClaim : Ix.Claim := + .checkEnv envCtx (canonicalTree #[a]).root (some (canonicalTree #[a]).root) + let selfAssumingTrees : Std.HashMap Address Ix.AssumptionTree := + ({} : Std.HashMap _ _).insert (canonicalTree #[a]).root (canonicalTree #[a]) + let ofClaimRejectsSelfAssumption := + (Aggr.CheckEnvTrees.ofClaim selfAssumingClaim selfAssumingTrees).isOk == false + let selfAssumingStmt : Aggr.CheckEnvTrees := + { ctx := envCtx, subjects := canonicalTree #[a], + assumptions := some (canonicalTree #[a]) } + let hostJoinRejectsSelfDischarge := + (selfAssumingStmt.join selfAssumingStmt).assumptions.map (·.leaves) == some #[a] && + (selfAssumingStmt.joinStructural selfAssumingStmt).assumptions.map (·.leaves) == some #[a] let leftBytes := Ix.Claim.ser left.claim let rightBytes := Ix.Claim.ser right.claim let flatBytes := Ix.Claim.ser flatOutput.claim @@ -586,6 +598,10 @@ def semanticSuite : IO UInt32 := do test "flat host fold constructs canonical union/discharge trees" flatHostCorrect, test "structural host fold constructs root-of-roots and survivors" structuralHostCorrect, + test "CheckEnvTrees.ofClaim rejects overlapping subjects and assumptions" + ofClaimRejectsSelfAssumption, + test "host join helpers do not self-discharge assumptions" + hostJoinRejectsSelfDischarge, test "manifest tree lowers to post-order binary slots" (manifestPlan == expectedPlan), test "manifest parser exposes its validated bisection tree" parsedManifestPlan, diff --git a/Tests/Cli.lean b/Tests/Cli.lean index 95749b04f..fce85bcfc 100644 --- a/Tests/Cli.lean +++ b/Tests/Cli.lean @@ -3,7 +3,9 @@ module public import Ix.Ixon public import Ix.Cli.CompileCmd -/- Integration tests for the Ix CLI -/ +/-! + Integration tests for the Ix CLI. +-/ /-- Spawn env for nested `lake` invocations: drop the LD_LIBRARY_PATH that `lake test` set for this process. It puts the Lean toolchain's lib dir, @@ -137,70 +139,6 @@ private def Tests.Cli.testCompileContracts : IO Unit := do finally IO.FS.removeDirAll dir -/-- `ix compile --sharing-limits`: a malformed override is rejected before - compiling (nothing written). The certified compiler, whose theorem fixes - the default limits, refuses a valid override, given or inherited, and - writes nothing. Under `--streamed` a valid one (Lean and Rust keys mixed) - compiles, and a limit no constant fits in fails the compile naming the - limit (so an override that never reached the compiler would be caught). - An invalid `IX_SHARING_LIMITS` set directly fails the compile once. -/ -private def Tests.Cli.testCompileSharingLimits : IO Unit := do - let ix ← IO.FS.realPath ".lake/build/bin/ix" - let dir ← IO.FS.createTempDir - let source := dir / "SharingLimits.lean" - let output := dir / "sharing-limits.ixe" - let occurrences (out : IO.Process.Output) (s : String) : Nat := - (out.stdout.splitOn s).length - 1 + ((out.stderr.splitOn s).length - 1) - try - IO.FS.writeFile source - "def limitsMarker : Nat := 7\ntheorem limitsProof : limitsMarker = 7 := rfl\n" - let args := #["compile", source.toString, "--no-build", "--consts", "limitsProof", - "--out", output.toString] - let run := fun (spec : String) => IO.Process.output { - cmd := ix.toString, args := args ++ #["--streamed", "--sharing-limits", spec] } - let bad ← run "bogus=1" - if bad.exitCode == 0 || (← output.pathExists) then - throw <| IO.userError s!"compile accepted --sharing-limits bogus=1:\n{bad.stdout}\n{bad.stderr}" - unless (bad.stderr.splitOn "unknown sharing limit bogus").length > 1 do - throw <| IO.userError s!"--sharing-limits bogus=1: unexpected error:\n{bad.stderr}" - -- The certified compiler refuses a valid override, as a flag or inherited. - for (flags, env) in [(#["--sharing-limits", "states=2^44"], #[]), - (#[], #[("IX_SHARING_LIMITS", some "states=2^44")])] do - let refused ← IO.Process.output { cmd := ix.toString, args := args ++ flags, env } - if refused.exitCode == 0 || (← output.pathExists) then - throw <| IO.userError s!"the certified compiler accepted a sharing-limit override:\n\ - {refused.stdout}\n{refused.stderr}" - unless occurrences refused "runs at the default sharing limits" > 0 do - throw <| IO.userError s!"certified sharing-limit override: unexpected error:\n\ - {refused.stdout}\n{refused.stderr}" - -- Every serialized constant is longer than one byte: the override must - -- reach the compiler and fail it, naming the limit to raise. - let tight ← run "output_bytes=1" - if tight.exitCode == 0 || (← output.pathExists) then - throw <| IO.userError s!"compile ignored --sharing-limits output_bytes=1:\n\ - {tight.stdout}\n{tight.stderr}" - unless occurrences tight "--sharing-limits output_bytes=N" > 0 do - throw <| IO.userError s!"--sharing-limits output_bytes=1: unexpected error:\n\ - {tight.stdout}\n{tight.stderr}" - -- An invalid override set directly in the environment (no flag, so no - -- Lean pre-check) fails the compile once, before any block. - let direct ← IO.Process.output { - cmd := ix.toString, args, env := #[("IX_SHARING_LIMITS", some "bogus=1")] } - if direct.exitCode == 0 || (← output.pathExists) then - throw <| IO.userError s!"compile accepted IX_SHARING_LIMITS=bogus=1:\n\ - {direct.stdout}\n{direct.stderr}" - unless occurrences direct "IX_SHARING_LIMITS: unknown sharing limit bogus" == 1 do - throw <| IO.userError s!"IX_SHARING_LIMITS=bogus=1: expected one error:\n\ - {direct.stdout}\n{direct.stderr}" - let good ← run "states=2^44, depth=2^22, work=max" - unless good.exitCode == 0 && (← output.pathExists) do - throw <| IO.userError s!"compile --sharing-limits failed:\n{good.stdout}\n{good.stderr}" - IO.println "compile --sharing-limits: malformed override rejected, certified compiler \ - refuses an override, tight limit enforced under --streamed, invalid IX_SHARING_LIMITS \ - rejected once, valid override compiled under --streamed" - finally - IO.FS.removeDirAll dir - /-- `ix compile --local`: the file's own constants with their selected closure. The output's named section holds the file's definition and its inductive's recursor (the selected closure carries recursors) but not an @@ -238,8 +176,7 @@ def localSize : LocalTree → Nat\n | .leaf => 1\n | .node l r => localSize l IO.FS.removeDirAll dir /-- Exercise the report-emitting fail-closed path with an actual ungrounded -status. Early pipeline exceptions, such as invalid sharing limits, return -before the existing report code and are a separate contract. -/ +status. -/ private def Tests.Cli.testCompileReportAbort : IO Unit := do let ix ← IO.FS.realPath ".lake/build/bin/ix" IO.FS.withTempDir fun dir => do @@ -287,14 +224,37 @@ private def Tests.Cli.testPublishedEnvelopeVersion : IO Unit := unless (← Ix.Cli.publishedEnvelopeVersion true path) == some 4 do throw <| IO.userError "published report did not read v4" +/-- `ix compile-export` binds another toolchain's roles where its export puts +them. The 4.35.0 fixture publishes `Int.decLt` at an address the default role +table does not bind it at (its `Decidable` has one constructor); it compiles +to an artifact with its own role binding. -/ +private def Tests.Cli.testCompileExportForeignRoles : IO Unit := do + let ix ← IO.FS.realPath ".lake/build/bin/ix" + IO.FS.withTempDir fun dir => do + let export_ := dir / "lean-4.35.0.ndjson" + let unpacked ← IO.Process.output { cmd := "zstd", args := #["-dc", "-q", "-o", + export_.toString, "Tests/Fixtures/toolchains/lean-4.35.0.ndjson.zst"] } + unless unpacked.exitCode == 0 do + throw <| IO.userError s!"zstd -dc lean-4.35.0.ndjson.zst: {unpacked.stderr}" + let output := dir / "lean-4.35.0.ixe" + let compiled ← IO.Process.output { + cmd := ix.toString + args := #["compile-export", export_.toString, "--out", output.toString, "--workers", "8"] } + unless compiled.exitCode == 0 do + throw <| IO.userError s!"compile-export of the 4.35.0 fixture failed:\n{compiled.stdout}\n{compiled.stderr}" + let env ← IO.ofExcept (Ixon.deEnv (← IO.FS.readBinFile output)) + unless env.roleBinding.isSome do + throw <| IO.userError "compile-export of the 4.35.0 fixture wrote no role binding" + IO.println "compile-export: the 4.35.0 export compiles with its own role binding" + public def Tests.Cli.suite : IO UInt32 := do Tests.Cli.run "lake" (#["exe", "ix", "--help"]) none Tests.Cli.testCompileNoBuild Tests.Cli.testCompileContracts - Tests.Cli.testCompileSharingLimits Tests.Cli.testCompileLocal Tests.Cli.testCompileReportAbort Tests.Cli.testPublishedEnvelopeVersion + Tests.Cli.testCompileExportForeignRoles --Tests.Cli.run "ix" (#["store", "ix_test/IxTest.lean"]) none --Tests.Cli.run "ix" (#["prove", "ix_test/IxTest.lean", "one"]) none return 0 diff --git a/Tests/FFI.lean b/Tests/FFI.lean index 674eba4cd..52de732ab 100644 --- a/Tests/FFI.lean +++ b/Tests/FFI.lean @@ -1,22 +1,21 @@ -/- - FFI test suite aggregator. - Imports all Tests.FFI.* modules and exports a combined suite. --/ module public import Tests.FFI.Basic public import Tests.FFI.CompileStatus -public import Tests.FFI.Ix public import Tests.FFI.Ixon public import Tests.FFI.Lifecycle public import Tests.FFI.Refcount +/-! + FFI test suite aggregator. + Imports all Tests.FFI.* modules and exports a combined suite. +-/ + namespace Tests.FFI public def suite : List LSpec.TestSeq := List.foldr (· ++ ·) [] [ Tests.FFI.Basic.suite , Tests.FFI.CompileStatus.suite - , Tests.FFI.Ix.suite , Tests.FFI.Ixon.suite , Tests.FFI.Lifecycle.suite , Tests.FFI.Refcount.suite diff --git a/Tests/FFI/CompileStatus.lean b/Tests/FFI/CompileStatus.lean index aa35cf158..1c8e4a462 100644 --- a/Tests/FFI/CompileStatus.lean +++ b/Tests/FFI/CompileStatus.lean @@ -1,15 +1,17 @@ -/- - `rs_compile_env` structured-status tests: the fail-closed default - writes nothing when a requested constant is ungrounded, while - allow-partial serializes the grounded subset as a reloadable artifact - and the status enumerates exactly the missing names. --/ module public import LSpec public import Ix.CompileM +public import Ix.CompileCertified public import Ix.Ixon +/-! + Structured-status compilation tests: the fail-closed default + writes nothing when a requested constant is ungrounded, while + allow-partial serializes the grounded subset as a reloadable artifact + and the status enumerates exactly the missing names. +-/ + public section open LSpec @@ -33,11 +35,23 @@ private def badAx : Lean.ConstantInfo := .axiomInfo { private def consts : List (Lean.Name × Lean.ConstantInfo) := [(goodName, goodAx), (badName, badAx)] +private def compileEnvBytes (cs : List (Lean.Name × Lean.ConstantInfo)) + (path : String) (allowPartial : Bool) : IO Ix.CompileM.CompileEnvStatus := do + let out ← IO.ofExcept (← Ix.CompileM.compileCertifiedPrepared cs false) + let status := { Ix.Cli.statusOf out.env out.failures.toMap with + named := out.env.named.size.toUInt64 + uniqueAnon := out.env.consts.size.toUInt64 } + if !allowPartial && !status.ungrounded.isEmpty then + return status + let bytes ← IO.ofExcept (Ixon.serEnv out.env) + Ix.RoleBinding.Compile.writeAtomic (System.FilePath.mk path) bytes + return { status with bytes := bytes.size.toUInt64 } + def failClosedTest : TestSeq := - .individualIO "rs_compile_env fail-closed: ungrounded ⇒ no artifact" none (do + .individualIO "compileEnvBytes fail-closed: ungrounded ⇒ no artifact" none (do let dir ← IO.FS.createTempDir let out := dir / "fail-closed.ixe" - let status ← Ix.CompileM.rsCompileEnvBytesFFI consts out.toString false + let status ← compileEnvBytes consts out.toString false let outExists ← out.pathExists let tmpExists ← (dir / "fail-closed.ixe.tmp").pathExists IO.FS.removeDirAll dir @@ -55,10 +69,10 @@ def failClosedTest : TestSeq := ) .done def allowPartialTest : TestSeq := - .individualIO "rs_compile_env allow-partial: grounded subset reloads" none (do + .individualIO "compileEnvBytes allow-partial: grounded subset reloads" none (do let dir ← IO.FS.createTempDir let out := dir / "partial.ixe" - let status ← Ix.CompileM.rsCompileEnvBytesFFI consts out.toString true + let status ← compileEnvBytes consts out.toString true let bytes ← IO.FS.readBinFile out IO.FS.removeDirAll dir let env ← match Ixon.deEnv bytes with diff --git a/Tests/FFI/Ix.lean b/Tests/FFI/Ix.lean deleted file mode 100644 index 10c05e385..000000000 --- a/Tests/FFI/Ix.lean +++ /dev/null @@ -1,330 +0,0 @@ -/- - Ix.* type FFI roundtrip tests. - Pattern: Lean value → Rust (decode) → Rust (re-encode via C API) → Lean value → compare --/ -module - -public import LSpec -public import Tests.Gen.Basic -public import Tests.Gen.Ix -public import Tests.Gen.Ixon -public import Ix.Environment -public import Ix.Address -public import Ix.CompileM -public import Ix.DecompileM -public import Ix.Ixon -public import Tests.FFI.Ixon - -open LSpec SlimCheck Gen -open Tests.Gen.Ix -open Tests.Gen.Ixon -open Tests.FFI.Ixon (rawEnvEq) - -namespace Tests.FFI.Ix - -/-! ## Ix type roundtrip FFI declarations -/ - -@[extern "rs_roundtrip_ix_address"] -opaque roundtripIxAddress : @& Address → Address - -@[extern "rs_roundtrip_ix_name"] -opaque roundtripIxName : @& Ix.Name → Ix.Name - -@[extern "rs_roundtrip_ix_level"] -opaque roundtripIxLevel : @& Ix.Level → Ix.Level - -@[extern "rs_roundtrip_ix_expr"] -opaque roundtripIxExpr : @& Ix.Expr → Ix.Expr - -@[extern "rs_roundtrip_ix_int"] -opaque roundtripIxInt : @& Ix.Int → Ix.Int - -@[extern "rs_roundtrip_ix_substring"] -opaque roundtripIxSubstring : @& Ix.Substring → Ix.Substring - -@[extern "rs_roundtrip_ix_source_info"] -opaque roundtripIxSourceInfo : @& Ix.SourceInfo → Ix.SourceInfo - -@[extern "rs_roundtrip_ix_syntax_preresolved"] -opaque roundtripIxSyntaxPreresolved : @& Ix.SyntaxPreresolved → Ix.SyntaxPreresolved - -@[extern "rs_roundtrip_ix_syntax"] -opaque roundtripIxSyntax : @& Ix.Syntax → Ix.Syntax - -@[extern "rs_roundtrip_ix_data_value"] -opaque roundtripIxDataValue : @& Ix.DataValue → Ix.DataValue - --- Need Inhabited instance for opaque declaration -instance : Inhabited Ix.ConstantInfo where - default := .axiomInfo { cnst := { name := default, levelParams := #[], type := default }, isUnsafe := false } - -@[extern "rs_roundtrip_ix_constant_info"] -opaque roundtripIxConstantInfo : @& Ix.ConstantInfo → Ix.ConstantInfo - --- Need Inhabited instance for Environment opaque declaration -instance : Inhabited Ix.Environment where - default := { consts := {} } - --- Rust roundtrip returns RawEnvironment (array-based), not Environment (HashMap-based) -@[extern "rs_roundtrip_ix_raw_environment"] -opaque roundtripIxRawEnvironment : @& Ix.RawEnvironment → Ix.RawEnvironment - --- Roundtrip Environment by going through RawEnvironment -@[extern "rs_roundtrip_ix_environment"] -opaque roundtripIxEnvironmentRaw : @& Ix.Environment → Ix.RawEnvironment - -def roundtripIxEnvironment (env : Ix.Environment) : Ix.Environment := - (roundtripIxEnvironmentRaw env).toEnvironment - --- Round-trip Ix.RustCondensedBlocks -instance : Inhabited Ix.RustCondensedBlocks where - default := { lowLinks := #[], blocks := #[], blockRefs := #[] } - -instance : Repr Ix.RustCondensedBlocks where - reprPrec cb _ := s!"RustCondensedBlocks(lowLinks={cb.lowLinks.size}, blocks={cb.blocks.size}, blockRefs={cb.blockRefs.size})" - -@[extern "rs_roundtrip_rust_condensed_blocks"] -opaque roundtripRustCondensedBlocks : @& Ix.RustCondensedBlocks → Ix.RustCondensedBlocks - --- Round-trip Ix.CompileM.RustCompilePhases -instance : Inhabited Ix.CompileM.RustCompilePhases where - default := { rawEnv := default, condensed := default, compileEnv := default } - -instance : Repr Ix.CompileM.RustCompilePhases where - reprPrec p _ := s!"RustCompilePhases(rawEnv.consts={p.rawEnv.consts.size}, condensed.blocks={p.condensed.blocks.size}, compileEnv.consts={p.compileEnv.consts.size})" - -@[extern "rs_roundtrip_rust_compile_phases"] -opaque roundtripRustCompilePhases : @& Ix.CompileM.RustCompilePhases → Ix.CompileM.RustCompilePhases - -/-! ## BlockCompareResult and BlockCompareDetail FFI tests -/ - -/-- Result of comparing a single block between Lean and Rust -/ -inductive BlockCompareResult where - | «match» - | mismatch (leanSize rustSize firstDiffOffset : UInt64) - | notFound - deriving Repr, BEq, DecidableEq, Inhabited - -/-- Detailed comparison with sharing statistics -/ -structure BlockCompareDetail where - result : BlockCompareResult - leanSharingLen : UInt64 - rustSharingLen : UInt64 - deriving Repr, BEq, DecidableEq, Inhabited - -@[extern "rs_roundtrip_block_compare_result"] -opaque roundtripBlockCompareResult : @& BlockCompareResult → BlockCompareResult - -@[extern "rs_roundtrip_block_compare_detail"] -opaque roundtripBlockCompareDetail : @& BlockCompareDetail → BlockCompareDetail - -def blockCompareResultTests : TestSeq := - let matchCase := BlockCompareResult.match - let mismatchCase := BlockCompareResult.mismatch 100 200 50 - let notFoundCase := BlockCompareResult.notFound - test "BlockCompareResult.match" (roundtripBlockCompareResult matchCase == matchCase) ++ - test "BlockCompareResult.mismatch" (roundtripBlockCompareResult mismatchCase == mismatchCase) ++ - test "BlockCompareResult.notFound" (roundtripBlockCompareResult notFoundCase == notFoundCase) - -def blockCompareDetailTests : TestSeq := - let detailMatch := BlockCompareDetail.mk .match 10 20 - let detailMismatch := BlockCompareDetail.mk (.mismatch 100 200 50) 15 25 - let detailNotFound := BlockCompareDetail.mk .notFound 5 0 - test "BlockCompareDetail match" (roundtripBlockCompareDetail detailMatch == detailMatch) ++ - test "BlockCompareDetail mismatch" (roundtripBlockCompareDetail detailMismatch == detailMismatch) ++ - test "BlockCompareDetail notFound" (roundtripBlockCompareDetail detailNotFound == detailNotFound) - -/-! ## Shrinkable instances -/ - -instance : Shrinkable Ix.RustCondensedBlocks where - shrink cb := - if cb.lowLinks.isEmpty && cb.blocks.isEmpty && cb.blockRefs.isEmpty then [] - else [{ - lowLinks := if cb.lowLinks.isEmpty then #[] else cb.lowLinks.pop, - blocks := if cb.blocks.isEmpty then #[] else cb.blocks.pop, - blockRefs := if cb.blockRefs.isEmpty then #[] else cb.blockRefs.pop - }] - -/-! ## Ix type comparison by hash -/ - -def ixNameEq (a b : Ix.Name) : Bool := a.getHash == b.getHash -def ixLevelEq (a b : Ix.Level) : Bool := a.getHash == b.getHash -def ixExprEq (a b : Ix.Expr) : Bool := a.getHash == b.getHash - -/-! ## String payload preservation -/ - -/-- Inspect payloads directly: cached Name/Expr hashes survive FFI conversion -even when a string constructor corrupts the payload. -/ -def stringPayloadTests : TestSeq := Id.run do - let mut tests := TestSeq.done - for (label, value) in [ - ("empty", ""), - ("leading NUL", "\x00prefix"), - ("interior NUL", "left\x00right"), - ("trailing NUL", "/profile\x00"), - ("multiple NULs", "\x00\x00"), - ("Unicode and NULs", "λ\x00雪\x00")] do - let name := Ix.Name.mkStr Ix.Name.mkAnon value - let expr := Ix.Expr.mkLit (.strVal value) - let substring : Ix.Substring := ⟨value, 0, value.utf8ByteSize⟩ - let atom : Ix.Syntax := .atom .none value - let preresolved : Ix.SyntaxPreresolved := .decl name #[value] - let data : Ix.DataValue := .ofString value - let sameName : Bool := match roundtripIxName name with - | .str _ actual _ => actual == value - | _ => false - let sameLiteral : Bool := match roundtripIxExpr expr with - | .lit (.strVal actual) _ => actual == value - | _ => false - tests := tests ++ - test s!"Ix.Name string payload: {label}" sameName ++ - test s!"Ix.Expr string payload: {label}" sameLiteral ++ - test s!"Ix.Substring string payload: {label}" (roundtripIxSubstring substring == substring) ++ - test s!"Ix.Syntax string payload: {label}" (roundtripIxSyntax atom == atom) ++ - test s!"Ix.SyntaxPreresolved string payload: {label}" - (roundtripIxSyntaxPreresolved preresolved == preresolved) ++ - test s!"Ix.DataValue string payload: {label}" (roundtripIxDataValue data == data) - return tests - -/-! ## Comparison helpers -/ - -/-- Compare RustCondensedBlocks by checking array sizes. - Size-only: element-wise comparison is not feasible because the Lean and Rust - condensation algorithms may produce different SCC orderings. -/ -def rustCondensedBlocksEq (a b : Ix.RustCondensedBlocks) : Bool := - a.lowLinks.size == b.lowLinks.size && - a.blocks.size == b.blocks.size && - a.blockRefs.size == b.blockRefs.size - -/-- Compare Ix.ConstantInfo by structural equality (all fields). -/ -def ixConstantInfoEq (a b : Ix.ConstantInfo) : Bool := a == b - -/-- Compare RawEnvironment with content-aware comparison. - Checks that all constants in a have matching constants in b by name hash. -/ -def ixRawEnvironmentEq (a b : Ix.RawEnvironment) : Bool := - a.consts.size == b.consts.size && - a.consts.all fun (name, info) => - b.consts.any fun (name', info') => - ixNameEq name name' && ixConstantInfoEq info info' - -/-- Compare RustCompilePhases by checking sizes -/ -def rustCompilePhasesEq (a b : Ix.CompileM.RustCompilePhases) : Bool := - ixRawEnvironmentEq a.rawEnv b.rawEnv && - rustCondensedBlocksEq a.condensed b.condensed && - rawEnvEq a.compileEnv b.compileEnv - -/-! ## Ix.RawEnvironment unit tests -/ - -/-- Test empty RawEnvironment roundtrip -/ -def ixRawEnvironmentTests : TestSeq := - let empty : Ix.RawEnvironment := { consts := #[] } - -- Create a simple ConstantInfo for testing - let name := Ix.Name.mkStr Ix.Name.mkAnon "test" - let expr := Ix.Expr.mkSort Ix.Level.mkZero - let constVal : Ix.ConstantVal := { name := name, levelParams := #[], type := expr } - let axiomVal : Ix.AxiomVal := { cnst := constVal, isUnsafe := false } - let constInfo : Ix.ConstantInfo := .axiomInfo axiomVal - let withOne : Ix.RawEnvironment := { consts := #[(name, constInfo)] } - test "Ix.RawEnvironment empty" (ixRawEnvironmentEq (roundtripIxRawEnvironment empty) empty) ++ - test "Ix.RawEnvironment single const" (ixRawEnvironmentEq (roundtripIxRawEnvironment withOne) withOne) - -/-! ## RustCondensedBlocks unit tests -/ - -def rustCondensedBlocksTests : TestSeq := - let empty : Ix.RustCondensedBlocks := { lowLinks := #[], blocks := #[], blockRefs := #[] } - let n1 := Ix.Name.mkStr Ix.Name.mkAnon "a" - let n2 := Ix.Name.mkStr Ix.Name.mkAnon "b" - let withData : Ix.RustCondensedBlocks := { - lowLinks := #[(n1, n2)], - blocks := #[(n1, #[n1, n2])], - blockRefs := #[(n2, #[n1])] - } - test "RustCondensedBlocks empty" (rustCondensedBlocksEq (roundtripRustCondensedBlocks empty) empty) ++ - test "RustCondensedBlocks with data" (rustCondensedBlocksEq (roundtripRustCondensedBlocks withData) withData) - -/-! ## SerializeError, DecompileError, and CompileError roundtrip FFI -/ - -@[extern "rs_roundtrip_serialize_error"] -opaque roundtripSerializeError : @& Ixon.SerializeError → Ixon.SerializeError - -@[extern "rs_roundtrip_decompile_error"] -opaque roundtripDecompileError : @& Ix.DecompileM.DecompileError → Ix.DecompileM.DecompileError - -@[extern "rs_roundtrip_compile_error"] -opaque roundtripCompileError : @& Ix.CompileM.CompileError → Ix.CompileM.CompileError - -def serializeErrorTests : TestSeq := - test "SerializeError.unexpectedEof" (roundtripSerializeError (.unexpectedEof "u64") == .unexpectedEof "u64") ++ - test "SerializeError.invalidTag" (roundtripSerializeError (.invalidTag 0xFF "expr") == .invalidTag 0xFF "expr") ++ - test "SerializeError.invalidFlag" (roundtripSerializeError (.invalidFlag 3 "univ") == .invalidFlag 3 "univ") ++ - test "SerializeError.invalidVariant" (roundtripSerializeError (.invalidVariant 99 "const") == .invalidVariant 99 "const") ++ - test "SerializeError.invalidBool" (roundtripSerializeError (.invalidBool 2) == .invalidBool 2) ++ - test "SerializeError.addressError" (roundtripSerializeError .addressError == .addressError) ++ - test "SerializeError.invalidShareIndex" (roundtripSerializeError (.invalidShareIndex 5 10) == .invalidShareIndex 5 10) - -def decompileErrorTests : TestSeq := - let addr := Address.blake3 (ByteArray.mk #[1, 2, 3]) - let se := Ixon.SerializeError.unexpectedEof "test" - test "DecompileError.invalidRefIndex" (roundtripDecompileError (.invalidRefIndex 5 10 "test") == .invalidRefIndex 5 10 "test") ++ - test "DecompileError.invalidUnivIndex" (roundtripDecompileError (.invalidUnivIndex 3 7 "foo") == .invalidUnivIndex 3 7 "foo") ++ - test "DecompileError.invalidShareIndex" (roundtripDecompileError (.invalidShareIndex 2 5 "bar") == .invalidShareIndex 2 5 "bar") ++ - test "DecompileError.invalidRecIndex" (roundtripDecompileError (.invalidRecIndex 1 4 "baz") == .invalidRecIndex 1 4 "baz") ++ - test "DecompileError.invalidUnivVarIndex" (roundtripDecompileError (.invalidUnivVarIndex 8 3 "qux") == .invalidUnivVarIndex 8 3 "qux") ++ - test "DecompileError.missingAddress" (roundtripDecompileError (.missingAddress addr) == .missingAddress addr) ++ - test "DecompileError.missingMetadata" (roundtripDecompileError (.missingMetadata addr) == .missingMetadata addr) ++ - test "DecompileError.blobNotFound" (roundtripDecompileError (.blobNotFound addr) == .blobNotFound addr) ++ - test "DecompileError.badBlobFormat" (roundtripDecompileError (.badBlobFormat addr "UTF-8") == .badBlobFormat addr "UTF-8") ++ - test "DecompileError.badConstantFormat" (roundtripDecompileError (.badConstantFormat "bad") == .badConstantFormat "bad") ++ - test "DecompileError.serializeError" (roundtripDecompileError (.serializeError se) == .serializeError se) ++ - test "DecompileError.invalidMetaShareIndex" (roundtripDecompileError (.invalidMetaShareIndex 9 2 4 3 "meta") == .invalidMetaShareIndex 9 2 4 3 "meta") - -def compileErrorTests : TestSeq := - let addr := Address.blake3 (ByteArray.mk #[4, 5, 6]) - let se := Ixon.SerializeError.addressError - test "CompileError.missingConstant" (roundtripCompileError (.missingConstant "Nat.add") == .missingConstant "Nat.add") ++ - test "CompileError.missingAddress" (roundtripCompileError (.missingAddress addr) == .missingAddress addr) ++ - test "CompileError.invalidMutualBlock" (roundtripCompileError (.invalidMutualBlock "empty") == .invalidMutualBlock "empty") ++ - test "CompileError.unsupportedExpr" (roundtripCompileError (.unsupportedExpr "mvar") == .unsupportedExpr "mvar") ++ - test "CompileError.unknownUnivParam" (roundtripCompileError (.unknownUnivParam "Nat" "u") == .unknownUnivParam "Nat" "u") ++ - test "CompileError.serializeError" (roundtripCompileError (.serializeError se) == .serializeError se) ++ - test "CompileError.resourceLimit" (roundtripCompileError (.resourceLimit "memory reserve") == .resourceLimit "memory reserve") ++ - test "CompileError.sharingConstruction" (roundtripCompileError (.sharingConstruction "internal") == .sharingConstruction "internal") - -/-! ## Test Suite -/ - -public def suite : List TestSeq := [ - stringPayloadTests, - -- Block comparison types - blockCompareResultTests, - blockCompareDetailTests, - -- Error type roundtrips - serializeErrorTests, - decompileErrorTests, - compileErrorTests, - -- Environment unit tests - ixRawEnvironmentTests, - rustCondensedBlocksTests, - -- Property tests for basic Ix types - checkIO "Address roundtrip" (∀ a : Address, roundtripIxAddress a == a), - checkIO "Ix.Name roundtrip" (∀ n : Ix.Name, ixNameEq (roundtripIxName n) n), - checkIO "Ix.Level roundtrip" (∀ l : Ix.Level, ixLevelEq (roundtripIxLevel l) l), - checkIO "Ix.Expr roundtrip" (∀ e : Ix.Expr, ixExprEq (roundtripIxExpr e) e), - checkIO "Ix.Int roundtrip" (∀ i : Ix.Int, roundtripIxInt i == i), - checkIO "Ix.Substring roundtrip" (∀ s : Ix.Substring, roundtripIxSubstring s == s), - checkIO "Ix.SourceInfo roundtrip" (∀ si : Ix.SourceInfo, roundtripIxSourceInfo si == si), - checkIO "Ix.SyntaxPreresolved roundtrip" (∀ sp : Ix.SyntaxPreresolved, roundtripIxSyntaxPreresolved sp == sp), - checkIO "Ix.Syntax roundtrip" (∀ s : Ix.Syntax, roundtripIxSyntax s == s), - checkIO "Ix.DataValue roundtrip" (∀ dv : Ix.DataValue, roundtripIxDataValue dv == dv), - checkIO "Ix.ConstantInfo roundtrip" (∀ ci : Ix.ConstantInfo, ixConstantInfoEq (roundtripIxConstantInfo ci) ci), - -- Property tests for Environment types - checkIO "Ix.RawEnvironment roundtrip" (∀ env : Ix.RawEnvironment, ixRawEnvironmentEq (roundtripIxRawEnvironment env) env), - -- Composite types - checkIO "RustCondensedBlocks roundtrip" (∀ cb : Ix.RustCondensedBlocks, rustCondensedBlocksEq (roundtripRustCondensedBlocks cb) cb), - checkIO "RustCompilePhases roundtrip" (∀ p : Ix.CompileM.RustCompilePhases, rustCompilePhasesEq (roundtripRustCompilePhases p) p), - -- Error type property tests - checkIO "SerializeError roundtrip" (∀ e : Ixon.SerializeError, roundtripSerializeError e == e), - checkIO "DecompileError roundtrip" (∀ e : Ix.DecompileM.DecompileError, roundtripDecompileError e == e), - checkIO "CompileError roundtrip" (∀ e : Ix.CompileM.CompileError, roundtripCompileError e == e), -] - -end Tests.FFI.Ix diff --git a/Tests/FFI/Ixon.lean b/Tests/FFI/Ixon.lean index 9615586ad..eb1a644e3 100644 --- a/Tests/FFI/Ixon.lean +++ b/Tests/FFI/Ixon.lean @@ -1,13 +1,15 @@ -/- - Ixon.* type FFI roundtrip tests. - Pattern: Lean value → Rust (decode) → Rust (re-encode via C API) → Lean value → compare --/ module public import LSpec public import Tests.Gen.Ixon public import Ix.Ixon public import Ix.IxonUniv +public import Ix.Cli.DiffCmd + +/-! + Ixon.* type FFI roundtrip tests. + Pattern: Lean value → Rust (decode) → Rust (re-encode via C API) → Lean value → compare +-/ public section @@ -221,15 +223,6 @@ def constantMetaTests : TestSeq := @[extern "rs_eq_univ_serialization"] opaque rsEqUnivSerialization : @& Univ → @& ByteArray → Bool -/-- P5 mirror parity (canonicity §10.6): Rust `canon_univ` of arg 1 - equals the Lean-computed canonical form (arg 2) structurally. -/ -@[extern "rs_canon_univ_matches"] -opaque rsCanonUnivMatches : @& Univ → @& Univ → Bool - -/-- Twin parity for the frozen `mk*` rebuild closure. -/ -@[extern "rs_reduce_univ_matches"] -opaque rsReduceUnivMatches : @& Univ → @& Univ → Bool - @[extern "rs_eq_expr_serialization"] opaque rsEqExprSerialization : @& Expr → @& ByteArray → Bool @@ -354,11 +347,13 @@ def envDiffTests : TestSeq := let pb := dir / "b.ixe" IO.FS.writeBinFile pa (serEnv! envBase) IO.FS.writeBinFile pb (serEnv! envValueChanged) - let same ← IO.Process.output { cmd := ".lake/build/bin/ix", args := #["diff", pa.toString, pa.toString, "--meta"] } - let differ ← IO.Process.output { cmd := ".lake/build/bin/ix", args := #["diff", pa.toString, pb.toString, "--anon"] } + let (sameOut, sameCode) ← IO.FS.withIsolatedStreams + (diffCmd.validate [pa.toString, pa.toString, "--meta"]) + let (_, differCode) ← IO.FS.withIsolatedStreams + (diffCmd.validate [pa.toString, pb.toString, "--anon"]) IO.FS.removeDirAll dir - let ok := same.exitCode == 0 && same.stdout.contains "identical" && differ.exitCode == 1 - return (ok, if ok then 1 else 0, 1, if ok then none else some s!"CLI exits {same.exitCode}/{differ.exitCode}")) .done) ++ + let ok := sameCode == 0 && sameOut.contains "identical" && differCode == 1 + return (ok, if ok then 1 else 0, 1, if ok then none else some s!"CLI exits {sameCode}/{differCode}")) .done) ++ test "EnvDiff: self-diff empty (anon)" ((runDiff envBase envBase).any (·.isEmpty)) ++ test "EnvDiff: self-diff empty (meta)" @@ -607,18 +602,17 @@ def mixedChain (n : Nat) : Univ := imaxChain else .imax (.var 1) (.var 0)) (.var (if n % 2 == 0 then 0 else 1)) n /-- `canonUniv` on long `imax` chains. The `imax` distributions repeat calls - of the normalizer (`2ⁿ` calls on these chains); both normalizers skip a - repeated call (`CanonUniv.NState`, Rust `Seen`), so chains of 64 finish. - Checked by value (P0), idempotence (P1), the `mk*` fixpoint (P3) and - Lean/Rust mirror parity. -/ + of the normalizer (`2ⁿ` calls on these chains); the normalizer skips a + repeated call (`CanonUniv.NState`), so chains of 64 finish. + Checked by value (P0), idempotence (P1) and the `mk*` fixpoint (P3). -/ def canonUnivChainTests : TestSeq := let ok (u : Univ) : Bool := let c := Ixon.canonUniv u (Tests.Gen.Ixon.univDifferAt? 2 u c).isNone && Ixon.canonUniv c == c && - Ixon.reduceUniv c == c && rsCanonUnivMatches u c + Ixon.reduceUniv c == c test "canonUniv: the Cutsat-shaped chain of 64 imax is `u` (0 at u = 0, else u)" (Ixon.canonUniv (cutsatChain 64) == .var 0) - ++ test "canonUniv P0, P1, P3 and mirror parity: imax chains of 0–10, 24, 40 and 64 links" + ++ test "canonUniv P0, P1 and P3: imax chains of 0–10, 24, 40 and 64 links" (((List.range 11) ++ [24, 40, 64]).all fun n => ok (cutsatChain n) && ok (mixedChain n)) /-- `canonUniv` accumulates successor offsets in `Nat`: in `UInt64` an @@ -682,29 +676,6 @@ def suite : List TestSeq := [ (∀ env : RawEnv, selfDiffEmpty true env), ---- Env pack envPackTests, - ---- Universe canonicalization mirror parity (canonicity §10.6, P5). - -- Exhaustive over all ≤6-node terms (3 params): the property-relevant - -- shapes — nested imax at depth ≥ 3 — sit outside `genUniv`'s - -- shallow-resized sampling, and the sweep is deterministic and cheap. - checkIO "Ixon.canonUniv mirror parity (exhaustive ≤6 nodes)" - ((Tests.Gen.Ixon.enumerateUniv 6).all fun u => - rsCanonUnivMatches u (Ixon.canonUniv u)), - checkIO "Ixon.reduceUniv mirror parity (exhaustive ≤6 nodes)" - ((Tests.Gen.Ixon.enumerateUniv 6).all fun u => - rsReduceUnivMatches u (Ixon.reduceUniv u)), - checkIO "Ixon.canonUniv mirror parity (sampled)" - (∀ x : Univ, rsCanonUnivMatches x (Ixon.canonUniv x)), - -- The value-change witness family and the kernel level comparison's - -- biased random levels (deep imax-by-parameter chains over 3–4 - -- params, where the leak-free self-strip proviso decides the output): - -- both languages must make the same leak-free self-strip decisions. - test "Ixon.canonUniv mirror parity (value-change witness family)" - (Tests.Gen.Ixon.univWitnessFamily.all fun u => - rsCanonUnivMatches u (Ixon.canonUniv u)), - test "Ixon.canonUniv mirror parity (biased random, 3 and 4 params)" - ((Tests.Gen.Ixon.biasedUnivFamily 3 10 2000 41 - ++ Tests.Gen.Ixon.biasedUnivFamily 4 12 500 43).all fun u => - rsCanonUnivMatches u (Ixon.canonUniv u)), -- long imax chains: the normalizers' call memo (FU item 10) canonUnivChainTests, canonUnivOffsetTests, diff --git a/Tests/FFI/Refcount.lean b/Tests/FFI/Refcount.lean index c132a1f21..995dfd498 100644 --- a/Tests/FFI/Refcount.lean +++ b/Tests/FFI/Refcount.lean @@ -33,27 +33,14 @@ namespace Tests.FFI.Refcount @[extern "rs_refcount_borrow_to_owned_name"] opaque borrowToOwnedName : @& Ix.Name → Ix.Name -@[extern "rs_refcount_borrow_to_owned_level"] -opaque borrowToOwnedLevel : @& Ix.Level → Ix.Level - -@[extern "rs_refcount_borrow_to_owned_expr"] -opaque borrowToOwnedExpr : @& Ix.Expr → Ix.Expr - -- Multiple borrows from same parent @[extern "rs_refcount_multi_borrow_name"] opaque multiBorrowName : @& Ix.Name → String --- Deep borrow traversal -@[extern "rs_refcount_deep_borrow_expr"] -opaque deepBorrowExpr : @& Ix.Expr → Nat - -- Owned drop (NOT @&, Rust takes ownership and drops) @[extern "rs_refcount_owned_array_drop"] opaque ownedArrayDrop : Array Ix.Name → Nat -@[extern "rs_refcount_owned_list_drop"] -opaque ownedListDrop : List Ix.Expr → Nat - -- Clone and compare @[extern "rs_refcount_clone_and_compare"] opaque cloneAndCompare : @& Ix.Name → Nat @@ -62,10 +49,6 @@ opaque cloneAndCompare : @& Ix.Name → Nat @[extern "rs_refcount_roundtrip_loop"] opaque roundtripLoop : @& Ix.Name → USize → Ix.Name --- Nested borrowed collection traversal -@[extern "rs_refcount_nested_borrow"] -opaque nestedBorrow : @& Array (Ix.Name × Ix.Level) → Nat - -- Cache dedup: build N copies from cache @[extern "rs_refcount_cache_dedup"] opaque cacheDedup : @& Ix.Name → USize → Array Ix.Name @@ -85,31 +68,18 @@ opaque stringLifecycle : @& String → String @[extern "rs_refcount_alloc_drop_loop"] opaque allocDropLoop : @& Ix.Name → USize → Ix.Name --- Array element borrow roundtrip -@[extern "rs_refcount_array_element_borrow"] -opaque arrayElementBorrow : @& Array Ix.Level → Array Ix.Level - -- Multi-threaded tests using LeanShared on ix domain types @[extern "rs_mt_parallel_decode_names"] opaque mtParallelDecodeNames : @& Array Ix.Name → USize → Nat -@[extern "rs_mt_parallel_decode_exprs"] -opaque mtParallelDecodeExprs : @& Array Ix.Expr → USize → Nat - @[extern "rs_mt_parallel_roundtrip_names"] opaque mtParallelRoundtripNames : @& Array Ix.Name → USize → Array Ix.Name -@[extern "rs_mt_shared_expr_stress"] -opaque mtSharedExprStress : @& Ix.Expr → USize → USize → Nat - /-! ## Test data -/ private def testAnon : Ix.Name := Ix.Name.mkAnon private def testStr : Ix.Name := Ix.Name.mkStr (Ix.Name.mkStr Ix.Name.mkAnon "foo") "bar" private def testNum : Ix.Name := Ix.Name.mkNat (Ix.Name.mkStr Ix.Name.mkAnon "x") 42 -private def testLevel0 : Ix.Level := Ix.Level.mkZero -private def testLevel1 : Ix.Level := Ix.Level.mkSucc Ix.Level.mkZero -private def testLevelMax : Ix.Level := Ix.Level.mkMax Ix.Level.mkZero (Ix.Level.mkSucc Ix.Level.mkZero) -- Module-level definitions become persistent (m_rc == 0) after initialization private def persistentName : Ix.Name := Ix.Name.mkStr Ix.Name.mkAnon "persistent" @@ -121,10 +91,7 @@ private def persistentName : Ix.Name := Ix.Name.mkStr Ix.Name.mkAnon "persistent def borrowToOwnedTests : TestSeq := test "borrow→owned Name anonymous" (borrowToOwnedName testAnon == testAnon) ++ test "borrow→owned Name str" (borrowToOwnedName testStr == testStr) ++ - test "borrow→owned Name num" (borrowToOwnedName testNum == testNum) ++ - test "borrow→owned Level zero" (borrowToOwnedLevel testLevel0 == testLevel0) ++ - test "borrow→owned Level succ" (borrowToOwnedLevel testLevel1 == testLevel1) ++ - test "borrow→owned Level max" (borrowToOwnedLevel testLevelMax == testLevelMax) + test "borrow→owned Name num" (borrowToOwnedName testNum == testNum) /-! ## Multiple borrows from same object Verifies that reading fields multiple times from the same borrowed ctor @@ -134,32 +101,14 @@ def multiBorrowTests : TestSeq := let name := Ix.Name.mkStr Ix.Name.mkAnon "hello" test "multi-borrow Name str" (multiBorrowName name == "hello|hello") -/-! ## Deep borrow tests - Recursively traverses an Expr tree using only borrowed refs. - Would crash if any intermediate borrow was invalid. -/ - -def deepBorrowTests : TestSeq := - let e0 := Ix.Expr.mkBVar 0 - let e1 := Ix.Expr.mkBVar 1 - let e2 := Ix.Expr.mkBVar 2 - let e3 := Ix.Expr.mkBVar 3 - let app1 := Ix.Expr.mkApp e0 e1 - let app2 := Ix.Expr.mkApp app1 e2 - let app3 := Ix.Expr.mkApp app2 e3 - -- 3 app nodes + 4 bvar leaves = 7 total - test "deep borrow expr 7 nodes" (deepBorrowExpr app3 == 7) - /-! ## Owned drop tests - Passes owned (NOT @&) arrays/lists. Rust drops each element + Passes owned (NOT @&) arrays. Rust drops each element on scope exit. A missing drop leaks; an extra drop crashes. -/ def ownedDropTests : TestSeq := let names := #[testAnon, testStr, testNum] test "owned array drop 3 names" (ownedArrayDrop names == 3) ++ - test "owned array drop empty" (ownedArrayDrop #[] == 0) ++ - let exprs := [Ix.Expr.mkBVar 0, Ix.Expr.mkBVar 1, Ix.Expr.mkSort Ix.Level.mkZero] - test "owned list drop 3 exprs" (ownedListDrop exprs == 3) ++ - test "owned list drop empty" (ownedListDrop [] == 0) + test "owned array drop empty" (ownedArrayDrop #[] == 0) /-! ## Clone tests Makes two owned clones of a borrowed Name, decodes both. @@ -182,19 +131,6 @@ def roundtripLoopTests : TestSeq := test "roundtrip loop anon 10x" (roundtripLoop testAnon 10 == testAnon) ++ test "roundtrip loop num 10x" (roundtripLoop testNum 10 == testNum) -/-! ## Nested borrow tests - Iterates a borrowed Array of pairs, accessing both fields of each pair. - Would crash if array element borrows outlived the parent. -/ - -def nestedBorrowTests : TestSeq := - let pairs : Array (Ix.Name × Ix.Level) := #[ - (testAnon, testLevel0), - (testStr, testLevel1), - (testNum, testLevelMax) - ] - test "nested borrow 3 pairs" (nestedBorrow pairs == 3) ++ - test "nested borrow empty" (nestedBorrow #[] == 0) - /-! ## Persistent object tests Module-level defs have m_rc == 0. Refcount ops are no-ops. Verifies that the typed API handles these correctly. -/ @@ -229,15 +165,6 @@ def allocDropTests : TestSeq := test "alloc/drop 100x" (allocDropLoop testStr 100 == testStr) ++ test "alloc/drop 1000x" (allocDropLoop testNum 1000 == testNum) -/-! ## Array element borrow tests - Borrows each element of a borrowed array, decodes, rebuilds. -/ - -def arrayElementTests : TestSeq := - let levels := #[testLevel0, testLevel1, testLevelMax] - let result := arrayElementBorrow levels - test "array element borrow size" (result.size == 3) ++ - test "array element borrow eq" (result == levels) - /-! ## Multi-threaded LeanShared tests These exercise lean_mark_mt + atomic refcounting on ix domain types across real OS threads. A refcount bug would cause SIGSEGV or assertion failure. -/ @@ -248,30 +175,14 @@ def mtTests : TestSeq := test "MT parallel decode names 4 threads" (mtParallelDecodeNames names 4 == 12) ++ test "MT parallel decode names 1 thread" (mtParallelDecodeNames names 1 == 3) ++ test "MT parallel decode names empty" (mtParallelDecodeNames #[] 4 == 0) ++ - -- Parallel decode exprs - let exprs := #[ - Ix.Expr.mkBVar 0, - Ix.Expr.mkApp (Ix.Expr.mkBVar 0) (Ix.Expr.mkBVar 1), - Ix.Expr.mkSort Ix.Level.mkZero - ] - -- bvar=1, app(bvar,bvar)=3, sort=1 → 5 nodes per thread - test "MT parallel decode exprs 4 threads" (mtParallelDecodeExprs exprs 4 == 20) ++ - test "MT parallel decode exprs 1 thread" (mtParallelDecodeExprs exprs 1 == 5) ++ -- Parallel roundtrip: decode and rebuild from multiple threads test "MT parallel roundtrip names" (mtParallelRoundtripNames names 4 == names) ++ - test "MT parallel roundtrip empty" (mtParallelRoundtripNames #[] 4 == #[]) ++ - -- Stress test: rapid clone/drop on complex expr - let complexExpr := Ix.Expr.mkApp - (Ix.Expr.mkApp (Ix.Expr.mkBVar 0) (Ix.Expr.mkBVar 1)) - (Ix.Expr.mkSort Ix.Level.mkZero) - test "MT shared expr stress 4×100" (mtSharedExprStress complexExpr 4 100 == 4) ++ - test "MT shared expr stress 8×50" (mtSharedExprStress complexExpr 8 50 == 8) + test "MT parallel roundtrip empty" (mtParallelRoundtripNames #[] 4 == #[]) /-! ## Property tests -/ def propertyTests : TestSeq := checkIO "borrow→owned Name" (∀ n : Ix.Name, borrowToOwnedName n == n) ++ - checkIO "borrow→owned Level" (∀ l : Ix.Level, borrowToOwnedLevel l == l) ++ checkIO "clone always equal" (∀ n : Ix.Name, cloneAndCompare n == 1) ++ checkIO "roundtrip loop 3x" (∀ n : Ix.Name, roundtripLoop n 3 == n) @@ -280,16 +191,13 @@ def propertyTests : TestSeq := public def suite : List TestSeq := [ borrowToOwnedTests, multiBorrowTests, - deepBorrowTests, ownedDropTests, cloneTests, roundtripLoopTests, - nestedBorrowTests, persistentTests, cacheDedupTests, stringTests, allocDropTests, - arrayElementTests, mtTests, propertyTests, ] diff --git a/Tests/FFIMain.lean b/Tests/FFIMain.lean new file mode 100644 index 000000000..5f29c437e --- /dev/null +++ b/Tests/FFIMain.lean @@ -0,0 +1,11 @@ +import Tests.FFI + +/-! # The FFI suite alone (`lake exe ffi-tests`) + +The same suite as `lake test -- ffi`, in an executable of its own, so that +the merge queue's Valgrind job builds and runs the Rust FFI boundary without +the rest of `IxTests`, whose import closure holds the certification's +proofs. -/ + +def main (_args : List String) : IO UInt32 := + LSpec.lspecIO (.ofList [("ffi", Tests.FFI.suite)]) [] diff --git a/Tests/Fixtures/initstd-reference.txt b/Tests/Fixtures/initstd-reference.txt index 22555fb70..8252277fa 100644 --- a/Tests/Fixtures/initstd-reference.txt +++ b/Tests/Fixtures/initstd-reference.txt @@ -1,17 +1,24 @@ # The Init+Std reference artifact, checked by the `initstd-reference` test # (`lake test -- initstd-reference`, merge queue tier `ci:landing-initstd-cli`). # -# `sha256` and `bytes` are the SHA-256 and the size of the file that -# `ix compile-lean Benchmarks/Compile/CompileInitStd.lean --rust-check` writes: -# the certified compile with its report checks and the default role binding, -# byte-identical with the Rust compiler's output. -# `rust-kernel-passed` of `rust-kernel-checked` constants pass -# `ix check-rs --anon`, which must also admit every role the default -# table binds. +# `blake3` and `bytes` are the Blake3 digest and the size of the file that +# `ix compile-lean Benchmarks/Compile/CompileInitStd.lean` writes: +# the certified compile with its report checks and the default role binding. +# `ix check-lean --anon`, strict as every entry is by default, must +# admit every role the default table binds and refuse none, pass +# `lean-kernel-passed` of `lean-kernel-checked` constants, and refuse exactly the +# `lean-kernel-refused` addresses (the names are for the reader): it declines +# the non-standard axiom `Lean.trustCompiler` and blocks the four +# native-evaluation constants that depend on it, so it exits 3. # -# A change that moves the compiler's bytes, or the Rust kernel's verdicts on +# A change that moves the compiler's bytes, or the kernel's verdicts on # this artifact, updates these lines in the same commit and says why. -sha256 e48adef624e70bcdd3f22e1d96251682ac046a088700ccf5cf512f1b49afcda1 +blake3 a24fd146fcf3c5ed6e6a4821f5f122a6d397270f7eb3106bd7b75ff23eeca9a3 bytes 253772604 -rust-kernel-passed 99514 -rust-kernel-checked 99519 +lean-kernel-passed 99514 +lean-kernel-checked 99519 +lean-kernel-refused 78d8bc334c7fd622793102acbf33ef9e3cae52615dcf5a9d5d63a56520640af2 Lean.trustCompiler +lean-kernel-refused b6327d9a1bbb461a447f0c971e622dd9a35132bd7de75bf69e20794d7a0e8595 Lean.reduceNat +lean-kernel-refused 1e8072e4aaed4cffe4f3199c07d92107b597a6271e7a0cd33723c675d748a05b Lean.ofReduceNat +lean-kernel-refused bacc4c814a4572ddfd969a1857adfe18239eb6bd8b1627ee79744a6a9b23b030 Lean.reduceBool +lean-kernel-refused 560ece8034077f8861254f5e330e608f84b2779413c4bc27442b25a0683dba3b Lean.ofReduceBool diff --git a/Tests/Fixtures/ixon-v4/reject-parity.txt b/Tests/Fixtures/ixon-v4/reject-parity.txt index 5fd83ced7..a6392b5fc 100644 --- a/Tests/Fixtures/ixon-v4/reject-parity.txt +++ b/Tests/Fixtures/ixon-v4/reject-parity.txt @@ -1,5 +1,5 @@ # Byte strings every reader of their kind must refuse, in Rust -# (crates/ixon/tests/reject_parity.rs, crates/kernel/tests/reject_parity.rs) +# (crates/ixon/tests/reject_parity.rs, every kind but ixes) # and in Lean (Tests/Ix/IxonRejectParity.lean, the `ixon-reject-parity` # suite), with accepted controls beside them. # diff --git a/Tests/Gen/Ix.lean b/Tests/Gen/Ix.lean index 7fe60a3e2..c3fb90bae 100644 --- a/Tests/Gen/Ix.lean +++ b/Tests/Gen/Ix.lean @@ -1,7 +1,3 @@ -/- - Generators for Ix.* types (canonical Lean types with Blake3 hashes). - Generators for property-based FFI roundtrip tests. --/ module public import LSpec @@ -10,8 +6,11 @@ public import Tests.Gen.Ixon public import Ix.Address public import Ix.Environment public import Ix.CondenseM -public import Ix.CompileM -public import Ix.DecompileM + +/-! + Generators for Ix.* types (canonical Lean types with Blake3 hashes). + Generators for property-based FFI roundtrip tests. +-/ public section @@ -485,30 +484,6 @@ instance : Shrinkable Ix.ConstantInfo where instance : SampleableExt Ix.ConstantInfo := SampleableExt.mkSelfContained genIxConstantInfo -/-! ## Generators for Ix.RawEnvironment -/ - -/-- Generate small arrays for RawEnvironment to avoid memory issues -/ -def genSmallArray (g : Gen α) : Gen (Array α) := - resize (fun s => if s > 3 then 3 else s / 2) <| - Array.mk <$> (listOf g >>= fun l => pure (l.take 3)) - -/-- Generate a simple ConstantInfo (only axiomInfo for FFI stability) -/ -def genSimpleConstantInfo : Gen Ix.ConstantInfo := - Ix.ConstantInfo.axiomInfo <$> genIxAxiomVal - -/-- Generate a (Name × ConstantInfo) pair for RawEnvironment -/ -def genNameConstantPair : Gen (Ix.Name × Ix.ConstantInfo) := - Prod.mk <$> genIxName 3 <*> genSimpleConstantInfo - -/-- Generate a RawEnvironment with small arrays to avoid memory issues -/ -def genIxRawEnvironment : Gen Ix.RawEnvironment := - Ix.RawEnvironment.mk <$> genSmallArray genNameConstantPair - -instance : Shrinkable Ix.RawEnvironment where - shrink env := if env.consts.isEmpty then [] else [{ consts := env.consts.pop }] - -instance : SampleableExt Ix.RawEnvironment := SampleableExt.mkSelfContained genIxRawEnvironment - /-! ## Generators for Additional Ix Types -/ def genAddress : Gen Address := Tests.Gen.Ixon.genAddress @@ -540,148 +515,6 @@ instance : Shrinkable Ix.DataValue where instance : SampleableExt Ix.DataValue := SampleableExt.mkSelfContained genIxDataValue -/-! ## Generators for RustCondensedBlocks and RustCompilePhases -/ - -/-- Generate a (Name × Name) pair for lowLinks -/ -def genNamePair : Gen (Ix.Name × Ix.Name) := - Prod.mk <$> genIxName 3 <*> genIxName 3 - -/-- Generate a (Name × Array Name) pair for blocks/blockRefs -/ -def genNameArrayPair : Gen (Ix.Name × Array Ix.Name) := do - let name ← genIxName 3 - let arr ← genSmallArray (genIxName 3) - pure (name, arr) - -/-- Generate Ix.RustCondensedBlocks -/ -def genRustCondensedBlocks : Gen Ix.RustCondensedBlocks := - Ix.RustCondensedBlocks.mk - <$> genSmallArray genNamePair - <*> genSmallArray genNameArrayPair - <*> genSmallArray genNameArrayPair - -instance : Shrinkable Ix.RustCondensedBlocks where - shrink cb := - (if cb.lowLinks.size > 0 then [{ cb with lowLinks := cb.lowLinks.pop }] else []) ++ - (if cb.blocks.size > 0 then [{ cb with blocks := cb.blocks.pop }] else []) ++ - (if cb.blockRefs.size > 0 then [{ cb with blockRefs := cb.blockRefs.pop }] else []) - -instance : SampleableExt Ix.RustCondensedBlocks := SampleableExt.mkSelfContained genRustCondensedBlocks - -/-- Generate Ix.CompileM.RustCompilePhases -/ -def genRustCompilePhases : Gen Ix.CompileM.RustCompilePhases := - Ix.CompileM.RustCompilePhases.mk - <$> genIxRawEnvironment - <*> genRustCondensedBlocks - <*> Tests.Gen.Ixon.genRawEnv - -instance : Shrinkable Ix.CompileM.RustCompilePhases where - shrink p := - -- Shrink to empty structures - let empty : Ix.CompileM.RustCompilePhases := { - rawEnv := { consts := #[] }, - condensed := { lowLinks := #[], blocks := #[], blockRefs := #[] }, - compileEnv := { consts := #[], named := #[], blobs := #[], comms := #[] } - } - if p.rawEnv.consts.isEmpty && p.condensed.lowLinks.isEmpty && p.compileEnv.consts.isEmpty - then [] - else [empty] - -instance : SampleableExt Ix.CompileM.RustCompilePhases := SampleableExt.mkSelfContained genRustCompilePhases - -/-! ## Generators for SerializeError, CompileError, and DecompileError -/ - -instance : Inhabited Ixon.SerializeError where - default := .addressError - -instance : Inhabited Ix.DecompileM.DecompileError where - default := .badConstantFormat "" - -instance : Inhabited Ix.CompileM.CompileError where - default := .missingConstant "" - -/-- Generate a SerializeError with all variants -/ -def genSerializeError : Gen Ixon.SerializeError := do - let s ← genIxString - let byte ← Gen.choose Nat 0 255 - let idx ← Gen.choose Nat 0 100 - let len ← Gen.choose Nat 0 100 - Gen.frequency #[ - (1, pure (.unexpectedEof s)), - (1, pure (.invalidTag byte.toUInt8 s)), - (1, pure (.invalidFlag byte.toUInt8 s)), - (1, pure (.invalidVariant idx.toUInt64 s)), - (1, pure (.invalidBool byte.toUInt8)), - (1, pure .addressError), - (1, pure (.invalidShareIndex idx.toUInt64 len)) - ] (pure default) - -instance : Shrinkable Ixon.SerializeError where - shrink e := match e with - | .addressError => [] - | _ => [.addressError] - -instance : SampleableExt Ixon.SerializeError := - SampleableExt.mkSelfContained genSerializeError - -/-- Generate a DecompileError with all variants -/ -def genDecompileError : Gen Ix.DecompileM.DecompileError := do - let addr ← genAddress - let idx ← Gen.choose Nat 0 100 - let len ← Gen.choose Nat 0 100 - let s ← genIxString - let se ← genSerializeError - Gen.frequency #[ - (1, pure (.invalidRefIndex idx.toUInt64 len s)), - (1, pure (.invalidUnivIndex idx.toUInt64 len s)), - (1, pure (.invalidShareIndex idx.toUInt64 len s)), - (1, pure (.invalidRecIndex idx.toUInt64 len s)), - (1, pure (.invalidUnivVarIndex idx.toUInt64 len s)), - (1, pure (.missingAddress addr)), - (1, pure (.missingMetadata addr)), - (1, pure (.blobNotFound addr)), - (1, do let expected ← genIxString; pure (.badBlobFormat addr expected)), - (1, pure (.badConstantFormat s)), - (1, pure (.serializeError se)), - (1, do let entry ← Gen.choose Nat 0 100; let q ← Gen.choose Nat 0 100 - pure (.invalidMetaShareIndex idx.toUInt64 entry.toUInt64 len q s)) - ] (pure default) - -instance : Shrinkable Ix.DecompileM.DecompileError where - shrink e := match e with - | .badConstantFormat s => if s.isEmpty then [] else [.badConstantFormat ""] - | .serializeError se => - [.badConstantFormat ""] ++ (.serializeError <$> Shrinkable.shrink se) - | _ => [.badConstantFormat ""] - -instance : SampleableExt Ix.DecompileM.DecompileError := - SampleableExt.mkSelfContained genDecompileError - -/-- Generate a CompileError with all variants -/ -def genCompileError : Gen Ix.CompileM.CompileError := do - let addr ← genAddress - let s ← genIxString - let se ← genSerializeError - Gen.frequency #[ - (1, pure (.missingConstant s)), - (1, pure (.missingAddress addr)), - (1, pure (.invalidMutualBlock s)), - (1, pure (.unsupportedExpr s)), - (1, do let s2 ← genIxString; pure (.unknownUnivParam s s2)), - (1, pure (.serializeError se)), - (1, pure (.resourceLimit s)), - (1, pure (.sharingConstruction s)) - ] (pure default) - -instance : Shrinkable Ix.CompileM.CompileError where - shrink e := match e with - | .missingConstant s => if s.isEmpty then [] else [.missingConstant ""] - | .serializeError se => - [.missingConstant ""] ++ (.serializeError <$> Shrinkable.shrink se) - | _ => [.missingConstant ""] - -instance : SampleableExt Ix.CompileM.CompileError := - SampleableExt.mkSelfContained genCompileError - end Tests.Gen.Ix end diff --git a/Tests/Ix/CanonM.lean b/Tests/Ix/CanonM.lean deleted file mode 100644 index 09aaa2cc5..000000000 --- a/Tests/Ix/CanonM.lean +++ /dev/null @@ -1,511 +0,0 @@ -/- - Unit tests for CanonM module - verifies canonicalization roundtrips. --/ - -module -public import Ix.CanonM -public import Ix.Environment -public import Ix.Meta -public import LSpec - -open LSpec Ix.CanonM - -namespace Tests.CanonM - -/-! ## Name roundtrip tests -/ - -def testNameRoundtrip : TestSeq := - let names : List Lean.Name := [ - .anonymous, - .str .anonymous "foo", - .str .anonymous "bar", - .str (.str .anonymous "Foo") "bar", - .num .anonymous 0, - .num .anonymous 42, - .num (.str .anonymous "test") 123 - ] - group "name roundtrip" <| names.foldl (init := .done) fun acc n => - let stt : CanonState := {} - let (name, _stt') := StateT.run (canonName n) stt - let ustt : UncanonState := { names := {}, levels := {}, exprs := {} } - let (leanName, _) := StateT.run (uncanonName name) ustt - acc ++ test s!"{n}" (n == leanName) - -/-! ## Level roundtrip tests -/ - -def testLevelRoundtrip : TestSeq := - let levels : List Lean.Level := [ - .zero, - .succ .zero, - .succ (.succ .zero), - .max .zero .zero, - .max (.succ .zero) .zero, - .imax .zero .zero, - .param `u, - .param `v, - .max (.param `u) (.succ (.param `v)) - ] - group "level roundtrip" <| levels.foldl (init := .done) fun acc l => - let stt : CanonState := {} - let (level, _stt') := StateT.run (canonLevel l) stt - let ustt : UncanonState := { names := {}, levels := {}, exprs := {} } - let (leanLevel, _) := StateT.run (uncanonLevel level) ustt - acc ++ test s!"{l}" (l == leanLevel) - -/-! ## Expr roundtrip tests -/ - -def testExprRoundtrip : TestSeq := - let exprs : List Lean.Expr := [ - .bvar 0, - .bvar 42, - .sort .zero, - .sort (.succ .zero), - .const `Nat [], - .const `List [.zero], - .const `Eq [.param `u], - .app (.const `Nat.succ []) (.bvar 0), - .lam `x (.const `Nat []) (.bvar 0) .default, - .forallE `x (.const `Nat []) (.const `Nat []) .default, - .lit (.natVal 0), - .lit (.natVal 42), - .lit (.strVal "hello"), - .lit (.strVal "") - ] - group "expr roundtrip" <| exprs.foldl (init := .done) fun acc e => - let stt : CanonState := {} - let (expr, _stt') := StateT.run (canonExpr e) stt - let ustt : UncanonState := { names := {}, levels := {}, exprs := {} } - let (leanExpr, _) := StateT.run (uncanonExpr expr) ustt - acc ++ test s!"{e}" (e == leanExpr) - -/-! ## Hash determinism tests -/ - -def testHashDeterminism : TestSeq := - group "hash determinism" <| - let n1 := Ix.Name.mkAnon - let n2 := Ix.Name.mkAnon - test "mkAnon same hash" (n1.getHash == n2.getHash) ++ - let n3 := Ix.Name.mkStr Ix.Name.mkAnon "foo" - let n4 := Ix.Name.mkStr Ix.Name.mkAnon "foo" - test "mkStr same hash" (n3.getHash == n4.getHash) ++ - let n5 := Ix.Name.mkStr Ix.Name.mkAnon "foo" - let n6 := Ix.Name.mkStr Ix.Name.mkAnon "bar" - test "different strings different hash" (n5.getHash != n6.getHash) ++ - let l1 := Ix.Level.mkZero - let l2 := Ix.Level.mkZero - test "mkZero same hash" (l1.getHash == l2.getHash) ++ - let l3 := Ix.Level.mkSucc Ix.Level.mkZero - let l4 := Ix.Level.mkSucc Ix.Level.mkZero - test "mkSucc same hash" (l3.getHash == l4.getHash) - -/-! ## Interning tests -/ - -def testInterning : TestSeq := - group "interning" <| - -- Same Lean name should produce same pointer - let stt : CanonState := {} - let n := Lean.Name.mkStr .anonymous "test" - let (name1, stt') := StateT.run (canonName n) stt - let (name2, _) := StateT.run (canonName n) stt' - -- They should have the same hash - test "same name same hash" (name1.getHash == name2.getHash) - -/-! ## Full suite -/ - -public def suite : TestSeq := - group "CanonM" <| - testNameRoundtrip ++ - testLevelRoundtrip ++ - testExprRoundtrip ++ - testHashDeterminism ++ - testInterning - -/-! ## Environment canonicalization test (IO) -/ - -/-- FFI to canonicalize environment in Rust and return Ix.RawEnvironment. - Takes the original environment as List (Lean.Name × Lean.ConstantInfo), - returns arrays of pairs that Lean converts to HashMaps. -/ -@[extern "rs_canonicalize_env_to_ix"] -opaque rsCanonicalizeEnvToIxRaw : - @& List (Lean.Name × Lean.ConstantInfo) → - IO Ix.RawEnvironment - -/-- Canonicalize environment in Rust and convert to Ix.Environment. -/ -def rsCanonicalizeEnvToIx (consts : List (Lean.Name × Lean.ConstantInfo)) : IO Ix.Environment := do - let raw ← rsCanonicalizeEnvToIxRaw consts - pure raw.toEnvironment - -/-! ## Ix.ConstantInfo comparison -/ - -/-- Compare two Ix.ConstantVal for equality. -/ -def ixConstantValEq (a b : Ix.ConstantVal) : Bool := - a.name == b.name && - a.levelParams == b.levelParams && - a.type == b.type - -/-- Compare two Ix.RecursorRule for equality. -/ -def ixRecursorRuleEq (a b : Ix.RecursorRule) : Bool := - a.ctor == b.ctor && a.nfields == b.nfields && a.rhs == b.rhs - -/-- Compare two Ix.ConstantInfo for full equality. -/ -def ixConstInfoEq (a b : Ix.ConstantInfo) : Bool := - match a, b with - | .axiomInfo v1, .axiomInfo v2 => - ixConstantValEq v1.cnst v2.cnst && v1.isUnsafe == v2.isUnsafe - | .defnInfo v1, .defnInfo v2 => - ixConstantValEq v1.cnst v2.cnst && - v1.value == v2.value && - v1.hints == v2.hints && - v1.safety == v2.safety && - v1.all == v2.all - | .thmInfo v1, .thmInfo v2 => - ixConstantValEq v1.cnst v2.cnst && - v1.value == v2.value && - v1.all == v2.all - | .opaqueInfo v1, .opaqueInfo v2 => - ixConstantValEq v1.cnst v2.cnst && - v1.value == v2.value && - v1.isUnsafe == v2.isUnsafe && - v1.all == v2.all - | .quotInfo v1, .quotInfo v2 => - ixConstantValEq v1.cnst v2.cnst && v1.kind == v2.kind - | .inductInfo v1, .inductInfo v2 => - ixConstantValEq v1.cnst v2.cnst && - v1.numParams == v2.numParams && - v1.numIndices == v2.numIndices && - v1.all == v2.all && - v1.ctors == v2.ctors && - v1.numNested == v2.numNested && - v1.isRec == v2.isRec && - v1.isUnsafe == v2.isUnsafe && - v1.isReflexive == v2.isReflexive - | .ctorInfo v1, .ctorInfo v2 => - ixConstantValEq v1.cnst v2.cnst && - v1.induct == v2.induct && - v1.cidx == v2.cidx && - v1.numParams == v2.numParams && - v1.numFields == v2.numFields && - v1.isUnsafe == v2.isUnsafe - | .recInfo v1, .recInfo v2 => - ixConstantValEq v1.cnst v2.cnst && - v1.all == v2.all && - v1.numParams == v2.numParams && - v1.numIndices == v2.numIndices && - v1.numMotives == v2.numMotives && - v1.numMinors == v2.numMinors && - v1.rules.size == v2.rules.size && - (v1.rules.zip v2.rules |>.all fun (r1, r2) => ixRecursorRuleEq r1 r2) && - v1.k == v2.k && - v1.isUnsafe == v2.isUnsafe - | _, _ => false - -/-- Describe which field differs between two Ix.ConstantInfo. -/ -def ixConstInfoDiff (a b : Ix.ConstantInfo) : String := - match a, b with - | .axiomInfo v1, .axiomInfo v2 => - if v1.cnst.name != v2.cnst.name then "name" - else if v1.cnst.levelParams != v2.cnst.levelParams then "levelParams" - else if v1.cnst.type != v2.cnst.type then "type" - else if v1.isUnsafe != v2.isUnsafe then "isUnsafe" - else "unknown" - | .defnInfo v1, .defnInfo v2 => - if v1.cnst.name != v2.cnst.name then "name" - else if v1.cnst.levelParams != v2.cnst.levelParams then "levelParams" - else if v1.cnst.type != v2.cnst.type then "type" - else if v1.value != v2.value then "value" - else if v1.hints != v2.hints then "hints" - else if v1.safety != v2.safety then "safety" - else if v1.all != v2.all then "all" - else "unknown" - | .thmInfo v1, .thmInfo v2 => - if v1.cnst.name != v2.cnst.name then "name" - else if v1.cnst.type != v2.cnst.type then "type" - else if v1.value != v2.value then "value" - else "unknown" - | _, _ => s!"variant mismatch: {a.getCnst.name} vs {b.getCnst.name}" - -/-! ## Consolidated canonicalization roundtrip test - -This test verifies the full canonicalization pipeline: -1. Get Lean.Environment -2. Canonicalize via Rust FFI → Ix.Environment -3. Compare Rust Ix.Environment against original env (iterate env.constants) -4. Canonicalize via Lean → Ix.Environment -5. Compare Lean Ix.Environment against Rust (should be identical) -6. Uncanonicalize back to Lean.Environment -7. Compare roundtripped environment to original (iterate env.constants) --/ - -/-- Run the full canonicalization roundtrip test. -/ -def testFullCanonRoundtrip : TestSeq := - .individualIO "full canonicalization roundtrip" none (do - let env ← get_env! - let numConsts := env.constants.toList.length - - IO.println s!"[Test] Starting canonicalization roundtrip test" - IO.println s!"[Test] Environment has {numConsts} constants" - IO.println "" - - -- Step 1: Canonicalize in Rust - IO.println s!"[Test] Step 1: Canonicalizing in Rust..." - let rustStart ← IO.monoMsNow - let rustIxEnv ← rsCanonicalizeEnvToIx env.constants.toList - let rustTime := (← IO.monoMsNow) - rustStart - IO.println s!"[Test] Rust: {rustIxEnv.consts.size} consts in {formatTime rustTime}" - IO.println "" - - -- Step 2: Canonicalize in Lean (parallel) - IO.println s!"[Test] Step 2: Canonicalizing in Lean (parallel)..." - let leanStart ← IO.monoMsNow - let leanIxConsts := canonEnvParallel env - IO.println s!"[Test] Lean: {leanIxConsts.size} consts" - let leanTime := (← IO.monoMsNow) - leanStart - IO.println s!" in {formatTime leanTime}" - IO.println "" - - -- Step 3: Compare Rust vs Lean by iterating env.constants - IO.println s!"[Test] Step 3: Comparing Rust vs Lean..." - let compareStart ← IO.monoMsNow - let mut mismatches := 0 - let mut rustMissing := 0 - let mut leanMissing := 0 - let mut processed := 0 - let mut lastReport := 0 - - for (name, _) in env.constants do - -- Compute canonical name to look up in both environments - let stt : CanonState := {} - let (ixName, _) := StateT.run (canonName name) stt - - -- Direct HashMap lookup (now that Hashable matches Rust) - let rustResult := rustIxEnv.consts.get? ixName - let leanResult := leanIxConsts.get? ixName - - match rustResult, leanResult with - | some rustConst, some leanConst => - if !ixConstInfoEq rustConst leanConst then - if mismatches < 5 then - let diff := ixConstInfoDiff rustConst leanConst - IO.println s!"[Test] Mismatch: {name} ({diff})" - mismatches := mismatches + 1 - | none, some _ => - if rustMissing < 5 then - IO.println s!"[Test] Missing in Rust: {name}" - rustMissing := rustMissing + 1 - | some _, none => - if leanMissing < 5 then - IO.println s!"[Test] Missing in Lean: {name}" - leanMissing := leanMissing + 1 - | none, none => - -- Both missing - this shouldn't happen since we're iterating env.constants - if mismatches < 5 then - IO.println s!"[Test] Missing in both: {name}" - mismatches := mismatches + 1 - - processed := processed + 1 - if processed - lastReport >= 10000 then - IO.print s!"\r[Test] Compared {processed}/{numConsts}... " - (← IO.getStdout).flush - lastReport := processed - - let compareTime := (← IO.monoMsNow) - compareStart - IO.println s!"\r[Test] Compared {processed}: {mismatches} mismatches, {rustMissing} missing in Rust, {leanMissing} missing in Lean ({formatTime compareTime})" - - if rustMissing > 0 || leanMissing > 0 || mismatches > 0 then - return (false, 0, 0, some s!"Rust vs Lean: {mismatches} mismatches, {rustMissing} missing in Rust, {leanMissing} missing in Lean") - IO.println "" - - -- Step 4: Uncanonicalize Lean's Ix constants back to Lean (parallel) - IO.println s!"[Test] Step 4: Uncanonicalize Lean's Ix constants (parallel)..." - let uncanonStart ← IO.monoMsNow - let roundtripped := uncanonEnvParallel leanIxConsts - IO.println s!"[Test] Uncanonicalized {roundtripped.size}" - let uncanonTime := (← IO.monoMsNow) - uncanonStart - IO.println s!" in {formatTime uncanonTime}" - IO.println "" - - -- Step 5: Compare roundtripped to original (parallel with pointer-pair caching) - IO.println s!"[Test] Step 5: Comparing roundtripped to original (parallel)..." - let verifyStart ← IO.monoMsNow - -- Convert env.constants (SMap) to HashMap for parallel comparison - let origMap : Std.HashMap Lean.Name Lean.ConstantInfo := - env.constants.fold (init := {}) fun acc name const => acc.insert name const - let (rtMismatches, rtMissing, mismatchNames, missingNames) := compareEnvsParallel origMap roundtripped - for name in missingNames.toList.take 5 do - IO.println s!"[Test] Missing after roundtrip: {name}" - for name in mismatchNames.toList.take 5 do - IO.println s!"[Test] Mismatch after roundtrip: {name}" - let verifyTime := (← IO.monoMsNow) - verifyStart - IO.println s!"[Test] Verified {numConsts}: {rtMissing} missing, {rtMismatches} mismatches ({formatTime verifyTime})" - IO.println "" - - -- Summary - let totalTime := rustTime + leanTime + compareTime + uncanonTime + verifyTime - let speedup := if rustTime > 0 then leanTime / rustTime else 0 - IO.println s!"[Test] Summary:" - IO.println s!"[Test] Total time: {formatTime totalTime}" - IO.println s!"[Test] Rust canonicalize: {formatTime rustTime}" - IO.println s!"[Test] Lean canonicalize: {formatTime leanTime}" - IO.println s!"[Test] Rust speedup: ~{speedup}x" - IO.println "" - - let success := rustMissing == 0 && leanMissing == 0 && mismatches == 0 && rtMissing == 0 && rtMismatches == 0 - let failMsg := if !success then - some s!"rustMissing={rustMissing}, leanMissing={leanMissing}, mismatches={mismatches}, rtMissing={rtMissing}, rtMismatches={rtMismatches}" - else none - - pure (success, 0, 0, failMsg) - ) .done - -/-! ## Pure Lean canonicalization roundtrip test - -This test verifies canonicalization and uncanonicalization work correctly -in pure Lean without any Rust FFI: -1. Get Lean.Environment -2. Canonicalize via Lean → Ix.Environment -3. Uncanonicalize back to Lean.Environment -4. Compare roundtripped environment to original --/ - -/-- Run the pure Lean canonicalization roundtrip test. -/ -def testPureLeanRoundtrip : TestSeq := - .individualIO "pure Lean canonicalization roundtrip" none (do - let env ← get_env! - let numConsts := env.constants.toList.length - - IO.println s!"[Test] Starting pure Lean canonicalization roundtrip test" - IO.println s!"[Test] Environment has {numConsts} constants" - IO.println "" - - -- Step 1: Canonicalize in Lean - IO.println s!"[Test] Step 1: Canonicalizing in Lean..." - let canonStart ← IO.monoMsNow - let (ixEnv, _) := StateT.run (canonEnv env) {} - IO.println s!"[Test] Canonicalized {ixEnv.consts.size} consts" - let canonTime := (← IO.monoMsNow) - canonStart - IO.println s!" in {formatTime canonTime}" - - -- Step 2: Uncanonicalize back to Lean - IO.println s!"[Test] Step 2: Uncanonicalize back to Lean..." - let uncanonStart ← IO.monoMsNow - let (env2, _) := StateT.run (uncanonEnv ixEnv) {} - IO.println s!"[Test] Uncanonicalized {env2.size} constants" - let uncanonTime := (← IO.monoMsNow) - uncanonStart - IO.println s!" in {formatTime uncanonTime}" - - -- Step 3: Compare roundtripped to original (parallel) - IO.println s!"[Test] Step 3: Comparing roundtripped to original (parallel)..." - let verifyStart ← IO.monoMsNow - let origMap : Std.HashMap Lean.Name Lean.ConstantInfo := - env.constants.fold (init := {}) fun acc name const => acc.insert name const - let (mismatches, missing, mismatchNames, missingNames) := compareEnvsParallel origMap env2 - for name in missingNames.toList.take 5 do - IO.println s!"[Test] Missing after roundtrip: {name}" - for name in mismatchNames.toList.take 5 do - IO.println s!"[Test] Mismatch after roundtrip: {name}" - let verifyTime := (← IO.monoMsNow) - verifyStart - IO.println s!"[Test] Verified {numConsts}: {missing} missing, {mismatches} mismatches ({formatTime verifyTime})" - IO.println "" - - -- Summary - let totalTime := canonTime + uncanonTime + verifyTime - IO.println s!"[Test] Summary:" - IO.println s!"[Test] Total time: {formatTime totalTime}" - IO.println s!"[Test] Canonicalize: {formatTime canonTime}" - IO.println s!"[Test] Uncanonicalize: {formatTime uncanonTime}" - IO.println s!"[Test] Verify: {formatTime verifyTime}" - IO.println "" - - let success := missing == 0 && mismatches == 0 - let failMsg := if !success then - some s!"missing={missing}, mismatches={mismatches}" - else none - - pure (success, 0, 0, failMsg) - ) .done - -/-! ## Parallel Lean canonicalization roundtrip test - -This test verifies parallel canonicalization using ShardMap: -1. Get Lean.Environment -2. Canonicalize via ParallelCanonM → Ix.Environment -3. Uncanonicalize back to Lean.Environment -4. Compare roundtripped environment to original --/ - -/-- Run the parallel Lean canonicalization roundtrip test. -/ -def testParallelLeanRoundtrip : TestSeq := - .individualIO "parallel Lean canonicalization roundtrip" none (do - let env ← get_env! - let numConsts := env.constants.toList.length - - IO.println s!"[Test] Starting parallel Lean canonicalization roundtrip test" - IO.println s!"[Test] Environment has {numConsts} constants" - IO.println "" - - -- Step 1: Canonicalize in parallel - IO.println s!"[Test] Step 1: Canonicalizing in parallel..." - let canonStart ← IO.monoMsNow - let ixConsts := canonEnvParallel env - IO.println s!"[Test] Canonicalized {ixConsts.size} consts" - let canonTime := (← IO.monoMsNow) - canonStart - IO.println s!" in {formatTime canonTime}" - - -- Step 2: Uncanonicalize back to Lean (parallel) - IO.println s!"[Test] Step 2: Uncanonicalize back to Lean (parallel)..." - let uncanonStart ← IO.monoMsNow - let env2 := uncanonEnvParallel ixConsts - IO.println s!"[Test] Uncanonicalized {env2.size} constants" - let uncanonTime := (← IO.monoMsNow) - uncanonStart - IO.println s!" in {formatTime uncanonTime}" - - -- Step 3: Compare roundtripped to original (parallel) - IO.println s!"[Test] Step 3: Comparing roundtripped to original (parallel)..." - let verifyStart ← IO.monoMsNow - let origMap : Std.HashMap Lean.Name Lean.ConstantInfo := - env.constants.fold (init := {}) fun acc name const => acc.insert name const - let (mismatches, missing, mismatchNames, missingNames) := compareEnvsParallel origMap env2 - for name in missingNames.toList.take 5 do - IO.println s!"[Test] Missing after roundtrip: {name}" - for name in mismatchNames.toList.take 5 do - IO.println s!"[Test] Mismatch after roundtrip: {name}" - let verifyTime := (← IO.monoMsNow) - verifyStart - IO.println s!"[Test] Verified {numConsts}: {missing} missing, {mismatches} mismatches ({formatTime verifyTime})" - IO.println "" - - -- Summary - let totalTime := canonTime + uncanonTime + verifyTime - IO.println s!"[Test] Summary:" - IO.println s!"[Test] Total time: {formatTime totalTime}" - IO.println s!"[Test] Canonicalize: {formatTime canonTime}" - IO.println s!"[Test] Uncanonicalize: {formatTime uncanonTime}" - IO.println s!"[Test] Verify: {formatTime verifyTime}" - IO.println "" - - let success := missing == 0 && mismatches == 0 - let failMsg := if !success then - some s!"missing={missing}, mismatches={mismatches}" - else none - - pure (success, 0, 0, failMsg) - ) .done - -public section - -/-- Full canonicalization roundtrip test suite (expensive, in ignored tests). -/ -def rustSuiteIO : List TestSeq := [ - testFullCanonRoundtrip, -] - -def serialSuiteIO : List TestSeq := [ - testPureLeanRoundtrip -] - -def parallelSuiteIO : List TestSeq := [ - testParallelLeanRoundtrip -] - -end - -end Tests.CanonM - diff --git a/Tests/Ix/Catalog.lean b/Tests/Ix/Catalog.lean index bd1bc3047..7a50b2638 100644 --- a/Tests/Ix/Catalog.lean +++ b/Tests/Ix/Catalog.lean @@ -1,4 +1,13 @@ -/- +module + +public import LSpec +public import Ix.Catalog +public import Ix.CompileM +public import Ix.CompileCertified +public import Ix.Cli.CatalogCmd +public import IxC.Kernel.Ixon.Prelude + +/-! `.ixc` catalog format suite (primary, network-free). Two fixture pieces are compiled in-process from a self-contained @@ -21,18 +30,11 @@ drift, truncation, trailing bytes, a preimage tag other than 0 or 1, duplicate env roots or labels, non-topo deps, path-traversal labels, chunk owners out of range). - - Strict-anon compile (`rs_compile_env_anon`): §5 empty, §3 hints + - Strict-anon compile: §5 empty, §3 hints SURVIVE (the `finalize_hints` ordering pin from the plan's risk table), and the env root equals the named compile's — the anon-invariance the old C5 suite asserted, now structural. -/ -module - -public import LSpec -public import Ix.Catalog -public import Ix.CompileM -public import Ix.Cli.CatalogCmd -public import IxC.Kernel.Ixon.Prelude public section @@ -102,6 +104,18 @@ private def bindPiece (path : String) : IO Unit := do let bound := { env with roleBinding := some { address, bytes } } IO.FS.writeBinFile path (← IO.ofExcept (Ixon.serEnv bound)) +private def envRootOf (env : Ixon.Env) : String := + toString ((Ix.Merkle.merkleRootCanonical (env.consts.toList.map (·.1)).toArray).getD Ix.Merkle.zeroAddress) + +private def compilePieceToFile (consts : List (Lean.Name × Lean.ConstantInfo)) + (path : String) (anon : Bool := false) : IO String := do + let (env0, failures) ← IO.ofExcept (Ix.Cert.compileReport (Ix.CompileM.canonInput consts.toArray 4)) + unless failures.isEmpty do + throw (IO.userError s!"catalog fixture compile failed: {failures.toMap.size} failures") + let env := if anon then { env0 with named := {}, names := {}, addrToName := {} } else env0 + IO.FS.writeBinFile path (← IO.ofExcept (Ixon.serEnv env)) + return envRootOf env + /-- The end-to-end fixture gate: compile two overlapping pieces, assemble, and pin format parity + identity dedup. -/ private def assembleParityTest : IO (Bool × Nat × Nat × Option String) := do @@ -112,8 +126,8 @@ private def assembleParityTest : IO (Bool × Nat × Nat × Option String) := do let constsB ← fixtureConsts [defnOver `TCat.idA 1, defnOver `TCat.idB 2] let pieceA := (dir / "A.ixe").toString let pieceB := (dir / "B.ixe").toString - let sA ← Ix.CompileM.rsCompileEnvBytesFFI constsA pieceA false - let sB ← Ix.CompileM.rsCompileEnvBytesFFI constsB pieceB false + let rootA ← compilePieceToFile constsA pieceA false + let rootB ← compilePieceToFile constsB pieceB false bindPiece pieceA bindPiece pieceB -- Assemble via the Rust core (the production path): a @@ -147,9 +161,9 @@ private def assembleParityTest : IO (Bool × Nat × Nat × Option String) := do -- Identity dedup: A's content ⊆ B's, so the union root IS B's -- env root — the virtual union env coincides with B. ("union collapses shared content (content_root = B's env root)", - toString cat.contentRoot == sB.root), + toString cat.contentRoot == rootB), ("piece A root recorded", - toString cat.members[0]!.envRoot == sA.root), + toString cat.members[0]!.envRoot == rootA), ("deps carried", cat.members[1]!.deps == #[0]), ("fat profile", !(cat.storage matches .chunked _)) ] match checks.find? (!·.2) with @@ -167,8 +181,8 @@ private def anonCompileTest : IO (Bool × Nat × Nat × Option String) := do let consts ← fixtureConsts [defnOver `TCat.idA 1] let named := (dir / "named.ixe").toString let anon := (dir / "anon.ixe").toString - let sNamed ← Ix.CompileM.rsCompileEnvBytesFFI consts named false - let sAnon ← Ix.CompileM.rsCompileEnvBytesAnonFFI consts anon false + let rootNamed ← compilePieceToFile consts named false + let rootAnon ← compilePieceToFile consts anon true let countsOf (path : String) : IO (Except String Lean.Json) := do match Lean.Json.parse (← rsEnvSectionCountsFFI path) with | .ok j => return .ok j @@ -180,7 +194,7 @@ private def anonCompileTest : IO (Bool × Nat × Nat × Option String) := do | .ok j => pure j | .error e => return (false, 0, 0, some s!"counts(anon): {e}") let checks : List (String × Bool) := [ - ("roots equal (anon-invariance)", sNamed.root == sAnon.root), + ("roots equal (anon-invariance)", rootNamed == rootAnon), ("named piece carries §5", (num cNamed "named").getD 0 > 0), ("anon piece has empty §5", num cAnon "named" == some 0), ("§2 identical", num cNamed "consts" == num cAnon "consts"), diff --git a/Tests/Ix/Cert/SourceGraph.lean b/Tests/Ix/Cert/SourceGraph.lean deleted file mode 100644 index 02253180b..000000000 --- a/Tests/Ix/Cert/SourceGraph.lean +++ /dev/null @@ -1,88 +0,0 @@ -import Ix.Compile.SourceGraph -import Ix.Cert.Canon -import Ix.Cert.Statement - -/-! Capture and collector controls. Synthetic records test exact data flow; -they do not claim Source acceptance or execution of the streaming compiler. -/ -namespace Tests.Ix.Cert.SourceGraph - -open _root_.Ix.Compile.SourceGraph - -private def addr (n : Nat) : Address := ⟨⟨Array.replicate 32 n.toUInt8⟩⟩ -private def name (n : Nat) : _root_.Ix.Name := .str (.anonymous (addr 0)) s!"n{n}" (addr n) -private def sameHash : _root_.Ix.Name := .str (.anonymous (addr 0)) "alias1" (addr 1) -private def sort : _root_.Ix.Expr := .sort (.zero (addr 50)) (addr 51) -private def ref (n : Nat) : _root_.Ix.Expr := .const (name n) #[] (addr (n + 100)) -private def cv (n : _root_.Ix.Name) : _root_.Ix.ConstantVal := ⟨n, #[], sort⟩ -private def code : _root_.Ix.ConstantInfo := .defnInfo ⟨cv (name 1), ref 2, .abbrev, .safe, #[name 1]⟩ -private def streamed : _root_.Ix.ConstantInfo := .opaqueInfo ⟨cv sameHash, ref 3, false, #[sameHash]⟩ -private def proof : _root_.Ix.ConstantInfo := .thmInfo ⟨cv sameHash, ref 4, #[sameHash]⟩ -private def other : _root_.Ix.ConstantInfo := .axiomInfo ⟨cv (name 9), false⟩ -/-- The input map the collector's rows follow, with the same keys and insertion order. -/ -private def full (xs : List (_root_.Ix.Name × _root_.Ix.ConstantInfo)) : _root_.Ix.Environment := - { consts := xs.foldl (fun m p => m.insert p.1 p.2) {} } -private def raw := [(name 1, code), (name 9, other), (sameHash, streamed)] -private def result := collect (raw.map fun (n, ci) => rowOfCanonical n ci) -private def hybrid : _root_.Ix.Environment := { consts := result.code, fallback? := some (full raw).get? } -private def noNode : Node := ⟨{}, .none, .plain⟩ -private def node := (result.nodes.get? (name 1)).getD noNode -private def legacy := raw.foldl (fun m p => match keep p.2 with - | some ci => m.insert p.1 ci | none => m) ({} : _root_.Ix.Map _root_.Ix.Name _root_.Ix.ConstantInfo) - --- The real insertion-seam countercase: all maps must follow the final hash-key winner. -#guard sameHash == name 1 && !_root_.Ix.Name.beqRaw sameHash (name 1) -#guard legacy.contains sameHash && !result.code.contains sameHash -#guard result.nodes.size == 2 && result.code.size == 1 -#guard node.direct.contains (name 3) && !node.direct.contains (name 2) -#guard (hybrid.get? (name 1)).any (fun | .opaqueInfo _ => true | _ => false) -#guard result.code.contains (name 9) -#guard !(result.nodes.contains (name 8)) && !(result.code.contains (name 8)) -#guard (rowOfCanonical sameHash streamed).kept.isNone -#guard (rowOfCanonical sameHash proof).kept.isNone -#guard (rowOfCanonical (name 1) code).kept.isSome -#guard (collect []).nodes.isEmpty && (collect []).code.isEmpty -private def backToCode := collect ([streamed, code].map fun ci => rowOfCanonical ci.getCnst.name ci) -#guard (backToCode.code.get? sameHash).any (fun | .defnInfo _ => true | _ => false) -#guard ((backToCode.nodes.get? sameHash).getD noNode).direct.contains (name 2) -private def toProof := collect ([code, proof].map fun ci => rowOfCanonical ci.getCnst.name ci) -#guard !(toProof.code.contains sameHash) && ((toProof.nodes.get? sameHash).getD noNode).direct.contains (name 4) -private def unsafeCode : _root_.Ix.ConstantInfo := match code with - | .defnInfo v => .defnInfo { v with safety := .unsafe } - | ci => ci -#guard (keep unsafeCode).isSome - -private def natLit : _root_.Ix.Expr := .lit (.natVal 7) (addr 60) -private def strLit : _root_.Ix.Expr := .lit (.strVal "sample") (addr 61) -private def withBody (body : _root_.Ix.Expr) : _root_.Ix.ConstantInfo := - .opaqueInfo ⟨cv sameHash, body, false, #[sameHash]⟩ -#guard (capture code).literals == .none -#guard (capture (withBody natLit)).literals == .nat -#guard (capture (withBody (.app natLit strLit (addr 62)))).literals == .string -#guard (rowOfCanonical sameHash (withBody strLit)).kept.isNone -#guard (dependencies {} (capture (withBody strLit))).contains (_root_.Ix.Name.fromLeanName `String) -#guard (dependencies {} (capture (withBody strLit))).contains (_root_.Ix.Name.fromLeanName `Nat) -private def ind : _root_.Ix.ConstantInfo := - .inductInfo ⟨cv (name 4), 0, 0, #[name 4, name 6], #[], 0, false, false, false⟩ -private def recursor : _root_.Ix.ConstantInfo := - .recInfo ⟨cv (name 5), #[name 4], 0, 0, 1, 0, #[⟨name 6, 0, strLit⟩], false, false⟩ -private def recIndex : _root_.Ix.Map _root_.Ix.Name (Array (_root_.Ix.Name × _root_.Ix.Name)) := - .ofList [(name 4, #[(name 4, name 5), (name 4, name 5)])] -#guard (capture recursor).literals == .string -#guard (dependencies recIndex (capture ind)).contains (name 5) -#guard (dependencies recIndex (capture ind)).contains (name 6) -#guard (dependencies {} (capture recursor)).contains (name 4) -#guard (dependencies {} (capture recursor)).contains (name 6) -#guard (keep ind).isSome && (keep recursor).isSome -private def alias4 : _root_.Ix.Name := .str (.anonymous (addr 0)) "alias4" (addr 4) -#guard !(dependencies (.ofList [(name 4, #[(alias4, name 5)])]) (capture ind)).contains (name 5) -#guard decide (dependencies recIndex (capture ind) = _root_.Ix.Cert.dependencies recIndex ind) --- Universe numbering and actual canonical conversion are exercised without retention assumptions. -private def leanCi : Lean.ConstantInfo := .opaqueInfo - { name := `StreamRow.poly, levelParams := [`u], type := .sort (.succ (.param `u)), - value := .sort (.param `u), isUnsafe := false, all := [`StreamRow.poly] } -private def canonical := _root_.Ix.CanonM.canonChunk #[(`StreamRow.poly, leanCi)] -#guard canonical.size == 1 -#guard canonical[0]?.any fun (n, ci) => (rowOfCanonical n ci).kept.isNone -#guard canonical[0]?.any fun (n, ci) => (rowOfCanonical n ci).node.literals == .none - -end Tests.Ix.Cert.SourceGraph diff --git a/Tests/Ix/Cert/SourceGraphLiteral.lean b/Tests/Ix/Cert/SourceGraphLiteral.lean deleted file mode 100644 index 89b8d843c..000000000 --- a/Tests/Ix/Cert/SourceGraphLiteral.lean +++ /dev/null @@ -1,83 +0,0 @@ -import Ix.Compile.SourceGraph.Literal - -namespace Tests.Ix.Cert.SourceGraphLiteral -open _root_.Ix -open _root_.Ix.Compile.SourceGraph - -def leaf : Expr := Expr.mkSort Level.mkZero -def dag : Nat → Expr - | 0 => leaf - | n + 1 => let child := dag n; Expr.mkApp child child -def unshared : Nat → Nat → Expr - | 0, seed => Expr.mkConst (Name.mkNat (Name.fromLeanName `LiteralTree) seed) #[] - | n + 1, seed => Expr.mkApp (unshared n (2 * seed)) (unshared n (2 * seed + 1)) -def sample (body : Expr) : ConstantInfo := .opaqueInfo - ⟨⟨Name.fromLeanName `LiteralSample, #[], Expr.mkSort (.mkSucc .mkZero)⟩, - body, false, #[Name.fromLeanName `LiteralSample]⟩ -private def agrees (e : Expr) : Bool := (Literal.scan Literal.empty e).bits == Literal.tree e - -#guard (List.range 13).all fun d => agrees (dag d) -#guard (List.range 13).all fun d => (Literal.scan Literal.empty (dag d)).memo.entries.size == d + 1 -private def repeated := dag 8 -private def first := Literal.scan Literal.empty repeated -private def again := Literal.scan first.memo repeated -#guard first.bits == (false, false) && again.bits == first.bits -#guard again.memo.entries.size == first.memo.entries.size -#guard again.memo.entries.get? ⟨repeated⟩ == some (false, false) -#guard (Literal.scanTerms Literal.empty [repeated, repeated, repeated]).memo.entries.size == 9 -#guard (Literal.scan Literal.empty (unshared 6 0)).memo.entries.size == 127 -#guard agrees (unshared 6 0) - -private def nat := Expr.mkLit (.natVal 7) -private def str := Expr.mkLit (.strVal "literal") -private def binder := Name.fromLeanName `x -private def branches : List Expr := - [Expr.mkApp nat str, Expr.mkLam binder nat str .default, - Expr.mkForallE binder nat str .default, Expr.mkLetE binder nat leaf str false, - Expr.mkMData #[] str, Expr.mkProj binder 0 nat, - Expr.mkBVar 0, Expr.mkFVar binder, Expr.mkMVar binder, - leaf, Expr.mkConst binder #[], nat, str] -#guard branches.all agrees -#guard (Literal.scan Literal.empty (Expr.mkApp nat str)).bits == (true, true) -#guard (branches.map sample).all fun ci => literalUseShared ci == literalUse ci -#guard literalUseShared (sample (Expr.mkApp nat str)) == .string -#guard literalUseShared (sample nat) == .nat -#guard literalUseShared (sample leaf) == .none - --- Equal digests never identify distinct literal nodes in the memo. -private def collision : Expr := .lit (.strVal "different") nat.getHash -private def natResult := Literal.scan Literal.empty nat -private def collisionResult := Literal.scan natResult.memo collision -#guard nat.getHash == collision.getHash && !_root_.Ix.Compile.ExactKey.exprEq nat collision -#guard collisionResult.bits == (true, false) && collisionResult.memo.entries.size == 2 -#guard (Literal.scan collisionResult.memo nat).bits == (false, true) -#guard (Literal.scanTerms Literal.empty [nat, collision, nat]).bits == (true, true) - --- One memo spans type and all rule RHSs, including repeated shared roots. -private def recursor : ConstantInfo := .recInfo - ⟨⟨binder, #[], repeated⟩, #[binder], 0, 0, 1, 0, - #[⟨binder, 0, repeated⟩, ⟨binder, 0, repeated⟩], false, false⟩ -#guard (Literal.scanTerms Literal.empty (terms recursor)).memo.entries.size == 9 -#guard literalUseShared recursor == literalUse recursor -#guard (captureUsingShared (sample str) {}).literals == .string -#guard (rowUsingShared binder (sample str) {}).kept.isNone -#guard (rowUsingShared binder recursor {}).kept.isSome - -/-- info: 'Ix.Compile.SourceGraph.Literal.scan_correct' depends on axioms: [propext, Classical.choice, Quot.sound] -/ -#guard_msgs in #print axioms _root_.Ix.Compile.SourceGraph.Literal.scan_correct -/-- info: 'Ix.Compile.SourceGraph.Literal.scan_sound' depends on axioms: [propext, Classical.choice, Quot.sound] -/ -#guard_msgs in #print axioms _root_.Ix.Compile.SourceGraph.Literal.scan_sound -/-- info: 'Ix.Compile.SourceGraph.Literal.scan_extends' depends on axioms: [propext, Classical.choice, Quot.sound] -/ -#guard_msgs in #print axioms _root_.Ix.Compile.SourceGraph.Literal.scan_extends -/-- info: 'Ix.Compile.SourceGraph.Literal.scan_hit' depends on axioms: [propext, Classical.choice, Quot.sound] -/ -#guard_msgs in #print axioms _root_.Ix.Compile.SourceGraph.Literal.scan_hit -/-- info: 'Ix.Compile.SourceGraph.Literal.scanTerms_correct' depends on axioms: [propext, Classical.choice, Quot.sound] -/ -#guard_msgs in #print axioms _root_.Ix.Compile.SourceGraph.Literal.scanTerms_correct -/-- info: 'Ix.Compile.SourceGraph.literalUseShared_eq' depends on axioms: [propext, Classical.choice, Quot.sound] -/ -#guard_msgs in #print axioms _root_.Ix.Compile.SourceGraph.literalUseShared_eq -/-- info: 'Ix.Compile.SourceGraph.captureUsingShared_eq' depends on axioms: [propext, Classical.choice, Quot.sound] -/ -#guard_msgs in #print axioms _root_.Ix.Compile.SourceGraph.captureUsingShared_eq -/-- info: 'Ix.Compile.SourceGraph.rowUsingShared_eq' depends on axioms: [propext, Classical.choice, Quot.sound] -/ -#guard_msgs in #print axioms _root_.Ix.Compile.SourceGraph.rowUsingShared_eq - -end Tests.Ix.Cert.SourceGraphLiteral diff --git a/Tests/Ix/Cert/SourceGraphLiteralBench.lean b/Tests/Ix/Cert/SourceGraphLiteralBench.lean deleted file mode 100644 index 06439b18a..000000000 --- a/Tests/Ix/Cert/SourceGraphLiteralBench.lean +++ /dev/null @@ -1,43 +0,0 @@ -import Tests.Ix.Cert.SourceGraphLiteral - -open Ix.Compile.SourceGraph -open Tests.Ix.Cert.SourceGraphLiteral - --- Keep both real code paths distinct: there is no csimp replacement of literalUse. -private def tagScore : LiteralUse → Nat | .none => 0 | .nat => 1 | .string => 2 -private def nodeScore (node : Node) : Nat := - tagScore node.literals + node.direct.size + match node.grouping with - | .plain => 0 | .induct all | .recursor all => all.size -@[noinline] private def treeEval (ci : Ix.ConstantInfo) := tagScore (literalUse ci) -@[noinline] private def sharedEval (ci : Ix.ConstantInfo) := tagScore (literalUseShared ci) -@[noinline] private def oldCaptureEval (ci : Ix.ConstantInfo) := nodeScore (captureUsing ci {}) -@[noinline] private def captureEval (ci : Ix.ConstantInfo) := nodeScore (captureUsingShared ci {}) -@[noinline] private def rowEval (ci : Ix.ConstantInfo) : Nat := - let row := rowUsingShared ci.getCnst.name ci {} - nodeScore row.node + (if row.name == ci.getCnst.name then 0 else 1) + (if row.kept.isSome then 1 else 0) - -private def timeLiteral (f : Ix.ConstantInfo → Nat) (ci : Ix.ConstantInfo) (repeats : Nat) : IO Nat := do - let start ← IO.monoNanosNow - for _ in [:repeats] do - let result ← IO.lazyPure fun _ => f ci - unless result == 0 do throw (IO.userError "literal-free measurement changed its result or fields") - return (← IO.monoNanosNow) - start - -private def runCase (label : String) (body : Ix.Expr) (expectedNodes : Nat) : IO Unit := do - let ci := sample body - let observed ← IO.lazyPure fun _ => (Literal.scanTerms Literal.empty (terms ci)).memo.entries.size - unless observed == expectedNodes do throw (IO.userError s!"{label}: memo has {observed}, expected {expectedNodes}") - let repeats := 3 - let treeNs ← timeLiteral treeEval ci repeats - let sharedNs ← timeLiteral sharedEval ci repeats - let oldCaptureNs ← timeLiteral oldCaptureEval ci repeats - let captureNs ← timeLiteral captureEval ci repeats - let rowNs ← timeLiteral rowEval ci repeats - let us := fun n => n / (1000 * repeats) - IO.println s!"{label}\tnodes={observed}\trepeats={repeats}\ttree_us={us treeNs}\tshared_us={us sharedNs}\told_capture_us={us oldCaptureNs}\tcapture_us={us captureNs}\trow_us={us rowNs}" - -def main : IO Unit := do - IO.println "literal/capture/row extraction; all output fields consumed; synthetic shapes, proper hashes; times per call, no ratio threshold" - for depth in [12, 16, 20] do - runCase s!"shared-depth-{depth}" (dag depth) (depth + 2) - runCase "unshared-depth-10" (unshared 10 0) 2048 diff --git a/Tests/Ix/Cert/SourcePrepass.lean b/Tests/Ix/Cert/SourcePrepass.lean deleted file mode 100644 index 7a7e85691..000000000 --- a/Tests/Ix/Cert/SourcePrepass.lean +++ /dev/null @@ -1,74 +0,0 @@ -import Ix.Compile.SourceGraph.Prepass -import Ix.Cert.Hybrid - -/-! Prepass, winner and diagnostic controls; synthetic records make no -Source-acceptance or canonical-name hash-collision claim. -/ -namespace Tests.Ix.Cert.SourcePrepass -open _root_.Ix -open _root_.Ix.Compile.SourceGraph -open _root_.Ix.Compile.SourceGraph.Prepass - -private def plain : Lean.ConstantInfo := .axiomInfo - { name := `Prepass.a, levelParams := [], type := .sort (.succ .zero), isUnsafe := false } -private def ctor : Lean.ConstantInfo := .ctorInfo - { name := `Prepass.I.mk, levelParams := [], type := .sort .zero, - induct := `Prepass.I, cidx := 2, numParams := 1, numFields := 3, isUnsafe := true } -private def ind : Lean.ConstantInfo := .inductInfo - { name := `Prepass.I, levelParams := [], type := .sort .zero, - numParams := 0, numIndices := 0, all := [`Prepass.I], ctors := [`Prepass.I.mk], - numNested := 0, isRec := false, isUnsafe := false, isReflexive := false } -private def opaq : Lean.ConstantInfo := .opaqueInfo - { name := `Prepass.o, levelParams := [], type := .sort (.succ .zero), - value := .sort .zero, isUnsafe := false, all := [`Prepass.o] } -private def input := [ind, ctor, plain, opaq].toArray.map fun ci => (ci.name, ci) -private def prep := run input -private def headerOk (cv : ConstantVal) : Bool := - Name.beqRaw cv.name .mkAnon && cv.levelParams.isEmpty && - _root_.Ix.Compile.ExactKey.exprEq cv.type (Expr.mkSort Level.mkZero) -private def placeholderOk (wantCtor : Bool) : ConstantInfo → Bool - | .axiomInfo v => !wantCtor && headerOk v.cnst && !v.isUnsafe - | .ctorInfo v => wantCtor && headerOk v.cnst && Name.beqRaw v.induct .mkAnon && - v.cidx == 0 && v.numParams == 0 && v.numFields == 0 && !v.isUnsafe - | _ => false -#guard (run #[]).leanByIx.isEmpty && (run #[]).thinConsts.isEmpty && (run #[]).labelled.isEmpty -#guard prep.labelled.size == input.size && prep.leanByIx.size == 4 && prep.thinConsts.size == 4 -#guard decide (prep.labelled.map (·.1) = input.map (fun p => Name.fromLeanName p.1)) -#guard input.all fun p => (prep.leanByIx.get? (Name.fromLeanName p.1)).any (fun r => r.1 == p.1) -#guard (prep.thinConsts.get? (Name.fromLeanName ctor.name)).any (placeholderOk true) -#guard [ind, plain, opaq].all fun ci => (prep.thinConsts.get? (Name.fromLeanName ci.name)).any (placeholderOk false) -private def addr (n : Nat) : Address := ⟨⟨Array.replicate 32 n.toUInt8⟩⟩ -private def first : Name := .str (.anonymous (addr 0)) "first" (addr 1) -private def last : Name := .str (.anonymous (addr 0)) "last" (addr 1) -private def aliases := addNamed (addNamed {} first (ctor.name, ctor)) last (opaq.name, opaq) -#guard first == last && !Name.beqRaw first last -#guard aliases.leanByIx.size == 1 && aliases.thinConsts.size == 1 && aliases.labelled.size == 2 -#guard decide (aliases.leanByIx.getKey? first = some last) && decide (aliases.thinConsts.getKey? first = some last) -#guard (aliases.thinConsts.get? first).any (placeholderOk false) -#guard decide (aliases.labelled.map (·.1) = #[first, last]) -private def reversed := addNamed (addNamed {} last (opaq.name, opaq)) first (ctor.name, ctor) -#guard (reversed.thinConsts.get? last).any (placeholderOk true) -#guard decide (reversed.leanByIx.getKey? last = some first) --- Pointwise values do not identify stored representatives or list order. -private def leftMap : Map Name Nat := .ofList [(first, 7)] -private def rightMap : Map Name Nat := .ofList [(last, 7)] -#guard leftMap.get? first == rightMap.get? first && leftMap.get? last == rightMap.get? last -#guard !decide (leftMap.getKey? first = rightMap.getKey? first) -#guard !decide (leftMap.toList = rightMap.toList) -#guard [0, 1, 2, 8].all fun w => - let out := (produce input w).2 - let hybrid := _root_.Ix.Cert.Hybrid.hybridEnv out.facts.code prep.leanByIx - out.facts.code.size == 3 && prep.leanByIx.size - out.facts.code.size == 1 && - (hybrid.get? (Name.fromLeanName opaq.name)).any (fun | .opaqueInfo _ => true | _ => false) && - (out.facts.code.get? (Name.fromLeanName ind.name)).any (fun | .inductInfo _ => true | _ => false) -private def wrong : Lean.ConstantInfo := .axiomInfo - { name := ctor.name, levelParams := [], type := .sort .zero, isUnsafe := false } -private def failed := groundConstCheck (_root_.Ix.Cert.Canon.constS ind) - { consts := (run #[(wrong.name, wrong)]).thinConsts } -#guard match failed with | .error (.indc _ (some ci)) => placeholderOk false ci | _ => false -#guard match failed with | .error e => e.refusal (fun _ => false) == - "UNGROUNDED-INPUT: an inductive type whose constructor is missing or not a constructor" | _ => false -#guard match groundConstCheck (_root_.Ix.Cert.Canon.constS ind) { consts := {} } with - | .error (.indc _ none) => true | _ => false -#guard (groundConstCheck (_root_.Ix.Cert.Canon.constS ind) { consts := prep.thinConsts }).isOk - -end Tests.Ix.Cert.SourcePrepass diff --git a/Tests/Ix/Cert/SourceStream.lean b/Tests/Ix/Cert/SourceStream.lean deleted file mode 100644 index 26a2a0bc9..000000000 --- a/Tests/Ix/Cert/SourceStream.lean +++ /dev/null @@ -1,62 +0,0 @@ -import Ix.Compile.SourceGraph.Stream -import Ix.Cert.Canon - -/-! Pure producer controls. Labelled aliases test the collector, not a -claimed collision among canonName outputs or Source acceptance. -/ -namespace Tests.Ix.Cert.SourceStream -open _root_.Ix -open _root_.Ix.Compile.SourceGraph -open _root_.Ix.Compile.SourceGraph.Stream - -private def workers := [0, 1, 2, 3, 8] -#guard (List.range 8).all fun n => workers.all fun w => (partition (Array.range n) w).flatten == Array.range n -#guard (partition (Array.range 5) 0).size == 5 -#guard (partition (Array.range 5) 1).size == 1 -#guard (partition (Array.range 5) 2).map (·.size) == #[3, 2] -#guard (partition (Array.range 5) 8).size == 5 -private def bad : Lean.ConstantInfo := .defnInfo - { name := `Stream.bad, levelParams := [], type := .sort (.succ .zero), - value := .const `Stream.missing [], hints := .abbrev, safety := .safe, all := [`Stream.bad] } -private def good : Lean.ConstantInfo := .opaqueInfo - { name := `Stream.good, levelParams := [], type := .sort (.succ .zero), - value := .sort .zero, isUnsafe := false, all := [`Stream.good] } -private def other : Lean.ConstantInfo := .axiomInfo - { name := `Stream.other, levelParams := [], type := .sort (.succ .zero), isUnsafe := false } -private def addr (n : Nat) : Address := ⟨⟨Array.replicate 32 n.toUInt8⟩⟩ -private def key : Name := .str (.anonymous (addr 0)) "first" (addr 1) -private def sameHash : Name := .str (.anonymous (addr 0)) "last" (addr 1) -private def otherKey : Name := .str (.anonymous (addr 0)) "other" (addr 9) -private def input := #[(key, bad), (otherKey, other), (sameHash, good)] -private def rows (w : Nat) := ((tasks { consts := {} } input w).map Task.get).flatten -#guard key == sameHash && !Name.beqRaw key sameHash -#guard workers.all fun w => decide ((rows w).map (·.graph.name) = input.map (·.1)) -#guard workers.all fun w => (rows w).map (·.immediate.isSome) == #[true, false, false] -#guard workers.all fun w => (rows w).map (·.digest) == input.map (fun p => hash (_root_.Ix.Cert.Canon.constS p.2)) -private def expected (out : Output) : Bool := - out.facts.nodes.size == 2 && out.facts.code.size == 1 && - !out.facts.code.contains key && out.facts.code.contains otherKey && - (out.outRefs.getD key {}).isEmpty && - (out.immediate.get? sameHash).any (fun | .ref n => n == Name.fromLeanName `Stream.missing | _ => false) && - out.digests.get? key == some (hash (_root_.Ix.Cert.Canon.constS good)) -#guard workers.all fun w => expected (runLabelled { consts := {} } input w) --- The later successful opaque erases stale code, but the earlier error is not --- erased by the existing collection policy. -#guard (runLabelled { consts := {} } input 0).immediate.contains sameHash -private def emptyOut := runLabelled { consts := {} } #[] 0 -#guard emptyOut.facts.nodes.isEmpty && emptyOut.outRefs.isEmpty -private def one := chunk { consts := {} } #[(key, bad)] -#guard one.size == 1 && one[0]?.any (fun r => r.graph.kept.isSome && r.immediate.isSome) -private def proofSource : Lean.ConstantInfo := .thmInfo - { name := `Stream.proof, levelParams := [], type := .sort (.succ .zero), - value := .lit (.strVal "captured"), all := [`Stream.proof] } -private def raw := #[(bad.name, bad), (good.name, good), (other.name, other), (proofSource.name, proofSource)] -private def noNode : Node := ⟨{}, .none, .plain⟩ -#guard workers.all fun w => - let out := runSource { consts := {} } raw w - out.facts.nodes.size == 4 && out.facts.code.size == 2 && - (raw.map fun p => Name.fromLeanName p.1).all out.facts.nodes.contains && - !out.facts.code.contains (Name.fromLeanName `Stream.proof) && - ((out.facts.nodes.get? (Name.fromLeanName `Stream.proof)).getD noNode).literals == .string -#guard (label raw).map (·.1) == raw.map (fun p => Name.fromLeanName p.1) - -end Tests.Ix.Cert.SourceStream diff --git a/Tests/Ix/Cert/Statement.lean b/Tests/Ix/Cert/Statement.lean index d0b377db1..67c597650 100644 --- a/Tests/Ix/Cert/Statement.lean +++ b/Tests/Ix/Cert/Statement.lean @@ -1,6 +1,9 @@ import LSpec import Ix.Meta -import Ix.Cert.Audit +import Ix.CompileCertified +import Ix.CompileCert.InductiveReading +import Ix.Cert.Stream +import Ix.Cert.Composition import Tests.Ix.Kernel.SourceIngress import Tests.Ix.SourceContract.SyntaxFixture import Ix.Ixon.CheckOrder @@ -9,8 +12,8 @@ import Tests.Ix.Cert.ExecutedChecks /-! # The certification statements on the Source-mode fixtures (`lake test -- cert-statement`) * **Compile parity.** `Ix.Cert.compile`, the function the statements are - about, against the IO pipeline (`Ix.CompileM.compileLeanConsts`) and the - Rust compiler (`Ix.CompileM.rsCompileEnvFFI`), on the closure of every + about, against the certified IO pipeline (`Ix.CompileM.compileCertifiedPrepared`), + on the closure of every constant of `Tests.Ix.Kernel.ReaderFidelityDefs` and `Tests.Ix.Kernel.SourceIngressDefs`: the serialized environments (`Ixon.serEnv`) are byte-identical, and so are the published addresses. @@ -23,7 +26,10 @@ import Tests.Ix.Cert.ExecutedChecks reference check (`Ix.Cert.referenceCycle`, Route A, B4) must find four planted reference cycles (two decoded, two stored as bytes), and its refs-only reader must agree with the decoder on every record. - `cert-compile-parity-initstd` (merge queue) does the same on Init+Std. + (`Ix.Compile.ReadyCompile.reportIO` proves by type that the IO worker-queue + driver computes `Ix.Cert.compileReport`, whose wave-snapshot fold is proved + semantically correct directly; equivalence with a one-block-at-a-time + live-state fold is not proved in `Ix.Cert.Schedule`.) * **The statements' hypotheses and conclusion, per root, on the accepted fixtures** (`Tests.Ix.Kernel.SourceIngress.acceptedSeeds` and the prelude's constants): @@ -46,7 +52,9 @@ import Tests.Ix.Cert.ExecutedChecks the same contracts in Source mode as its reading does in the certified entry's environment of `Ix.Cert.compile`'s output. -Importing `Ix.Cert.Audit` builds the statements' freeze with the tests. -/ +The statements' freeze, `Ix.Cert.Audit`, is not imported here: the certified +kernel gate (`lake run check-kernel`) and `lake lint` build it, so the test +binary does not carry its proof closure. -/ namespace Tests.Ix.Cert.Statement @@ -63,17 +71,6 @@ def fixtureConsts (leanEnv : Lean.Environment) : IO (List (Lean.Name × Lean.Con if m == some idx || m == some idx' then some n else none IO.ofExcept (Tests.Ix.Kernel.ReaderFidelity.closure leanEnv (seeds ++ #[`Nat.div] ++ preludeNames)) -/-- The published addresses of two compiled environments that differ, by name. -/ -def addressDiffs (a b : Ixon.Env) : Array String := Id.run do - let mut out := #[] - for (n, na) in a.named.toList do - match b.named.get? n with - | some nb => unless na.addr == nb.addr do out := out.push s!"{n}: {na.addr} vs {nb.addr}" - | none => out := out.push s!"{n}: missing from the second" - for (n, _) in b.named.toList do - unless a.named.contains n do out := out.push s!"{n}: missing from the first" - return out - /-- `Ix.CompileCovers` on one compile: the input constants published, the failed ones, those both published and failed, and those neither. -/ def coverage (env : Ix.Environment) (ixe : Ixon.Env) (failures : Ix.Cert.Failures) : @@ -725,8 +722,8 @@ def noCtorCases (label : String) (env : Ix.Environment) : IO (Array String × Ar else return (shown.toArray.push s!"{label}: {errors.size} failure(s)", #[]) -/-- `Ix.Cert.compile` against the IO pipeline and the Rust compiler on -`consts`: errors, and lines for the report. `compile` is `compileReport`'s +/-- `Ix.Cert.compile` on `consts`, against the statements it is proved to +meet: errors, and lines for the report. `compile` is `compileReport`'s first component (`Ix.Cert.compile_eq_ok`), and `compileReport` is `compileUnchecked` followed by `checkReport` against the resolution map `compileUnchecked` returns, which `checkReportCases` runs (and times) here @@ -780,35 +777,10 @@ def parity (label : String) (consts : List (Lean.Name × Lean.ConstantInfo)) (wo let (roleErrors, roleLines) ← rolesCases label env ixe errors := errors ++ roleErrors lines := lines ++ roleLines - -- the IO pipeline - let t3 ← IO.monoMsNow - match ← Ix.CompileM.compileLeanConsts consts (numWorkers := workers) with - | .error e => errors := errors.push s!"{label}: compileLeanConsts failed: {e}" - | .ok out => - let t4 ← IO.monoMsNow - lines := lines.push s!"{label}: compileLeanConsts {t4 - t3} ms: {out.bytes.size} bytes, \ - {out.ungroundedCount} ungrounded, {out.cenv.ungrounded.size} per-block failures" - unless out.bytes == pureBytes do - errors := errors.push s!"{label}: serEnv bytes differ from compileLeanConsts's \ - ({pureBytes.size} vs {out.bytes.size})" - let diffs := addressDiffs ixe out.env - unless diffs.isEmpty do - errors := errors.push s!"{label}: {diffs.size} published addresses differ from \ - compileLeanConsts's: {diffs.toList.take 5}" - -- the Rust compiler - let raw ← Ix.CompileM.rsCompileEnvFFI consts - let rust := raw.toEnv - match Ixon.serEnv rust with - | .error e => errors := errors.push s!"{label}: serEnv of the Rust output failed: {e}" - | .ok rustBytes => - lines := lines.push s!"{label}: Rust compiler: {rust.consts.size} records, {rustBytes.size} bytes" - unless rustBytes == pureBytes do - errors := errors.push s!"{label}: serEnv bytes differ from the Rust compiler's \ - ({pureBytes.size} vs {rustBytes.size})" - let diffs := addressDiffs ixe rust - unless diffs.isEmpty do - errors := errors.push s!"{label}: {diffs.size} published addresses differ from the \ - Rust compiler's: {diffs.toList.take 5}" + -- N-READS N4 (a)'s premise `NoCtorIn` on every inductive + let (noCtorErrors, noCtorLines) ← noCtorCases label env + errors := errors ++ noCtorErrors + lines := lines ++ noCtorLines return (errors, lines) /-! ## The Ixon side of one closure -/ @@ -1711,55 +1683,4 @@ def evidenceSuite : List TestSeq := [ report "the role table's evidence against quotient Source's, on the well-founded operations" (do evidenceAgreement (← getCompileEnv #[defsModule, ownModule])) 1] -/-- `cert-compile-parity-initstd` (merge queue): the parity on Init+Std. -/ -def initStdSuite : List TestSeq := [ - report "Ix.Cert.compile against the IO pipeline and the Rust compiler, on Init+Std" (do - let leanEnv ← getCompileEnv #[`Init, `Std] - parity "Init+Std" leanEnv.constants.toList 16) 1] - -/-- `cert-roles-initstd` (ignored): the role table's bindings (Route A, B5a) on Init+Std, -compiled in process by the Rust compiler (byte-identical to `Ix.Cert.compile`'s, the -gates' byte leg `46214545…`). -/ -def rolesInitStdSuite : List TestSeq := [ - report "the role table's bindings in the compiled Init+Std (Route A, B5a)" (do - let leanEnv ← getCompileEnv #[`Init, `Std] - let env ← IO.lazyPure fun _ => - Tests.Ix.Kernel.SourceIngressStep.canon leanEnv.constants.toList.toArray 16 - let dir ← IO.FS.createTempDir - let ixe ← try - let path := dir / "initstd.ixe" - let _ ← Ix.CompileM.rsCompileEnvBytes leanEnv path.toString - IO.ofExcept (Ixon.deEnv (← IO.FS.readBinFile path)) - finally IO.FS.removeDirAll dir - rolesCases "Init+Std" env ixe) 1] - -/-- `cert-evidence-initstd`: the evidence check (part of the artifact checks) on the compiled -Init+Std, timed. -/ -def evidenceInitStdSuite : List TestSeq := [ - report "the evidence check on the compiled Init+Std" (do - let leanEnv ← getCompileEnv #[`Init, `Std] - let dir ← IO.FS.createTempDir - let ixe ← try - let path := dir / "initstd.ixe" - let _ ← Ix.CompileM.rsCompileEnvBytes leanEnv path.toString - IO.ofExcept (Ixon.deEnv (← IO.FS.readBinFile path)) - finally IO.FS.removeDirAll dir - let some t := defaultTable? | return (#["evidence: the default role table does not read"], #[]) - let started ← IO.monoMsNow - let checked ← IO.lazyPure fun _ => Ix.Cert.EvidenceCheck.evidenceCheck t ixe - let ms := (← IO.monoMsNow) - started - match checked with - | .ok () => return (#[], #[s!"evidence check on Init+Std ({ixe.consts.size} records): passes in {ms} ms"]) - | .error e => return (#[s!"evidence check on Init+Std refuses the artifact after {ms} ms: {e}"], #[])) 1] - -/-- `cert-noctor-initstd` (ignored): N-READS N4 (a)'s premise `NoCtorIn` on every inductive of -Init+Std. -/ -def noCtorInitStdSuite : List TestSeq := [ - report "no constructor of a family in its types, on Init+Std (N-READS N4 a)" (do - let leanEnv ← getCompileEnv #[`Init, `Std] - let env ← IO.lazyPure fun _ => - Tests.Ix.Kernel.SourceIngressStep.canon leanEnv.constants.toList.toArray 16 - noCtorCases "Init+Std" env) 1] - - end Tests.Ix.Cert.Statement diff --git a/Tests/Ix/Compile.lean b/Tests/Ix/Compile.lean deleted file mode 100644 index d19f9c3b3..000000000 --- a/Tests/Ix/Compile.lean +++ /dev/null @@ -1,471 +0,0 @@ -/- - Cross-implementation tests for Compilation. - Verifies Lean and Rust implementations produce equivalent results. --/ - -module -public import Ix.Ixon -public import Ix.Environment -public import Ix.Address -public import Ix.Common -public import Ix.Meta -public import Ix.CompileM -public import Ix.CompileDriver -public import Ix.CondenseM -public import Ix.GraphM -public import Lean -public import LSpec -public import Tests.Ix.Fixtures -public import Tests.Ix.Compile.Mutual - -open LSpec - -namespace Tests.Compile - -/-! ## Helpers -/ - -/-- Show hex dump of first N bytes -/ -def hexDump (bytes : ByteArray) (maxBytes : Nat := 64) : String := Id.run do - let mut s := "" - for i in [:min bytes.size maxBytes] do - if i > 0 && i % 16 == 0 then s := s ++ "\n" - else if i > 0 && i % 8 == 0 then s := s ++ " " - else if i > 0 then s := s ++ " " - let b := bytes.get! i - let hi := b / 16 - let lo := b % 16 - let toHex (n : UInt8) : Char := if n < 10 then Char.ofNat (n.toNat + 48) else Char.ofNat (n.toNat + 87) - s := s ++ String.singleton (toHex hi) ++ String.singleton (toHex lo) - if bytes.size > maxBytes then s := s ++ s!" ... ({bytes.size} bytes total)" - return s - -/-- Find first byte position where two arrays differ -/ -def findFirstDiff (a b : ByteArray) : Option Nat := Id.run do - for i in [:min a.size b.size] do - if a.get! i != b.get! i then return some i - if a.size != b.size then return some (min a.size b.size) - return none - -/-! ## Comparison Results -/ - -/-- Result of comparing a single constant -/ -structure ConstCompareResult where - name : Ix.Name - leanAddr : Address - rustAddr : Address - isMatch : Bool - leanBytes : ByteArray - rustBytes : ByteArray - -/-- Result of a per-constant metadata comparison -/ -structure MetaMismatch where - name : Ix.Name - leanByType : Nat × Nat × Nat × Nat × Nat -- (binder, letBinder, ref, prj, mdata) - rustByType : Nat × Nat × Nat × Nat × Nat - -/-- Result of comparing all constants -/ -structure CompareResult where - totalConstants : Nat - matchingConstants : Nat - mismatchedConstants : Array ConstCompareResult - metaMismatches : Array MetaMismatch - fullMetaMismatches : Array (Ix.Name × Ixon.ConstantMeta × Ixon.ConstantMeta) - missingInRust : Array Ix.Name - missingInLean : Array Ix.Name - -/-- Compare Lean and Rust compilation results using Ixon.Env -/ -def compareEnvResults - (leanEnv rustEnv : Ixon.Env) - : CompareResult := Id.run do - let mut matching := 0 - let mut mismatched : Array ConstCompareResult := #[] - let mut metaMismatches : Array MetaMismatch := #[] - let mut fullMetaMismatches : Array (Ix.Name × Ixon.ConstantMeta × Ixon.ConstantMeta) := #[] - let mut missingInRust : Array Ix.Name := #[] - let mut missingInLean : Array Ix.Name := #[] - - -- Check all Lean constants against Rust - for (name, leanNamed) in leanEnv.named do - let leanAddr := leanNamed.addr - match rustEnv.named.get? name with - | some rustNamed => - let rustAddr := rustNamed.addr - if leanAddr == rustAddr then - matching := matching + 1 - else - let leanBytes := leanEnv.getConst? leanAddr |>.map Ixon.ser |>.getD default - let rustBytes := rustEnv.getConst? rustAddr |>.map Ixon.ser |>.getD default - mismatched := mismatched.push ⟨name, leanAddr, rustAddr, false, leanBytes, rustBytes⟩ - -- Check metadata regardless of addr match - let leanMeta := leanNamed.constMeta.exprMetaByType - let rustMeta := rustNamed.constMeta.exprMetaByType - if leanMeta != rustMeta then - metaMismatches := metaMismatches.push ⟨name, leanMeta, rustMeta⟩ - -- Full ConstantMeta comparison (catches all/ctx/lvls/hints differences) - if leanNamed.constMeta != rustNamed.constMeta then - fullMetaMismatches := fullMetaMismatches.push (name, leanNamed.constMeta, rustNamed.constMeta) - | none => - missingInRust := missingInRust.push name - - -- Check for constants in Rust but not in Lean - for (name, _) in rustEnv.named do - if !leanEnv.named.contains name then - missingInLean := missingInLean.push name - - { - totalConstants := leanEnv.named.size - matchingConstants := matching - mismatchedConstants := mismatched - metaMismatches - fullMetaMismatches - missingInRust - missingInLean - } - -/-- Serialize a Lean Ixon.Env to bytes -/ -def serializeEnv (env : Ixon.Env) : IO ByteArray := - IO.ofExcept (Ixon.serEnv env) - -/-! ## Integrated Test -/ - -/-- Cross-implementation compilation test using the new CompilePhases API -/ -def testCrossImpl : TestSeq := - .individualIO "Compilation Cross-Implementation" none (do - let leanEnv ← get_env! - let totalConsts := leanEnv.constants.toList.length - - IO.println s!"[Test] Cross-Implementation Compilation Test" - IO.println s!"[Test] Environment has {totalConsts} constants" - IO.println "" - - -- Step 1: Get all compilation phases from Rust in one call - IO.println s!"[Step 1] Running Rust compilation pipeline (rsCompilePhases)..." - let rustStart ← IO.monoMsNow - let phases ← Ix.CompileM.rsCompilePhases leanEnv - let rustTime := (← IO.monoMsNow) - rustStart - - IO.println s!"[Step 1] Rust: {phases.rawEnv.consts.size} consts, {phases.condensed.blocks.size} blocks, {phases.compileEnv.constCount} compiled in {rustTime}ms" - IO.println "" - - -- Step 2: Compile with Lean using Rust's rawEnv and condensed blocks - -- (the driver, `compileEnvFold`, with the production semantics). - IO.println s!"[Step 2] Running the Lean driver (compileEnvFold)..." - let leanStart ← IO.monoMsNow - - match ← IO.lazyPure fun _ => Ix.CompileM.compileEnvFold phases.rawEnv phases.condensed (dbg := true) with - | .error err => - let leanTime := (← IO.monoMsNow) - leanStart - IO.println s!"[Step 2] Compilation failed after {leanTime}ms" - IO.println s!"[Error] {err}" - return (false, 0, 0, some err) - - | .ok (leanIxonEnv, totalBytes, _driverCenv) => - let leanTime := (← IO.monoMsNow) - leanStart - IO.println s!"[Step 2] Lean: {leanIxonEnv.constCount} constants, {fmtBytes totalBytes} in {leanTime}ms" - IO.println "" - - -- Step 3: Compare compilation results - IO.println s!"[Step 3] Comparing Lean and Rust results..." - let compareStart ← IO.monoMsNow - - let result := compareEnvResults leanIxonEnv phases.compileEnv - - IO.print s!"[Step 3] Compared {result.totalConstants} " - let compareTime := (← IO.monoMsNow) - compareStart - IO.println s!"constants in {compareTime}ms" - - if result.mismatchedConstants.isEmpty && result.missingInRust.isEmpty && result.missingInLean.isEmpty then - IO.println s!"[Step 3] All {result.matchingConstants} constants match! ✓" - - if !result.metaMismatches.isEmpty then - IO.println s!"[Step 3] Metadata mismatches: {result.metaMismatches.size} constants" - -- Aggregate deltas - let mut totB : Int := 0; let mut totL : Int := 0; let mut totR : Int := 0 - let mut totP : Int := 0; let mut totM : Int := 0 - let mut negCount := 0 -- constants where Rust has MORE metadata - for mm in result.metaMismatches do - let (lb, ll, lr, lp, lm) := mm.leanByType - let (rb, rl, rr, rp, rm) := mm.rustByType - let db := (Int.ofNat lb) - (Int.ofNat rb) - let dl := (Int.ofNat ll) - (Int.ofNat rl) - let dr := (Int.ofNat lr) - (Int.ofNat rr) - let dp := (Int.ofNat lp) - (Int.ofNat rp) - let dm := (Int.ofNat lm) - (Int.ofNat rm) - totB := totB + db; totL := totL + dl; totR := totR + dr - totP := totP + dp; totM := totM + dm - if db < 0 || dl < 0 || dr < 0 || dp < 0 || dm < 0 then - negCount := negCount + 1 - IO.println s!"[Step 3] Totals: Δbinder={totB} Δlet={totL} Δref={totR} Δprj={totP} Δmdata={totM}" - IO.println s!"[Step 3] Constants where Rust > Lean: {negCount}" - -- Show first 30 mismatches - for mm in result.metaMismatches[:min 30 result.metaMismatches.size] do - let (lb, ll, lr, lp, lm) := mm.leanByType - let (rb, rl, rr, rp, rm) := mm.rustByType - let db := (Int.ofNat lb) - (Int.ofNat rb) - let dl := (Int.ofNat ll) - (Int.ofNat rl) - let dr := (Int.ofNat lr) - (Int.ofNat rr) - let dp := (Int.ofNat lp) - (Int.ofNat rp) - let dm := (Int.ofNat lm) - (Int.ofNat rm) - IO.println s!" {mm.name} Δbinder={db} Δlet={dl} Δref={dr} Δprj={dp} Δmdata={dm}" - -- Detailed dump for first mismatch with Δref>0 - if let some mm := result.metaMismatches.find? (fun mm => - let (_, _, lr, _, _) := mm.leanByType - let (_, _, rr, _, _) := mm.rustByType - lr > rr) then - IO.println s!"\n[Detail] {mm.name}" - let leanNamed := leanIxonEnv.named.get! mm.name - let rustNamed := phases.compileEnv.named.get! mm.name - let dumpArena (label : String) (tag : String) (arena : Ixon.ExprMetaArena) : IO Unit := do - IO.println s!" {label} {tag}: {arena.nodes.size} nodes" - for i in [:arena.nodes.size] do - IO.println s!" [{i}] {reprStr arena.nodes[i]!}" - let dumpMeta (label : String) (cm : Ixon.ConstantMeta) : IO Unit := do - match cm.info with - | .defn _ _ _ _ arena typeRoot valueRoot => do - dumpArena label "arena" arena - IO.println s!" {label} typeRoot={typeRoot} valueRoot={valueRoot}" - | .axio _ _ arena typeRoot => do - dumpArena label "arena" arena - IO.println s!" {label} typeRoot={typeRoot}" - | .quot _ _ arena typeRoot => do - dumpArena label "arena" arena - IO.println s!" {label} typeRoot={typeRoot}" - | .ctor _ _ _ arena typeRoot => do - dumpArena label "arena" arena - IO.println s!" {label} typeRoot={typeRoot}" - | .recr _ _ _ _ _ arena typeRoot ruleRoots => do - dumpArena label "arena" arena - IO.println s!" {label} typeRoot={typeRoot} ruleRoots={ruleRoots}" - | .indc _ _ _ _ _ arena typeRoot => do - dumpArena label "arena" arena - IO.println s!" {label} typeRoot={typeRoot}" - | .muts all _ => IO.println s!" {label}: muts classes={all.size}" - | .empty => IO.println s!" {label}: empty" - dumpMeta "Lean" leanNamed.constMeta - dumpMeta "Rust" rustNamed.constMeta - else - IO.println s!"[Step 3] All metadata matches! ✓" - - -- Check full ConstantMeta equality (catches all/ctx/lvls/hints differences) - if !result.fullMetaMismatches.isEmpty then - IO.println s!"[Step 3] Full ConstantMeta mismatches: {result.fullMetaMismatches.size}" - for (name, leanCM, rustCM) in result.fullMetaMismatches[:min 5 result.fullMetaMismatches.size] do - IO.println s!" {name}:" - -- Compare variant tags - let leanTag := match leanCM.info with | .empty => "empty" | .defn .. => "defn" | .axio .. => "axio" | .quot .. => "quot" | .indc .. => "indc" | .ctor .. => "ctor" | .recr .. => "recr" | .muts .. => "muts" - let rustTag := match rustCM.info with | .empty => "empty" | .defn .. => "defn" | .axio .. => "axio" | .quot .. => "quot" | .indc .. => "indc" | .ctor .. => "ctor" | .recr .. => "recr" | .muts .. => "muts" - if leanTag != rustTag then - IO.println s!" VARIANT DIFFERS: Lean={leanTag} Rust={rustTag}" - else - IO.println s!" variant: {leanTag}" - -- Field-by-field comparison for common variants - match leanCM.info, rustCM.info with - | .defn ln ll la lc larena ltr lvr, .defn rn rl ra rc rarena rtr rvr => do - if ln != rn then IO.println s!" name DIFFERS: Lean={ln} Rust={rn}" - if ll != rl then IO.println s!" lvls DIFFERS: Lean={ll.size} Rust={rl.size}" - if la != ra then IO.println s!" all DIFFERS: Lean={la} Rust={ra}" - if lc != rc then IO.println s!" ctx DIFFERS: Lean={lc} Rust={rc}" - if larena != rarena then IO.println s!" arena DIFFERS: Lean={larena.nodes.size} Rust={rarena.nodes.size}" - if ltr != rtr then IO.println s!" typeRoot DIFFERS: Lean={ltr} Rust={rtr}" - if lvr != rvr then IO.println s!" valueRoot DIFFERS: Lean={lvr} Rust={rvr}" - | .indc ln ll lctors la lc larena ltr, .indc rn rl rctors ra rc rarena rtr => do - if ln != rn then IO.println s!" name DIFFERS: Lean={ln} Rust={rn}" - if ll != rl then IO.println s!" lvls DIFFERS: Lean={ll.size} Rust={rl.size}" - if lctors != rctors then IO.println s!" ctors DIFFERS: Lean={lctors} Rust={rctors}" - if la != ra then IO.println s!" all DIFFERS: Lean={la} Rust={ra}" - if lc != rc then IO.println s!" ctx DIFFERS: Lean={lc} Rust={rc}" - if larena != rarena then IO.println s!" arena DIFFERS: Lean={larena.nodes.size} Rust={rarena.nodes.size}" - if ltr != rtr then IO.println s!" typeRoot DIFFERS: Lean={ltr} Rust={rtr}" - | .ctor ln ll li larena ltr, .ctor rn rl ri rarena rtr => do - if ln != rn then IO.println s!" name DIFFERS: Lean={ln} Rust={rn}" - if ll != rl then IO.println s!" lvls DIFFERS: Lean={ll.size} Rust={rl.size}" - if li != ri then IO.println s!" induct DIFFERS: Lean={li} Rust={ri}" - if larena != rarena then IO.println s!" arena DIFFERS: Lean={larena.nodes.size} Rust={rarena.nodes.size}" - if ltr != rtr then IO.println s!" typeRoot DIFFERS: Lean={ltr} Rust={rtr}" - | .recr ln ll lr la lc larena ltr lrr, .recr rn rl rr ra rc rarena rtr rrr => do - if ln != rn then IO.println s!" name DIFFERS: Lean={ln} Rust={rn}" - if ll != rl then IO.println s!" lvls DIFFERS: Lean={ll.size} Rust={rl.size}" - if lr != rr then IO.println s!" rules DIFFERS: Lean={lr} Rust={rr}" - if la != ra then IO.println s!" all DIFFERS: Lean={la} Rust={ra}" - if lc != rc then IO.println s!" ctx DIFFERS: Lean={lc} Rust={rc}" - if larena != rarena then IO.println s!" arena DIFFERS: Lean={larena.nodes.size} Rust={rarena.nodes.size}" - if ltr != rtr then IO.println s!" typeRoot DIFFERS: Lean={ltr} Rust={rtr}" - if lrr != rrr then IO.println s!" ruleRoots DIFFERS: Lean={lrr} Rust={rrr}" - | .muts la lal, .muts ra ral => do - if la != ra then IO.println s!" all DIFFERS: Lean={la} Rust={ra}" - if lal != ral then IO.println s!" auxLayout DIFFERS: Lean={reprStr lal} Rust={reprStr ral}" - | _, _ => IO.println s!" (other variant - use repr for details)" - else - IO.println s!"[Step 3] All full ConstantMeta match! ✓" - - IO.println "" - - -- Step 4: Compare serialized environments - IO.println s!"[Step 4] Comparing serialized environments..." - let serStart ← IO.monoMsNow - - -- Count total ExprMeta entries and mdata sizes across all named constants - let (leanTotalMetas, leanTotalMdata) := leanIxonEnv.named.fold (init := (0, 0)) fun (accMeta, accMdata) _ named => - let (metaCount, mdataSize) := named.constMeta.exprMetaStats - (accMeta + metaCount, accMdata + mdataSize) - let (rustTotalMetas, rustTotalMdata) := phases.compileEnv.named.fold (init := (0, 0)) fun (accMeta, accMdata) _ named => - let (metaCount, mdataSize) := named.constMeta.exprMetaStats - (accMeta + metaCount, accMdata + mdataSize) - - -- Count ExprMeta by type: (binder, letBinder, ref, prj, mdata) - let (leanBinder, leanLet, leanRef, leanPrj, leanMd) := leanIxonEnv.named.fold (init := (0, 0, 0, 0, 0)) fun (b, l, r, p, m) _ named => - let (b', l', r', p', m') := named.constMeta.exprMetaByType - (b + b', l + l', r + r', p + p', m + m') - let (rustBinder, rustLet, rustRef, rustPrj, rustMd) := phases.compileEnv.named.fold (init := (0, 0, 0, 0, 0)) fun (b, l, r, p, m) _ named => - let (b', l', r', p', m') := named.constMeta.exprMetaByType - (b + b', l + l', r + r', p + p', m + m') - - IO.println s!"[Step 4] Serializing Lean env ({leanIxonEnv.constCount} consts)..." - IO.println s!"[Step 4] Lean env has {leanIxonEnv.named.size} named, {leanIxonEnv.blobs.size} blobs, {leanIxonEnv.names.size} names" - IO.println s!"[Step 4] Lean total ExprMeta entries: {leanTotalMetas}, total mdata items: {leanTotalMdata}" - IO.println s!"[Step 4] Lean ExprMeta by type: binder={leanBinder}, letBinder={leanLet}, ref={leanRef}, prj={leanPrj}, mdata={leanMd}" - - -- Analyze blob sizes - let leanBlobSizes := leanIxonEnv.blobs.toList.map (·.2.size) - let leanTotalBlobData := leanBlobSizes.foldl (· + ·) 0 - let leanMaxBlob := leanBlobSizes.foldl max 0 - let leanAvgBlob := if leanBlobSizes.length > 0 then leanTotalBlobData / leanBlobSizes.length else 0 - let leanBig := leanBlobSizes.filter (· > 1000) |>.length - let leanHuge := leanBlobSizes.filter (· > 100000) |>.length - let leanTopSizes := leanBlobSizes.toArray.insertionSort (· > ·) |>.toList.take 10 - IO.println s!"[Step 4] Lean blob stats: total={fmtBytes leanTotalBlobData}, max={fmtBytes leanMaxBlob}, avg={fmtBytes leanAvgBlob}, big(>1kB)={leanBig}, huge(>100kB)={leanHuge}" - IO.println s!"[Step 4] Lean top 10 blob sizes: {leanTopSizes.map fmtBytes}" - - let (leanBlobs, leanConsts, leanHints, leanNames, leanNamed, leanComms) := Ixon.envSectionSizes leanIxonEnv - IO.println s!"[Step 4] Lean sections: blobs={fmtBytes leanBlobs}, consts={fmtBytes leanConsts}, hints={fmtBytes leanHints}, names={fmtBytes leanNames}, named={fmtBytes leanNamed}, comms={fmtBytes leanComms}" - let leanEnvBytes ← serializeEnv leanIxonEnv - IO.println s!"[Step 4] Lean env done: {fmtBytes leanEnvBytes.size}" - - IO.println s!"[Step 4] Serializing Rust env ({phases.compileEnv.constCount} consts)..." - IO.println s!"[Step 4] Rust env has {phases.compileEnv.named.size} named, {phases.compileEnv.blobs.size} blobs, {phases.compileEnv.names.size} names" - IO.println s!"[Step 4] Rust total ExprMeta entries: {rustTotalMetas}, total mdata items: {rustTotalMdata}" - IO.println s!"[Step 4] Rust ExprMeta by type: binder={rustBinder}, letBinder={rustLet}, ref={rustRef}, prj={rustPrj}, mdata={rustMd}" - - -- Analyze Rust blob sizes - let rustBlobSizes := phases.compileEnv.blobs.toList.map (·.2.size) - let rustTotalBlobData := rustBlobSizes.foldl (· + ·) 0 - let rustMaxBlob := rustBlobSizes.foldl max 0 - let rustAvgBlob := if rustBlobSizes.length > 0 then rustTotalBlobData / rustBlobSizes.length else 0 - let rustBig := rustBlobSizes.filter (· > 1000) |>.length - let rustHuge := rustBlobSizes.filter (· > 100000) |>.length - let rustTopSizes := rustBlobSizes.toArray.insertionSort (· > ·) |>.toList.take 10 - IO.println s!"[Step 4] Rust blob stats: total={fmtBytes rustTotalBlobData}, max={fmtBytes rustMaxBlob}, avg={fmtBytes rustAvgBlob}, big(>1kB)={rustBig}, huge(>100kB)={rustHuge}" - IO.println s!"[Step 4] Rust top 10 blob sizes: {rustTopSizes.map fmtBytes}" - - let (rustBlobs, rustConsts, rustHints, rustNames, rustNamed, rustComms) := Ixon.envSectionSizes phases.compileEnv - IO.println s!"[Step 4] Rust sections: blobs={fmtBytes rustBlobs}, consts={fmtBytes rustConsts}, hints={fmtBytes rustHints}, names={fmtBytes rustNames}, named={fmtBytes rustNamed}, comms={fmtBytes rustComms}" - let rustEnvBytes ← serializeEnv phases.compileEnv - IO.println s!"[Step 4] Rust env done: {fmtBytes rustEnvBytes.size}" - let serTime := (← IO.monoMsNow) - serStart - - IO.println s!"[Step 4] Lean env: {fmtBytes leanEnvBytes.size}" - IO.println s!"[Step 4] Rust env: {fmtBytes rustEnvBytes.size}" - IO.println s!"[Step 4] Serialization time: {serTime}ms" - - if leanEnvBytes == rustEnvBytes then - IO.println s!"[Step 4] Serialized environments match exactly! ✓" - IO.println "" - - return (true, 0, 0, none) - else - IO.println s!"[Step 4] Serialized environments DIFFER" - if let some diffPos := findFirstDiff leanEnvBytes rustEnvBytes then - IO.println s!"[Step 4] First difference at byte {diffPos}:" - let leanByte := if diffPos < leanEnvBytes.size then s!"0x{String.ofList <| Nat.toDigits 16 (leanEnvBytes.get! diffPos).toNat}" else "EOF" - let rustByte := if diffPos < rustEnvBytes.size then s!"0x{String.ofList <| Nat.toDigits 16 (rustEnvBytes.get! diffPos).toNat}" else "EOF" - IO.println s!"[Step 4] Lean: {leanByte}" - IO.println s!"[Step 4] Rust: {rustByte}" - - -- Find blobs in Rust but not in Lean - let mut missingInLean : Array (Address × Nat) := #[] - for (addr, bytes) in phases.compileEnv.blobs.toList do - if !leanIxonEnv.blobs.contains addr then - missingInLean := missingInLean.push (addr, bytes.size) - if !missingInLean.isEmpty then - IO.println s!"[Step 4] Blobs in Rust but not Lean: {missingInLean.size}" - for (addr, size) in missingInLean.toList.take 10 do - -- Try to show content if small - if let some bytes := phases.compileEnv.blobs.get? addr then - if size < 100 then - let content := String.fromUTF8? bytes |>.getD s!"" - IO.println s!"[Step 4] {addr} ({fmtBytes size}): {content}" - else - IO.println s!"[Step 4] {addr} ({fmtBytes size})" - - -- Find blobs in Lean but not in Rust - let mut missingInRust : Array (Address × Nat) := #[] - for (addr, bytes) in leanIxonEnv.blobs.toList do - if !phases.compileEnv.blobs.contains addr then - missingInRust := missingInRust.push (addr, bytes.size) - if !missingInRust.isEmpty then - IO.println s!"[Step 4] Blobs in Lean but not Rust: {missingInRust.size}" - for (addr, size) in missingInRust.toList.take 10 do - if let some bytes := leanIxonEnv.blobs.get? addr then - if size < 100 then - let content := String.fromUTF8? bytes |>.getD s!"" - IO.println s!"[Step 4] {addr} ({fmtBytes size}): {content}" - else - IO.println s!"[Step 4] {addr} ({fmtBytes size})" - - IO.println "" - -- Note: We expect this to fail until Lean generates metadata - return (false, 0, 0, some s!"Serialized environments differ (Lean: {fmtBytes leanEnvBytes.size}, Rust: {fmtBytes rustEnvBytes.size})") - else - -- Report mismatches - IO.println s!"[Step 3] Found {result.mismatchedConstants.size} mismatches!" - IO.println "" - - -- Show first mismatch in detail - if let some first := result.mismatchedConstants[0]? then - IO.println s!"[Mismatch] First discrepancy: {first.name}" - IO.println s!" Lean address: {first.leanAddr}" - IO.println s!" Rust address: {first.rustAddr}" - IO.println "" - - IO.println s!" Lean bytes ({fmtBytes first.leanBytes.size}):" - IO.println s!"{hexDump first.leanBytes 128}" - IO.println "" - - IO.println s!" Rust bytes ({fmtBytes first.rustBytes.size}):" - IO.println s!"{hexDump first.rustBytes 128}" - IO.println "" - - if let some diffPos := findFirstDiff first.leanBytes first.rustBytes then - IO.println s!" First difference at byte {diffPos}:" - let leanByte := if diffPos < first.leanBytes.size then s!"0x{String.ofList <| Nat.toDigits 16 (first.leanBytes.get! diffPos).toNat}" else "EOF" - let rustByte := if diffPos < first.rustBytes.size then s!"0x{String.ofList <| Nat.toDigits 16 (first.rustBytes.get! diffPos).toNat}" else "EOF" - IO.println s!" Lean: {leanByte}" - IO.println s!" Rust: {rustByte}" - IO.println "" - - if !result.missingInRust.isEmpty then - IO.println s!"[Missing] {result.missingInRust.size} constants in Lean but not in Rust" - for name in result.missingInRust.toList.take 5 do - IO.println s!" - {name}" - if result.missingInRust.size > 5 then - IO.println s!" ... and {result.missingInRust.size - 5} more" - IO.println "" - - if !result.missingInLean.isEmpty then - IO.println s!"[Missing] {result.missingInLean.size} constants in Rust but not in Lean" - for name in result.missingInLean.toList.take 5 do - IO.println s!" - {name}" - if result.missingInLean.size > 5 then - IO.println s!" ... and {result.missingInLean.size - 5} more" - IO.println "" - - return (false, 0, 0, some s!"Found {result.mismatchedConstants.size} mismatches") - ) .done - -/-! ## Test Suite -/ - -public def compileSuiteIO : List TestSeq := [ - testCrossImpl, -] - -end Tests.Compile diff --git a/Tests/Ix/Compile/AuxiliaryClosure.lean b/Tests/Ix/Compile/AuxiliaryClosure.lean deleted file mode 100644 index 32825087b..000000000 --- a/Tests/Ix/Compile/AuxiliaryClosure.lean +++ /dev/null @@ -1,176 +0,0 @@ -/- - auxiliary-closure: Lean's auxiliaries and mutual definitions on - closure-only environments. - - A raw dependency closure (`Ix.EnvScope.collectDeps`, which any claim - built from a dependency closure may use) hands the compiler only the - seeds' transitive dependencies. The cases below keep the raw collector on - purpose: the selected closure (`collectSelectedDeps`: recursors, whole - logical units, checker support) is a superset and would hide the partial - families tested here. Such a closure can hold a nested auxiliary - `.brecOn_N`/`.below_N`, or one member's `.brecOn`, without the - `.brecOn`/`.below` of the block's first member, and a Prop inductive's - auxiliaries nested through a Prop inductive. Each case compiles exactly - that closure (`ix compile`'s path, fail-closed) and type-checks the seed - with the Rust kernel (`rsCheckConstsFFI`: Lean closure → Ixon compile → - kernel), and expects both to pass. - - A second case, `mutual` definitions on closure-only environments: a - structural, well-founded or `partial` `mutual` member's value goes - through auxiliaries and never mentions its siblings, but its compiled - metadata carries Lean's `all` (the siblings' names), which meta kernel - ingress resolves through the env's Named entries. `collectDeps` follows a - definition's `all`, so the closure of `oddN` (or of a theorem about it) - holds `evenN`, and `ix check-rs` accepts the written `.ixe` (before, - `resolve_all: Named entry for 'evenN' missing`). That case compiles the - closure to a file, as `ix compile` does, and checks the file with the - meta Rust kernel, as `ix check-rs` does. - - Run with: `lake test -- --ignored auxiliary-closure`. --/ -import Ix.Meta -import Ix.EnvScope -import Ix.KernelCheck -import Ix.CompileM -import Ix.Ixon -import LSpec - -open LSpec -open Ix.KernelCheck (CheckError rsCheckConstsFFI rsCheckIxonFFI) - -namespace Tests.Ix.Compile.AuxiliaryClosure - -/-- Nested through `List`: one class, one auxiliary (`T.rec_1` and so on). -/ -inductive T where - | mk : List T → T - --- Mutual and nested. -mutual -inductive A where - | mk : List B → A -inductive B where - | mk : A → B -end - -/-- Prop nested through a Prop-valued family: one class, one auxiliary - (`PV.below_1` is an inductive, `PV.brecOn_1` a theorem). -/ -inductive Pw (R : Nat → Nat → Prop) : List Nat → List Nat → Prop where - | nil : Pw R [] [] - | cons : R a b → Pw R as bs → Pw R (a :: as) (b :: bs) - -inductive PV : Nat → Nat → Prop where - | base : PV 0 0 - | node : Pw PV xs ys → PV xs.length ys.length - -/-- Seeds whose closure lacks the first member's `.brecOn`/`.below`, - and the auxiliaries of a nested Prop block. -/ -def seeds : List Lean.Name := [ - ``T.brecOn_1, ``T.below_1, ``A.brecOn_1, ``A.below_1, ``A.brecOn, - ``PV.brecOn, ``PV.brecOn_1, ``PV.below_1 -] - --- `mutual` definitions whose values do not mention their siblings. -mutual -/-- Structural: the value goes through `evenN._f`/`oddN._f`. -/ -def evenN : Nat → Bool - | 0 => true - | n + 1 => oddN n -def oddN : Nat → Bool - | 0 => false - | n + 1 => evenN n -end - -mutual -/-- Well-founded: the value goes through `wfEven._mutual`. -/ -def wfEven : Nat → Bool - | 0 => true - | n + 1 => wfOdd n -termination_by n => n -def wfOdd : Nat → Bool - | 0 => false - | n + 1 => wfEven n -termination_by n => n -end - -mutual -/-- `partial`: an opaque with a default value. -/ -partial def pEven : Nat → Bool - | 0 => true - | n + 1 => pOdd n -partial def pOdd : Nat → Bool - | 0 => false - | n + 1 => pEven n -end - -theorem oddN_one : oddN 1 = true := rfl - -/-- Seeds whose closure must hold the named sibling. -/ -def mutualSeeds : List (Lean.Name × Lean.Name) := [ - (``oddN, ``evenN), (``wfOdd, ``wfEven), (``pOdd, ``pEven), (``oddN_one, ``evenN) -] - -def suite : List TestSeq := [ - .individualIO "auxiliaries on closure-only environments" none (do - let env ← get_env! - let mut failed := 0 - let dir ← IO.FS.createTempDir - for seed in seeds do - let mut errs : Array String := #[] - let closure := Ix.EnvScope.collectDeps env [seed] - -- `ix compile`'s path on the raw closure, fail-closed. - let prepared ← IO.ofExcept <| - Ix.Compile.prepareRegisteredConstants env closure - let status ← Ix.CompileM.rsCompileEnvBytesFFI prepared - (dir / s!"{seed}.ixe").toString false - if let some (n, r) := status.ungrounded[0]? then - errs := errs.push s!"compile ({status.ungrounded.size} ungrounded): {n}: {r}" - let results ← rsCheckConstsFFI closure #[seed] #[true] false - match results[0]? with - | some none => pure () - | some (some (CheckError.kernelException m)) => errs := errs.push s!"kernel: {m}" - | some (some (CheckError.compileError m)) => errs := errs.push s!"check-compile: {m}" - | none => errs := errs.push "no kernel result" - if errs.isEmpty then - IO.println s!"[auxiliary-closure] {seed}: ok" - else - failed := failed + 1 - for e in errs do IO.println s!"[auxiliary-closure] FAIL {seed}: {e}" - let n := seeds.length - return (failed == 0, n - failed, n, - if failed == 0 then none else some s!"{failed} closure(s) failed")) - (.individualIO "mutual definitions on closure-only environments" none (do - let env ← get_env! - let mut failed := 0 - let dir ← IO.FS.createTempDir - for (seed, sibling) in mutualSeeds do - let mut errs : Array String := #[] - let closure := Ix.EnvScope.collectDeps env [seed] - if !closure.any (·.1 == sibling) then - errs := errs.push s!"closure lacks {sibling}" - -- `ix compile`'s path on the raw closure, written to a file … - let path := (dir / s!"{seed}.ixe").toString - let prepared ← IO.ofExcept <| - Ix.Compile.prepareRegisteredConstants env closure - let status ← Ix.CompileM.rsCompileEnvBytesFFI prepared path false - if let some (n, r) := status.ungrounded[0]? then - errs := errs.push s!"compile ({status.ungrounded.size} ungrounded): {n}: {r}" - else - -- … and the `ix check-rs` path (meta mode) on that file. - let results ← rsCheckIxonFFI path #[seed] #[true] true "" - match results[0]? with - | some none => pure () - | some (some (CheckError.kernelException m)) => errs := errs.push s!"kernel: {m}" - | some (some (CheckError.compileError m)) => errs := errs.push s!"check-compile: {m}" - | none => errs := errs.push "no kernel result" - if errs.isEmpty then - IO.println s!"[auxiliary-closure] {seed}: ok" - else - failed := failed + 1 - for e in errs do IO.println s!"[auxiliary-closure] FAIL {seed}: {e}" - let n := mutualSeeds.length - return (failed == 0, n - failed, n, - if failed == 0 then none else some s!"{failed} closure(s) failed")) - .done) -] - -end Tests.Ix.Compile.AuxiliaryClosure diff --git a/Tests/Ix/Compile/AuxiliaryClosureCanon.lean b/Tests/Ix/Compile/AuxiliaryClosureCanon.lean index f9f2b69da..1d1550ea4 100644 --- a/Tests/Ix/Compile/AuxiliaryClosureCanon.lean +++ b/Tests/Ix/Compile/AuxiliaryClosureCanon.lean @@ -1,4 +1,14 @@ -/- +import Ix.Meta +import Ix.EnvScope +import Ix.KernelCheck +import Tests.Ix.Kernel.FocusedLeanCheck +import Ix.CompileM +import Ix.CompileCertified +import Ix.RoleBinding.Compile +import Ix.Resource.Typing +import LSpec + +/-! canon-closure-aux: an auxiliary's address does not depend on the compile set. @@ -15,35 +25,52 @@ For each fixture constant (the seed) this compiles the seed's closure and checks, for every name the closure output shares with the reference - compile (the closure of all fixtures, where every family is complete), - that the address is the reference's; and type-checks the seed with the - Rust kernel. Two legs: + compile, that the address is the reference's. The reference is the + selected closure of all fixtures (`collectSelectedDeps`, where every + family is complete), compiled and bound as `ix compile --consts` compiles + and binds it (`Ix.RoleBinding.Compile.finalize`). Four legs: - `collectDeps`: the raw closure producers' closure, which pulls an auxiliary's whole family (`Lean.auxFamilySiblings`) and a definition's - `all`. Every address must match. (`ix compile --consts` now takes the - selected closure, `collectSelectedDeps`, a superset with recursors, - whole logical units and checker support; this leg keeps the - raw collector, which reaches fewer family members.) + `all`. Every address must match. - `raw`: the closure without family completion (`rawDeps`), which tests the compiler's per-family block membership on its own. Every address must match: the compiler compiles Lean's auxiliaries as Lean declared them, so a family-incomplete slice (a nested block's class `.brecOn.eq`, whose family holds `.brecOn_N.eq`) compiles to the whole compile's addresses. - - `pack`: `ix pack` (`rsPackEnv`) of the reference to each auxiliary - seed keeps the seed's address and type-checks (`rsCheckIxonFFI`). + - `selected`: the selected closure (`collectSelectedDeps`, what + `ix compile --consts` takes: recursors, whole logical units, checker + and literal support). Every address must match, and the certified + checker (IxC, `rsCheckIxonFFI`) accepts or types the seed in that + artifact. + - `pack`: `ix pack` (`rsPackEnv`) of the bound reference to each + auxiliary seed keeps the seed's address, and IxC accepts or types the + seed in the bundle. + + And `twin` (`twinRecursorCheck`): a closure that is not closed over its + recursors can meet the compile's recursor check on alpha-equivalent + families, which refuses it whole; closed over them it compiles. + + Only the `selected` and `pack` artifacts are type-checked. IxC reads an + inductive block together with its recursor and declines it otherwise + (`reader: inductive block without a recursor in the input`, + `Ix.EnvScope.ClosureOptions.recursors`), and neither the `collectDeps` + nor the `raw` closure carries the recursors of the library families it + reaches (`PProd`, `Nat`, …). The selected closure of either leg's closure + is the seed's own selected closure (`collectSelectedDeps` is a fixed + point, and each leg's closure lies inside the seed's), so the `selected` + leg checks the seed in exactly the closure that makes either leg's input + self-contained. Likewise `ix pack` carries the recursors of the + inductives a bundle reaches only for a bound artifact + (`crates/ixon/src/binding_support.rs`), so the reference is bound. On + `main` these checks ran the Rust kernel, which needed neither. - Run with: `lake test -- --ignored canon-closure-aux`. + Run with: `lake test -- canon-closure-aux`. -/ -import Ix.Meta -import Ix.EnvScope -import Ix.KernelCheck -import Ix.CompileM -import LSpec open LSpec -open Ix.KernelCheck (CheckError rsCheckConstsFFI rsCheckIxonFFI) +open Ix.KernelCheck (CheckError rsCheckIxonFFI) namespace Tests.Ix.Compile.AuxiliaryClosureCanon @@ -154,18 +181,47 @@ end end Fx -/-- Compile `consts` with the Rust compiler (the `ix compile` - path: fail-closed) and read the output back. -/ +/-- The file `compileClosure` writes for `tag`. -/ +def artifactPath (dir : System.FilePath) (tag : String) : System.FilePath := + dir / s!"{tag}.ixe" + +/-- Compile `consts` with the certified compiler (`ix compile` + path: fail-closed), write it to `artifactPath dir tag` and read the + output back. With `bind`, the role support, certificates and binding are + added first, as `ix compile --consts` adds them + (`Ix.RoleBinding.Compile.finalize`). -/ def compileClosure (env : Lean.Environment) (dir : System.FilePath) - (tag : String) (consts : List (Lean.Name × Lean.ConstantInfo)) : + (tag : String) (consts : List (Lean.Name × Lean.ConstantInfo)) (bind : Bool := false) : IO (Except String Ixon.Env) := do - let prepared ← IO.ofExcept <| Ix.Compile.prepareRegisteredConstants env consts - let path := dir / s!"{tag}.ixe" - let status ← Ix.CompileM.rsCompileEnvBytesFFI prepared path.toString false - if let some (n, r) := status.ungrounded[0]? then - return .error s!"compile ({status.ungrounded.size} ungrounded): {n}: {r}" + let out ← match ← Ix.CompileM.compileCertified env consts with + | .ok out => pure out + | .error e => return .error e + let ungrounded := out.failures.toMap.toArray + if let some (n, r) := ungrounded[0]? then + return .error s!"compile ({ungrounded.size} ungrounded): {n.pretty}: {r}" + let mut artifact := out.env + if bind then + try + artifact ← Ix.RoleBinding.Compile.finalize (Ix.RoleBinding.Compile.certifiedBackend 32) env + Ix.RoleBinding.Compile.nativeCertSource (Ix.Compile.CompileInput.plain consts) out.env + Ix.Resource.typingLimits (existing? := out.existing?) + catch e => return .error s!"finalize: {e}" + let path := artifactPath dir tag + let bytes ← IO.ofExcept (Ixon.serEnv artifact) + IO.FS.writeBinFile path bytes return Ixon.rsDeEnv (← IO.FS.readBinFile path) +/-- The certified checker's verdict on `seed` in the artifact at `path` + (`rsCheckIxonFFI`: `ix check-lean`'s selected check), as failure + messages: none when IxC accepts or types it. -/ +def checkSeed (path : System.FilePath) (seed : Lean.Name) : IO (Array String) := do + let results ← rsCheckIxonFFI path.toString #[seed] #[true] true "" + return match results[0]? with + | some none => #[] + | some (some (CheckError.kernelException m)) => #[s!"kernel: {m}"] + | some (some (CheckError.compileError m)) => #[s!"check-compile: {m}"] + | none => #["no kernel result"] + /-- The closure WITHOUT auxiliary-family completion: `Ix.EnvScope.collectDeps` minus `Lean.auxFamilySiblings` (type, value, ctor/inductive links, `all` links, recursor rules). This is what the closure producers handed the @@ -222,14 +278,42 @@ def withSupport (env : Lean.Environment) (closure : List (Lean.Name × Lean.Cons closure ++ (Ix.EnvScope.collectDeps env (Ix.EnvScope.introducedSupport env)).filter (!present.contains ·.1) +/-- The compile's recursor check on alpha-equivalent families + (`Ix.Cert.RecursorsOwned`, `twin`): `HAdd` and `HPow` compile to one + inductive block, and `HAdd.rec`'s record is the one `HPow.rec` would + compile to. A closure that holds `HAdd.rec` and reaches `HPow` without + `HPow.rec` is refused whole, since the recursor index files the shared + record under the block and it is not a published recursor of `HPow`'s + family; closed over its recursors (`ClosureOptions.recursors`, as every + selected closure and whole environment is) it compiles. Failure + messages, none when both hold. -/ +def twinRecursorCheck (env : Lean.Environment) : Array String := Id.run do + let names (c : List (Lean.Name × Lean.ConstantInfo)) := c.map (·.1) + let without := (Ix.EnvScope.collectDeps env [``HAdd.rec, ``HPow]).filter (·.1 != ``HPow.rec) + let closed := Ix.EnvScope.collectDeps env (names without) { recursors := true } + let mut errors : Array String := #[] + unless (names without).contains ``HAdd.rec && (names closed).contains ``HPow.rec do + errors := errors.push s!"twin: the closures are not the case: {names without}" + match Ix.Cert.compileReport (Ix.CompileM.canonInput without.toArray 32) with + | .error e => + let e := toString e + unless (e.splitOn "recursor check failed").length > 1 && (e.splitOn "HPow").length > 1 do + errors := errors.push s!"twin: refused, but not by HPow's recursor check: {e}" + | .ok _ => errors := errors.push "twin: HPow without HPow.rec beside HAdd.rec compiled" + if let .error e := Ix.Cert.compileReport (Ix.CompileM.canonInput closed.toArray 32) then + errors := errors.push s!"twin: closed over its recursors, refused: {e}" + return errors + /-- One leg: per seed, compile `closureOf seed` (with `withSupport`), compare every shared name's - address with `ref`, kernel-check the seed. A seed with `refused seed = - some msg` must instead be refused by the compiler with `msg` in the - error (and is not kernel-checked: nothing was written). -/ + address with `ref`, and with `check` kernel-check the seed in that + artifact (`checkSeed`). A seed with `refused seed = some msg` must + instead be refused by the compiler with `msg` in the error (and is not + kernel-checked: nothing was written). -/ def runLeg (env : Lean.Environment) (dir : System.FilePath) (ref : Ixon.Env) (label : String) (seeds : Array Lean.Name) (closureOf : Lean.Name → List (Lean.Name × Lean.ConstantInfo)) - (refused : Lean.Name → Option String) : IO Nat := do + (refused : Lean.Name → Option String) (check : Bool := false) : IO Nat := do + let t0 ← IO.monoMsNow let mut failed := 0 for seed in seeds do let mut errs : Array String := #[] @@ -256,18 +340,15 @@ def runLeg (env : Lean.Environment) (dir : System.FilePath) (ref : Ixon.Env) if a != b then errs := errs.push s!"{n}: {a} in the closure, {b} in the reference" | none, _ => errs := errs.push s!"{n}: not in the closure output" | some _, none => pure () - let results ← rsCheckConstsFFI closure #[seed] #[true] false - match results[0]? with - | some none => pure () - | some (some (CheckError.kernelException m)) => errs := errs.push s!"kernel: {m}" - | some (some (CheckError.compileError m)) => errs := errs.push s!"check-compile: {m}" - | none => errs := errs.push "no kernel result" + if check then + errs := errs ++ (← checkSeed (artifactPath dir s!"{label}-{seed}") seed) if !errs.isEmpty then failed := failed + 1 for e in errs[:4] do IO.println s!"[canon-closure-aux] {label} FAIL {seed}: {e}" if errs.size > 4 then IO.println s!"[canon-closure-aux] {label} FAIL {seed}: … {errs.size - 4} more" - IO.println s!"[canon-closure-aux] {label}: {seeds.size - failed}/{seeds.size} seeds ok" + IO.println s!"[canon-closure-aux] {label}: {seeds.size - failed}/{seeds.size} seeds ok \ + ({(← IO.monoMsNow) - t0} ms)" return failed def suite : List TestSeq := [ @@ -278,31 +359,42 @@ def suite : List TestSeq := [ if fx.isPrefixOf n && n != fx then some n else none).toArray.qsort (·.toString < ·.toString) let dir ← IO.FS.createTempDir + -- The reference: every fixture's selected closure, compiled and bound as + -- `ix compile --consts` compiles and binds it. + let t0 ← IO.monoMsNow let ref ← match ← compileClosure env dir "ref" - (withSupport env (Ix.EnvScope.collectDeps env seeds.toList)) with + (Ix.EnvScope.collectSelectedDeps env seeds.toList) (bind := true) with | .ok e => pure e | .error e => throw (IO.userError s!"reference compile: {e}") + IO.println s!"[canon-closure-aux] reference: {seeds.size} seeds, {ref.named.size} names, \ + bound ({(← IO.monoMsNow) - t0} ms); files in {dir}" -- Leg 1, the closure producers (`collectDeps`): every address is the - -- reference's, every seed type-checks. + -- reference's. let f1 ← runLeg env dir ref "collectDeps" seeds (fun s => Ix.EnvScope.collectDeps env [s]) (fun _ => none) -- Leg 2, the compiler on family-incomplete slices: same, a nested -- block's class `.brecOn.eq` included. let f2 ← runLeg env dir ref "raw" seeds (fun s => rawDeps env [s]) (fun _ => none) - -- Leg 3, the pack-side closure (`ix pack`, `Env::prune_to_closure`): + -- Leg 3, the selected closure (`ix compile --consts`), self-contained + -- for IxC: every address is the reference's, and the seed type-checks. + let f3 ← runLeg env dir ref "selected" seeds + (fun s => Ix.EnvScope.collectSelectedDeps env [s]) (fun _ => none) (check := true) + -- Leg 4, the pack-side closure (`ix pack`, `Env::prune_to_closure`): -- a block is one Ixon constant, so pruning the reference to an -- auxiliary keeps its whole block; the bundle must name the seed at the - -- reference's address and type-check. + -- reference's address and type-check. The reference is bound, so the + -- bundle carries the recursors of the inductives it reaches. let auxSeeds := seeds.filter fun s => match s with | .str _ last => ["rec", "casesOn", "recOn", "below", "brecOn", "go", "eq"].contains last || ["rec_", "below_", "brecOn_"].any (fun p => last.startsWith p) | _ => false - let refPath := (dir / "ref.ixe").toString - let mut f3 := 0 + let refPath := (artifactPath dir "ref").toString + let t4 ← IO.monoMsNow + let mut f4 := 0 for seed in auxSeeds do let mut errs : Array String := #[] - let bundle := (dir / s!"pack-{seed}.ixe").toString - match ← (Ixon.rsPackEnv refPath (toString seed) #[] bundle false false).toBaseIO with + let bundle := artifactPath dir s!"pack-{seed}" + match ← (Ixon.rsPackEnv refPath (toString seed) #[] bundle.toString false false).toBaseIO with | .error e => errs := errs.push s!"pack: {e}" | .ok () => match Ixon.rsDeEnv (← IO.FS.readBinFile bundle) with @@ -311,18 +403,20 @@ def suite : List TestSeq := [ let ixn := Ix.Name.fromLeanName seed if b.getAddr? ixn != ref.getAddr? ixn then errs := errs.push s!"bundle address {b.getAddr? ixn} ≠ reference {ref.getAddr? ixn}" - let results ← rsCheckIxonFFI bundle #[seed] #[true] true "" - match results[0]? with - | some none => pure () - | some (some (CheckError.kernelException m)) => errs := errs.push s!"kernel: {m}" - | some (some (CheckError.compileError m)) => errs := errs.push s!"check-compile: {m}" - | none => errs := errs.push "no kernel result" + errs := errs ++ (← checkSeed bundle seed) if !errs.isEmpty then - f3 := f3 + 1 + f4 := f4 + 1 for e in errs do IO.println s!"[canon-closure-aux] pack FAIL {seed}: {e}" - IO.println s!"[canon-closure-aux] pack: {auxSeeds.size - f3}/{auxSeeds.size} seeds ok" - let n := 2 * seeds.size + auxSeeds.size - let failed := f1 + f2 + f3 + IO.println s!"[canon-closure-aux] pack: {auxSeeds.size - f4}/{auxSeeds.size} seeds ok \ + ({(← IO.monoMsNow) - t4} ms)" + -- The recursor check on alpha-equivalent families, which a closure + -- not closed over its recursors can meet. + let twinErrors := twinRecursorCheck env + for e in twinErrors do IO.println s!"[canon-closure-aux] FAIL {e}" + IO.println s!"[canon-closure-aux] twin: {if twinErrors.isEmpty then "ok" else "FAIL"}" + let n := 3 * seeds.size + auxSeeds.size + 1 + let failed := f1 + f2 + f3 + f4 + (if twinErrors.isEmpty then 0 else 1) + if failed == 0 then IO.FS.removeDirAll dir return (failed == 0, n - failed, n, if failed == 0 then none else some s!"{failed} seed check(s) failed")) .done diff --git a/Tests/Ix/Compile/CheckerSupport.lean b/Tests/Ix/Compile/CheckerSupport.lean index 0e828533b..dfdaa46bb 100644 --- a/Tests/Ix/Compile/CheckerSupport.lean +++ b/Tests/Ix/Compile/CheckerSupport.lean @@ -1,4 +1,8 @@ -/- +import Ix.Meta +import Ix.EnvScope +import Tests.Ix.Compile.ClosureHarness + +/-! checker-support-regression: a selected closure carries the checker's certificate ground (`Ix.EnvScope.checkerSupportOf`, from `IxC.Kernel.Roles` `Role.deps`), on the exact failing corpus case `S_T_0_0_dir` @@ -22,7 +26,8 @@ compile's. The raw compile's Named entries are unchanged by the support. - The certified checker accepts `Nat.land`, `Nat.mul` and the helpers. - Every fixture constant is accepted or declined in a documented class. + Every fixture constant is accepted, typed by the typing tier (the + partial `go._unsafe_rec`), or declined in a documented class. - The output decompiles to the source. Ported from 650500c2 and a9cf3607 `Tests/Ix/Compile/CheckerSupport.lean`: @@ -43,9 +48,6 @@ Run with: `lake test -- --ignored checker-support-regression` (after `lake build kernel-check-ixe`). -/ -import Ix.Meta -import Ix.EnvScope -import Tests.Ix.Compile.ClosureHarness open Lean open Tests.Ix.Compile.ClosureHarness @@ -72,7 +74,7 @@ private def require (ok : Bool) (message : String) : IO Unit := /-- `compileClosure`, with no refusal. -/ private def compileClean (label : String) (env : Environment) - (closure : List (Name × ConstantInfo)) : IO Ix.CompileM.LeanPipelineOut := do + (closure : List (Name × ConstantInfo)) : IO ClosureOut := do let out ← compileClosure env closure (workers := 4) require out.cenv.ungrounded.isEmpty s!"{label}: {out.cenv.ungrounded.size} refusals, first \ @@ -127,7 +129,9 @@ def run : IO UInt32 := do require (row.outcome == "accept") s!"selected: {name}: {row.outcome}: {row.reason}" for name in seeds do let row ← verdict positive closed.env name - require (row.outcome == "accept" || + -- `typed`: a partial or unsafe fixture constant (`go._unsafe_rec`) is a + -- tier record, which the typing tier types (`KernelReport.outcomes`) + require (row.outcome == "accept" || row.outcome == "typed" || (row.outcome == "decline" && Tests.Ix.Compile.ClosureHarness.documentedDecline row.reason)) s!"selected: fixture {name}: {row.outcome}: {row.reason}" -- the raw closure: the exact decline, and the helpers blocked diff --git a/Tests/Ix/Compile/ClaimConflict.lean b/Tests/Ix/Compile/ClaimConflict.lean deleted file mode 100644 index 62a48ca53..000000000 --- a/Tests/Ix/Compile/ClaimConflict.lean +++ /dev/null @@ -1,324 +0,0 @@ -/- - compile-claim-conflict: single ownership of compiled names, in both - compilers (a name assigned twice is an error unless both assignments are - equal). - - Rust's scheduler claims names insert-once (`CompileState::claim_compiled_name`) - and raises - `conflicting claims for name '…': already registered at …, claimed again at …` - on a second claim at another address. The Lean drivers check the same claims - at merge time (`Ix.CompileM.checkBlockClaims`). Two parts: - - 1. **Driver API.** The insert-once merge is driven directly: through - `checkBlockClaims` then `mergeCompiledBlock` (what every driver does - with a compiled block) and through `applyAuxBlockOutcome` (the wave - driver's merge, with two blocks of one wave claiming one name): a second - compiled claim at another address is refused with Rust's message, - character for character, and an identical re-claim is accepted. An - identical re-claim of a name an earlier block owns keeps the owner's - `Named` entry, while a block's own second registration of a name - replaces its first. A source dependency is ready only when its own block - has settled (`auxDependencyReady`). - 2. **Both compilers.** A constructed closure holds a user constant named - `T.below` (a plain definition, not Lean's auxiliary) for a recursive - inductive `T` whose field type depends on it, so `T.below` compiles - before `T` in every schedule. Real Lean input cannot contain this: - Lean's own `mkBelow` would have failed on the name. Both compilers - compile Lean's declarations as given, so `T`'s block claims only its - own names and nothing claims the user's `T.below` a second time: the - Rust compiler (`ix compile`'s FFI), the fold, the reference schedule - (least and greatest key first) and the `ix compile-lean` pipeline (1, 4, - 16 workers) must all compile the whole closure, with no refusal. - - Run with: `lake test -- --ignored compile-claim-conflict`. --/ -import Ix.Meta -import Ix.EnvScope -import Ix.CompileDriver -import Tests.Ix.Compile.ScheduleReference - -open Lean - -namespace Tests.Ix.Compile.ClaimConflict - -namespace Fixture - -/-- Stands in for a user constant named `T.below`: the constructed closure - renames it. -/ -def FakeBelow : Type := PUnit - -/-- `T`'s field type. In the constructed closure its body is the user - `T.below`, so `T`'s block depends on it. -/ -def Dep : Type := FakeBelow - -/-- A recursive inductive: Lean generates `T.below` for it. -/ -inductive T where - | leaf : T - | node : Dep → T → T - -end Fixture - -/-! ## Part 1: the driver API -/ - -section DriverApi -open Ix.CompileM - -private def addr (s : String) : Address := Address.blake3 s.toUTF8 - -private def ixName (s : String) : Ix.Name := Ix.Name.fromLeanName s.toName - -/-- Rust's `name_claim_conflict` format, written out. -/ -private def rustConflict (n : Ix.Name) (existing claimed : Address) : String := - s!"conflicting claims for name '{n.pretty}': already registered at \ -{(toString existing).take 12}, claimed again at {(toString claimed).take 12}" - -private def emptyAcc : DriverAcc := { cenv := default } - -/-- A block result whose lone constant `lo` sits at `a` (no projections). -/ -private def loneResult (a : Address) : BlockResult := - { block := default, blockBytes := ByteArray.empty, blockAddr := a } - -/-- What every driver does with a compiled block: `checkBlockClaims`, then - `mergeCompiledBlock` if the check passes. -/ -private def merge (acc : DriverAcc) (lo : Ix.Name) (result : BlockResult) (cache : BlockState) : - Except CompileError DriverAcc := do - checkBlockClaims acc.cenv (primaryClaims lo result) - pure (mergeCompiledBlock acc lo result cache) - -private def errOf : Except CompileError DriverAcc → Option String - | .error (.invalidMutualBlock r) => some r - | .error e => some s!"(another error) {e}" - | .ok _ => none - -/-- The driver-API cases: (label, expected message or `none` for accepted, - actual). -/ -def driverCases : List (String × Option String × Option String) := Id.run do - let n := ixName "Fx.T.below" - let x := addr "x" - let y := addr "y" - let withCompiled : DriverAcc := - { cenv := { (default : CompileEnv) with nameToAddr := ({} : Std.HashMap _root_.Ix.Compile.ExactKey.ExactName _).insert n x } } - let mut cases : List (String × Option String × Option String) := [] - -- a second compiled claim at another address - cases := cases ++ [("second compiled claim", some (rustConflict n x y), - errOf (merge withCompiled n (loneResult y) default))] - cases := cases ++ [("identical second compiled claim", none, - errOf (merge withCompiled n (loneResult x) default))] - -- the wave driver: two blocks of one wave, computed on one snapshot, - -- claim `n` at different addresses; the second merge is refused and the - -- block is reported failed (its dependents are released as failed) - let all : Ix.Set Ix.Name := ({} : Ix.Set Ix.Name).insert n - let (acc₁, _, failed₁) := applyAuxBlockOutcome emptyAcc n all - (.compiled (loneResult x) default) - let (acc₂, names₂, failed₂) := applyAuxBlockOutcome acc₁ n all - (.compiled (loneResult y) default) - let waveMsg : Option String := - if failed₁ || !failed₂ || !names₂.isEmpty || acc₂.cenv.nameToAddr.get? n != some x then - some s!"wrong outcome: failed {failed₁}/{failed₂}, registered {names₂.size}, \ -claim {acc₂.cenv.nameToAddr.get? n}" - else (acc₂.cenv.ungrounded.get? n).map fun m => (m.drop "invalidMutualBlock: ".length).toString - cases := cases ++ [("wave: two blocks of one wave", some (rustConflict n x y), waveMsg)] - return cases - -/-- An identical re-claim of a name an earlier block owns does not replace - the owner's `Named` entry (its metadata, its hints); a name one block - registers twice keeps that block's last entry. Mirrors Rust - `compile::tests::identical_reclaim_keeps_the_owner_named` and - `reclaim_within_one_block_reregisters`. -/ -def ownerNamedCheck : Except String Unit := do - let n := ixName "Fx.A.rec_1" - let lo := ixName "Fx.T" - let x := addr "x" - let tag (s : String) : Ixon.ConstantMeta := .new (.muts #[#[addr s]] none) - let owner : Ixon.Named := - { addr := x, constMeta := tag "owner", hints := some .abbrev } - -- a primary name over an earlier block's compiled claim of `n` at `x` - let compiled : DriverAcc := { cenv := { (default : CompileEnv) with - nameToAddr := ({} : Std.HashMap _root_.Ix.Compile.ExactKey.ExactName _).insert n x - nameToNamed := ({} : Std.HashMap _ _).insert n owner } } - let acc ← (merge compiled n { loneResult x with blockMeta := tag "later" } default).mapError toString - if acc.cenv.nameToNamed.get? n != some owner then - throw "an identical primary re-claim replaced the owner's Named" - -- one block registering `n` twice keeps its own last entry - let twice : BlockState := - { (default : BlockState) with - mutsNamed := #[(n, { addr := x, constMeta := tag "first" }), - (n, { addr := x, constMeta := tag "second" })] } - let acc ← (merge emptyAcc lo (loneResult (addr "t")) twice).mapError toString - if (acc.cenv.nameToNamed.get? n).map (·.constMeta) != some (tag "second") then - throw "a block's own second registration was dropped" - -/-- The one readiness rule: a source-owned name is ready only when its own - block has settled, even when the state already resolves it; a name no - block owns is ready once it resolves. -/ -def readinessCases : List (String × Bool) := - let a := ixName "Fx.Ready.a" - let other := ixName "Fx.Ready.other" - let canonical := addr "ready-canonical" - let all : Ix.Set Ix.Name := ({} : Ix.Set Ix.Name).insert a - let cenv : CompileEnv := { (default : CompileEnv) with - nameToAddr := ({} : Std.HashMap _root_.Ix.Compile.ExactKey.ExactName _).insert a canonical - |>.insert other canonical } - let sourceOwned := fun n => all.contains n - [("source dependency waits despite resolution", - !auxDependencyReady sourceOwned cenv {} a && - auxDependencyReady sourceOwned cenv all a && - auxDependencyReady sourceOwned cenv {} other && - !auxDependencyReady sourceOwned cenv {} (ixName "Fx.Ready.absent"))] - -end DriverApi - -/-! ## Part 2: both compilers on a constructed closure -/ - -def tName : Name := ``Fixture.T -def belowName : Name := tName ++ `below - -/-- The closure of `T`, `T.rec` and `Dep`, with `FakeBelow` renamed to - `T.below` and `Dep`'s body pointing at it. Lean's own auxiliaries of `T` - are not in it: nothing in the closure references them. -/ -def conflictClosure (env : Environment) : Except String (List (Name × ConstantInfo)) := do - let some (.defnInfo fv) := env.find? ``Fixture.FakeBelow | throw "FakeBelow missing" - let some (.defnInfo dv) := env.find? ``Fixture.Dep | throw "Dep missing" - if (dv.value.getUsedConstants.contains ``Fixture.FakeBelow) == false then - throw "Dep does not mention FakeBelow" - let base := Ix.EnvScope.collectDeps env [``Fixture.Dep, tName, tName ++ `rec] - let userBelow : ConstantInfo := .defnInfo { fv with name := belowName, all := [belowName] } - let dep' : ConstantInfo := .defnInfo { dv with value := .const belowName [] } - let rest := base.filterMap fun (n, ci) => - if n == ``Fixture.FakeBelow then none - else if n == ``Fixture.Dep then some (n, dep') - else some (n, ci) - return (belowName, userBelow) :: rest - -/-- The streamed Lean pipeline on a closure (`compileInputFromEnv`, then - `compileLeanInput`), before CLI finalization. -/ -def leanCompile (env : Environment) (closure : List (Name × ConstantInfo)) (workers : Nat) : - IO Ix.CompileM.LeanPipelineOut := do - let input ← IO.ofExcept ((Ix.Compile.compileInputFromEnv env closure).mapError toString) - match ← Ix.CompileM.compileLeanInput input (numWorkers := workers) with - | .ok o => pure o - | .error e => throw (IO.userError s!"Lean compile failed: {e}") - -/-- A block fails after interning a literal and metadata. Partial output must -equal compilation of the successful subset, while complete output must leave -the destination untouched. This exercises the production file-writing FFI. -/ -private def partialOutputCheck (env : Environment) : IO Unit := do - let owner := `PartialPublication.owner - let bad := `PartialPublication.badProjection - let dependent := `PartialPublication.dependent - let typ := Expr.sort (.succ .zero) - let ownerInfo : ConstantInfo := .axiomInfo - { name := owner, levelParams := [], type := typ, isUnsafe := false } - let mkDef (name : Name) (value : Expr) : ConstantInfo := .defnInfo - { name, levelParams := [], type := typ, value, hints := .opaque, - safety := .safe, all := [name] } - let success := [(owner, ownerInfo)] - let input := success ++ [ - (bad, mkDef bad (.proj owner (2^64) (.lit (.strVal "failed-block-literal")))), - (dependent, mkDef dependent (.const bad []))] - let success ← IO.ofExcept (Ix.Compile.prepareRegisteredConstants env success) - let prepared ← IO.ofExcept (Ix.Compile.prepareRegisteredConstants env input) - let dir ← IO.FS.createTempDir - try - for anonymous in [false, true] do - let compile := if anonymous then Ix.CompileM.rsCompileEnvBytesAnonFFI - else Ix.CompileM.rsCompileEnvBytesFFI - let expectedPath := dir / "expected.ixe" - let partialPath := dir / "partial.ixe" - let closedPath := dir / "complete.ixe" - let expected ← compile success expectedPath.toString false - let partialStatus ← compile prepared partialPath.toString true - let expectedBytes ← IO.FS.readBinFile expectedPath - let partialBytes ← IO.FS.readBinFile partialPath - unless expected.ungrounded.isEmpty && partialStatus.ungrounded.size == 2 && - partialStatus.ungrounded.any (·.1 == bad.toString) && - partialStatus.ungrounded.any (·.1 == dependent.toString) do - throw (IO.userError s!"partial output has wrong refusals: {partialStatus.ungrounded}") - unless partialBytes == expectedBytes && partialStatus.root == expected.root && - partialStatus.named == expected.named && partialStatus.uniqueAnon == expected.uniqueAnon && - partialStatus.bytes == expected.bytes && partialStatus.bytes > 0 do - throw (IO.userError s!"failed block changed partial output (anonymous={anonymous})") - let closed ← compile prepared closedPath.toString false - unless closed.bytes == 0 && !(← closedPath.pathExists) && - closed.ungrounded == partialStatus.ungrounded && closed.root == expected.root do - throw (IO.userError "complete output wrote a file or changed failure accounting") - IO.FS.writeBinFile closedPath expectedBytes - let closedAgain ← compile prepared closedPath.toString false - unless closedAgain.bytes == 0 && (← IO.FS.readBinFile closedPath) == expectedBytes do - throw (IO.userError "complete output replaced an existing destination") - IO.FS.removeFile closedPath - unless anonymous do - let lean ← leanCompile env input 2 - let leanBytes ← IO.ofExcept (Ixon.serEnv lean.env) - unless lean.cenv.ungrounded.size == 2 && leanBytes == partialBytes do - throw (IO.userError "Rust and Lean partial output differ") - IO.println "[claim-conflict] partial output: named/anonymous bytes, refusals, and fail-closed files PASS" - finally IO.FS.removeDirAll dir - -def run : IO UInt32 := do - let mut failures := 0 - match ownerNamedCheck with - | .ok () => IO.println "[claim-conflict] owner Named: an identical re-claim keeps the \ -owner's entry; a block's own re-registration wins" - | .error e => - failures := failures + 1 - IO.println s!"[claim-conflict] owner Named: FAIL: {e}" - -- Part 1 - for (label, expected, actual) in driverCases do - if expected == actual then - IO.println s!"[claim-conflict] driver: {label}: {actual.getD "accepted"}" - else - failures := failures + 1 - IO.println s!"[claim-conflict] driver: {label}: FAIL: expected {expected.getD "accepted"}, \ -got {actual.getD "accepted"}" - for (label, passed) in readinessCases do - if passed then - IO.println s!"[claim-conflict] readiness: {label}: PASS" - else - failures := failures + 1 - IO.println s!"[claim-conflict] readiness: {label}: FAIL" - -- Part 2 - let env ← get_env! - partialOutputCheck env - let closure ← IO.ofExcept (conflictClosure env) - IO.println s!"[claim-conflict] constructed closure: {closure.length} constants" - -- Rust (the `ix compile` FFI), partial output allowed so the status - -- lists every refused constant - let dir ← IO.FS.createTempDir - let prepared ← IO.ofExcept (Ix.Compile.prepareRegisteredConstants env closure) - let status ← Ix.CompileM.rsCompileEnvBytesFFI prepared (dir / "rs.ixe").toString true - IO.FS.removeDirAll dir - for (n, m) in status.ungrounded.toList do - failures := failures + 1 - IO.println s!"[claim-conflict] FAIL: rust refuses {n}: {m}" - -- Lean: every schedule - let mut runs : Array (String × List (String × String)) := #[] - let phases ← Ix.CompileM.rsCompilePhasesOf closure - match Ix.CompileM.compileEnvFold phases.rawEnv phases.condensed with - | .error e => throw (IO.userError s!"[claim-conflict] fold: {e}") - | .ok (_, _, cenv) => - runs := runs.push ("fold", cenv.ungrounded.toList.map fun (n, m) => (n.pretty, m)) - -- the test-only reference schedule, with the least and with the greatest - -- ready key first - for least in [true, false] do - match ScheduleReference.sequential phases.rawEnv phases.condensed (least := least) with - | .error e => throw (IO.userError s!"[claim-conflict] reference schedule: {e}") - | .ok (_, _, cenv) => - runs := runs.push (s!"reference, {if least then "least" else "greatest"} key first", - cenv.ungrounded.toList.map fun (n, m) => (n.pretty, m)) - for k in [1, 4, 16] do - let out ← leanCompile env closure k - runs := runs.push (s!"compile-lean --workers {k}", - out.cenv.ungrounded.toList.map fun (n, m) => (n.pretty, m)) - for (label, refusals) in runs do - if refusals.isEmpty then - IO.println s!"[claim-conflict] {label}: the whole closure compiles, as in Rust" - else - failures := failures + 1 - IO.println s!"[claim-conflict] {label}: FAIL: {refusals.length} refusal(s)" - for (n, m) in refusals do - IO.println s!"[claim-conflict] lean {n}: {m}" - IO.println s!"[claim-conflict] {if failures == 0 then "PASS" else s!"FAIL ({failures})"}" - return if failures == 0 then 0 else 1 - -end Tests.Ix.Compile.ClaimConflict diff --git a/Tests/Ix/Compile/ClosureHarness.lean b/Tests/Ix/Compile/ClosureHarness.lean index 02b2d0a07..b037c7195 100644 --- a/Tests/Ix/Compile/ClosureHarness.lean +++ b/Tests/Ix/Compile/ClosureHarness.lean @@ -1,14 +1,23 @@ -/- +import Ix.Meta +import Ix.EnvScope +import Ix.CompileM +import Ix.CompileDriver +import Ix.CompileCertified +import Ix.DecompileDriver +import Ix.Ixon +import Tests.Ix.Compile.KernelReport + +/-! The closure suites' shared harness (`compiler-selected-closure`, `compiler-selected-closure-e2e`, `checker-support-regression`, `compile-closure-whole`, `pack-units`). - - `compileClosure`: the unvalidated streamed Lean entry - (`compileInputFromEnv`, then `compileLeanInput`) for fixtures without + - `compileClosure`: the unvalidated Lean entry + (`compileInputFromEnv`, then `compileEnvFold`) for fixtures without source contracts, with the configured sharing limits. The harness compares raw artifacts before CLI role finalization and resource checks. - - `rustClosure`/`rustCheck`: the raw Rust compile - (`rsCompileEnvBytesFFI`, used by `ix compile --rust` before finalization) + - `rustClosure`/`rustCheck`: the certified compile + (`compileCertifiedPrepared`, before finalization) of the same prepared constants, and its byte comparison with the Lean artifact. - `decompileCheck`: decompile fidelity of an output against its source @@ -21,13 +30,6 @@ Callers provide output paths in temporary directories made with `IO.FS.createTempDir`. -/ -import Ix.Meta -import Ix.EnvScope -import Ix.CompileM -import Ix.CompileDriver -import Ix.DecompileDriver -import Ix.Ixon -import Tests.Ix.Compile.KernelReport open Lean @@ -58,37 +60,52 @@ def ownConstants (env : Environment) : List Name := /-! ## Compiles -/ -/-- Read `IX_SHARING_LIMITS` for the Lean fixture legs with the shared parser. -The Rust FFI reads the same setting for its comparison leg. -/ -def compilerLimits : IO Ix.Sharing.Exact.Limits := do - IO.ofExcept (← Ix.CompileM.compilerSharingLimitsFromEnv) +/-- Output of `compileClosure` before CLI finalization. -/ +structure ClosureOut where + bytes : ByteArray + env : Ixon.Env + cenv : Ix.CompileM.CompileEnv -/-- Compile `closure` through the unvalidated streamed Lean entry -(`compileInputFromEnv`, then `compileLeanInput`), before CLI finalization. +/-- The compiler's default sharing limits (`Ix.CompileM.compilerSharingLimits`). -/ +def compilerLimits : IO Ix.Sharing.Exact.Limits := + pure Ix.CompileM.compilerSharingLimits + +/-- Compile `closure` through the unvalidated Lean entry +(`compileInputFromEnv`, then `compileEnvFold`), before CLI finalization. Recorded failures are in `out.cenv.ungrounded`; the caller checks them. -/ def compileClosure (env : Environment) (closure : List (Name × ConstantInfo)) - (workers : Nat := 32) : IO Ix.CompileM.LeanPipelineOut := do + (workers : Nat := 32) : IO ClosureOut := do let input ← IO.ofExcept ((Ix.Compile.compileInputFromEnv env closure).mapError toString) - let opts : Ix.CompileM.DriverOptions := { sharingLimits := (← compilerLimits) } - match ← Ix.CompileM.compileLeanInput input (numWorkers := workers) (opts := opts) with - | .ok o => pure o - | .error e => throw (IO.userError s!"Lean compile failed: {e}") - -/-- The raw Rust compile of the same prepared constants as `compileClosure`, + let prepared ← IO.ofExcept input.prepare + let rawEnv := Ix.CompileM.canonInput prepared.toArray workers + let split := Ix.Cert.groundSplit rawEnv + let blocks := Ix.CondenseM.run split.1 + let limits ← compilerLimits + let (ixon, _, cenv) ← IO.ofExcept + (Ix.CompileM.compileEnvFold rawEnv blocks (sharingLimits := limits) (width := workers)) + let ungrounded := split.2.fold (init := cenv.ungrounded) fun m n e => + m.insert n (e.refusal split.2.contains) + let bytes ← IO.ofExcept (Ixon.serEnv ixon) + return { bytes, env := ixon, cenv := { cenv with ungrounded } } + +/-- The certified compile of the same prepared constants as `compileClosure`, written to `path` before CLI finalization. Partial output is allowed, so the status lists every refused constant. Returns the written bytes and status. -/ def rustClosure (env : Environment) (closure : List (Name × ConstantInfo)) (path : System.FilePath) : IO (ByteArray × Ix.CompileM.CompileEnvStatus) := do let input ← IO.ofExcept ((Ix.Compile.compileInputFromEnv env closure).mapError toString) let constants ← IO.ofExcept input.prepare - let status ← Ix.CompileM.rsCompileEnvBytesFFI constants path.toString true - return (← IO.FS.readBinFile path, status) + let out ← IO.ofExcept (← Ix.CompileM.compileCertifiedPrepared constants false) + let bytes ← IO.ofExcept (Ixon.serEnv out.env) + IO.FS.writeBinFile path bytes + let status := { Ix.Cli.statusOf out.env out.failures.toMap with bytes := bytes.size.toUInt64 } + return (bytes, status) /-- The Rust leg: the Rust compile of `closure` (written to `path`) must be the Lean compile `lean`, byte for byte, with no refusal. `none` when it is; otherwise the difference, counted by Named entry. -/ def rustCheck (label : String) (env : Environment) (closure : List (Name × ConstantInfo)) - (lean : Ix.CompileM.LeanPipelineOut) (path : System.FilePath) : IO (Option String) := do + (lean : ClosureOut) (path : System.FilePath) : IO (Option String) := do let (bytes, status) ← rustClosure env closure path if bytes == lean.bytes && status.ungrounded.isEmpty then return none let rust ← IO.ofExcept (Ixon.rsDeEnv bytes) @@ -118,13 +135,12 @@ def namedDiff (a b : Ixon.Named) : Option String := /-! ## Decompile -/ /-- Decompile fidelity: the output, decompiled by the full Lean decompiler -(`decompileEnvFullParallel`, as `ix validate-lean --full-oracle` phase 5 and -the `decompile-diff` suite run it), equals the canonicalized source constants +(`decompileEnvFullParallel`), equals the canonicalized source constants of `closure` (`ConstantInfo` BEq), in both directions. A constant whose block failed to compile (`out.cenv.ungrounded`) is not expected. Returns the problems and a summary line. -/ def decompileCheck (env : Environment) (closure : List (Name × ConstantInfo)) - (out : Ix.CompileM.LeanPipelineOut) : IO (Array String × String) := do + (out : ClosureOut) : IO (Array String × String) := do let input ← IO.ofExcept ((Ix.Compile.compileInputFromEnv env closure).mapError toString) let prepared ← IO.ofExcept input.prepare let src : Std.HashMap _root_.Ix.Name _root_.Ix.ConstantInfo := diff --git a/Tests/Ix/Compile/ClosurePhase4.lean b/Tests/Ix/Compile/ClosurePhase4.lean deleted file mode 100644 index 4859d34a9..000000000 --- a/Tests/Ix/Compile/ClosurePhase4.lean +++ /dev/null @@ -1,59 +0,0 @@ -import LSpec -import Tests.Ix.Compile.ClosureWhole - -namespace Tests.Ix.Compile.ClosurePhase4 - -open LSpec Tests.Ix.Compile.ClosureWhole - -private def ensure (ok : Bool) (message : String) : IO Unit := - unless ok do throw (IO.userError message) - -private def check : IO Nat := do - let dir ← IO.FS.createTempDir - IO.println s!"LocalCollapse phase-4 gate artifacts: {dir}" - -- Only this bounded fixture, never ClosureWhole.run's whole-artifact work. - -- This executes the existing byte/source/reference/no-support checks too. - let errors ← localCollapse dir (workers := 1) - ensure errors.isEmpty ("\n".intercalate errors.toList) - let json ← IO.ofExcept (Lean.Json.parse - (← IO.FS.readFile (dir / "local-collapse-validate-lean.json"))) - ensure ((← IO.ofExcept (json.getObjValAs? Bool "passed")) && - (← IO.ofExcept (json.getObjValAs? Nat "totalFailures")) == 0) "actual CLI report did not pass" - let rows ← IO.ofExcept (json.getObjValAs? (Array Lean.Json) "phases") - let phases ← rows.mapM fun row => IO.ofExcept do - return (← row.getObjValAs? String "name", ← row.getObjValAs? String "result", - ← row.getObjValAs? String "detail") - ensure (localPhaseProblems 0 phases).isEmpty "actual all-phase report was refused" - let some (_, _, source) := phases.find? (·.1.startsWith "4.") - | throw (IO.userError "actual CLI omitted phase 4") - ensure (source.contains "source coverage: 269/269 named constants" && - source.contains "declined: partial definition") "actual source coverage/declines changed" - let edit := fun name result detail => phases.map fun row => - if row.1.startsWith "4." then (name, result, detail) else row - let variants := [ - ("missing", phases.filter (!·.1.startsWith "4."), "phase 4."), - ("failed", edit "4. Source/reader fidelity" "fail" source, "fidelity: fail"), - ("skipped", edit "4. Source/reader fidelity" "skip" source, "fidelity: skip"), - ("old scope", edit "4. Kernel meta roundtrip" "pass" source, "wrong phase-4 scope"), - ("no coverage", edit "4. Source/reader fidelity" "pass" "no coverage", "nonzero source coverage"), - ("zero", edit "4. Source/reader fidelity" "pass" "source coverage: 0/0 named constants", "nonzero source coverage"), - ("partial", edit "4. Source/reader fidelity" "pass" "source coverage: 268/269 named constants", "nonzero source coverage"), - ("malformed", edit "4. Source/reader fidelity" "pass" "source coverage: bad/269 named constants", "nonzero source coverage"), - ("duplicate coverage", edit "4. Source/reader fidelity" "pass" - (source ++ "\nsource coverage: 269/269 named constants"), "nonzero source coverage"), - ("duplicate row", phases.push ("4. Source/reader fidelity", "pass", source), "phase 4."), - ("phase 5", phases.map (fun (n, r, d) => if n.startsWith "5." then (n, "fail", d) else (n, r, d)), "5. Decompile: fail")] - for (label, changed, message) in variants do - let rejected := localPhaseProblems 0 changed - ensure (rejected.any (·.contains message)) s!"{label} was accepted or failed for another reason: {rejected}" - ensure ((localPhaseProblems 1 phases).any (·.contains "exit 1")) "nonzero CLI exit was ignored" - ensure (variants.length == 11) "not every report control executed" - IO.println "LocalCollapse phase-4 gate: all 5 actual phases pass, 269/269 source coverage; 12 report refusals" - return variants.length + 1 - -def suite : List TestSeq := [ - .individualIO "LocalCollapse requires phase 4 and complete nonzero source coverage" none (do - let n ← check - return (true, n, 0, none)) .done] - -end Tests.Ix.Compile.ClosurePhase4 diff --git a/Tests/Ix/Compile/ClosureWhole.lean b/Tests/Ix/Compile/ClosureWhole.lean index a1da6dc2e..d1f5ad5c5 100644 --- a/Tests/Ix/Compile/ClosureWhole.lean +++ b/Tests/Ix/Compile/ClosureWhole.lean @@ -1,4 +1,8 @@ -/- +import Ix.Meta +import Ix.EnvScope +import Tests.Ix.Compile.ClosureHarness + +/-! compile-closure-whole: a closure compile agrees with the whole compile, and a closure carries whole logical units. The harness compares each selected closure's compiled names and metadata with the whole artifact. @@ -47,20 +51,17 @@ (the set observed is printed); 7. **local scope** of a mutual-inductive fixture (`Tests/Ix/Compile/Fixtures/LocalCollapse.lean`): its selected closure - (the `--local` scope) compiles with no refusal, the Rust compile is + (the `--local` scope) compiles with no refusal, the certified compile is byte-identical, it decompiles to the source, its output introduces no reference, and `ix validate-lean --ns LocalCollapse` passes all five phases, including source/reader fidelity; the same closure with recursors only (without that support) also compiles with no refusal; - 8. **the Rust compiler**: the Rust compile of every closure - (`rsCompileEnvBytesFFI`) is the Lean compile's artifact, byte for + 8. **the certified compiler**: the certified compile of every closure + is the streamed Lean compile's artifact, byte for byte. Run with: `lake test -- --ignored compile-closure-whole`. -/ -import Ix.Meta -import Ix.EnvScope -import Tests.Ix.Compile.ClosureHarness open Lean open Tests.Ix.Compile.ClosureHarness @@ -186,7 +187,7 @@ def localPhaseProblems (exitCode : UInt32) (phases : Array (String × String × /-- The local scope of a mutual-inductive fixture: the selected closure of the file's own constants (the scope of `ix compile --local`) compiles with -no refusal through the harness, the Rust compile is byte-identical, it +no refusal through the harness, the certified compile is byte-identical, it decompiles to the source, and its output introduces no reference. The closure with recursors only also compiles without refusal or added support. @@ -286,15 +287,13 @@ def run : IO UInt32 := do say s!"introduced references carried by every closure: {support}" say s!"closures: {closures.foldl (· + ·.2.1.length) 0} constants over {roots.length} roots; \ {unitChecks} unit memberships checked" - -- 3, 4, 5, 8: each closure compiled, against the whole compile + -- 3, 4, 5: each closure compiled, against the whole compile let ref ← loadOrCompile env whole - let dir ← IO.FS.createTempDir let mut compared := 0 let mut addrDiffs := 0 let mut metaDiffs := 0 let mut classes : Std.HashMap String Nat := {} let mut missing := 0 - let mut rustSame := 0 let mut introduced : Std.HashSet Name := {} for (r, c, base) in closures do let out ← compileClosure env c @@ -302,11 +301,7 @@ def run : IO UInt32 := do unless out.cenv.ungrounded.isEmpty do errors := errors.push s!"{r}: {out.cenv.ungrounded.size} refusals, first \ {out.cenv.ungrounded.toList.head?.map (·.1.pretty)}" - -- 8. the Rust compiler on the same closure - match ← rustCheck s!"{r}" env c out (dir / "rust.ixe") with - | none => rustSame := rustSame + 1 - | some e => errors := errors.push e - let outEnv ← IO.ofExcept (Ixon.rsDeEnv out.bytes) + let outEnv := out.env -- 5. every closure constant is in the output for (n, _) in c do unless outEnv.named.contains (ixN n) do @@ -345,14 +340,9 @@ def run : IO UInt32 := do say s!"{roots.length} roots, {compared} carried names compared with the whole compile: \ {addrDiffs} address differences, {metaDiffs} metadata differences (classes: {classLine}), \ {missing} closure constants missing from the output" - say s!"Rust compiler on the same closures: {rustSame}/{closures.size} byte-identical with the \ - Lean compile" say s!"references introduced by the outputs (derived): {sortedNames introduced}" - -- 7. the local scope of the mutual-inductive fixture - errors := errors ++ (← localCollapse dir) for e in errors do say s!"FAIL {e}" say s!"{if errors.isEmpty then "PASS" else s!"FAIL ({errors.size})"}" - IO.FS.removeDirAll dir return if errors.isEmpty then 0 else 1 end Tests.Ix.Compile.ClosureWhole diff --git a/Tests/Ix/Compile/DecompileDiff.lean b/Tests/Ix/Compile/DecompileDiff.lean deleted file mode 100644 index 3ddb55300..000000000 --- a/Tests/Ix/Compile/DecompileDiff.lean +++ /dev/null @@ -1,92 +0,0 @@ -/- - Cross-compiler decompilation inventory for the decompiler. - - Rust-compiles the shared fixture corpus (`validateAuxClosure`), decompiles - the resulting Ixon env with the pure-Lean `Ix.DecompileM`, and compares - every reconstructed constant against the SOURCE `Ix.Environment` by full - structural equality (`ConstantInfo` BEq — hash-based at the - Name/Level/Expr leaves, so this is Rust `check_decompile`'s `get_hash` - comparison at full fidelity). - - The gate is total: no mismatch, no decompile error, no decompiled constant - outside the source and no source constant missing from the decompile. - - Invoked via `lake test -- --ignored decompile-diff`. --/ -import Ix.Common -import Ix.CompileM -import Ix.DecompileM -import Ix.DecompileDriver -import Lean -import LSpec -import Tests.Ix.Compile.ValidateAux - -namespace Tests.Compile.DecompileDiff - -private def sample (xs : Array Ix.Name) (n : Nat := 12) : String := - ", ".intercalate ((xs.toList.take n).map (·.pretty)) - -def run (env : Lean.Environment) : IO UInt32 := do - IO.println "[decompile-diff] collecting fixture closure..." - let filtered := validateAuxClosure env - IO.println s!"[decompile-diff] {filtered.length} constants" - - IO.println "[decompile-diff] Rust compile (rsCompilePhases)..." - let raw ← Ix.CompileM.rsCompilePhasesFFI filtered - let rawEnv := raw.rawEnv.toEnvironment - let rustEnv := raw.compileEnv.toEnv - IO.println s!"[decompile-diff] Rust: {rustEnv.named.size} named, {rustEnv.consts.size} consts" - - IO.println "[decompile-diff] Lean decompile (full driver)..." - let t0 ← IO.monoMsNow - let origView : Std.HashMap Ix.Name Ix.ConstantInfo := rawEnv.consts - -- The limits the Rust compile above read (`IX_SHARING_LIMITS`). - let sharingLimits ← IO.ofExcept (← Ix.CompileM.compilerSharingLimitsFromEnv) - let (decompiled, errors, _) ← - Ix.DecompileM.decompileEnvFullParallel rustEnv (some origView) - (sharingLimits := sharingLimits) - IO.println s!"[decompile-diff] {decompiled.size} constants, {errors.size} errors in {(← IO.monoMsNow) - t0}ms" - - -- Forward comparison: every decompiled constant vs the source env. - let mut matched := (0 : Nat) - let mut mismatched : Array Ix.Name := #[] - let mut notInSource : Array Ix.Name := #[] - for (name, info) in decompiled do - match rawEnv.consts.get? name with - | none => notInSource := notInSource.push name - | some orig => - if info == orig then matched := matched + 1 - else mismatched := mismatched.push name - - -- Reverse coverage: source constants never reconstructed. Errored names - -- are already counted above; exclude them here so the buckets partition - -- cleanly. - let mut errNames : Ix.Set Ix.Name := {} - for (n, _) in errors do - errNames := errNames.insert n - let mut missing : Array Ix.Name := #[] - for (name, _) in rawEnv.consts do - if !decompiled.contains name && !errNames.contains name then - missing := missing.push name - - IO.println "" - IO.println s!"[decompile-diff] decompiled {decompiled.size} constants, {errors.size} errors" - IO.println s!"[decompile-diff] {matched} matched, {mismatched.size} mismatched (must be 0)" - if !mismatched.isEmpty then - IO.println s!"[decompile-diff] mismatch e.g. {sample mismatched}" - IO.println s!"[decompile-diff] errors: {errors.size} (must be 0)" - for (n, e) in errors.toList.take 8 do - IO.println s!"[decompile-diff] {n.pretty}: {e.take 300}" - IO.println s!"[decompile-diff] decompiled-not-in-source: {notInSource.size} (must be 0)" - if !notInSource.isEmpty then - IO.println s!"[decompile-diff] e.g. {sample notInSource}" - IO.println s!"[decompile-diff] missing from decompile: {missing.size} (must be 0)" - if !missing.isEmpty then - IO.println s!"[decompile-diff] e.g. {sample missing}" - - let gateOk := mismatched.isEmpty && errors.isEmpty && notInSource.isEmpty && missing.isEmpty - IO.println "" - IO.println s!"[decompile-diff] fidelity gate: {if gateOk then "PASS" else "FAIL"}" - return if gateOk then 0 else 1 - -end Tests.Compile.DecompileDiff diff --git a/Tests/Ix/Compile/DriverInput.lean b/Tests/Ix/Compile/DriverInput.lean deleted file mode 100644 index 01b6428af..000000000 --- a/Tests/Ix/Compile/DriverInput.lean +++ /dev/null @@ -1,65 +0,0 @@ -import LSpec -import Ix.Meta -import Ix.EnvScope -import Ix.CompileDriver -import Tests.Ix.Compile.ScheduleReference - -/-! The compile drivers' input checks and keys: a condensation with a block -that has no reference set is refused by both drivers -(`Ix.CompileM.missingBlockRefs?`) instead of being scheduled as if it had no -dependencies; a block is named by its least member and an address by its -least alias (`canonicalKey`, `insertCanonicalAlias`), whatever the order. -/ - -namespace Tests.Ix.Compile.DriverInput - -open LSpec -open _root_.Ix (Name) - -private def missing : Name := Name.fromLeanName `DriverInput.missing -private def present : Name := Name.fromLeanName `DriverInput.present - -/-- Two singleton blocks; only `present` has a reference set. -/ -private def blocks : _root_.Ix.CondensedBlocks := - { lowLinks := ({} : _root_.Ix.Map Name Name).insert missing missing |>.insert present present - blocks := ({} : _root_.Ix.Map Name (_root_.Ix.Set Name)) - |>.insert missing (({} : _root_.Ix.Set Name).insert missing) - |>.insert present (({} : _root_.Ix.Set Name).insert present) - blockRefs := ({} : _root_.Ix.Map Name (_root_.Ix.Set Name)).insert present {} } - -private def expected : String := "block DriverInput.missing has no condensation reference set" - -/-- Three aliases at one address, in every insertion order. -/ -private def aliases : List Name := - [`DriverInput.a, `DriverInput.b, `DriverInput.c].map Name.fromLeanName - -private def permutations : List (List Name) := match aliases with - | [a, b, c] => [[a, b, c], [a, c, b], [b, a, c], [b, c, a], [c, a, b], [c, b, a]] - | _ => [] - -private def least : Name := aliases.foldl (init := aliases.head!) fun k n => - if _root_.Ix.nameCompare n k == .lt then n else k - -def suite : List TestSeq := [ - test "the reverse index keeps the least alias whatever the insertion order" - (permutations.all fun order => - (order.foldl (init := ({} : Std.HashMap Address Name)) fun m n => - _root_.Ix.CompileM.insertCanonicalAlias m default n).get? default == some least), - test "a block is keyed by its least member whatever the iteration order" - (permutations.all fun order => - _root_.Ix.CompileM.canonicalKey Name.mkAnon order.toArray == least), - test "missingBlockRefs? names the block without references" - (_root_.Ix.CompileM.missingBlockRefs? blocks == some expected), - test "missingBlockRefs? accepts a complete condensation" - (_root_.Ix.CompileM.missingBlockRefs? - { blocks with blockRefs := blocks.blockRefs.insert missing {} } == none), - test "the driver refuses a block without references" - (show Bool from match _root_.Ix.CompileM.compileEnvFold { consts := {} } blocks with - | .error why => why == expected - | .ok _ => false), - test "the driver refuses a block without references at every width" - ([1, 2, 256].all fun width => - match _root_.Ix.CompileM.compileEnvFold { consts := {} } blocks (width := width) with - | .error why => why == expected - | .ok _ => false)] - -end Tests.Ix.Compile.DriverInput diff --git a/Tests/Ix/Compile/ExactKey.lean b/Tests/Ix/Compile/ExactKey.lean deleted file mode 100644 index a62645124..000000000 --- a/Tests/Ix/Compile/ExactKey.lean +++ /dev/null @@ -1,94 +0,0 @@ -import LSpec -import Ix.Compile.ExactKey -import Ix.CompileM - -namespace Tests.Ix.Compile.ExactKey - -open LSpec -open _root_.Ix (Expr Level Name Syntax) - -def controls : List (String × Bool) := Id.run do - let digest : Address := ⟨⟨Array.replicate 32 7⟩⟩ - let otherDigest : Address := ⟨⟨Array.replicate 32 9⟩⟩ - let anon : Name := .anonymous digest - let xName : Name := .str anon "x" digest - let yName : Name := .str anon "y" digest - let zero : Expr := .bvar 0 digest - let one : Expr := .bvar 1 digest - let equal := _root_.Ix.Compile.ExactKey.exprEq - let mut left := zero - let mut right := zero - let mut different := one - for _ in [:64] do - left := .lam anon left left .default digest - right := .lam anon right right .default digest - different := .lam anon different different .default digest - let mut levelLeft : Level := .zero digest - let mut levelRight : Level := .zero digest - for _ in [:64] do - levelLeft := .max levelLeft levelLeft digest - levelRight := .max levelRight levelRight digest - let mut syntaxLeft : Syntax := .missing - let mut syntaxRight : Syntax := .missing - for _ in [:64] do - syntaxLeft := .node .none anon #[syntaxLeft, syntaxLeft] - syntaxRight := .node .none anon #[syntaxRight, syntaxRight] - return [ - ("equal leaf", equal zero zero), - ("equal-hash different indices", !equal zero one), - ("same payload with different stored hashes", !equal zero (.bvar 0 otherDigest)), - ("equal-hash different names", !equal (.fvar xName digest) (.fvar yName digest)), - ("equal-hash different universe names", !equal - (.sort (.param xName digest) digest) (.sort (.param yName digest) digest)), - ("binder information", !equal - (.lam anon zero zero .default digest) (.lam anon zero zero .implicit digest)), - ("let dependency flag", !equal - (.letE anon zero zero zero false digest) (.letE anon zero zero zero true digest)), - ("semantic metadata payload", !equal - (.mdata #[(xName, .ofNat 0)] zero digest) (.mdata #[(xName, .ofNat 1)] zero digest)), - ("64-level distinct equal expression DAGs", equal left right), - ("64-level unequal expression DAGs", !equal left different), - ("64-level distinct equal universe DAGs", equal (.sort levelLeft digest) (.sort levelRight digest)), - ("64-level distinct equal metadata DAGs", equal - (.mdata #[(xName, .ofSyntax syntaxLeft)] zero digest) - (.mdata #[(xName, .ofSyntax syntaxRight)] zero digest))] - -/-- Two names with one stored hash, as only a digest collision makes them: -the compiler's name tables keep them apart, where a table keyed by -`Ix.Name`'s `==` (stored hashes only) makes them one entry. -/ -def nameTableControls : List (String × Bool) := Id.run do - let digest : Address := ⟨⟨Array.replicate 32 7⟩⟩ - let anon : Name := .anonymous digest - let xName : Name := .str anon "x" digest - let yName : Name := .str anon "y" digest - let zName : Name := .str anon "z" digest - let xAddr : Address := ⟨⟨Array.replicate 32 1⟩⟩ - let yAddr : Address := ⟨⟨Array.replicate 32 2⟩⟩ - let hashKeyed : Std.HashMap Name Address := ({} : Std.HashMap Name Address).insert xName xAddr |>.insert yName yAddr - let table : Std.HashMap _root_.Ix.Compile.ExactKey.ExactName Address := - ({} : Std.HashMap _root_.Ix.Compile.ExactKey.ExactName Address).insert xName xAddr |>.insert yName yAddr - let ctx : _root_.Ix.MutCtx := (Std.TreeMap.empty : _root_.Ix.MutCtx).insert xName 0 |>.insert yName 1 - let cenv : _root_.Ix.CompileM.CompileEnv := { (default : _root_.Ix.CompileM.CompileEnv) with nameToAddr := table } - let block : _root_.Ix.CompileM.BlockEnv := { all := {}, current := anon, mutCtx := ctx, univCtx := [] } - let lookup (n : Name) : Option Address := - match _root_.Ix.CompileM.CompileM.run cenv block {} (_root_.Ix.CompileM.lookupConstAddr n) with - | .ok (address, _) => some address - | .error _ => none - return [ - ("control: a hash-keyed table holds the two names as one entry", - hashKeyed.size == 1 && hashKeyed.get? xName == some yAddr), - ("a name table holds both", table.size == 2 - && table.get? xName == some xAddr && table.get? yName == some yAddr), - ("a name absent from a name table is absent, whatever its hash", - table.get? zName == none && !table.contains zName), - ("an equal name built separately is found", table.get? (Name.str anon "x" digest) == some xAddr), - ("lookupConstAddr resolves each name to its own address", - lookup xName == some xAddr && lookup yName == some yAddr && lookup zName == none), - ("the mutual context holds both", ctx.size == 2 - && ctx.get? xName == some 0 && ctx.get? yName == some 1 && ctx.get? zName == none)] - -def suite : List TestSeq := - controls.map (fun (label, passed) => test s!"exact expression key: {label}" passed) - ++ nameTableControls.map (fun (label, passed) => test s!"exact name table: {label}" passed) - -end Tests.Ix.Compile.ExactKey diff --git a/Tests/Ix/Compile/ExpressionFrame.lean b/Tests/Ix/Compile/ExpressionFrame.lean deleted file mode 100644 index 69e537e3c..000000000 --- a/Tests/Ix/Compile/ExpressionFrame.lean +++ /dev/null @@ -1,107 +0,0 @@ -import LSpec -import Ix.CompileM - -/-! Controls for the expression compiler's frame (`Ix.CompileM.compileExpr`): -App spines are flattened, partial -spines are allocated but never cached, cache hits reuse roots, failures keep -their completed children's effects, and a DAG-shared term compiles without a -walk of its exponentially larger tree. -/ - -namespace Tests.Ix.Compile.ExpressionFrame - -open LSpec -open _root_.Ix (Name Expr) -open _root_.Ix.CompileM - -private def context : CompileEnv × BlockEnv := - ((default : CompileEnv), - { all := {}, current := Name.mkAnon, mutCtx := default, univCtx := [] }) - -private def raw (action : CompileM α) (state : BlockState := {}) : - Except CompileError α × BlockState := (action context).run state - -def controls : List (String × Bool) := Id.run do - let f := Expr.mkBVar 0 - let a := Expr.mkBVar 1 - let b := Expr.mkBVar 2 - let inner := Expr.mkApp f a - let outer := Expr.mkApp inner b - let (compiled, after) := raw (compileExpr outer) - let (innerResult, afterInner) := raw (compileExpr inner) after - let (cached, afterCached) := raw (compileExpr outer) afterInner - let seeded : BlockState := { - exprCache := ({} : Std.HashMap _root_.Ix.Compile.ExactKey.ExprKey (Ixon.Expr × UInt64)).insert inner (.var 99, 99) } - let (seedResult, seedAfter) := raw (compileExpr outer) seeded - let broken := Expr.mkApp inner (Expr.mkMVar (Name.fromLeanName `missing)) - let (failure, failed) := raw (compileExpr broken) - -- A 64-level shared DAG has exponentially many tree occurrences. A - -- runtime walk of the tree would make this small regression infeasible. - let mut shared := f - for _ in [:64] do - shared := Expr.mkLam Name.mkAnon shared shared .default - let (sharedResult, sharedAfter) := raw (compileExpr shared) - -- Semantic-contract wrappers are cached under their exact source key. - let contract : _root_.Ix.SemanticContract.Contract := { - kind := .lam, binder := ⟨.linear, .shared⟩ } - let contractData := #[ - (Name.fromLeanName `ix.contract, _root_.Ix.DataValue.ofNat 3), - (Name.fromLeanName `ix.contract.kind, _root_.Ix.DataValue.ofNat contract.kindCode), - (Name.fromLeanName `ix.contract.code, _root_.Ix.DataValue.ofNat contract.code)] - let binder := Expr.mkLam Name.mkAnon (Expr.mkSort _root_.Ix.Level.mkZero) f .default - let annotated := Expr.mkMData contractData binder - let (annotation, annotationAfter) := raw (compileExpr annotated) - let (annotationAgain, annotationAgainAfter) := raw (compileExpr annotated) annotationAfter - -- Supplied raw keys deliberately share a digest. They exercise the - -- lookup boundary without claiming a cryptographic collision. - let colliding : Expr := .bvar 1 f.getHash - let collidingBad : Expr := .mvar (Name.fromLeanName `missing) f.getHash - let (_, firstAfter) := raw (compileExpr f) - let (collision, collisionAfter) := raw (compileExpr colliding) firstAfter - let (originalAgain, originalAfter) := raw (compileExpr f) collisionAfter - let (collisionError, collisionErrorAfter) := raw (compileExpr collidingBad) firstAfter - return [ - ("flattened application result and root", - compiled.toOption == some (.app (.app (.var 0) (.var 1)) (.var 2), 4)), - ("head argument application allocation order", - after.arena.nodes == #[.leaf, .leaf, .app 0 1, .leaf, .app 2 3]), - ("inner spine is not cached", !after.exprCache.contains inner), - ("maximal inner occurrence allocates its own application", - innerResult.toOption == some (.app (.var 0) (.var 1), 5)), - ("maximal outer occurrence reuses its root", - cached.isOk && cached.toOption == compiled.toOption && afterCached.arena.nodes.size == 6), - ("an inner cache entry cannot substitute into the spine", - seedResult.isOk && seedResult.toOption == compiled.toOption), - ("the untouched inner entry remains available", - seedAfter.exprCache.get? inner == some (.var 99, 99)), - ("error retains completed child effects", (match failure with - | .error (.unsupportedExpr "metavariable") => true | _ => false) && - failed.arena.nodes == #[.leaf, .leaf, .app 0 1] && failed.exprCache.contains f && - failed.exprCache.contains a), - ("failed whole expression is not cached", !failed.exprCache.contains broken), - ("shared DAG has no runtime tree-size prepass", - sharedResult.isOk && sharedAfter.arena.nodes.size == 65), - ("semantic contract lowers the anonymous binder", match annotation with - | .ok (.lam actual (.sort 0) (.var 0), _) => actual == contract.binder - | _ => false), - ("semantic contract wrapper is cached after success", - annotation.isOk && annotationAfter.exprCache.get? annotated == annotation.toOption), - ("repeated semantic contract reuses its result and arena", - annotationAgain.toOption == annotation.toOption && - annotationAgainAfter.arena.nodes == annotationAfter.arena.nodes), - ("different indices sharing a stored hash compile independently", - collision.toOption == some (.var 1, 1)), - ("colliding keys retain both cache entries", - collisionAfter.exprCache.get? f == some (.var 0, 0) && - collisionAfter.exprCache.get? colliding == some (.var 1, 1) && - collisionAfter.exprCache.size == 2), - ("colliding insertion preserves the original cached root", - originalAgain.toOption == some (.var 0, 0) && originalAfter.arena.nodes.size == 2), - ("a colliding unsupported expression still fails", (match collisionError with - | .error (.unsupportedExpr "metavariable") => true | _ => false) && - collisionErrorAfter.arena.nodes == firstAfter.arena.nodes) - ] - -def suite : List TestSeq := controls.map fun (label, ok) => - test s!"expression frame: {label}" ok - -end Tests.Ix.Compile.ExpressionFrame diff --git a/Tests/Ix/Compile/ExpressionIndex.lean b/Tests/Ix/Compile/ExpressionIndex.lean deleted file mode 100644 index 5492c75a2..000000000 --- a/Tests/Ix/Compile/ExpressionIndex.lean +++ /dev/null @@ -1,90 +0,0 @@ -import LSpec -import Ix.CompileM - -/-! Finite controls for expression wire indices (`Ix.CompileM.checkedTableIndex`). -Oversized source indices and mutual contexts are supplied directly; no huge -term or table is allocated. The expression compiler (`compileExpr`) and the -constant-head entry (`compileConstExprRaw`) are checked, including retries -after refusals and a cache hit at the representable bound. -/ - -namespace Tests.Ix.Compile.ExpressionIndex - -open LSpec -open _root_.Ix (Name Expr) -open _root_.Ix.CompileM - -private def structName := Name.fromLeanName `IndexBoundary.Structure -private def recursiveName := Name.fromLeanName `IndexBoundary.recursive -private def env : CompileEnv := { (default : CompileEnv) with - nameToAddr := ({} : Std.HashMap _root_.Ix.Compile.ExactKey.ExactName Address).insert structName default } -private def block (index : Nat := 0) : BlockEnv := { - all := {}, current := Name.mkAnon, - mutCtx := (default : _root_.Ix.MutCtx).insert recursiveName index, univCtx := [] } - -private def raw (action : CompileM α) (index : Nat := 0) (state : BlockState := {}) : - Except CompileError α × BlockState := (action (env, block index)).run state - -private def overflow (result : Except CompileError α) : Bool := match result with - | .error (.resourceLimit _) => true - | _ => false - -def controls : List (String × Bool) := Id.run do - let last := UInt64.size - 1 - let big := Expr.mkBVar UInt64.size - let bigger := Expr.mkBVar (UInt64.size + 7) - let edge := Expr.mkBVar last - let (bad, badAfter) := raw (compileExpr big) - let (badRetry, retryAfter) := raw (compileExpr big) 0 badAfter - let (biggerResult, _) := raw (compileExpr bigger) - let (edgeResult, edgeAfter) := raw (compileExpr edge) - let (edgeCached, _) := raw (compileExpr edge) 0 edgeAfter - let (zeroResult, _) := raw (compileExpr (Expr.mkBVar 0)) - let projection := Expr.mkProj structName UInt64.size (Expr.mkBVar 0) - let (projResult, projAfter) := raw (compileExpr projection) - let (projRetry, projRetryAfter) := raw (compileExpr projection) 0 projAfter - let edgeProjection := Expr.mkProj structName last (Expr.mkBVar 0) - let (edgeProj, _) := raw (compileExpr edgeProjection) - let recursive := Expr.mkConst recursiveName #[] - let (recResult, recAfter) := raw (compileExpr recursive) UInt64.size - let (recRetry, recRetryAfter) := raw (compileExpr recursive) UInt64.size recAfter - let (rawRec, rawRecAfter) := raw (compileConstExprRaw recursiveName #[]) UInt64.size - let (edgeRec, _) := raw (compileExpr recursive) last - let first := Expr.mkBVar 0 - let application := Expr.mkApp first big - let (appResult, appAfter) := raw (compileExpr application) - return [ - ("first oversized bound variable is refused", overflow bad), - ("larger bound variable does not alias its residue", overflow biggerResult), - ("retry refuses the same bound variable", overflow badRetry), - ("bound-variable refusal precedes allocation", badAfter.arena.nodes.isEmpty && - retryAfter.arena.nodes.isEmpty), - ("refused bound variable is not cached", badAfter.exprCache.isEmpty && - retryAfter.exprCache.isEmpty), - ("last representable bound variable is exact", - edgeResult.toOption == some (.var last.toUInt64, 0) && - edgeCached.toOption == some (.var last.toUInt64, 0)), - ("bound variable zero is unchanged", zeroResult.toOption == some (.var 0, 0)), - ("oversized projection field is refused on both attempts", - overflow projResult && overflow projRetry), - ("projection refusal precedes name reference and child writes", - projAfter.blockNames.isEmpty && projAfter.refs.isEmpty && - projAfter.arena.nodes.isEmpty && projAfter.exprCache.isEmpty && - projRetryAfter.blockNames.isEmpty && projRetryAfter.arena.nodes.isEmpty), - ("last representable projection field is exact", - edgeProj.toOption == some (.prj 0 last.toUInt64 (.var 0), 1)), - ("oversized mutual reference is refused on both attempts", - overflow recResult && overflow recRetry), - ("raw constant entry also refuses mutual overflow", overflow rawRec), - ("mutual refusal allocates no expression arena node", recAfter.arena.nodes.isEmpty && - recRetryAfter.arena.nodes.isEmpty && rawRecAfter.arena.nodes.isEmpty), - ("mutual refusal does not cache the source expression", - recAfter.exprCache.isEmpty && recRetryAfter.exprCache.isEmpty), - ("last representable mutual slot is exact", - edgeRec.toOption == some (.recur last.toUInt64 #[], 0)), - ("parent propagates overflow while retaining completed children", overflow appResult && - appAfter.arena.nodes == #[.leaf] && appAfter.exprCache.contains first && - !appAfter.exprCache.contains big && !appAfter.exprCache.contains application)] - -def suite : List TestSeq := controls.map fun (label, ok) => test s!"expression index: {label}" ok - -end Tests.Ix.Compile.ExpressionIndex diff --git a/Tests/Ix/Compile/GroundRefusal.lean b/Tests/Ix/Compile/GroundRefusal.lean index cbbbbec86..eeb059c6c 100644 --- a/Tests/Ix/Compile/GroundRefusal.lean +++ b/Tests/Ix/Compile/GroundRefusal.lean @@ -1,4 +1,11 @@ -/- +import Ix.Meta +import Ix.EnvScope +import Ix.CompileDriver +import Ix.CompileCertified +import Ix.Cert.Compile +import LSpec + +/-! The compile's record of ungrounded input (part of `ground-unit`; the adversarial matrix's row 11): a constant whose value dependency is left out of the input is refused by name in both compilers and in the pure @@ -9,11 +16,6 @@ `--allow-partial` (`Ix.Cli.CompileLeanCmd`), and `Ix.Cert.compileComplete` fails on any, so both are fail-closed on ungrounded input. -/ -import Ix.Meta -import Ix.EnvScope -import Ix.CompileDriver -import Ix.Cert.Compile -import LSpec open Lean LSpec @@ -35,23 +37,17 @@ def closure (env : Environment) (drop : Name → Bool) : List (Name × ConstantI /-- The Lean IO pipeline's failures `(name, reason)`, sorted by name. -/ def leanFailures (env : Environment) (cs : List (Name × ConstantInfo)) : IO (Array (String × String)) := do - let input ← IO.ofExcept ((Ix.Compile.compileInputFromEnv env cs).mapError toString) - match ← Ix.CompileM.compileLeanInput input (numWorkers := 1) with - | .error e => throw (IO.userError s!"compileLeanInput: {e}") - | .ok out => - let failures := out.cenv.ungrounded.toArray.map fun (n, m) => (n.pretty, m) - return failures.qsort fun a b => a.1 < b.1 - -/-- The Rust compiler's failures `(name, reason)` (fail-closed `ix compile`), + let out ← IO.ofExcept (← Ix.CompileM.compileCertified env cs 1) + let failures := out.failures.toMap.toArray.map fun (n, m) => (n.pretty, m) + return failures.qsort fun a b => a.1 < b.1 + +/-- The certified compiler's failures `(name, reason)` (`compileCertified`), sorted by name. -/ def rustFailures (env : Environment) (cs : List (Name × ConstantInfo)) : IO (Array (String × String)) := do - let prepared ← IO.ofExcept (Ix.Compile.prepareRegisteredConstants env cs) - let dir ← IO.FS.createTempDir - try - let status ← Ix.CompileM.rsCompileEnvBytesFFI prepared (dir / "rust.ixe").toString false - return status.ungrounded.qsort fun a b => a.1 < b.1 - finally IO.FS.removeDirAll dir + let out ← IO.ofExcept (← Ix.CompileM.compileCertified env cs) + let failures := out.failures.toMap.toArray.map fun (n, m) => (n.pretty, m) + return failures.qsort fun a b => a.1 < b.1 /-- The pure compile's report on the prepared constants (`Ix.Cert.compileReport`). -/ def pureReport (env : Environment) (cs : List (Name × ConstantInfo)) : diff --git a/Tests/Ix/Compile/HeaderCounts.lean b/Tests/Ix/Compile/HeaderCounts.lean deleted file mode 100644 index fc4ef0d33..000000000 --- a/Tests/Ix/Compile/HeaderCounts.lean +++ /dev/null @@ -1,133 +0,0 @@ -import LSpec -import Ix.CompileM - -/-! Finite boundary controls for declaration-header numbers. These directly -exercise structured compiler inputs, without claiming that oversized headers -are well-typed Lean declarations or allocating huge arrays/telescopes. -/ - -namespace Tests.Ix.Compile.HeaderCounts - -open LSpec -open _root_.Ix (Name Expr Level ConstantVal ConstructorVal RecursorVal RecursorRule InductiveVal Ind Def) -open _root_.Ix.CompileM - -private def name := Name.fromLeanName `HeaderBoundary -private def context : CompileEnv × BlockEnv := - (default, { all := {}, current := name, mutCtx := {}, univCtx := [] }) -private def sourceType := Expr.mkSort Level.mkZero -private def cnst : ConstantVal := { name, levelParams := #[], type := sourceType } -private def ctor : ConstructorVal := - { cnst, induct := name, cidx := 0, numParams := 0, numFields := 0, isUnsafe := false } -private def recursor : RecursorVal := - { cnst, all := #[], numParams := 0, numIndices := 0, numMotives := 0, numMinors := 0, - rules := #[], k := false, isUnsafe := false } -private def inductiveVal : InductiveVal := - { cnst, numParams := 0, numIndices := 0, all := #[], ctors := #[], numNested := 0, - isRec := false, isUnsafe := false, isReflexive := false } -private def inductiveData : Ind := - { name, levelParams := #[], type := sourceType, numParams := 0, numIndices := 0, - all := #[], ctors := #[], numNested := 0, isRec := false, isUnsafe := false, isReflexive := false } -private def metadata : InductiveTypeCompileMeta := - { arena := {}, metaUnivs := #[], univPatches := #[] } -private def raw (action : CompileM α) (state : BlockState := {}) : Except CompileError α × BlockState := (action context).run state -private def refused (result : Except CompileError α) : Bool := match result with - | .error (.resourceLimit _) => true - | _ => false - -private def ruleAt (fields : Nat) : RecursorRule := { ctor := name, nfields := fields, rhs := Expr.mkBVar 0 } - -def controls : List (String × Bool) := Id.run do - let last := UInt64.size - 1 - let mut result := [] - for fields in [0, 1, last] do - let (compiled, _) := raw (compileRecursorRule (ruleAt fields)) - result := result ++ [(s!"recursor rule preserves {fields} fields", - compiled.toOption.map (fun output => output.1.fields.toNat) == some fields)] - for fields in [UInt64.size, UInt64.size + 7] do - result := result ++ [(s!"recursor rule refuses {fields} fields", refused (raw (compileRecursorRule (ruleAt fields))).1)] - let badRule := ruleAt UInt64.size - result := result ++ [("recursor rule loop propagates header overflow", - refused (raw (compileRecursorRules [ruleAt 0, badRule] {})).1)] - let (_, failedRule) := raw (compileRecursorRule badRule) - result := result ++ [("rule overflow retains completed RHS effects", - failedRule.arena.nodes == #[.leaf] && failedRule.exprCache.contains badRule.rhs)] - for count in [UInt64.size, UInt64.size + 7] do - for (label, modified) in [ - ("constructor index", { ctor with cidx := count }), - ("constructor parameters", { ctor with numParams := count }), - ("constructor fields", { ctor with numFields := count })] do - let (compiled, _) := raw (finishConstructorCompilation modified (.sort 0) 0) - result := result ++ [(s!"{label} refuses {count}", refused compiled)] - for (label, modified) in [ - ("recursor parameters", { recursor with numParams := count }), - ("recursor indices", { recursor with numIndices := count }), - ("recursor motives", { recursor with numMotives := count }), - ("recursor minors", { recursor with numMinors := count })] do - let (compiled, _) := raw (finishRecursorCompilation modified (.sort 0) 0 {}) - result := result ++ [(s!"{label} refuses {count}", refused compiled)] - for (label, modified) in [ - ("inductive parameters", { inductiveVal with numParams := count }), - ("inductive indices", { inductiveVal with numIndices := count })] do - result := result ++ [(s!"{label} refuses {count}", - refused (raw (finishInductiveCompilation modified (.sort 0) 0 metadata {})).1)] - for (label, modified) in [ - ("mutual inductive parameters", { inductiveData with numParams := count }), - ("mutual inductive indices", { inductiveData with numIndices := count })] do - result := result ++ [(s!"{label} refuses {count}", - refused (raw (finishInductiveDataCompilation modified (.sort 0) 0 metadata {})).1)] - let maxCtor := { ctor with cidx := last, numParams := last, numFields := last } - let ctorResult := (raw (finishConstructorCompilation maxCtor (.sort 0) 0)).1 - result := result ++ [("constructor maximum headers remain exact", ctorResult.toOption.any fun output => - output.1.cidx.toNat == last && output.1.params.toNat == last && output.1.fields.toNat == last)] - let maxRec := { recursor with numParams := last, numIndices := last, numMotives := last, numMinors := last } - let recResult := (raw (finishRecursorCompilation maxRec (.sort 0) 0 {})).1 - result := result ++ [("recursor maximum headers remain exact", recResult.toOption.any fun output => - output.1.params.toNat == last && output.1.indices.toNat == last && output.1.motives.toNat == last && output.1.minors.toNat == last)] - let maxInd := { inductiveVal with numParams := last, numIndices := last } - let indResult := (raw (finishInductiveCompilation maxInd (.sort 0) 0 metadata {})).1 - let maxData := { inductiveData with numParams := last, numIndices := last } - let dataResult := (raw (finishInductiveDataCompilation maxData (.sort 0) 0 metadata {})).1 - result := result ++ [("inductive maximum headers remain exact", indResult.toOption.any fun output => - output.1.params.toNat == last && output.1.indices.toNat == last), - ("mutual inductive maximum headers remain exact", dataResult.toOption.any fun output => - output.1.params.toNat == last && output.1.indices.toNat == last)] - let params := #[Name.fromLeanName `u, Name.fromLeanName `v] - let polymorphic := { cnst with levelParams := params } - let definition : Def := { - name := name - levelParams := params - type := sourceType - value := Expr.mkBVar 0 - kind := .defn - hints := .opaque - safety := .safe - all := #[] } - result := result ++ [ - ("definition preserves universe arity", (raw (finishDefinitionDataCompilation definition (.sort 0) 0 (.var 0) 1)).1.toOption.any - (fun output => output.1.lvls == 2)), - ("axiom preserves universe arity", (raw (finishAxiomCompilation ⟨polymorphic, false⟩ (.sort 0) 0)).1.toOption.any - (fun output => output.1.lvls == 2)), - ("quotient preserves universe arity", (raw (finishQuotientCompilation ⟨polymorphic, .type⟩ (.sort 0) 0)).1.toOption.any - (fun output => output.1.lvls == 2)), - ("recursor preserves universe arity", (raw (finishRecursorCompilation { recursor with cnst := polymorphic } (.sort 0) 0 {})).1.toOption.any - (fun output => output.1.lvls == 2)), - ("constructor preserves universe arity", (raw (finishConstructorCompilation { ctor with cnst := polymorphic } (.sort 0) 0)).1.toOption.any - (fun output => output.1.lvls == 2)), - ("inductive preserves universe arity", (raw (finishInductiveCompilation { inductiveVal with cnst := polymorphic } - (.sort 0) 0 metadata {})).1.toOption.any (fun output => output.1.lvls == 2)), - ("mutual inductive preserves universe arity", (raw (finishInductiveDataCompilation { inductiveData with levelParams := params } - (.sort 0) 0 metadata {})).1.toOption.any (fun output => output.1.lvls == 2))] - let initial : BlockState := { - arena := { nodes := #[.leaf] } - refs := #[default] - univs := #[.zero] - exprCache := ({} : Std.HashMap _root_.Ix.Compile.ExactKey.ExprKey _).insert (Expr.mkBVar 0) (.var 0, 0) } - let (failed, failedState) := raw (finishConstructorCompilation { ctor with numFields := UInt64.size } (.sort 0) 0) initial - result := result ++ [("header refusal drains per-constant state and retains tables", - refused failed && failedState.arena.nodes.isEmpty && failedState.exprCache.isEmpty && - failedState.refs == initial.refs && failedState.univs == initial.univs)] - return result - -def suite : List TestSeq := controls.map fun (label, ok) => test s!"header count: {label}" ok - -end Tests.Ix.Compile.HeaderCounts diff --git a/Tests/Ix/Compile/IndFlags.lean b/Tests/Ix/Compile/IndFlags.lean index dfbf10421..cb922c9e3 100644 --- a/Tests/Ix/Compile/IndFlags.lean +++ b/Tests/Ix/Compile/IndFlags.lean @@ -1,13 +1,20 @@ -/- +import Ix.Meta +import Ix.EnvScope +import Ix.CompileDriver +import Ix.CompileCertified +import Ix.Cert.Compile +import LSpec + +/-! compile-ind-flags: both compilers refuse an input inductive whose stored flags (`isRec`, `isReflexive`, `numNested`) differ from the recomputed - ones (Rust `validate_ind_groups`, Lean `Ix.CompileM.validateInductiveFlags`), + ones (`Ix.CompileM.validateInductiveFlags`), and compile the same closure with the true flags byte-identically. The closure is `T` (a recursive inductive) with its recursor; the refused neighbour stores `isRec := false` on `T`. The Lean refusal is checked on both Lean drivers: the IO pipeline - (`compileLeanConsts`, the parallel driver) and the pure compiler + (`compileCertified`, the parallel driver) and the pure compiler (`Ix.Cert.compile`, the sequential driver). The second domain restriction: both compilers refuse a mutual block whose @@ -21,11 +28,6 @@ Run with: `lake test -- --ignored compile-ind-flags`. -/ -import Ix.Meta -import Ix.EnvScope -import Ix.CompileDriver -import Ix.Cert.Compile -import LSpec open Lean @@ -86,27 +88,28 @@ def flagsMessage : String := s!"non-canonical inductive flags for '{tName}': stored isRec=false \ isReflexive=false numNested=0, computed isRec=true isReflexive=false numNested=0" -/-- Rust (`ix compile`, fail-closed): `ok bytes`, or the refusal text. -/ +/-- Certified compile (`ix compile`, fail-closed): `ok bytes`, or the refusal text. -/ def rust (env : Environment) (cs : List (Name × ConstantInfo)) (out : System.FilePath) : IO (Except String ByteArray) := do let prepared ← IO.ofExcept (Ix.Compile.prepareRegisteredConstants env cs) try - let status ← Ix.CompileM.rsCompileEnvBytesFFI prepared out.toString false - if let some (n, m) := status.ungrounded[0]? then - return .error s!"{n}: {m}" + let res ← IO.ofExcept (← Ix.CompileM.compileCertifiedPrepared prepared false) + if let some (n, m) := res.failures.toMap.toArray[0]? then + return .error s!"{n.pretty}: {m}" + let bytes ← IO.ofExcept (Ixon.serEnv res.env) + IO.FS.writeBinFile out bytes return .ok (← IO.FS.readBinFile out) catch e => return .error (toString e) /-- The Lean IO pipeline (`compile-lean`): `ok bytes`, or the refusal text. -/ def lean (env : Environment) (cs : List (Name × ConstantInfo)) : IO (Except String ByteArray) := do - let input ← IO.ofExcept ((Ix.Compile.compileInputFromEnv env cs).mapError toString) - match ← Ix.CompileM.compileLeanInput input (numWorkers := 1) with + match ← Ix.CompileM.compileCertified env cs 1 with | .error e => return .error e | .ok o => - if let some (n, m) := o.cenv.ungrounded.toList.head? then + if let some (n, m) := o.failures.toMap.toList.head? then return .error s!"{n}: {m}" - return .ok o.bytes + return Ixon.serEnv o.env /-- The pure compiler (`Ix.Cert.compile`): `ok bytes`, or the refusal text. -/ def pure (cs : List (Name × ConstantInfo)) : Except String ByteArray := do diff --git a/Tests/Ix/Compile/IndexSafety.lean b/Tests/Ix/Compile/IndexSafety.lean deleted file mode 100644 index cb294e4a2..000000000 --- a/Tests/Ix/Compile/IndexSafety.lean +++ /dev/null @@ -1,45 +0,0 @@ -import LSpec -import Ix.CompileM - -/-! The compiler's total array accesses (`Ix.CompileM.arrIdx`): an in-range -index reads the value, an out-of-range one is the named error -(`internalIndexError`, naming the block and the site) instead of `PANIC` and -a default value; `byteArrayCmp` reads its bytes by proof; a standalone -payload is read by its shape. -/ - -namespace Tests.Ix.Compile.IndexSafety - -open LSpec -open _root_.Ix (Name) - -private def rootName : Name := Name.fromLeanName `IndexSafety.missing - -private def checkedRead (values : Array Nat) (index : Nat) : Except String Nat := do - let cenv := _root_.Ix.CompileM.CompileEnv.new { consts := {} } - let blockEnv : _root_.Ix.CompileM.BlockEnv := - { all := {}, current := rootName, mutCtx := default, univCtx := [] } - let (out, _) ← (_root_.Ix.CompileM.CompileM.run cenv blockEnv {} - (_root_.Ix.CompileM.arrIdx values index "index-safety read")).mapError toString - return out - -private def readError (values : Array Nat) (index : Nat) : Option String := - match checkedRead values index with - | .error message => some message - | .ok _ => none - -def suite : List TestSeq := [ - test "checked reads return the in-range value and name the block and site otherwise" - (((checkedRead #[3, 5] 1).toOption == some 5 - && readError #[3] 1 == some - "invalidMutualBlock: internal error in block 'IndexSafety.missing': index-safety read: index 1 out of range (size 1)" - && readError #[] 0 == some - "invalidMutualBlock: internal error in block 'IndexSafety.missing': index-safety read: index 0 out of range (size 0)" : Bool)), - test "byteArrayCmp is lexicographic, then by length" - (_root_.Ix.CompileM.byteArrayCmp ⟨#[1, 2]⟩ ⟨#[1, 3]⟩ == .lt - && _root_.Ix.CompileM.byteArrayCmp ⟨#[2]⟩ ⟨#[1, 9]⟩ == .gt - && _root_.Ix.CompileM.byteArrayCmp ⟨#[1]⟩ ⟨#[1, 0]⟩ == .lt - && _root_.Ix.CompileM.byteArrayCmp ⟨#[1, 0]⟩ ⟨#[1, 0]⟩ == .eq), - test "a standalone payload is a single definition or recursor" - ((_root_.Ix.CompileM.standaloneMutConstInfo? #[]).isNone)] - -end Tests.Ix.Compile.IndexSafety diff --git a/Tests/Ix/Compile/KernelReport.lean b/Tests/Ix/Compile/KernelReport.lean index ddede67bc..1a274a686 100644 --- a/Tests/Ix/Compile/KernelReport.lean +++ b/Tests/Ix/Compile/KernelReport.lean @@ -12,9 +12,18 @@ public section namespace Tests.Ix.Compile.KernelReport +/-- The outcomes a row may carry (`Ix.KernelCheck.Step`, on every driver +path, `--jobs` included): `accept`; `typed`, a tier record (a partial or +unsafe declaration) that the typing tier typed, whose reason is its safety +(`partial` or `unsafe`, `Ix.Kernel.Tier.Safety.word`); `decline`; `reject`; +`blocked`. A run with `--allow-user-axioms` keeps these outcomes: it lists +the user axioms it admitted on stderr (`CONDITIONAL on K user axioms`), not +in the rows. -/ +def outcomes : Array String := #["accept", "typed", "decline", "reject", "blocked"] + /-- One row's verdict: the record's display names (at most three), its -outcome (`accept`, `decline`, `reject` or `blocked`) and its reason (a -blocked row's reason is its root record's address). -/ +outcome (one of `outcomes`) and its reason (a typed row's reason is its +safety, a blocked row's its root record's address). -/ structure Verdict where names : Array String outcome : String @@ -27,8 +36,9 @@ abbrev Report := Std.HashMap String Verdict def isAddress (address : String) : Bool := address.length == 64 && address.toList.all (fun c => "0123456789abcdef".contains c) -/-- Reject failed processes, malformed or incomplete rows, duplicate records, -and empty output. A valid report can contain declines, rejects and blocked +/-- Reject failed processes, malformed or incomplete rows, unknown outcomes, a +typed row whose reason is not a tier safety, duplicate records, and empty +output. A valid report can contain typed, declined, rejected and blocked records; those verdicts remain available for the caller's explicit policy. -/ def parse (content : String) (exitCode : UInt32 := 0) : Except String Report := do unless exitCode == 0 do @@ -47,8 +57,10 @@ def parse (content : String) (exitCode : UInt32 := 0) : Except String Report := let names ← j.getObjValAs? (Array String) "names" let outcome ← j.getObjValAs? String "outcome" let reason ← j.getObjValAs? String "reason" - unless #["accept", "decline", "reject", "blocked"].contains outcome do + unless outcomes.contains outcome do throw s!"unknown outcome {outcome}" + if outcome == "typed" && !(reason == "partial" || reason == "unsafe") then + throw s!"typed row whose reason {reason} is not a tier safety" return (address, { names, outcome, reason }) let (address, verdict) ← read.mapError (fun e => s!"certified row {lineNo}: {e}") if report.contains address then @@ -67,14 +79,14 @@ def checkCoverage (report : Report) (expected : Array (String × String)) : Exce {String.intercalate ", " (missing.toList.map fun (n, a) => s!"{n}@{a}")}" /-- The address of an anonymous diagnostic label: `#` or ``, -optionally followed by a display name (`check-rs --anon`'s fail-out rows). -/ +optionally followed by a display name (anonymous fail-out rows). -/ def anonymousAddress? (label : String) : Option String := do let token ← (label.trimAscii.toString.splitOn " ").head? let address := if token.startsWith "#" then (token.drop 1).toString else token if isAddress address then some address else none -/-- `(label, message)` of every record of a FailureLog file (the `--fail-out` -of `check-rs`): a `# message` comment, then the record's label lines. The +/-- `(label, message)` of every record of a FailureLog file (`--fail-out`): +a `# message` comment, then the record's label lines. The header and trailer comments are not messages. -/ def failOutRows (content : String) : Array (String × String) := Id.run do let mut out : Array (String × String) := #[] @@ -90,7 +102,7 @@ def failOutRows (content : String) : Array (String × String) := Id.run do else out := out.push (l, msg) return out -/-- The number of constants `check-rs` reports it checked (`[check] P/N passed`). -/ +/-- The number of constants the checker summary reports it checked (`[check] P/N passed`). -/ def rsChecked (out : String) : Option Nat := (out.splitOn "\n").findSome? fun line => let l := line.trimAscii.toString diff --git a/Tests/Ix/Compile/KernelReportTests.lean b/Tests/Ix/Compile/KernelReportTests.lean index 8059654d7..ef50bb900 100644 --- a/Tests/Ix/Compile/KernelReportTests.lean +++ b/Tests/Ix/Compile/KernelReportTests.lean @@ -1,16 +1,17 @@ -/- +import LSpec +import Tests.Ix.Compile.KernelReport + +/-! compiler-reports: the strict readers of the checkers' reports (`Tests.Ix.Compile.KernelReport`), which the closure suites use. The certified checker's JSONL rows: coverage by owning record address, every verdict kept for the caller's policy, an incomplete process or a malformed, truncated, duplicate or unknown row refused. - `check-rs`'s fail-out rows and summary: messages per label, header and + Fail-out rows and summary: messages per label, header and trailer comments skipped, anonymous labels resolved to their address. Ported from a9cf3607 (`Tests/Ix/Compile/KernelReportTests.lean`). The migrated IxC `check-lean` protocol has its separate strict controls in LeanReportTests. -/ -import LSpec -import Tests.Ix.Compile.KernelReport namespace Tests.Ix.Compile.KernelReportTests @@ -31,10 +32,10 @@ private def failsWith (result : Except String α) (fragment : String) : Bool := | .error e => (e.splitOn fragment).length > 1 | .ok _ => false -/-- A `check-rs --fail-out` file: header, two records under one message, one +/-- A `--fail-out` file: header, two records under one message, one under another, trailer. -/ private def failOut : String := - "# ix check-rs failures\n# env: out.ixe\n# seeds: names.txt\n" ++ + "# ix check failures\n# env: out.ixe\n# seeds: names.txt\n" ++ "# AppTypeMismatch: expected Nat\nA.f\nA.g\n#\n" ++ "# declaration type mismatch\n#" ++ addressA ++ " (A.h)\n" ++ "# total failures: 3\n" @@ -58,6 +59,14 @@ def suite : List TestSeq := [ ((do let report ← parse (row addressA "blocked" addressB) pure (report[addressA]?.map Verdict.outcome)).toOption == some (some "blocked")) + ++ test "a tier record the typing tier typed is kept with its safety" + ((do + let report ← parse (row addressA "typed" "partial" ++ "\n" ++ row addressB "typed" "unsafe") + pure ([addressA, addressB].map fun a => + report[a]?.map fun (v : Verdict) => (v.outcome, v.reason))).toOption == + some [some ("typed", "partial"), some ("typed", "unsafe")]) + ++ test "a typed row whose reason is not a tier safety fails" + (failsWith (parse (row addressA "typed" "")) "not a tier safety") ++ test "an incomplete process fails despite complete-looking rows" (failsWith (parse accepted 3) "exited with code 3") ++ test "empty successful output fails" @@ -86,7 +95,7 @@ def suite : List TestSeq := [ (failOutRows failOut == #[("A.f", "AppTypeMismatch: expected Nat"), ("A.g", "AppTypeMismatch: expected Nat"), ("#" ++ addressA ++ " (A.h)", "declaration type mismatch")]) - ++ test "check-rs's summary gives the checked count" + ++ test "checker summary gives the checked count" (rsChecked "[check] seeded 7\n[check] 5/7 passed\n" == some 7) ++ test "no summary, no checked count" (rsChecked "[check] seeded 7\n" == none) diff --git a/Tests/Ix/Compile/LevelSpellings.lean b/Tests/Ix/Compile/LevelSpellings.lean index 1aefc11d0..5d391ecd4 100644 --- a/Tests/Ix/Compile/LevelSpellings.lean +++ b/Tests/Ix/Compile/LevelSpellings.lean @@ -17,10 +17,9 @@ declaration's expressions as given, which is exactly the Mathlib WF-eq_def situation these fixtures reproduce. - Consumed by: `validateAuxClosure` (compile-schedule-identity / - decompile-diff), and — via the Tests binary env — the whole-env - tc-roundtrip and kernel-ixon-roundtrip suites, where the stage-1 - spelling decorations (Lean `Ix.Tc` + Rust kernel) are load-bearing. + Consumed by: `validateAuxClosure` and the compiler/kernel test suites + over the Tests binary environment, where the stage-1 spelling decorations + are load-bearing. Import note: `Lean.Elab.BuiltinEvalCommand` (the `run_cmd` elab), NOT all of `Lean` — the roundtrip suites' envs are this file's module diff --git a/Tests/Ix/Compile/LocalBlob.lean b/Tests/Ix/Compile/LocalBlob.lean deleted file mode 100644 index e3703225a..000000000 --- a/Tests/Ix/Compile/LocalBlob.lean +++ /dev/null @@ -1,112 +0,0 @@ -import LSpec -import Ix.CompileM -import Ix.Compile.Publication - -/-! The block's blob writes are checked against the block's own blobs and the -published ones (`Ix.CompileM.storeBlob`): a reused address must hold the same -bytes. Supplied conflicting stores exercise the local compatibility checks. -These controls do not claim a cryptographic collision or a valid-source -miscompile. -/ - -namespace Tests.Ix.Compile.LocalBlob - -open LSpec -open _root_.Ix (Name Expr Level) -open _root_.Ix.CompileM - -private def raw (action : CompileM α) (state : BlockState := {}) - (env : CompileEnv := default) : Except CompileError α × BlockState := - (action (env, { all := {}, current := Name.mkAnon, mutCtx := default, univCtx := [] })).run state - -private def conflict (result : Except CompileError α) : Bool := match result with - | .error (.invalidMutualBlock "anonymous publication: conflicting blob payload") => true - | _ => false - -def controls : List (String × Bool) := Id.run do - let bytes := "new".toUTF8 - let old := "old".toUTF8 - let address := Address.admissionHash bytes - let poisoned : BlockState := { blockBlobs := ({} : Std.HashMap Address ByteArray).insert address old } - let prior : CompileEnv := { (default : CompileEnv) with blobs := poisoned.blockBlobs } - let matching : CompileEnv := - { (default : CompileEnv) with blobs := ({} : Std.HashMap Address ByteArray).insert address bytes } - let (localResult, localAfter) := raw (storeString "new") poisoned - let (ambientResult, ambientAfter) := raw (storeString "new") {} prior - let (accepted, acceptedAfter) := raw (storeString "new") {} matching - let (aliasResult, aliasAfter) := raw (storeString "new") acceptedAfter matching - let (hiddenConflict, hiddenAfter) := raw (storeString "new") acceptedAfter prior - let name := Name.fromLeanName `LocalBlob.new - let (nameResult, nameAfter) := raw (compileName name) poisoned - let (retryResult, retryAfter) := raw (compileName name) nameAfter - let (namesResult, namesAfter) := raw (compileNames #[Name.fromLeanName `Safe, name]) poisoned - let (parentResult, parentAfter) := raw (compileName (Name.fromLeanName `new.child)) poisoned - let literal := Expr.mkLit (.strVal "new") - let (direct, directAfter) := raw (compileExpr literal) poisoned - let (retried, retriedAfter) := raw (compileExpr literal) directAfter - let (structural, structuralAfter) := - raw (collectExprTablesStructural default default [] literal (#[], #[], {})) poisoned - let (runtime, runtimeAfter) := raw (collectExprTables literal default (#[], #[], {})) poisoned - let (datum, datumAfter) := raw (compileDataValue (.ofString "new")) poisoned - let natural := ByteArray.mk (Nat.toBytesLE 42) - let naturalAddress := Address.admissionHash natural - let naturalPoisoned : BlockState := { - blockBlobs := ({} : Std.HashMap Address ByteArray).insert naturalAddress old } - let (natResult, natAfter) := raw (compileExpr (Expr.mkLit (.natVal 42))) naturalPoisoned - let (natDatum, natDatumAfter) := raw (compileDataValue (.ofNat 42)) naturalPoisoned - let (intDatum, intAfter) := raw (compileDataValue (.ofInt (.negSucc 41))) { - blockBlobs := ({} : Std.HashMap Address ByteArray).insert - (Address.admissionHash (serializeIxInt (.negSucc 41))) old } - let syntaxAddress := Address.admissionHash (ByteArray.mk #[0]) - let (syntaxDatum, syntaxAfter) := raw (compileDataValue (.ofSyntax .missing)) { - blockBlobs := ({} : Std.HashMap Address ByteArray).insert syntaxAddress old } - -- The merge-time check (`checkBlobContent`, part of `checkCompiledBlock`): a block's blob - -- at an address the published store holds with other bytes is refused before the merge - -- writes it; the same bytes are accepted. - let mergeConflict : Bool := match checkBlobContent prior acceptedAfter with - | .error e => conflict (.error e : Except CompileError Unit) - | .ok () => false - let mergeAgree := (checkBlobContent matching acceptedAfter).toOption.isSome - return [ - ("merge refuses a block blob the published store holds with other bytes", mergeConflict), - ("merge accepts a block blob the published store holds with the same bytes", mergeAgree), - ("local conflict is rejected without replacement", - conflict localResult && localAfter.blockBlobs.get? address == some old), - ("ambient conflict is rejected before shadowing", - conflict ambientResult && ambientAfter.blockBlobs.isEmpty), - ("identical ambient payload is accepted", - accepted.toOption == some address && acceptedAfter.blockBlobs.get? address == some bytes), - ("identical local payload is accepted", - aliasResult.toOption == some address && aliasAfter.blockBlobs.size == 1), - ("matching local payload cannot hide ambient conflict", - conflict hiddenConflict && hiddenAfter.blockBlobs.get? address == some bytes), - ("name metadata cannot overwrite literal data", - conflict nameResult && nameAfter.blockBlobs.get? address == some old), - ("failed name leaves no cache entry masking a retry", - nameAfter.blockNames.isEmpty && conflict retryResult && retryAfter.blockNames.isEmpty), - ("array of names commits only after all checks", - conflict namesResult && namesAfter.blockNames.isEmpty && namesAfter.blockBlobs.size == 1), - ("ancestor conflict rolls back earlier child writes", - conflict parentResult && parentAfter.blockNames.isEmpty && parentAfter.blockBlobs.size == 1), - ("expression compiler preserves the conflicting payload", - conflict direct && directAfter.blockBlobs.get? address == some old), - ("retry after a failed literal also refuses", - conflict retried && retriedAfter.blockBlobs.get? address == some old), - ("failed literal is not cached or allocated", - directAfter.exprCache.isEmpty && directAfter.arena.nodes.isEmpty), - ("structural preseed walk refuses", - conflict structural && structuralAfter.blockBlobs.get? address == some old), - ("runtime preseed walk refuses", - conflict runtime && runtimeAfter.blockBlobs.get? address == some old), - ("string metadata uses the same guard", - conflict datum && datumAfter.blockBlobs.get? address == some old), - ("natural literal uses the same guard", - conflict natResult && natAfter.blockBlobs.get? naturalAddress == some old), - ("natural metadata uses the same guard", - conflict natDatum && natDatumAfter.blockBlobs.get? naturalAddress == some old), - ("integer metadata uses the same guard", conflict intDatum && intAfter.blockBlobs.size == 1), - ("syntax metadata uses the same guard", - conflict syntaxDatum && syntaxAfter.blockBlobs.get? syntaxAddress == some old)] - -def suite : List TestSeq := controls.map fun (label, ok) => test s!"local blob: {label}" ok - -end Tests.Ix.Compile.LocalBlob diff --git a/Tests/Ix/Compile/Mutual.lean b/Tests/Ix/Compile/Mutual.lean index f009028eb..f81d73eec 100644 --- a/Tests/Ix/Compile/Mutual.lean +++ b/Tests/Ix/Compile/Mutual.lean @@ -1,12 +1,14 @@ module public import Lean -namespace Tests.Ix.Compile.Mutual +/-! + Mutual and auxiliary shapes in the shared compiler fixture corpus. + Members retain their source positions even when their shapes agree under + renaming. Reordered declarations are separate inputs; the compiler does + not promise equal addresses across different member orders. +-/ --- Mutual and auxiliary shapes in the shared compiler fixture corpus. --- Members retain their source positions even when their shapes agree under --- renaming. Reordered declarations are separate inputs; the compiler does --- not promise equal addresses across different member orders. +namespace Tests.Ix.Compile.Mutual -- Symmetric pair (A ≅ B under renaming), with two distinct member slots. namespace AlphaCollapse @@ -607,7 +609,7 @@ end BelowPredicate2 -- Type-level mutual indexed inductives + mutual structural recursion + -- forced `.eq_def` equation lemmas (the TorchLean `NN.GraphSpec.DAG` --- `Term`/`Args` shape, torchlean.ixe check-rs failure of 2026-08-22). +-- `Term`/`Args` shape, torchlean.ixe failure of 2026-08-22). -- -- The mutual defs compile through `X.brecOn`, and the auto-generated -- `.eq_def` proofs reference `X.brecOn.go` and `X.brecOn.eq` directly. A @@ -774,7 +776,7 @@ end TypeBrecOnForwardDepOrder -- Mutual Prop-valued inductive predicates consumed through the raw mutual -- recursor with explicit motives (the PhiConfluence `Par`/`ParB` shape; --- phiconfluence.ixe check-rs failures of 2026-08-22: `parB_domain`, +-- phiconfluence.ixe failures of 2026-08-22: `parB_domain`, -- `parB_preserves`, `par_to_red`, `WF.par`, `par_triangle`, …). -- -- `induction h using Pb.rec (motive_1 := fun _ _ _ => True)` elaborates to a @@ -995,7 +997,7 @@ end PropRecMotives -- Type-valued nested mutual consumed through the raw recursor with explicit -- motives, including the nested-aux motive (the PhiConfluence -- `Term`/`Binding` shape — `Term.form` nests `Binding` through `List` — and --- its `nf_devel`/`nf_false_reducible` check-rs failures of 2026-08-22). +-- its `nf_devel`/`nf_false_reducible` failures of 2026-08-22). -- `Tm.rec` has three motives: Tm, Bd, and the `List Bd` nested auxiliary; -- a user `induction … using Tm.rec (motive_2 := …) (motive_3 := …)` passes -- all three in source order. Twins in both source orders, as above. diff --git a/Tests/Ix/Compile/PackUnits.lean b/Tests/Ix/Compile/PackUnits.lean index 7e736e1ad..7cd333823 100644 --- a/Tests/Ix/Compile/PackUnits.lean +++ b/Tests/Ix/Compile/PackUnits.lean @@ -1,11 +1,16 @@ -/- +import Ix.Meta +import Ix.EnvScope +import Ix.Ixon +import Tests.Ix.Compile.ClosureHarness + +/-! pack-units: `ix pack` carries the root's reference closure, never its compilation unit (`Ix.Cli.PackCmd`). Source: the selected closure of the fixture `Tests/Ix/Compile/Fixtures/PackUnits.lean`, that is of its own constants (`PackU.*`; `Ix.EnvScope.localConstList`, the `--local` scope). It is - compiled by the Lean pipeline (`compileLeanInput`) and written as an + compiled by the Lean pipeline (`compileClosure`) and written as an `.ixe` from the output's bytes. Each root below is packed as `ix pack` packs it (`Ixon.rsPackEnv`, with metadata). @@ -27,10 +32,6 @@ Run with: `lake test -- --ignored pack-units`. -/ -import Ix.Meta -import Ix.EnvScope -import Ix.Ixon -import Tests.Ix.Compile.ClosureHarness open Lean open Tests.Ix.Compile.ClosureHarness diff --git a/Tests/Ix/Compile/PayloadArrays.lean b/Tests/Ix/Compile/PayloadArrays.lean deleted file mode 100644 index 0c9f6848f..000000000 --- a/Tests/Ix/Compile/PayloadArrays.lean +++ /dev/null @@ -1,75 +0,0 @@ -import LSpec -import Ix.CompileM - -/-! Finite structured-payload controls for production array admission. -The unrepresentable array-size boundary is checked symbolically in proofs; -these are codec/assembly controls, not source-typing certificates. -/ - -namespace Tests.Ix.Compile.PayloadArrays - -open LSpec -open _root_.Ix.CompileM - -private def definition : Ixon.Definition := - { kind := .defn, safety := .safe, lvls := 0, typ := .var 0, value := .var 0 } - -private def inductiveValue (count : Nat) : Ixon.Inductive := { - isUnsafe := false - lvls := 0 - params := 0 - indices := 0 - typ := .var 0 - ctors := Array.replicate count { isUnsafe := false, lvls := 0, cidx := 0, params := 0, fields := 0, typ := .var 0 } } - -private def recursorValue (count : Nat) : Ixon.Recursor := { - k := false - isUnsafe := false - lvls := 0 - params := 0 - indices := 0 - motives := 0 - minors := 0 - typ := .var 0 - rules := Array.replicate count { fields := 0, rhs := .var 0 } } - -private def context : CompileEnv × BlockEnv := - (default, { all := {}, current := _root_.Ix.Name.mkAnon, mutCtx := {}, univCtx := [] }) - -private def raw (action : CompileM α) (state : BlockState := {}) : Except CompileError α × BlockState := - (action context).run state - -private def agrees (members : Array Ixon.MutConst) : Bool := - let expected := buildCompiledMutualBlock context.1.sharingLimits [] members #[] {} - let actual := (raw (finishMutualCompilation [] members #[])).1 - match expected, actual with - | .ok before, .ok after => before.blockBytes == after.blockBytes && before.blockAddr == after.blockAddr && - (match Ixon.deConstant after.blockBytes with - | .ok decoded => Ixon.serConstant decoded == after.blockBytes - | .error _ => false) - | .error first, .error last => toString first == toString last - | _, _ => false - -def controls : List (String × Bool) := Id.run do - let mut result := [] - for count in [0, 1, 31, 32, 127, 128, 256] do - let members := #[Ixon.MutConst.defn definition, .indc (inductiveValue count), .recr (recursorValue count)] - result := result ++ [(s!"nested arrays of size {count} pass admission", (raw (checkMutualPayloadCounts members)).1.isOk)] - result := result ++ [ - ("empty mutual payload is admitted", (raw (checkMutualPayloadCounts #[])).1.isOk), - ("definition collapse retains the pure builder's bytes", agrees #[.defn definition]), - ("recursor collapse retains the pure builder's bytes", agrees #[.recr (recursorValue 2)]), - ("single inductive retains the mutual wrapper's bytes", agrees #[.indc (inductiveValue 2)]), - ("mixed mutual payload retains bytes and round-trip", agrees #[.defn definition, .indc (inductiveValue 3), .recr (recursorValue 3)]), - ("sharing refusal remains the builder's refusal", agrees #[.defn { definition with typ := .share 0 }])] - let initial : BlockState := { - refs := #[default] - univs := #[.zero] - univsFinal := true } - let (accepted, after) := raw (checkMutualPayloadCounts #[.indc (inductiveValue 2), .recr (recursorValue 3)]) initial - result := result ++ [("array admission retains state tables and flag", accepted.isOk && - after.refs == initial.refs && after.univs == initial.univs && after.univsFinal)] - return result - -def suite : List TestSeq := controls.map fun (label, ok) => test s!"payload arrays: {label}" ok - -end Tests.Ix.Compile.PayloadArrays diff --git a/Tests/Ix/Compile/Performance.lean b/Tests/Ix/Compile/Performance.lean deleted file mode 100644 index fa12cc417..000000000 --- a/Tests/Ix/Compile/Performance.lean +++ /dev/null @@ -1,240 +0,0 @@ -import Ix.EnvScope -import Ix.LeanExport -import Ix.Cert.Compile - -/-! Semantic controls for the source-unit and family-check caches. The small -runner constructs its own environments and needs no fixture imports or FFI -test library. `Scripts/Performance/CompilerChecks.lean` shares the builders -for native scale measurements. --/ - -namespace Tests.Ix.Compile.Performance - -private def need (label : String) (condition : Bool) : IO Unit := - unless condition do throw (IO.userError label) - -private def sourceDef (name : Lean.Name) (value : Lean.Expr := .sort .zero) - (all : Option (List Lean.Name) := none) : Lean.Name × Lean.ConstantInfo := - (name, .defnInfo { - name, levelParams := [], type := .sort (.succ .zero), value - hints := .opaque, safety := .safe, all := all.getD [name] }) - -private def sourceAxiom (name : Lean.Name) : Lean.Name × Lean.ConstantInfo := - (name, .axiomInfo { name, levelParams := [], type := .sort (.succ .zero), isUnsafe := false }) - -private def sourceInductive (name : Lean.Name) (all ctors : List Lean.Name) : - Lean.Name × Lean.ConstantInfo := - (name, .inductInfo { - name, levelParams := [], type := .sort (.succ .zero) - numParams := 0, numIndices := 0, all, ctors, numNested := 0, - isRec := false, isUnsafe := false, isReflexive := false }) - -private def sourceConstructor (name owner : Lean.Name) : Lean.Name × Lean.ConstantInfo := - (name, .ctorInfo { - name, levelParams := [], type := .const owner [], induct := owner - cidx := 0, numParams := 0, numFields := 0, isUnsafe := false }) - -structure UnitFixture where - env : Lean.Environment - root : Lean.Name - auxiliaries : Array Lean.Name - dependencies : Array Lean.Name - expected : List Lean.Name - -/-- Every auxiliary has a separate dependency outside its owner's unit. -/ -def unitFixture (count : Nat) : IO UnitFixture := do - let root := `CompilerPerformance.unit - let auxiliaries := (Array.range count).map fun i => root.str s!"eq_{i}" - let dependencies := (Array.range count).map fun i => (`CompilerPerformance).str s!"dependency{i}" - let mut constants := #[sourceDef root, sourceDef `CompilerPerformance.unrelated] - for i in [:count] do - constants := constants.push (sourceDef dependencies[i]!) - constants := constants.push (sourceDef auxiliaries[i]! (.const dependencies[i]! [])) - return { env := ← _root_.Ix.LeanExport.environment constants, root, auxiliaries, dependencies - expected := root :: auxiliaries.toList ++ dependencies.toList } - -private def selectedNames (env : Lean.Environment) (seeds : List Lean.Name) : List Lean.Name := - (_root_.Ix.EnvScope.collectSelectedDeps env seeds).map (·.1) - -private def unitChecks : IO Unit := do - let data ← unitFixture 64 - let reference := (_root_.Ix.EnvScope.collectDeps data.env data.expected).map (·.1) - let actual := selectedNames data.env [data.root] - need "wide unit lost an auxiliary's independent dependency" (actual == reference) - need "wide unit included unrelated declarations" (!actual.contains `CompilerPerformance.unrelated) - need "selected closure is not idempotent" (selectedNames data.env actual == actual) - need "auxiliary seeds changed the selected closure" - (selectedNames data.env data.auxiliaries.toList == actual) - need "seed order changed the selected closure" - (selectedNames data.env data.auxiliaries.toList.reverse == actual) - let index := _root_.Ix.EnvScope.unitIndex data.env.constants - let expectedOrder := data.env.constants.toList.filterMap fun (name, _) => - if data.auxiliaries.contains name then some name else none - need "unit index changed auxiliary order or omitted entries" - (index.size == 1 && index.getD data.root #[] == expectedOrder.toArray) - -- Neither malformed owner's `all` lists the owner itself. Starting from - -- an auxiliary therefore reaches the roots only through unit expansion. - let left := `MalformedUnit.left - let right := `MalformedUnit.right - let first := `MalformedUnit.shared - let leftRoot := `MalformedUnit.leftRoot - let rightRoot := `MalformedUnit.rightRoot - let leftDependency := `MalformedUnit.leftDependency - let rightDependency := `MalformedUnit.rightDependency - let leftAux := left.str "eq_1" - let rightAux := right.str "eq_1" - let malformed ← _root_.Ix.LeanExport.environment #[ - sourceDef left (.sort .zero) (some [first, leftRoot]), - sourceDef right (.sort .zero) (some [first, rightRoot]), - sourceDef leftAux, sourceDef rightAux, sourceAxiom first, - sourceDef leftRoot (.const leftDependency []), - sourceDef rightRoot (.const rightDependency []), - sourceDef leftDependency, sourceDef rightDependency] - let expected := (_root_.Ix.EnvScope.collectDeps malformed - [first, leftRoot, rightRoot, leftDependency, rightDependency, leftAux, rightAux]).map (·.1) - need "malformed owner control does not share a first unit key" - (_root_.Ix.EnvScope.unitKey malformed.constants left == - _root_.Ix.EnvScope.unitKey malformed.constants right) - for seeds in [[leftAux], [rightAux], [leftAux, rightAux], [rightAux, leftAux]] do - let closed := selectedNames malformed seeds - need "owners with one unit key lost distinct root dependencies" (closed == expected) - need "malformed unit closure is not idempotent" (selectedNames malformed closed == closed) - -private def unitKeyChecks : IO Unit := do - let root := `UnitKeys.root - let ctor := `UnitKeys.root.ctor - let empty := `UnitKeys.empty - let constants := #[ - sourceInductive root [root] [ctor], sourceConstructor ctor root, - sourceInductive empty [] [], sourceConstructor `UnitKeys.emptyCtor empty, - sourceInductive `UnitKeys.malformed [`UnitKeys.missing, root] [], - sourceConstructor `UnitKeys.malformedCtor `UnitKeys.malformed, - sourceConstructor `UnitKeys.missingOwnerCtor `UnitKeys.missing, - sourceConstructor `UnitKeys.wrongOwnerCtor `UnitKeys.ordinary, - sourceDef `UnitKeys.ordinary, - sourceDef `UnitKeys.emptyDefinition (.sort .zero) (some []), - sourceAxiom `UnitKeys.axiom, - (`UnitKeys.theorem, Lean.ConstantInfo.thmInfo { - name := `UnitKeys.theorem, levelParams := [], type := .sort .zero, - value := .sort .zero, all := [] }), - (`UnitKeys.opaque, Lean.ConstantInfo.opaqueInfo { - name := `UnitKeys.opaque, levelParams := [], type := .sort .zero, - value := .sort .zero, isUnsafe := false, all := [] })] - let env ← _root_.Ix.LeanExport.environment constants - for name in `UnitKeys.absent :: (constants.map (·.1)).toList do - need s!"unitKey differs from the first root for {name}" - (_root_.Ix.EnvScope.unitKey env.constants name == - (_root_.Ix.EnvScope.unitRoots env.constants name).head?.getD name) - -structure FamilyFixture where - env : _root_.Ix.Environment - blocks : _root_.Ix.CondensedBlocks - root : _root_.Ix.Name - constructors : Array _root_.Ix.Name - members : _root_.Ix.Set _root_.Ix.Name - -def familyFixture (count : Nat) : FamilyFixture := Id.run do - let root := _root_.Ix.Name.fromLeanName `CompilerPerformance.Enum - let constructors := (Array.range count).map (_root_.Ix.Name.mkNat root ·) - let family : _root_.Ix.ConstantInfo := .inductInfo { - cnst := { name := root, levelParams := #[], type := .mkSort (.mkSucc .mkZero) } - numParams := 0, numIndices := 0, all := #[root], ctors := constructors, - numNested := 0, isRec := false, isUnsafe := false, isReflexive := false } - let mut env : _root_.Ix.Environment := { consts := ({} : _root_.Ix.Map _ _).insert root family } - let mut members : _root_.Ix.Set _root_.Ix.Name := ({} : _root_.Ix.Set _).insert root - for i in [:count] do - let name := constructors[i]! - let ctor : _root_.Ix.ConstantInfo := .ctorInfo { - cnst := { name, levelParams := #[], type := .mkConst root #[] } - induct := root, cidx := i, numParams := 0, numFields := 0, isUnsafe := false } - env := { env with consts := env.consts.insert name ctor } - members := members.insert name - return { - env, root, constructors, members - blocks := { - lowLinks := members.fold (fun map name => map.insert name root) {} - blocks := ({} : _root_.Ix.Map _ _).insert root members - blockRefs := ({} : _root_.Ix.Map _ _).insert root {} } } - -/-- Deliberately call the unchanged per-member helper. Calling -`strayFamilyMemberWith` here would compare its csimp replacement to itself. -/ -def familyReference (env : _root_.Ix.Environment) (blocks : _root_.Ix.CondensedBlocks) - (recs : Std.HashMap Address (Array _root_.Ix.Name)) : Option (_root_.Ix.Name × _root_.Ix.Name) := - blocks.blocks.toList.findSome? fun (rep, all) => - (all.toList.find? fun name => !_root_.Ix.Cert.familyBlockOk env recs all name).map (rep, ·) - -private def checkFind (label : String) (env : _root_.Ix.Environment) - (members : _root_.Ix.Set _root_.Ix.Name) (names : List _root_.Ix.Name) - (expected : Option _root_.Ix.Name) : IO Unit := do - let reference := names.find? fun name => !_root_.Ix.Cert.familyBlockOk env {} members name - let cached := (_root_.Ix.Cert.familyBlockFindCached env {} members names {}).val - -- Ix.Name's BEq uses stored hashes. These comparisons must also distinguish - -- the spelling of a colliding original member from its owning cache key. - need s!"{label}: reference fixture has the wrong first failure" (decide (reference = expected)) - need s!"{label}: cache changed the exact first failure" (decide (cached = reference)) - -private def familyChecks : IO Unit := do - for count in [0, 1] do - let small := familyFixture count - need "singleton family fast path changed the reference result" - (decide (_root_.Ix.Cert.strayFamilyMemberWithCached small.env small.blocks {} = - familyReference small.env small.blocks {})) - let data := familyFixture 64 - checkFind "wide enum" data.env data.members (data.root :: data.constructors.toList) none - need "wide enum wrapper was rejected" - ((_root_.Ix.Cert.strayFamilyMemberWithCached data.env data.blocks {}).isNone) - let first := data.constructors[0]! - let second := data.constructors[1]! - let last := data.constructors.back! - let ordinary := _root_.Ix.Name.fromLeanName `CompilerPerformance.ordinary - let missing := _root_.Ix.Name.fromLeanName `CompilerPerformance.missing - let ordinaryInfo : _root_.Ix.ConstantInfo := .axiomInfo { - cnst := { name := ordinary, levelParams := #[], type := .mkSort .mkZero }, isUnsafe := false } - let env := { data.env with consts := data.env.consts.insert ordinary ordinaryInfo } - let changeOwner (env : _root_.Ix.Environment) (name owner : _root_.Ix.Name) := - match env.consts.get? name with - | some (.ctorInfo ctor) => { env with consts := env.consts.insert name (.ctorInfo { ctor with induct := owner }) } - | _ => env - let broken := changeOwner (changeOwner env first missing) second ordinary - checkFind "missing constructor owner after a warm family hit" broken data.members - [data.root, first, second] (some first) - checkFind "wrong-kind constructor owner is the first failure" broken data.members - [data.root, second, first] (some second) - let recursor := _root_.Ix.Name.mkStr data.root "rec" - let recursorInfo : _root_.Ix.ConstantInfo := .recInfo { - cnst := { name := recursor, levelParams := #[], type := .mkSort .mkZero } - all := #[data.root], numParams := 0, numIndices := 0, numMotives := 1 - numMinors := data.constructors.size, rules := #[], k := false, isUnsafe := false } - let withRecursor := { env with consts := env.consts.insert recursor recursorInfo } - checkFind "a recursor must not reuse its inductive's family-block answer" withRecursor data.members - [data.root, recursor] (some recursor) - let missingMember := data.members.erase last - checkFind "missing family member reports the original constructor" env missingMember - [ordinary, first, data.root] (some first) - checkFind "extra family member" env (data.members.insert ordinary) - [ordinary, second, first] (some second) - let tampered := match env.consts.get? data.root with - | some (.inductInfo family) => - let changed := _root_.Ix.ConstantInfo.inductInfo { family with ctors := family.ctors.pop } - { env with consts := env.consts.insert data.root changed } - | _ => env - checkFind "tampered source family list" tampered data.members [ordinary, first, data.root] (some first) - let aliasName : _root_.Ix.Name := .str .mkAnon "colliding-constructor" first.getHash - need "name-hash collision fixture is not structurally distinct" - (aliasName == first && !decide (aliasName = first)) - checkFind "hash alias keeps its exact failing spelling" env missingMember - [ordinary, aliasName, first, data.root] (some aliasName) - checkFind "hash alias preserves the reference membership semantics" env - ((data.members.erase first).insert aliasName) [aliasName, first, data.root] none - for source in [env, broken, tampered] do - let reference := familyReference source data.blocks {} - let cached := _root_.Ix.Cert.strayFamilyMemberWithCached source data.blocks {} - need "block wrapper changed its first failing member" (decide (cached = reference)) - -def run : IO Unit := do - unitChecks - unitKeyChecks - familyChecks - IO.println "Compiler unit and family cache regressions PASS" - -end Tests.Ix.Compile.Performance diff --git a/Tests/Ix/Compile/ScheduleIdentity.lean b/Tests/Ix/Compile/ScheduleIdentity.lean deleted file mode 100644 index ea12b36c5..000000000 --- a/Tests/Ix/Compile/ScheduleIdentity.lean +++ /dev/null @@ -1,239 +0,0 @@ -/- - compile-schedule-identity: compare the pure fold and streamed Lean - pipeline under different schedules on the same fixture corpus. - - Over the fixture corpus (`validateAuxClosure`: `Tests.Ix.Compile.Mutual`, - `Canonicity`, `LevelSpellings`, the IxVM and `Test.Ix.Fixtures` - families), compile with - - - the driver, the pure wave fold `Ix.CompileM.compileEnvFold`, on the - canonical environment and condensation of `rsCompilePhasesOf`, - at its default width and at widths 1 and - 32 (tasks per wave; the value must not depend on it); - - the reference schedule (`Tests.Ix.Compile.ScheduleReference.sequential`, - a sequential driver: live state, one block at a - time) on the same input, - with the least and with the greatest ready key first; - - the streamed `compileLeanInput` pipeline through - `ClosureHarness.compileClosure`, with its own canonicalization and - condensation, at 1, 4 and 16 workers; - - and require every serialized environment and decline report to be identical. A - difference is reported with the constant and its two addresses. - These comparisons cover raw compiler artifacts before CLI finalization. - - Failures: no block fails under any schedule, and every source name is in - the output. - - The legs run concurrently (each is an independent compile of the same - read-only input; `SCHED_JOBS=` runs at most n at a time), and are - reported and compared in the order above once all have finished. - - Tiers: the default (`SCHED_LEGS=full`) is the integration gate, all eight - legs. `SCHED_LEGS=quick` is the per-iteration tier: `fold` (the - reference), `reference, least key first` and the streamed pipeline at 16 workers, - with every check below; it is never the integration gate. - `SCHED_OUTPUT_DIR=` writes each leg's bytes and failures, for - diagnosis. - - Run with: `lake exe IxTests --ignored compile-schedule-identity`. --/ -import Tests.Ix.Compile.ClosureHarness -import Tests.Ix.Compile.ScheduleReference -import Tests.Ix.Compile.ValidateAux -import Tests.Ix.IxVM -import Tests.Ix.Fixtures - -open Lean - -namespace Tests.Ix.Compile.ScheduleIdentity - -/-- The per-name differences of two environments: (name, address in `a`, - address in `b`), names missing on one side included. -/ -def namedDiffs (a b : Ixon.Env) : Array (String × String × String) := Id.run do - let mut out := #[] - for (n, x) in a.named do - match b.named.get? n with - | some y => if x.addr != y.addr then out := out.push (toString n, toString x.addr, toString y.addr) - | none => out := out.push (toString n, toString x.addr, "-") - for (n, y) in b.named do - if !a.named.contains n then out := out.push (toString n, "-", toString y.addr) - return out - -/-- Print a line with the monotonic clock in seconds and flush, so a long - or stuck leg is visible in the log while it runs. -/ -def say (s : String) : IO Unit := do - IO.println s!"{s} (t={(← IO.monoMsNow) / 1000}s)" - (← IO.getStdout).flush - -/-- Worker counts for the streamed pipeline legs. -/ -def pipelineWorkers : List Nat := [1, 4, 16] - -/-- Optional diagnostic artifact capture (`SCHED_OUTPUT_DIR`). -/ -def saveRun (label : String) (bytes : ByteArray) (failures : List (String × String)) : - IO Unit := do - if let some dir ← IO.getEnv "SCHED_OUTPUT_DIR" then - IO.FS.createDirAll dir - let path := s!"{dir}/{label}.ixe" - IO.FS.writeBinFile path bytes - let sorted := failures.toArray.qsort fun a b => a.1 < b.1 || (a.1 == b.1 && a.2 < b.2) - let lines := sorted.toList.map fun (name, cause) => - (Json.mkObj [("name", toJson name), ("cause", toJson cause)]).compress - IO.FS.writeFile s!"{path}.failures.jsonl" (String.intercalate "\n" lines ++ "\n") - say s!"[schedule] saved {path}: {Address.blake3 bytes}" - -/-- One leg's result: (label, bytes, environment, block failures as (name, message)). -/ -abbrev Declines := Array (String × String) - -/-- There is no separate decline list; compilation failures are reported - through `CompileEnv.ungrounded`. -/ -def declinesOf (_cenv : Ix.CompileM.CompileEnv) : Declines := #[] - -abbrev LegResult := String × ByteArray × Ixon.Env × List (String × String) × Declines - -/-- A leg: its label in the log, its file label for `saveRun`, and the compile. -/ -structure Leg where - label : String - saveLabel : String - act : IO (ByteArray × Ixon.Env × List (String × String) × Declines) - -/-- The leg tier: `SCHED_LEGS=quick` (three legs) or `full` (the default, the - integration tier). -/ -def quickTier : IO Bool := do - match ← IO.getEnv "SCHED_LEGS" with - | none | some "full" => pure false - | some "quick" => pure true - | some other => throw (IO.userError s!"SCHED_LEGS={other}: expected full or quick") - -/-- The legs, in the order the log reports them; the first is the reference. -/ -def legsOf (env : Environment) (closure : List (Name × ConstantInfo)) - (phases : Ix.CompileM.CompilePhases) : - IO (Array Leg) := do - let quick ← quickTier - let failuresOf (cenv : Ix.CompileM.CompileEnv) : List (String × String) := - cenv.ungrounded.toList.map fun (n, e) => (n.pretty, e) - let outcome (label : String) - (result : Except String (Ixon.Env × Nat × Ix.CompileM.CompileEnv)) : - IO (ByteArray × Ixon.Env × List (String × String) × Declines) := do - match result with - | .error e => throw (IO.userError s!"[schedule] {label}: {e}") - | .ok (ixon, _, cenv) => - let bytes ← IO.ofExcept (Ixon.serEnv ixon) - pure (bytes, ixon, failuresOf cenv, declinesOf cenv) - let mut legs : Array Leg := #[] - -- the driver, at its default width (the reference) and at widths 1 and 32 - for width in (if quick then [none] else [none, some 1, some 32]) do - let label := match width with | none => "fold" | some w => s!"fold, width {w}" - let saveLabel := match width with | none => "fold" | some w => s!"fold-{w}" - legs := legs.push { label, saveLabel, act := do - -- `IO.lazyPure`: the pure driver is evaluated inside the action, so the - -- legs run concurrently - outcome label (← IO.lazyPure fun _ => match width with - | none => Ix.CompileM.compileEnvFold phases.rawEnv phases.condensed - | some w => Ix.CompileM.compileEnvFold phases.rawEnv phases.condensed - (width := w)) } - -- the reference schedule, least and greatest ready key first - for least in (if quick then [true] else [true, false]) do - let label := s!"reference, {if least then "least" else "greatest"} key first" - let saveLabel := s!"reference-{if least then "least" else "greatest"}" - legs := legs.push { label, saveLabel, act := do - outcome label (← IO.lazyPure fun _ => - ScheduleReference.sequential phases.rawEnv phases.condensed - (least := least)) } - -- the streamed pipeline through the shared closure harness - for k in (if quick then [16] else pipelineWorkers) do - legs := legs.push { label := s!"compile-lean --workers {k}", saveLabel := s!"pipeline-{k}", - act := do - let out ← ClosureHarness.compileClosure env closure k - pure (out.bytes, out.env, failuresOf out.cenv, declinesOf out.cenv) } - return legs - -/-- Run the legs, at most `SCHED_JOBS` at a time (default: all at once; each - leg is an independent compile of the same read-only input), and return - their results in leg order. -/ -def runLegs (legs : Array Leg) : IO (Array LegResult) := do - let jobs := ((← IO.getEnv "SCHED_JOBS").bind String.toNat?).getD 0 - let jobs := if jobs == 0 then legs.size else jobs - let mut done : Array (ByteArray × Ixon.Env × List (String × String) × Declines) := #[] - let mut pending : Array (Task (Except IO.Error (ByteArray × Ixon.Env × List (String × String) × Declines))) := #[] - for leg in legs do - if pending.size ≥ jobs then - done := done.push (← IO.ofExcept (← IO.wait pending[0]!)) - pending := pending.extract 1 pending.size - pending := pending.push (← IO.asTask (prio := .dedicated) leg.act) - for t in pending do done := done.push (← IO.ofExcept (← IO.wait t)) - return (legs.zip done).map fun (leg, (bytes, ixon, fails, declines)) => (leg.label, bytes, ixon, fails, declines) - -/-- Every schedule's result, compared. Returns the number of failures. -/ -def compareLegs (closure : List (Name × ConstantInfo)) (legs : Array Leg) - (runs : Array LegResult) : IO Nat := do - if ← quickTier then - say "[schedule] SCHED_LEGS=quick: the per-iteration tier (3 legs); not the integration gate" - for (leg, (_, bytes, _, fails, declines)) in legs.zip runs do - saveRun leg.saveLabel bytes fails - say s!"[schedule] {leg.label}: {bytes.size} bytes, {fails.length} block failures, {declines.size} declines" - let some (refLabel, refBytes, refEnv, refFails, refDeclines) := runs[0]? | return 1 - let mut failures := 0 - -- Failures: no block fails under any schedule, every source name is in the - -- output, and the (name, message) list of failures is the same in every - -- schedule. - let sortFails (xs : List (String × String)) : Array (String × String) := - xs.toArray.qsort fun a b => a.1 < b.1 || (a.1 == b.1 && a.2 < b.2) - for (label, _, ixon, fails, declines) in runs do - let mut bad := 0 - for (n, e) in fails do - say s!"[schedule] {label}: block failure: {n}: {(e.replace "\n" " ").take 200}" - bad := bad + 1 - let missing := closure.filter fun (name, _) => - !ixon.named.contains (_root_.Ix.Name.fromLeanName name) - for (name, _) in missing do - say s!"[schedule] {label}: output omitted source name {name}" - if bad + missing.length == 0 then - say s!"[schedule] {label}: no block failure, every source name present" - else - failures := failures + 1 - say s!"[schedule] {label}: {bad + missing.length} violation(s)" - if sortFails fails != sortFails refFails then - failures := failures + 1 - say s!"[schedule] {label}: block failures (names or messages) differ from {refLabel}" - if declines != refDeclines then - failures := failures + 1 - say s!"[schedule] {label}: decline reports differ from {refLabel}" - for (label, bytes, ixon, _, _) in runs[1:] do - if bytes == refBytes then - say s!"[schedule] {label} = {refLabel}" - else - failures := failures + 1 - let ds := namedDiffs refEnv ixon - say s!"[schedule] {label} ≠ {refLabel}: {bytes.size} vs {refBytes.size} bytes, \ -{ds.size} names differ" - for (n, x, y) in ds[:20] do - say s!"[schedule] {n}: {refLabel} {x}, {label} {y}" - say s!"[schedule] tables: constants {refEnv.consts.size}/{ixon.consts.size}, \ -names {refEnv.names.size}/{ixon.names.size}, blobs {refEnv.blobs.size}/{ixon.blobs.size}" - let mut metaDiffs := 0 - for (n, x) in refEnv.named do - if let some y := ixon.named.get? n then - if x != y then - metaDiffs := metaDiffs + 1 - say s!"[schedule] Named differs {n}: addr={x.addr != y.addr} \ -meta={x.constMeta != y.constMeta} original={x.original != y.original} hints={x.hints != y.hints}" - say s!"[schedule] Named differences: {metaDiffs}" - return failures - -def run : IO UInt32 := do - -- this module's imports (the fixture corpus) - let env ← getCompileEnv (this_file!) - let closure := validateAuxClosure env - say s!"[schedule] {closure.length} constants (fixture corpus)" - -- the shared fold input: canonical environment and condensation from Rust - let phases ← Ix.CompileM.rsCompilePhasesOf closure - -- Every leg is an independent compile of the same input: run them - -- concurrently (`SCHED_JOBS` bounds it), then compare in leg order. - let legs ← legsOf env closure phases - let results ← runLegs legs - let failures ← compareLegs closure legs results - say s!"[schedule] {if failures == 0 then "PASS" else s!"FAIL ({failures})"}" - return if failures == 0 then 0 else 1 - -end Tests.Ix.Compile.ScheduleIdentity diff --git a/Tests/Ix/Compile/ScheduleReference.lean b/Tests/Ix/Compile/ScheduleReference.lean deleted file mode 100644 index cba7713da..000000000 --- a/Tests/Ix/Compile/ScheduleReference.lean +++ /dev/null @@ -1,113 +0,0 @@ -/- - A second schedule of the Lean compiler, for the tests only: a sequential - driver that compiles one block at a time against the live state, taking - the ready block with the least canonical key first (or, with - `least := false`, the greatest). It is the driver of no compile: - `compile-schedule-identity`, `compile-claim-conflict` and - `compile-driver-input` compare the fold (`compileEnvFold`, which computes a - wave's blocks against the wave's snapshot) with it, so a schedule-dependent - value shows as a byte difference. --/ -import Ix.CompileDriver - -public section - -namespace Tests.Ix.Compile.ScheduleReference - -open _root_.Ix _root_.Ix.CompileM - -/-- Compile an entire environment's condensation one block at a time, each - against the live state, in least-key-first (or greatest-key-first) - topological order of the block graph. An input inductive group with - non-canonical flags, or a mutual block of mixed safety, fails the compile - first, as in the fold. - - Per-block failures, and conflicting claims or payloads, are recorded in - `ungrounded` per member and the scheduler continues (dependents cascade - into `MissingConstant` failures recorded the same way), as Rust's - scheduler does. -/ -def sequential (env : Ix.Environment) (blocks : Ix.CondensedBlocks) - (dbg : Bool := false) - (sharingLimits : Ix.Sharing.Exact.Limits := compilerSharingLimits) - (least : Bool := true) - : Except String (Ixon.Env × Nat × CompileEnv) := Id.run do - if let some msg := missingBlockRefs? blocks then return .error msg - if let .error e := validateInductiveFlags env then return .error e - if let .error e := validateMutualSafety env blocks then return .error e - let mut acc : DriverAcc := { cenv := { CompileEnv.new env with sharingLimits } } - - let totalBlocks := blocks.blocks.size - let mut blockInfo : Std.HashMap Name (Set Name × Nat) := {} - let mut reverseDeps : Std.HashMap Name (Array Name) := {} - for (lo, all) in blocks.blocks do - let deps := match blocks.blockRefs.get? lo with - | some d => d - | none => {} - blockInfo := blockInfo.insert lo (all, deps.size) - for depName in deps do - reverseDeps := reverseDeps.alter depName fun - | some arr => some (arr.push lo) - | none => some #[lo] - -- The ready blocks keyed by their canonical key (`blockKey`), the least - -- (or the greatest) taken first: a function of the graph and the keys - -- alone. - let mut readyQueue : Std.TreeMap Name (Set Name) Ix.nameCompare := {} - for (lo, (all, depCount)) in blockInfo do - if depCount == 0 then - readyQueue := readyQueue.insert (blockKey lo all) all - - let mut blocksCompleted : Nat := 0 - let mut lastPct : Nat := 0 - -- Deps already satisfied (released): a name is released at most once per - -- dependent, as Rust's `remaining.remove`. - let mut released : Set Name := {} - - while !readyQueue.isEmpty do - -- `lo` is the block's canonical key from here on. - let some (lo, all) := (if least then readyQueue.minEntry? else readyQueue.maxEntry?) | break - readyQueue := readyQueue.erase lo - let fail (acc : DriverAcc) (e : CompileError) : DriverAcc := Id.run do - -- Soft failure: record per member; the scheduler keeps running and - -- dependents cascade. - let msg := toString e - let mut acc := acc - for m in all do - acc := { acc with cenv := { acc.cenv with - ungrounded := acc.cenv.ungrounded.insert m msg } } - return acc - match runBlock acc.cenv all lo with - | .error e => acc := fail acc e - | .ok (result, cache) => - -- A conflicting claim is a failure of this block (Rust raises it - -- inside `compile_const`): nothing of it is merged. - match checkCompiledBlock acc.cenv lo result cache with - | .error e => acc := fail acc e - | .ok () => acc := mergeCompiledBlock acc lo result cache - - -- Release dependents: the block's members. - for name in all do - if released.contains name then - continue - released := released.insert name - if let some dependents := reverseDeps.get? name then - for dependentLo in dependents do - if let some (depAll, depCount) := blockInfo.get? dependentLo then - let newCount := depCount - 1 - blockInfo := blockInfo.insert dependentLo (depAll, newCount) - if newCount == 0 then - readyQueue := readyQueue.insert (blockKey dependentLo depAll) depAll - - blocksCompleted := blocksCompleted + 1 - if dbg then - let pct := (blocksCompleted * 100) / totalBlocks - if pct >= lastPct + 10 then - dbg_trace s!" [schedule-reference] {pct}% ({blocksCompleted}/{totalBlocks})" - lastPct := pct - - if blocksCompleted != totalBlocks then - return .error s!"Only compiled {blocksCompleted}/{totalBlocks} blocks \ -- circular dependency?" - - return finalizeEnv acc - -end Tests.Ix.Compile.ScheduleReference diff --git a/Tests/Ix/Compile/SelectedClosure.lean b/Tests/Ix/Compile/SelectedClosure.lean index d441d7227..5cb190e24 100644 --- a/Tests/Ix/Compile/SelectedClosure.lean +++ b/Tests/Ix/Compile/SelectedClosure.lean @@ -1,4 +1,11 @@ -/- +import LSpec +import Ix.Meta +import Ix.EnvScope +import Tests.Ix.Compile.ClosureHarness +import IxC.Kernel.CoreDefs +import IxC.Kernel.Roles + +/-! compiler-selected-closure: what a selected closure (`Ix.EnvScope.collectSelectedDeps`, `ClosureOptions.selected`) carries beyond the references of its declarations. No compile. @@ -46,21 +53,13 @@ - The donor's `introducedSupport` (LC-15b) and Pass 3 harness are gone: the e2e runner compiles through the ordinary compiler (`ClosureHarness`). - - The e2e runner keeps the certified leg only (no `check-rs`/ - `check-lean`) and writes to a temporary directory, not + - The e2e runner keeps the certified leg only and writes to a temporary directory, not `out/codex-a7/…` (R11-24). Run with: `lake test -- compiler-selected-closure` and `lake test -- --ignored compiler-selected-closure-e2e` (after `lake build kernel-check-ixe`). -/ -import LSpec -import Ix.Meta -import Ix.EnvScope -import Tests.Ix.Compile.AuxiliaryClosure -import Tests.Ix.Compile.ClosureHarness -import IxC.Kernel.CoreDefs -import IxC.Kernel.Roles open LSpec Lean open Tests.Ix.Compile.ClosureHarness @@ -73,6 +72,41 @@ def matcher (n : Nat) : Nat := n theorem matcher._arg_pusher : matcher 0 = 0 := rfl theorem carriedProof : matcher 0 = 0 := matcher._arg_pusher +mutual +def evenN : Nat → Bool + | 0 => true + | n + 1 => oddN n +def oddN : Nat → Bool + | 0 => false + | n + 1 => evenN n +end + +mutual +def wfEven : Nat → Bool + | 0 => true + | n + 1 => wfOdd n +termination_by n => n +def wfOdd : Nat → Bool + | 0 => false + | n + 1 => wfEven n +termination_by n => n +end + +mutual +partial def pEven : Nat → Bool + | 0 => true + | n + 1 => pOdd n +partial def pOdd : Nat → Bool + | 0 => false + | n + 1 => pEven n +end + +theorem oddN_one : oddN 1 = true := rfl + +def mutualSeeds : List (Lean.Name × Lean.Name) := [ + (``oddN, ``evenN), (``wfOdd, ``wfEven), (``pOdd, ``pEven), (``oddN_one, ``evenN) +] + /-- A user of `Nat.land`, whose certified install needs `Nat.mul`'s record (`natOpDeps Nat.land`), which `Nat.land`'s value does not reference. -/ def landUser (a b : Nat) : Nat := Nat.land a b @@ -185,7 +219,7 @@ def suite : List TestSeq := [ errors := errors.push "the bare matcher pulled its caller carriedProof" checked := checked + 1 -- 3. mutual siblings - for (root, sibling) in Tests.Ix.Compile.AuxiliaryClosure.mutualSeeds do + for (root, sibling) in mutualSeeds do checked := checked + 1 unless (nameSet (Ix.EnvScope.collectSelectedDeps env [root])).contains sibling do errors := errors.push s!"{root}: missing its sibling {sibling}" @@ -259,9 +293,6 @@ def run : IO UInt32 := do let out ← compileClosure env closure unless out.cenv.ungrounded.isEmpty do errors := errors.push s!"{label}: {out.cenv.ungrounded.size} refusals" - -- the Rust compiler on the same closure - let rust ← rustCheck label env closure out (dir / s!"rust-{i}.ixe") - if let some e := rust then errors := errors.push e -- closure against whole for (name, named) in out.env.named do match whole.env.named[name]? with @@ -283,7 +314,7 @@ def run : IO UInt32 := do unless v.outcome == "accept" do errors := errors.push s!"{label}: {seed}'s owning record: {v.outcome}: {v.reason}" IO.println s!"[selected-closure-e2e] {label}: closure {closure.length}, {out.bytes.size} B; \ - {summary}; Rust {if rust.isNone then "byte-identical" else "DIFFERENT"}" + {summary}" for e in errors do IO.println s!"[selected-closure-e2e] FAIL {e}" IO.println s!"[selected-closure-e2e] {errors.size} failure(s)" if errors.isEmpty then IO.FS.removeDirAll dir diff --git a/Tests/Ix/Compile/SourceAccounting.lean b/Tests/Ix/Compile/SourceAccounting.lean deleted file mode 100644 index 83b0cacae..000000000 --- a/Tests/Ix/Compile/SourceAccounting.lean +++ /dev/null @@ -1,150 +0,0 @@ -import Ix.Compile.SourceGraph.Accounting -import Ix.CompileResource -import Ix.Cert.Compile -import Ix.Meta -import Ix.EnvScope -import Tests.Ix.Compile.GroundRefusal -import Tests.Ix.Compile.ClaimConflict -import Tests.Ix.Kernel.TierIndDefs -import Tests.Ix.ContractPipeline - -namespace Tests.Ix.Compile.SourceAccounting -open _root_.Ix (Name) -open _root_.Ix.Compile.SourceGraph.Accounting - -run_cmd do - let env ← Lean.getEnv - for (caller, callee) in #[(`Ix.CompileM.resourcePostStep, `Ix.Compile.SourceGraph.Accounting.check), - (`Ix.CompileM.compileLeanInputValidated, `Ix.CompileM.resourcePostStep), - (`Ix.CompileM.compileLeanConstsValidated, `Ix.CompileM.resourcePostStep)] do - let some (.defnInfo d) := env.find? caller | throwError "caller unavailable: {caller}" - unless d.value.getUsedConstantsAsSet.contains callee do throwError "{caller} no longer calls {callee}" - -def ordinary : Nat := 7 -private def need (ok : Bool) (why : String) : IO Unit := unless ok do throw (IO.userError why) -private def postError (out : _root_.Ix.CompileM.LeanPipelineOut) (want : String) : IO Unit := - need (match _root_.Ix.CompileM.resourcePostStep out with | .error got => got == want | .ok _ => false) - s!"post-step did not preserve expected error: {want}" -private def refuses (want : Issue) : Except Issue Unit → Bool - | .error got => decide (got = want) - | .ok _ => false -private def facts (out : _root_.Ix.CompileM.LeanPipelineOut) : Except String (_root_.Ix.Map Name _root_.Ix.Compile.SourceGraph.Node) := - match out.sourceNodes? with - | none => .error "source facts unavailable" - | some nodes => .ok nodes -private def compile (env : Lean.Environment) (raw : List (Lean.Name × Lean.ConstantInfo)) (workers : Nat := 1) : - IO _root_.Ix.CompileM.LeanPipelineOut := do - let input ← IO.ofExcept ((_root_.Ix.Compile.compileInputFromEnv env raw).mapError toString) - IO.ofExcept (← _root_.Ix.CompileM.compileLeanInput input (numWorkers := workers)) - -private def audit (label : String) (out : _root_.Ix.CompileM.LeanPipelineOut) : IO Nat := do - let nodes ← IO.ofExcept (facts out) - let named := out.env.named - let failed := out.cenv.ungrounded - let report := scan nodes named failed - let both := report.uncovered.filter failed.contains - let neither := report.uncovered.filter (!failed.contains ·) - let (publishedOnly, failedOnly, regenerated) := nodes.fold (init := (0, 0, 0)) fun (p, f, r) n _ => - (p + if named.contains n && !failed.contains n then 1 else 0, - f + if failed.contains n && !named.contains n then 1 else 0, - r + if (named.get? n).any (·.original.isSome) then 1 else 0) - IO.println s!"accounting {label}: input={nodes.size} published-only={publishedOnly} failed-only={failedOnly} both={both.size} neither={neither.size} outside={report.outside.size} regenerated-original={regenerated}" - for n in report.uncovered ++ report.outside do - IO.println s!" anomaly {n.pretty}: input={nodes.contains n} published={named.contains n} failure={repr (failed.get? n)}" - need (check nodes named failed |>.isOk) s!"genuine final producer accounting anomaly: {label}" - IO.ofExcept (_root_.Ix.CompileM.resourcePostStep out) - return regenerated - -private def mutations (out partialOut : _root_.Ix.CompileM.LeanPipelineOut) : IO Unit := do - let nodes ← IO.ofExcept (facts out) - let named := out.env.named - let failed := out.cenv.ungrounded - let some n := nodes.toList.findSome? (fun (n, _) => - if named.contains n && !failed.contains n then some n else none) - | throw (IO.userError "mutation requires a published original") - need (refuses (.neither n) (check nodes (named.erase n) failed)) "missing-publication mutation escaped" - postError { out with env := { out.env with named := named.erase n } } - s!"source accounting: '{n.pretty}' is neither published nor recorded as failed" - need (refuses (.both n) (check nodes named (failed.insert n "planted failure"))) "both mutation escaped" - postError { out with cenv := { out.cenv with ungrounded := failed.insert n "planted failure" } } - s!"source accounting: '{n.pretty}' is both published and recorded as failed" - let outside := Name.fromLeanName `Accounting.absent - need (!nodes.contains outside) "outside mutation is not outside the input" - need (refuses (.outside outside) (check nodes named (failed.insert outside "planted outsider"))) "outside failure escaped" - postError { out with cenv := { out.cenv with ungrounded := failed.insert outside "planted outsider" } } - s!"source accounting: failure name '{outside.pretty}' is outside the original input" - need ((check nodes (named.insert outside { addr := default }) failed).isOk) "generated non-input publication rejected" - IO.ofExcept (_root_.Ix.CompileM.resourcePostStep { out with env := { out.env with named := named.insert outside { addr := default } } }) - -- A retained error under a structural alias stays in the original key - -- class. This accounting control makes no grounding-history claim. - let aliasName : Name := .str .mkAnon "retained-error" n.getHash - need (aliasName == n && !Name.beqRaw aliasName n) "alias control is not structurally distinct" - let one := nodes.filter (fun k _ => k == n) - let oldError : _root_.Ix.Map Name String := .ofList [(aliasName, "earlier error")] - need (one.size == 1 && (check one {} oldError).isOk && refuses (.both n) (check one named oldError)) - "BEq alias failure was lost, treated as outside, or exempted from XOR" - let partialNodes ← IO.ofExcept (facts partialOut) - let some (bad, _) := partialNodes.toList.find? (fun (n, _) => partialOut.cenv.ungrounded.contains n) - | throw (IO.userError "partial mutation requires an actual failure") - need (!partialOut.env.named.contains bad) "partial negative starts already both" - need (refuses (.both bad) (check partialNodes (partialOut.env.named.insert bad { addr := default }) partialOut.cenv.ungrounded)) - "failed-only to both mutation escaped" - postError { partialOut with env := { partialOut.env with named := partialOut.env.named.insert bad { addr := default } } } - s!"source accounting: '{bad.pretty}' is both published and recorded as failed" - postError { partialOut with annotated := true, sourceNodes? := none } - "resource compilation requires the complete requested closure" - let f : _root_.Ix.Cert.Failures := - { ungrounded := .ofList [(n, "ground")], blocks := .ofList [(n, "block"), (outside, "other")] } - need (f.toMap.get? n == some "ground" && [n, outside].all (fun k => f.toMap.contains k == f.failed k)) - "merged membership/diagnostic precedence control failed" - IO.println "accounting mutations: neither/both/outside/failed-only-to-both rejected; generated publication allowed; BEq classes and overlapping report messages preserved" - -def run : IO Unit := do - let env ← getCompileEnv #[`Tests.Ix.Compile.SourceAccounting] - let empty ← compile env [] - let _ ← audit "empty" empty - need ((facts empty).isOk && !(facts { empty with sourceNodes? := none }).isOk) "unavailable facts became empty" - postError { empty with sourceNodes? := none } "source accounting: original input facts are unavailable" - let tiny : Lean.ConstantInfo := .axiomInfo { name := `Accounting.tiny, levelParams := [], type := .sort .zero, isUnsafe := false } - for xs in [[], [(tiny.name, tiny)]] do - let bare ← compile env xs - let input ← IO.ofExcept (← _root_.Ix.CompileM.compileLeanInputValidated (.plain xs) (numWorkers := 1)) - let consts ← IO.ofExcept (← _root_.Ix.CompileM.compileLeanConstsValidated xs (numWorkers := 1)) - need (input.bytes == bare.bytes && consts.bytes == bare.bytes && input.sourceNodes?.isSome && consts.sourceNodes?.isSome) - "validated wrapper changed successful bytes or discarded facts" - let raw := _root_.Ix.EnvScope.collectDeps env [``ordinary, `Nat.rec, `Nat.brecOn, `List.rec] - let normal ← compile env raw - -- Lean's recursors and auxiliaries are compiled as given: none of these - -- inputs is regenerated. - let regenerated ← audit "ordinary/regenerated" normal - need (regenerated == 0) "an input was regenerated" - let groundRaw := GroundRefusal.closure env (· == `Nat.add) - let partialOut ← compile env groundRaw 2 - let _ ← audit "ground-refusal partial" partialOut - for (n, msg) in GroundRefusal.expected do - need (partialOut.cenv.ungrounded.get? (Name.fromLeanName n.toName) == some msg) "existing ground refusal changed" - need (partialOut.cenv.ungrounded.size == GroundRefusal.expected.size) "unexpected ground-refusal count" - let conflicts ← IO.ofExcept (ClaimConflict.conflictClosure env) - for workers in [1, 4] do - let out ← compile env conflicts workers - let _ ← audit s!"claim-conflict/{workers}" out - -- No auxiliary is regenerated, so the closure's declarations named like - -- auxiliaries claim nothing of another's: the closure compiles whole. - need out.cenv.ungrounded.isEmpty "the claim-conflict closure was refused" - need (ClaimConflict.readinessCases.all (·.2)) "existing readiness controls failed" - let tierRaw := _root_.Ix.EnvScope.collectDeps env - [`Tests.Ix.Kernel.TierIndDefs.spin, `Tests.Ix.Kernel.TierIndDefs.kdef] - let tier ← compile env tierRaw - let _ ← audit "partial/unsafe source" tier - for n in [`Tests.Ix.Kernel.TierIndDefs.spin, `Tests.Ix.Kernel.TierIndDefs.kdef] do - need (tier.env.named.contains (Name.fromLeanName n) && !tier.cenv.ungrounded.contains (Name.fromLeanName n)) - "typed partial/unsafe input was not positively published" - mutations normal partialOut - let invalid ← IO.ofExcept ((Tests.ContractPipeline.input .shared).mapError toString) - let invalidOut ← IO.ofExcept (← _root_.Ix.CompileM.compileLeanInput invalid (numWorkers := 1)) - need (invalidOut.annotated && invalidOut.cenv.ungrounded.isEmpty) "resource precedence fixture did not reach validation" - let .error prior := _root_.Ix.Resource.validate invalidOut.env (_root_.Ix.Resource.standardProfile invalidOut.env) - | throw (IO.userError "resource precedence fixture unexpectedly validated") - postError { invalidOut with sourceNodes? := none } prior - IO.println "accounting post-step: pinned errors; both validated wrappers; completeness/resource errors retain precedence" - diff --git a/Tests/Ix/Compile/SourceGraph.lean b/Tests/Ix/Compile/SourceGraph.lean deleted file mode 100644 index a74c49865..000000000 --- a/Tests/Ix/Compile/SourceGraph.lean +++ /dev/null @@ -1,157 +0,0 @@ -import Ix.Meta -import Ix.EnvScope -import Ix.CompileDriver -import Ix.Compile.SourceGraph.Prepass -import Ix.Cert.Compile -import Tests.Ix.Compile.GroundRefusal -import Tests.Ix.Compile.SourceAccounting -import Lean.Elab.Command - -namespace Tests.Ix.Compile.SourceGraph -open _root_.Ix.Compile.SourceGraph - --- The live wrapper must use the proved producer, not a duplicate inline loop. -run_cmd do - let some (.defnInfo d) := (← Lean.getEnv).find? `Ix.CompileM.compileDecoratedConsts - | throwError "IO compiler definition unavailable" - let used := d.value.getUsedConstantsAsSet - for n in [`Ix.Compile.SourceGraph.Prepass.run, `Ix.Compile.SourceGraph.Stream.runLabelled] do - unless used.contains n do throwError "IO compiler does not call {n}" - if used.contains `Ix.CanonM.chunks then throwError "IO compiler retained the unproved partition" - -private def need (ok : Bool) (why : String) : IO Unit := unless ok do throw (IO.userError why) -private def groupEq : Grouping → Grouping → Bool - | .plain, .plain => true - | .induct a, .induct b | .recursor a, .recursor b => a.isEqv b _root_.Ix.Name.beqRaw - | _, _ => false -private def nodeEq (a b : Node) : Bool := - a.literals == b.literals && groupEq a.grouping b.grouping && - a.direct.size == b.direct.size && a.direct.toArray.all b.direct.contains -private def compile (raw : List (Lean.Name × Lean.ConstantInfo)) (workers : Nat) : - IO _root_.Ix.CompileM.LeanPipelineOut := do - IO.ofExcept (← _root_.Ix.CompileM.compileLeanConsts raw (numWorkers := workers)) - -private def facts (raw : List (Lean.Name × Lean.ConstantInfo)) (workers : Nat) - (out : _root_.Ix.CompileM.LeanPipelineOut) : IO Unit := do - let input ← IO.ofExcept ((_root_.Ix.Compile.CompileInput.fromAnnotations raw).mapError toString) - let prepared ← IO.ofExcept input.prepare - let (_, expected) := Prepass.produce prepared.toArray workers - let some actual := out.sourceNodes? | throw (IO.userError "successful IO result omitted source facts") - need (actual.size == expected.facts.nodes.size) "source graph domain size differs" - for (n, expectedNode) in expected.facts.nodes do - let some node := actual.get? n | throw (IO.userError s!"source graph omitted {n.pretty}") - need (nodeEq node expectedNode && decide (actual.getKey? n = expected.facts.nodes.getKey? n)) - s!"source graph changed facts or representative at {n.pretty}" - need (out.cenv.env.consts.size == expected.facts.code.size) "kept fields differ" - need (out.digests.size == expected.digests.size && expected.digests.toList.all - (fun (n, d) => out.digests.get? n == some d)) "input digests differ" - -private def reference (raw : List (Lean.Name × Lean.ConstantInfo)) - (out : _root_.Ix.CompileM.LeanPipelineOut) : IO Unit := do - let input ← IO.ofExcept ((_root_.Ix.Compile.CompileInput.fromAnnotations raw).mapError toString) - let prepared ← IO.ofExcept input.prepare - let consts := (_root_.Ix.CanonM.canonChunk prepared.toArray).foldl - (fun m (n, ci) => m.insert n ci) ({} : _root_.Ix.Map _root_.Ix.Name _root_.Ix.ConstantInfo) - let (expected, failures) ← IO.ofExcept (_root_.Ix.Cert.compileReport { consts }) - let bytes ← IO.ofExcept (Ixon.serEnv expected) - need (out.bytes == bytes && failures.toMap.size == out.cenv.ungrounded.size && - failures.toMap.toList.all (fun (n, e) => out.cenv.ungrounded.get? n == some e)) - "public pipeline differs from materialized reference" - -private def simple (name : Lean.Name) : Lean.ConstantInfo := .defnInfo - { name, levelParams := [], type := .sort (.succ .zero), value := .sort .zero, - hints := .abbrev, safety := .safe, all := [name] } -private def opaqueNat : Lean.ConstantInfo := .opaqueInfo - { name := `SourceFacts.o, levelParams := [], type := .const `Nat [], value := .lit (.natVal 7), - isUnsafe := false, all := [`SourceFacts.o] } -private def theoremTrue : Lean.ConstantInfo := .thmInfo - { name := `SourceFacts.t, levelParams := [], type := .const `True [], value := .const `True.intro [], - all := [`SourceFacts.t] } -private def failedDef : Lean.ConstantInfo := .defnInfo - { name := `SourceFacts.bad, levelParams := [], type := .sort (.succ .zero), - value := .const `SourceFacts.absent [], hints := .abbrev, safety := .safe, all := [`SourceFacts.bad] } - -def run : IO UInt32 := do - let empty ← compile [] 0 - facts [] 0 empty - reference [] empty - need (empty.sourceNodes?.any (·.isEmpty)) "empty input was confused with unavailable facts" - let unavailable : _root_.Ix.CompileM.LeanPipelineOut := - { bytes := empty.bytes, env := empty.env, cenv := empty.cenv, - ungroundedCount := 0, blockCount := 0 } - need unavailable.sourceNodes?.isNone "missing facts became an empty graph" - let env ← getCompileEnv #[`Init] - let support := _root_.Ix.EnvScope.collectDeps env [`True.rec, `Nat.rec] - let d := simple `SourceFacts.d - let raw := support ++ [(d.name, d), (opaqueNat.name, opaqueNat), (theoremTrue.name, theoremTrue)] - let base ← compile raw 1 - facts raw 1 base - reference raw base - need base.cenv.ungrounded.isEmpty "ordinary source-facts fixture failed compilation" - let some nodes := base.sourceNodes? | throw (IO.userError "source facts absent") - let opaqueName := _root_.Ix.Name.fromLeanName opaqueNat.name - need ((nodes.get? opaqueName).any (·.literals == .nat) && !base.cenv.env.consts.contains opaqueName && - (base.cenv.env.get? opaqueName).any (fun | .opaqueInfo _ => true | _ => false)) - "streamed opaque facts/fallback retention differs" - let theoremName := _root_.Ix.Name.fromLeanName theoremTrue.name - need (nodes.contains theoremName && !base.cenv.env.consts.contains theoremName && - (base.cenv.env.get? theoremName).any (fun | .thmInfo _ => true | _ => false)) - "streamed theorem facts/fallback retention differs" - let natName := _root_.Ix.Name.fromLeanName `Nat - need ((nodes.get? natName).any (fun n => groupEq n.grouping (.induct #[natName])) && - (nodes.get? (_root_.Ix.Name.fromLeanName `Nat.rec)).any - (fun n => groupEq n.grouping (.recursor #[natName]))) "inductive/recursor grouping changed" - for workers in [0, 2, 8] do - let out ← compile raw workers - facts raw workers out - need (out.bytes == base.bytes && out.cenv.ungrounded.isEmpty) s!"public pipeline differs at {workers} workers" - -- Forced labels exercise stored representatives at the helper seam; - -- they do not claim a collision among canonName outputs. - let h : Address := ⟨⟨Array.replicate 32 7⟩⟩ - let first : _root_.Ix.Name := .str (.anonymous h) "first" h - let last : _root_.Ix.Name := .str (.anonymous h) "last" h - for workers in [0, 1, 2, 8] do - let out := Stream.runLabelled { consts := (Prepass.run raw.toArray).thinConsts } - #[(first, d), (last, opaqueNat)] workers - need (decide (out.facts.nodes.getKey? first = some last) && - decide (out.outRefs.getKey? first = some last) && decide (out.digests.getKey? first = some last) && - out.facts.code.isEmpty) "streamed alias winner or stored representative changed" - let brokenRaw := [(failedDef.name, failedDef)] - let broken ← compile brokenRaw 2 - facts brokenRaw 2 broken - let bad := _root_.Ix.Name.fromLeanName failedDef.name - let absent := _root_.Ix.Name.fromLeanName `SourceFacts.absent - need (broken.cenv.ungrounded.get? bad == some - "UNGROUNDED-INPUT: references SourceFacts.absent, which is not in the input") "ground refusal changed" - let some badNodes := broken.sourceNodes? | throw (IO.userError "failure lost original facts") - need ((badNodes.get? bad).any (·.direct.contains absent) && !badNodes.contains absent) - "failed input or missing endpoint was erased/fabricated" - let refusalEnv ← getCompileEnv #[`Tests.Ix.Compile.GroundRefusal] - let refusalRaw := GroundRefusal.closure refusalEnv (· == `Nat.add) - let refusal ← compile refusalRaw 2 - facts refusalRaw 2 refusal - let some refusalNodes := refusal.sourceNodes? | throw (IO.userError "transitive failure lost facts") - for (name, message) in GroundRefusal.expected do - let key := _root_.Ix.Name.fromLeanName name.toName - need (refusal.cenv.ungrounded.get? key == some message && refusalNodes.contains key) - s!"ground refusal or original facts changed at {name}" - need (!refusalNodes.contains (_root_.Ix.Name.fromLeanName `Nat.add)) "missing dependency acquired a node" - -- A native prepared-input control with many streamed bodies. The shared - -- original Lean body is intentional; only the compact facts survive canon. - let body := (List.range 64).foldl (fun e _ => - Lean.Expr.letE `x (.sort (.succ .zero)) (.sort .zero) e false) (.sort .zero) - let many := (List.range 128).map fun i => - let name := Lean.Name.num `SourceFacts.stream i - let ci : Lean.ConstantInfo := .opaqueInfo - { name, levelParams := [], type := .sort (.succ .zero), value := body, - isUnsafe := false, all := [name] } - (name, ci) - let streamed ← compile many 8 - facts many 8 streamed - need (streamed.cenv.ungrounded.isEmpty && streamed.cenv.env.consts.isEmpty) - "streamed-body control retained canonical code or failed" - SourceAccounting.run - IO.println "source graph IO: complete/empty/failure facts; materialized reference; worker bytes; streamed fallback; digests/counts" - return 0 - -end Tests.Ix.Compile.SourceGraph diff --git a/Tests/Ix/Compile/TableWorklist.lean b/Tests/Ix/Compile/TableWorklist.lean deleted file mode 100644 index 34451a5f2..000000000 --- a/Tests/Ix/Compile/TableWorklist.lean +++ /dev/null @@ -1,161 +0,0 @@ -import LSpec -import Ix.CompileM - -/-! The preseed table walks run as proved-equal total implementations -(`Ix.CompileM.collectExprTables_eq_worklist`, -`preseedExprTables_eq_worklist`), not as `implemented_by` unsafe code: shared -DAGs need no tree-size prepass, deep pending siblings use the worklist (no -native recursion proportional to depth), wide root arrays use the folds, and -the effects and errors are the structural semantics'. Table indices and -counts that do not fit the wire's `UInt64` are refused (`checkedTableIndex`, -`checkedTableCount`), virtual universe indices included. -/ - -namespace Tests.Ix.Compile.TableWorklist - -open LSpec -open _root_.Ix (Name Expr Level) -open _root_.Ix.CompileM - -private def context : CompileEnv × BlockEnv := - (default, { all := {}, current := Name.fromLeanName `Tables, mutCtx := {}, univCtx := [] }) - -private def raw (action : CompileM α) (state : BlockState := {}) : - Except CompileError α × BlockState := (action context).run state - -private def resourceFailure (result : Except CompileError α) : Bool := - match result with - | .error (.resourceLimit _) => true - | _ => false - -private def expressionCollision (collide : Bool) : Bool := Id.run do - let u := Name.fromLeanName `u - let v := Name.fromLeanName `v - let first := Expr.mkSort (Level.mkParam u) - let second := if collide then Expr.sort (Level.mkParam v) first.getHash else Expr.mkSort (Level.mkParam v) - let env := (context.1, { context.2 with univCtx := [u, v] }) - let (seeded, state) := (preseedExprTables #[(first, [u, v]), (second, [u, v])] env).run {} - let (compiled, after) := (compileExpr second env).run state - return seeded.isOk && state.univs == #[.var 0, .var 1] && - compiled.toOption.map (·.1) == some (.sort 1) && after.univs == state.univs && after.univsFinal - -private def contextCollision (collide : Bool) : Bool := Id.run do - let u := Name.fromLeanName `u - let v := if collide then Name.str Name.mkAnon "v" u.getHash else Name.fromLeanName `v - let expression := Expr.mkSort (Level.mkParam u) - let env := (context.1, { context.2 with univCtx := [v, u] }) - let (seeded, state) := (preseedExprTables #[(expression, [u, v]), (expression, [v, u])] env).run {} - let (compiled, after) := (compileExpr expression env).run state - return seeded.isOk && state.univs == #[.var 0, .var 1] && - compiled.toOption.map (·.1) == some (.sort 1) && after.univs == state.univs && after.univsFinal - -private def collisionRejectsUnsupported : Bool := Id.run do - let valid := Expr.mkSort Level.mkZero - let invalid := Expr.mvar (Name.fromLeanName `bad) valid.getHash - let (result, state) := raw (preseedExprTables #[(valid, []), (invalid, [])]) - return (match result with | .error (.unsupportedExpr "metavariable") => true | _ => false) && - state.univs.isEmpty && !state.univsFinal - -def controls : List (String × Bool) := Id.run do - let key := univParamsKey [] - let mut shared := Expr.mkBVar 0 - for _ in [:64] do - shared := Expr.mkLam Name.mkAnon shared shared .default - let (sharedResult, _) := raw (collectExprTables shared key (#[], #[], {})) - let (sharedSeed, sharedSeedState) := raw (preseedExprTables #[(shared, [])]) - -- Left-associated applications force pending siblings. A recursive - -- structural collector would consume native stack proportional to depth. - let mut deep := Expr.mkBVar 0 - let argument := Expr.mkBVar 1 - for _ in [:65536] do deep := Expr.mkApp deep argument - let (deepResult, _) := raw (collectExprTables deep key (#[], #[], {})) - let roots := Array.replicate 65536 (Expr.mkSort Level.mkZero, []) - let (wideResult, wideState) := raw (preseedExprTables roots) - let u := Name.fromLeanName `u - let v := Name.fromLeanName `v - let level := Level.mkParam u - let sort := Expr.mkSort level - let (positions, positionalState) := raw (preseedExprTables #[(sort, [u, v]), (sort, [v, u])]) - let literal := Expr.mkLit (.natVal 37) - let literalBytes := ByteArray.mk (Nat.toBytesLE 37) - let literalAddress := Address.admissionHash literalBytes - let textBytes := "kept".toUTF8 - let textAddress := Address.admissionHash textBytes - let skippedAddress := Address.admissionHash "after".toUTF8 - let invalid := Expr.mkMVar (Name.fromLeanName `bad) - let broken := Expr.mkLetE Name.mkAnon literal - (Expr.mkApp (Expr.mkLit (.strVal "kept")) invalid) - (Expr.mkLit (.strVal "after")) false - let (failure, failed) := raw (preseedExprTables #[(broken, [])]) - let seen : Std.HashMap _root_.Ix.Compile.ExactKey.PreseedKey Unit := - ({} : Std.HashMap _root_.Ix.Compile.ExactKey.PreseedKey Unit).insert ⟨invalid, [], key⟩ () - let (warm, _) := raw (collectExprTables invalid key (#[literalAddress], #[], seen)) - let badLevels := Expr.mkConst (Name.fromLeanName `Missing) - #[Level.mkZero, Level.mkMvar (Name.fromLeanName `badLevel)] - let (levelFailure, levelState) := raw (preseedExprTables #[(badLevels, [])]) - let largest := UInt64.size - 1 - let (zeroIndex, _) := raw (checkedTableIndex "control" 0) - let (lastIndex, _) := raw (checkedTableIndex "control" largest) - let (tooLarge, _) := raw (checkedTableIndex "control" UInt64.size) - let (largerStill, _) := raw (checkedTableIndex "control" (UInt64.size + 1)) - let (emptyCount, _) := raw (checkedTableCount "control" 0) - let (largestCount, _) := raw (checkedTableCount "control" largest) - let (overflowCount, _) := raw (checkedTableCount "control" UInt64.size) - let (beyondCount, _) := raw (checkedTableCount "control" (UInt64.size + 1)) - let one : Ixon.Univ := .succ .zero - let goodMeta : BlockState := - { univs := #[.zero] - metaUnivs := #[one] - metaUnivsIndex := ({} : Std.HashMap Ixon.Univ UInt64).insert one 0 } - let badMeta := { goodMeta with - metaUnivsIndex := goodMeta.metaUnivsIndex.insert one largest.toUInt64 } - let (goodUniv, goodUnivAfter) := raw (internMetaUniv one) goodMeta - let (badUniv, badUnivAfter) := raw (internMetaUniv one) badMeta - return [ - ("shared DAG collector has no tree-size prepass", match sharedResult with - | .ok (refs, univs, seen) => refs.isEmpty && univs.isEmpty && seen.size == 65 - | .error _ => false), - ("shared DAG survives the public preseed entry", sharedSeed.isOk && sharedSeedState.univsFinal), - ("deep pending siblings use the worklist", match deepResult with - | .ok (refs, univs, seen) => refs.isEmpty && univs.isEmpty && seen.size == 65538 - | .error _ => false), - ("wide root arrays use the compiled folds", - wideResult.isOk && wideState.univs == #[.zero] && wideState.univsFinal), - ("seen keys retain universe parameter positions", - positions.isOk && positionalState.univs == #[.var 0, .var 1] && - positionalState.univCache.get? ⟨level, [v, u]⟩ == some (.var 1)), - ("preorder error preserves completed blob writes", (match failure with - | .error (.unsupportedExpr "metavariable") => true | _ => false) && - failed.blockBlobs.get? literalAddress == some literalBytes && - failed.blockBlobs.get? textAddress == some textBytes), - ("preorder error leaves later syntax and tables untouched", - !failed.blockBlobs.contains skippedAddress && - failed.refs.isEmpty && failed.univs.isEmpty && !failed.univsFinal), - ("an exact warm seen-cache hit retains existing behavior", match warm with - | .ok (refs, univs, afterSeen) => refs == #[literalAddress] && univs.isEmpty && afterSeen.size == 1 - | .error _ => false), - ("ordinary distinct expressions preseed both universes", expressionCollision false), - ("equal expression digests retain both universe dependencies", expressionCollision true), - ("ordinary reordered contexts preseed both positions", contextCollision false), - ("equal context digests retain both universe positions", contextCollision true), - ("a digest collision cannot suppress an unsupported expression", collisionRejectsUnsupported), - ("universe failure precedes missing reference lookup", (match levelFailure with - | .error (.unsupportedExpr "level metavariable") => true | _ => false) && - levelState.univCache.get? ⟨Level.mkZero, []⟩ == some .zero), - ("checked indices accept zero and the last wire slot", - zeroIndex.toOption == some 0 && lastIndex.toOption == some largest.toUInt64), - ("checked indices reject overflow", resourceFailure tooLarge && resourceFailure largerStill), - ("wire table counts accept empty and largest representable tables", - emptyCount.toOption == some 0 && largestCount.toOption == some largest.toUInt64), - ("a representable last slot cannot justify an overflowing table count", - lastIndex.isOk && resourceFailure overflowCount && resourceFailure beyondCount), - ("virtual universe hit retains its intended slot", - goodUniv.toOption == some 1 && goodUnivAfter.univs == #[.zero] && - goodUnivAfter.metaUnivs == #[one]), - ("overflowing virtual universe cannot wrap to the primary slot", - resourceFailure badUniv && badUnivAfter.univs == badMeta.univs && - badUnivAfter.metaUnivs == badMeta.metaUnivs && - badUnivAfter.metaUnivsIndex.get? one == some largest.toUInt64)] - -def suite : List TestSeq := controls.map fun (label, ok) => test s!"table worklist: {label}" ok - -end Tests.Ix.Compile.TableWorklist diff --git a/Tests/Ix/Compile/UniverseCache.lean b/Tests/Ix/Compile/UniverseCache.lean deleted file mode 100644 index 078890ba2..000000000 --- a/Tests/Ix/Compile/UniverseCache.lean +++ /dev/null @@ -1,69 +0,0 @@ -import LSpec -import Ix.CompileM - -namespace Tests.Ix.Compile.UniverseCache - -open LSpec -open _root_.Ix (Level Name) -open _root_.Ix.CompileM - -private def digest : Address := ⟨⟨Array.replicate 32 7⟩⟩ -private def u : Name := .str (.anonymous digest) "u" digest -private def v : Name := .str (.anonymous digest) "v" digest - -private def run (level : Level) (params : List Name := []) (state : BlockState := {}) : - Except CompileError Ixon.Univ × BlockState := - (compileUniv level (default, - { all := {}, current := .anonymous digest, mutCtx := {}, univCtx := params })).run state - -def controls : List (String × Bool) := Id.run do - let zero : Level := .zero digest - let succ : Level := .succ zero digest - let mvar : Level := .mvar u digest - let (first, state) := run zero - let (cold, _) := run succ - let (warm, after) := run succ [] state - let (refused, failed) := run mvar [] state - let (parameter, _) := run (.param v digest) [u, v] - let (missing, _) := run (.param v digest) [u] - let realU := Name.fromLeanName `u - let realV := Name.fromLeanName `v - let parameterLevel := Level.mkParam realU - let scopedAction : CompileM (Ixon.Univ × Ixon.Univ) := withUnivCtx [realU, realV] do - let inner ← withUnivCtx [realV, realU] (compileUniv parameterLevel) - let outer ← compileUniv parameterLevel - pure (inner, outer) - let (scopedResult, _) := (scopedAction (default, - { all := {}, current := .anonymous digest, mutCtx := {}, univCtx := [] })).run {} - -- The ports lane's reproduction (ports.md "Findings before porting", - -- `ports-runs/HashCollision.lean`, box run `ports/20261015T153904Z-base-ix`): - -- before exact keys, `max u v` compiled to `max (var 0) (var 0)` when `v`'s - -- level carried `u`'s level hash field, under the context `[u, v]`. - let addr (b : UInt8) : Address := ⟨⟨Array.replicate 32 b⟩⟩ - let cu : Name := .str (.anonymous (addr 0)) "u" (addr 1) - let cv : Name := .str (.anonymous (addr 0)) "v" (addr 2) - let lu : Level := .param cu (addr 3) - let honest : Level := .max lu (.param cv (addr 4)) (addr 5) - let forged : Level := .max lu (.param cv (addr 3)) (addr 6) - let (honestMax, _) := run honest [cu, cv] - let (forgedMax, _) := run forged [cu, cv] - return [ - ("a parameter level carrying another's hash field compiles to its own position", - honestMax.toOption == some (.max (.var 0) (.var 1)) - && forgedMax.toOption == some (.max (.var 0) (.var 1))), - ("cold zero and successor retain their shapes", - first.toOption == some .zero && cold.toOption == some (.succ .zero)), - ("colliding stored hashes do not alias compiled levels", - warm.toOption == some (.succ .zero) && after.univCache.size == 2), - ("colliding metavariables retain the unsupported error", - (match refused with | .error (.unsupportedExpr "level metavariable") => true | _ => false) - && failed.univCache.size == 1), - ("parameter positions use exact names", parameter.toOption == some (.var 1)), - ("a hash-aliasing absent parameter remains unbound", - match missing with | .error (.unknownUnivParam ..) => true | _ => false), - ("leaving a nested parameter context restores the outer meaning", - scopedResult.toOption == some (.var 1, .var 0))] - -def suite : List TestSeq := controls.map fun (label, ok) => test s!"universe cache: {label}" ok - -end Tests.Ix.Compile.UniverseCache diff --git a/Tests/Ix/Compile/UniverseWire.lean b/Tests/Ix/Compile/UniverseWire.lean deleted file mode 100644 index f1f62dc22..000000000 --- a/Tests/Ix/Compile/UniverseWire.lean +++ /dev/null @@ -1,58 +0,0 @@ -import LSpec -import Ix.CompileM - -/-! Native controls for the production preseed wire guard (`checkPreseedUnivs`). The 2^64 -overflow boundary itself is not constructed; these controls -exercise all constructors, compressed-count boundaries, depth and integration. -/ - -namespace Tests.Ix.Compile.UniverseWire - -open LSpec -open _root_.Ix (Expr Level Name) -open _root_.Ix.CompileM - -private def name := Name.fromLeanName `UniverseWire -private def context : CompileEnv × BlockEnv := - (default, { all := {}, current := name, mutCtx := {}, univCtx := [] }) - -private def raw (action : CompileM α) (state : BlockState := {}) : Except CompileError α × BlockState := - (action context).run state - -private def successors (count : Nat) : Ixon.Univ := Id.run do - let mut value := Ixon.Univ.zero - for _ in [:count] do value := .succ value - return value - -def controls : List (String × Bool) := Id.run do - let last := (UInt64.size - 1).toUInt64 - let shapes : Array Ixon.Univ := #[.zero, .var 0, .var last, .succ (.var 1), - .max (.succ .zero) (.var 2), .imax (.var 0) (.succ .zero)] - let initial : BlockState := { - refs := #[default] - univs := #[.var 0] - univsFinal := true } - let (accepted, after) := raw (checkPreseedUnivs shapes) initial - let mut result := [ - ("empty universe list is admitted", (raw (checkPreseedUnivs #[])).1.isOk), - ("all universe constructors and the largest parameter index are admitted", accepted.isOk), - ("wire checking retains tables, flags and memo sizes", - after.refs == initial.refs && after.univs == initial.univs && after.univsFinal == initial.univsFinal && - after.univCache.size == initial.univCache.size && - after.canonUnivCache.size == initial.canonUnivCache.size)] - for count in [0, 1, 31, 32, 127, 128, 255, 256, 4096] do - let value := successors count - let accepted := (raw (checkPreseedUnivs #[value])).1.isOk - let exactBytes := (Ixon.deUniv (univSortKey value)).toOption == some value - result := result ++ [(s!"{count} successors pass admission and byte round-trip", accepted && exactBytes)] - let roots := #[(Expr.mkSort Level.mkZero, []), - (Expr.mkSort (Level.mkSucc Level.mkZero), []), - (Expr.mkSort (Level.mkSucc Level.mkZero), [])] - let (seeded, state) := raw (preseedExprTables roots) - result := result ++ [("actual preseed admits canonical values and retains deduplication", - seeded.isOk && state.univsFinal && state.univs.size == 2 && - state.univs.contains .zero && state.univs.contains (.succ .zero))] - return result - -def suite : List TestSeq := controls.map fun (label, ok) => test s!"universe wire: {label}" ok - -end Tests.Ix.Compile.UniverseWire diff --git a/Tests/Ix/Compile/ValidateAux.lean b/Tests/Ix/Compile/ValidateAux.lean index 68f240c79..c19ad366d 100644 --- a/Tests/Ix/Compile/ValidateAux.lean +++ b/Tests/Ix/Compile/ValidateAux.lean @@ -3,8 +3,6 @@ the mutual, canonicity, level-spelling, IxVM and partial/well-founded fixture families, with the library inductive families their auxiliaries reference, closed over their dependencies (`collectDeps`). - `compile-schedule-identity`, `decompile-diff` and the kernel parity - environment compile it. -/ import Ix.Common import Ix.Meta diff --git a/Tests/Ix/Compile/WorkerSnapshot.lean b/Tests/Ix/Compile/WorkerSnapshot.lean deleted file mode 100644 index c0af48c7f..000000000 --- a/Tests/Ix/Compile/WorkerSnapshot.lean +++ /dev/null @@ -1,119 +0,0 @@ -module - -public import Ix.CompileDriver -public import LSpec - -/-! Constructed caller states test the worker's read boundary. Some source -expressions and unused output fields are deliberately malformed; these are -refusal and field-independence controls, not well-typed source or admitted -artifact fixtures. -/ - -namespace Tests.Ix.Compile.WorkerSnapshot - -open _root_.Ix (Name Expr Level) -open _root_.Ix.CompileM - -private def need (label : String) (ok : Bool) : Except String Unit := - if ok then .ok () else .error label - -private def sameOutcome : AuxBlockOutcome → AuxBlockOutcome → Bool - | .failed left, .failed right => left == right - | .compiled left ls, .compiled right rs => - left.block == right.block && left.blockBytes == right.blockBytes && left.blockAddr == right.blockAddr && - left.blockMeta == right.blockMeta && left.projections == right.projections && left.classNames == right.classNames && - ls.blockBlobs.toArray == rs.blockBlobs.toArray && ls.blockNames.toArray == rs.blockNames.toArray && - ls.blockNameToAddr.toArray == rs.blockNameToAddr.toArray && ls.mutsNamed == rs.mutsNamed && - (ls.defHints.toArray.map fun (n, h) => (n, reprStr h)) == - (rs.defHints.toArray.map fun (n, h) => (n, reprStr h)) - | _, _ => false - -private def sameOutcomes (left right : Array (Name × _root_.Ix.Set Name × AuxBlockOutcome)) : Bool := - left.size == right.size && (left.zip right).all fun ((ln, la, lo), (rn, ra, ro)) => - ln == rn && la.toArray == ra.toArray && sameOutcome lo ro - -private def fixture (count : Nat) : CompileEnv × Array (Name × Name × _root_.Ix.Set Name) := Id.run do - let base := Name.fromLeanName `WorkerSnapshotFixture - let dependency := Name.fromLeanName `WorkerSnapshotDependency - let address := Address.admissionHash "resolved dependency".toUTF8 - let mut source : _root_.Ix.Environment := { consts := {} } - let mut ready := #[] - for i in [:count] do - let name := Name.mkNat base i - let body := match i % 5 with - | 0 => Expr.mkLit (.strVal "worker literal") - | 1 => Expr.mkConst dependency #[] - | 2 => Expr.mkBVar UInt64.size - | 3 => Expr.mkMVar name - | _ => Expr.mkSort Level.mkZero - let value : _root_.Ix.ConstantInfo := .defnInfo { - cnst := { name, levelParams := #[], type := Expr.mkSort (Level.mkSucc Level.mkZero) } - value := body, hints := .opaque, safety := .safe, all := #[name] } - source := { source with consts := source.consts.insert name value } - ready := ready.push (name, name, ({} : _root_.Ix.Set Name).insert name) - let unused := Name.fromLeanName `UnusedDriverOutput - let snapshot : CompileEnv := { - CompileEnv.new source with - nameToAddr := ({} : Std.HashMap _root_.Ix.Compile.ExactKey.ExactName Address).insert dependency address - nameToNamed := ({} : Std.HashMap Name Ixon.Named).insert unused { addr := address } - constants := ({} : Std.HashMap Address ByteArray).insert address (ByteArray.mk #[255]) - totalBytes := 123456 - ungrounded := ({} : Std.HashMap Name String).insert unused "earlier refusal" - blocks := ({} : Std.HashMap Name (Array (Array Name))).insert unused #[#[unused]] } - return (snapshot, ready) - -private def compareTasks (label : String) (snapshot : CompileEnv) - (ready : Array (Name × Name × _root_.Ix.Set Name)) : Except String Unit := do - -- auxBlockOutcome retains the original full-environment implementation; - -- the production csimp changes only waveTasks. - let expected := ready.map fun (key, _, all) => (key, all, auxBlockOutcome snapshot key all) - for width in [0, 1, 8, 256, 1024] do - let explicit := ((waveTasksProjected width snapshot ready).map Task.get).flatten - let production := ((waveTasks width snapshot ready).map Task.get).flatten - need s!"{label}, width {width}: projected tasks preserve complete ordered outcomes" - (sameOutcomes explicit expected) - need s!"{label}, width {width}: production uses equivalent outcomes" - (sameOutcomes production expected) - -private def checks (count : Nat) : Except String Unit := do - let (snapshot, ready) := fixture count - let projected := _root_.Ix.Compile.WorkerContext.project snapshot - need "the fixture populates every output-only snapshot field" - (!snapshot.nameToNamed.isEmpty && !snapshot.constants.isEmpty && snapshot.totalBytes != 0 && - !snapshot.ungrounded.isEmpty && !snapshot.blocks.isEmpty) - need "projected tasks capture none of the driver's output-only fields" - (projected.nameToNamed.isEmpty && projected.constants.isEmpty && projected.totalBytes == 0 && - projected.ungrounded.isEmpty && projected.blocks.isEmpty) - compareTasks "mixed successful and failing blocks" snapshot ready - compareTasks "empty task array" snapshot #[] - let fallback := { snapshot with env := { consts := {}, fallback? := some snapshot.env.get? } } - compareTasks "fallback source lookups" fallback ready - let conflictingBlobs := ({} : Std.HashMap Address ByteArray).insert - (Address.admissionHash "worker literal".toUTF8) "different payload".toUTF8 - let conflict := { snapshot with blobs := conflictingBlobs } - compareTasks "ambient blob conflicts" conflict ready - let tight := { snapshot with sharingLimits := { snapshot.sharingLimits with maxNodes := 0 } } - compareTasks "sharing resource refusals" tight ready - let some (literal, _, literalAll) := ready[0]? | throw "fixture is empty" - need "ordinary literal block succeeds before changing its inputs" (runBlock snapshot literalAll literal).isOk - need "projected workers retain ambient collision checks" (match runBlock conflict literalAll literal with - | .error (.invalidMutualBlock "anonymous publication: conflicting blob payload") => true - | _ => false) - need "projected workers retain nondefault sharing limits" (match runBlock tight literalAll literal with - | .error (.resourceLimit _) => true - | _ => false) - let some (reference, _, referenceAll) := ready[1]? | throw "fixture has no reference block" - need "the address-map fixture resolves its external reference" (runBlock snapshot referenceAll reference).isOk - need "dropping the required address map changes the refusal" (match runBlock - { snapshot with nameToAddr := {} } referenceAll reference with - | .error (.missingConstant _) => true - | _ => false) - -public def suite : List LSpec.TestSeq := [ - .individualIO "worker snapshot: exact tasks, retained reads and refusals" none (do - let result ← IO.lazyPure fun _ => checks 269 - return match result with - | .ok _ => (true, 0, 0, none) - | .error message => (false, 0, 0, some message)) .done -] - -end Tests.Ix.Compile.WorkerSnapshot diff --git a/Tests/Ix/CompileDeterminism.lean b/Tests/Ix/CompileDeterminism.lean index 420e682ca..0f266bab4 100644 --- a/Tests/Ix/CompileDeterminism.lean +++ b/Tests/Ix/CompileDeterminism.lean @@ -1,30 +1,16 @@ /- - Cross-process compile determinism (ixc-catalog plan Phase 0.1 / A0.1). + Cross-process compile determinism. - Every existing determinism gate runs both compiles in ONE process - (memory/parallelism modes over a shared frontend env). The piece - cache of the `.ixc` catalog trusts more: two `ix compile` runs of - the same member, in separate processes on possibly different days, - must produce `sha256`-identical `.ixe` files — the file hash in the - catalog manifest is the cache key's witness. The root alone is not - enough: `status.root` commits only §2, while the file also carries - blobs, hints, and names. - - Two legs, both driving the real CLI binary in fresh subprocesses: - - * fixture-class: `Tests/Ix/Compile/Mutual.lean` — the mutual - fixture corpus, module-visible closure, seconds. - * Batteries-class: `Benchmarks/Compile/CompileBatteries.lean` — a - real library through the sub-workspace bootstrap path - (`buildFile` + oleans), minutes. - - `IxTests` depends on the `ix` executable target, so the ignored suite - (`lake test -- --ignored compile-determinism`) is self-contained. The - Batteries leg needs network on the first workspace build. + Two `ix compile` runs of the same module in separate subprocesses must + produce Blake3-identical `.ixe` files — committing not only the canonical + constants (`status.root`), but also blobs, hints, and names. + (Large-corpus cross-process byte determinism against a pinned reference + digest is also gated by `initstd-reference` in `ci:landing-initstd-cli`.) -/ module public import LSpec +public import Ix.FileDigest public section @@ -34,27 +20,16 @@ namespace Tests.Ix.CompileDeterminism private def ixExe : System.FilePath := ".lake" / "build" / "bin" / "ix" -/-- `sha256sum` of a file (Linux coreutils, same availability class as - the RSS gates). -/ -private def sha256 (path : System.FilePath) : IO (Except String String) := do - let out ← IO.Process.output { - cmd := "sha256sum", args := #[path.toString] } - if out.exitCode != 0 then - return .error s!"sha256sum {path} failed ({out.exitCode}): {out.stderr.take 200}" - match out.stdout.splitOn " " with - | hash :: _ => - if hash.length == 64 then return .ok hash - else return .error s!"sha256sum {path}: unparsable output {out.stdout.take 80}" - | _ => return .error s!"sha256sum {path}: empty output" - -/-- One `ix compile` run in a fresh subprocess. Output captured; tails - surfaced on failure. -/ +/-- One `ix compile` run in a fresh subprocess. -/ private def compileOnce (source : String) (out : System.FilePath) : IO (Except String Unit) := do let exe ← IO.FS.realPath ixExe let r ← IO.Process.output { cmd := exe.toString - args := #["compile", source, "--out", out.toString] } + -- `--local`: the fixture's own declarations and their dependency + -- closure. The fixture is a `module` with `public import Lean`, whose + -- default scope is every public declaration of Init, Std and Lean. + args := #["compile", source, "--local", "--out", out.toString] } if r.exitCode != 0 then return .error s!"ix compile {source} failed ({r.exitCode}): \ {r.stderr.take 300} … {(r.stdout.takeEnd 300).toString}" @@ -62,8 +37,8 @@ private def compileOnce (source : String) (out : System.FilePath) : return .error s!"ix compile {source} exited 0 but wrote no {out}" return .ok () -/-- The gate: two separate `ix compile` processes over `source`, byte - identity asserted via `sha256`. -/ +/-- Two separate `ix compile` processes over `source`, byte identity asserted via their Blake3 +digests (`Ix.FileDigest.blake3File`). -/ private def determinismLeg (source : String) : IO (Bool × Nat × Nat × Option String) := do unless (← ixExe.pathExists) do @@ -76,12 +51,10 @@ private def determinismLeg (source : String) : return (false, 0, 0, some e) if let .error e ← compileOnce source out2 then return (false, 0, 0, some e) - let h1 ← match ← sha256 out1 with - | .ok h => pure h - | .error e => return (false, 0, 0, some e) - let h2 ← match ← sha256 out2 with - | .ok h => pure h - | .error e => return (false, 0, 0, some e) + let h1 ← try Ix.FileDigest.blake3File out1 + catch e => return (false, 0, 0, some s!"blake3 of {out1}: {e}") + let h2 ← try Ix.FileDigest.blake3File out2 + catch e => return (false, 0, 0, some s!"blake3 of {out2}: {e}") let bytes := (← out1.metadata).byteSize.toNat if h1 != h2 then return (false, bytes, 0, some s!"{source}: cross-process compile \ @@ -92,10 +65,7 @@ is nondeterministic — {h1} vs {h2}") def suite : List TestSeq := [ .individualIO - "compile determinism: fixture corpus, two processes, sha256-identical" - none (determinismLeg "Tests/Ix/Compile/Mutual.lean") .done, - .individualIO - "compile determinism: Batteries, two processes, sha256-identical" - none (determinismLeg "Benchmarks/Compile/CompileBatteries.lean") .done ] + "compile determinism: fixture corpus, two processes, Blake3-identical" + none (determinismLeg "Tests/Ix/Compile/Mutual.lean") .done ] end Tests.Ix.CompileDeterminism diff --git a/Tests/Ix/CondenseM.lean b/Tests/Ix/CondenseM.lean deleted file mode 100644 index f593d0885..000000000 --- a/Tests/Ix/CondenseM.lean +++ /dev/null @@ -1,610 +0,0 @@ -/- - Tests for Ix.CondenseM module. - - Unit tests for SCC computation mirroring Rust test cases - - Cross-implementation tests comparing Lean vs Rust SCC results --/ - -module -public import Ix.GraphM -public import Ix.CondenseM -public import Ix.Environment -public import Ix.Meta -public import LSpec - -open LSpec Ix - -namespace Tests.Ix.CondenseM - -/-! ## Helper functions -/ - -/-- Create a simple Ix.Name from a string -/ -def mkName (s : String) : Ix.Name := Ix.Name.mkStr Ix.Name.mkAnon s - -/-- Build a reference graph from adjacency list -/ -def buildGraph (edges : List (String × List String)) : Map Ix.Name (Set Ix.Name) := - edges.foldl (init := {}) fun acc (src, dsts) => - let srcName := mkName src - let dstSet := dsts.foldl (init := {}) fun s d => s.insert (mkName d) - acc.insert srcName dstSet - -/-- Extract SCC structure as sorted list of sorted lists (for deterministic comparison) -/ -def sccsToSorted (blocks : CondensedBlocks) : List (List String) := - let sccs := blocks.blocks.toList.map fun (_, members) => - (members.toList.map toString).mergeSort - sccs.mergeSort - -/-- Check if two SCC results are equivalent (same SCCs, ignoring order) -/ -def sccsEq (actual expected : List (List String)) : Bool := - actual.length == expected.length && - actual.all (expected.contains ·) - -/-! ## Test cases (mirroring Rust's src/ix/condense.rs tests) -/ - -/-- Test 1: Single node with no edges → one SCC containing just that node -/ -def testSingleNode : TestSeq := - let graph := buildGraph [("A", [])] - let sccs := Ix.CondenseM.run graph - let result := sccsToSorted sccs - test "single node: 1 SCC" (result.length == 1) ++ - test "single node: SCC contains A" (result == [["A"]]) - -/-- Test 2: Simple cycle A→B→A → one SCC containing both -/ -def testSimpleCycle : TestSeq := - let graph := buildGraph [("A", ["B"]), ("B", ["A"])] - let sccs := Ix.CondenseM.run graph - let result := sccsToSorted sccs - test "simple cycle: 1 SCC" (result.length == 1) ++ - test "simple cycle: SCC contains A,B" (result == [["A", "B"]]) - -/-- Test 3: Chain with no cycle A→B→C → three separate SCCs -/ -def testChainNoCycle : TestSeq := - let graph := buildGraph [("A", ["B"]), ("B", ["C"]), ("C", [])] - let sccs := Ix.CondenseM.run graph - let result := sccsToSorted sccs - test "chain: 3 SCCs" (result.length == 3) ++ - test "chain: each singleton" (sccsEq result [["A"], ["B"], ["C"]]) - -/-- Test 4: Two cycles connected: A↔B→C↔D → two SCCs -/ -def testTwoCyclesConnected : TestSeq := - let graph := buildGraph [ - ("A", ["B"]), - ("B", ["A", "C"]), - ("C", ["D"]), - ("D", ["C"]) - ] - let sccs := Ix.CondenseM.run graph - let result := sccsToSorted sccs - test "two cycles: 2 SCCs" (result.length == 2) ++ - test "two cycles: correct SCCs" (sccsEq result [["A", "B"], ["C", "D"]]) - -/-- Test 5: Complex graph with 8 nodes → 3 SCCs - Graph: A→B, B→{C,E,F}, C→{D,G}, D→{C,H}, E→{A,F}, F→G, G→F, H→{D,G} - Expected SCCs: {A,B,E}, {C,D,H}, {F,G} -/ -def testComplexGraph : TestSeq := - let graph := buildGraph [ - ("A", ["B"]), - ("B", ["C", "E", "F"]), - ("C", ["D", "G"]), - ("D", ["C", "H"]), - ("E", ["A", "F"]), - ("F", ["G"]), - ("G", ["F"]), - ("H", ["D", "G"]) - ] - let sccs := Ix.CondenseM.run graph - let result := sccsToSorted sccs - test "complex: 3 SCCs" (result.length == 3) ++ - test "complex: correct SCCs" (sccsEq result [["A", "B", "E"], ["C", "D", "H"], ["F", "G"]]) - -/-! ## Additional edge case tests -/ - -/-- Empty graph -/ -def testEmptyGraph : TestSeq := - let graph : Map Ix.Name (Set Ix.Name) := {} - let sccs := Ix.CondenseM.run graph - let result := sccsToSorted sccs - test "empty: 0 SCCs" (result.length == 0) - -/-- Self-loop: A→A -/ -def testSelfLoop : TestSeq := - let graph := buildGraph [("A", ["A"])] - let sccs := Ix.CondenseM.run graph - let result := sccsToSorted sccs - test "self-loop: 1 SCC" (result.length == 1) ++ - test "self-loop: contains A" (result == [["A"]]) - -/-- Multiple disconnected components -/ -def testDisconnected : TestSeq := - let graph := buildGraph [ - ("A", ["B"]), - ("B", ["A"]), - ("C", ["D"]), - ("D", ["C"]), - ("E", []) - ] - let sccs := Ix.CondenseM.run graph - let result := sccsToSorted sccs - test "disconnected: 3 SCCs" (result.length == 3) ++ - test "disconnected: correct SCCs" (sccsEq result [["A", "B"], ["C", "D"], ["E"]]) - -/-- Linear chain -/ -def testLinearChain : TestSeq := - let graph := buildGraph [ - ("A", ["B"]), - ("B", ["C"]), - ("C", ["D"]), - ("D", ["E"]), - ("E", []) - ] - let sccs := Ix.CondenseM.run graph - let result := sccsToSorted sccs - test "linear: 5 SCCs" (result.length == 5) ++ - test "linear: all singletons" (result.all (·.length == 1)) - -/-- Large cycle -/ -def testLargeCycle : TestSeq := - let graph := buildGraph [ - ("A", ["B"]), - ("B", ["C"]), - ("C", ["D"]), - ("D", ["E"]), - ("E", ["A"]) - ] - let sccs := Ix.CondenseM.run graph - let result := sccsToSorted sccs - test "large cycle: 1 SCC" (result.length == 1) ++ - test "large cycle: contains all 5" (result == [["A", "B", "C", "D", "E"]]) - -/-! ## Test lowLinks correctness -/ - -def testLowLinksSimpleCycle : TestSeq := - let graph := buildGraph [("A", ["B"]), ("B", ["A"])] - let sccs := Ix.CondenseM.run graph - -- All nodes in the same SCC should have the same lowLink - let aLow := sccs.lowLinks.get? (mkName "A") - let bLow := sccs.lowLinks.get? (mkName "B") - test "lowLinks: A has lowLink" aLow.isSome ++ - test "lowLinks: B has lowLink" bLow.isSome ++ - test "lowLinks: A and B same root" (aLow == bLow) - -def testLowLinksChain : TestSeq := - let graph := buildGraph [("A", ["B"]), ("B", ["C"]), ("C", [])] - let sccs := Ix.CondenseM.run graph - -- Each node should be its own root in a chain - let aLow := sccs.lowLinks.get? (mkName "A") - let bLow := sccs.lowLinks.get? (mkName "B") - let cLow := sccs.lowLinks.get? (mkName "C") - test "chain lowLinks: A is own root" (aLow == some (mkName "A")) ++ - test "chain lowLinks: B is own root" (bLow == some (mkName "B")) ++ - test "chain lowLinks: C is own root" (cLow == some (mkName "C")) - -/-! ## Test blockRefs correctness -/ - -def testBlockRefsChain : TestSeq := - let graph := buildGraph [("A", ["B"]), ("B", ["C"]), ("C", [])] - let sccs := Ix.CondenseM.run graph - -- A's block should reference B's block - let aBlockRefs := sccs.blockRefs.get? (mkName "A") - let bBlockRefs := sccs.blockRefs.get? (mkName "B") - let cBlockRefs := sccs.blockRefs.get? (mkName "C") - test "blockRefs: A refs B" (aBlockRefs.map (·.contains (mkName "B")) |>.getD false) ++ - test "blockRefs: B refs C" (bBlockRefs.map (·.contains (mkName "C")) |>.getD false) ++ - test "blockRefs: C refs nothing" (cBlockRefs.map (·.isEmpty) |>.getD true) - -def testBlockRefsTwoCycles : TestSeq := - let graph := buildGraph [ - ("A", ["B"]), - ("B", ["A", "C"]), - ("C", ["D"]), - ("D", ["C"]) - ] - let sccs := Ix.CondenseM.run graph - -- The {A,B} SCC should reference the {C,D} SCC - -- Find the root of A's SCC - let aRoot := sccs.lowLinks.get? (mkName "A") - match aRoot with - | some root => - let blockRefs := sccs.blockRefs.get? root - -- Should contain C (which is in the other SCC) - test "blockRefs: AB SCC refs C" (blockRefs.map (·.contains (mkName "C")) |>.getD false) - | none => test "blockRefs: found A's root" false - -/-! ## References outside the keys, and large graphs -/ - -/-- A reference to a name that is not a key is not an edge: it joins no -component and has no representative, but stays in its block's `blockRefs`. -/ -def testOutsideRefs : TestSeq := - let graph := buildGraph [("A", ["B", "X"]), ("B", ["A", "Y"]), ("C", ["X", "A"])] - let sccs := Ix.CondenseM.run graph - let aRoot := sccs.lowLinks.get? (mkName "A") - let cRoot := sccs.lowLinks.get? (mkName "C") - let refsOf := fun (r : Option Ix.Name) => - ((r.bind sccs.blockRefs.get?).getD {}).toList.map toString |>.mergeSort - test "outside refs: SCCs" (sccsToSorted sccs == [["A", "B"], ["C"]]) ++ - test "outside refs: no representative for X, Y" - (!sccs.lowLinks.contains (mkName "X") && !sccs.lowLinks.contains (mkName "Y")) ++ - test "outside refs: AB block refs X, Y" (refsOf aRoot == ["X", "Y"]) ++ - test "outside refs: C block refs A, X" (refsOf cRoot == ["A", "X"]) - -/-- A chain and a cycle of 100,000 nodes: the total Tarjan keeps its own -call stack, so depth costs no native stack. -/ -def testLargeGraphs : TestSeq := - let n := 100000 - let chain : Map Ix.Name (Set Ix.Name) := (List.range n).foldl (init := {}) fun m i => - m.insert (mkName s!"n{i}") (if i + 1 < n then {mkName s!"n{i+1}"} else {}) - let cycle : Map Ix.Name (Set Ix.Name) := (List.range n).foldl (init := {}) fun m i => - m.insert (mkName s!"n{i}") {mkName s!"n{(i + 1) % n}"} - let c1 := Ix.CondenseM.run chain - let c2 := Ix.CondenseM.run cycle - test "100k chain: 100k singleton blocks" - (c1.blocks.size == n && c1.lowLinks.size == n && c1.blocks.toList.all (·.2.size == 1)) ++ - test "100k cycle: one block of 100k" - (c2.blocks.size == 1 && c2.lowLinks.size == n && c2.blocks.toList.all (·.2.size == n)) - -/-! ## The total Tarjan on index graphs -/ - -/-- `Ix.Compile.Canon.tarjan` on small graphs (components compared as sets). -/ -def testTarjan : TestSeq := - let norm := fun (cs : Array (Array Nat)) => (cs.map (·.qsort (· < ·))).qsort - (fun a b => a[0]?.getD 0 < b[0]?.getD 0) - let cases : List (Array (Array Nat) × Array (Array Nat)) := [ - (#[], #[]), - (#[#[]], #[#[0]]), - (#[#[1], #[0]], #[#[0, 1]]), - (#[#[1], #[2], #[]], #[#[0], #[1], #[2]]), - (#[#[1], #[0, 2], #[3], #[2]], #[#[0, 1], #[2, 3]]), - (#[#[1], #[2], #[0, 3], #[4], #[5], #[3], #[7], #[6]], - #[#[0, 1, 2], #[3, 4, 5], #[6, 7]]), - (#[#[0]], #[#[0]]), - -- out-of-range successors are ignored - (#[#[1, 7], #[0, 9]], #[#[0, 1]])] - cases.foldl (init := .done) fun t (g, want) => - t ++ test s!"tarjan {g}" ((Ix.Compile.Canon.tarjan g).map norm == some (norm want)) - -/-! ## The former `partial` Tarjan, as a reference - -`Former.run` is the implementation `CondenseM.run` replaced, kept verbatim -except that `visit` takes fuel (the number of keys plus one, more than the -recursion depth) instead of being `partial`. `CondenseM.run` must reproduce -its three maps exactly: contents and iteration orders, which downstream -compilation and Source-mode ingress depend on. -/ - -namespace Former - -structure CondenseEnv where - validNames: Set Ix.Name - outRefs: Map Ix.Name (Set Ix.Name) - -structure CondenseState where - names: Map Ix.Name UInt64 - ids: Map UInt64 Ix.Name - lowLink: Map UInt64 UInt64 - stack: Array UInt64 - onStack: Set UInt64 - id : UInt64 - -def CondenseState.init : CondenseState := ⟨{}, {}, {}, #[], {}, 0⟩ - -abbrev CondenseM := ReaderT CondenseEnv <| StateT CondenseState Id - -def visit : Nat → Ix.Name → CondenseM Unit -| 0, _ => pure () -| fuel + 1, name => do - if !(<- read).validNames.contains name then return () - let refs := match (<- read).outRefs.get? name with - | .some x => x - | .none => {} - let id := (<- get).id - modify fun stt => { stt with - names := stt.names.insert name id - ids := stt.ids.insert id name - stack := stt.stack.push id - onStack := stt.onStack.insert id - lowLink := stt.lowLink.insert id id - id := id + 1 - } - for ref in refs do - if !(<- read).validNames.contains ref then continue - match (<- get).names.get? ref with - | .none => do - visit fuel ref - modify fun stt => - let ll := stt.lowLink.get! id - let rll := stt.lowLink.get! (stt.names.get! ref) - { stt with lowLink := stt.lowLink.insert id (min ll rll) } - | .some id' => if (<- get).onStack.contains id' then - modify fun stt => - let ll := stt.lowLink.get! id - { stt with lowLink := stt.lowLink.insert id (min ll id') } - if id == (<- get).lowLink.get! id then - let mut stack := (<- get).stack - if !stack.isEmpty then - while true do - let top := stack.back! - stack := stack.pop - modify fun stt => { stt with - lowLink := stt.lowLink.insert top id - onStack := stt.onStack.erase top - } - if top == id then break - modify fun stt => { stt with stack := stack } - -def condense (fuel : Nat) : CondenseM CondensedBlocks := do - for (name, _) in (<- read).outRefs do - match (<- get).names.get? name with - | .some _ => continue - | .none => visit fuel name - let mut blocks : Map Ix.Name (Set Ix.Name) := {} - let mut lowLinks := {} - for (i, low) in (<- get).lowLink do - let name := (<- get).ids.get! i - let lowName := (<- get).ids.get! low - lowLinks := lowLinks.insert name lowName - blocks := blocks.alter lowName fun x => match x with - | .some s => .some (s.insert name) - | .none => .some {name} - let mut blockRefs := {} - let refs := (<- read).outRefs - for (lo, all) in blocks do - let mut rs: Set Ix.Name := {} - for a in all do - rs := rs.union (refs.get! a) - rs := rs.filter (!all.contains ·) - blockRefs := blockRefs.insert lo rs - return ⟨lowLinks, blocks, blockRefs⟩ - -def run (refs : Map Ix.Name (Set Ix.Name)) : CondensedBlocks := - let validNames : Set Ix.Name := refs.fold (init := {}) fun acc k _ => acc.insert k - Id.run (StateT.run (ReaderT.run (condense (refs.size + 1)) ⟨validNames, refs⟩) - CondenseState.init).1 - -end Former - -/-- The three maps in their iteration orders, member sets included. -/ -def layout (cb : CondensedBlocks) : - Array (Ix.Name × Ix.Name) × Array (Ix.Name × Array Ix.Name) × - Array (Ix.Name × Array Ix.Name) := Id.run do - let mut lows := #[] - for p in cb.lowLinks do lows := lows.push p - let mut blocks := #[] - for (k, s) in cb.blocks do - let mut ms := #[] - for m in s do ms := ms.push m - blocks := blocks.push (k, ms) - let mut refs := #[] - for (k, s) in cb.blockRefs do - let mut rs := #[] - for r in s do rs := rs.push r - refs := refs.push (k, rs) - return (lows, blocks, refs) - -/-- A pseudo-random graph on `n` keys: up to `deg` references per key, some -to the key itself and some to names outside the keys. -/ -def randomGraph (seed n deg : Nat) : Map Ix.Name (Set Ix.Name) := Id.run do - let mut s := seed - let mut m : Map Ix.Name (Set Ix.Name) := {} - for i in [0:n] do - s := (s * 6364136223846793005 + 1442695040888963407) % 18446744073709551616 - let k := (s / 65536) % (deg + 1) - let mut rs : Set Ix.Name := {} - for _ in [0:k] do - s := (s * 6364136223846793005 + 1442695040888963407) % 18446744073709551616 - let j := (s / 65536) % (n + n / 8 + 1) - rs := rs.insert (if j < n then mkName s!"n{j}" else mkName s!"x{j}") - m := m.insert (mkName s!"n{i}") rs - return m - -/-- `CondenseM.run` against the former implementation on a fixed list of -graphs: equal maps, member sets and iteration orders. -/ -def testAgainstFormer : TestSeq := Id.run do - let mut graphs : Array (String × Map Ix.Name (Set Ix.Name)) := #[ - ("complex", buildGraph [("A", ["B"]), ("B", ["C", "E", "F"]), ("C", ["D", "G"]), - ("D", ["C", "H"]), ("E", ["A", "F"]), ("F", ["G"]), ("G", ["F"]), ("H", ["D", "G"])]), - ("outside", buildGraph [("A", ["B", "X"]), ("B", ["A", "Y"]), ("C", ["X", "A"])]), - ("self", buildGraph [("A", ["A"]), ("B", ["A", "B"])])] - for n in [0, 1, 2, 3, 5, 8, 13, 40, 200, 1000, 3000] do - for deg in [1, 2, 4] do - for seed in [1, 7, 42] do - graphs := graphs.push (s!"random n={n} deg={deg} seed={seed}", randomGraph seed n deg) - let mut t : TestSeq := .done - for (label, g) in graphs do - t := t ++ test s!"same maps as the former Tarjan: {label}" - (layout (Ix.CondenseM.run g) == layout (Former.run g)) - return t - -/-! ## Full Test Suite (unit tests) -/ - -public def suite : List TestSeq := [ - group "basic SCCs" ( - testSingleNode ++ - testSimpleCycle ++ - testChainNoCycle ++ - testTwoCyclesConnected ++ - testComplexGraph - ), - group "edge cases" ( - testEmptyGraph ++ - testSelfLoop ++ - testDisconnected ++ - testLinearChain ++ - testLargeCycle - ), - group "lowLinks" ( - testLowLinksSimpleCycle ++ - testLowLinksChain - ), - group "blockRefs" ( - testBlockRefsChain ++ - testBlockRefsTwoCycles - ), - group "outside references" testOutsideRefs, - group "large graphs" testLargeGraphs, - group "total Tarjan" testTarjan, - group "former implementation" testAgainstFormer -] - -/-! ## Cross-Implementation Tests (Lean vs Rust) -/ - -/-- Canonicalize environment in Rust (fast). Returns Ix.RawEnvironment. -/ -@[extern "rs_canonicalize_env_to_ix"] -opaque rsCanonicalizeEnvToIxRaw : - @& List (Lean.Name × Lean.ConstantInfo) → IO Ix.RawEnvironment - -/-- Compute SCCs in Rust. - Returns RustCondensedBlocks (defined in Ix.CondenseM) -/ -@[extern "rs_compute_sccs"] -opaque rsComputeSccs : @& List (Lean.Name × Lean.ConstantInfo) → - IO Ix.RustCondensedBlocks - -/-- Convert Rust lowLinks array to HashMap. -/ -def rustLowLinksToMap (arr : Array (Ix.Name × Ix.Name)) - : Std.HashMap Ix.Name Ix.Name := Id.run do - let mut m : Std.HashMap Ix.Name Ix.Name := {} - for (name, root) in arr do - m := m.insert name root - return m - -/-- Convert Rust's blocks array to a HashMap. -/ -def rustBlocksToMap (arr : Array (Ix.Name × Array Ix.Name)) - : Std.HashMap Ix.Name (Std.HashSet Ix.Name) := Id.run do - let mut m : Std.HashMap Ix.Name (Std.HashSet Ix.Name) := {} - for (name, members) in arr do - let memberSet := members.foldl (init := {}) fun s n => s.insert n - m := m.insert name memberSet - return m - -/-- Convert Lean's CondensedBlocks.blocks to a HashMap. -/ -def leanBlocksToHashMap (m : Ix.Map Ix.Name (Ix.Set Ix.Name)) - : Std.HashMap Ix.Name (Std.HashSet Ix.Name) := Id.run do - let mut result : Std.HashMap Ix.Name (Std.HashSet Ix.Name) := {} - for (name, members) in m do - let memberSet : Std.HashSet Ix.Name := members.fold (init := {}) fun s n => s.insert n - result := result.insert name memberSet - return result - -/-- Compare SCC results efficiently by checking block counts and sizes. - Returns (matches, totalChecked, mismatches). -/ -def compareSCCResults - (leanBlocks rustBlocks : Std.HashMap Ix.Name (Std.HashSet Ix.Name)) - (rustLowLinks : Std.HashMap Ix.Name Ix.Name) - : Bool × Nat × Array String := Id.run do - let mut mismatches : Array String := #[] - let mut checked : Nat := 0 - - -- Check that block counts match - if leanBlocks.size != rustBlocks.size then - mismatches := mismatches.push s!"Block count mismatch: Lean={leanBlocks.size}, Rust={rustBlocks.size}" - return (false, 0, mismatches) - - -- For each Lean block, find corresponding Rust block and compare sizes - for (_leanRoot, leanMembers) in leanBlocks do - checked := checked + 1 - -- Find any member's Rust root - let someMember := leanMembers.fold (init := none) fun acc n => - if acc.isNone then some n else acc - match someMember with - | none => continue - | some member => - match rustLowLinks.get? member with - | none => - if mismatches.size < 5 then - mismatches := mismatches.push s!"Member {member} not in Rust lowLinks" - | some rustRoot => - match rustBlocks.get? rustRoot with - | none => - if mismatches.size < 5 then - mismatches := mismatches.push s!"Rust root {rustRoot} not in blocks" - | some rustMembers => - if leanMembers.size != rustMembers.size then - if mismatches.size < 5 then - mismatches := mismatches.push s!"Size mismatch for SCC containing {member}: Lean={leanMembers.size}, Rust={rustMembers.size}" - - return (mismatches.isEmpty, checked, mismatches) - -/-- Cross-implementation test: compare Lean and Rust SCC computation -/ -def testSccComparison : TestSeq := - .individualIO "SCC Computation: Lean vs Rust" none (do - let env ← get_env! - let numConsts := env.constants.toList.length - - IO.println s!"[Test] SCC Computation Comparison Test" - IO.println s!"[Test] Environment has {numConsts} constants" - IO.println "" - - -- Step 0: Canonicalize environment using Rust FFI (fast) - IO.println s!"[Test] Step 0: Canonicalizing environment via Rust FFI..." - let canonStart ← IO.monoMsNow - let rawEnv ← rsCanonicalizeEnvToIxRaw env.constants.toList - let ixEnv := rawEnv.toEnvironment - let canonTime := (← IO.monoMsNow) - canonStart - IO.println s!"[Test] Canonicalized {ixEnv.consts.size} constants in {canonTime}ms" - IO.println "" - - -- Step 1: Compute SCCs in Rust - IO.println s!"[Test] Step 1: Computing SCCs in Rust..." - let rustStart ← IO.monoMsNow - let rustSccs ← rsComputeSccs env.constants.toList - let rustTime := (← IO.monoMsNow) - rustStart - IO.println s!"[Test] Rust: {rustSccs.blocks.size} SCCs, {rustSccs.lowLinks.size} lowLinks in {rustTime}ms" - - -- Step 2: Compute SCCs in Lean (using pre-canonicalized environment) - IO.println s!"[Test] Step 2: Computing SCCs in Lean..." - let leanStart ← IO.monoMsNow - let leanRefMap := Ix.GraphM.envParallel ixEnv - let sccs := Ix.CondenseM.run leanRefMap - IO.print s!"[Test] Lean: {sccs.blocks.size} SCCs, {sccs.lowLinks.size} lowLinks " - let leanTime := (← IO.monoMsNow) - leanStart - IO.println s!"in {leanTime}ms" - IO.println "" - - -- Step 3: Compare SCC content - IO.println s!"[Test] Step 3: Comparing SCC content..." - let rustBlocksMap := rustBlocksToMap rustSccs.blocks - let leanBlocksMap := leanBlocksToHashMap sccs.blocks - let rustLowLinksMap := rustLowLinksToMap rustSccs.lowLinks - - let (sccsMatch, checkedCount, mismatches) := - compareSCCResults leanBlocksMap rustBlocksMap rustLowLinksMap - - for msg in mismatches do - IO.println s!"[Test] {msg}" - - IO.println "" - IO.println s!"[Test] Summary:" - IO.println s!"[Test] Canon time: {canonTime}ms" - IO.println s!"[Test] Rust SCC time: {rustTime}ms" - IO.println s!"[Test] Lean SCC time: {leanTime}ms" - IO.println s!"[Test] Checked {checkedCount} SCCs" - IO.println s!"[Test] SCCs match: {sccsMatch}" - - if !sccsMatch then - return (false, 0, 0, some s!"SCCs do not match: {mismatches.size} mismatches") - - return (true, 0, 0, none) - ) .done - -/-- `CondenseM.run` against the former implementation on the reference graph -of the test binary's environment: equal maps, member sets and iteration -orders. -/ -def testFormerOnEnv : TestSeq := - .individualIO "CondenseM.run vs the former Tarjan on the environment" none (do - let env ← get_env! - let rawEnv ← rsCanonicalizeEnvToIxRaw env.constants.toList - let refs := Ix.GraphM.envParallel rawEnv.toEnvironment - let mine := layout (Ix.CondenseM.run refs) - let former := layout (Former.run refs) - let same := mine == former - IO.println s!"[Test] {refs.size} nodes, {mine.2.1.size} blocks; \ - same maps and orders: {same}" - if !same then - return (false, 0, 0, some "the maps differ from the former implementation's") - return (true, 0, 0, none) - ) .done - -/-- Cross-implementation test suite (expensive, run with --ignored) -/ -public def suiteIO : List TestSeq := [ - testSccComparison, - testFormerOnEnv -] - -end Tests.Ix.CondenseM diff --git a/Tests/Ix/ContractPipeline.lean b/Tests/Ix/ContractPipeline.lean index 985408b73..e7ab69479 100644 --- a/Tests/Ix/ContractPipeline.lean +++ b/Tests/Ix/ContractPipeline.lean @@ -1,7 +1,10 @@ module -public import Ix.CompileDriver -public import Ix.CompileResource +public import Ix.CompileCertified + +/-! + Contract pipeline test suite (`Tests.ContractPipeline`). +-/ public section @@ -29,37 +32,28 @@ def input (result : Ixon.ValueContract := .localShared) : Except SourceContractE def get (label : String) (r : Except String α) : IO α := match r with | .ok value => pure value | .error e => throw <| IO.userError s!"contract pipeline {label}: {e}" +def compileInput (input : CompileInput) : IO (Except String Ix.CompileM.CertifiedOut) := do + match input.prepare >>= fun p => (Ix.CompileM.checkPreparedSource p).map (p, ·) with + | .error e => return .error e + | .ok (prepared, annotated) => Ix.CompileM.compileCertifiedPrepared prepared annotated 1 + def run : IO Unit := do let valid ← get "input" ((input).mapError toString) - let lean ← Ix.CompileM.compileLeanInputValidated valid (numWorkers := 1) >>= get "Lean compiler" + let lean ← compileInput valid >>= get "Lean compiler" let prepared ← get "preparation" valid.prepare - let rust ← Ix.CompileM.rsCompileEnvFFI prepared - let rust := rust.toEnv + let (certEnv, _) ← get "certified compiler" (Ix.Cert.compileReport (Ix.CompileM.canonInput prepared.toArray 1)) unless (lean.env.consts.toList.map Prod.fst).mergeSort (fun a b => Address.cmpBytes a b != .gt) == - (rust.consts.toList.map Prod.fst).mergeSort (fun a b => Address.cmpBytes a b != .gt) do - throw <| IO.userError "contract pipeline: Lean/Rust address sets differ" + (certEnv.consts.toList.map Prod.fst).mergeSort (fun a b => Address.cmpBytes a b != .gt) do + throw <| IO.userError "contract pipeline: Lean/certified address sets differ" let profile : Ix.Resource.Profile := {} - let explicit ← Ix.CompileM.rsCompileInput valid (some profile) - unless (rust.consts.toList.map Prod.fst).mergeSort (fun a b => Address.cmpBytes a b != .gt) == - (explicit.consts.toList.map Prod.fst).mergeSort (fun a b => Address.cmpBytes a b != .gt) do - throw <| IO.userError "contract pipeline: explicit profile changed constant addresses" - let malformedRejected ← try - let _ ← Ix.CompileM.rsCompileEnvProfileFFI prepared (profile.bytes.push 0) - pure false - catch _ => pure true + let _ ← get "explicit profile" (Ix.Resource.validate lean.env profile) + let malformedRejected := (Ix.Resource.Profile.ofBytes (profile.bytes.push 0)).toOption.isNone unless malformedRejected do throw <| IO.userError "contract pipeline: malformed profile bytes accepted" let invalid ← get "invalid input" ((input .shared).mapError toString) - if (← Ix.CompileM.compileLeanInputValidated invalid (numWorkers := 1)).toOption.isSome then + if (← compileInput invalid).toOption.isSome then throw <| IO.userError "contract pipeline: pure compiler emitted an escaping local value" - let prepared ← get "invalid preparation" invalid.prepare - let rejected ← try - let _ ← Ix.CompileM.rsCompileEnvFFI prepared - pure false - catch _ => pure true - unless rejected do - throw <| IO.userError "contract pipeline: native compiler emitted an escaping local value" - IO.println "Contract pipeline: both compilers preserve addresses and reject local escape before emission" + IO.println "Contract pipeline: certified compiler preserves addresses and rejects local escape before emission" end Tests.ContractPipeline diff --git a/Tests/Ix/Decompile.lean b/Tests/Ix/Decompile.lean index d28199382..da673dc90 100644 --- a/Tests/Ix/Decompile.lean +++ b/Tests/Ix/Decompile.lean @@ -1,9 +1,3 @@ -/- - Decompilation tests. - Runs the Rust compilation pipeline, then decompiles back to Ix constants - and compares via content hashes. --/ - module public import Ix.Ixon public import Ix.Environment @@ -11,6 +5,7 @@ public import Ix.Address public import Ix.Common public import Ix.Meta public import Ix.CompileM +public import Ix.CompileCertified public import Ix.DecompileM public import Ix.DecompileDriver public import Ix.ImportIxe @@ -18,116 +13,16 @@ public import Lean public import LSpec public import Tests.Ix.Fixtures +/-! + Decompilation tests. + Runs the certified compilation pipeline, then decompiles back to Ix constants + and compares via content hashes. +-/ + open LSpec namespace Tests.Decompile -/-- Decompile roundtrip test: Rust compile → parallel decompile → hash comparison -/ -def testDecompile : TestSeq := - .individualIO "Decompilation Roundtrip" none (do - let leanEnv ← get_env! - let totalConsts := leanEnv.constants.toList.length - - IO.println s!"[Test] Decompilation Roundtrip Test" - IO.println s!"[Test] Environment has {totalConsts} constants" - IO.println "" - - -- Step 1: Run Rust compilation pipeline - IO.println s!"[Step 1] Running Rust compilation pipeline..." - let rustStart ← IO.monoMsNow - let phases ← Ix.CompileM.rsCompilePhases leanEnv - let rustTime := (← IO.monoMsNow) - rustStart - IO.println s!"[Step 1] Rust: {phases.compileEnv.constCount} compiled in {rustTime}ms" - IO.println s!"[Step 1] names={phases.compileEnv.names.size}, named={phases.compileEnv.named.size}, consts={phases.compileEnv.consts.size}, blobs={phases.compileEnv.blobs.size}" - IO.println "" - - -- Step 2: Read stored originals and restore inductive flags. The source - -- view remains a compatibility argument; comparison is below. - IO.println s!"[Step 2] Decompiling (full driver) to Ix types..." - let decompStart ← IO.monoMsNow - let origView : Std.HashMap Ix.Name Ix.ConstantInfo := phases.rawEnv.consts - -- The limits the Rust compile above read (`IX_SHARING_LIMITS`). - let sharingLimits ← IO.ofExcept (← Ix.CompileM.compilerSharingLimitsFromEnv) - let (decompiled, decompErrors, _p2st) ← - Ix.DecompileM.decompileEnvFullParallel phases.compileEnv (some origView) - (sharingLimits := sharingLimits) - IO.println s!"[Step 2] {decompiled.size} constants, {decompErrors.size} errors in {(← IO.monoMsNow) - decompStart}ms" - IO.println "" - - -- Report errors - if !decompErrors.isEmpty then - IO.println s!"[Errors] First 20 errors:" - for (name, err) in decompErrors.toList.take 20 do - IO.println s!" {name}: {err}" - IO.println "" - - -- Count by constant type - let mut nDefn := (0 : Nat); let mut nAxiom := (0 : Nat) - let mut nInduct := (0 : Nat); let mut nCtor := (0 : Nat) - let mut nRec := (0 : Nat); let mut nQuot := (0 : Nat) - let mut nOpaque := (0 : Nat); let mut nThm := (0 : Nat) - for (_, info) in decompiled do - match info with - | .defnInfo _ => nDefn := nDefn + 1 - | .axiomInfo _ => nAxiom := nAxiom + 1 - | .inductInfo _ => nInduct := nInduct + 1 - | .ctorInfo _ => nCtor := nCtor + 1 - | .recInfo _ => nRec := nRec + 1 - | .quotInfo _ => nQuot := nQuot + 1 - | .opaqueInfo _ => nOpaque := nOpaque + 1 - | .thmInfo _ => nThm := nThm + 1 - IO.println s!"[Types] defn={nDefn}, thm={nThm}, opaque={nOpaque}, axiom={nAxiom}, induct={nInduct}, ctor={nCtor}, rec={nRec}, quot={nQuot}" - IO.println "" - - -- Step 3: Hash-based comparison against original Ix.Environment - let ixEnv := phases.rawEnv - IO.println s!"[Step 3] Original Ix.Environment has {ixEnv.consts.size} constants" - - IO.println s!"[Compare] Hash-comparing {decompiled.size} decompiled constants..." - let compareStart ← IO.monoMsNow - - -- Sequential hash comparison (cheap: just address equality on 32-byte hashes) - let mut nMatch := (0 : Nat); let mut nMismatch := (0 : Nat); let mut nMissing := (0 : Nat) - let mut firstMismatches : Array (Ix.Name × String) := #[] - -- Full structural comparison (`ConstantInfo` BEq — hash-based at - -- the Name/Level/Expr leaves): every field, not just type/value. - for (name, decompInfo) in decompiled do - match ixEnv.consts.get? name with - | some origInfo => - if decompInfo == origInfo then - nMatch := nMatch + 1 - else - nMismatch := nMismatch + 1 - if firstMismatches.size < 10 then - firstMismatches := firstMismatches.push (name, "constant-info mismatch") - | none => - nMissing := nMissing + 1 - if firstMismatches.size < 10 then - firstMismatches := firstMismatches.push (name, "not in original") - -- Reverse coverage: source constants never reconstructed. - let mut nMissingFromDecompile := (0 : Nat) - for (name, _) in ixEnv.consts do - if !decompiled.contains name then - nMissingFromDecompile := nMissingFromDecompile + 1 - if firstMismatches.size < 10 then - firstMismatches := firstMismatches.push (name, "missing from decompile") - nMissing := nMissing + nMissingFromDecompile - - let compareTime := (← IO.monoMsNow) - compareStart - IO.println s!"[Compare] Matched: {nMatch}, Mismatched: {nMismatch}, Missing: {nMissing} ({compareTime}ms)" - if !firstMismatches.isEmpty then - IO.println s!"[Compare] First mismatches:" - for (name, diff) in firstMismatches do - IO.println s!" {name}: {diff}" - IO.println "" - - let success := decompErrors.size == 0 && nMismatch == 0 && nMissing == 0 - if success then - return (true, 0, 0, none) - else - return (false, 0, 0, some s!"{decompErrors.size} decompilation errors") - ) .done - /-! ## Extension-table append (pure unit) `mkBlockCtx` extends the primary `refs`/`univs` tables with the @@ -205,8 +100,7 @@ end ExtensionAppend `metaSharing[i - p]` (`p ≤ i < p + j`); anything else is `invalidMetaShareIndex`. No compiler emits such a share today, so these hand-built tables are the only thing pinning the table check -(`validateMetaSharing`, which every constant's block setup runs; Rust -mirror: `meta_share_*` tests in `crates/compile/src/decompile.rs`). +(`validateMetaSharing`, which every constant's block setup runs). Every fixture has one primary entry (`p = 1`), `prim0 = (#1 #2)`. -/ @@ -300,10 +194,4 @@ public def unitSuite : List TestSeq := [ ++ test "metadata share: shared subterms are checked once" msSharedChain ] -/-! ## Test Suite -/ - -public def decompileSuiteIO : List TestSeq := [ - testDecompile, -] - end Tests.Decompile diff --git a/Tests/Ix/GraphM.lean b/Tests/Ix/GraphM.lean deleted file mode 100644 index 2d300ddd4..000000000 --- a/Tests/Ix/GraphM.lean +++ /dev/null @@ -1,494 +0,0 @@ -/- - Tests for Ix.GraphM module. - - Unit tests for reference extraction from expressions and constants - - Cross-implementation tests comparing Lean vs Rust graph construction --/ - -module -public import Ix.GraphM -public import Ix.Environment -public import Ix.Meta -public import LSpec - -open LSpec Ix - -namespace Tests.Ix.GraphM - -/-! ## Helper functions -/ - -/-- Create a simple Ix.Name from a string -/ -def mkName (s : String) : Ix.Name := Ix.Name.mkStr Ix.Name.mkAnon s - -/-- Create an Ix constant expression -/ -def mkConstExpr (s : String) : Ix.Expr := Ix.Expr.mkConst (mkName s) #[] - -/-- Simple type expression (Nat) -/ -def natType : Ix.Expr := mkConstExpr "Nat" - -/-- Create a simple ConstantVal -/ -def mkConstVal (name : String) (type : Ix.Expr) : Ix.ConstantVal := - { name := mkName name, levelParams := #[], type := type } - -/-- Convert Set to sorted list for deterministic comparison -/ -def setToSortedList (s : Set Ix.Name) : List String := - (s.toList.map toString).mergeSort - -/-! ## Test: graphExpr reference extraction -/ - -def testGraphExprConst : TestSeq := - let env : Ix.Environment := { consts := {} } - let expr := mkConstExpr "Foo" - let (refs, _) := Ix.GraphM.run env .init (Ix.graphExpr expr) - test "const extracts name" (refs.contains (mkName "Foo")) - -def testGraphExprBvar : TestSeq := - let env : Ix.Environment := { consts := {} } - let expr := Ix.Expr.mkBVar 0 - let (refs, _) := Ix.GraphM.run env .init (Ix.graphExpr expr) - test "bvar has no refs" (refs.isEmpty) - -def testGraphExprSort : TestSeq := - let env : Ix.Environment := { consts := {} } - let expr := Ix.Expr.mkSort Ix.Level.mkZero - let (refs, _) := Ix.GraphM.run env .init (Ix.graphExpr expr) - test "sort has no refs" (refs.isEmpty) - -def testGraphExprApp : TestSeq := - let env : Ix.Environment := { consts := {} } - let f := mkConstExpr "f" - let a := mkConstExpr "a" - let expr := Ix.Expr.mkApp f a - let (refs, _) := Ix.GraphM.run env .init (Ix.graphExpr expr) - test "app collects f" (refs.contains (mkName "f")) ++ - test "app collects a" (refs.contains (mkName "a")) ++ - test "app has 2 refs" (refs.size == 2) - -def testGraphExprLam : TestSeq := - let env : Ix.Environment := { consts := {} } - let ty := mkConstExpr "T" - let body := mkConstExpr "B" - let expr := Ix.Expr.mkLam (mkName "x") ty body .default - let (refs, _) := Ix.GraphM.run env .init (Ix.graphExpr expr) - test "lam collects type" (refs.contains (mkName "T")) ++ - test "lam collects body" (refs.contains (mkName "B")) ++ - test "lam has 2 refs" (refs.size == 2) - -def testGraphExprForallE : TestSeq := - let env : Ix.Environment := { consts := {} } - let ty := mkConstExpr "T" - let body := mkConstExpr "B" - let expr := Ix.Expr.mkForallE (mkName "x") ty body .default - let (refs, _) := Ix.GraphM.run env .init (Ix.graphExpr expr) - test "forallE collects type" (refs.contains (mkName "T")) ++ - test "forallE collects body" (refs.contains (mkName "B")) ++ - test "forallE has 2 refs" (refs.size == 2) - -def testGraphExprLetE : TestSeq := - let env : Ix.Environment := { consts := {} } - let ty := mkConstExpr "T" - let val := mkConstExpr "V" - let body := mkConstExpr "B" - let expr := Ix.Expr.mkLetE (mkName "x") ty val body false - let (refs, _) := Ix.GraphM.run env .init (Ix.graphExpr expr) - test "letE collects type" (refs.contains (mkName "T")) ++ - test "letE collects val" (refs.contains (mkName "V")) ++ - test "letE collects body" (refs.contains (mkName "B")) ++ - test "letE has 3 refs" (refs.size == 3) - -def testGraphExprProj : TestSeq := - let env : Ix.Environment := { consts := {} } - let struct := mkConstExpr "S" - let expr := Ix.Expr.mkProj (mkName "MyType") 0 struct - let (refs, _) := Ix.GraphM.run env .init (Ix.graphExpr expr) - test "proj collects typeName" (refs.contains (mkName "MyType")) ++ - test "proj collects struct" (refs.contains (mkName "S")) ++ - test "proj has 2 refs" (refs.size == 2) - -def testGraphExprLit : TestSeq := - let env : Ix.Environment := { consts := {} } - let expr := Ix.Expr.mkLit (.natVal 42) - let (refs, _) := Ix.GraphM.run env .init (Ix.graphExpr expr) - test "lit has no refs" (refs.isEmpty) - -/-! ## Test: graphConst for each ConstantInfo variant -/ - -def testGraphConstAxiom : TestSeq := - let env : Ix.Environment := { consts := {} } - let ty := mkConstExpr "T" - let ax := Ix.ConstantInfo.axiomInfo { - cnst := mkConstVal "myAxiom" ty, - isUnsafe := false - } - let (refs, _) := Ix.GraphM.run env .init (Ix.graphConst ax) - test "axiom refs type" (refs.contains (mkName "T")) ++ - test "axiom has 1 ref" (refs.size == 1) - -def testGraphConstDefn : TestSeq := - let env : Ix.Environment := { consts := {} } - let ty := mkConstExpr "T" - let val := mkConstExpr "V" - let defn := Ix.ConstantInfo.defnInfo { - cnst := mkConstVal "myDef" ty, - value := val, - hints := .opaque, - safety := .safe, - all := #[] - } - let (refs, _) := Ix.GraphM.run env .init (Ix.graphConst defn) - test "defn refs type" (refs.contains (mkName "T")) ++ - test "defn refs value" (refs.contains (mkName "V")) ++ - test "defn has 2 refs" (refs.size == 2) - -def testGraphConstThm : TestSeq := - let env : Ix.Environment := { consts := {} } - let ty := mkConstExpr "T" - let val := mkConstExpr "V" - let thm := Ix.ConstantInfo.thmInfo { - cnst := mkConstVal "myThm" ty, - value := val, - all := #[] - } - let (refs, _) := Ix.GraphM.run env .init (Ix.graphConst thm) - test "thm refs type" (refs.contains (mkName "T")) ++ - test "thm refs value" (refs.contains (mkName "V")) ++ - test "thm has 2 refs" (refs.size == 2) - -def testGraphConstOpaque : TestSeq := - let env : Ix.Environment := { consts := {} } - let ty := mkConstExpr "T" - let val := mkConstExpr "V" - let opq := Ix.ConstantInfo.opaqueInfo { - cnst := mkConstVal "myOpaque" ty, - value := val, - isUnsafe := false, - all := #[] - } - let (refs, _) := Ix.GraphM.run env .init (Ix.graphConst opq) - test "opaque refs type" (refs.contains (mkName "T")) ++ - test "opaque refs value" (refs.contains (mkName "V")) ++ - test "opaque has 2 refs" (refs.size == 2) - -def testGraphConstQuot : TestSeq := - let env : Ix.Environment := { consts := {} } - let ty := mkConstExpr "T" - let quot := Ix.ConstantInfo.quotInfo { - cnst := mkConstVal "myQuot" ty, - kind := .type - } - let (refs, _) := Ix.GraphM.run env .init (Ix.graphConst quot) - test "quot refs type" (refs.contains (mkName "T")) ++ - test "quot has 1 ref" (refs.size == 1) - -def testGraphConstInduct : TestSeq := - let env : Ix.Environment := { consts := {} } - let ty := mkConstExpr "T" - let induct := Ix.ConstantInfo.inductInfo { - cnst := mkConstVal "MyInductive" ty, - numParams := 0, - numIndices := 0, - all := #[mkName "MyInductive"], - ctors := #[mkName "MyInductive.mk"], - numNested := 0, - isRec := false, - isUnsafe := false, - isReflexive := false - } - let (refs, _) := Ix.GraphM.run env .init (Ix.graphConst induct) - test "induct refs type" (refs.contains (mkName "T")) ++ - test "induct refs ctor name" (refs.contains (mkName "MyInductive.mk")) ++ - test "induct has 2 refs" (refs.size == 2) - -def testGraphConstCtor : TestSeq := - let env : Ix.Environment := { consts := {} } - let ty := mkConstExpr "T" - let ctor := Ix.ConstantInfo.ctorInfo { - cnst := mkConstVal "MyType.mk" ty, - induct := mkName "MyType", - cidx := 0, - numParams := 0, - numFields := 0, - isUnsafe := false - } - let (refs, _) := Ix.GraphM.run env .init (Ix.graphConst ctor) - test "ctor refs type" (refs.contains (mkName "T")) ++ - test "ctor refs induct" (refs.contains (mkName "MyType")) ++ - test "ctor has 2 refs" (refs.size == 2) - -def testGraphConstRec : TestSeq := - let env : Ix.Environment := { consts := {} } - let ty := mkConstExpr "T" - let rhs := mkConstExpr "R" - let recConst := Ix.ConstantInfo.recInfo { - cnst := { name := Ix.Name.mkStr (mkName "MyType") "rec", levelParams := #[], type := ty }, - all := #[mkName "MyType"], - numParams := 0, - numIndices := 0, - numMotives := 1, - numMinors := 1, - rules := #[{ ctor := mkName "MyType.mk", nfields := 0, rhs := rhs }], - k := false, - isUnsafe := false - } - let (refs, _) := Ix.GraphM.run env .init (Ix.graphConst recConst) - test "rec refs type" (refs.contains (mkName "T")) ++ - test "rec refs ctor name" (refs.contains (mkName "MyType.mk")) ++ - test "rec refs rhs" (refs.contains (mkName "R")) ++ - test "rec has 3 refs" (refs.size == 3) - -/-- A recursor of a mutual block refers to its family: the other members' -recursors and the nested auxiliaries' `rec_N`, not to itself. -/ -def testGraphConstRecFamily : TestSeq := - let env : Ix.Environment := { consts := {} } - let a := mkName "A" - let b := mkName "B" - let recConst := Ix.ConstantInfo.recInfo { - cnst := { name := Ix.Name.mkStr b "rec", levelParams := #[], type := mkConstExpr "T" }, - all := #[a, b], - numParams := 0, - numIndices := 0, - numMotives := 3, - numMinors := 0, - rules := #[], - k := false, - isUnsafe := false - } - let (refs, _) := Ix.GraphM.run env .init (Ix.graphConst recConst) - test "mutual rec refs its sibling's recursor" (refs.contains (Ix.Name.mkStr a "rec")) ++ - test "mutual rec refs the nested auxiliary's recursor" (refs.contains (Ix.Name.mkStr a "rec_1")) ++ - test "mutual rec does not ref itself" (!refs.contains (Ix.Name.mkStr b "rec")) ++ - test "mutual rec has 3 refs" (refs.size == 3) - -/-! ## Test: GraphM.env builds complete graph -/ - -def testGraphMEnv : TestSeq := - -- Create a small synthetic environment - let a := mkName "A" - let b := mkName "B" - let c := mkName "C" - - -- A: type refs B - let aConst := Ix.ConstantInfo.axiomInfo { - cnst := { name := a, levelParams := #[], type := mkConstExpr "B" }, - isUnsafe := false - } - -- B: type refs C - let bConst := Ix.ConstantInfo.axiomInfo { - cnst := { name := b, levelParams := #[], type := mkConstExpr "C" }, - isUnsafe := false - } - -- C: type refs nothing (just a sort) - let cConst := Ix.ConstantInfo.axiomInfo { - cnst := { name := c, levelParams := #[], type := Ix.Expr.mkSort Ix.Level.mkZero }, - isUnsafe := false - } - - let env : Ix.Environment := { - consts := ({} : Std.HashMap _ _).insert a aConst |>.insert b bConst |>.insert c cConst - } - - let graph := Ix.GraphM.env env - test "graph has 3 entries" (graph.size == 3) ++ - test "A refs B" ((graph.get? a).map (·.contains b) |>.getD false) ++ - test "B refs C" ((graph.get? b).map (·.contains c) |>.getD false) ++ - test "C refs empty" ((graph.get? c).map (·.isEmpty) |>.getD false) - -/-! ## Test: envParallel vs envSerial equivalence -/ - -def testEnvParallelVsSerial : TestSeq := Id.run do - -- Create a synthetic environment - let names := #["A", "B", "C", "D", "E"].map mkName - let mut consts : Std.HashMap Ix.Name Ix.ConstantInfo := {} - - -- Create chain: A->B->C->D->E->() - for i in [:names.size] do - let name := names[i]! - let ty := if i + 1 < names.size - then Ix.Expr.mkConst names[i+1]! #[] - else Ix.Expr.mkSort Ix.Level.mkZero - let c := Ix.ConstantInfo.axiomInfo { - cnst := { name := name, levelParams := #[], type := ty }, - isUnsafe := false - } - consts := consts.insert name c - - let env : Ix.Environment := { consts := consts } - - let graphParallel := Ix.GraphM.envParallel env - let graphSerial := Ix.GraphM.envSerial env - - -- Compare sizes - let sizeMatch := graphParallel.size == graphSerial.size - - -- Compare all entries - let mut allMatch := true - for (name, parallelRefs) in graphParallel do - match graphSerial.get? name with - | none => allMatch := false - | some serialRefs => - if parallelRefs.size != serialRefs.size then - allMatch := false - else - for r in parallelRefs do - if !serialRefs.contains r then - allMatch := false - - return test "sizes match" sizeMatch ++ - test "all entries match" allMatch - -/-! ## Full Test Suite (unit tests) -/ - -public def suite : List TestSeq := [ - group "graphExpr" ( - testGraphExprConst ++ - testGraphExprBvar ++ - testGraphExprSort ++ - testGraphExprApp ++ - testGraphExprLam ++ - testGraphExprForallE ++ - testGraphExprLetE ++ - testGraphExprProj ++ - testGraphExprLit - ), - group "graphConst" ( - testGraphConstAxiom ++ - testGraphConstDefn ++ - testGraphConstThm ++ - testGraphConstOpaque ++ - testGraphConstQuot ++ - testGraphConstInduct ++ - testGraphConstCtor ++ - testGraphConstRec ++ - testGraphConstRecFamily - ), - group "GraphM.env" ( - testGraphMEnv ++ - testEnvParallelVsSerial - ) -] - -/-! ## Cross-Implementation Tests (Lean vs Rust) -/ - -/-- Canonicalize environment in Rust (fast). Returns Ix.RawEnvironment. -/ -@[extern "rs_canonicalize_env_to_ix"] -opaque rsCanonicalizeEnvToIxRaw : - @& List (Lean.Name × Lean.ConstantInfo) → IO Ix.RawEnvironment - -/-- Build reference graph in Rust. - Returns Array (Ix.Name × Array Ix.Name) -/ -@[extern "rs_build_ref_graph"] -opaque rsBuildRefGraph : @& List (Lean.Name × Lean.ConstantInfo) → - IO (Array (Ix.Name × Array Ix.Name)) - -/-- Convert Rust's ref graph array to a HashMap for comparison. -/ -def rustRefGraphToMap (arr : Array (Ix.Name × Array Ix.Name)) - : Std.HashMap Ix.Name (Std.HashSet Ix.Name) := Id.run do - let mut m : Std.HashMap Ix.Name (Std.HashSet Ix.Name) := {} - for (name, refs) in arr do - let refSet := refs.foldl (init := {}) fun s n => s.insert n - m := m.insert name refSet - return m - -/-- Convert Lean's ref graph (Map) to a HashMap for comparison. -/ -def leanRefGraphToHashMap (m : Ix.Map Ix.Name (Ix.Set Ix.Name)) - : Std.HashMap Ix.Name (Std.HashSet Ix.Name) := Id.run do - let mut result : Std.HashMap Ix.Name (Std.HashSet Ix.Name) := {} - for (name, refs) in m do - let refSet : Std.HashSet Ix.Name := refs.fold (init := {}) fun s n => s.insert n - result := result.insert name refSet - return result - -/-- Compare two reference graphs for equality. Returns (isEqual, mismatches). -/ -def compareRefGraphs (lean rust : Std.HashMap Ix.Name (Std.HashSet Ix.Name)) - : Bool × Array String := Id.run do - let mut mismatches : Array String := #[] - - -- Check all entries in Lean's graph - for (name, leanRefs) in lean do - match rust.get? name with - | none => - if mismatches.size < 5 then - mismatches := mismatches.push s!"Missing in Rust: {name}" - | some rustRefs => - let leanSize := leanRefs.size - let rustSize := rustRefs.size - if leanSize != rustSize then - if mismatches.size < 5 then - mismatches := mismatches.push s!"Size mismatch for {name}: Lean={leanSize}, Rust={rustSize}" - else - for r in leanRefs do - if !rustRefs.contains r then - if mismatches.size < 5 then - mismatches := mismatches.push s!"{name}: ref {r} in Lean but not Rust" - break - - -- Check for extra entries in Rust - for (name, _) in rust do - if lean.get? name |>.isNone then - if mismatches.size < 5 then - mismatches := mismatches.push s!"Extra in Rust: {name}" - - return (mismatches.isEmpty, mismatches) - -/-- Cross-implementation test: compare Lean and Rust reference graph construction -/ -def testRefGraphComparison : TestSeq := - .individualIO "Reference Graph: Lean vs Rust" none (do - let env ← get_env! - let numConsts := env.constants.toList.length - - IO.println s!"[Test] Reference Graph Comparison Test" - IO.println s!"[Test] Environment has {numConsts} constants" - IO.println "" - - -- Step 0: Canonicalize environment using Rust FFI (fast) - IO.println s!"[Test] Step 0: Canonicalizing environment via Rust FFI..." - let canonStart ← IO.monoMsNow - let rawEnv ← rsCanonicalizeEnvToIxRaw env.constants.toList - let ixEnv := rawEnv.toEnvironment - let canonTime := (← IO.monoMsNow) - canonStart - IO.println s!"[Test] Canonicalized {ixEnv.consts.size} constants in {canonTime}ms" - IO.println "" - - -- Step 1: Build reference graph in Rust - IO.println s!"[Test] Step 1: Building reference graph in Rust..." - let rustStart ← IO.monoMsNow - let rustRefArr ← rsBuildRefGraph env.constants.toList - let rustTime := (← IO.monoMsNow) - rustStart - IO.println s!"[Test] Rust: {rustRefArr.size} entries in {rustTime}ms" - - -- Step 2: Build reference graph in Lean (using pre-canonicalized environment) - IO.println s!"[Test] Step 2: Building reference graph in Lean..." - let leanStart ← IO.monoMsNow - let leanRefMap := Ix.GraphM.envParallel ixEnv - IO.println s!"[Test] Lean: {leanRefMap.size} entries " - let leanTime := (← IO.monoMsNow) - leanStart - IO.print s!"in {leanTime}ms" - IO.println "" - - -- Step 3: Compare results - IO.println s!"[Test] Step 3: Comparing results..." - let rustMap := rustRefGraphToMap rustRefArr - let leanMap := leanRefGraphToHashMap leanRefMap - let (isEqual, mismatches) := compareRefGraphs leanMap rustMap - - for msg in mismatches do - IO.println s!"[Test] {msg}" - - IO.println "" - IO.println s!"[Test] Summary:" - IO.println s!"[Test] Canon time: {canonTime}ms" - IO.println s!"[Test] Rust graph time: {rustTime}ms" - IO.println s!"[Test] Lean graph time: {leanTime}ms" - IO.println s!"[Test] Match: {isEqual}" - - if !isEqual then - return (false, 0, 0, some s!"Reference graphs do not match: {mismatches.size} mismatches") - - return (true, 0, 0, none) - ) .done - -/-- Cross-implementation test suite (expensive, run with --ignored) -/ -public def suiteIO : List TestSeq := [ - testRefGraphComparison -] - -end Tests.Ix.GraphM diff --git a/Tests/Ix/GroundTests.lean b/Tests/Ix/GroundTests.lean deleted file mode 100644 index 0b36ff4c5..000000000 --- a/Tests/Ix/GroundTests.lean +++ /dev/null @@ -1,386 +0,0 @@ -module - -public import LSpec -public import Ix.Ground -public import Ix.Compile.FrontEnd -public import Ix.Cert.Compile - -/-! -Unit tests for `Ix.Ground` (the pure-Lean port of -`crates/compile/src/ground.rs`). - -Coverage mirrors the Rust `#[cfg(test)]` module in ground.rs (grounded -constants, missing refs, out-of-scope bvars, mvars, level params, -inductive ctor checks, depth increments, fvars, propagation) plus -Lean-side checks of the exact traversal/cache semantics: the -`(binder-depth, expr)` cache key, sub-check ordering (ctors before -type, rec rules before type, const levels before env lookup, proj env -lookup before struct), and worklist termination on cyclic in-ref -graphs. - -Per the contract documented on `Ix.proliferateUngrounded`, everything -transitive is asserted by error KIND only (`GroundError.kind`) — never -by transitive `.ref` attribution payloads, which are pop-order -dependent. Immediate-error payloads (which ARE order-independent) are -asserted where the Rust tests assert them. --/ - -public section - -namespace Tests.Ground - -open LSpec -open Ix (Name Level Expr Environment ConstantInfo RecursorRule - GroundError GroundErrorKind groundConstCheck groundConsts - proliferateUngrounded) - -/-! ## Construction helpers -/ - -def nm (s : String) : Name := Name.mkStr .mkAnon s - -def sort0 : Expr := Expr.mkSort Level.mkZero - -def axiomC (name : String) (typ : Expr) (lps : Array Name := #[]) : ConstantInfo := - .axiomInfo { cnst := { name := nm name, levelParams := lps, type := typ }, - isUnsafe := false } - -def defnC (name : String) (typ value : Expr) : ConstantInfo := - .defnInfo { cnst := { name := nm name, levelParams := #[], type := typ }, - value, hints := .opaque, safety := .safe, all := #[nm name] } - -def thmC (name : String) (typ value : Expr) : ConstantInfo := - .thmInfo { cnst := { name := nm name, levelParams := #[], type := typ }, - value, all := #[nm name] } - -def opaqueC (name : String) (typ value : Expr) : ConstantInfo := - .opaqueInfo { cnst := { name := nm name, levelParams := #[], type := typ }, - value, isUnsafe := false, all := #[nm name] } - -def quotC (name : String) (typ : Expr) : ConstantInfo := - .quotInfo { cnst := { name := nm name, levelParams := #[], type := typ }, - kind := .type } - -def ctorC (name : String) (induct : String) (typ : Expr := sort0) : ConstantInfo := - .ctorInfo { cnst := { name := nm name, levelParams := #[], type := typ }, - induct := nm induct, cidx := 0, numParams := 0, numFields := 0, - isUnsafe := false } - -def inductC (name : String) (ctors : Array Name) (typ : Expr := sort0) : - ConstantInfo := - .inductInfo { cnst := { name := nm name, levelParams := #[], type := typ }, - numParams := 0, numIndices := 0, all := #[nm name], ctors, - numNested := 0, isRec := false, isUnsafe := false, - isReflexive := false } - -def recC (name : String) (rules : Array RecursorRule) (typ : Expr := sort0) : - ConstantInfo := - .recInfo { cnst := { name := nm name, levelParams := #[], type := typ }, - all := #[nm name], numParams := 0, numIndices := 0, - numMotives := 1, numMinors := 0, rules, k := false, - isUnsafe := false } - -def mkEnv (cs : List (Name × ConstantInfo)) : Environment := - { consts := Std.HashMap.ofList cs } - -def refsMap (l : List (Name × List Name)) : Ix.Map Name (Ix.Set Name) := - l.foldl (init := {}) fun m (k, vs) => - m.insert k (vs.foldl (init := ({} : Ix.Set Name)) fun s v => s.insert v) - -/-! ## Assertion helpers (kind-based, per the proliferation contract) -/ - -/-- Per-constant check result as an error, `none` = grounded. -/ -def checkErr? (c : ConstantInfo) (env : Environment) : Option GroundError := - match groundConstCheck c env with - | .ok _ => none - | .error e => some e - -def checkKind? (c : ConstantInfo) (env : Environment) : Option GroundErrorKind := - (checkErr? c env).map (·.kind) - -def kindAt? (errs : Ix.Map Name GroundError) (s : String) : - Option GroundErrorKind := - (errs.get? (nm s)).map (·.kind) - -def refPayload? : GroundError → Option Name - | .ref n => some n - | _ => none - -def varBinds? : GroundError → Option Nat - | .var _ b => some b - | _ => none - -def indcInfoIsSome? : GroundError → Option Bool - | .indc _ ci => some ci.isSome - | _ => none - -/-! ## Grounded constants (Rust: grounded_axiom, -grounded_defn_with_bvar_in_lam, grounded_level_param_when_declared) -/ - -def groundedTests : TestSeq := - test "grounded axiom: Sort 0 type, empty groundConsts result" - ((let a := axiomC "A" sort0 - let env := mkEnv [(nm "A", a)] - checkKind? a env == none - && (groundConsts env (refsMap [(nm "A", [])])).isEmpty : Bool)) - ++ test "grounded defn: fun (_ : Sort 0) => #0 (bvar under one binder)" - ((let f := defnC "f" sort0 - (Expr.mkLam .mkAnon sort0 (Expr.mkBVar 0) .default) - let env := mkEnv [(nm "f", f)] - checkKind? f env == none && (groundConsts env {}).isEmpty : Bool)) - ++ test "grounded level param: A.{u} : Sort u with u declared" - ((let a := axiomC "A" (Expr.mkSort (Level.mkParam (nm "u"))) #[nm "u"] - checkKind? a (mkEnv [(nm "A", a)]) == none : Bool)) - ++ test "letE depths: body at binds+1 grounded, value at binds escapes" - ((let ok := axiomC "Ok" (Expr.mkLetE .mkAnon sort0 sort0 (Expr.mkBVar 0) false) - let bad := axiomC "Bad" (Expr.mkLetE .mkAnon sort0 (Expr.mkBVar 0) sort0 false) - let env := mkEnv [(nm "Ok", ok), (nm "Bad", bad)] - checkKind? ok env == none && checkKind? bad env == some .var : Bool)) - ++ test "mdata is transparent (same binder depth)" - ((let b := axiomC "B" sort0 - let a := axiomC "A" (Expr.mkMData #[] (Expr.mkConst (nm "B") #[])) - let env := mkEnv [(nm "A", a), (nm "B", b)] - checkKind? a env == none : Bool)) - ++ test "lit is grounded" - ((let a := axiomC "L" (Expr.mkLit (.natVal 42)) - checkKind? a (mkEnv [(nm "L", a)]) == none : Bool)) - ++ test "inductive with a proper ctor is grounded" - ((let t := inductC "T" #[nm "T.mk"] - let mk := ctorC "T.mk" "T" - let env := mkEnv [(nm "T", t), (nm "T.mk", mk)] - checkKind? t env == none && (groundConsts env {}).isEmpty : Bool)) - -/-! ## Immediate error kinds (Rust: ungrounded_missing_ref, -ungrounded_bvar_out_of_scope, ungrounded_mvar, ungrounded_level_param, -inductive_missing_ctor, inductive_ctor_wrong_kind, -binding_increments_depth, fvar_is_ungrounded) -/ - -def immediateErrorTests : TestSeq := - test "missing const ref: A : B with B absent -> ref B" - ((let a := axiomC "A" (Expr.mkConst (nm "B") #[]) - let env := mkEnv [(nm "A", a)] - checkKind? a env == some .ref - && (checkErr? a env).bind refPayload? == some (nm "B") : Bool)) - ++ test "bvar with no enclosing binder -> var at binds 0" - ((let a := axiomC "A" (Expr.mkBVar 0) - let env := mkEnv [(nm "A", a)] - checkKind? a env == some .var - && (checkErr? a env).bind varBinds? == some 0 : Bool)) - ++ test "lam: #0 grounded, #1 escapes with binds 1" - ((let ok := defnC "ok" sort0 - (Expr.mkLam .mkAnon sort0 (Expr.mkBVar 0) .default) - let bad := defnC "bad" sort0 - (Expr.mkLam .mkAnon sort0 (Expr.mkBVar 1) .default) - let env := mkEnv [(nm "ok", ok), (nm "bad", bad)] - checkKind? ok env == none - && checkKind? bad env == some .var - && (checkErr? bad env).bind varBinds? == some 1 : Bool)) - ++ test "forallE: #0 grounded, #1 escapes" - ((let ok := axiomC "Ok" (Expr.mkForallE .mkAnon sort0 (Expr.mkBVar 0) .default) - let bad := axiomC "Bad" (Expr.mkForallE .mkAnon sort0 (Expr.mkBVar 1) .default) - let env := mkEnv [(nm "Ok", ok), (nm "Bad", bad)] - checkKind? ok env == none && checkKind? bad env == some .var : Bool)) - ++ test "undeclared level param -> level" - ((let a := axiomC "A" (Expr.mkSort (Level.mkParam (nm "u"))) - checkKind? a (mkEnv [(nm "A", a)]) == some .level : Bool)) - ++ test "level mvar -> level" - ((let a := axiomC "A" (Expr.mkSort (Level.mkMvar (nm "m"))) - checkKind? a (mkEnv [(nm "A", a)]) == some .level : Bool)) - ++ test "level params checked inside succ/max/imax spines" - ((let u := Level.mkParam (nm "u") - let a := axiomC "A" (Expr.mkSort (Level.mkSucc (Level.mkMax Level.mkZero - (Level.mkIMax u Level.mkZero)))) - checkKind? a (mkEnv [(nm "A", a)]) == some .level : Bool)) - ++ test "expression mvar -> mvar" - ((let a := axiomC "A" (Expr.mkMVar (nm "m")) - checkKind? a (mkEnv [(nm "A", a)]) == some .mvar : Bool)) - ++ test "fvar -> var at binds 0" - ((let a := axiomC "A" (Expr.mkFVar (nm "x")) - let env := mkEnv [(nm "A", a)] - checkKind? a env == some .var - && (checkErr? a env).bind varBinds? == some 0 : Bool)) - ++ test "inductive missing ctor -> indc with info none" - ((let t := inductC "T" #[nm "T.mk"] - let env := mkEnv [(nm "T", t)] - checkKind? t env == some .indc - && (checkErr? t env).bind indcInfoIsSome? == some false : Bool)) - ++ test "inductive ctor of wrong kind -> indc with info some" - ((let t := inductC "T" #[nm "T.mk"] - let env := mkEnv [(nm "T", t), (nm "T.mk", axiomC "T.mk" sort0)] - checkKind? t env == some .indc - && (checkErr? t env).bind indcInfoIsSome? == some true : Bool)) - ++ test "thm and opaque check their values" - ((let t := thmC "t" sort0 (Expr.mkMVar (nm "m")) - let o := opaqueC "o" sort0 (Expr.mkFVar (nm "x")) - let env := mkEnv [(nm "t", t), (nm "o", o)] - checkKind? t env == some .mvar && checkKind? o env == some .var : Bool)) - ++ test "quot and ctor check their types" - ((let q := quotC "q" (Expr.mkBVar 0) - let c := ctorC "c" "T" (Expr.mkMVar (nm "m")) - let env := mkEnv [(nm "q", q), (nm "c", c)] - checkKind? q env == some .var && checkKind? c env == some .mvar : Bool)) - -/-! ## Traversal-order and cache-key semantics -/ - -def orderingCacheTests : TestSeq := - test "inductive: ctor presence checked BEFORE its own type" - ((let t := inductC "T" #[nm "T.mk"] (Expr.mkConst (nm "Missing") #[]) - checkKind? t (mkEnv [(nm "T", t)]) == some .indc : Bool)) - ++ test "recursor: rule rhs checked BEFORE its own type" - ((let r := recC "R" - #[{ ctor := nm "T.mk", nfields := 0, - rhs := Expr.mkConst (nm "Missing") #[] }] - (Expr.mkMVar (nm "m")) - checkKind? r (mkEnv [(nm "R", r)]) == some .ref : Bool)) - ++ test "defn: type checked BEFORE value" - ((let f := defnC "f" (Expr.mkConst (nm "Missing") #[]) (Expr.mkMVar (nm "m")) - checkKind? f (mkEnv [(nm "f", f)]) == some .ref : Bool)) - ++ test "const: level args checked BEFORE the env lookup" - ((let a := axiomC "A" (Expr.mkConst (nm "B") #[Level.mkParam (nm "u")]) - -- u undeclared AND B missing: levels first -> level, not ref - checkKind? a (mkEnv [(nm "A", a)]) == some .level : Bool)) - ++ test "proj: type-name env lookup BEFORE recursing into the struct" - ((let a := axiomC "A" (Expr.mkProj (nm "Missing") 0 (Expr.mkMVar (nm "m"))) - checkKind? a (mkEnv [(nm "A", a)]) == some .ref : Bool)) - ++ test "proj: struct recursed once the type name resolves" - ((let s := axiomC "S" sort0 - let a := axiomC "A" (Expr.mkProj (nm "S") 0 (Expr.mkMVar (nm "m"))) - let env := mkEnv [(nm "S", s), (nm "A", a)] - checkKind? a env == some .mvar : Bool)) - ++ test "expr cache keys on (binder-depth, expr): #0 rechecked at depth 0" - ((-- (fun (_ : Sort 0) => #0) #0 — the App visits the lam first, - -- caching #0 at depth 1 (in scope); the argument #0 at depth 0 - -- must MISS the cache and fail. An expr-only cache key would - -- wrongly skip it and report grounded. - let e := Expr.mkApp - (Expr.mkLam .mkAnon sort0 (Expr.mkBVar 0) .default) - (Expr.mkBVar 0) - let a := axiomC "A" e - let env := mkEnv [(nm "A", a)] - checkKind? a env == some .var - && (checkErr? a env).bind varBinds? == some 0 : Bool)) - -/-! ## Transitive proliferation (Rust: propagation_through_in_refs). -Kind-only assertions for everything transitive. -/ - -def proliferationTests : TestSeq := - test "A refs ungrounded B -> both ungrounded; bystander D grounded" - ((let b := axiomC "B" (Expr.mkConst (nm "C") #[]) -- C not in env - let a := axiomC "A" (Expr.mkConst (nm "B") #[]) - let d := axiomC "D" sort0 - let env := mkEnv [(nm "B", b), (nm "A", a), (nm "D", d)] - let inRefs := refsMap - [(nm "C", [nm "B"]), (nm "B", [nm "A"]), (nm "A", []), (nm "D", [])] - let errs := groundConsts env inRefs - errs.size == 2 - && kindAt? errs "B" == some .ref - && kindAt? errs "A" == some .ref - -- immediate attribution (B's own error) IS order-independent: - && (errs.get? (nm "B")).bind refPayload? == some (nm "C") - && (errs.get? (nm "D")).isNone : Bool)) - ++ test "three-hop chain: result SET invariant under env insertion order" - ((let c := axiomC "C" (Expr.mkConst (nm "X") #[]) -- X not in env - let b := axiomC "B" (Expr.mkConst (nm "C") #[]) - let a := axiomC "A" (Expr.mkConst (nm "B") #[]) - let d := axiomC "D" sort0 - let l := [(nm "A", a), (nm "B", b), (nm "C", c), (nm "D", d)] - let inRefs := refsMap - [(nm "X", [nm "C"]), (nm "C", [nm "B"]), (nm "B", [nm "A"])] - let e1 := groundConsts (mkEnv l) inRefs - let e2 := groundConsts (mkEnv l.reverse) inRefs - e1.size == 3 && e2.size == 3 - && ["A", "B", "C"].all (fun s => - kindAt? e1 s == some .ref && kindAt? e2 s == some .ref) - && (e1.get? (nm "D")).isNone && (e2.get? (nm "D")).isNone : Bool)) - ++ test "proliferateUngrounded: vacant-only inserts, grounded refs ignored" - ((let seed : Ix.Map Name GroundError := - ({} : Ix.Map Name GroundError).insert (nm "B") (.ref (nm "X")) - -- G is grounded (not in seed): its in-refs must NOT leak in. - let inRefs := refsMap [(nm "B", [nm "A"]), (nm "G", [nm "H"])] - let out := proliferateUngrounded seed inRefs - out.size == 2 - -- the seeded (immediate) entry is never overwritten: - && (out.get? (nm "B")).bind refPayload? == some (nm "X") - && kindAt? out "A" == some .ref - && (out.get? (nm "H")).isNone : Bool)) - ++ test "proliferateUngrounded terminates on cyclic in-ref graphs" - ((let seed : Ix.Map Name GroundError := - ({} : Ix.Map Name GroundError).insert (nm "B") (.ref (nm "C")) - -- A and B reference each other; the visited-set must break the cycle. - let inRefs := refsMap - [(nm "C", [nm "B"]), (nm "B", [nm "A"]), (nm "A", [nm "B"])] - let out := proliferateUngrounded seed inRefs - out.size == 2 - && kindAt? out "A" == some .ref - && kindAt? out "B" == some .ref : Bool)) - -/-- Keep a literal serial loop so a compiler substitution cannot make the -runtime parity check compare the batched implementation with itself. -/ -def serialGraph (env : Environment) : Ix.Map Name (Ix.Set Name) := Id.run do - let mut out : Ix.Map Name (Ix.Set Name) := {} - for (name, ci) in env.consts do - let (refs, _) := Ix.GraphM.run env .init (Ix.graphConst ci) - out := out.insert name refs - return out - -def serialGround (env : Environment) (reverse : Ix.Map Name (Ix.Set Name)) : - Ix.Map Name GroundError := Id.run do - let mut out : Ix.Map Name GroundError := {} - for (name, ci) in env.consts do - match groundConstCheck ci env with - | .error error => out := out.insert name error - | .ok _ => pure () - return proliferateUngrounded out reverse - -def frontEndParity (env : Environment) : Bool := Id.run do - let expected := serialGraph env - let actual := Ix.Compile.FrontEnd.graph env - -- Repr retains complete names, stored hashes and error payloads, and the - -- list order checks that insertion and propagation order stayed unchanged. - if reprStr expected.toList != reprStr actual.toList then return false - let reverse := expected.fold (init := ({} : Ix.Map Name (Ix.Set Name))) fun out name refs => - refs.fold (init := out) fun out ref => out.alter ref fun - | some names => some (names.insert name) - | none => some (({} : Ix.Set Name).insert name) - let failures := serialGround env reverse - if reprStr failures.toList != reprStr (Ix.Compile.FrontEnd.ground env reverse).toList then - return false - let filtered := if failures.isEmpty then expected else - expected.fold (init := {}) fun out name refs => - if failures.contains name then out - else out.insert name (refs.fold (init := {}) fun kept ref => - if failures.contains ref then kept else kept.insert ref) - let split := Ix.Cert.groundSplit env - return reprStr filtered.toList == reprStr split.1.toList && - reprStr failures.toList == reprStr split.2.toList - -def frontEndParityTests : TestSeq := Id.run do - let missing := Expr.mkConst (nm "missing") #[] - let badLevel := Expr.mkSort (Level.mkParam (nm "undeclared")) - let fixtures : List (Name × ConstantInfo) := - [(nm "Valid", axiomC "Valid" sort0), - (nm "Missing", axiomC "Missing" missing), - (nm "Bound", axiomC "Bound" (Expr.mkBVar 0)), - (nm "Level", axiomC "Level" badLevel), - (nm "TypeFirst", defnC "TypeFirst" missing (Expr.mkBVar 1)), - (nm "CtorMissing", inductC "CtorMissing" #[nm "absentCtor"]), - (nm "CtorWrong", inductC "CtorWrong" #[nm "Valid"]), - (nm "Choice", defnC "Choice" (Expr.mkConst (nm "Missing") #[]) - (Expr.mkConst (nm "Bound") #[])), - (nm "CycleA", defnC "CycleA" (Expr.mkConst (nm "CycleB") #[]) missing), - (nm "CycleB", axiomC "CycleB" (Expr.mkConst (nm "CycleA") #[]))] - let many := (List.range 1025).map fun i => - let name := s!"entry{i}" - let typ := if i % 17 == 0 then missing - else if i % 3 == 0 then Expr.mkConst (nm s!"entry{i - 1}") #[] - else sort0 - (nm name, axiomC name typ) - return test "batched graph and ground preserve complete refusals and insertion order" - ([mkEnv [], mkEnv [(nm "Valid", axiomC "Valid" sort0)], mkEnv fixtures, - mkEnv fixtures.reverse, mkEnv many, mkEnv many.reverse].all frontEndParity) - -public def suite : List TestSeq := - [groundedTests, immediateErrorTests, orderingCacheTests, proliferationTests, frontEndParityTests] - -end Tests.Ground - -end diff --git a/Tests/Ix/ImportIxe.lean b/Tests/Ix/ImportIxe.lean index 0abe993b7..42622142a 100644 --- a/Tests/Ix/ImportIxe.lean +++ b/Tests/Ix/ImportIxe.lean @@ -1,4 +1,20 @@ -/- +module + +public import LSpec +public import Ix.Ixon +public import Ix.CompileCertified +public import Ix.Merkle +public import Ix.ImportIxe +-- The C8 consumer subprocess `import`s Ix.IxEval; importing it here keeps +-- its olean in the test target's build closure (hermetic builds ship only +-- that closure — a local `lake build ix` leftover masked this once). +public import Ix.IxEval +public import Ix.Replay +public import Ix.CompileM +public import Ix.Commit +public import Ix.Meta + +/-! Materialization FFI + `import_ixe` tests (plan C6/C8). A self-contained fixture environment is built by direct kernel @@ -17,7 +33,7 @@ - Root equality: recompiling the complete materialized source output reproduces the original canonical root — compile ∘ materialize ∘ compile is a fixed point, so every computed field (hashes, cached - flags) the Rust side produced is byte-faithful under re-reading. + flags) the compiler produced is byte-faithful under re-reading. - Fresh kernel replay: `Ix.Replay.planDeclarations` consumes the complete materialized source map and replays clean into an empty kernel env (the non-elaborator core of `import_ixe`); every source @@ -28,19 +44,6 @@ defines a term over the materialized constants (interpreter + linked FFI, `supportInterpreter`). -/ -module - -public import LSpec -public import Ix.Ixon -public import Ix.ImportIxe --- The C8 consumer subprocess `import`s Ix.IxEval; importing it here keeps --- its olean in the test target's build closure (hermetic builds ship only --- that closure — a local `lake build ix` leftover masked this once). -public import Ix.IxEval -public import Ix.Replay -public import Ix.CompileM -public import Ix.Commit -public import Ix.Meta public section @@ -257,16 +260,23 @@ private def compareCI (name : Lean.Name) (a b : Lean.ConstantInfo) : /-! ### The tests -/ +private def compileFixtureToFile (consts : List (Lean.Name × Lean.ConstantInfo)) + (path : String) : IO (String × Nat) := do + let (env, failures) ← IO.ofExcept (Ix.Cert.compileReport (Ix.CompileM.canonInput consts.toArray 4)) + IO.FS.writeBinFile path (← IO.ofExcept (Ixon.serEnv env)) + let root := toString ((Ix.Merkle.merkleRootCanonical (env.consts.toList.map (·.1)).toArray).getD Ix.Merkle.zeroAddress) + return (root, failures.toMap.size) + /-- C6 parity, source root equality and fresh replay. -/ private def roundtripTest : IO (Bool × Nat × Nat × Option String) := do let dir ← IO.FS.createTempDir let path := (dir / "fixture.ixe").toString try let original ← buildFixtureConsts - let status ← Ix.CompileM.rsCompileEnvBytesFFI original.toList path false - if status.ungrounded.size > 0 then + let (root1, ungrounded1) ← compileFixtureToFile original.toList path + if ungrounded1 > 0 then return (false, 0, 0, - some s!"fixture compile ungrounded: {status.ungrounded}") + some s!"fixture compile ungrounded: {ungrounded1}") -- C6 parity: materialize everything and compare per constant. Source -- import restores the complete original declaration set. let materialized ← Ix.ImportIxe.materializeIxe path @@ -288,11 +298,10 @@ private def roundtripTest : IO (Bool × Nat × Nat × Option String) := do -- Root equality: recompiling the complete materialized source output is -- a fixed point of the canonical root. let path2 := (dir / "rebuilt.ixe").toString - let status2 ← Ix.CompileM.rsCompileEnvBytesFFI materialized.toList - path2 false - if status2.root != status.root then - return (false, 0, 0, some s!"root drift: {status.root.take 12}… → \ -{status2.root.take 12}…") + let (root2, _) ← compileFixtureToFile materialized.toList path2 + if root2 != root1 then + return (false, 0, 0, some s!"root drift: {root1.take 12}… → \ +{root2.take 12}…") -- Fresh kernel replay via the shared planner (import_ixe core). let matMapFrozen := matMap let plan ← match Ix.Replay.planDeclarations matMap @@ -320,7 +329,7 @@ private def closureTest : IO (Bool × Nat × Nat × Option String) := do let path := (dir / "fixture.ixe").toString try let original ← buildFixtureConsts - let _ ← Ix.CompileM.rsCompileEnvBytesFFI original.toList path false + let _ ← compileFixtureToFile original.toList path let subset ← Ix.ImportIxe.materializeIxe path #[`TIxImp.dbl] let names : Std.HashSet Lean.Name := subset.foldl (fun s (n, _) => s.insert n) {} @@ -347,7 +356,7 @@ private def elabTest : IO (Bool × Nat × Nat × Option String) := do let consumerPath := dir / "Consumer.lean" try let original ← buildFixtureConsts - let _ ← Ix.CompileM.rsCompileEnvBytesFFI original.toList ixePath false + let _ ← compileFixtureToFile original.toList ixePath -- Materialized constants carry no compiled code (kernel-level -- import), so consumer definitions over them are `noncomputable`; -- execution is `#ixeval`'s job, Lean-native code the post-hoc LCNF @@ -388,7 +397,7 @@ private def zkPatternTest : IO (Bool × Nat × Nat × Option String) := do let consumerPath := dir / "Consumer.lean" try let original ← buildFixtureConsts - let _ ← Ix.CompileM.rsCompileEnvBytesFFI original.toList ixePath false + let _ ← compileFixtureToFile original.toList ixePath IO.FS.writeFile consumerPath s!"import Ix.ImportIxe\nimport_ixe \"{ixePath}\"\n" let env ← getFileEnv consumerPath @@ -407,7 +416,7 @@ private def zkPatternTest : IO (Bool × Nat × Nat × Option String) := do for r in Ix.Replay.constantInfoReferences ci do unless seen.contains r do work := work.push r - let phases ← Ix.CompileM.rsCompilePhasesOf closure + let phases ← Ix.CompileM.compilePhasesOf env closure let compileEnv := Ix.Commit.mkCompileEnv phases -- Commit a private value of the imported type. let voteType := Lean.Expr.const nN [] diff --git a/Tests/Ix/InitStd.lean b/Tests/Ix/InitStd.lean index 116efdacd..1394ab912 100644 --- a/Tests/Ix/InitStd.lean +++ b/Tests/Ix/InitStd.lean @@ -1,19 +1,25 @@ import Lean.Data.Json import LSpec import IxC.Kernel.Ixon.Prelude +import Ix.FileDigest /-! # Init+Std through the `ix` binary (merge queue tier `ci:landing-initstd-cli`) -* `initstd-reference`: `ix compile-lean Benchmarks/Compile/CompileInitStd.lean - --rust-check`, the certified compile of Init+Std, which refuses the compile - on any failed report check, with the default role binding. Its bytes are - compared with the Rust compiler's output - (`ALIGNED`). The artifact's SHA-256 and size must equal the reference in - `Tests/Fixtures/initstd-reference.txt`. The Rust kernel then checks the - artifact (`ix check-rs --anon`): it must admit every role the default role - table binds and refuse no role group, and pass the reference's count of - constants. A change that moves the compiler's bytes, or the Rust kernel's - verdicts on the artifact, updates that file in the same commit. +* `initstd-reference`: `ix compile-lean Benchmarks/Compile/CompileInitStd.lean`, + the certified compile of Init+Std, which refuses the compile + on any failed report check, with the default role binding. The artifact's + Blake3 digest and size must equal the reference in + `Tests/Fixtures/initstd-reference.txt`. The kernel then checks the + artifact (`ix check-lean --anon`), strict as every entry is by default: it + must admit every role the default role table binds and refuse none, pass + the reference's count of constants, and refuse exactly the reference's + constants (`Lean.trustCompiler`, the one native-evaluation axiom it declines, + and the four constants that depend on it), so the whole-artifact entry does + not run and the check exits 3. A change that moves the compiler's bytes, or + the kernel's verdicts on the artifact, updates that file in the same commit. + (`--allow-user-axioms`, the uncertified conditional path, is covered on + small fixtures by `RoleArtifactConsumer`: a second pass here would add + almost three minutes.) * `fidelity-initstd`: `ix validate-lean Benchmarks/Compile/CompileInitStd.lean --report`, gated on the report: every phase (the compile with its role binding, the serde gate, projection and block-order fidelity, the reader @@ -31,78 +37,106 @@ def source : String := "Benchmarks/Compile/CompileInitStd.lean" def referencePath : System.FilePath := "Tests" / "Fixtures" / "initstd-reference.txt" -/-- The reference artifact's digest and size, and the Rust kernel's verdicts on it. -/ +/-- Where `initstd-reference` leaves a copy of its artifact, for the later +entries of its partition that read Init+Std (`admission-hash-diff-initstd`). +A run removes the previous copy first, and writes one only when the compile +passes. -/ +def keptArtifact : System.FilePath := ".lake" / "build" / "initstd-reference.ixe" + +/-- The reference artifact's digest and size, and the Lean kernel's strict +verdicts on it: the constants it passes and checks, and the ones it refuses. -/ structure Reference where - sha256 : String + blake3 : String bytes : Nat - rustPassed : Nat - rustChecked : Nat + leanPassed : Nat + leanChecked : Nat + /-- `lean-kernel-refused` lines: the addresses strict checking refuses. -/ + refused : Array String def isHex64 (s : String) : Bool := s.length == 64 && s.all fun c => c.isDigit || ('a' ≤ c && c ≤ 'f') -/-- The reference file: `#` comments and `keyvalue` lines, each key once, -no other key. -/ +/-- The keys a reference file gives once. -/ +def singleKeys : List String := ["blake3", "bytes", "lean-kernel-passed", "lean-kernel-checked"] + +/-- The reference file: `#` comments, `keyvalue` lines, each key above +once, and `lean-kernel-refusedaddress[name]` lines, each address +once, the name for the reader; no other key. The refused addresses are +exactly the constants not passed. -/ def parseReference (text : String) : Except String Reference := do - let keys := ["sha256", "bytes", "rust-kernel-passed", "rust-kernel-checked"] let mut kv : Std.HashMap String String := {} + let mut refused : Array String := #[] for raw in text.splitOn "\n" do let line := raw.trimAscii.toString if line.isEmpty || line.startsWith "#" then continue match line.splitOn "\t" with - | [k, v] => - unless keys.contains k do throw s!"unknown key '{k}'" - if kv.contains k then throw s!"key '{k}' given twice" - kv := kv.insert k v + | k :: v :: rest => + if k == "lean-kernel-refused" then + unless isHex64 v && rest.length ≤ 1 do + throw s!"malformed line '{line}' (expected {k}<64 hex digits>[name])" + if refused.contains v then throw s!"{k} {v} given twice" + refused := refused.push v + else + unless singleKeys.contains k do throw s!"unknown key '{k}'" + unless rest.isEmpty do throw s!"malformed line '{line}' (expected keyvalue)" + if kv.contains k then throw s!"key '{k}' given twice" + kv := kv.insert k v | _ => throw s!"malformed line '{line}' (expected keyvalue)" let get (k : String) : Except String String := match kv.get? k with | some v => .ok v | none => .error s!"no '{k}' line" - let sha ← get "sha256" - unless isHex64 sha do throw s!"sha256 '{sha}' is not 64 lowercase hex digits" + let digest ← get "blake3" + unless isHex64 digest do throw s!"blake3 '{digest}' is not 64 lowercase hex digits" let nat (k : String) : Except String Nat := do let v ← get k match v.toNat? with | some n => .ok n | none => .error s!"{k} '{v}' is not a number" - return { - sha256 := sha - bytes := (← nat "bytes") - rustPassed := (← nat "rust-kernel-passed") - rustChecked := (← nat "rust-kernel-checked") } + let leanPassed ← nat "lean-kernel-passed" + let leanChecked ← nat "lean-kernel-checked" + unless leanPassed + refused.size == leanChecked do + throw s!"{refused.size} lean-kernel-refused lines for {leanChecked - leanPassed} constants not passed" + return { blake3 := digest, bytes := (← nat "bytes"), leanPassed, leanChecked, refused } -private def sample (sha : String) (more : String) : String := - s!"# c\nsha256\t{sha}\nbytes\t12\nrust-kernel-passed\t3\nrust-kernel-checked\t4\n{more}" +private def sample (digest : String) (more : String) : String := + s!"# c\nblake3\t{digest}\nbytes\t12\nlean-kernel-passed\t3\nlean-kernel-checked\t4\n\ + lean-kernel-refused\t{"".pushn 'b' 64}\tA.b\n{more}" #guard (parseReference (sample ("".pushn 'a' 64) "")).toOption.map - (fun r => (r.bytes, r.rustPassed, r.rustChecked)) == some (12, 3, 4) + (fun r => (r.bytes, r.leanPassed, r.leanChecked, r.refused)) == some (12, 3, 4, #["".pushn 'b' 64]) #guard (parseReference (sample "ab" "")).toOption.isNone #guard (parseReference (sample ("".pushn 'a' 64) "bytes\t1")).toOption.isNone #guard (parseReference (sample ("".pushn 'a' 64) "size\t1")).toOption.isNone -#guard (parseReference ("sha256\t" ++ "".pushn 'a' 64 ++ "\nbytes\t1")).toOption.isNone +#guard (parseReference ("blake3\t" ++ "".pushn 'a' 64 ++ "\nbytes\t1")).toOption.isNone +-- every constant not passed is listed, once, by its address +#guard (parseReference (sample ("".pushn 'a' 64) s!"lean-kernel-refused\t{"".pushn 'c' 64}")).toOption.isNone +#guard (parseReference (sample ("".pushn 'a' 64) s!"lean-kernel-refused\t{"".pushn 'b' 64}")).toOption.isNone +#guard (parseReference ((sample ("".pushn 'a' 64) "").replace "\tA.b" "")).toOption.isSome +#guard (parseReference ((sample ("".pushn 'a' 64) "").replace "\tA.b" "\tA.b\tC")).toOption.isNone -/-- The `bound roles: N admitted, M group(s) refused` line of `ix check-rs`: -`(N, M)`. -/ +/-- The `bound roles: N admitted, M refused` line of `ix check-lean`: `(N, M)`. -/ def admission (out : String) : Option (Nat × Nat) := do let line ← (out.splitOn "\n").find? fun l => (l.splitOn "bound roles: ").length > 1 let rest ← ((line.splitOn "bound roles: ")[1]?) let [admitted, refused] := rest.splitOn " admitted, " | none - let refused ← (refused.splitOn " group").head? + let refused ← (refused.splitOn " refused").head? return ((← admitted.trimAscii.toString.toNat?), (← refused.trimAscii.toString.toNat?)) -/-- The `[check] P/T passed` line of `ix check-rs`: `(P, T)`. -/ +/-- The `[check-lean] P/T subjects passed` line of `ix check-lean`: `(P, T)`. -/ def passedCount (out : String) : Option (Nat × Nat) := do - let line ← (out.splitOn "\n").find? fun l => l.startsWith "[check] " && l.endsWith " passed" - let [_, frac, _] := line.splitOn " " | none + let line ← (out.splitOn "\n").find? fun l => + l.startsWith "[check-lean] " && (l.splitOn " subjects passed").length > 1 + let rest ← ((line.splitOn "[check-lean] ")[1]?) + let frac ← (rest.splitOn " subjects passed").head? let [p, t] := frac.splitOn "/" | none - return ((← p.toNat?), (← t.toNat?)) + return ((← p.trimAscii.toString.toNat?), (← t.trimAscii.toString.toNat?)) -#guard admission "x\n[rs_kernel_check_anon] bound roles: 59 admitted, 0 group(s) refused (1ms)\n" == - some (59, 0) -#guard admission "[rs_kernel_check_anon] bound roles: 57 admitted, 2 group(s) refused" == some (57, 2) +#guard admission "x\n[check-lean] bound roles: 103 admitted, 0 refused\n" == some (103, 0) +#guard admission "[check-lean] bound roles: 57 admitted, 2 refused" == some (57, 2) #guard admission "no binding" == none -#guard passedCount "[check] checked 4 constants in 1s\n[check] 3/4 passed\n" == some (3, 4) +#guard passedCount "[check-lean] 3/4 subjects passed (0 typed only), 4 checked/read records\n" == + some (3, 4) /-- Run `ix` with `args`, logging the command line, its exit code and time. -/ def runIx (args : Array String) : IO IO.Process.Output := do @@ -123,15 +157,6 @@ def linesWith (s : String) (needles : List String) : List String := def tail (out : IO.Process.Output) : String := s!"{(out.stdout.takeEnd 2000).toString}\n{(out.stderr.takeEnd 2000).toString}" -/-- The SHA-256 of a file, by `sha256sum`. -/ -def sha256File (path : System.FilePath) : IO String := do - let out ← IO.Process.output { cmd := "sha256sum", args := #[path.toString] } - unless out.exitCode == 0 do - throw (IO.userError s!"sha256sum {path} exited {out.exitCode}: {out.stderr}") - match (out.stdout.splitOn " ").head? with - | some h => if isHex64 h then pure h else throw (IO.userError s!"sha256sum printed {out.stdout}") - | none => throw (IO.userError s!"sha256sum printed nothing") - def reportChecks (name : String) (checks : Array (String × Bool)) : IO UInt32 := LSpec.lspecIO (.ofList [(name, checks.toList.map fun (label, ok) => LSpec.test label ok)]) [] @@ -139,48 +164,59 @@ def reportChecks (name : String) (checks : Array (String × Bool)) : IO UInt32 : def reference : IO UInt32 := do let expected ← IO.ofExcept ((parseReference (← IO.FS.readFile referencePath)).mapError fun e => s!"{referencePath}: {e}") + if ← keptArtifact.pathExists then IO.FS.removeFile keptArtifact let dir ← IO.FS.createTempDir try let artifact := dir / "initstd.ixe" - let compiled ← runIx #["compile-lean", source, "--rust-check", "--out", artifact.toString] - for l in linesWith compiled.stdout ["] compile:", "post-steps", "wrote", "ALIGNED", "DIVERGED"] do + let compiled ← runIx #["compile-lean", source, "--out", artifact.toString] + for l in linesWith compiled.stdout ["] compile:", "post-steps", "wrote"] do IO.println l unless compiled.exitCode == 0 && (← artifact.pathExists) do IO.eprintln (tail compiled) return ← reportChecks "initstd-reference" - #[("ix compile-lean on Init+Std passes its report checks and matches the Rust compiler", false)] - let aligned := !(linesWith compiled.stdout ["[compile-lean] ALIGNED:"]).isEmpty - let sha ← sha256File artifact + #[("ix compile-lean on Init+Std passes its report checks", false)] + IO.FS.createDirAll (keptArtifact.parent.getD ".") + IO.FS.writeBinFile keptArtifact (← IO.FS.readBinFile artifact) + let digest ← Ix.FileDigest.blake3File artifact let size := (← artifact.metadata).byteSize.toNat - IO.println s!"[initstd] artifact sha256 {sha}, {size} bytes; reference {expected.sha256}, \ + IO.println s!"[initstd] artifact blake3 {digest}, {size} bytes; reference {expected.blake3}, \ {expected.bytes} bytes" - unless sha == expected.sha256 && size == expected.bytes do + unless digest == expected.blake3 && size == expected.bytes do IO.eprintln s!"[initstd] the Init+Std artifact differs from {referencePath}. If the change \ - is meant to move the compiler's bytes, record sha256 {sha} and bytes {size} there." - -- the Rust kernel on the artifact, under its role binding; it exits 3 when - -- some constant fails, which the count of passing constants decides + is meant to move the compiler's bytes, record blake3 {digest} and bytes {size} there." + -- the kernel on the artifact, under its role binding: strict, as every + -- entry's default, it declines `Lean.trustCompiler` and blocks what depends + -- on it, so the whole-artifact entry does not run and the run exits 3 let roles := (← IO.ofExcept Ix.Kernel.Reader.defaultTable).bound.size - let checked ← runIx #["check-rs", artifact.toString, "--anon"] + let failOut := dir / "refused.txt" + let checked ← runIx #["check-lean", artifact.toString, "--anon", "--fail-out", failOut.toString] let both := checked.stdout ++ "\n" ++ checked.stderr - for l in linesWith both ["bound roles:", " passing, ", "[check] ", " ✗ "] do + for l in linesWith both ["bound roles:", "subjects passed", "[check-lean]"] do IO.println l - let exited := checked.exitCode == 0 || checked.exitCode == 3 + let exited := checked.exitCode == 3 unless exited do IO.eprintln (tail checked) + let refused := ((← try IO.FS.readFile failOut catch _ => pure "").splitOn "\n").toArray.filterMap + fun l => let l := l.trimAscii.toString; if l.isEmpty || l.startsWith "#" then none else some l + let sorted (xs : Array String) := xs.qsort (· < ·) + let refusedOk := sorted refused == sorted expected.refused let admitted := admission both let counts := passedCount checked.stdout - if exited && counts != some (expected.rustPassed, expected.rustChecked) then - IO.eprintln s!"[initstd] the Rust kernel's count on the artifact differs from \ - {referencePath}. If the change is meant to move it, record it there." + if exited && (counts != some (expected.leanPassed, expected.leanChecked) || !refusedOk) then + IO.eprintln s!"[initstd] the kernel's verdicts on the artifact differ from {referencePath}: \ + refused {refused}. If the change is meant to move them, record them there." reportChecks "initstd-reference" #[ - ("ix compile-lean on Init+Std passes its report checks and matches the Rust compiler \ - byte for byte", aligned), - (s!"the artifact's SHA-256 is the reference's ({expected.sha256})", sha == expected.sha256), + ("ix compile-lean on Init+Std passes its report checks", compiled.exitCode == 0), + (s!"the artifact's Blake3 digest is the reference's ({expected.blake3})", digest == expected.blake3), (s!"the artifact's size is the reference's ({expected.bytes} bytes)", size == expected.bytes), - ("ix check-rs --anon exits 0, or 3 for failed constants", exited), - (s!"the Rust kernel admits all {roles} roles the default table binds and refuses no \ - role group", admitted == some (roles, 0)), - (s!"the Rust kernel passes {expected.rustPassed} of {expected.rustChecked} constants, \ - as the reference says", counts == some (expected.rustPassed, expected.rustChecked))] + ("ix check-lean --anon exits 3: strict checking refuses constants", exited), + (s!"the kernel admits all {roles} roles the default table binds and refuses none", + admitted == some (roles, 0)), + (s!"the kernel passes {expected.leanPassed} of {expected.leanChecked} constants, \ + as the reference says", counts == some (expected.leanPassed, expected.leanChecked)), + (s!"the kernel refuses exactly the reference's {expected.refused.size} constants", refusedOk), + ("it claims no whole-artifact acceptance and no host statement", + !checked.stdout.contains "accepted all" && !checked.stdout.contains "host admission statement" && + !checked.stdout.contains "CONDITIONAL")] finally IO.FS.removeDirAll dir diff --git a/Tests/Ix/IxVM.lean b/Tests/Ix/IxVM.lean index 723ff5307..ff11b516a 100644 --- a/Tests/Ix/IxVM.lean +++ b/Tests/Ix/IxVM.lean @@ -412,9 +412,8 @@ public def serdeNatAddComm (env : Lean.Environment) : IO AiurTestCase := do Blake3 binding is preserved end-to-end: every closure member reaches the kernel via `get_ci(addr)` which reads bytes from ch 2 and asserts blake3(bytes) == addr before returning. -/ -public def kernelCheck (name : Lean.Name) (env : Lean.Environment) +public def kernelCheckWithEnv (ixonEnv : Ixon.Env) (name : Lean.Name) (assumeBinding : Bool := true) : IO AiurTestCase := do - let ixonEnv ← loadIxonEnv name env let target ← lookupAddr ixonEnv name -- Claim.Check with transitive semantics: run_check_transitive walks the -- block-level ref DAG lazily. No AssumptionTree needed — Aiur @@ -434,6 +433,11 @@ public def kernelCheck (name : Lean.Name) (env : Lean.Environment) expectedIOBuffer := witness.inputIOBuffer interpret := false, withProof := false } +public def kernelCheck (name : Lean.Name) (env : Lean.Environment) + (assumeBinding : Bool := true) : IO AiurTestCase := do + let ixonEnv ← loadIxonEnv name env + kernelCheckWithEnv ixonEnv name assumeBinding + private def nameOfString (str : String) : Lean.Name := str.splitOn "." |>.foldl (init := .anonymous) fun acc s => match s.toNat? with @@ -521,8 +525,8 @@ private def kernelCheckEntries : List (String × Nat) := [ ("_private.Tests.Ix.Compile.Mutual.0.Tests.Ix.Compile.Mutual.AuxDedupMixed.M.rec", 206_728_123), ("_private.Tests.Ix.Compile.Mutual.0.Tests.Ix.Compile.Mutual.AuxDedupMixed.M.rec_1", 206_728_123), ("_private.Tests.Ix.Compile.Mutual.0.Tests.Ix.Compile.Mutual.AuxDedupMixed.M.rec_2", 206_728_123), - ("strOfListFoldSize", 2_310_764_927), - ("strOfListFoldSizeAscii", 2_311_375_306), + ("strOfListFoldSize", 2_310_765_047), + ("strOfListFoldSizeAscii", 2_311_375_426), ("IxVMPrim.lazy_ble_offset", 220_893_731), ("IxVMPrim.lazy_unit_cast", 345_818_198), ("IxVMPrim.sizeof_unit", 209_770_262), @@ -546,12 +550,10 @@ private def validatingCheckEntries : List (String × Nat) := [ /-- The binding claim for the default table: every group validated once, the proof aggregates reuse. -/ -private def bindingCheckEntry : Nat := 277_988_602_536 +private def bindingCheckEntry : Nat := 277_988_605_740 -/-- The binding claim for the default table, over the test environment - with the table's support records. -/ -public def bindingCheck (env : Lean.Environment) : IO AiurTestCase := do - let ixonEnv ← loadSharedIxonEnv #[] env +/-- The binding claim for the default table, over `ixonEnv`. -/ +public def bindingCheckWithEnv (ixonEnv : Ixon.Env) : IO AiurTestCase := do let ctx ← IO.ofExcept (Ix.ClaimContext.ofEnv ixonEnv) let claim := Ix.Claim.binding ctx.spec ctx.binding let witness ← IO.ofExcept <| buildClaimWitness ixonEnv claim {} @@ -560,17 +562,28 @@ public def bindingCheck (env : Lean.Environment) : IO AiurTestCase := do expectedIOBuffer := witness.inputIOBuffer interpret := false, withProof := false } +/-- The binding claim for the default table, over the test environment + with the table's support records. -/ +public def bindingCheck (env : Lean.Environment) : IO AiurTestCase := do + let ixonEnv ← loadSharedIxonEnv #[] env + bindingCheckWithEnv ixonEnv + /-- Variant of `kernelChecks`, pinned to the baseline (`kernelCheckEntries`). Any kernel change that shifts the kernel FFT cost fails the suite and forces a manual pin bump. -/ public def kernelChecks (env : Lean.Environment) : IO (List AiurTestCase) := do + let allNames := + (kernelCheckEntries.map fun (name, _) => nameOfString name).toArray ++ + (validatingCheckEntries.map fun (name, _) => nameOfString name).toArray ++ + synthesizedPrimNames + let ixonEnv ← loadSharedIxonEnv allNames env let assumed ← kernelCheckEntries.mapM fun (name, expected) => do - let tc ← kernelCheck (nameOfString name) env + let tc ← kernelCheckWithEnv ixonEnv (nameOfString name) pure { tc with expectedFftCost := some expected } let validating ← validatingCheckEntries.mapM fun (name, expected) => do - let tc ← kernelCheck (nameOfString name) env (assumeBinding := false) + let tc ← kernelCheckWithEnv ixonEnv (nameOfString name) (assumeBinding := false) pure { tc with expectedFftCost := some expected } - let binding ← bindingCheck env + let binding ← bindingCheckWithEnv ixonEnv return assumed ++ validating ++ [{ binding with expectedFftCost := some bindingCheckEntry }] /-! ## Codegen ↔ bytecode parity diff --git a/Tests/Ix/IxVM/DefinitionDependencies.lean b/Tests/Ix/IxVM/DefinitionDependencies.lean index 48ac1fb5e..7b2aad3d3 100644 --- a/Tests/Ix/IxVM/DefinitionDependencies.lean +++ b/Tests/Ix/IxVM/DefinitionDependencies.lean @@ -1,6 +1,8 @@ import Tests.Ix.IxVM.Exploits -import Ix.KernelCheck -import Ix.EnvScope + +/-! + Definition dependency tests for IxVM (`Tests.Ix.IxVM.DefinitionDependencies`). +-/ namespace Tests.Ix.IxVM.DefinitionDependencies @@ -92,92 +94,11 @@ private def ixvmSafetyCases : Array ExploitCase := "the wire safety byte must not bypass the safe declaration peer-slot restriction" (block #[{ definition kind everyProp (.recur 0 #[]) with safety }]) -def rustTests : IO TestSeq := do - let directory ← IO.FS.createTempDir - try - let mut tests : TestSeq := .done - for c in cases do - let path := directory / s!"{c.name}.ixe" - let bytes ← IO.ofExcept (Ixon.serEnv c.env) - IO.FS.writeBinFile path bytes - let rows ← Ix.KernelCheck.rsCheckAnonFFI path.toString true "" - let .check _ target _ := c.claim | throw <| IO.userError "expected a Check claim" - let row := rows.find? (fun row => row.1 == toString target) - let correct : Bool := match row with - | none => false - | some (_, error) => - if c.expectAccept then rows.all (·.2.isNone) - else error.any fun e => - (e.message.splitOn "cyclic definition dependency").length > 1 - tests := tests ++ test s!"Rust definition dependencies: {c.name}" correct - return tests - finally - IO.FS.removeDirAll directory - -namespace Fixtures - -def countStruct : Nat → Nat - | 0 => 0 - | n + 1 => countStruct n + 1 -termination_by structural n => n - -def countWellFounded (n : Nat) : Nat := - if h : n = 0 then 0 else countWellFounded (n - 1) + 1 -termination_by n -decreasing_by omega - -mutual - def even : Nat → Bool - | 0 => true - | n + 1 => odd n - termination_by structural n => n - - def odd : Nat → Bool - | 0 => false - | n + 1 => even n - termination_by structural n => n -end - -end Fixtures - -/-- The Rust guard must preserve termination-checked source recursion. -Check every declaration in each exported closure. -/ -private def recursionTests : IO TestSeq := do - let env ← get_env! - let directory ← IO.FS.createTempDir - try - let mut tests : TestSeq := .done - for (label, seeds) in [("structural", [``Fixtures.countStruct]), - ("well-founded", [``Fixtures.countWellFounded]), - ("mutual", [``Fixtures.even, ``Fixtures.odd])] do - for seed in seeds do - let some (.defnInfo declaration) := env.find? seed - | throw <| IO.userError s!"missing elaborated definition {seed}" - unless declaration.safety == .safe do - throw <| IO.userError s!"{seed} is not a safe definition" - let path := directory / s!"{label}.ixe" - let constants := Ix.EnvScope.collectDeps env seeds - let status ← Ix.CompileM.rsCompileEnvBytesFFI constants path.toString false - unless status.ungrounded.isEmpty do - throw <| IO.userError s!"compilation omitted {status.ungrounded.size} declarations" - let source ← IO.ofExcept <| Ixon.deEnv (← IO.FS.readBinFile path) - let rows ← Ix.KernelCheck.rsCheckAnonFFI path.toString true "" - for seed in seeds do - let some address := source.getAddr? (Ix.Name.fromLeanName seed) - | throw <| IO.userError s!"export omitted {seed}" - unless rows.any (fun row => row.1 == toString address) do - throw <| IO.userError s!"no checking result for {seed}" - tests := tests ++ test s!"Rust accepts safe {label} recursion" (rows.all (·.2.isNone)) - return tests - finally - IO.FS.removeDirAll directory - def tests (compiled : Aiur.CompiledToplevel) : IO TestSeq := do let control := asCase "control-closed-identity" "a genuine closed proof must still be accepted" (storeAt {} ⟨.defn (definition .thm identityType identityValue), #[], #[], #[.zero]⟩) true let ixvmCases := cases.filter (fun c => !c.expectAccept) ++ ixvmSafetyCases ++ #[control] - return (← runCases compiled ixvmCases) ++ (← runCases compiled ixvmCases true) ++ - (← rustTests) ++ (← recursionTests) + return (← runCases compiled ixvmCases) ++ (← runCases compiled ixvmCases true) end Tests.Ix.IxVM.DefinitionDependencies diff --git a/Tests/Ix/IxVM/Eval.lean b/Tests/Ix/IxVM/Eval.lean index 71212f91d..7c3773aa8 100644 --- a/Tests/Ix/IxVM/Eval.lean +++ b/Tests/Ix/IxVM/Eval.lean @@ -3,9 +3,14 @@ public import Ix.IxVM.Toplevel public import Ix.IxVM.ClaimHarness public import Tests.Aiur.Common public import Ix.Meta +public import Ix.CompileCertified public import Ix.Commit public import Ix.IxEval +/-! + IxVM evaluation claim tests (`Tests.Ix.IxVM.Eval`). +-/ + public section namespace Tests.Ix.IxVM.Eval @@ -457,7 +462,7 @@ private def compileClosure (leanEnv : Lean.Environment) (names : Array Lean.Name unless seen.contains entry.1 do seen := seen.insert entry.1 entries := entries.push entry - Ix.CompileM.rsCompilePhasesOf entries.toList + Ix.CompileM.compilePhasesOf leanEnv entries.toList private def compiledOutput (base : Ix.CompileM.CompileEnv) (env : Ixon.Env) (lvls : List Lean.Name) (ty value : Lean.Expr) : IO (Ixon.Env × Ixon.Constant) := do diff --git a/Tests/Ix/IxVM/Exploits.lean b/Tests/Ix/IxVM/Exploits.lean index 17b1b71cf..dde654d9e 100644 --- a/Tests/Ix/IxVM/Exploits.lean +++ b/Tests/Ix/IxVM/Exploits.lean @@ -1,11 +1,22 @@ -/- +import Ix.Ixon +import Ix.Claim +import Ix.IxVM +import Ix.IxVM.ClaimHarness +import Ix.Aiur.Protocol +import Ix.Aiur.Compiler +import Tests.Aiur.Common +import Tests.Ix.Kernel.FastPathDefs +import Tests.Ix.Kernel.FastPaths +import LSpec + +/-! Adversarial Ixon fixtures: exploit attempts authored DIRECTLY as `Ixon.Constant` values, bypassing the Lean → Ixon compiler. The kernel-arena corpus (`Tests.Ix.Kernel.Arena`) attacks the layer ABOVE this one: its `bad_*` fixtures are raw `Lean.ConstantInfo` declarations that skip Lean's elaborator, but they are still compiled - by `rsCompileEnvFFI`, so the Ixon they produce is always well-formed — + by the Ixon compiler, so the Ixon they produce is always well-formed — `sharing` comes from real sharing analysis, `refs` from the `IndexSet` of addresses actually referenced. An honest compiler cannot emit the malformed Ixon a real prover is free to author. @@ -26,9 +37,9 @@ A case may instead assert ACCEPTANCE when accepting is the specified semantics and the claim is merely VACUOUS rather than false — a - conditional claim that assumes its own target being the example. Those - mark where the kernel's obligation ends and a consumer's begins; they - are pinned so the boundary cannot move silently. + conditional claim that assumes an unsatisfiable role binding being the + example. Those mark where the kernel's obligation ends and a consumer's + begins; they are pinned so the boundary cannot move silently. ## Writing a case @@ -39,16 +50,6 @@ catches it absent the exploit trick. Without those a green suite may only mean the fixture never reached the code it targets. -/ -import Ix.Ixon -import Ix.Claim -import Ix.IxVM -import Ix.IxVM.ClaimHarness -import Ix.Aiur.Protocol -import Ix.Aiur.Compiler -import Tests.Aiur.Common -import Tests.Ix.Kernel.FastPathDefs -import Tests.Ix.Kernel.FastPaths -import LSpec open LSpec @@ -75,6 +76,9 @@ structure ExploitCase where /-- Whether the kernel is expected to ACCEPT. Exploits set `false`; controls that must typecheck set `true`. -/ expectAccept : Bool := false + /-- For a rejection, text the execution error must contain: the in-kernel + `assert_eq!` message expected to fire, when the case pins one. -/ + rejectsWith : Option String := none /-! ## Ixon construction helpers @@ -1087,6 +1091,10 @@ def cases (ctx : Ix.ClaimContext) (base : Ixon.Env) (strAddr falseAddr : Address let selfTree := (Ix.AssumptionTree.canonical #[badAddr]).get! let selfTrees : Std.HashMap Address Ix.AssumptionTree := ({} : Std.HashMap Address Ix.AssumptionTree).insert selfTree.root selfTree + -- a well-formed tree whose every slot is padding: no leaf + let emptyTree : Ix.AssumptionTree := .node .padding .padding + let emptyTrees : Std.HashMap Address Ix.AssumptionTree := + ({} : Std.HashMap Address Ix.AssumptionTree).insert emptyTree.root emptyTree #[ -- CONTROL: the smuggled constant is genuinely ill-typed. If this -- ever passes, the exploit below proves nothing. @@ -1158,27 +1166,33 @@ def cases (ctx : Ix.ClaimContext) (base : Ixon.Env) (strAddr falseAddr : Address lean4#14613 fixed, reachable if `is_prop_type` tested \ structural rather than semantic zero" env := etaExploitEnv, claim := .check ctx etaExploitAddr none }, - -- BOUNDARY, not a kernel bug: assumption sets are prover-supplied, - -- and naming the claim's own target as its own assumption makes the - -- walk exit immediately, so an ill-typed constant is accepted. The - -- kernel is right to accept: `some root` means "conditional on every - -- leaf being well-typed" (`Ix/Claim.lean`), so this claim asserts - -- `A → A`, which is vacuously true rather than false. - -- - -- The obligation therefore sits with whoever DISCHARGES assumptions: - -- a conditional claim must never discharge its own target, and an - -- aggregation of such claims must reject cycles. Sharded CheckEnv is - -- not exposed — `shardClaimDigest` recomputes the claim from - -- (env, blocks), so a prover-chosen frontier fails the digest — but - -- a bare Check claim carries whatever assumptions its prover picked. - -- Pinned as ACCEPT so the day a cycle guard lands anywhere on this - -- path, this case turns red and gets re-examined deliberately. + -- Assumptions waive out-of-shard dependency checks, never the check of + -- a claim's own target or owned subjects (`Eval`, `Check`, and `CheckEnv`). + -- Allowing a `Check` or `CheckEnv` claim to assume its own subject would + -- let `verify_claim` accept a malformed constant without loading or + -- checking it, and any downstream consumer or aggregator would see the + -- constant in the subject tree without it ever having been typechecked. { name := "self-assumption-covers-claim-target" - intent := "an ill-typed constant accepted under a claim that \ - assumes precisely itself — vacuous (A → A), so sound as a claim; \ - the cycle must be rejected when assumptions are discharged" + intent := "an ill-typed constant accepted under a `Check` claim that \ + places its own target in the assumption set, skipping the subject check" env := badEnv, claim := .check ctx badAddr (some selfTree.root) - trees := selfTrees, expectAccept := true }, + trees := selfTrees }, + { name := "check-env-self-assumption-covers-owned-subject" + intent := "an ill-typed constant accepted under a `CheckEnv` claim whose \ + owned subject tree and assumption tree both contain that constant, \ + skipping `run_check` on the owned leaf" + env := badEnv, + claim := .checkEnv ctx selfTree.root (some selfTree.root) + trees := selfTrees }, + -- An owned tree with no leaf owns no constant: a `CheckEnv` claim over it + -- would check nothing and still state that an environment typechecks. + -- `run_check_env` refuses it before any walk. + { name := "check-env-empty-owned-tree" + intent := "a `CheckEnv` claim accepted over an owned tree with no leaves, \ + checking no constant at all" + env := badEnv, claim := .checkEnv ctx emptyTree.root none + trees := emptyTrees + rejectsWith := some "owned tree has no leaves" }, -- CONTROL: the same shape over well-formed UTF-8 must typecheck, so -- a rejection below is attributable to the bytes and not to the -- fixture's shape. @@ -1988,8 +2002,12 @@ one of them or `sorryAx`, which is inert: declared, but used by no constant. Native evaluation is outside that theory. Of its five constants the three axioms (`Lean.trustCompiler`, `Lean.ofReduceBool`, `Lean.ofReduceNat`) are declined as axioms, and the opaques `Lean.reduceBool` and `Lean.reduceNat`, -which never reduce, through their dependency on `Lean.trustCompiler`. IxC -refuses the same five, and the cases pin IxC's verdicts beside IxVM's. -/ +which never reduce, through their dependency on `Lean.trustCompiler`. IxC's +strict checking, the default of every entry and of `ix check-lean` and +`kernel-check-ixe`, refuses the same five, and the cases pin IxC's verdicts +beside IxVM's. Only `--allow-user-axioms` admits the three axioms, as user +axioms of an uncertified conditional run that lists them, issues no host +statement and exits 5 (`Ix.Cli.CheckLeanCmd.exitConditional`). -/ /-- `theorem _ : False := @sorryAx.{0} False Bool.false`. refs: 0=sorryAx, 1=False, 2=Bool.false. -/ @@ -2008,7 +2026,7 @@ def axiomPolicyCases (ctx : Ix.ClaimContext) (leanEnv : Lean.Environment) : IO (Array ExploitCase × TestSeq) := do let closed ← IO.ofExcept (Tests.Ix.Kernel.ReaderFidelity.closure leanEnv nativeEvaluationNames.toArray) - let compiled ← match ← Ix.CompileM.compileLeanConsts closed (numWorkers := 4) with + let compiled ← match ← Ix.CompileM.compileCertified leanEnv closed 4 with | .ok out => pure out | .error e => throw (IO.userError s!"compiling the native-evaluation closure: {e}") let ixc ← Tests.Ix.Kernel.FastPaths.ixcVerdicts compiled nativeEvaluationNames @@ -2074,7 +2092,8 @@ def runCases (compiled : Aiur.CompiledToplevel) (fixtures : Array ExploitCase) test s!"{label} (accepted — buys: {c.intent})" c.expectAccept | .error e => IO.println s!" [{c.name}] rejected by: {e}" - tests := tests ++ test label (!c.expectAccept) + let cause := c.rejectsWith.all fun m => (toString e).contains m + tests := tests ++ test label (!c.expectAccept && cause) return tests /-- Run every case against the compiled `IxVM.ixVM` toplevel through the diff --git a/Tests/Ix/IxonCorpus.lean b/Tests/Ix/IxonCorpus.lean deleted file mode 100644 index 8c1ffa20a..000000000 --- a/Tests/Ix/IxonCorpus.lean +++ /dev/null @@ -1,128 +0,0 @@ -module - -public import LSpec -public import Ix.Ixon -public import Ix.CompileM -public import Ix.Meta -public import Ix.Common - -/-! -Real-corpus gate for the Lean↔Rust Ixon sync (`ixon-corpus`, ignored). - -The generator-driven properties (`Tests.Ixon.suite`) cover the wire grammar -compositionally; this suite covers whatever hand-written generators miss, -because its input is the production Rust compiler's real output over the -ENTIRE current Lean environment — every block kind, its metadata, and every -§-section interaction. - -Gate: -1. Rust-compile the whole `get_env!` to `.ixe` bytes. -2. The pure-Lean parser (`Ixon.deEnv`) must parse them — every metadata - variant, no lossy skips. -3. The pure-Lean writer (`Ixon.serEnv`) must reproduce the input bytes - EXACTLY (writers are canonical: section sort orders, name topological - order, index assignment, and metadata encoding all agree). -4. The Rust FFI value path (`Ixon.rsDeEnv`) must agree with the pure parse - (content comparison per section). --/ - -namespace Tests.Ixon.Corpus - -open LSpec - -public section - -/-- Content comparison between two parsed envs (no BEq instance on `Env` — - HashMaps compare per-key). Returns the first difference found. -/ -def envContentDiff (a b : Ixon.Env) : Option String := Id.run do - if a.consts.size != b.consts.size then - return some s!"consts size {a.consts.size} vs {b.consts.size}" - for (addr, lc) in a.consts do - match b.consts.get? addr with - | none => return some s!"const {addr} missing" - | some lc' => - if lc.rawBytes != lc'.rawBytes then - return some s!"const {addr} bytes differ" - if a.named.size != b.named.size then - return some s!"named size {a.named.size} vs {b.named.size}" - for (name, n) in a.named do - match b.named.get? name with - | none => return some s!"named {name} missing" - | some n' => - if n != n' then - let field := - if n.addr != n'.addr then s!"addr {n.addr} vs {n'.addr}" - else if n.hints != n'.hints then - s!"hints {reprStr n.hints} vs {reprStr n'.hints}" - else if n.constMeta != n'.constMeta then - let cm := n.constMeta - let cm' := n'.constMeta - if cm.info != cm'.info then - s!"constMeta.info:\n pure: {(reprStr cm.info).take 400}\n ffi: {(reprStr cm'.info).take 400}" - else - s!"constMeta ext tables: sharing {cm.metaSharing.size}/{cm'.metaSharing.size} refs {cm.metaRefs.size}/{cm'.metaRefs.size} univs {cm.metaUnivs.size}/{cm'.metaUnivs.size}" - else - s!"original:\n pure: {(reprStr n.original).take 400}\n ffi: {(reprStr n'.original).take 400}" - return some s!"named {name} differs — {field}" - if a.blobs.size != b.blobs.size then - return some s!"blobs size {a.blobs.size} vs {b.blobs.size}" - for (addr, bytes) in a.blobs do - if b.blobs.get? addr != some bytes then - return some s!"blob {addr} differs" - if a.names.size != b.names.size then - return some s!"names size {a.names.size} vs {b.names.size}" - for (addr, name) in a.names do - if b.names.get? addr != some name then - return some s!"name {addr} differs" - if a.comms.size != b.comms.size then - return some s!"comms size {a.comms.size} vs {b.comms.size}" - if a.main != b.main then - return some s!"main differs" - if a.assumptions.size != b.assumptions.size then - return some "assumptions differ" - if a.anonHints.size != b.anonHints.size then - return some s!"anonHints size {a.anonHints.size} vs {b.anonHints.size}" - for (addr, h) in a.anonHints do - if b.anonHints.get? addr != some h then - return some s!"anonHints {addr} differs" - return none - -/-- Compile the whole current env through the Rust compiler and return the - `.ixe` bytes. -/ -def compileWholeEnvBytes (leanEnv : Lean.Environment) : IO ByteArray := do - let dir ← IO.FS.createTempDir - let path := dir / "ixon-corpus.ixe" - let _ ← Ix.CompileM.rsCompileEnvBytesFFI leanEnv.constants.toList path.toString true - let bytes ← IO.FS.readBinFile path - IO.FS.removeDirAll dir - return bytes - -def corpusSuite : TestSeq := - .individualIO "pure deEnv/serEnv roundtrip whole Rust-compiled env" none (do - let leanEnv ← get_env! - let bytes ← compileWholeEnvBytes leanEnv - -- 2. Pure parse. - match Ixon.deEnv bytes with - | .error e => return (false, 0, 0, some s!"pure deEnv failed: {e}") - | .ok env => do - -- 3. Byte-exact pure re-serialization. - match Ixon.serEnv env with - | .error e => return (false, 0, 0, some s!"pure serEnv failed: {e}") - | .ok bytes' => - if bytes' != bytes then - return (false, env.consts.size, 0, - some s!"serEnv bytes differ: {bytes'.size} vs {bytes.size}") - -- 4. FFI value path agrees with the pure parse. - match Ixon.rsDeEnv bytes with - | .error e => return (false, 0, 0, some s!"rsDeEnv failed: {e}") - | .ok rsEnv => - match envContentDiff env rsEnv with - | some d => return (false, env.consts.size, 0, - some s!"pure vs FFI parse disagree: {d}") - | none => return (true, env.consts.size, 0, none)) .done - -public def suite : List TestSeq := [corpusSuite] - -end - -end Tests.Ixon.Corpus diff --git a/Tests/Ix/IxonRejectParity.lean b/Tests/Ix/IxonRejectParity.lean index 67e01b543..5af5504d5 100644 --- a/Tests/Ix/IxonRejectParity.lean +++ b/Tests/Ix/IxonRejectParity.lean @@ -10,8 +10,8 @@ public import LSpec /-! # Malformed inputs Lean's readers refuse, as Rust's do The Lean side of the shared fixture `Tests/Fixtures/ixon-v4/reject-parity.txt`, -whose Rust side is `crates/ixon/tests/reject_parity.rs` and -`crates/kernel/tests/reject_parity.rs`: every reader of a case's kind refuses +whose Rust side is `crates/ixon/tests/reject_parity.rs` (every kind but +`ixes`): every reader of a case's kind refuses it, and accepts the kind's controls. The kinds and the readers: * `record`: the canonical decoder of host admission (`Canonical.deConstant` at diff --git a/Tests/Ix/Kernel/AccelSemantics.lean b/Tests/Ix/Kernel/AccelSemantics.lean index 876fe7259..b4c8b2754 100644 --- a/Tests/Ix/Kernel/AccelSemantics.lean +++ b/Tests/Ix/Kernel/AccelSemantics.lean @@ -1,6 +1,5 @@ import Ix.Meta -import Ix.CompileDriver -import Ix.KernelCheck +import Ix.CompileCertified import Tests.Ix.Kernel.ReaderFidelity import Tests.Ix.Kernel.SourceIngress import Tests.Ix.Kernel.SourceIngressCompare @@ -40,13 +39,8 @@ def run : IO UInt32 := do (roots ++ Tests.Ix.Kernel.SourceIngress.preludeNames)) let names := (closed.map (·.1)).toArray need (!closed.isEmpty && roots.all names.contains) "selected semantic closure lost a root" - let compiled ← IO.ofExcept (← Ix.CompileM.compileLeanConsts closed (numWorkers := 4)) - need (compiled.ungroundedCount == 0) "semantic closure has ungrounded inputs" - let rust ← Ix.KernelCheck.rsCheckConstsFFI closed names (Array.replicate names.size true) true - need (rust.size == names.size) "Rust omitted selected closure verdicts" - for i in [:names.size] do - if let some error := rust[i]! then - throw (IO.userError s!"Rust rejected {names[i]!}: {error.message}") + let compiled ← IO.ofExcept (← Ix.CompileM.compileCertified leanEnv closed 4) + need compiled.failures.isEmpty "semantic closure has ungrounded inputs" let checked ← Tests.Ix.Kernel.SourceIngressCompare.ixonVerdicts compiled.env for name in names do let key := Ix.LeanIngress.ingressKey (Ix.Name.fromLeanName name) diff --git a/Tests/Ix/Kernel/AdmissionHashDiff.lean b/Tests/Ix/Kernel/AdmissionHashDiff.lean index 1511736cf..d1a1fbe81 100644 --- a/Tests/Ix/Kernel/AdmissionHashDiff.lean +++ b/Tests/Ix/Kernel/AdmissionHashDiff.lean @@ -2,8 +2,10 @@ import Ix.Address import Ix.Address.Pure import Ix.Address.Admission import Ix.Ixon +import Tests.Ix.Kernel.ReaderRoundtrip +import Tests.Ix.InitStd -/-! # The hash substitution, differentially (ignored runner `admission-hash-diff`) +/-! # The hash substitution, differentially (`admission-hash-diff`) `Address.admissionHash` is pure BLAKE3 (`Address.blake3Pure`) in the logic and executes as the Rust BLAKE3 (`Address.blake3`), through the one approved @@ -12,23 +14,24 @@ addresses and the certified entries is about pure BLAKE3; what runs is the Rust one. `Tests.Ix.Kernel.AddressPure` compares the two on a few fixture inputs at elaboration time. This runner compares them across: -* **every record and blob of the Init+Std artifact**: each record's stored - bytes (`Ixon.LazyConstant.rawBytes`, the bytes its address is computed - over) and each blob's bytes; * **generated inputs**: every length from 0 to 2049 (the 64-byte block and 1024-byte chunk boundaries, twice), lengths around every chunk-count power of two up to 1024 chunks (1 MiB + 1), odd chunk counts, and pseudo-random lengths up to 200,000, each in four byte patterns (zeros, `0xff`, - `i % 251`, and a linear congruential stream). + `i % 251`, and a linear congruential stream); +* **every record and blob of a compiled Ixon artifact** (`$IX_HASH_DIFF_IXE` + when set, or the compiled closure of `ReaderFidelityDefs` by default): each + record's stored bytes (`Ixon.LazyConstant.rawBytes`), each blob's bytes, + and the role binding's bytes when the artifact has one. Each input is hashed with `Address.blake3` (Rust), `Address.blake3Pure` (Lean) and `Address.admissionHash` (as executed), and the three must agree. -On the artifact, each record and blob must also sit at its own address, the -pure BLAKE3 of its bytes. The pure hashes run on parallel tasks. +On the artifact, each record, blob and binding must also sit at its own +address, the pure BLAKE3 of its bytes. The pure hashes run on parallel tasks, +the inputs dealt out largest first. -The artifact is `$IX_HASH_DIFF_IXE` when set; otherwise the runner compiles -`Benchmarks/Compile/CompileInitStd.lean` with `ix compile` -(`.lake/build/bin/ix`, which `IxTests` builds) into a temporary directory. -/ +`admission-hash-diff-initstd`, in the merge queue's Init+Std partition, runs +the artifact comparison on the Init+Std artifact `initstd-reference` compiles. -/ namespace Tests.Ix.Kernel.AdmissionHashDiff @@ -70,12 +73,28 @@ structure Mismatch where lean : Address admission : Address -/-- The pure hashes of `inputs`, on about `workers` parallel tasks, in order. -/ +/-- The indices of the inputs, given by their sizes, that each of `workers` +tasks hashes: ranked by size, largest first, and dealt out round-robin, so +every task gets its share of the largest. (The generated inputs come grouped +by kind of length, with the long ones together at the end; contiguous slices +left one task most of the bytes.) -/ +def deal (sizes : Array Nat) (workers : Nat) : Array (Array Nat) := + let order := (Array.range sizes.size).qsort fun i j => sizes[i]! > sizes[j]! + (Array.range workers).map fun t => + (Array.range ((order.size + workers - 1 - t) / workers)).map fun k => order[t + k * workers]! + +/-- The pure hashes of `inputs`, on `workers` parallel tasks, in order. -/ def pureHashes (inputs : Array ByteArray) (workers : Nat) : Array Address := - let size := max ((inputs.size + workers - 1) / max workers 1) 1 - let tasks := (List.range ((inputs.size + size - 1) / size)).toArray.map fun i => - Task.spawn fun _ => (inputs.extract (i * size) ((i + 1) * size)).map Address.blake3Pure - tasks.foldl (init := #[]) fun acc t => acc ++ t.get + let tasks := (deal (inputs.map (·.size)) (max workers 1)).map fun share => + Task.spawn fun _ => share.map fun i => (i, Address.blake3Pure inputs[i]!) + let hashed := tasks.foldl (init := #[]) fun acc task => acc ++ task.get + (hashed.qsort fun a b => a.1 < b.1).map (·.2) + +-- every index once, whatever the sizes and the worker count +#guard [1, 2, 3, 7, 10].all fun w => + ((deal #[1, 9, 0, 4, 4, 7, 2] w).toList.flatMap (·.toList)).mergeSort == List.range 7 +-- the largest inputs go to different tasks +#guard (deal #[1, 9, 0, 8, 4, 7, 2] 3).toList.map (·.toList) == [[1, 4, 2], [3, 6], [5, 0]] /-- The times of one comparison, in milliseconds. -/ structure Times where @@ -110,22 +129,21 @@ def compare (inputs : Array ByteArray) (label : Nat → String) let bytes := inputs.foldl (init := 0) fun n b => n + b.size return (bad, { rust := t1 - t0, admission := t2 - t1, lean := t3 - t2 }, bytes) -/-- The Init+Std artifact's path, and a temporary directory to remove. -/ -def artifact : IO (System.FilePath × Option System.FilePath) := do - if let some path := (← IO.getEnv "IX_HASH_DIFF_IXE") then return (path, none) - let ix : System.FilePath := ".lake" / "build" / "bin" / "ix" - unless (← ix.pathExists) do - throw (IO.userError s!"{ix} missing: run `lake build ix`, or set IX_HASH_DIFF_IXE") - let dir ← IO.FS.createTempDir - let out := dir / "initstd.ixe" - let exe ← IO.FS.realPath ix - IO.println s!"[admission-hash-diff] compiling Benchmarks/Compile/CompileInitStd.lean with {exe}" - let args := #["compile", "Benchmarks/Compile/CompileInitStd.lean", "--out", out.toString] - let r ← IO.Process.output { cmd := exe.toString, args } - unless r.exitCode == 0 do - IO.FS.removeDirAll dir - throw (IO.userError s!"ix compile exited {r.exitCode}: {r.stderr.takeEnd 600}") - return (out, some dir) +/-- `$IX_HASH_DIFF_IXE`, when set and not empty. -/ +def overridePath : IO (Option System.FilePath) := do + return (← IO.getEnv "IX_HASH_DIFF_IXE").bind fun p => if p.isEmpty then none else some p + +/-- Read and decode an `.ixe` file. -/ +def readArtifact (path : System.FilePath) : IO Ixon.Env := do + IO.ofExcept (Ixon.deEnv (← IO.FS.readBinFile path)) + +/-- Load `$IX_HASH_DIFF_IXE` when set, otherwise compile the `ReaderFidelityDefs` +fixture closure in-process. -/ +def loadArtifact : IO (String × Ixon.Env) := do + if let some path ← overridePath then + return (path.toString, ← readArtifact path) + let (_, inp) ← Tests.Ix.Kernel.ReaderRoundtrip.input + return ("ReaderFidelityDefs", inp.ixon) def report (label : String) (bad : Array Mismatch) : IO Unit := do for m in bad.toList.take 20 do @@ -135,10 +153,11 @@ def report (label : String) (bad : Array Mismatch) : IO Unit := do def timesText (t : Times) (workers : Nat) : String := s!"Rust {t.rust} ms, admission {t.admission} ms, pure {t.lean} ms ({workers} tasks)" -def run : IO UInt32 := do - let workers := 32 - let mut failed := false - -- generated inputs +/-- The pure hashes run on this many tasks. -/ +def workers : Nat := 32 + +/-- The generated inputs, compared; whether all agree. -/ +def generatedLeg : IO Bool := do let gen : Array (Nat × Nat) := lengths.foldl (init := #[]) fun acc n => acc ++ #[(0, n), (1, n), (2, n), (3, n)] let genInputs := gen.map fun (k, n) => pattern k n @@ -147,37 +166,60 @@ def run : IO UInt32 := do IO.println s!"[admission-hash-diff] generated: {genInputs.size} inputs ({lengths.size} lengths \ × 4 patterns, longest {lengths.foldl max 0} bytes), {bytes} bytes: {timesText times workers}; \ {bad.size} mismatches" - unless bad.isEmpty do - report "generated" bad - failed := true - -- the Init+Std artifact - let (path, tmp) ← artifact - try - let t0 ← IO.monoMsNow - let file ← IO.FS.readBinFile path - let ixe ← IO.ofExcept (Ixon.deEnv file) - let records := ixe.consts.toArray - let blobs := ixe.blobs.toArray - let inputs := records.map (·.2.rawBytes) ++ blobs.map (·.2) - let addrs := records.map (·.1) ++ blobs.map (·.1) - let t1 ← IO.monoMsNow - let label (i : Nat) : String := - if i < records.size then s!"record {addrs[i]!}" else s!"blob {addrs[i]!}" - let (bad, times, bytes) ← compare inputs label (some addrs) workers - IO.println s!"[admission-hash-diff] Init+Std artifact {path}: {file.size} bytes, \ - {records.size} records and {blobs.size} blobs ({bytes} bytes hashed; load {t1 - t0} ms): \ - {timesText times workers}; {bad.size} mismatches (disagreeing hashes, or an input not at \ - its address)" - unless bad.isEmpty do - report "Init+Std" bad - failed := true - finally - if let some dir := tmp then IO.FS.removeDirAll dir - if failed then + unless bad.isEmpty do report "generated" bad + return bad.isEmpty + +/-- Every record and blob of `ixe`, and its role binding if it has one, +compared, each also at its own address; whether all agree. `loadMs` is the +time it took to load. -/ +def artifactLeg (source : String) (ixe : Ixon.Env) (loadMs : Nat) : IO Bool := do + let records := ixe.consts.toArray + let blobs := ixe.blobs.toArray + let binding := ixe.roleBinding.toArray + let inputs := records.map (·.2.rawBytes) ++ blobs.map (·.2) ++ binding.map (·.bytes) + let addrs := records.map (·.1) ++ blobs.map (·.1) ++ binding.map (·.address) + let label (i : Nat) : String := + if i < records.size then s!"record {addrs[i]!}" + else if i < records.size + blobs.size then s!"blob {addrs[i]!}" + else s!"role binding {addrs[i]!}" + let (bad, times, bytes) ← compare inputs label (some addrs) workers + IO.println s!"[admission-hash-diff] artifact {source}: \ + {records.size} records, {blobs.size} blobs and {binding.size} role binding(s) \ + ({bytes} bytes hashed; load {loadMs} ms): \ + {timesText times workers}; {bad.size} mismatches (disagreeing hashes, or an input not at \ + its address)" + unless bad.isEmpty do report "artifact" bad + return bad.isEmpty + +def verdict (ok : Bool) : IO UInt32 := do + unless ok do IO.eprintln "[admission-hash-diff] FAILED" return 1 IO.println "[admission-hash-diff] OK: Address.blake3, Address.blake3Pure and \ Address.admissionHash agree on every input" return 0 +/-- `admission-hash-diff` (primary): the generated inputs and the artifact +(`$IX_HASH_DIFF_IXE`, or the fixture closure). -/ +def run : IO UInt32 := do + let generated ← generatedLeg + let t0 ← IO.monoMsNow + let (source, ixe) ← loadArtifact + let artifact ← artifactLeg source ixe ((← IO.monoMsNow) - t0) + verdict (generated && artifact) + +/-- `admission-hash-diff-initstd` (the merge queue's Init+Std partition): the +artifact comparison alone, on the Init+Std artifact that `initstd-reference` +leaves at `Tests.Ix.InitStd.keptArtifact` (or `$IX_HASH_DIFF_IXE`, when +set). The generated inputs are the primary entry's. -/ +def runInitStd : IO UInt32 := do + let path := (← overridePath).getD Tests.Ix.InitStd.keptArtifact + unless ← path.pathExists do + IO.eprintln s!"[admission-hash-diff] {path} is missing: run `initstd-reference` first, \ + or set IX_HASH_DIFF_IXE to an .ixe" + return 1 + let t0 ← IO.monoMsNow + let ixe ← readArtifact path + verdict (← artifactLeg path.toString ixe ((← IO.monoMsNow) - t0)) + end Tests.Ix.Kernel.AdmissionHashDiff diff --git a/Tests/Ix/Kernel/Arena.lean b/Tests/Ix/Kernel/Arena.lean index 66185e919..22e1d302e 100644 --- a/Tests/Ix/Kernel/Arena.lean +++ b/Tests/Ix/Kernel/Arena.lean @@ -1,4 +1,16 @@ -/- +import Ix.Meta +import Ix.CompileCertified +import Ix.Aiur.Protocol +import Ix.Aiur.Compiler +import Ix.IxVM +import Ix.IxVM.ClaimHarness +import Tests.Aiur.Common +import Tests.Ix.Kernel.TutorialMeta +import Tests.Ix.Kernel.TutorialDefs +import Tests.Ix.Kernel.NatReduction +import LSpec + +/-! Drives the Aiur kernel through every lean-kernel-arena tutorial fixture (`Tests.Ix.Kernel.TutorialDefs` + `NatReduction`) using the shared `IxVM.ixVMFull` toplevel + `verify_const` entrypoint. @@ -26,16 +38,6 @@ - constants in `knownIncompatible` (meta-only Lean kernel checks the Aiur kernel structurally cannot see). -/ -import Ix.Meta -import Ix.Aiur.Protocol -import Ix.Aiur.Compiler -import Ix.IxVM -import Ix.IxVM.ClaimHarness -import Tests.Aiur.Common -import Tests.Ix.Kernel.TutorialMeta -import Tests.Ix.Kernel.TutorialDefs -import Tests.Ix.Kernel.NatReduction -import LSpec open LSpec open Tests.Ix.Kernel.TutorialMeta @@ -59,7 +61,7 @@ def knownIncompatible : Array (Lean.Name × String) := #[ (`tut06_bad01, "duplicate levelParams: Anon-mode hygiene check, see src/ix/kernel/check.rs:107"), -- AdvNat.rec is a malformed raw recursor payload. Tests.Ix.Kernel.Tutorial - -- checks it through a dedicated FFI (`rs_kernel_check_malformed_rec_rule_ixon`) + -- checks it through a dedicated path (`rsCheckMalformedRecRuleIxonFFI`) -- that injects the bad rule into Ixon. (`Tests.Ix.Kernel.TutorialDefs.AdvNat.rec, "malformed rec rule; Tutorial uses bespoke FFI"), @@ -131,10 +133,11 @@ def arenaTests (env : Lean.Environment) rawConsts.foldl (fun m ci => m.insert ci.name ci) (Std.HashMap.emptyWithCapacity rawConsts.size) let (_, closed) := collectDepsWithExtras env rawMap - (checks.map (·.name)).toList - let rawEnv ← Ix.CompileM.rsCompileEnvFFI - (closed ++ rawConsts.toList.map fun ci => (ci.name, ci)) - let ixonEnv ← IO.ofExcept (IxVM.ClaimHarness.withDefaultRoleBinding rawEnv.toEnv) + ((checks.map (·.name)).toList ++ (rawConsts.toList.map (·.name))) + let allConsts := closed ++ rawConsts.toList.map fun ci => (ci.name, ci) + let (rawEnv, _, _) ← IO.ofExcept <| + Ix.Cert.compileUnchecked (Ix.CompileM.canonInput allConsts.toArray 32) + let ixonEnv ← IO.ofExcept (IxVM.ClaimHarness.withDefaultRoleBinding rawEnv) let mut tests : TestSeq := .done for c in checks do let label := s!"arena {if c.expectPass then "GOOD" else "BAD"} {c.name}" diff --git a/Tests/Ix/Kernel/BuildPrimOrigs.lean b/Tests/Ix/Kernel/BuildPrimOrigs.lean deleted file mode 100644 index 74eab609d..000000000 --- a/Tests/Ix/Kernel/BuildPrimOrigs.lean +++ /dev/null @@ -1,175 +0,0 @@ -/- - Dump ORIGINAL (LEON content-hash) primitive addresses for hardcoding - into the Rust kernel (`crates/common/src/prim_addrs.rs::PrimAddrs::new_orig`). - - Run with: `lake test -- --ignored rust-kernel-build-prim-origs`. The test prints a - `(lean_name, leon_hash_hex)` line for every primitive the Rust kernel - expects to find in `PrimAddrs::new_orig`. Each hex is - `ConstantInfo::get_hash()` (defined in `src/ix/env.rs`) on the - primitive's declaration in the current Lean environment — a Blake3 - digest over the serialized original `ConstantInfo` (name + level - params + type expression + variant-specific fields: ctors, rules, - `all`, value, hints, etc.). - - This is the addressing scheme `orig_kenv` uses: two Lean constants - with the same name but different content hash to different addresses, - so a rogue environment can't silently shadow a primitive just by - naming its own declaration `Nat`. - - Paste the output lines into `PrimAddrs::new_orig` whenever either: - - a primitive's Lean-side name or content changes upstream, or - - the `ConstantInfo::get_hash` byte layout is revised. - - The primitive name list itself is shared with - `Tests.Ix.Kernel.BuildPrimitives.kernelPrimitives` — a single source - of truth. When upstream Lean renames a primitive, update that list - once and regenerate BOTH this table AND the canonical one (via - `rust-kernel-build-primitives`). - - Failure modes: - - Missing: a primitive name isn't in the Lean env (likely renamed - upstream). Printed as `// MISSING:` comments so the emitted table is - still valid as-is for partial regeneration. - - Address change: the LEON hex for a primitive has changed — paste - the new hex into `PrimAddrs::new_orig`. --/ -import Ix.Common -import Ix.CompileM -- rsLeonHashesFFI -import Ix.Environment -import Ix.Address -import Tests.Ix.Kernel.BuildPrimitives -import LSpec - -open LSpec - -namespace Tests.Ix.Kernel.BuildPrimOrigs - -open Tests.Ix.Kernel.BuildPrimitives (kernelPrimitives getConstRefs collectDeps parseNameToLean) - -/-- Dump the current `(name, leon_hash_hex)` table for every entry in - `Tests.Ix.Kernel.BuildPrimitives.kernelPrimitives`. Pass iff every - entry resolves; missing names are printed as `// MISSING:` comments - so the output is still valid as-is for partial regeneration. - - Mirrors the structure of `BuildPrimitives.testBuildPrimitives` — the - only semantic difference is the hash we dump (LEON - `ConstantInfo::get_hash` vs. the canonical post-compile content - address). -/ -def testBuildPrimOrigs : TestSeq := - .individualIO "build prim-origs dump" none (do - let leanEnv ← get_env! - let roots := kernelPrimitives.map parseNameToLean - let needed := collectDeps leanEnv roots - let filtered := leanEnv.constants.toList.filter fun (name, _) => - needed.contains name - - IO.println s!"[build-prim-origs] {filtered.length} constants in transitive closure" - - -- Compute LEON hashes for every constant in the transitive closure. - let pairs : Array (Ix.Name × Address) ← Ix.CompileM.rsLeonHashesFFI filtered - - IO.println s!"[build-prim-origs] LEON hashes computed: {pairs.size}" - - -- Build Ix.Name → Address lookup. - let mut byName : Std.HashMap Ix.Name Address := {} - for p in pairs do - byName := byName.insert p.1 p.2 - - IO.println "" - IO.println "// === Primitive ORIGINAL (LEON content-hash) addresses ===" - IO.println "// Format: (\"lean_name\", \"leon_hash_hex\")" - IO.println "// Hash: ConstantInfo::get_hash (src/ix/env.rs) —" - IO.println "// Blake3 over the serialized original ConstantInfo." - IO.println "// These are the addresses KIds live at in `orig_kenv`." - IO.println "" - - let mut found : Nat := 0 - let mut missing : Array String := #[] - - for primName in kernelPrimitives do - let ixName := Ix.Name.fromLeanName (parseNameToLean primName) - match byName[ixName]? with - | none => - IO.println s!"// MISSING: {primName}" - missing := missing.push primName - | some addr => - let addrHex := toString addr - IO.println s!"(\"{primName}\", \"{addrHex}\")," - found := found + 1 - - IO.println "" - IO.println s!"// Found: {found}/{kernelPrimitives.size}" - if !missing.isEmpty then - IO.println s!"// Missing: {missing}" - - let msg : Option String := - if missing.isEmpty then none else some s!"{missing.size} primitives missing from Lean env" - return (missing.isEmpty, found, missing.size, msg) - ) .done - -@[extern "rs_prim_addrs_orig"] -opaque rsPrimAddrsOrigFFI : IO (Array (String × String)) - -/-- Every original/LEON primitive address stored in `PrimAddrs::new_orig()` - must match `ConstantInfo::get_hash()` over the live Lean environment. - `eagerReduce` is excluded because its original address is a synthetic - kernel marker rather than the hash of the Lean declaration. -/ -def testPrimOrigsParity : TestSeq := - .individualIO "original primitive address parity (PrimAddrs vs live LEON hashes)" none (do - let leanEnv ← get_env! - let roots := kernelPrimitives.map parseNameToLean - let needed := collectDeps leanEnv roots - let filtered := leanEnv.constants.toList.filter fun (name, _) => - needed.contains name - let pairs : Array (Ix.Name × Address) ← Ix.CompileM.rsLeonHashesFFI filtered - - let live : Std.HashMap Ix.Name Address := - pairs.foldl (init := {}) fun m (name, addr) => m.insert name addr - let hardcoded ← rsPrimAddrsOrigFFI - let lookup : Std.HashMap String String := - hardcoded.foldl (init := {}) fun m (name, hex) => m.insert name hex - - let mut mismatches : Array String := #[] - let mut missing : Array String := #[] - let hardcodedNames := hardcoded.map fun (name, _) => name - if hardcodedNames != kernelPrimitives then - mismatches := mismatches.push - s!"primitive catalog mismatch: expected {kernelPrimitives}, got {hardcodedNames}" - - for primName in kernelPrimitives do - if primName == "eagerReduce" then continue - let ixName := Ix.Name.fromLeanName (parseNameToLean primName) - match live[ixName]? with - | none => missing := missing.push primName - | some liveAddr => - match lookup[primName]? with - | none => - mismatches := mismatches.push - s!"{primName}: missing from PrimAddrs::lean_orig_parity_table" - | some hardcodedHex => - let liveHex := toString liveAddr - if liveHex != hardcodedHex then - mismatches := mismatches.push - s!"{primName}: live={liveHex} PrimAddrs={hardcodedHex}" - - if !missing.isEmpty then - IO.eprintln s!"original primitive parity: {missing.size} primitive(s) missing from Lean env:" - for name in missing do IO.eprintln s!" {name}" - if !mismatches.isEmpty then - IO.eprintln s!"original primitive parity: {mismatches.size} mismatch(es):" - for mismatch in mismatches do IO.eprintln s!" {mismatch}" - IO.eprintln "Regenerate via `lake test -- --ignored rust-kernel-build-prim-origs` and paste into PrimAddrs::new_orig in crates/common/src/prim_addrs.rs." - - let totalProblems := missing.size + mismatches.size - let passed := kernelPrimitives.size - min totalProblems kernelPrimitives.size - let msg : Option String := - if totalProblems == 0 then none - else some s!"{mismatches.size} mismatch(es), {missing.size} missing" - return (totalProblems == 0, passed, totalProblems, msg) - ) .done - -def paritySuite : List TestSeq := [testPrimOrigsParity] - -def suite : List TestSeq := [testBuildPrimOrigs] - -end Tests.Ix.Kernel.BuildPrimOrigs diff --git a/Tests/Ix/Kernel/BuildPrimitives.lean b/Tests/Ix/Kernel/BuildPrimitives.lean index bcca6deba..b0cf58f4c 100644 --- a/Tests/Ix/Kernel/BuildPrimitives.lean +++ b/Tests/Ix/Kernel/BuildPrimitives.lean @@ -1,12 +1,23 @@ -/- - Dump primitive constant names and content-addresses for hardcoding into the - Rust kernel (`crates/common/src/prim_addrs.rs`). +import Ix.Common +import Ix.CompileM +import Ix.CompileCertified +import Ix.Meta +import Ix.Address +import Ix.Environment +import Ix.Ixon +import LSpec - Run with: `lake test -- --ignored rust-kernel-build-primitives`. The test prints a - `(lean_name, content_address_hex)` line for every primitive the Rust - kernel expects to find in `PrimAddrs::new`. Paste the output over the - corresponding entries whenever Lean's stdlib changes and tests start - failing with `@@` / synthetic-KId fallbacks. +/-! + Dump primitive constant names and content-addresses for hardcoding into + `PrimAddrs::new` (`crates/common/src/prim_addrs.rs`), the table the IxVM + witness builder and the shard planner seed primitives from, and its Lean + mirror `Ix/PrimAddrs.lean`. + + Run with: `lake test -- --ignored rust-kernel-build-primitives` (the suite + keeps its old name). The test prints a `(lean_name, content_address_hex)` + line for every primitive in `PrimAddrs::new`. Paste the output over the + corresponding entries whenever Lean's stdlib changes and + `primitive-address-parity` (`paritySuite` below) fails. Failure modes: - Missing: a primitive name isn't in the Lean env (likely renamed upstream). @@ -14,21 +25,13 @@ - Address change: the address for a primitive has changed — paste the new hex into `PrimAddrs::new`. -/ -import Ix.Common -import Ix.CompileM -import Ix.Meta -import Ix.Address -import Ix.Environment -import Ix.Ixon -import LSpec open LSpec namespace Tests.Ix.Kernel.BuildPrimitives -/-- The Lean names of every primitive the Rust kernel resolves in - `PrimAddrs::new`. Keep this in sync with the `Primitives` struct in - `crates/kernel/src/primitive.rs`. -/ +/-- The Lean names of every primitive in `PrimAddrs::new` + (`crates/common/src/prim_addrs.rs`); keep the two in sync. -/ def kernelPrimitives : Array String := #[ "Nat", "Nat.zero", "Nat.succ", "Nat.add", "Nat.pred", "Nat.sub", "Nat.mul", "Nat.pow", @@ -89,10 +92,7 @@ def kernelPrimitives : Array String := #[ ] /-- Parse a dotted string into a `Lean.Name`, preferring numeric components - when the part parses as `Nat`. Mirrors the ix_old helper. - - Public so `Tests.Ix.Kernel.BuildPrimOrigs` (the LEON-hash sister test) - can share the same parse logic. -/ + when the part parses as `Nat`. Mirrors the ix_old helper. -/ def parseNameToLean (s : String) : Lean.Name := Id.run do let mut name := Lean.Name.anonymous for part in s.splitOn "." do @@ -152,8 +152,7 @@ def testBuildPrimitives : TestSeq := IO.println s!"[build-primitives] {filtered.length} constants in transitive closure" - let rawEnv ← Ix.CompileM.rsCompileEnvFFI filtered - let env : Ixon.Env := rawEnv.toEnv + let (env, _) ← IO.ofExcept (Ix.Cert.compileReport (Ix.CompileM.canonInput filtered.toArray 32)) IO.println s!"[build-primitives] Ixon env: {env.consts.size} consts, {env.named.size} named" IO.println "" @@ -188,14 +187,14 @@ def testBuildPrimitives : TestSeq := /-- FFI: expose `PrimAddrs::new()` from Rust as `(lean_name, hex_address)` pairs in the same order as `kernelPrimitives` below. Used to detect drift between hardcoded - addresses and what `rsCompileEnvFFI` would produce today. -/ + addresses and what `Ix.Cert.compileReport` would produce today. -/ @[extern "rs_prim_addrs_canonical"] opaque rsPrimAddrsCanonicalFFI : IO (Array (String × String)) /-- Parity check: every primitive's content address as stored in Rust's `PrimAddrs::new()` must match the address produced by compiling that primitive's Lean declaration through the live - `rsCompileEnvFFI` pipeline. A mismatch means someone changed the + certified compile pipeline. A mismatch means someone changed the compile/serialize logic in a way that altered a primitive's content hash — Aiur and downstream kernel primitive resolution will silently break if `PrimAddrs::new()` isn't updated. @@ -217,8 +216,7 @@ def testPrimitivesParity : TestSeq := let needed := collectDeps leanEnv roots let filtered := leanEnv.constants.toList.filter fun (name, _) => needed.contains name - let rawEnv ← Ix.CompileM.rsCompileEnvFFI filtered - let env : Ixon.Env := rawEnv.toEnv + let (env, _) ← IO.ofExcept (Ix.Cert.compileReport (Ix.CompileM.canonInput filtered.toArray 32)) let hardcoded ← rsPrimAddrsCanonicalFFI let lookup : Std.HashMap String String := @@ -264,54 +262,7 @@ def testPrimitivesParity : TestSeq := return (total_problems == 0, passed, total_problems, msg) ) .done -/-- The native-evaluation constants the Rust kernel declines, in the order of - `ix_kernel::primitive::native_evaluation_addrs`. -/ -def nativeEvaluationConstants : Array String := #[ - "Lean.trustCompiler", "Lean.reduceBool", "Lean.reduceNat", - "Lean.ofReduceBool", "Lean.ofReduceNat" -] - -/-- FFI: the kernel's `(lean_name, hex_address)` table of the - native-evaluation constants it declines. -/ -@[extern "rs_native_evaluation_addrs"] -opaque rsNativeEvaluationAddrsFFI : IO (Array (String × String)) - -/-- Parity check: every address the Rust kernel declines as a - native-evaluation constant must be the address a live compile gives that - Lean declaration. A stale address would let the kernel accept the - declaration it is meant to decline. -/ -def testNativeEvaluationParity : TestSeq := - .individualIO "native-evaluation address parity (kernel table vs live compile)" none (do - let leanEnv ← get_env! - let roots := nativeEvaluationConstants.map parseNameToLean - let needed := collectDeps leanEnv roots - let filtered := leanEnv.constants.toList.filter fun (name, _) => - needed.contains name - let rawEnv ← Ix.CompileM.rsCompileEnvFFI filtered - let env : Ixon.Env := rawEnv.toEnv - - let table ← rsNativeEvaluationAddrsFFI - let mut problems : Array String := #[] - let names := table.map (·.1) - if names != nativeEvaluationConstants then - problems := problems.push - s!"catalog mismatch: expected {nativeEvaluationConstants}, got {names}" - for (name, hex) in table do - match env.named[parseIxName name]? with - | none => problems := problems.push s!"{name}: missing from the compiled env" - | some named => - let live := toString named.addr - if live != hex then - problems := problems.push s!"{name}: live={live} kernel={hex}" - for p in problems do - IO.eprintln s!"native-evaluation parity: {p}" - let size := nativeEvaluationConstants.size - let msg : Option String := - if problems.isEmpty then none else some s!"{problems.size} problem(s)" - return (problems.isEmpty, size - min problems.size size, problems.size, msg) - ) .done - -def paritySuite : List TestSeq := [testPrimitivesParity, testNativeEvaluationParity] +def paritySuite : List TestSeq := [testPrimitivesParity] def suite : List TestSeq := [testBuildPrimitives] diff --git a/Tests/Ix/Kernel/CheckCompPoly.lean b/Tests/Ix/Kernel/CheckCompPoly.lean index cad1485ad..a64561060 100644 --- a/Tests/Ix/Kernel/CheckCompPoly.lean +++ b/Tests/Ix/Kernel/CheckCompPoly.lean @@ -13,6 +13,7 @@ lake test -- kernel-check-comppoly --ignored -/ import Ix.KernelCheck +import Tests.Ix.Kernel.FocusedLeanCheck import LSpec open LSpec diff --git a/Tests/Ix/Kernel/CheckEnv.lean b/Tests/Ix/Kernel/CheckEnv.lean deleted file mode 100644 index 097bd2833..000000000 --- a/Tests/Ix/Kernel/CheckEnv.lean +++ /dev/null @@ -1,388 +0,0 @@ -/- - Full-environment typechecking test for the Rust kernel. - - Mirrors ix_old's `Tests/Ix/Kernel/CheckEnv.lean::testRustCheckEnv`: - capture the `get_env!` environment, ship every constant through the Rust - FFI pipeline (Lean env → Ixon compile → kernel ingress → typecheck), pass - iff every constant typechecks. - - Reuses `CheckError` and `rsCheckConstsFFI` from `Tests.Ix.Kernel.Tutorial` - so the FFI ABI (ctor tags 0 = kernelException, 1 = compileError) has a - single Lean-side source of truth. - - Run with: `lake test -- kernel-check-env --ignored` --/ -import Ix.Common -import Ix.Meta -import Ix.KernelCheck -import Tests.Ix.Kernel.Tutorial -import Tests.Ix.Compile.Mutual -import Tests.Ix.Kernel.TutorialMeta -import LSpec - -open LSpec -open Ix.KernelCheck (CheckError rsCheckConstsFFI) -open Tests.Ix.Kernel.TutorialMeta - -namespace Tests.Ix.Kernel.CheckEnv - -private def tutorialDefsNamespace : Lean.Name := - `Tests.Ix.Kernel.TutorialDefs - -private def natReductionNamespace : Lean.Name := - `Tests.Ix.Kernel.NatReduction - -private def stringReductionNamespace : Lean.Name := - `Tests.Ix.Kernel.StringReduction - -private def isFromFixtureModule (env : Lean.Environment) (name : Lean.Name) : Bool := - match env.getModuleIdxFor? name with - | some modIdx => - match env.header.moduleNames[modIdx]? with - | some modName => modName == tutorialDefsNamespace || modName == natReductionNamespace - || modName == stringReductionNamespace - | none => false - | none => false - -private def fixtureNames (env : Lean.Environment) : Std.HashSet Lean.Name := - Id.run do - let mut names : Std.HashSet Lean.Name := Std.HashSet.emptyWithCapacity 256 - for tc in getTestCases env do - for n in tc.decls do - if isFromFixtureModule env n then - names := names.insert n - for ci in getRawConsts env do - if isFromFixtureModule env ci.name then - names := names.insert ci.name - return names - -private def isFixtureName (fixtures : Std.HashSet Lean.Name) (name : Lean.Name) : Bool := - tutorialDefsNamespace.isPrefixOf name - || name.toString.contains "_private.Tests.Ix.Kernel.TutorialDefs." - || natReductionNamespace.isPrefixOf name - || name.toString.contains "_private.Tests.Ix.Kernel.NatReduction." - || stringReductionNamespace.isPrefixOf name - || name.toString.contains "_private.Tests.Ix.Kernel.StringReduction." - || fixtures.contains name - -/-- Lean's native-evaluation constants. Evaluation by the compiler is outside - the checked theory, so the Rust kernel declines them, as the certified - checker does. -/ -private def nativeEvaluationNames : Std.HashSet Lean.Name := - Std.HashSet.ofList [`Lean.trustCompiler, `Lean.reduceBool, `Lean.reduceNat, - `Lean.ofReduceBool, `Lean.ofReduceNat] - -/-- Whether `msg` is the Rust kernel's native-evaluation decline. -/ -private def isNativeEvaluationDecline (msg : String) : Bool := - (msg.splitOn "native-evaluation constant").length > 1 - -def testRustCheckEnv : TestSeq := - .individualIO "Rust kernel check_env" none (do - let leanEnv ← get_env! - let envConsts := leanEnv.constants.toList - let fixtures := fixtureNames leanEnv - let allConsts := envConsts.filter fun (name, _) => - !isFixtureName fixtures name - -- Pass `Lean.Name` structurally across the FFI; Rust's - -- `decode_name_array` reconstructs the same `Name` value (same - -- component strings, same content hash) that the kernel uses - -- internally, so name lookup is an exact structural match. - let allNames : Array Lean.Name := - allConsts.toArray.map fun (name, _) => name - -- Every env constant except the native-evaluation ones is expected to - -- typecheck; those must be declined. `expect_pass` is an FFI-side - -- progress-log hint (see `src/ffi/kernel.rs`'s `ErrKind` and - -- `check_consts_loop`) that keeps the `[ok]` / `[FAIL]` log lines - -- consistent. - let expectPass : Array Bool := - allNames.map fun name => !nativeEvaluationNames.contains name - let skippedCount := envConsts.length - allConsts.length - - IO.println s!"[check-env] Environment has {envConsts.length} constants; checking {allNames.size} (skipping {skippedCount} fixture constants)" - - let start ← IO.monoMsNow - -- Full-env runs ship tens of thousands of constants: `quiet=true` - -- keeps the console usable by rewriting the current-constant label - -- in place and only persisting slow (>=7s by default) / failing / - -- not-found entries. Parallel quiet mode also prints periodic - -- done/total, rate, ETA, and oldest in-flight constants. - -- - -- Rust returns results in the same order as `allNames`, so - -- `results[i]` pairs with `allNames[i]`. - let results ← rsCheckConstsFFI allConsts allNames expectPass true - let elapsed := (← IO.monoMsNow) - start - - let mut passed := 0 - let mut declined := 0 - let mut failures : Array (Lean.Name × String) := #[] - for i in [:allNames.size] do - let name := allNames[i]! - let native := nativeEvaluationNames.contains name - match results[i]! with - | none => - if native then - failures := failures.push (name, "accepted; native evaluation must be declined") - else - passed := passed + 1 - | some err => - -- Unpack the `CheckError` ctor manually; `repr err` on multi-line - -- kernel messages is seconds-slow per call (see the same comment - -- in `Tutorial.lean`). - let msg := match err with - | .kernelException m => s!"kernel: {m}" - | .compileError m => s!"compile: {m}" - if native && isNativeEvaluationDecline msg then - declined := declined + 1 - else - failures := failures.push (name, msg) - - IO.println s!"[check-env] Checked {allNames.size} constants in {elapsed}ms" - IO.println s!"[check-env] {passed}/{allNames.size} passed, {declined} native-evaluation constant(s) declined as required" - - if !failures.isEmpty then - IO.println s!"[check-env] {failures.size} failure(s):" - for (name, err) in failures[:min 30 failures.size] do - IO.println s!" ✗ {name}: {err}" - - let total := passed + declined + failures.size - if failures.isEmpty then - return (true, passed + declined, total, none) - else - return (false, passed + declined, total, - some s!"Kernel check failed with {failures.size} failure(s)") - ) .done - -/-- Known failing / hanging constants from a `testRustCheckEnv` run. - Used by `testRustCheckConsts` for fast reproduction without paying for - the full env pass. Edit when bisecting a regression; grouped by root - cause in order of discovery. - - The *Rust side* prints `[i/N] name ... ok/FAIL` per constant as the - check proceeds, so a hang is recognisable by a missing terminator - after `[i/N] name ...` — look for the last printed name. -/ -def focusConsts : Array Lean.Name := #[ - -- 2026-08-22: `.eq_def` proofs of mutual structural defs over a - -- Type-level mutual inductive reference `X.brecOn.go` / `X.brecOn.eq` - -- directly, so a compiler that reorders the block must permute those - -- call sites (torchlean.ixe `NN.GraphSpec.DAG.*.eq_def` failure family). - -- The two fixture blocks have opposite source orders, so any reordering - -- breaks one family's eq_defs with AppTypeMismatch. - Lean.mkPrivateNameCore `Tests.Ix.Compile.Mutual - `Tests.Ix.Compile.Mutual.TypeBrecOnEqDef.Ar.wk.eq_def, - Lean.mkPrivateNameCore `Tests.Ix.Compile.Mutual - `Tests.Ix.Compile.Mutual.TypeBrecOnEqDef.Tm.wk.eq_def, - Lean.mkPrivateNameCore `Tests.Ix.Compile.Mutual - `Tests.Ix.Compile.Mutual.TypeBrecOnEqDef.Ar2.wk.eq_def, - Lean.mkPrivateNameCore `Tests.Ix.Compile.Mutual - `Tests.Ix.Compile.Mutual.TypeBrecOnEqDef.Tm2.wk.eq_def, - -- Tier B `.brecOn` references: bare and applied only through the major, - -- leaving the canonically permuted handler band unapplied. - `Tests.Ix.Compile.Mutual.TypeBrecOnEqDef.arBrecAlias, - `Tests.Ix.Compile.Mutual.TypeBrecOnEqDef.ar2BrecAlias, - `Tests.Ix.Compile.Mutual.TypeBrecOnEqDef.arBrecPartial, - `Tests.Ix.Compile.Mutual.TypeBrecOnEqDef.ar2BrecPartial, - - -- 2026-08-22: user proofs that apply a mutual `.rec` DIRECTLY with - -- explicit motives (`induction … using X.rec (motive_N := …)`). The - -- motive/minor arguments follow Lean's source order, so a compiler that - -- reorders the block must permute the call site (phiconfluence.ixe - -- `parB_*`/`par_*`/`nf_*` AppTypeMismatch failure family). The two blocks - -- have opposite source orders, so any reordering breaks one of them. - -- PropRecMotives: mutual Prop-valued predicates (the `Par`/`ParB` shape). - -- NestedMutualRecMotives: Type-valued nested mutual with an aux motive - -- (the `Term`/`Binding` shape behind `nf_devel`/`nf_false_reducible`). - `Tests.Ix.Compile.Mutual.PropRecMotives.pb_length, - `Tests.Ix.Compile.Mutual.PropRecMotives.pa_eq, - `Tests.Ix.Compile.Mutual.PropRecMotives.pb2_length, - `Tests.Ix.Compile.Mutual.PropRecMotives.pa2_eq, - -- Call-site shape coverage (plans/callsite-adapter-generalization.md): - -- direct full application at the Const head, inner-full redex, and - -- dead-binder split redex (Collapsed-entry fallback), each in both - -- source orders. - `Tests.Ix.Compile.Mutual.PropRecMotives.pb_len_direct, - `Tests.Ix.Compile.Mutual.PropRecMotives.pb_len_lam, - `Tests.Ix.Compile.Mutual.PropRecMotives.pb_len_dead, - `Tests.Ix.Compile.Mutual.PropRecMotives.pb2_len_direct, - `Tests.Ix.Compile.Mutual.PropRecMotives.pb2_len_lam, - `Tests.Ix.Compile.Mutual.PropRecMotives.pb2_len_dead, - -- Tier A partial applications (motives+minors present; identity - -- indices/major suffix absent) and Tier B partial/bare references. - `Tests.Ix.Compile.Mutual.PropRecMotives.pbLenFn, - `Tests.Ix.Compile.Mutual.PropRecMotives.pb_len_let, - `Tests.Ix.Compile.Mutual.PropRecMotives.pb_len_arg, - `Tests.Ix.Compile.Mutual.PropRecMotives.pbCases, - `Tests.Ix.Compile.Mutual.PropRecMotives.pbRecAlias, - `Tests.Ix.Compile.Mutual.PropRecMotives.pbRec_eq_self, - `Tests.Ix.Compile.Mutual.PropRecMotives.pb2LenFn, - `Tests.Ix.Compile.Mutual.PropRecMotives.pb2_len_let, - `Tests.Ix.Compile.Mutual.PropRecMotives.pb2_len_arg, - `Tests.Ix.Compile.Mutual.PropRecMotives.pb2Cases, - `Tests.Ix.Compile.Mutual.PropRecMotives.pb2RecAlias, - `Tests.Ix.Compile.Mutual.PropRecMotives.pb2Rec_eq_self, - `Tests.Ix.Compile.Mutual.NestedMutualRecMotives.tm_eq_self, - `Tests.Ix.Compile.Mutual.NestedMutualRecMotives.tm2_eq_self, - - -- 2026-08-19 (v4.33 bump fallout): Prop-mutual IndPredBelow matchers. - -- The `match_2` matchers mention `OddP.below`/`EvenP.below` applied to - -- the parent family's motives, so they pin the below inductive's - -- parameter order against Lean's authored order (see #571). - Lean.mkPrivateNameCore `Tests.Ix.Compile.Mutual - `Tests.Ix.Compile.Mutual.BelowPredicate.oddp_nonneg.match_2, - Lean.mkPrivateNameCore `Tests.Ix.Compile.Mutual - `Tests.Ix.Compile.Mutual.BelowPredicate.evenp_nonneg.match_2, - -- Tier A `.below` references with the complete motive band and an - -- unapplied identity major suffix, in both source orders. - `Tests.Ix.Compile.Mutual.BelowPredicate.evenpBelowPartial, - Lean.mkPrivateNameCore `Tests.Ix.Compile.Mutual - `Tests.Ix.Compile.Mutual.BelowPredicate2.oddp2_nonneg.match_2, - Lean.mkPrivateNameCore `Tests.Ix.Compile.Mutual - `Tests.Ix.Compile.Mutual.BelowPredicate2.evenp2_nonneg.match_2, - `Tests.Ix.Compile.Mutual.BelowPredicate2.evenp2BelowPartial, - - -- Current Nat-conformance follow-up residue from 2026-04-30. - `Lean.Grind.Fin.instPowFinCoOfNatIntCast, - `Fin.pred_one, - `Fin.mul_one, - `Array.setIfInBounds_empty, - `Nat.eq_of_beq_eq_true, - `Nat.gcd_add_one, - `BitVec.msb_neg, - Lean.mkPrivateNameCore `Init.GrindInstances.ToInt - `Lean.Grind.instOfNatInt32SintOfNatNat._proof_2, - Lean.mkPrivateNameCore `Init.GrindInstances.ToInt - `Lean.Grind.instOfNatInt32SintOfNatNat._proof_3, - Lean.mkPrivateNameCore `Init.GrindInstances.ToInt - `Lean.Grind.instOfNatInt64SintOfNatNat._proof_2, - Lean.mkPrivateNameCore `Init.GrindInstances.ToInt - `Lean.Grind.instOfNatInt16SintOfNatNat._proof_2, - Lean.mkPrivateNameCore `Init.Data.Range.Polymorphic.SInt - `ISize.instRxcHasSize_eq, - - -- Current full-env residue from 2026-04-26 after the LRAT/SInt fixes. - `System.Platform.numBits_eq, - `BitVec.umulOverflow_eq, - `Char.ofOrdinal_ordinal, - Lean.mkPrivateNameCore `Init.Data.Char.Ordinal - `Char.ofOrdinal_ordinal._proof_1_4, - `String.toByteArray_empty, - -- Nested auxiliary recursor canonical-order mismatch. - `Lean.Json.rec_1, - -- Extended-structure projection regression coverage. These exercise - -- chained projections generated for `structure HeaderParsedSnapshot extends - -- Snapshot`. - `Lean.Language.Lean.HeaderParsedSnapshot.stx, - `Lean.Language.Lean.HeaderParsedSnapshot.result?, - `Lean.Language.Lean.HeaderParsedSnapshot.metaSnap, - `Lean.Language.Lean.HeaderParsedSnapshot.toSnapshot, - `Lean.Language.Lean.HeaderParsedSnapshot.ictx, - - -- Full-env recursion-depth residue from 2026-04-30 after Nat reducer - -- conformance cleanup. - `List.drop_replicate, - `List.getElem_cons_drop, - `Nat.ble_succ_eq_true, - `Nat.le_of_ble_eq_true, - `Int.negSucc_mul_subNatNat, - Lean.mkPrivateNameCore `Lean.Server.FileWorker.WidgetRequests - `Lean.Widget.makePopup._sparseCasesOn_3, - Lean.mkPrivateNameCore `Lean.Server.References - `Lean.Server.identOf._sparseCasesOn_4, - Lean.mkPrivateNameCore `Lean.Server.InfoUtils - `Lean.Elab.Info.type?._sparseCasesOn_1, - Lean.mkPrivateNameCore `Std.Time.Format - `Std.Time.PlainTime.format._sparseCasesOn_1, - Lean.mkPrivateNameCore `Lean.Server.InfoUtils - `Lean.Elab.Info.lctx._sparseCasesOn_1, - Lean.mkPrivateNameCore `Lean.Server.GoTo - `Lean.Server.locationLinksOfInfo._sparseCasesOn_1, - Lean.mkPrivateNameCore `Lean.Server.InfoUtils - `Lean.Elab.Info.docString?._sparseCasesOn_9, - Lean.mkPrivateNameCore `Init.Prelude - `noConfusion_of_Nat.aux, - Lean.mkPrivateNameCore `Init.Data.Char.Ordinal - `Char.succ?._proof_5, - Lean.mkPrivateNameCore `Lean.Exception - `Lean.throwKernelException._sparseCasesOn_1, - Lean.mkPrivateNameCore `Lean.Compiler.IR.Basic - `Lean.IR.FnBody.isTerminal._sparseCasesOn_1, - - -- 2026-09-01: guard the nested-auxiliary recursor-selection fuel regression. - `Lean.Meta.Grind.Arith.Cutsat.CooperSplitPred.rec, - `Lean.Doc.Block.rec -] - -def expectedPass (_name : Lean.Name) : Bool := true - -/-- Focus-mode helper: typecheck each constant in `names` through the - same Rust FFI pipeline as `testRustCheckEnv`, but restricted to a - small list. Compile + ingress still pays ~20s (full env), but the - check loop is short. Default `names` = `focusConsts`. -/ -private def filterFocusConsts (names : Array Lean.Name) : IO (Array Lean.Name) := do - match (← IO.getEnv "IX_KERNEL_FOCUS_CONST") with - | none => pure names - | some filter => - let filtered := names.filter fun name => name.toString.contains filter - IO.println s!"[check-focus] IX_KERNEL_FOCUS_CONST={filter} matched {filtered.size}/{names.size}" - pure filtered - -def testRustCheckConsts (names : Array Lean.Name := focusConsts) : TestSeq := - .individualIO s!"kernel check {names.size} focus consts" none (do - let leanEnv ← get_env! - let names ← filterFocusConsts names - let fixtures := fixtureNames leanEnv - let allConsts := leanEnv.constants.toList.filter fun (name, _) => - !isFixtureName fixtures name - let expectPass : Array Bool := names.map expectedPass - let start ← IO.monoMsNow - -- Focus batches are intentionally tiny — keep verbose output so each - -- targeted constant prints its elapsed time and depth inline. - let results ← rsCheckConstsFFI allConsts names expectPass false - let elapsed := (← IO.monoMsNow) - start - - let mut passed := 0 - let mut failures : Array (Lean.Name × String) := #[] - -- Rust preserves input order, so `results[i]` lines up with `names[i]`. - -- We still build a `Name → result` map so we can report names in the - -- same order as `focusConsts` and surface any gap (shouldn't happen - -- with order-preserving results, but kept defensively). - let mut resultMap : Std.HashMap Lean.Name (Option CheckError) := - Std.HashMap.emptyWithCapacity results.size - for i in [:names.size] do - resultMap := resultMap.insert names[i]! results[i]! - for name in names do - let shouldPass := expectedPass name - match resultMap.get? name with - | some none => - if shouldPass then - passed := passed + 1 - else - failures := failures.push (name, "unexpected pass") - | some (some err) => - let msg := match err with - | .kernelException m => s!"kernel: {m}" - | .compileError m => s!"compile: {m}" - if shouldPass then - failures := failures.push (name, msg) - else - passed := passed + 1 - | none => - failures := failures.push (name, "not reported by FFI") - - IO.println s!"[check-focus] {passed}/{names.size} passed in {elapsed}ms" - if !failures.isEmpty then - IO.println s!"[check-focus] {failures.size} failure(s):" - for (name, msg) in failures do - IO.println s!" ✗ {name}: {msg}" - - let total := passed + failures.size - if failures.isEmpty then - return (true, passed, total, none) - else - return (false, passed, total, - some s!"Focus check failed with {failures.size} failure(s)") - ) .done - -def suite : List TestSeq := [testRustCheckEnv] -def constSuite : List TestSeq := [testRustCheckConsts] - -end Tests.Ix.Kernel.CheckEnv diff --git a/Tests/Ix/Kernel/CheckLeanCli.lean b/Tests/Ix/Kernel/CheckLeanCli.lean index b81ee9255..2c1fc3ecd 100644 --- a/Tests/Ix/Kernel/CheckLeanCli.lean +++ b/Tests/Ix/Kernel/CheckLeanCli.lean @@ -82,9 +82,7 @@ private def check : IO Nat := do IO.FS.writeBinFile corrupt tampered IO.FS.writeFile empty "# only comments\n" let neutral : Array (String × Option String) := - #["IX_TC_DEBUG_CONST", "IX_TC_STEP_TRACE", "IX_TC_STATS", "IX_TC_NO_ACCEL", "IX_MAX_REC_FUEL", - "IX_KERNEL_CHECK_ACTIVE_SLOW_MS", "IX_PROGRESS_MS"].map (·, none) ++ - #[("IX_KERNEL_CHECK_PROGRESS_MS", some "0"), ("IX_KERNEL_CHECK_SLOW_MS", some "0")] + #[("IX_PROGRESS_MS", none), ("IX_KERNEL_CHECK_PROGRESS_MS", some "0"), ("IX_KERNEL_CHECK_SLOW_MS", some "0")] let calls ← IO.mkRef 0 let call := fun (args : Array String) (vars : Array (String × Option String)) => do calls.modify (· + 1) @@ -123,17 +121,12 @@ private def check : IO Nat := do expect replay 3 checkRow replay (0 : Nat) (1 : Nat) (1 : Nat) for args in [#["--consts", "missing"], #["--consts", ""], #["--consts-file", empty.toString], - #["--max", "0"], #["--workers", "0"], #["--no-verify"], #["--clear-every", "1"]] do + #["--max", "0"], #["--workers", "0"]] do expect (← call args #[]) 2 - for (name, value) in [("IX_TC_DEBUG_CONST", "x"), ("IX_TC_STEP_TRACE", "1"), ("IX_TC_STATS", "1"), - ("IX_TC_NO_ACCEL", "1"), ("IX_MAX_REC_FUEL", "0"), ("IX_KERNEL_CHECK_ACTIVE_SLOW_MS", "1"), - ("IX_TC_STATS", "bad"), ("IX_KERNEL_CHECK_PROGRESS_MS", "bad")] do - let out ← call #["--consts", "CliAlias0"] #[(name, some value)] + for name in ["IX_KERNEL_CHECK_PROGRESS_MS", "IX_KERNEL_CHECK_SLOW_MS"] do + let out ← call #["--consts", "CliAlias0"] #[(name, some "bad")] expect out 2 - ensure (out.stderr.contains name) s!"unsupported setting {name} was silently ignored" - expect (← call #["--consts", "CliAlias0", "--clear-every", "0"] - #[("IX_TC_STEP_TRACE", some "0"), ("IX_TC_STATS", some "0"), ("IX_TC_NO_ACCEL", some "0"), - ("IX_KERNEL_CHECK_ACTIVE_SLOW_MS", some "0")]) 0 + ensure (out.stderr.contains name) s!"malformed setting {name} was silently ignored" for mode in [#[], #["--anon"]] do calls.modify (· + 1) let args := #["check-lean", corrupt.toString] ++ mode @@ -164,8 +157,64 @@ private def check : IO Nat := do runExit { wholeReport with targets := #[] } false == 0 && (coverageNotice { wholeReport with targets := #[] } false).any (·.contains "selected checking only")) "partial-check exit or notice changed" + -- `--json`, `--json-name` and `--per-const`. A whole run writes one results row, + -- keyed by --json-name, and the per-record CSV, and exits as the same run + -- without --json does, the row's status naming the exit: 3 (`rejected`) with a + -- failing subject, `exitPartial` (`partial`) for the clean unbound artifact. + let results := dir / "results.json" + let csv := dir / "perconst.csv" + let row := fun (key : String) => do + let rows ← _root_.Ix.Benchmark.Results.readRows results.toString + return (rows.getObjVal? key).toOption + let status := fun (key : String) => do + return (← row key).bind fun r => (r.getObjValAs? String "status").toOption + let constants := fun (key : String) => do + return (← row key).bind fun r => (r.getObjValAs? Nat "constants").toOption + let reset := fun () => do + if ← results.pathExists then IO.FS.removeFile results + if ← csv.pathExists then IO.FS.removeFile csv + let csvRows := fun () => do + let lines := ((← IO.FS.readFile csv).splitOn "\n").filter (!·.isEmpty) + ensure (lines.head? == some "name,addr,consts,nanos,heartbeats,subst,whnf,def_eq,nat_arith,intern,cost") + s!"per-const CSV header changed: {lines.head?}" + return lines.length - 1 + reset () + let jsonWhole ← call #["--json", results.toString, "--json-name", "fixture", "--per-const", csv.toString] #[] + expect jsonWhole 3 + ensure ((← status "fixture") == some "rejected" && (← row "env").isNone && + (← constants "fixture") == some wholeReport.records) + s!"whole --json row lost its key, status or record count: {← IO.FS.readFile results}" + ensure ((← csvRows ()) == wholeReport.rows.size) "whole --per-const CSV is not one row per record" + reset () + calls.modify (· + 1) + let partialJson ← IO.Process.output { + cmd := ix.toString, args := #["check-lean", cleanPath.toString, "--json", results.toString], env := neutral } + expect partialJson exitPartial + ensure ((← status "env") == some "partial") "unbound whole --json row missing or not partial" + -- One full-closure row per selected name, keyed after `/`, and one + -- CSV over every per-name run; --json-name and --per-const are honoured. + reset () + let names := dir / "names.txt" + IO.FS.writeFile names "CliAlias0\nRejectedSubject\n" + let perName ← call #["--consts-file", names.toString, "--json", results.toString, "--json-name", "fx", + "--per-const", csv.toString] #[] + expect perName 3 + ensure ((← status "fx/CliAlias0") == some "ok" && (← status "fx/RejectedSubject") == some "rejected" && + (← row "CliAlias0").isNone && (← row "fx").isNone && ((← constants "fx/CliAlias0").getD 0) > 0) + s!"per-name rows lost their keys or statuses: {← IO.FS.readFile results}" + ensure ((← csvRows ()) > 0) "per-name --per-const CSV is empty" + ensure (perName.stdout.contains "1/2 subjects passed") s!"per-name summary changed\n{perName.stdout}" + -- An anonymous run resolves Lean names too; a name that resolves to nothing is + -- reported and the others still run, with the usage exit. + reset () + let anonRows ← call #["--anon", "--consts", "CliAlias0,missing", "--json", results.toString] #[] + expect anonRows 2 + ensure ((← status "CliAlias0") == some "ok" && (← row "missing").isNone && + anonRows.stderr.contains "missing: unresolved") + s!"unresolved name aborted the per-name rows\n{anonRows.stderr}" + expect (← call #["--consts", "CliAlias0", "--json", results.toString, "--fail-out", failures.toString] #[]) 2 let count ← calls.get - IO.println s!"check-lean CLI: {count} process controls; modes, exact aliases/cap, all five outcomes, fail-out, usage/infra exits and diagnostic flags passed" + IO.println s!"check-lean CLI: {count} process controls; modes, exact aliases/cap, all five outcomes, fail-out, usage/infra exits, --json/--json-name/--per-const rows passed" return count finally IO.FS.removeDirAll dir diff --git a/Tests/Ix/Kernel/CheckPrimeGaps.lean b/Tests/Ix/Kernel/CheckPrimeGaps.lean index 2836cad96..aba54ff17 100644 --- a/Tests/Ix/Kernel/CheckPrimeGaps.lean +++ b/Tests/Ix/Kernel/CheckPrimeGaps.lean @@ -8,7 +8,6 @@ Run with: `lake test -- kernel-check-primegaps --ignored` -/ -import Ix.KernelCheck import Ix.Meta import Tests.Ix.Kernel.FocusedLeanCheck import Tests.Ix.Kernel.PrimeGapsReduction @@ -16,7 +15,6 @@ import Tests.Ix.Kernel.TutorialMeta import LSpec open LSpec -open Ix.KernelCheck (CheckError rsCheckConstsFFI) namespace Tests.Ix.Kernel.CheckPrimeGaps @@ -34,42 +32,6 @@ private def filterFocusConsts (names : Array Lean.Name) : IO (Array Lean.Name) : {filtered.size}/{names.size}" pure filtered -def testRustCheckPrimeGaps : TestSeq := - .individualIO s!"kernel check {focusConsts.size} inlined PrimeGaps consts" none (do - let names ← filterFocusConsts focusConsts - if names.isEmpty then - return (false, 0, 0, some "IX_KERNEL_FOCUS_CONST matched no PrimeGaps canary") - - let leanEnv ← get_env! - let (_, closedConsts) := - Tests.Ix.Kernel.TutorialMeta.collectDepsWithExtras leanEnv {} names.toList - IO.println s!"[check-primegaps] compiling {closedConsts.length}-constant \ - closure for {names.size} exact subject(s)" - - let expected := names.map fun _ => true - let start ← IO.monoMsNow - let results ← rsCheckConstsFFI closedConsts names expected false - let elapsed := (← IO.monoMsNow) - start - - let mut failures : Array (Lean.Name × String) := #[] - for i in [:names.size] do - if let some err := results[i]! then - let message := match err with - | .kernelException m => s!"kernel: {m}" - | .compileError m => s!"compile: {m}" - failures := failures.push (names[i]!, message) - - IO.println s!"[check-primegaps] {names.size - failures.size}/{names.size} \ - passed in {elapsed}ms" - for (name, message) in failures do - IO.println s!" ✗ {name}: {message}" - - if failures.isEmpty then - return (true, names.size, names.size, none) - return (false, names.size - failures.size, names.size, - some s!"PrimeGaps focus check failed with {failures.size} failure(s)") - ) .done - def testLeanCheckPrimeGaps : TestSeq := .individualIO s!"IxC check {focusConsts.size} inlined PrimeGaps consts" none (do let names ← filterFocusConsts focusConsts @@ -80,6 +42,6 @@ def testLeanCheckPrimeGaps : TestSeq := "primegaps-reduction" leanEnv names ) .done -def suite : List TestSeq := [testRustCheckPrimeGaps, testLeanCheckPrimeGaps] +def suite : List TestSeq := [testLeanCheckPrimeGaps] end Tests.Ix.Kernel.CheckPrimeGaps diff --git a/Tests/Ix/Kernel/CheckTauCeti.lean b/Tests/Ix/Kernel/CheckTauCeti.lean index c2a3a5299..2a0b3db6a 100644 --- a/Tests/Ix/Kernel/CheckTauCeti.lean +++ b/Tests/Ix/Kernel/CheckTauCeti.lean @@ -1,8 +1,12 @@ -/- +import Ix.KernelCheck +import Tests.Ix.Kernel.FocusedLeanCheck +import LSpec + +/-! TauCeti-corpus focus constants for the Rust kernel. The TauCeti analogue of `Tests.Ix.Kernel.CheckEnv.focusConsts`: known - failing / slow constants from full `ix check-rs tauceti.ixe` runs, kept + failing / slow constants from full `ix check-lean tauceti.ixe` runs, kept here for fast reproduction without paying for the ~557k-constant env pass. Unlike `CheckEnv`'s list, these names do not exist in this test binary's own Lean environment (TauCeti builds against Mathlib in the @@ -11,7 +15,7 @@ `tauceti.ixe` with the focus names as exact seeds. That is the same subject-only seeded meta check as - ix check-rs tauceti.ixe --consts + ix check-lean tauceti.ixe --consts (deps lazily ingressed and trusted, only the seeds re-checked), so a single constant can also be cherry-picked straight from the CLI, e.g. @@ -23,8 +27,6 @@ Run with: `lake test -- kernel-check-tauceti --ignored` -/ -import Ix.KernelCheck -import LSpec open LSpec open Ix.KernelCheck (CheckError rsCheckIxonFFI) @@ -34,10 +36,10 @@ namespace Tests.Ix.Kernel.CheckTauCeti /-- Repo-root serialized TauCeti env (see the module docstring). -/ def ixePath : System.FilePath := "tauceti.ixe" -/-- Known failing / slow constants from a full `ix check-rs tauceti.ixe` +/-- Known failing / slow constants from a full `ix check-lean tauceti.ixe` run. Edit when bisecting a regression; grouped by root cause in order of discovery. Timings below are from the 2026-08-22 full-env run - (10M-fuel default, `MAX_REC_FUEL` in `crates/kernel/src/tc.rs`). -/ + (10M-fuel default of the retired Rust kernel). -/ def focusConsts : Array Lean.Name := #[ -- 2026-08-22: recursive fuel exhausted (FAIL at ~800s/838s in the -- full-env sweep, def-eq peak depth 47/50). Not divergence: both pass diff --git a/Tests/Ix/Kernel/CheckTauCetiReduction.lean b/Tests/Ix/Kernel/CheckTauCetiReduction.lean index 3e76acbd9..9ced38820 100644 --- a/Tests/Ix/Kernel/CheckTauCetiReduction.lean +++ b/Tests/Ix/Kernel/CheckTauCetiReduction.lean @@ -8,7 +8,6 @@ Run with: `lake test -- kernel-check-tauceti-reduction --ignored` -/ -import Ix.KernelCheck import Ix.Meta import Tests.Ix.Kernel.FocusedLeanCheck import Tests.Ix.Kernel.TauCetiReduction @@ -16,7 +15,6 @@ import Tests.Ix.Kernel.TutorialMeta import LSpec open LSpec -open Ix.KernelCheck (CheckError rsCheckConstsFFI) namespace Tests.Ix.Kernel.CheckTauCetiReduction @@ -25,38 +23,6 @@ def focusConsts : Array Lean.Name := #[ `Tests.Ix.Kernel.TauCetiReduction.composedSymmApply ] -def testRustCheckTauCetiReduction : TestSeq := - .individualIO s!"kernel check {focusConsts.size} inlined TauCeti reductions" none (do - let leanEnv ← get_env! - let (_, closedConsts) := - Tests.Ix.Kernel.TutorialMeta.collectDepsWithExtras leanEnv {} focusConsts.toList - IO.println s!"[check-tauceti-reduction] compiling {closedConsts.length}-constant \ - closure for {focusConsts.size} subject(s)" - - let expected := focusConsts.map fun _ => true - let start ← IO.monoMsNow - let results ← rsCheckConstsFFI closedConsts focusConsts expected false - let elapsed := (← IO.monoMsNow) - start - - let mut failures : Array (Lean.Name × String) := #[] - for i in [:focusConsts.size] do - if let some err := results[i]! then - let message := match err with - | .kernelException m => s!"kernel: {m}" - | .compileError m => s!"compile: {m}" - failures := failures.push (focusConsts[i]!, message) - - IO.println s!"[check-tauceti-reduction] \ - {focusConsts.size - failures.size}/{focusConsts.size} passed in {elapsed}ms" - for (name, message) in failures do - IO.println s!" ✗ {name}: {message}" - - if failures.isEmpty then - return (true, focusConsts.size, focusConsts.size, none) - return (false, focusConsts.size - failures.size, focusConsts.size, - some s!"TauCeti reduction check failed with {failures.size} failure(s)") - ) .done - def testLeanCheckTauCetiReduction : TestSeq := .individualIO s!"IxC check {focusConsts.size} inlined TauCeti reductions" none (do let leanEnv ← get_env! @@ -67,7 +33,6 @@ def testLeanCheckTauCetiReduction : TestSeq := ) .done def suite : List TestSeq := [ - testRustCheckTauCetiReduction, testLeanCheckTauCetiReduction ] diff --git a/Tests/Ix/Kernel/CodecHost.lean b/Tests/Ix/Kernel/CodecHost.lean deleted file mode 100644 index 3acacfefe..000000000 --- a/Tests/Ix/Kernel/CodecHost.lean +++ /dev/null @@ -1,56 +0,0 @@ -import Tests.Ix.Ixon -import IxC.Ixon.Canonical -import IxC.Ixon.Bounded.Size - -/-! Host-only production codec regressions, including generated values and -Rust serialization comparisons. This runner is outside the certified closure. -/ - -open LSpec SlimCheck Ixon - -def boundedUnivMatchesRust (value : Univ) : Bool := - let bytes := serUniv value - match Bounded.deUniv bytes.size value.nodeCount bytes with - | .error _ => false - | .ok decoded => - decoded == value && Tests.FFI.Ixon.rsEqUnivSerialization decoded bytes && - !(Bounded.deUniv bytes.size (value.nodeCount - 1) bytes).isOk && - !(Bounded.deUniv (bytes.size - 1) value.nodeCount bytes).isOk - -def boundedConstantMatchesRust (value : Constant) : Bool := - let bytes := serConstant value - let nodes := Bounded.univNodes value.univs - match Bounded.deConstant bytes.size nodes bytes with - | .error _ => false - | .ok decoded => - decoded == value && Tests.FFI.Ixon.rsEqConstantSerialization decoded bytes && - decoded.resourceSize + Bounded.univNodes decoded.univs ≤ 2 * bytes.size + nodes && - !(Bounded.deConstant (bytes.size - 1) nodes bytes).isOk && - (nodes == 0 || !(Bounded.deConstant bytes.size (nodes - 1) bytes).isOk) - -def canonicalConstantMatchesRust (value : Constant) : Bool := - let bytes := serConstant value - match Canonical.deConstant bytes.size (Bounded.univNodes value.univs) bytes with - | .error _ => false - | .ok decoded => decoded == value && Tests.FFI.Ixon.rsEqConstantSerialization decoded bytes - -def resourceExprMatchesRust (value : Expr) : Bool := - let payload := serExpr value - let bytes := (⟨#[0xff, 0xee]⟩ : ByteArray) ++ payload ++ ⟨#[0xdd]⟩ - match getExpr { bytes, idx := 2 } with - | .error _ _ => false - | .ok decoded finish => - decoded == value && finish.bytes == bytes && finish.idx == 2 + payload.size && - decoded.resourceSize + 1 ≤ 2 * (finish.idx - 2) && - Tests.FFI.Ixon.rsEqExprSerialization decoded payload - -def main : IO UInt32 := - LSpec.lspecIO (.ofList [ - ("certified-codec-production", Tests.Ixon.suite), - ("bounded-universe", [checkIO "exact limits and Rust serialization" - (∀ value : Univ, boundedUnivMatchesRust value)]), - ("bounded-constant", [checkIO "aggregate limits and Rust serialization" - (∀ value : Constant, boundedConstantMatchesRust value)]), - ("canonical-constant", [checkIO "canonical round trips and Rust serialization" - (∀ value : Constant, canonicalConstantMatchesRust value)]), - ("expression-resources", [checkIO "consumed-byte bounds, nonzero cursor, and Rust serialization" - (∀ value : Expr, resourceExprMatchesRust value)])]) [] diff --git a/Tests/Ix/Kernel/DisplayRead.lean b/Tests/Ix/Kernel/DisplayRead.lean deleted file mode 100644 index 8d72ead57..000000000 --- a/Tests/Ix/Kernel/DisplayRead.lean +++ /dev/null @@ -1,52 +0,0 @@ -import Lean.Elab.Command -import IxC.Kernel.Admission -import IxC.Kernel.Admission.Tier -import Ix.Ixon.Authenticated -import IxC.Kernel.Audit.Runtime -import Ix.Cert.Compile -import Ix.LeanIngress -import Benchmarks.Kernel.CheckIxeMeta - -/-! # The display-only read stays out of every certified closure (step 6b) - -`Ix.KernelDisplay.peekSquash` is the one `unsafe` step 6b adds: it reads a -display annotation (a `Squash`) to print a name, and it is allowlisted in the -trust-surface fence for that reason alone (`Tests/Ix/Kernel/TrustSurface.lean`). -This module checks, on the compiled code (`Ix.Kernel.Audit.runtimeClosure`, -the walk the frozen root audits use), that no certified root reaches it: - -* the verified fold `Ix.Kernel.Cached.checkDecls`, the Ixon reader - `Ix.Kernel.Reader.readRecords` and the certified entries - (`Ix.Kernel.Admission.checkBytes`, `checkConstants` and their `With` forms); -* the typing tier's entries (`checkBytesTiered`, `checkConstantsTiered`, - `Ixon.Authenticated.checkBytesTiered`); -* the compiler `Ix.Cert.compile` and the Source checks - `Ix.LeanIngress.checkSource` and `checkSourceTiered`, whose terms carry - Lean's names. - -The positive control: the erasure run (`kernel-check-ixe --meta`), which -prints failures with names, does reach it. -/ - -open Lean Elab Command - -/-- info: certified roots reaching the display read: [] -/ -#guard_msgs in -run_cmd liftTermElabM do - let env ← getEnv - let roots := #[``Ix.Kernel.Cached.checkDecls, ``Ix.Kernel.Reader.readRecords, - ``Ix.Kernel.Admission.checkBytes, ``Ix.Kernel.Admission.checkBytesWith, - ``Ix.Kernel.Admission.checkConstants, ``Ix.Kernel.Admission.checkConstantsWith, - ``Ix.Kernel.Admission.checkBytesTiered, ``Ix.Kernel.Admission.checkConstantsTiered, - ``Ixon.Authenticated.checkBytesTiered, - ``Ix.Cert.compile, ``Ix.LeanIngress.checkSource, ``Ix.LeanIngress.checkSourceTiered] - let hits := roots.filter fun root => - (Ix.Kernel.Audit.runtimeClosure env #[root]).visited.contains ``Ix.KernelDisplay.peekSquash - logInfo m!"certified roots reaching the display read: {hits}" - -/-- info: the erasure run reaches the display read: true -/ -#guard_msgs in -run_cmd liftTermElabM do - let env ← getEnv - let r := Ix.Kernel.Audit.runtimeClosure env #[``Benchmarks.Kernel.CheckIxeMeta.run] - logInfo m!"the erasure run reaches the display read: \ - {r.visited.contains ``Ix.KernelDisplay.peekSquash}" diff --git a/Tests/Ix/Kernel/EntryCases.lean b/Tests/Ix/Kernel/EntryCases.lean index 4366a8293..6e981885d 100644 --- a/Tests/Ix/Kernel/EntryCases.lean +++ b/Tests/Ix/Kernel/EntryCases.lean @@ -1,5 +1,5 @@ import Ix.Ixon.Authenticated -import Ix.CompileDriver +import Ix.CompileCertified import Ix.Meta import Benchmarks.Kernel.CheckIxeStep import Tests.Ix.Kernel.EntryCaseDefs @@ -9,7 +9,7 @@ import Tests.Ix.Kernel.EntryCaseDefs Each case names Lean declarations of `Tests.Ix.Kernel.EntryCaseDefs`. The harness loads that module's environment, takes the dependency closure of the seeds (with the recursors of every inductive in it, which the reader -requires), compiles it with Ix's compiler (`Ix.CompileM.compileLeanConsts`), +requires), compiles it with Ix's compiler (`Ix.CompileM.compileCertified`), loads the serialized environment with the host codec, orders the primary records as the environment check does (`Benchmarks.Kernel.CheckIxeStep`: dependencies, `Nat`-operation grounds and literal edges first; projections last), and @@ -96,12 +96,13 @@ check order (without the prelude's own records, which the entry supplies), then projections by address. -/ def prepare (leanEnv : Lean.Environment) (seeds : List Lean.Name) : IO Input := do let closed ← IO.ofExcept (closure leanEnv seeds) - let compiled ← match ← Ix.CompileM.compileLeanConsts closed (numWorkers := 1) with + let compiled ← match ← Ix.CompileM.compileCertified leanEnv closed 1 with | .ok out => pure out | .error e => throw (IO.userError s!"compilation failed: {e}") - unless compiled.ungroundedCount == 0 do - throw (IO.userError s!"{compiled.ungroundedCount} constants are ungrounded") - let env ← IO.ofExcept (Ixon.deEnv compiled.bytes) + unless compiled.failures.isEmpty do + throw (IO.userError s!"{compiled.failures.toMap.size} constants are ungrounded") + let bytes ← IO.ofExcept (Ixon.serEnv compiled.env) + let env ← IO.ofExcept (Ixon.deEnv bytes) let mut store : RecordStore := {} for (address, lazy) in env.consts.toList do store := store.insert address (← IO.ofExcept lazy.get) diff --git a/Tests/Ix/Kernel/FastPaths.lean b/Tests/Ix/Kernel/FastPaths.lean index 98c3ff5f8..5f8be888c 100644 --- a/Tests/Ix/Kernel/FastPaths.lean +++ b/Tests/Ix/Kernel/FastPaths.lean @@ -1,7 +1,6 @@ import LSpec import Ix.Meta -import Ix.CompileDriver -import Ix.KernelCheck +import Ix.CompileCertified import Tests.Ix.Kernel.ReaderFidelity import Tests.Ix.Kernel.SourceIngressCompare import Tests.Ix.Kernel.FastPathDefs @@ -9,21 +8,18 @@ import Tests.Ix.Kernel.FastPathDefs /-! # Reductions beyond Lean's, per kernel (`kernel-fast-paths`) Each probe of `Tests.Ix.Kernel.FastPathDefs` is checked with its whole -dependency closure (recursors included) by two kernels: - -* the Rust kernel (`rsCheckConstsFFI`, every closure member by name); -* IxC on the Lean compiler's output, through the environment check - (`SourceIngressCompare.ixonVerdicts`: - the verified fold one record at a time, under the default role table and - its evidence). +dependency closure (recursors included) by IxC on the Lean compiler's +output, through the environment check (`SourceIngressCompare.ixonVerdicts`: +the verified fold one record at a time, under the default role table and +its evidence). A probe expected to be rejected names its culprit: the false or non-definitional equation itself, or, for a derivation of `False`, the -equation it uses. Every kernel must reject the culprit, and nothing else in -the closure may fail except the probe itself when it is a derivation (the -Rust kernel checks it with its dependencies trusted and accepts it; -IxC blocks it behind the culprit). A probe expected to be accepted must -have every closure member accepted. So the test fails both when a kernel +equation it uses. The kernel must reject the culprit, and nothing else in +the closure may fail except the probe itself when it is a derivation +(accepted with its dependencies trusted, or, as IxC does, blocked behind +the culprit; never rejected on its own). A probe expected to be accepted must +have every closure member accepted. So the test fails both when IxC accepts a proof of `False` and when a removed fast path's honest replacement stops reducing the canonical forms. -/ @@ -82,22 +78,8 @@ def Verdict.label : Verdict → String abbrev Verdicts := Std.HashMap Lean.Name Verdict -/-- The Rust kernel: every name checked, its dependencies trusted. -/ -def rustVerdicts (closed : List (Lean.Name × Lean.ConstantInfo)) : IO Verdicts := do - let names := (closed.map (·.1)).toArray - let results ← Ix.KernelCheck.rsCheckConstsFFI closed names - (Array.replicate names.size true) true - unless results.size == names.size do - throw (IO.userError s!"Rust omitted closure verdicts: expected {names.size}, got {results.size}") - let mut out : Verdicts := {} - for i in [:names.size] do - out := out.insert names[i]! <| match results[i]! with - | none => .accept - | some e => .reject e.message - return out - /-- IxC's environment check on the compiled environment, by name. -/ -def ixcVerdicts (compiled : Ix.CompileM.LeanPipelineOut) (names : List Lean.Name) : +def ixcVerdicts (compiled : Ix.CompileM.CertifiedOut) (names : List Lean.Name) : IO Verdicts := do let rows ← Tests.Ix.Kernel.SourceIngressCompare.ixonVerdicts compiled.env let mut out : Verdicts := {} @@ -146,20 +128,17 @@ def run : IO UInt32 := do let names := closed.map (·.1) IO.println s!"[kernel-fast-paths] {probes.length} probes, {closed.length} constants in their closures" let t0 ← IO.monoMsNow - let compiled ← match ← Ix.CompileM.compileLeanConsts closed (numWorkers := 4) with + let compiled ← match ← Ix.CompileM.compileCertified leanEnv closed 4 with | .ok out => pure out | .error e => throw (IO.userError s!"compilation failed: {e}") - unless compiled.ungroundedCount == 0 do - throw (IO.userError s!"{compiled.ungroundedCount} constants are ungrounded") + unless compiled.failures.isEmpty do + throw (IO.userError s!"{compiled.failures.toMap.size} constants are ungrounded") let t1 ← IO.monoMsNow - let rust ← rustVerdicts closed - let t2 ← IO.monoMsNow let ixc ← ixcVerdicts compiled names - let t3 ← IO.monoMsNow - IO.println s!"[kernel-fast-paths] compile {t1 - t0} ms, Rust {t2 - t1} ms, \ - IxC {t3 - t2} ms" + let t2 ← IO.monoMsNow + IO.println s!"[kernel-fast-paths] compile {t1 - t0} ms, IxC {t2 - t1} ms" let mut seq : TestSeq := .done - for (kernel, verdicts) in [("Rust", rust), ("IxC", ixc)] do + for (kernel, verdicts) in [("IxC", ixc)] do for p in probes do seq := seq ++ checkProbe kernel (closures.getD p.name []) verdicts p lspecIO (.ofList [("kernel-fast-paths", [seq])]) [] diff --git a/Tests/Ix/Kernel/FocusedLeanCheck.lean b/Tests/Ix/Kernel/FocusedLeanCheck.lean index 7965d1dd7..418078e9d 100644 --- a/Tests/Ix/Kernel/FocusedLeanCheck.lean +++ b/Tests/Ix/Kernel/FocusedLeanCheck.lean @@ -2,17 +2,81 @@ module public import Ix.Cli.CheckLeanCmd public import Ix.CompileM +public import Ix.CompileCertified +public import Ix.Ixon public import Ix.EnvScope +public import Ix.KernelCheck /-! Shared harness for checking a small source-level closure with the pure-Lean -kernel. The Rust compiler writes one temporary Ixon environment, then the +kernel. The certified compiler writes one temporary Ixon environment, then the same driver used by `ix check-lean --consts` checks their supporting closure and requires exact named coverage of the requested regression subjects. -/ public section +namespace Ix.KernelCheck + +def rsCheckIxonFFI (path : String) (names : Array Lean.Name) + (_expectPass : Array Bool) (quiet : Bool) (_failOut : String) : + IO (Array (Option CheckError)) := do + let bytes ← IO.FS.readBinFile path + let cfg : Ix.Cli.CheckLeanCmd.CheckCfg := { + workers := 1, verbose := !quiet, silent := quiet, progressMs := 0, slowMs := 0 } + let only := names.map toString + match ← Ix.Cli.CheckLeanCmd.runMetaCheck bytes cfg (.only only) with + | .error err => + return Array.replicate names.size (some (.kernelException err.message)) + | .ok report => + let mut byLabel : Std.HashMap String (String × String) := {} + for t in report.targets do + byLabel := byLabel.insert t.target.label (t.outcome, t.reason) + return names.map fun n => + match byLabel[toString n]? with + | some ("accept", _) | some ("typed", _) => none + | some (outcome, reason) => some (.kernelException s!"{outcome}: {reason}") + | none => some (.kernelException s!"missing target verdict for {n}") + +def rsCheckConstsFFI (consts : List (Lean.Name × Lean.ConstantInfo)) + (names : Array Lean.Name) (_expectPass : Array Bool) (quiet : Bool) : + IO (Array (Option CheckError)) := do + let rawEnv := Ix.CompileM.canonInput consts.toArray 32 + let (compiledEnv, failures) ← match Ix.Cert.compileReport rawEnv with + | .ok res => pure res + | .error err => + return Array.replicate names.size (some (.compileError (toString err))) + let failMap := failures.toMap + let bytes ← match Ixon.serEnv compiledEnv with + | .ok b => pure b + | .error err => + return Array.replicate names.size (some (.compileError (toString err))) + let cfg : Ix.Cli.CheckLeanCmd.CheckCfg := { + workers := 1, verbose := !quiet, silent := quiet, progressMs := 0, slowMs := 0 } + let unfailed := names.filter fun n => !failMap.contains (Ix.Name.fromLeanName n) + let only := unfailed.map toString + let mut byLabel : Std.HashMap String (Option CheckError) := {} + if !only.isEmpty then + match ← Ix.Cli.CheckLeanCmd.runMetaCheck bytes cfg (.only only) with + | .error err => + for n in unfailed do + byLabel := byLabel.insert (toString n) (some (.kernelException err.message)) + | .ok report => + for t in report.targets do + let v := match t.outcome with + | "accept" | "typed" => none + | outcome => some (.kernelException s!"{outcome}: {t.reason}") + byLabel := byLabel.insert t.target.label v + return names.map fun n => + if let some reason := failMap[Ix.Name.fromLeanName n]? then + some (.compileError reason) + else + match byLabel[toString n]? with + | some res => res + | none => some (.kernelException s!"missing target verdict for {n}") + +end Ix.KernelCheck + namespace Tests.Ix.Kernel.FocusedLeanCheck def checkClosure (tag fileStem : String) @@ -26,15 +90,19 @@ def checkClosure (tag fileStem : String) let dir ← IO.FS.createTempDir try let path := dir / s!"{fileStem}.ixe" - let status ← Ix.CompileM.rsCompileEnvBytesFFI consts path.toString false - unless status.ungrounded.isEmpty do - let first := match status.ungrounded[0]? with + let outEnv ← match ← Ix.CompileM.compileCertified env consts with + | .ok out => pure out + | .error err => return (false, 0, names.size, some s!"[{tag}] compile failed: {err}") + unless outEnv.failures.isEmpty do + let first := match outEnv.failures.toMap.toList.head? with | some (name, reason) => s!"{name}: {reason}" | none => "" return (false, 0, names.size, some s!"[{tag}] compile failed for \ - {status.ungrounded.size} constant(s); first: {first}") - - let bytes ← IO.FS.readBinFile path + {outEnv.failures.toMap.size} constant(s); first: {first}") + let bytes ← match Ixon.serEnv outEnv.env with + | .ok b => pure b + | .error err => return (false, 0, names.size, some s!"[{tag}] serEnv failed: {err}") + IO.FS.writeBinFile path bytes let cfg : Ix.Cli.CheckLeanCmd.CheckCfg := { workers := 1, verbose := true, progressMs := 0, slowMs := 0, tag := s!"[{tag}]" } let only := names.map toString diff --git a/Tests/Ix/Kernel/LevelComparison.lean b/Tests/Ix/Kernel/LevelComparison.lean deleted file mode 100644 index d472de81b..000000000 --- a/Tests/Ix/Kernel/LevelComparison.lean +++ /dev/null @@ -1,324 +0,0 @@ -import IxC.Kernel.Verify.Level -import IxC.Kernel.Verify.LevelGeran -import Ix.IxonUniv - -/-! # The kernel's level comparison against brute-force evaluation - -`Ix.Kernel.Level.leq` is nanoda's comparison with Géran's sublevels as the -fallback of its `(param, max)` case (`IxC/Kernel/Level.lean`, -`IxC/Kernel/LevelGeran.lean`). `leqCore_sound` proves a `true` verdict -pointwise; `Geran.leq_iff` proves the fallback a decision. This host-only -gate checks that the whole comparison is complete in practice too. For every -pair below it compares `Level.leq`, `Level.isEquiv` and `Level.Geran.leq` -(at offsets `-2‥2`) with evaluation (`Level.eval`) at every valuation of the -parameters in a range: - -* exhaustively: every level of up to five constructors over two parameters - and of up to four over three, at values `0‥M`, where `M` is four above the - pair's largest `succ` nesting; -* on 100,000 random pairs of up to sixteen constructors over three - parameters, with each pair's `max` commuted and an `imax` absorbed (so - that true equalities are frequent), at values `{0, 1, 2, M - 1, M}`; -* on 20,000 random levels of up to twelve constructors over three - parameters, biased towards `imax` by a parameter (the shape of canonical - forms), against the same level rewritten by laws of the semantics - (equal by construction), at the same values; -* on 20,000 such levels through Ixon's canonical form (`Ix/IxonUniv.lean`, - the form the reader hands the checker): a level against its canonical - form, and the canonical form of a `max`, `imax` or `succ` against the - constructor over canonical forms, as type inference builds them - (`RatFunc.liftOn_def` is `max (canon W) 1` against `canon (max 1 W)`). - These pairs are not all equalities: `canonUniv` changes the value of some - levels (the smallest found is `imax (imax (imax u w + 1) u) v`, whose - canonical form is `2` at `u = 0, v = 1, w = 2` where the level is `1`), - counted, not failures; -* on the recorded witnesses: Ixon's canonical levels of `RatFunc.liftOn_def` - and the equality Ix.Tc's subsumption misses. - -Some valuation at values in `{0, 1, M}` is a counterexample whenever one -exists, at offsets up to `2` (the separating valuations of -`Geran.dominated_of_le` work with any `N` above the other side's -`valueBound`), so the oracle is exact. A `true` verdict that some valuation -refutes is unsound; a `false` verdict that no valuation refutes is -incomplete; a `none` is an internal error. Each is a failure. Output is one -summary line; the exit code is nonzero on any failure. -/ - -namespace Tests.Ix.Kernel.LevelComparison - -open _root_.Ix.Kernel (Level LevelParam) - -def param (i : Nat) : Level := .param i - -/-- Every level with exactly `size` constructors over `params` parameters. -/ -partial def levelsOfSize (params : Nat) (size : Nat) : Array Level := - if size = 0 then #[] - else if size = 1 then #[.zero] ++ (List.range params).toArray.map param - else Id.run do - let mut out : Array Level := (levelsOfSize params (size - 1)).map .succ - for i in [1:size - 1] do - let left := levelsOfSize params i - let right := levelsOfSize params (size - 1 - i) - for a in left do - for b in right do - out := out.push (.max a b) |>.push (.imax a b) - return out - -def levelsUpTo (params size : Nat) : Array Level := - (List.range (size + 1)).foldl (fun acc n => acc ++ levelsOfSize params n) #[] - -/-- The largest `succ` nesting. -/ -def offset : Level → Nat - | .zero | .param _ => 0 - | .succ l => offset l + 1 - | .max a b | .imax a b => Max.max (offset a) (offset b) - -/-- Every valuation of `params` parameters with values in `values`. -/ -def valuations (params : Nat) (values : List Nat) : List (LevelParam → Nat) := - let tuples := (List.range params).foldr - (fun _ rest => values.flatMap fun v => rest.map (v :: ·)) [[]] - tuples.map fun vs n => vs.getD n 0 - -/-- `a ≤ b + diff` at every valuation in `vals`. -/ -def bruteLe (vals : List (LevelParam → Nat)) (a b : Level) (diff : Int) : Bool := - vals.all fun φ => (Level.eval φ a : Int) ≤ Level.eval φ b + diff - -partial def levelString : Level → String - | .zero => "0" - | .succ l => s!"{levelString l}+1" - | .max a b => s!"max({levelString a},{levelString b})" - | .imax a b => s!"imax({levelString a},{levelString b})" - | .param i => s!"u{i}" - -structure Counts where - pairs : Nat := 0 - unsound : Nat := 0 - incomplete : Nat := 0 - internal : Nat := 0 - geranWrong : Nat := 0 - equalities : Nat := 0 - /-- canonical forms that differ in value from their level (not a failure - here: `Ix/IxonUniv.lean`'s, see the canonical family) -/ - canonChanged : Nat := 0 - witness : Option String := none - deriving Inhabited - -def Counts.note (c : Counts) (w : String) : Counts := - { c with witness := c.witness <|> some w } - -/-- Record one verdict against the truth. -/ -def Counts.verdict (c : Counts) (what : String) (v : Option Bool) (truth : Bool) : Counts := - match v, truth with - | some true, false => { c with unsound := c.unsound + 1 }.note s!"unsound: {what}" - | some false, true => { c with incomplete := c.incomplete + 1 }.note s!"incomplete: {what}" - | none, _ => { c with internal := c.internal + 1 }.note s!"internal error: {what}" - | _, _ => c - -/-- One pair, against the valuations `vals`: `leq` both ways, `isEquiv`, and -`Geran.leq` at offsets `-2‥2`. -/ -def check (vals : List (LevelParam → Nat)) (c : Counts) (a b : Level) : Counts := Id.run do - let sa := levelString a - let sb := levelString b - let le := bruteLe vals a b 0 - let ge := bruteLe vals b a 0 - let mut c := { c with pairs := c.pairs + 1 } - if le && ge then c := { c with equalities := c.equalities + 1 } - c := c.verdict s!"leq {sa} {sb}" (Level.leq a b) le - c := c.verdict s!"leq {sb} {sa}" (Level.leq b a) ge - c := c.verdict s!"isEquiv {sa} {sb}" (Level.isEquiv a b) (le && ge) - for d in [-2, -1, 0, 1, 2] do - if Level.Geran.leq a b d != bruteLe vals a b d then - c := { c with geranWrong := c.geranWrong + 1 }.note s!"Geran.leq {sa} {sb} {d}" - return c - -/-- Exhaustively, at values `0‥M`. -/ -def exhaustive (params size : Nat) (c : Counts) : Counts := Id.run do - let levels := levelsUpTo params size - let mut c := c - for a in levels do - for b in levels do - let m := Max.max (offset a) (offset b) + 4 - c := check (valuations params (List.range (m + 1))) c a b - return c - -/-- A deterministic pseudo-random level of at most `size` constructors. -/ -partial def randomLevel (params : Nat) (size : Nat) (seed : UInt64) : Level × UInt64 := - let seed := seed * 6364136223846793005 + 1442695040888963407 - let pick := ((seed >>> 33) % 8).toNat - if size ≤ 1 || pick < 2 then - (if pick % 2 = 0 then .zero else param (((seed >>> 40).toNat) % params), seed) - else if pick < 3 then - let (a, seed) := randomLevel params (size - 1) seed - (.succ a, seed) - else - let (a, seed) := randomLevel params (size / 2) seed - let (b, seed) := randomLevel params (size / 2) seed - (if pick < 5 then .max a b else .imax a b, seed) - -/-- Random pairs, each also with its `max` commuted and an `imax` absorbed, -at values `{0, 1, 2, M - 1, M}`. -/ -def random (params size count : Nat) (c : Counts) : Counts := Id.run do - let mut c := c - let mut seed : UInt64 := 17 - for _ in [0:count] do - let (a, s1) := randomLevel params size seed - let (b, s2) := randomLevel params size s1 - seed := s2 - let m := Max.max (offset a) (offset b) + 4 - let vals := valuations params [0, 1, 2, m - 1, m] - for (x, y) in [(a, b), (Level.max a b, Level.max b a), (Level.max a (.imax a b), Level.max a b)] do - c := check vals c x y - return c - -def next (seed : UInt64) : UInt64 := seed * 6364136223846793005 + 1442695040888963407 - -/-- A pseudo-random level biased towards the shapes of canonical forms: -`imax` by a parameter, offsets and `max`. -/ -partial def biasedLevel (params size : Nat) (seed : UInt64) : Level × UInt64 := - let seed := next seed - let pick := ((seed >>> 33) % 10).toNat - let p := param (((seed >>> 40).toNat) % params) - if size ≤ 1 || pick < 2 then (if pick = 0 then .zero else p, seed) - else if pick < 4 then - let (a, seed) := biasedLevel params (size - 1) seed - (.succ a, seed) - else if pick < 6 then - let (a, seed) := biasedLevel params (size / 2) seed - let (b, seed) := biasedLevel params (size / 2) seed - (.max a b, seed) - else if pick < 9 then - let (a, seed) := biasedLevel params (size - 1) seed - (.imax a p, seed) - else - let (a, seed) := biasedLevel params (size / 2) seed - let (b, seed) := biasedLevel params (size / 2) seed - (.imax a b, seed) - -/-- Rewrite by laws of the semantics at pseudo-random positions: -`succ` over `max`, `max` commuted, `imax a b ≤ max a b`, `imax` over a `max` -or an `imax` on its right, `imax a (succ c) = max a (succ c)`, -`b ≤ imax a b`, and `imax (max a b) b = imax a b`. -/ -partial def rewrite (l : Level) (seed : UInt64) : Level × UInt64 := - let seed := next seed - let pick := ((seed >>> 33) % 8).toNat - match l with - | .succ a => - let (a, seed) := rewrite a seed - match a with - | .max x y => if pick < 3 then (.max (.succ x) (.succ y), seed) else (.succ a, seed) - | a => (.succ a, seed) - | .max a b => - let (a, seed) := rewrite a seed - let (b, seed) := rewrite b seed - match pick with - | 0 | 1 => (.max b a, seed) - | 2 => (.max (.max a b) (.imax a b), seed) - | _ => (.max a b, seed) - | .imax a b => - let (a, seed) := rewrite a seed - let (b, seed) := rewrite b seed - match b, pick with - | .max x y, 0 | .max x y, 1 => (.max (.imax a x) (.imax a y), seed) - | .imax x y, 0 | .imax x y, 1 => (.max (.imax a y) (.imax x y), seed) - | .succ _, 0 | .succ _, 1 => (.max a b, seed) - | _, 2 => (.max (.imax a b) b, seed) - | _, 3 => (.imax (.max a b) b, seed) - | _, _ => (.imax a b, seed) - | l => (l, seed) - -/-- Biased random levels against two rounds of rewriting (an equality by -construction), and against a further round, at values -`{0, 1, 2, M - 1, M}`. -/ -def rewrites (params size count : Nat) (c : Counts) : Counts := Id.run do - let mut c := c - let mut seed : UInt64 := 29 - for _ in [0:count] do - let (a, s) := biasedLevel params size seed - let (b, s) := rewrite a s - let (b, s) := rewrite b s - let (b', s) := rewrite b s - seed := s - let m := Max.max (Max.max (offset a) (offset b)) (offset b') + 4 - let vals := valuations params [0, 1, 2, m - 1, m] - c := check vals (check vals c a b) b' a - return c - -/-- Ixon's universe of a level over `param`s (parameter `i` is `var i`), and back, -as the reader converts it (`Ix.Kernel.Reader.convUniv`). -/ -def toUniv : Level → Ixon.Univ - | .zero => .zero - | .succ l => .succ (toUniv l) - | .max a b => .max (toUniv a) (toUniv b) - | .imax a b => .imax (toUniv a) (toUniv b) - | .param i => .var i.toUInt64 - -def ofUniv : Ixon.Univ → Level - | .zero => .zero - | .succ u => .succ (ofUniv u) - | .max a b => .max (ofUniv a) (ofUniv b) - | .imax a b => .imax (ofUniv a) (ofUniv b) - | .var i => param i.toNat - -/-- Ixon's canonical form of a level (`Ix/IxonUniv.lean`), the form the -reader hands the checker. -/ -def canon (l : Level) : Level := ofUniv (Ixon.canonUniv (toUniv l)) - -/-- Canonical forms against levels built from them, as the checker meets -them: a canonical level against itself, and the canonical form of a -`max`, `imax` or `succ` against the same constructor over canonical forms -(`RatFunc.liftOn_def` is `max (canon W) 1` against `canon (max 1 W)`), on -biased random levels, at values `{0, 1, 2, M - 1, M}`. -/ -def canonical (params size count : Nat) (c : Counts) : Counts := Id.run do - let mut c := c - let mut seed : UInt64 := 41 - for _ in [0:count] do - let (a, s) := biasedLevel params size seed - let (b, s) := biasedLevel params size s - seed := s - let one := Level.succ .zero - let pairs := [(a, canon a), (Level.max (canon a) one, canon (.max one a)), - (Level.max (canon a) (canon b), canon (.max a b)), - (Level.imax (canon a) (canon b), canon (.imax a b)), - (Level.succ (canon a), canon (.succ a))] - let m := pairs.foldl (fun m (x, y) => Max.max m (Max.max (offset x) (offset y))) 0 + 4 - let vals := valuations params [0, 1, 2, m - 1, m] - unless bruteLe vals a (canon a) 0 && bruteLe vals (canon a) a 0 do - c := { c with canonChanged := c.canonChanged + 1 } - for (x, y) in pairs do c := check vals c x y - return c - -def succN (l : Level) : Nat → Level - | 0 => l - | k + 1 => .succ (succN l k) - -/-- The recorded witnesses. -/ -def witnesses (c : Counts) : Counts := - let u := param 0 - let v := param 1 - -- Ixon's canonical levels in `RatFunc.liftOn_def` - let subtypeLevel := Level.max (.imax (.max (succN u 2) (succN v 1)) v) (succN .zero 1) - let eqLevel := Level.max (succN v 1) (.imax (succN u 2) v) - -- equal; Ix.Tc's `univEq` misses it - let tcX := Level.max (succN v 1) (.imax (.imax (succN .zero 2) u) v) - let tcY := Level.max (succN v 1) (.imax u v) - let vals := valuations 2 (List.range 8) - check vals (check vals c subtypeLevel eqLevel) tcX tcY - -def main : IO UInt32 := do - let c := witnesses {} - let wOk := c.equalities == 2 && c.unsound + c.incomplete + c.internal + c.geranWrong == 0 - let c := exhaustive 3 4 (exhaustive 2 5 c) - let c := random 3 16 100000 c - let c := rewrites 3 12 20000 c - let c := canonical 3 10 20000 c - IO.println s!"Level comparison against evaluation: {c.pairs} pairs, {c.equalities} \ - equalities; unsound {c.unsound}; incomplete {c.incomplete}; internal errors \ - {c.internal}; Geran.leq disagreements {c.geranWrong}; canonical forms that change \ - their level's value {c.canonChanged}" - if let some w := c.witness then IO.eprintln s!"first failure: {w}" - unless wOk do IO.eprintln "a recorded witness is not decided as an equality" - let failed := c.unsound + c.incomplete + c.internal + c.geranWrong - return if failed == 0 && wOk then 0 else 1 - -end Tests.Ix.Kernel.LevelComparison - -def main : IO UInt32 := Tests.Ix.Kernel.LevelComparison.main diff --git a/Tests/Ix/Kernel/MetaNames.lean b/Tests/Ix/Kernel/MetaNames.lean index 009619e79..831d12873 100644 --- a/Tests/Ix/Kernel/MetaNames.lean +++ b/Tests/Ix/Kernel/MetaNames.lean @@ -1,5 +1,6 @@ import Ix.Meta import Ix.CompileM +import Ix.CompileCertified import Tests.Ix.Kernel.SourceIngressStep import Benchmarks.Kernel.CheckIxeMeta import Ix.KernelDisplay.MetaNames @@ -296,10 +297,11 @@ unsafe def main (_args : List String) : IO UInt32 := do -- `.meta`: compiled with the metadata, read, named, checked let dir ← IO.FS.createTempDir let path := dir / "meta-names.ixe" - let status ← Ix.CompileM.rsCompileEnvBytesFFI consts.toList path.toString true - unless status.ungrounded.isEmpty do - IO.println s!"kernel-meta-names: {status.ungrounded.size} constants not compiled: \ - {status.ungrounded.toList.take 5}" + let (ixe0, failures) ← IO.ofExcept (Ix.Cert.compileReport (Ix.CompileM.canonInput consts 32)) + IO.FS.writeBinFile path (← IO.ofExcept (Ixon.serEnv ixe0)) + unless failures.isEmpty do + IO.println s!"kernel-meta-names: {failures.toMap.size} constants not compiled: \ + {failures.toMap.toList.take 5}" let metaSeen ← metaSide path wanted let sourceSeen ← sourceSide consts leanEnv wanted let mut failures := 0 diff --git a/Tests/Ix/Kernel/ParityEnv.lean b/Tests/Ix/Kernel/ParityEnv.lean index 25961b957..356828235 100644 --- a/Tests/Ix/Kernel/ParityEnv.lean +++ b/Tests/Ix/Kernel/ParityEnv.lean @@ -1,11 +1,12 @@ module -- Private imports: `ensureSelected` returns only `IO FilePath`; these provide --- only its implementation. `Ix.Meta` re-exports `getFileEnv` and `Ix.CompileM` --- (`rsCompileEnvBytesFFI`). The transitive dep closure comes from the shared +-- only its implementation. `Ix.Meta` re-exports `getFileEnv` and `Ix.CompileCertified` +-- provides `compileCertified`. The transitive dep closure comes from the shared -- `Ix.EnvScope.collectDeps` (namespaced, so it can meet the same-named -- top-level def in `Tests.Ix.Compile.ValidateAux` inside `Tests.Main`). import Ix.Meta +import Ix.CompileCertified import Ix.EnvScope import Ix.Cli.ConstsFile @@ -60,13 +61,14 @@ public def ensureSelected : IO System.FilePath := do else throw <| IO.userError s!"tc-parity: seed constant not found in Ix.lean env: {s}" let closed := Ix.EnvScope.collectSelectedDeps leanEnv seeds - -- Fail-closed (`allowPartial := false`): with `getFileEnv` always - -- full-content, every referenced constant — `_private.*` proof auxiliaries - -- included — must ground; a silent drop here is exactly how the - -- exported-level trim regression hid until the tc gates fired. - let status ← Ix.CompileM.rsCompileEnvBytesFFI closed out.toString false - if !status.ungrounded.isEmpty then - throw (IO.userError s!"tc-pins: selected fixture compilation refused {status.ungrounded.size} constants") + -- Fail-closed: with `getFileEnv` always full-content, every referenced + -- constant — `_private.*` proof auxiliaries included — must ground; a silent + -- drop here is exactly how the exported-level trim regression hid until the + -- tc gates fired. + let outEnv ← IO.ofExcept (← Ix.CompileM.compileCertified leanEnv closed) + if !outEnv.failures.isEmpty then + throw (IO.userError s!"tc-pins: selected fixture compilation refused {outEnv.failures.toMap.size} constants") + IO.FS.writeBinFile out (← IO.ofExcept (Ixon.serEnv outEnv.env)) return out end Tests.Tc.ParityEnv diff --git a/Tests/Ix/Kernel/PreludeFidelity.lean b/Tests/Ix/Kernel/PreludeFidelity.lean index de57cd0a7..6c38dbc36 100644 --- a/Tests/Ix/Kernel/PreludeFidelity.lean +++ b/Tests/Ix/Kernel/PreludeFidelity.lean @@ -1,8 +1,12 @@ import LSpec -import Ix.CompileResource +import Ix.CompileCertified import Ix.EnvScope import Tests.Ix.Kernel.ReaderRoundtrip +/-! + Prelude fidelity regression suite (`kernel-prelude-fidelity`). +-/ + namespace Tests.Ix.Kernel.PreludeFidelity open LSpec Tests.Ix.Kernel.ReaderFidelity Tests.Ix.Kernel.ReaderRoundtrip @@ -24,10 +28,11 @@ private def check : IO Nat := do -- adding the roundtrip fixture's explicit prelude seeds. let seeds := lean.constants.toList.filterMap fun (n, _) => if defsModule.isPrefixOf n then some n else none - let out ← IO.ofExcept ((← _root_.Ix.CompileM.compileLeanConstsValidated - (_root_.Ix.EnvScope.collectSelectedDeps lean seeds) (numWorkers := 1)).mapError toString) - ensure (out.ungroundedCount == 0) "selected closure has compile failures" - let parts ← IO.ofExcept (Ixon.deEnvVerifiedLazy out.bytes) + let out ← IO.ofExcept (← _root_.Ix.CompileM.compileCertified lean + (_root_.Ix.EnvScope.collectSelectedDeps lean seeds) 1) + ensure out.failures.isEmpty "selected closure has compile failures" + let outBytes ← IO.ofExcept (Ixon.serEnv out.env) + let parts ← IO.ofExcept (Ixon.deEnvVerifiedLazy outBytes) let access ← IO.ofExcept (InputAccess.ofParts lean parts) let injected := pre.records.filter fun (a, _) => !access.store.contains a ensure (!injected.isEmpty) "selected closure exercises no setup-only prelude records" diff --git a/Tests/Ix/Kernel/PrimAddrs.lean b/Tests/Ix/Kernel/PrimAddrs.lean index ac29b491c..5b7dba834 100644 --- a/Tests/Ix/Kernel/PrimAddrs.lean +++ b/Tests/Ix/Kernel/PrimAddrs.lean @@ -1,32 +1,23 @@ /- - Guards the kernel's hardcoded primitive addresses against the - canonical table. - - The Aiur kernel hardcodes ~77 primitive constant addresses as 32-byte - literals (`fn nat_add_addr() -> Addr { store([0xd6u8, ...]) }`) so - reduction can recognize `Nat.add` etc. by address alone. Those bytes - are the kernel's entire notion of "this Const IS the primitive whose - semantics I am about to substitute", so a single wrong byte is a - soundness-relevant defect: the kernel either fails to recognize a - primitive (incompleteness) or — worse — recognizes the WRONG constant - and applies native semantics to it. - - Such a defect is invisible to the FFT-pinned suite and to the arena - fixtures: a corrupted literal simply never matches, so well-typed - targets still check and ill-typed ones still fail. One shipped for - months (`bool_false_addr`, last 8 bytes wrong) and only surfaced when - a new reduction path finally tried to emit `Bool.false`. - - This test closes that class. It reads the byte arrays out of the - ELABORATED toplevel — not the source text — and compares each against - the address computed at test runtime from `Ix.PrimAddrs`, which is - the same table the host-side closure walker and ingress verifier use. - Any drift between the two sources of truth fails here. - - Coverage is by construction: every `fn () -> Addr` in the - toplevel whose body is a 32-byte literal must resolve to a canonical - address, and the test reports the count so a silent drop to zero - (e.g. an AST shape change) cannot pass unnoticed. + Guards the IxVM's address literals. + + The IxVM kernel once hardcoded ~77 primitive constant addresses as + 32-byte literals (`fn nat_add_addr() -> Addr { store([0xd6u8, ...]) }`) + so reduction could recognize `Nat.add` etc. by address alone. A single + wrong byte was a soundness-relevant defect, invisible to the FFT-pinned + suite and the arena fixtures; one shipped for months (`bool_false_addr`, + last 8 bytes wrong). Every constant a native rule recognizes or builds + is now a role, read through the claim's role binding and validated + against its specification, so the only literal left is the + specification digest. + + This test keeps it that way. It reads the byte arrays out of the + ELABORATED toplevel — not the source text — and requires the + specification digest, equal to `Ix.SpecDigest.specDigest`, to be the + only one: an address literal reappearing fails, and so does the + extractor finding nothing (e.g. an AST shape change). A literal that + did reappear must also resolve to the canonical address its name + claims, computed at test runtime from `Ix.PrimAddrs`. -/ import Ix.Meta import Ix.Aiur.Compiler diff --git a/Tests/Ix/Kernel/ReadCache.lean b/Tests/Ix/Kernel/ReadCache.lean deleted file mode 100644 index ca4e9e29c..000000000 --- a/Tests/Ix/Kernel/ReadCache.lean +++ /dev/null @@ -1,79 +0,0 @@ -import LSpec -import Benchmarks.Kernel.CheckIxeReadCache -import Tests.Ix.Kernel.ReaderRoundtrip - -/-! # The environment check's persistent read cache (`lake test`) - -`Benchmarks.Kernel.CheckIxeReadCache` on the compiled fixture closure of -`Tests.Ix.Kernel.ReaderRoundtrip`: an environment-check run over the closure of a few -fixture declarations (nested and mutual blocks, the modeller's records, a -nested structure) records its readings; the plan is written as a compacted -region, mapped back, and the environment-check run from the plan gives the same rows -(address, kind, names, outcome, reason) for every record. A plan written -under another version is not used. -/ - -namespace Tests.Ix.Kernel.ReadCache - -open LSpec -open Ix.Kernel.Reader -open Benchmarks.Kernel.CheckIxeStep -open Tests.Ix.Kernel.ReaderFidelity - -def rowKey (r : Row) : String := - s!"{r.address} {r.kind} {r.names} {r.outcome} {r.reason}" - -def check : IO (Nat × Array String) := do - let (_, inp) ← Tests.Ix.Kernel.ReaderRoundtrip.input - let table ← IO.ofExcept defaultTable - let pre ← IO.ofExcept (preludeFor table) - let hints := Hints.ofStore inp.store inp.ixon.anonHints - let s : Setup := setup inp.store (inp.ixon.blobs[·]?) table pre hints.lookup - let names := reportNames inp.ixon inp.store - let roots := Tests.Ix.Kernel.ReaderRoundtrip.nestedSeeds.filterMap fun n => - (inp.ixon.named[_root_.Ix.Name.fromLeanName n]?).map (·.addr) - let ordered := closure s.store s.extra (pre.records.map (fun (p : Address × Ixon.Constant) => p.1) ++ roots) - -- a live run, recording its readings - let liveRows ← IO.mkRef (#[] : Array Row) - let readings ← IO.mkRef (#[] : Array (Address × Except ReadError (Read .anon))) - let _ ← checkLoop s (names.getD · #[]) ordered {} (emit := fun r => liveRows.modify (·.push r)) - (onRead := fun a r _ => readings.modify (·.push (a, r))) - -- the plan, written and mapped back - let dir ← IO.FS.createTempDir - let mut errors : Array String := #[] - try - let bytes := ByteArray.mk #[1, 2, 3] - let (path, ixe) := Benchmarks.Kernel.CheckIxeReadCache.planPath dir bytes - let plan := Benchmarks.Kernel.CheckIxeReadCache.ofRun s ixe "fixture" names (← readings.get) - Benchmarks.Kernel.CheckIxeReadCache.save path plan - let some mapped ← Benchmarks.Kernel.CheckIxeReadCache.load path ixe - | throw (IO.userError "the written plan does not load") - unless mapped.records.size == (← readings.get).size do - errors := errors.push s!"{mapped.records.size} plan records for {(← readings.get).size} readings" - let plannedRows ← IO.mkRef (#[] : Array Row) - let _ ← Benchmarks.Kernel.CheckIxeReadCache.checkLoopPlan mapped none {} - (emit := fun r => plannedRows.modify (·.push r)) - let live := (← liveRows.get).map rowKey - let planned := (← plannedRows.get).map rowKey - unless live == planned do - let first := (live.zip planned).find? (fun (a, b) => a != b) - errors := errors.push s!"{live.size} live rows, {planned.size} planned rows; first difference {first}" - unless (← liveRows.get).any (·.outcome == "accept") do errors := errors.push "no accepted row" - -- a plan under another version or key is not used - let other := dir / "other.reads" - Benchmarks.Kernel.CheckIxeReadCache.save other { plan with version := "another-reader" } - if (← Benchmarks.Kernel.CheckIxeReadCache.load other ixe).isSome then - errors := errors.push "a plan of another version was used" - if (← Benchmarks.Kernel.CheckIxeReadCache.load path "another-ixe").isSome then - errors := errors.push "a plan of another .ixe was used" - finally - IO.FS.removeDirAll dir - return ((← liveRows.get).size, errors) - -def suite : List TestSeq := [ - .individualIO "read cache: an environment check from the mapped plan gives the live rows" none (do - let (rows, errors) ← check - IO.println s!"read cache: {rows} rows" - let msg := if errors.isEmpty then none else some ("\n".intercalate errors.toList) - return (errors.isEmpty, rows, 0, msg)) .done ] - -end Tests.Ix.Kernel.ReadCache diff --git a/Tests/Ix/Kernel/ReaderFidelityMain.lean b/Tests/Ix/Kernel/ReaderFidelityMain.lean index e82c90ea8..d0c8211ae 100644 --- a/Tests/Ix/Kernel/ReaderFidelityMain.lean +++ b/Tests/Ix/Kernel/ReaderFidelityMain.lean @@ -1,4 +1,5 @@ import Ix.CompileDriver +import Ix.CompileCertified import Ix.Meta import Tests.Ix.Kernel.ReaderFidelity import Tests.Ix.Kernel.ReaderRoundtrip @@ -17,11 +18,10 @@ from this toolchain's `.olean` files. exactly `Init` and `Std`); `limit` bounds the records read, in the environment check order. * `kernel-reader-fidelity --compile [limit]`: compiles `Init` and `Std` in - process with Ix's Rust compiler (`Ix.CompileM.rsCompileEnvBytes`, the - compiler behind `ix compile`) and compares that. -* `kernel-reader-fidelity --check-kernel`: what `lake run check-kernel` runs, - the compiled environment if it is present and an in-process compile otherwise, - at `checkKernelLimit` records. + process with Ix's certified compiler (`Ix.CompileM.compileCertified`) and compares that. +* `kernel-reader-fidelity --check-kernel`: checks the compiled environment if + it is present and an in-process compile otherwise, at `checkKernelLimit` + records. * `kernel-reader-fidelity --fixture`: the check of the fixture closure that the `lake test` suite `kernel-reader-roundtrip` runs (`Tests.Ix.Kernel.ReaderRoundtrip.evaluate`: expected verdicts and tampers). * `kernel-reader-fidelity --egress `: the output side only, with no @@ -47,15 +47,10 @@ def checkKernelLimit : Nat := 20000 def initStdEnv : System.FilePath := ".lake/envs/initstd.ixe" -/-- `Init` and `Std` compiled in process by Ix's Rust compiler. -/ +/-- `Init` and `Std` compiled in process by Ix's certified compiler. -/ def compileInitStd (leanEnv : Lean.Environment) : IO Ixon.Env := do - let dir ← IO.FS.createTempDir - try - let path := dir / "initstd.ixe" - let _ ← Ix.CompileM.rsCompileEnvBytes leanEnv path.toString - IO.ofExcept (Ixon.deEnv (← IO.FS.readBinFile path)) - finally - IO.FS.removeDirAll dir + let out ← IO.ofExcept (← Ix.CompileM.compileCertified leanEnv leanEnv.constants.toList) + return out.env def report (r : Report) : IO UInt32 := do IO.println r.summary diff --git a/Tests/Ix/Kernel/ReaderRoundtrip.lean b/Tests/Ix/Kernel/ReaderRoundtrip.lean index 2560951ec..58be0dd1f 100644 --- a/Tests/Ix/Kernel/ReaderRoundtrip.lean +++ b/Tests/Ix/Kernel/ReaderRoundtrip.lean @@ -1,5 +1,5 @@ import LSpec -import Ix.CompileDriver +import Ix.CompileCertified import Ix.Meta import Tests.Ix.Kernel.ReaderFidelity import Tests.Ix.Kernel.EgressFidelity @@ -44,8 +44,8 @@ def defsModule : Lean.Name := `Tests.Ix.Kernel.ReaderFidelityDefs def defs (n : String) : Lean.Name := defsModule ++ n.toName -/-- The compiled closure of the fixture module's own declarations. -/ -def input : IO (Lean.Environment × Input) := do +/-- The compiled closure of the fixture module's own declarations, compiled. -/ +def compileInput : IO (Lean.Environment × Input) := do let leanEnv ← getCompileEnv #[defsModule] let some idx := leanEnv.getModuleIdx? defsModule | throw (IO.userError "fixture module not loaded") let seeds := leanEnv.constants.toList.toArray.filterMap fun (n, _) => @@ -54,14 +54,27 @@ def input : IO (Lean.Environment × Input) := do let prelude := #[`Eq, `Nat, `PUnit, `Empty, `False, `Quot, `Quot.mk, `Quot.lift, `Quot.ind, `Quot.sound, `And, `Bool] let closed ← IO.ofExcept (closure leanEnv (seeds ++ prelude)) - let compiled ← match ← Ix.CompileM.compileLeanConsts closed (numWorkers := 1) with + let compiled ← match ← Ix.CompileM.compileCertified leanEnv closed 1 with | .ok out => pure out | .error e => throw (IO.userError s!"compilation failed: {e}") - unless compiled.ungroundedCount == 0 do - throw (IO.userError s!"{compiled.ungroundedCount} constants are ungrounded") - let ixon ← IO.ofExcept (Ixon.deEnv compiled.bytes) + unless compiled.failures.isEmpty do + throw (IO.userError s!"{compiled.failures.toMap.size} constants are ungrounded") + let bytes ← IO.ofExcept (Ixon.serEnv compiled.env) + let ixon ← IO.ofExcept (Ixon.deEnv bytes) return (leanEnv, ← Input.ofEnv leanEnv ixon) +initialize inputCache : IO.Ref (Option (Lean.Environment × Input)) ← IO.mkRef none + +/-- `compileInput`, once per process: the suites that read this closure +(`kernel-reader-roundtrip`, `kernel-fidelity-access`, `kernel-record-check`, +`kernel-check-lean-targets`, `admission-hash-diff`, `kernel-check-lean-cli`) +share one compile. The values are immutable, so sharing them is safe. -/ +def input : IO (Lean.Environment × Input) := do + if let some cached ← inputCache.get then return cached + let computed ← compileInput + inputCache.set (some computed) + return computed + /-- The verdict each fixture declaration must get. -/ def expected : List (Lean.Name × Verdict) := [ -- inductives: members, constructors, recursors diff --git a/Tests/Ix/Kernel/RecordCheck.lean b/Tests/Ix/Kernel/RecordCheck.lean index 3e0eed3ce..11934df1a 100644 --- a/Tests/Ix/Kernel/RecordCheck.lean +++ b/Tests/Ix/Kernel/RecordCheck.lean @@ -1,6 +1,7 @@ import LSpec import Ix.KernelCheck.Run import Ix.KernelCheck.Meta +import Ix.RoleBinding.Generate import Tests.Ix.Kernel.ReaderRoundtrip /-! The normal record API returns verdicts, not mode-erasure agreement. @@ -117,6 +118,12 @@ private def outcomes (key : Nat → Key md) : IO Nat := do let _ ← runRecords src [] names addresses { workers := some 0 } catch e => refusedZero := e.toString == "record check: the pool needs a worker" ensure refusedZero "zero workers did not give the precise configuration error" + -- `kernel-check-ixe`'s hand-rolled row line is `Row.json`'s compressed + -- rendering, byte for byte (key order, escapes, numbers) + let escaped : Row := ⟨addr 0, #["a\"b", "c\\d\ne", "é\u0001"], "kind\t", "decline", + "reason: \"x\"\r\n", 12, 3⟩ + for row in (baseline.rows ++ tiered.rows).push escaped do + ensure (row.jsonLine == row.json.compress) s!"Row.jsonLine is not Row.json: {row.jsonLine}" return baseline.rows.size + tiered.rows.size private def compiled : IO Nat := do @@ -163,9 +170,144 @@ private def check : IO Nat := do IO.println s!"record checks: {a} anon + {m} meta fixture outcomes, {c} compiled records; serial/1/3 workers" return a + m + c +/-! ## The host loader's record-store refusals + +`Ix.RoleBinding.recordStore` (and `checkedStore`, which role generation and +emission reuse) authenticates and decodes every record of an artifact, named +or not. Its parallel form is proved equal to the serial one +(`recordStore_eq_parallel`, `RecordDecoding.parallel_eq`); no theorem says +what it refuses. These cases pin it: an authenticated record that does not +decode, or decodes only non-canonically (a trailing byte), refuses the whole +store even when no name reaches it (an orphan), with the exact error of the +first such record in address order; a record over a per-record bound is +refused the same way; the batch limits come before authentication, and +authentication before decoding; and a materialized cache beside the bytes +cannot rescue them. -/ + +namespace StoreRefusals + +open _root_.Ix.Kernel.Admission (Limits ByteError) + +private def need (label : String) (ok : Bool) : Except String Unit := + if ok then .ok () else .error label + +private def storeLimits : Limits := ⟨2048, 8, 1 <<< 26, 1 <<< 20, 1 <<< 16⟩ + +private def fixtureConstant (index : Nat) : Ixon.Constant := { + sharing := #[], refs := #[], univs := #[.zero, .succ .zero] + info := .defn { + kind := .defn, safety := .safe, lvls := index.toUInt64, typ := .sort 1, value := .sort 0 } } + +private def rawRecord (bytes : ByteArray) : Address × ByteArray := + (Address.admissionHash bytes, bytes) + +private def canonicalRecords (count : Nat) : Array (Address × ByteArray) := + (Array.range count).map fun i => rawRecord (Ixon.serConstant (fixtureConstant i)) + +/-- An environment of these records and no names: every record is an orphan. -/ +private def orphanEnv (records : Array (Address × ByteArray)) : Ixon.Env := + { consts := records.foldl (fun out (a, bytes) => out.insert a + { buf := bytes, len := bytes.size }) {} } + +/-- `recordStore` and `checkedStore` both refuse `env` with exactly `e`, as +`byteError` renders it. -/ +private def refuses (label : String) (limits : Limits) (env : Ixon.Env) (e : ByteError) : + Except String Unit := do + let expected := _root_.Ix.RoleBinding.byteError e + match _root_.Ix.RoleBinding.recordStore limits env with + | .ok _ => throw s!"{label}: the store was built; expected the refusal {expected}" + | .error actual => + need s!"{label}: refused with {actual}; expected {expected}" (actual == expected) + match _root_.Ix.RoleBinding.checkedStore limits env with + | .ok _ => throw s!"{label}: checkedStore built the store" + | .error actual => need s!"{label}: checkedStore refused with {actual}" (actual == expected) + +/-- The decoder's message for `bytes` under `limits`; an error if they decode. -/ +private def decodeReason (limits : Limits) (bytes : ByteArray) : Except String String := + match Ixon.Canonical.deConstant limits.maxRecordBytes limits.maxRecordUnivNodes bytes with + | .ok _ => .error "a refusal fixture unexpectedly decodes" + | .error reason => .ok reason + +/-- The position of `address` in the store's address order. -/ +private def addressPosition (env : Ixon.Env) (address : Address) : Nat := + let sorted := (env.consts.toArray.map (·.1)).qsort fun x y => x.cmpBytes y == .lt + (sorted.findIdx? (· == address)).getD sorted.size + +/-- The refusals, over `count` canonical records; the number of cases. -/ +def checks (count : Nat) : Except String Nat := do + let input := canonicalRecords count + let canonical := orphanEnv input + let .ok store := _root_.Ix.RoleBinding.recordStore storeLimits canonical + | throw "the canonical store is refused" + need "every canonical record is in the store" (store.size == count) + let malformed := rawRecord (ByteArray.mk #[255]) + let noncanonical := rawRecord ((Ixon.serConstant (fixtureConstant (count + 1))).push 0) + let mut cases := 0 + -- an authenticated orphan that does not decode, or decodes only with a trailing byte + for (label, orphan) in [("malformed", malformed), ("noncanonical", noncanonical)] do + let env := orphanEnv (input.push orphan) + need "orphan controls have no named roots" env.named.isEmpty + refuses s!"an authenticated {label} orphan" storeLimits env + (.decode (addressPosition env orphan.1) orphan.1 (← decodeReason storeLimits orphan.2)) + cases := cases + 1 + -- both: the first in address order is the one reported + let both := orphanEnv ((input.push malformed).push noncanonical) + let first := if malformed.1.cmpBytes noncanonical.1 == .lt then malformed else noncanonical + refuses "two authenticated orphans: the first in address order" storeLimits both + (.decode (addressPosition both first.1) first.1 (← decodeReason storeLimits first.2)) + -- a record over a per-record bound + let oversized := rawRecord (ByteArray.mk (Array.replicate 65 0)) + let manyUniverses := + rawRecord (Ixon.serConstant { fixtureConstant (count + 2) with univs := Array.replicate 64 .zero }) + for (label, tight, excessive) in [ + ("bytes", { storeLimits with maxRecordBytes := 64 }, oversized), + ("universe nodes", { storeLimits with maxRecordUnivNodes := 8 }, manyUniverses)] do + need s!"the canonical records fit the tighter {label} bound" + (_root_.Ix.RoleBinding.recordStore tight canonical).isOk + let env := orphanEnv (input.push excessive) + refuses s!"an orphan over the per-record {label} bound" tight env + (.decode (addressPosition env excessive.1) excessive.1 (← decodeReason tight excessive.2)) + cases := cases + 1 + -- phase precedence: the batch limits, then authentication (records, then blobs), then decoding + let bad := orphanEnv #[malformed] + let wrong := Address.admissionHash (ByteArray.mk #[1, 2, 3]) + need "the mismatch fixture has distinct addresses" (wrong != malformed.1) + let unauthenticated := orphanEnv #[(wrong, malformed.2)] + let badBlob := { bad with blobs := ({} : Std.HashMap Address ByteArray).insert wrong malformed.2 } + refuses "the record count precedes authentication and decoding" + { storeLimits with maxRecords := 0 } unauthenticated (.limit .records) + refuses "the blob count precedes decoding" { storeLimits with maxBlobs := 0 } badBlob (.limit .blobs) + refuses "the payload total precedes authentication and decoding" + { storeLimits with maxTotalBytes := 0 } unauthenticated (.limit .totalBytes) + refuses "record authentication precedes decoding" storeLimits unauthenticated + (.mismatch .records 0 wrong) + refuses "blob authentication precedes record decoding" storeLimits badBlob (.mismatch .blobs 0 wrong) + refuses "record authentication precedes blob authentication" storeLimits + { unauthenticated with blobs := badBlob.blobs } (.mismatch .records 0 wrong) + -- a materialized cache beside malformed bytes is not read + let reason ← decodeReason storeLimits malformed.2 + refuses "a decode error keeps its address and position" storeLimits bad (.decode 0 malformed.1 reason) + let cachedRecord : Ixon.LazyConstant := + { buf := malformed.2, len := malformed.2.size, cache := some (fixtureConstant 0) } + refuses "a valid materialized cache cannot rescue malformed bytes" storeLimits + { bad with consts := bad.consts.insert malformed.1 cachedRecord } (.decode 0 malformed.1 reason) + return cases + 9 + +/-- `pure x`, opaque to the compiler, so that `checks` of it is not a closed term +evaluated when the module initializes. -/ +@[noinline] def atRunTime (α : Type) (x : α) : IO α := pure x + +end StoreRefusals + def suite : List TestSeq := [ .individualIO "actual anon/meta outcomes, rollback, tiers and worker replay" none (do let n ← check - return (true, n, 0, none)) .done] + return (true, n, 0, none)) .done, + -- 769 records: at 256 tasks the parallel decoder's chunks are four records long + .individualIO "record store: orphan, noncanonical and oversized records, phase precedence, cache" + none (do + return match StoreRefusals.checks (← StoreRefusals.atRunTime Nat 769) with + | .ok n => (true, n, 0, none) + | .error message => (false, 0, 0, some message)) .done] end Tests.Ix.Kernel.RecordCheck diff --git a/Tests/Ix/Kernel/Roundtrip.lean b/Tests/Ix/Kernel/Roundtrip.lean deleted file mode 100644 index 0cc71b478..000000000 --- a/Tests/Ix/Kernel/Roundtrip.lean +++ /dev/null @@ -1,49 +0,0 @@ -/- - Kernel ixon roundtrip test. - - Exercises - `Lean env → compile → ixon_ingress → kenv → ixon_egress → decompile → Lean` - on the full current environment and compares each constant (by content - hash) against the original, through `ixon_egress + decompile_env` (the - validated decompile path) rather than a second ad-hoc `KEnv → Lean` - decompiler. - - If `kernel-ixon-roundtrip` passes but `kernel-tutorial` fails, the bug - is in the check side. --/ -import Ix.Common -import Ix.Meta -import LSpec --- Level-spelling fixtures (canonicity §10.6): mk*-reducible spellings --- must survive the kernel leg via the Rust spelling decorations — --- imported so `get_env!` (this file's module closure) carries them. -import Tests.Ix.Compile.LevelSpellings - -open LSpec - -namespace Tests.Ix.Kernel.Roundtrip - -/-- FFI: run the kernel roundtrip and collect per-constant diff messages. - Empty array = roundtrip agrees with the original Lean env. - - Implemented in `src/ffi/kernel.rs::rs_kernel_roundtrip`. -/ -@[extern "rs_kernel_roundtrip"] -opaque rsKernelRoundtripFFI : - @& List (Lean.Name × Lean.ConstantInfo) → IO (Array String) - -def testRoundtrip : TestSeq := - .individualIO "kernel ixon roundtrip" none (do - let leanEnv ← get_env! - let errors ← rsKernelRoundtripFFI leanEnv.constants.toList - if errors.isEmpty then - return (true, 0, 0, none) - else - IO.println s!"[kernel-ixon-roundtrip] {errors.size} errors:" - for msg in errors[:min 20 errors.size] do - IO.println s!" {msg}" - return (false, 0, 0, some s!"{errors.size} roundtrip mismatches") - ) .done - -def suite : List TestSeq := [testRoundtrip] - -end Tests.Ix.Kernel.Roundtrip diff --git a/Tests/Ix/Kernel/RoundtripNoCompile.lean b/Tests/Ix/Kernel/RoundtripNoCompile.lean deleted file mode 100644 index 1be21135a..000000000 --- a/Tests/Ix/Kernel/RoundtripNoCompile.lean +++ /dev/null @@ -1,46 +0,0 @@ -/- - Kernel lean roundtrip test (skips compile). - - Exercises `Lean env → lean_ingress → KEnv → lean_egress → Lean env` - on the full current environment and compares each constant (by content - hash) against the original. Unlike `kernel-ixon-roundtrip`, this path - skips `compile_env` and `ixon_ingress` entirely, so it isolates - direct-from-Lean kernel ingress from any compile/Ixon bugs. - - Used as a bisecting diagnostic: if this test is clean but - `kernel-ixon-roundtrip` has errors, the bug lives in the compile - pipeline. If both tests fail with - the same errors, the bug is in the ingress/egress pipeline itself. --/ -import Ix.Common -import Ix.Meta -import LSpec - -open LSpec - -namespace Tests.Ix.Kernel.RoundtripNoCompile - -/-- FFI: run the no-compile kernel roundtrip and collect per-constant diff - messages. Empty array = roundtrip agrees with the original Lean env. - - Implemented in `src/ffi/kernel.rs::rs_kernel_roundtrip_no_compile`. -/ -@[extern "rs_kernel_roundtrip_no_compile"] -opaque rsKernelRoundtripNoCompileFFI : - @& List (Lean.Name × Lean.ConstantInfo) → IO (Array String) - -def testRoundtripNoCompile : TestSeq := - .individualIO "kernel lean roundtrip" none (do - let leanEnv ← get_env! - let errors ← rsKernelRoundtripNoCompileFFI leanEnv.constants.toList - if errors.isEmpty then - return (true, 0, 0, none) - else - IO.println s!"[kernel-lean-roundtrip] {errors.size} errors:" - for msg in errors[:min 20 errors.size] do - IO.println s!" {msg}" - return (false, 0, 0, some s!"{errors.size} roundtrip mismatches") - ) .done - -def suite : List TestSeq := [testRoundtripNoCompile] - -end Tests.Ix.Kernel.RoundtripNoCompile diff --git a/Tests/Ix/Kernel/SourceIngressCompare.lean b/Tests/Ix/Kernel/SourceIngressCompare.lean index 64aa7511c..7c7e72a15 100644 --- a/Tests/Ix/Kernel/SourceIngressCompare.lean +++ b/Tests/Ix/Kernel/SourceIngressCompare.lean @@ -1,4 +1,5 @@ import Ix.CompileM +import Benchmarks.Kernel.CheckIxePool import Tests.Ix.Kernel.SourceIngressStep /-! # Source-mode verdicts against the Ixon path's (untrusted harness) @@ -20,22 +21,35 @@ open Benchmarks.Kernel.CheckIxeStep /-- The verdicts of the Ixon environment check of `ixon`, by Lean name (the checker's spelling of it). -/ -def ixonVerdicts (ixon : Ixon.Env) : +def ixonVerdicts (ixon : Ixon.Env) (workers : Nat := 1) : IO (Std.HashMap (CName .source) (String × String)) := do - let mut store : RecordStore := {} - for (address, lazy) in ixon.consts.toList do - store := store.insert address (← IO.ofExcept lazy.get) + let constsArr := ixon.consts.toList.toArray + let decoded := if workers ≤ 1 then + constsArr.map fun (address, lazy) => (address, lazy.get) + else + Ix.Compile.Parallel.map 64 (fun (address, lazy) => (address, lazy.get)) constsArr + let mut store : RecordStore := Std.HashMap.emptyWithCapacity constsArr.size + for (address, res) in decoded do + store := store.insert address (← IO.ofExcept res) let table ← IO.ofExcept defaultTable let pre ← IO.ofExcept (preludeFor table) let hints := Hints.ofStore store ixon.anonHints let s := setup store (ixon.blobs[·]?) table pre hints.lookup - let rows ← IO.mkRef (Std.HashMap.emptyWithCapacity (α := Address) (β := String × String) 0) + let rows ← IO.mkRef (Std.HashMap.emptyWithCapacity (α := Address) (β := String × String) s.ordered.size) let names := reportNames ixon store let total := s.ordered.size - let _ ← checkLoop s (names.getD · #[]) s.ordered {} (tier := true) - (fun row => rows.modify (·.insert row.address (row.outcome, row.reason))) - (progress := fun i _ => do - if i % 10000 == 0 then IO.eprintln s!"source-ingress: ixon check {i}/{total}") + let emit := fun (row : Row) => rows.modify (·.insert row.address (row.outcome, row.reason)) + if workers > 1 then + let slots ← (Array.range workers).mapM fun _ => IO.mkRef (none : Option (Address × Nat)) + let _ ← Benchmarks.Kernel.CheckIxePool.runOutcome s.source s.evidence (names.getD · #[]) + s.ordered {} slots emit (log := fun _ => pure ()) + (progress := fun i _ => do + if i % 10000 == 0 then IO.eprintln s!"source-ingress: ixon check {i}/{total}") + (tier := s.source.tierHook) + else + let _ ← checkLoop s (names.getD · #[]) s.ordered {} (tier := true) emit + (progress := fun i _ => do + if i % 10000 == 0 then IO.eprintln s!"source-ingress: ixon check {i}/{total}") let rows ← rows.get let mut out : Std.HashMap (CName .source) (String × String) := {} for (name, named) in ixon.named.toList do diff --git a/Tests/Ix/Kernel/SourceIngressMain.lean b/Tests/Ix/Kernel/SourceIngressMain.lean index 6d09026c8..9d35c0437 100644 --- a/Tests/Ix/Kernel/SourceIngressMain.lean +++ b/Tests/Ix/Kernel/SourceIngressMain.lean @@ -1,4 +1,5 @@ import Ix.Meta +import Ix.CompileCertified import Tests.Ix.Kernel.SourceIngress import Tests.Ix.Kernel.SourceIngressStep import Tests.Ix.Kernel.SourceIngressCompare @@ -101,7 +102,8 @@ def main (args : List String) : IO UInt32 := do let some path := a.ixe | throw (IO.userError "--fiber-hints and --collapse need --ixe") unless ← path.pathExists do let t ← IO.monoMsNow - let _ ← Ix.CompileM.rsCompileEnvBytes leanEnv path.toString + let out ← IO.ofExcept (← Ix.CompileM.compileCertified leanEnv leanEnv.constants.toList a.workers) + IO.FS.writeBinFile path (← IO.ofExcept (Ixon.serEnv out.env)) IO.println s!"compiled {path} in {(← IO.monoMsNow) - t} ms" pure (some (← IO.ofExcept (Ixon.deEnv (← IO.FS.readBinFile path)))) else pure none @@ -236,11 +238,14 @@ def main (args : List String) : IO UInt32 := do if let some path := a.ixe then unless ← path.pathExists do let t ← IO.monoMsNow - let _ ← Ix.CompileM.rsCompileEnvBytes leanEnv path.toString + let out ← IO.ofExcept (← Ix.CompileM.compileCertified leanEnv leanEnv.constants.toList a.workers) + IO.FS.writeBinFile path (← IO.ofExcept (Ixon.serEnv out.env)) IO.println s!"compiled {path} in {(← IO.monoMsNow) - t} ms" let t ← IO.monoMsNow - let ixon ← IO.ofExcept (Ixon.deEnv (← IO.FS.readBinFile path)) - let verdicts ← Tests.Ix.Kernel.SourceIngressCompare.ixonVerdicts ixon + let ixon ← match artifact with + | some ixon => pure ixon + | none => IO.ofExcept (Ixon.deEnv (← IO.FS.readBinFile path)) + let verdicts ← Tests.Ix.Kernel.SourceIngressCompare.ixonVerdicts ixon a.workers let cmp := Tests.Ix.Kernel.SourceIngressCompare.compare r.rows verdicts IO.println s!"ixon check (default role table) in {(← IO.monoMsNow) - t} ms: {verdicts.size} \ constants with a verdict, {cmp.unmatched} source constants without one" diff --git a/Tests/Ix/Kernel/SourceIngressStep.lean b/Tests/Ix/Kernel/SourceIngressStep.lean index 255ea6a6d..a10cc3866 100644 --- a/Tests/Ix/Kernel/SourceIngressStep.lean +++ b/Tests/Ix/Kernel/SourceIngressStep.lean @@ -1,6 +1,7 @@ import Ix.CanonM import Ix.GraphM import Ix.CondenseM +import Ix.Compile.FrontEnd import Ix.LeanIngress import Benchmarks.Kernel.CheckIxeStep @@ -58,7 +59,7 @@ def canon (consts : Array (Lean.Name × Lean.ConstantInfo)) (workers : Nat := 1) /-- The condensation of an environment's reference graph, as the compiler's driver computes it (`Ix.GraphM`, `Ix.CondenseM`). -/ def condense (env : Ix.Environment) (parallel : Bool := false) : Ix.CondensedBlocks := - Ix.CondenseM.run (if parallel then Ix.GraphM.envParallel env else Ix.GraphM.env env) + Ix.CondenseM.run (if parallel then Ix.Compile.FrontEnd.graph env else Ix.GraphM.envSerial env) /-- `groups.map convert` on `workers` workers. -/ def convertAll (groups : Array Group) (workers : Nat) : Array (Except ReadError Converted) := @@ -141,7 +142,12 @@ def run (pre : Prelude .source) (env : Ix.Environment) -- canonical levels, after the hint override, before lowering -- (`Ix.LeanIngress.graphCollapsed`'s `Converted.quotient`) let g : Graph := match collapse with - | some ρ => { g with converted := g.converted.map fun c => c.map (Converted.quotient ρ) } + | some ρ => + let qConv := if workers ≤ 1 then + g.converted.map fun c => c.map (Converted.quotient ρ) + else + Ix.Compile.Parallel.map 256 (fun c => c.map (Converted.quotient ρ)) g.converted + { g with converted := qConv } | none => g -- the tier groups (`isTierGroup`): the tier's conversion; their slots pass -- the logical conversion, so that the schedule's verdict on them is the @@ -253,9 +259,6 @@ def run (pre : Prelude .source) (env : Ix.Environment) aliases := aliasOf.size, partialDrops, multiDrops } let (hoistMoved, hoistGuarded) := Ix.Kernel.Frontend.prepareReport pre.ix lowered IO.println s!"source-ingress: hoist would move {hoistMoved} records; guard fired: {hoistGuarded}" - match Ix.Kernel.checkUnits prepared with - | .ok () => IO.println "source-ingress: units check passed on the prepared stream" - | .error (e, i) => IO.println s!"source-ingress: units check FAILED at {i}: {e}" let (ev, evErrors) ← match evidence with | some ev => pure (ev, []) | none => IO.lazyPure fun _ => Ix.LeanIngress.sourceEvidenceReport prepared @@ -267,44 +270,140 @@ def run (pre : Prelude .source) (env : Ix.Environment) if rows[i]!.isSome then failed := failed.insert i i let mut started : Std.HashSet Nat := {} let mut micros : Array Nat := Array.replicate groups.size 0 - let mut count := 0 - for d in prepared do - count := count + 1 - if progress && count % 5000 == 0 then - IO.eprintln s!"source-ingress: checked {count}/{prepared.size} declarations" - let gi? := d.names.head?.bind (owner[·]?) - if let some gi := gi? then - if failed.contains gi then continue - unless started.contains gi do - started := started.insert gi - let resolve (j : Nat) : Nat := aliasOf.getD j j - if let some blocker := (g.hard[gi]! ++ dropDeps.getD gi #[]).find? - (fun j => failed.contains (resolve j)) then - let root := failed.getD (resolve blocker) (resolve blocker) - failed := failed.insert gi root - rows := rows.set! gi (some ⟨gi, names[gi]!, kindOf groups[gi]!, "blocked", - s!"{(names[root]!)[0]?.getD (.id .anonymous)}", 0⟩) - continue - -- the fence: a logical group that references a tier group - if let some t := g.hard[gi]!.find? tierSet.contains then + let mut usedParallel := false + if workers > 1 then + let mut feP : Ix.Kernel.FEnv .source := Ix.Kernel.mkFEnv Ix.Kernel.Env.empty + let mut csP : Ix.Kernel.Cached.CState .source := {} + let mut posP : Nat := 0 + let mut pendP : Array (Ix.Kernel.Cached.PendingCheck .source) := #[] + let mut pendOwners : Array (Option Nat) := #[] + let mut failedP := failed + let mut startedP := started + let mut rowsP := rows + let mut microsP := micros + let mut countP := 0 + for d in prepared do + countP := countP + 1 + if progress && countP % 5000 == 0 then + IO.eprintln s!"source-ingress: installed {countP}/{prepared.size} declarations" + let gi? := d.names.head?.bind (owner[·]?) + if let some gi := gi? then + if failedP.contains gi then continue + unless startedP.contains gi do + startedP := startedP.insert gi + let resolve (j : Nat) : Nat := aliasOf.getD j j + if let some blocker := (g.hard[gi]! ++ dropDeps.getD gi #[]).find? + (fun j => failedP.contains (resolve j)) then + let root := failedP.getD (resolve blocker) (resolve blocker) + failedP := failedP.insert gi root + rowsP := rowsP.set! gi (some ⟨gi, names[gi]!, kindOf groups[gi]!, "blocked", + s!"{(names[root]!)[0]?.getD (.id .anonymous)}", 0⟩) + continue + if let some t := g.hard[gi]!.find? tierSet.contains then + failedP := failedP.insert gi gi + rowsP := rowsP.set! gi (some ⟨gi, names[gi]!, kindOf groups[gi]!, "decline", + s!"fence: references the tier group {(names[t]!)[0]?.getD (.id .anonymous)}", 0⟩) + continue + let s0 ← IO.monoNanosNow + let before := feP.env + let oldPend := pendP.size + let stepRes ← IO.lazyPure fun _ => + Ix.Kernel.Cached.annotDeclStep .verified ev (posP, feP, pendP) d csP + let us := ((← IO.monoNanosNow) - s0) / 1000 + match stepRes with + | .ok ((pos', fe', pend'), cs') => + posP := pos' + feP := fe' + pendP := pend' + csP := cs' + pendOwners := pendOwners ++ Array.replicate (pend'.size - oldPend) gi? + if let some gi := gi? then + microsP := microsP.set! gi (microsP[gi]! + us) + | .error (e, _) => + posP := posP + 1 + feP := Ix.Kernel.mkFEnv before + csP := {} + match gi? with + | some gi => + microsP := microsP.set! gi (microsP[gi]! + us) + let (o, why) := Benchmarks.Kernel.CheckIxeStep.checkOutcome e + failedP := failedP.insert gi gi + rowsP := rowsP.set! gi (some ⟨gi, names[gi]!, kindOf groups[gi]!, o, why, microsP[gi]!⟩) + | none => + IO.eprintln s!"source-ingress: prelude declaration {d.names.head?.getD (.id .anonymous)} failed: {e}" + let nextRef ← IO.mkRef 0 + let pendSize := pendP.size + let rec workerLoop (fuel : Nat) (acc : Array (Nat × Option (Ix.Kernel.CheckError .source) × Nat)) : + IO (Array (Nat × Option (Ix.Kernel.CheckError .source) × Nat)) := do + match fuel with + | 0 => pure acc + | fuel + 1 => + let k ← nextRef.modifyGet fun a => (a, a + 1) + if k < pendSize then + let t0 ← IO.monoNanosNow + let r ← IO.lazyPure fun _ => match pendP[k]? with + | some pc => match Ix.Kernel.Cached.checkPending .verified feP pc {} with + | .ok _ => none + | .error e => some e + | none => some (.internal s!"check phase: no recorded check {k}") + let us := ((← IO.monoNanosNow) - t0) / 1000 + workerLoop fuel (acc.push (k, r, us)) + else pure acc + let mut tasks := #[] + for _ in [0:workers] do + tasks := tasks.push (← IO.asTask (prio := .dedicated) (workerLoop (pendSize + 1) #[])) + let mut phaseBFail := false + for t in tasks do + for (k, r, us) in ← IO.ofExcept (← IO.wait t) do + if r.isSome then phaseBFail := true + if let some (some gi) := pendOwners[k]? then + microsP := microsP.set! gi (microsP[gi]! + us) + unless phaseBFail do + usedParallel := true + checker := { fe := feP, cs := {}, pos := posP } + failed := failedP + started := startedP + rows := rowsP + micros := microsP + unless usedParallel do + let mut count := 0 + for d in prepared do + count := count + 1 + if progress && count % 5000 == 0 then + IO.eprintln s!"source-ingress: checked {count}/{prepared.size} declarations" + let gi? := d.names.head?.bind (owner[·]?) + if let some gi := gi? then + if failed.contains gi then continue + unless started.contains gi do + started := started.insert gi + let resolve (j : Nat) : Nat := aliasOf.getD j j + if let some blocker := (g.hard[gi]! ++ dropDeps.getD gi #[]).find? + (fun j => failed.contains (resolve j)) then + let root := failed.getD (resolve blocker) (resolve blocker) + failed := failed.insert gi root + rows := rows.set! gi (some ⟨gi, names[gi]!, kindOf groups[gi]!, "blocked", + s!"{(names[root]!)[0]?.getD (.id .anonymous)}", 0⟩) + continue + -- the fence: a logical group that references a tier group + if let some t := g.hard[gi]!.find? tierSet.contains then + failed := failed.insert gi gi + rows := rows.set! gi (some ⟨gi, names[gi]!, kindOf groups[gi]!, "decline", + s!"fence: references the tier group {(names[t]!)[0]?.getD (.id .anonymous)}", 0⟩) + continue + let s0 ← IO.monoNanosNow + let (c', err) ← IO.lazyPure fun _ => checker.step ev d + let us := ((← IO.monoNanosNow) - s0) / 1000 + checker := c' + match gi? with + | some gi => + micros := micros.set! gi (micros[gi]! + us) + if let some e := err then + let (o, why) := Benchmarks.Kernel.CheckIxeStep.checkOutcome e failed := failed.insert gi gi - rows := rows.set! gi (some ⟨gi, names[gi]!, kindOf groups[gi]!, "decline", - s!"fence: references the tier group {(names[t]!)[0]?.getD (.id .anonymous)}", 0⟩) - continue - let s0 ← IO.monoNanosNow - let (c', err) ← IO.lazyPure fun _ => checker.step ev d - let us := ((← IO.monoNanosNow) - s0) / 1000 - checker := c' - match gi? with - | some gi => - micros := micros.set! gi (micros[gi]! + us) - if let some e := err then - let (o, why) := Benchmarks.Kernel.CheckIxeStep.checkOutcome e - failed := failed.insert gi gi - rows := rows.set! gi (some ⟨gi, names[gi]!, kindOf groups[gi]!, o, why, micros[gi]!⟩) - | none => - if let some e := err then - IO.eprintln s!"source-ingress: prelude declaration {d.names.head?.getD (.id .anonymous)} failed: {e}" + rows := rows.set! gi (some ⟨gi, names[gi]!, kindOf groups[gi]!, o, why, micros[gi]!⟩) + | none => + if let some e := err then + IO.eprintln s!"source-ingress: prelude declaration {d.names.head?.getD (.id .anonymous)} failed: {e}" -- the typing tier, at the index of the checked environment (also the -- logical view of the unsafe opaques), staged as the tier fold stages it, -- in schedule order within a stage; a tier group behind a failed group is diff --git a/Tests/Ix/Kernel/TrustSurface.lean b/Tests/Ix/Kernel/TrustSurface.lean index 1938eedfc..543475dd5 100644 --- a/Tests/Ix/Kernel/TrustSurface.lean +++ b/Tests/Ix/Kernel/TrustSurface.lean @@ -96,10 +96,10 @@ list fails just as loudly as one in a bare file. `peekSquash` reads one (`unsafeCast`, a `Squash` is its value at run time) to print it, and `peekAll` hands it to the safe renderer (`Ix.KernelDisplay.Render`). No certified root's compiled closure - reaches it (`Tests.Ix.Kernel.DisplayRead`), and the `.meta` and - `.source` verdicts are `.anon`'s (the erasure test). The file is - scanned with the display modules (`displaySources`), which hold no - other escape. + reaches it (`IxC/Kernel/Audit/Roots.lean`, `Ix/Cert/RuntimeAudit.lean`), + and the `.meta` and `.source` verdicts are `.anon`'s (the erasure test). + The file is scanned with the display modules (`displaySources`), which + hold no other escape. Not policed, as upstream: `partial`, `@[csimp]`, `withPtrEq`/`withPtrAddr`, `opaque`, `noncomputable`. The Lean runtime audit polices the compiled diff --git a/Tests/Ix/Kernel/Tutorial.lean b/Tests/Ix/Kernel/Tutorial.lean index f2bfe4e2a..e20ec6379 100644 --- a/Tests/Ix/Kernel/Tutorial.lean +++ b/Tests/Ix/Kernel/Tutorial.lean @@ -3,10 +3,21 @@ Reads test cases registered by TutorialDefs.lean via the env extension, then checks each through the full pipeline: Lean env → Ixon → kernel. Good constants must pass; bad constants must be rejected. + + The tutorial was written for the Rust kernel. The certified checker (IxC) + gives four kinds of fixture another verdict, each with the check that + stands in for the tutorial's (`nativeEvaluationFixtures`, + `axiomFixtures`, `cornerCases`, `sourceOnlyBad`), as + `Tests.Ix.Kernel.Arena` does for the IxVM kernel. -/ import Ix.Common import Ix.Meta import Ix.KernelCheck +import Ix.RoleBinding.Compile +import Ix.Resource.Typing +import Tests.Ix.Kernel.FocusedLeanCheck +import Tests.Ix.Kernel.SourceIngressCompare +import Tests.Ix.Kernel.SourceIngressStep import Tests.Ix.Kernel.TutorialMeta import Tests.Ix.Kernel.TutorialDefs import Tests.Ix.Kernel.NatReduction @@ -17,23 +28,141 @@ open LSpec namespace Tests.Ix.Kernel.Tutorial --- Re-export the shared `CheckError` type so existing call sites --- (e.g. `Tests/Ix/Kernel/CheckEnv.lean`) keep working unchanged. +-- Re-export the shared `CheckError` type for the suites that use it. -- The single source of truth lives in `Ix/KernelCheck.lean`. export Ix.KernelCheck (CheckError) --- Re-export the shared FFI binding so existing call sites keep working --- without an explicit `Ix.KernelCheck.` qualifier. The single --- `@[extern "rs_kernel_check_consts"]` declaration lives in --- `Ix/KernelCheck.lean` so that `lake exe ix check` (production CLI) --- and the test runners share the same Lean-side opaque. +-- Re-export the shared check helper so existing call sites keep working +-- without an explicit `Ix.KernelCheck.` qualifier. export Ix.KernelCheck (rsCheckConstsFFI) -@[extern "rs_kernel_check_malformed_rec_rule_ixon"] -opaque rsCheckMalformedRecRuleIxonFFI : - @& List (Lean.Name × Lean.ConstantInfo) → - @& Lean.Name → - IO (Option CheckError) +private def defs : Lean.Name := `Tests.Ix.Kernel.TutorialDefs + +/-- The `native_decide` fixtures (`Arena.nativeEvaluationFixtures` for the +IxVM kernel). Their proof is an auxiliary axiom that asserts the compiled +evaluation's result, a non-standard axiom IxC's strict policy declines, so +they must not pass. The kernel's own reduction of `Nat.ble`/`Nat.beq` on +literals is checked by `NatReduction`'s `natBle*`/`natBeq*` declarations. -/ +def nativeEvaluationFixtures : Array Lean.Name := #[ + `natBleTrue, `natBleFalse, `natBeqTrue, `natBeqFalse] + +/-- The tutorial's axioms. -/ +def tutorialAxioms : Array Lean.Name := + #[defs ++ `aDepProp, defs ++ `mkADepProp, defs ++ `aType, defs ++ `aProp] + +/-- Good fixtures that use the tutorial's axioms. IxC's strict policy +declines a non-standard axiom, so the strict check blocks them; with user +axioms admitted (`ix check-lean --allow-user-axioms` on the bound artifact, +`checkWithUserAxioms`) they must be accepted, conditional on tutorial +axioms only. -/ +def axiomFixtures : Array Lean.Name := #[`letTypeDep, `letRed] + +/-- Corner cases: fixtures registered with the good cases (the IxVM arena run, +which follows the Rust kernel, accepts them) whose right verdict is not +settled, pinned to IxC's verdict with its reason. + +`nestedSpecLocalF`'s field `P n F` is a nested occurrence whose parametric +argument `n` is the constructor's own field, so no auxiliary type can be +formed from it, and `P` stores nothing, so the occurrence is phantom. Lean's +elaborator rejects the declaration, and the Rust kernel skipped the occurrence +and accepted it. lean-kernel-arena's nearest test, +`corner-cases/nested-nonuniform-param` (a nested occurrence that supplies +something other than the parameter, phantom too), has outcome `either`. Its +relative `bugs/nested-unused-param`, whose parametric argument was never +type-checked, is a proof of `False` (lean4#14576, fixed in lean4#14577). + +IxC declines it as outside its model, which is neither an acceptance nor a +rejection. An IxC change that accepts or rejects it is allowed by the +`either` outcome, and updates the pinned reason. -/ +def cornerCases : Array (Lean.Name × String) := + let reason := "decline: direct rec: a nested occurrence of the block (not modeled here)" + #[(`nestedSpecLocalF, reason), (`nestedSpecLocalF.mk, reason), (`nestedSpecLocalF.rec, reason)] + +/-- Bad fixtures whose badness is the duplicate names of their level +parameters (`Arena.knownIncompatible` for the IxVM kernel). The anonymous +compile keeps only their count and resolves a name to its first occurrence, +so the selected check, whose verdict is the anonymous run's, cannot see +them. Source mode reads the names, and its acceptance gives distinct level +parameters (`Ix.Cert.TraceSource.axiom_nodup` and its siblings), so it must +not accept them (`sourceVerdict`). -/ +def sourceOnlyBad : Array Lean.Name := #[`tut06_bad01] + +/-- The Source-mode verdict on `n` in `closure` (`kernel-source-ingress`'s +check, `Tests.Ix.Kernel.SourceIngressStep.run`), as `(outcome, reason)`. -/ +def sourceVerdict (closure : List (Lean.Name × Lean.ConstantInfo)) (n : Lean.Name) : + IO (Option (String × String)) := do + let env := Tests.Ix.Kernel.SourceIngressStep.canon closure.toArray + let blocks := Tests.Ix.Kernel.SourceIngressStep.condense env + let pre ← IO.ofExcept (Ix.Kernel.Reader.builtinPrelude.mapError IO.userError) + let r ← Tests.Ix.Kernel.SourceIngressStep.run pre env blocks + return (r.rowOf (Ix.Kernel.Key.ofName (Ix.Kernel.Name.ofLeanName n))).map + fun row => (row.outcome, row.reason) + +/-- `names` checked with user axioms admitted: `closure` compiled and bound +as `ix compile` binds it (`Ix.RoleBinding.Compile.finalize`), then checked +as `ix check-lean --allow-user-axioms` checks a selection. Each name's +`(outcome, reason)`, and the names of the user axioms the checks admitted. -/ +def checkWithUserAxioms (env : Lean.Environment) (closure : List (Lean.Name × Lean.ConstantInfo)) + (names : Array Lean.Name) : + IO (Except String (Std.HashMap String (String × String) × Array String)) := do + let out ← match ← Ix.CompileM.compileCertified env closure 8 with + | .ok out => pure out + | .error e => return .error s!"compile: {e}" + unless out.failures.isEmpty do + return .error s!"compile: {out.failures.toMap.size} constants failed" + let artifact ← Ix.RoleBinding.Compile.finalize (Ix.RoleBinding.Compile.certifiedBackend 8) env + Ix.RoleBinding.Compile.nativeCertSource (Ix.Compile.CompileInput.plain closure) out.env + Ix.Resource.typingLimits (existing? := out.existing?) + let bytes ← IO.ofExcept (Ixon.serEnv artifact) + let prepared ← match ← Ix.Cli.CheckLeanCmd.prepareInput bytes (allowUserAxioms := true) with + | .ok p => pure p + | .error e => return .error s!"prepare: {e.message}" + let cfg : Ix.Cli.CheckLeanCmd.CheckCfg := { workers := 1, silent := true, progressMs := 0, slowMs := 0 } + match ← Ix.Cli.CheckLeanCmd.runPrepared prepared cfg (.only (names.map toString)) with + | .error e => return .error s!"check: {e.message}" + | .ok report => + let outcomes := report.targets.foldl (init := {}) fun m t => + m.insert t.target.label (t.outcome, t.reason) + let axiomNames := report.userAxioms.flatMap fun a => + (artifact.named.toList.filter (·.2.addr == a)).toArray.map (·.1.pretty) + return .ok (outcomes, axiomNames) + +def rsCheckMalformedRecRuleIxonFFI + (consts : List (Lean.Name × Lean.ConstantInfo)) + (recName : Lean.Name) : + IO (Option CheckError) := do + let rawEnv := Ix.CompileM.canonInput consts.toArray 32 + let (compiledEnv, _) ← match Ix.Cert.compileReport rawEnv with + | .ok res => pure res + | .error err => return some (.compileError (toString err)) + let ixRecName := Ix.Name.fromLeanName recName + let some named := compiledEnv.named[ixRecName]? + | return some (.kernelException s!"missing recursor {recName}") + let some lc := compiledEnv.consts[named.addr]? + | return some (.kernelException s!"missing constant record for {recName}") + let c ← match lc.get with + | .ok c => pure c + | .error e => return some (.kernelException e) + let mutateRec (r : Ixon.Recursor) : Ixon.Recursor := + if h : r.rules.size ≥ 2 then + let r0 := r.rules[0] + let r1 := r.rules[1] + { r with rules := r.rules.set! 1 { r1 with rhs := r0.rhs } } + else r + let mutatedInfo : Ixon.ConstantInfo := match c.info with + | .recr r => .recr (mutateRec r) + | .muts ms => .muts (ms.map fun + | .recr r => .recr (mutateRec r) + | m => m) + | info => info + let mutatedConst : Ixon.Constant := { c with info := mutatedInfo } + let mutatedLazy := Ixon.LazyConstant.ofConstant mutatedConst + let mutatedEnv : Ixon.Env := { compiledEnv with consts := compiledEnv.consts.insert named.addr mutatedLazy } + let rows ← Tests.Ix.Kernel.SourceIngressCompare.ixonVerdicts mutatedEnv + match rows[Ix.LeanIngress.ingressKey ixRecName]? with + | some ("accept", _) | some ("typed", _) => return none + | some (outcome, reason) => return some (.kernelException s!"{outcome}: {reason}") + | none => return some (.kernelException s!"missing verdict for {recName}") def testTutorialConsts : TestSeq := .individualIO "kernel tutorial checks" none (do @@ -112,55 +241,64 @@ def testTutorialConsts : TestSeq := -- Deduplicate let constNames := allNames.toList.eraseDups.toArray - -- Build expected outcomes: false for names in bad test cases (excluding - -- renaming tests, whose constants are individually valid), true otherwise + -- The names of bad test cases (excluding renaming tests, whose constants + -- are individually valid) let mut badNames : Std.HashSet Lean.Name := Std.HashSet.emptyWithCapacity 64 for tc in testCases do if tc.outcome == .bad && tc.renamings.size == 0 then for n in tc.decls do badNames := badNames.insert n let advNatRec := p ++ `AdvNat.rec - let expectPass := constNames.map (fun n => - if n == advNatRec then true else !badNames.contains n) - -- Collect raw constants stored by bad_raw_consts (inductInfo/ctorInfo/recInfo - -- that couldn't go through the Lean kernel). + -- Raw constants stored by good_raw_consts/bad_raw_consts + -- (inductInfo/ctorInfo/recInfo that couldn't go through the Lean kernel). let rawConsts := TutorialMeta.getRawConsts leanEnv - let extraConstList := rawConsts.toList.map (fun ci => (ci.name, ci)) - - -- Filter the Lean env down to the transitive closure of the test - -- constants before shipping to Rust. Without this, `compile_env` processes - -- ~200k unrelated blocks (full Mathlib if imported), turning a 5s test - -- into a 45s test. Mirrors `Ix/Cli/ValidateCmd.lean`'s `collectDeps`. - let rawConstsMap : Std.HashMap Lean.Name Lean.ConstantInfo := + let rawByName : Std.HashMap Lean.Name Lean.ConstantInfo := rawConsts.foldl (fun m ci => m.insert ci.name ci) (Std.HashMap.emptyWithCapacity rawConsts.size) - let seeds : List Lean.Name := - constNames.toList ++ (rawConsts.toList.map (·.name)) - let (_, closedConsts) := TutorialMeta.collectDepsWithExtras leanEnv rawConstsMap seeds - let allConstList := closedConsts ++ extraConstList + -- A compile input: the closure of `seeds` (filtering the Lean env down + -- keeps the compile to the test constants), with the raw payloads among + -- them added. A payload replaces the environment's declaration of its + -- name (`AdvNat.rec`'s replaces the recursor Lean generated). + let inputOf (seeds : Array Lean.Name) : List (Lean.Name × Lean.ConstantInfo) := + let payloads := seeds.filterMap fun n => rawByName[n]?.map fun ci => (n, ci) + let pay : Std.HashMap Lean.Name Lean.ConstantInfo := + payloads.foldl (fun m (n, ci) => m.insert n ci) {} + let (_, closed) := TutorialMeta.collectDepsWithExtras leanEnv pay seeds.toList + closed.filter (!pay.contains ·.1) ++ payloads.toList - IO.println s!"[kernel-tutorial] {testCases.size} test cases, {constNames.size} constants to check ({allConstList.length} consts in closure)" + -- The good constants (good cases, stdlib and direct constants) are + -- compiled and checked together; each bad case is compiled and checked + -- on its own. The certified compile is fail-closed: its report checks + -- refuse an input whole when one family breaks the shapes its proof + -- needs (`Ix.Cert.compileReport`; a raw recursor without its family's + -- level parameters, `inductWrongCtorResLevel`, or the raw `AdvNat.rec` + -- beside its alpha twin `Nat`, `twinRefusal` below). In one shared + -- compile, a single adversarial payload would refuse every good + -- constant and make every bad case pass vacuously. + let goodNames := constNames.filter (!badNames.contains ·) + let goodInput := inputOf goodNames + IO.println s!"[kernel-tutorial] {testCases.size} test cases, {constNames.size} constants to check \ + ({goodNames.size} good in one closure of {goodInput.length} consts; bad cases one by one)" -- Tutorial batches are small and targeted — every constant's outcome -- is individually meaningful, so keep the verbose per-constant log. - -- Rust returns results in the same order as `constNames`, so we zip + -- Results come back in the order of the names asked for, so we zip -- them back into a `Name → result` map below. - let results ← rsCheckConstsFFI allConstList constNames expectPass false - - -- Build Name → result map by pairing each input name with its result. - -- Rust preserves input order, so `results[i]` corresponds to - -- `constNames[i]`. + let results ← rsCheckConstsFFI goodInput goodNames (goodNames.map fun _ => true) false let mut resultMap : Std.HashMap Lean.Name (Option CheckError) := - Std.HashMap.emptyWithCapacity results.size - for i in [:constNames.size] do - resultMap := resultMap.insert constNames[i]! results[i]! - - -- `AdvNat.rec` is an adversarial raw recursor payload. The production - -- compile path is allowed to regenerate aux recursors, which sanitizes - -- that raw payload before `rsCheckConstsFFI` sees it. For this one test, - -- mutate the compiled Ixon after aux generation and check that exact - -- malformed stored rule directly. + Std.HashMap.emptyWithCapacity constNames.size + for i in [:goodNames.size] do + resultMap := resultMap.insert goodNames[i]! results[i]! + for tc in testCases do + if tc.outcome == .bad && tc.renamings.size == 0 then + let rs ← rsCheckConstsFFI (inputOf tc.decls) tc.decls (tc.decls.map fun _ => false) true + for i in [:tc.decls.size] do + resultMap := resultMap.insert tc.decls[i]! rs[i]! + + -- `AdvNat.rec` is an adversarial raw recursor payload. For this one + -- test, compile `AdvNat`'s genuine family, mutate its compiled + -- recursor's rule and check that exact malformed stored rule directly. if constNames.contains advNatRec then let advNatConsts := #[ p ++ `AdvNat, p ++ `AdvNat.zero, p ++ `AdvNat.succ, advNatRec @@ -176,24 +314,103 @@ def testTutorialConsts : TestSeq := let mut failed := 0 let mut errors : Array String := #[] - -- Check good test cases (must pass). When a good constant is rejected, - -- pull the raw message string out of `CheckError.kernelException` rather - -- than calling `repr err` — derived `Repr` for long multi-line strings is - -- extremely slow (seconds per call) and can make the test appear to hang. + -- `twinRefusal`: the raw `AdvNat.rec` beside `Nat`'s family. `AdvNat` is + -- alpha-equivalent to `Nat` (and `TN`, `TN2`), so their one inductive + -- block would hold two recursor records; the recursor index files + -- `Nat.rec`'s under the block, which is not a published recursor of + -- `AdvNat`'s family, and the compile's recursor check + -- (`Ix.Cert.RecursorsOwned`) refuses the input whole. It is the refusal + -- that refused the shared tutorial corpus while the payload was in it. + match rawByName[advNatRec]? with + | none => + failed := failed + 1 + errors := errors.push s!" ✗ the raw payload {advNatRec} is missing" + | some payload => + let named (ns : List Lean.Name) := ns.filterMap fun n => + (leanEnv.constants.find? n).map fun ci => (n, ci) + let twin := named [``Nat, ``Nat.zero, ``Nat.succ, ``Nat.rec] ++ + named [p ++ `AdvNat, p ++ `AdvNat.zero, p ++ `AdvNat.succ] ++ [(advNatRec, payload)] + match Ix.Cert.compileReport (Ix.CompileM.canonInput twin.toArray 32) with + | .error e => + if ((toString e).splitOn "recursor check failed").length > 1 then + passed := passed + 1 + else + failed := failed + 1 + errors := errors.push s!" ✗ BAD raw {advNatRec} beside Nat: refused, but not by \ + the recursor check: {e}" + | .ok _ => + failed := failed + 1 + errors := errors.push s!" ✗ BAD raw {advNatRec} beside Nat: the certified compile \ + accepted it" + + -- The axiom fixtures with the tutorial's axioms admitted (`axiomFixtures`). + -- The opt-in needs a binding that binds the axiom roles + -- (`Ix.RoleBinding.UserAxioms.axiomRoles`), so the input carries them. + let conditional ← checkWithUserAxioms leanEnv + (inputOf (axiomFixtures ++ #[``sorryAx, ``propext, ``Classical.choice])) axiomFixtures + match conditional with + | .error e => + failed := failed + 1 + errors := errors.push s!" ✗ axiom fixtures with user axioms admitted: {e}" + | .ok (_, admitted) => + IO.println s!"[kernel-tutorial] {axiomFixtures} with user axioms admitted: \ + conditional on {admitted}" + let allowed := tutorialAxioms.map toString + if admitted.isEmpty || !admitted.all allowed.contains then + failed := failed + 1 + errors := errors.push s!" ✗ axiom fixtures: admitted user axioms {admitted}, \ + expected tutorial axioms among {allowed}" + else passed := passed + 1 + + -- Check good test cases (must pass, but for the IxC verdicts above). + -- When a good constant is rejected, pull the raw message string out of + -- `CheckError.kernelException` rather than calling `repr err` — derived + -- `Repr` for long multi-line strings is extremely slow (seconds per + -- call) and can make the test appear to hang. for tc in testCases do if tc.outcome == .good then for n in tc.decls do - match resultMap.get? n with - | some none => passed := passed + 1 - | some (some err) => - failed := failed + 1 - let msg := match err with - | .kernelException m => s!"kernel: {m}" - | .compileError m => s!"compile: {m}" - errors := errors.push s!" ✗ GOOD {n}: rejected with {msg}" - | none => - failed := failed + 1 - errors := errors.push s!" ✗ GOOD {n}: not found in results" + let msg? : Option String := (resultMap.get? n).map fun + | none => "accepted" + | some (.kernelException m) => s!"kernel: {m}" + | some (.compileError m) => s!"compile: {m}" + if nativeEvaluationFixtures.contains n then + -- must not pass: its proof is a declined `native_decide` axiom + match resultMap.get? n with + | some (some _) => passed := passed + 1 + | _ => + failed := failed + 1 + errors := errors.push s!" ✗ GOOD {n} (native_decide): {msg?.getD "not found"}, \ + expected IxC not to accept it" + else if let some (_, reason) := cornerCases.find? (·.1 == n) then + if msg? == some s!"kernel: {reason}" then passed := passed + 1 + else + failed := failed + 1 + errors := errors.push s!" ✗ CORNER {n} (arena outcome either): {msg?.getD "not found"}, \ + expected kernel: {reason}" + else if axiomFixtures.contains n then + -- strict: blocked by the declined axiom; admitted: accepted + let admitted := match conditional with + | .ok (outcomes, _) => outcomes[toString n]? + | .error _ => none + match resultMap.get? n, admitted with + | some (some (.kernelException m)), some (o, _) => + if m.startsWith "blocked: " && (o == "accept" || o == "typed") then + passed := passed + 1 + else + failed := failed + 1 + errors := errors.push s!" ✗ GOOD {n} (tutorial axioms): strict {m}, with user \ + axioms admitted {o}" + | _, _ => + failed := failed + 1 + errors := errors.push s!" ✗ GOOD {n} (tutorial axioms): strict {msg?.getD "not found"}; \ + with user axioms admitted {admitted}" + else + match resultMap.get? n with + | some none => passed := passed + 1 + | _ => + failed := failed + 1 + errors := errors.push s!" ✗ GOOD {n}: rejected with {msg?.getD "not found in results"}" -- Check bad test cases (must fail) for tc in testCases do @@ -228,6 +445,20 @@ def testTutorialConsts : TestSeq := errors := errors.push s!" ✗ BAD renaming: expected name collision but none found in {targetStrs}" continue for n in tc.decls do + if sourceOnlyBad.contains n then + -- duplicate level-parameter names: Source mode must not accept it + match ← sourceVerdict (inputOf tc.decls) n with + | some (outcome, reason) => + IO.println s!"[kernel-tutorial] {n}: Source mode {outcome}: {reason}" + if outcome != "accept" && outcome != "typed" then passed := passed + 1 + else + failed := failed + 1 + errors := errors.push s!" ✗ BAD {n}: Source mode {outcome} {reason}, \ + expected it not to accept duplicate level parameters" + | none => + failed := failed + 1 + errors := errors.push s!" ✗ BAD {n}: no Source-mode verdict" + continue match resultMap.get? n with | some (some _) => passed := passed + 1 -- correctly rejected | some none => diff --git a/Tests/Ix/Kernel/TutorialDefs.lean b/Tests/Ix/Kernel/TutorialDefs.lean index ebafc0fb4..1c8c39797 100644 --- a/Tests/Ix/Kernel/TutorialDefs.lean +++ b/Tests/Ix/Kernel/TutorialDefs.lean @@ -1254,16 +1254,24 @@ good_thm natBeqTrue : Nat.beq 42 42 = true := by native_decide good_thm natBeqFalse : Nat.beq 42 43 = false := by native_decide -/-! ## Nested-occurrence spec_params validity +/-! ## Nested-occurrence spec_params validity (a corner case) `F.mk : (n : Nat) → P n F → F`. The `P n F` occurrence LOOKS nested (head `P` is an external inductive whose param args mention the block member `F`), but its first param arg `n` references the constructor's own field-local binder, so it is NOT a valid nested-inductive -parameterization. Rust (`crates/kernel/src/inductive.rs:723-730`) -skips the occurrence: no `P` aux is synthesized, `F.rec` keeps the -plain non-nested shape (no IH for the `P n F` field), and the env is -accepted — this is a GOOD fixture. +parameterization. `P` stores nothing, so the occurrence is phantom. The +Rust kernel skipped the occurrence: no `P` aux is synthesized, `F.rec` +keeps the plain non-nested shape (no IH for the `P n F` field), and the env +was accepted. + +Its right verdict is not settled. lean-kernel-arena's nearest test, +`corner-cases/nested-nonuniform-param`, has outcome `either`; its relative +`bugs/nested-unused-param` is a proof of `False` through an unchecked +parametric argument (lean4#14576, fixed in lean4#14577). It is registered +with the good cases, for the IxVM arena run (`Tests.Ix.Kernel.Arena`), which +follows the Rust kernel. The IxC tutorial run treats it as a corner case and +pins IxC's verdict, a decline (`Tests.Ix.Kernel.Tutorial.cornerCases`). Lean's elaborator rejects this declaration (nested occurrences must use only the block's fixed params), so it is hand-built here with its @@ -1389,7 +1397,7 @@ run_cmd do `nestedSpecLocalF, `nestedSpecLocalF.mk, `nestedSpecLocalF.rec] let some tc := cases[cases.size - 1]? | throwError "nested-local fixture is absent" unless tc.outcome == .good && tc.decls == expected do - throwError "nested-local fixture must retain all six accepted targets" + throwError "nested-local fixture must retain all six targets" let raw := getRawConsts env for (owner, params, fields) in [(`nestedSpecLocalP, 2, 0), (`nestedSpecLocalF, 0, 2)] do let found := raw.filter (fun ci => ci.name == owner ++ `rec) diff --git a/Tests/Ix/Kernel/TutorialMeta.lean b/Tests/Ix/Kernel/TutorialMeta.lean index ac875fb13..2b2b95959 100644 --- a/Tests/Ix/Kernel/TutorialMeta.lean +++ b/Tests/Ix/Kernel/TutorialMeta.lean @@ -135,6 +135,10 @@ partial def collectDepsWithExtras for r in ctorCi.type.getUsedConstantsAsSet do refs := refs.insert r for mutName in v.all do refs := refs.insert mutName + refs := refs.insert (Lean.mkRecName mutName) + if let some first := v.all.head? then + for i in [:v.numNested] do + refs := refs.insert (first.str s!"rec_{i + 1}") | .ctorInfo v => refs := refs.insert v.induct | .recInfo v => diff --git a/Tests/Ix/Kernel/UserAxioms.lean b/Tests/Ix/Kernel/UserAxioms.lean new file mode 100644 index 000000000..b57f1eb5d --- /dev/null +++ b/Tests/Ix/Kernel/UserAxioms.lean @@ -0,0 +1,161 @@ +import LSpec +import IxC.Kernel.Admission + +/-! # The checks that admit user axioms, at the kernel + +Small declaration arrays over the committed Ixon prelude, through the fold +under each axiom policy (`Ix.Kernel.Cached.AxiomPolicy`, +`IxC/Kernel/Cached/Policy.lean`), and the role table's admission under the +policy (`Ix.Kernel.Admission.admitAxiomPolicy`): + +* a user axiom (`axiom cheat : False`) declines under `strict` and is admitted + under `allowUser`, which lists it, and a theorem proved from it is accepted + only there; +* Lean's own `sorryAx`, read under an input key because the table leaves the + role unbound, declines under both, and so does a restated `Quot.sound`: the + user arm refuses an axiom whose type is an axiom role's specification; +* without user axioms the two folds agree (T1), on the prelude and with an + ordinary axiom-free declaration added; +* a check that admits user axioms refuses a table that leaves `sorryAx`, + `propext` or `Classical.choice` unbound, as an admission error, before + reading; the strict check does not ask. -/ + +open Ix.Kernel +open Ix.Kernel.Reader +open Ix.Kernel.Admission + +namespace Tests.Ix.Kernel.UserAxioms + +/-- A 32-byte address of one repeated byte. -/ +def address (tag : UInt8) : Address := ⟨⟨Array.replicate 32 tag⟩⟩ + +/-- An input key. -/ +def key (tag : UInt8) : Key .anon := .id (address tag) + +def table : RoleTable := match defaultTable with | .ok t => t | .error _ => default +def pre : Prelude .anon := match builtinPrelude with | .ok p => p | .error _ => default + +/-- The table without one role's binding. -/ +def unbind (r : Role) (t : RoleTable) : RoleTable := { t with bound := t.bound.filter (·.1 != r) } + +/-- The prelude's declarations followed by `extra`. -/ +def decls (extra : Array (Declaration .anon)) : Array (Declaration .anon) := pre.ix.decls ++ extra + +def strict (extra : Array (Declaration .anon)) := + Cached.checkDecls .verified [] (decls extra) + +def user (extra : Array (Declaration .anon)) := + Cached.checkDeclsWithUserAxioms .verified [] (decls extra) + +/-- The failure's message, rendered. -/ +def failure (r : Except (CheckError .anon × Nat) (Env .anon)) : String := + match r with + | .error (e, _) => toString e + | .ok _ => "" + +def contains (s pattern : String) : Bool := (s.splitOn pattern).length > 1 + +/-- The environments' sizes when both accept, and whether both decline. -/ +def agree (a b : Except (CheckError .anon × Nat) (Env .anon)) : Bool := + match a, b with + | .ok ea, .ok eb => ea.consts.length == eb.consts.length + | .error (ea, ia), .error (eb, ib) => ia == ib && toString ea == toString eb + | _, _ => false + +/-- `axiom cheat : False`. -/ +def cheat : Declaration .anon := .axiomDecl { name := key 211, levelParams := [], type := .const falseName [] } + +/-- `theorem t : False := cheat`. -/ +def fromCheat : Declaration .anon := .thmDecl { name := key 212, levelParams := [], type := .const falseName [] } + (.const (key 211) []) + +/-- Lean's `sorryAx.{u} : (α : Sort u) → Bool → α`, under an input key. -/ +def sorryAxUnbound : Declaration .anon := + .axiomDecl { name := key 213, levelParams := [0], type := sorryAxSpecTy } + +/-- `theorem t : False := sorryAx False false`, over the unbound `sorryAx`. -/ +def fromSorry : Declaration .anon := .thmDecl { name := key 214, levelParams := [], type := .const falseName [] } + (.app (.app (.const (key 213) [.zero]) (.const falseName [])) (.const boolFalseName [])) + +/-- `Quot.sound`'s specification, restated under an input key. -/ +def quotSoundRestated : Declaration .anon := + .axiomDecl { quotSoundRaw.toConstantVal with name := key 215 } + +/-- An axiom-free definition: `def d : Sort 1 := Sort 0`. -/ +def plain : Declaration .anon := + .defnDecl { name := key 216, levelParams := [], type := .sort (.succ (.succ .zero)) } + (.sort (.succ .zero)) .opaque + +private def need (label : String) (ok : Bool) : Except String Unit := + if ok then .ok () else .error label + +/-- The fold under each policy. -/ +def foldChecks : Except String Unit := do + need "the committed prelude reads" (pre.ix.decls.size > 0) + -- a user axiom + need "strict declines a user axiom" (strict #[cheat]).toOption.isNone + need "the strict decline names a non-standard axiom" (contains (failure (strict #[cheat])) "non-standard axiom") + need "allowUser admits a user axiom" (user #[cheat]).isOk + need "allowUser lists exactly that user axiom" + ((userAxiomsOfDecls (decls #[cheat]).toList).map (·.name) == [key 211]) + need "the prelude declares no user axiom" (userAxiomsOfDecls (decls #[]).toList).isEmpty + need "strict declines a theorem from a user axiom" (strict #[cheat, fromCheat]).toOption.isNone + need "allowUser accepts a theorem from a user axiom" (user #[cheat, fromCheat]).isOk + -- Lean's `sorryAx` under an input key: an axiom role's specification + need "strict declines an unbound sorryAx" (strict #[sorryAxUnbound]).toOption.isNone + need "allowUser declines an unbound sorryAx" (user #[sorryAxUnbound]).toOption.isNone + need "the decline names the sorryAx specification" + (contains (failure (user #[sorryAxUnbound])) "restates the specification of") + need "allowUser declines a theorem from an unbound sorryAx" + (user #[sorryAxUnbound, fromSorry]).toOption.isNone + need "allowUser declines a restated Quot.sound" (user #[quotSoundRestated]).toOption.isNone + need "the decline names the Quot.sound specification" + (contains (failure (user #[quotSoundRestated])) "restates the specification of") + -- T1: without user axioms the folds agree + need "the prelude: both folds accept alike" ((strict #[]).isOk && agree (strict #[]) (user #[])) + need "an axiom-free definition: both folds accept alike" + ((strict #[plain]).isOk && agree (strict #[plain]) (user #[plain])) + need "an ill-typed theorem without user axioms: both folds decline alike" + ((strict #[fromCheat]).toOption.isNone && agree (strict #[fromCheat]) (user #[fromCheat])) + +/-- The role table's admission under each policy, and the entry over decoded +records under it. -/ +def gateChecks : Except String Unit := do + need "the default table binds sorryAx, propext and Classical.choice" + (userAxiomRoles.all fun r => (table.addressOf? r).isSome) + need "allowUser admits the default table" (admitAxiomPolicy .allowUser table).isOk + for r in userAxiomRoles do + let t := unbind r table + need s!"the table without {repr r} leaves it unbound" (t.addressOf? r).isNone + need s!"strict does not ask for {repr r}" (admitAxiomPolicy .strict t).isOk + match admitAxiomPolicy .allowUser t with + | .error (.axiomRoleUnbound r') => need s!"allowUser refuses the table without {repr r}" (r' == r) + | _ => throw s!"allowUser admits a table without {repr r}" + match checkConstantsUnder .allowUser (fun _ => address 0) t pre [] [] with + | .error e@(.axiomRoleUnbound _) => + need s!"the refusal is a decline, as an admission error ({repr r})" (e.outcome == .declined) + need s!"the refusal says which role ({repr r})" (contains (toString e) "unbound") + | _ => throw s!"the entry admitting user axioms runs without {repr r}" + need s!"checkConstantsWithUserAxioms refuses the table without {repr r}" + (checkConstantsWithUserAxioms (fun _ => address 0) t pre [] []).toOption.isNone + match checkConstantsUnder .allowUser (fun _ => address 0) table pre [] [], + checkConstantsWith (fun _ => address 0) table pre [] [] with + | .ok (env, axs), .ok env' => + need "the prelude through the entry lists no user axiom" axs.isEmpty + need "the prelude through the entry: the same environment as strict" + (env.consts.length == env'.consts.length) + | _, _ => throw "the prelude through the entry does not accept under both policies" + +private def unit (label : String) (check : Unit → Except String Unit) : LSpec.TestSeq := + .individualIO label none (do + let result ← IO.lazyPure check + return match result with + | .ok _ => (true, 0, 0, none) + | .error message => (false, 0, 0, some message)) .done + +def suite : List LSpec.TestSeq := [ + unit "the fold under each axiom policy: user axioms, axiom role specifications, T1" (fun _ => foldChecks), + unit "the role table's admission under each axiom policy" (fun _ => gateChecks) +] + +end Tests.Ix.Kernel.UserAxioms diff --git a/Tests/Ix/LeanExport.lean b/Tests/Ix/LeanExport.lean index 24cb63061..a2385eda1 100644 --- a/Tests/Ix/LeanExport.lean +++ b/Tests/Ix/LeanExport.lean @@ -1,4 +1,14 @@ -/- +module + +public import LSpec +public import Ix.LeanExport +public import Ix.LeanExport.Compare +public import Ix.CompileCertified +public import Ix.RoleBinding.Compile +public import Ix.Meta +public import Tests.Fixtures.toolchains.Primitives + +/-! The `lean4export` bridge (`Ix.LeanExport`, `ix compile-export`). - **The reader** accepts a well-formed export and refuses malformed ones: @@ -14,17 +24,13 @@ included, gives exactly the content of the native compile of the same declarations: every name, address, constant and the binding. Metadata differences are reported. + - **Another toolchain's roles.** The 4.35.0 and 4.33.1 fixtures publish + `Int.decLt` and `Nat.xor` at addresses the default role table does not + bind them at. The compile at the default table refuses them; the compile + of `ix compile-export` binds them in the export's own role binding. - The fixture is decompressed with the `zstd` command. + The fixtures are decompressed with the `zstd` command. -/ -module - -public import LSpec -public import Ix.LeanExport -public import Ix.LeanExport.Compare -public import Ix.RoleBinding.Compile -public import Ix.Meta -public import Tests.Fixtures.toolchains.Primitives public section @@ -152,11 +158,11 @@ def sameBinders (a b : Lean.ConstantInfo) : Bool := certificate theorems from the same environment. -/ def compileBound (env : Lean.Environment) : IO Ixon.Env := do let input ← IO.ofExcept ((Ix.Compile.compileInputFromEnv env env.constants.toList).mapError toString) - let out ← IO.ofExcept (← Ix.CompileM.compileLeanInputValidated input (numWorkers := 8)) - require out.cenv.ungrounded.isEmpty - s!"{out.cenv.ungrounded.size} constants failed to compile" - Ix.RoleBinding.Compile.finalize (Ix.RoleBinding.Compile.leanBackend 8 {}) env (pure env) input - out.env Ix.Resource.typingLimits + let out ← IO.ofExcept (← Ix.CompileM.compileCertified env env.constants.toList 8) + require out.failures.isEmpty + s!"{out.failures.toMap.size} constants failed to compile" + Ix.RoleBinding.Compile.finalize (Ix.RoleBinding.Compile.certifiedBackend 8) env (pure env) input + out.env Ix.Resource.typingLimits out.existing? def fidelity : IO (Bool × Nat × Nat × Option String) := do let exported ← IO.ofExcept (importString (← readFixture Lean.versionString)) @@ -182,10 +188,58 @@ native ones in binder names or info; {exported.droppedSyntaxMData} syntax-valued require c.contentExact s!"the export's compile differs in content: {report}" return (true, 0, 0, none) +/-! ## Another toolchain's roles -/ + +/-- An export of another toolchain binds its roles where its own declarations +put them. 4.35.0's `Decidable` has one constructor (`Decidable.intro`), so its +`Int.decLt` sits elsewhere than ix's. 4.33.1's `Bool.not` is another term, so +its `Nat.xor` does too. + +- **At the default role table** (`compileCertified`), the published role is + refused. +- **As `ix compile-export` compiles it** (`defaultRoles := false`), the export + is finalized. Its binding binds the role at the address the export publishes + it at, which is not the default table's. `finalize`'s own check of the + published role names against that binding passed. -/ +def foreignRoles (version : String) (role : _root_.Ix.Kernel.Role) : + IO (Bool × Nat × Nat × Option String) := do + let exported ← IO.ofExcept (importString (← readFixture version)) + let env ← environment exported.constants + let constants := env.constants.toList + match ← Ix.CompileM.compileCertified env constants 8 with + | .ok _ => + throw (IO.userError s!"{version}: the compile at the default role table accepted {repr role}") + | .error e => + require (e.startsWith "role check failed") s!"{version}: refused for another reason: {e}" + let artifact ← match ← Ix.CompileM.compileAndFinalize env constants 8 (allowPartial := false) + (certSource := pure env) (defaultRoles := false) with + | .ok (_, .done artifact _) => pure artifact + | .ok (out, .refused) => + throw (IO.userError s!"{version}: {out.failures.toMap.size} constants failed to compile") + | .ok (_, .failed e) => throw (IO.userError s!"{version}: finalization failed: {e}") + | .error e => throw (IO.userError s!"{version}: the compile failed: {e}") + let some binding := artifact.roleBinding + | throw (IO.userError s!"{version}: the artifact carries no role binding") + let some table := _root_.Ix.Kernel.RoleTable.ofBytes binding.bytes + | throw (IO.userError s!"{version}: the role binding does not decode") + let published := (← IO.ofExcept (_root_.Ix.RoleBinding.sourceAddresses artifact))[role.leanName]? + let atDefault := (_root_.Ix.Cert.roleTableBound.find? (·.1 == role)).map (·.2) + require (published.isSome && table.addressOf? role == published) + s!"{version}: {repr role} is published at {published}, bound at {table.addressOf? role}" + require (published != atDefault) + s!"{version}: {repr role} is published at the default table's address {atDefault}" + IO.println s!"[lean-export] {version}: {repr role} bound at {published}; the default table \ +binds it at {atDefault}" + return (true, 0, 0, none) + def suite : List LSpec.TestSeq := [ readerTests, .individualIO "a 4.34.1 export compiles to exactly the native content, role binding included" none - fidelity .done] + fidelity .done, + .individualIO "a 4.35.0 export binds its own Int.decLt, which the default role table refuses" + none (foreignRoles "4.35.0" .intDecLt) .done, + .individualIO "a 4.33.1 export binds its own Nat.xor, which the default role table refuses" + none (foreignRoles "4.33.1" .natXor) .done] end Tests.Ix.LeanExport diff --git a/Tests/Ix/RecordDecoding.lean b/Tests/Ix/RecordDecoding.lean deleted file mode 100644 index 285251714..000000000 --- a/Tests/Ix/RecordDecoding.lean +++ /dev/null @@ -1,174 +0,0 @@ -module - -public import Ix.RoleBinding.Generate -public import LSpec - -namespace Tests.Ix.RecordDecoding - -open Ixon -open Ix.Kernel.Admission -open Ixon.CheckOrder (RecordStore) - -private def need (label : String) (ok : Bool) : Except String Unit := - if ok then .ok () else .error label - -private instance : BEq ByteError := ⟨fun left right => decide (left = right)⟩ - -private def sameResult [BEq ε] [BEq α] : Except ε α → Except ε α → Bool - | .ok left, .ok right => left == right - | .error left, .error right => left == right - | _, _ => false - -private def limits : Limits := ⟨2048, 8, 1 <<< 26, 1 <<< 20, 1 <<< 16⟩ - -private def constant (index : Nat) : Constant := { - sharing := #[], refs := #[], univs := #[.zero, .succ .zero] - info := .defn { - kind := .defn, safety := .safe, lvls := index.toUInt64, typ := .sort 1, value := .sort 0 } -} - -private def raw (bytes : ByteArray) : Address × ByteArray := - (Address.admissionHash bytes, bytes) - -private def records (count : Nat) : Array (Address × ByteArray) := - (Array.range count).map fun i => raw (serConstant (constant i)) - -private def environment (records : Array (Address × ByteArray)) : Env := - { consts := records.foldl (fun out (a, bytes) => out.insert a - { buf := bytes, len := bytes.size }) {} } - -/-- Keep the original literal implementation: the production entry has a -compiler simplification, so comparing two calls to that entry is insufficient. -/ -private def serialStore (limits : Limits) (env : Env) : Except String RecordStore := do - let records := (env.consts.toArray.map fun (a, c) => (a, c.rawBytes)).qsort - fun x y => x.1.cmpBytes y.1 == .lt - let blobs := (env.blobs.toArray.qsort fun x y => x.1.cmpBytes y.1 == .lt).toList - (preflight limits records.toList blobs).mapError Ix.RoleBinding.byteError - (uniqueKeys records.toList blobs).mapError Ix.RoleBinding.byteError - (authenticate Address.admissionHash records.toList blobs).mapError Ix.RoleBinding.byteError - let decoded ← (decodeRecords limits records.toList).mapError Ix.RoleBinding.byteError - return decoded.foldl (fun out (a, c) => out.insert a c) {} - -private def storeBytes (result : Except String RecordStore) : Except String (Array (Address × ByteArray)) := - result.map fun store => (store.toArray.map fun (a, c) => (a, serConstant c)).qsort - fun x y => x.1.cmpBytes y.1 == .lt - -private def storeParity (label : String) (limits : Limits) (env : Env) : Except String Unit := do - let expected := storeBytes (serialStore limits env) - need s!"{label}: explicit parallel store preserves the serial result" - (sameResult (storeBytes (Ix.RoleBinding.recordStoreParallel limits env)) expected) - need s!"{label}: production store preserves the serial result" - (sameResult (storeBytes (Ix.RoleBinding.recordStore limits env)) expected) - -private def storeError (label : String) (limits : Limits) (env : Env) (error : ByteError) : - Except String Unit := do - storeParity label limits env - need s!"{label}: exact refusal" - (sameResult (storeBytes (Ix.RoleBinding.recordStore limits env)) - (.error (Ix.RoleBinding.byteError error))) - -private def decodingChecks (largeCount : Nat) : Except String Unit := do - for count in [0, 1, 17, largeCount] do - let input := records count - let expected := (Array.range count).map fun i => - (Address.admissionHash (serConstant (constant i)), constant i) - for tasks in [0, 1, 2, 8, 256, 1024] do - let actual := Ix.RoleBinding.RecordDecoding.parallel tasks limits input.toList - need s!"{count} records / {tasks} tasks: complete input order and values" - (sameResult actual (.ok expected.toList)) - need s!"{count} records / {tasks} tasks: serial parity" - (sameResult actual (decodeRecords limits input.toList)) - let input := records largeCount - let malformed := raw (ByteArray.mk #[255]) - let noncanonical := raw ((serConstant (constant 900)).push 0) - -- With 769 records and 256 tasks the chunk size is four. The earlier - -- failure may end one chunk while the other starts the next. - for (first, second) in [(0, 4), (3, 4), (4, 255), (255, 256), (767, 768)] do - for (early, later) in [(malformed, noncanonical), (noncanonical, malformed)] do - let altered := (input.set! first early).set! second later - let .error reason := Ixon.Canonical.deConstant limits.maxRecordBytes - limits.maxRecordUnivNodes early.2 | throw "fixture unexpectedly decodes" - let expected := Except.error (ByteError.decode first early.1 reason) - for tasks in [1, 8, 256, 1024] do - let actual := Ix.RoleBinding.RecordDecoding.parallel tasks limits altered.toList - need s!"failures at {first}/{second}, {tasks} tasks: earliest complete error" - (sameResult actual expected && sameResult actual (decodeRecords limits altered.toList)) - let manyUniverses := { constant 900 with univs := Array.replicate 64 .zero } - let oversized := raw (ByteArray.mk (Array.replicate 65 0)) - for (tight, excessive) in [ - ({ limits with maxRecordBytes := 64 }, oversized), - ({ limits with maxRecordUnivNodes := 8 }, raw (serConstant manyUniverses))] do - need "ordinary records fit the tighter per-record bound" (decodeRecords tight input.toList).isOk - let .error reason := Ixon.Canonical.deConstant tight.maxRecordBytes - tight.maxRecordUnivNodes excessive.2 | throw "oversized fixture unexpectedly decodes" - for first in [3, 4, 768] do - let altered := input.set! first excessive - let expected := Except.error (ByteError.decode first excessive.1 reason) - let actual := Ix.RoleBinding.RecordDecoding.parallel 256 tight altered.toList - need "per-record bounds preserve the exact late refusal across chunks" - (sameResult actual expected && sameResult actual (decodeRecords tight altered.toList)) - -private def storeChecks (count : Nat) : Except String Unit := do - let input := records count - let env := environment input.reverse - storeParity "large canonical store" limits env - let .ok store := Ix.RoleBinding.recordStore limits env | throw "canonical store refused" - need "all distinct records survive parallel store construction" (store.size == count) - for i in [0, 3, 4, 255, 256, 768] do - let record := constant i - need "stored values match their original address" - (store[Address.admissionHash (serConstant record)]? == some record) - - let malformed := raw (ByteArray.mk #[255]) - let noncanonical := raw ((serConstant (constant 900)).push 0) - for orphan in [malformed, noncanonical] do - let altered := environment (input.push orphan) - need "orphan controls have no named roots" altered.named.isEmpty - storeParity "authenticated orphan" limits altered - need "an authenticated malformed or noncanonical orphan refuses the checked store" - (!(Ix.RoleBinding.checkedStore limits altered).isOk) - let both := environment ((input.push malformed).push noncanonical) - storeParity "multiple authenticated orphan failures" limits both - need "multiple authenticated orphans still refuse" (!(Ix.RoleBinding.recordStore limits both).isOk) - - let bad : Env := environment #[malformed] - let wrong := Address.admissionHash (ByteArray.mk #[1, 2, 3]) - need "mismatch fixture has distinct addresses" (wrong != malformed.1) - let unauthenticated := environment #[(wrong, malformed.2)] - let badBlob := { bad with blobs := ({} : Std.HashMap Address ByteArray).insert wrong malformed.2 } - storeError "record count precedes authentication and decoding" - { limits with maxRecords := 0 } unauthenticated (.limit .records) - storeError "blob count precedes decoding" - { limits with maxBlobs := 0 } badBlob (.limit .blobs) - storeError "payload total precedes authentication and decoding" - { limits with maxTotalBytes := 0 } unauthenticated (.limit .totalBytes) - storeError "record authentication precedes decoding" limits unauthenticated - (.mismatch .records 0 wrong) - storeError "blob authentication precedes record decoding" limits badBlob - (.mismatch .blobs 0 wrong) - let bothBad := { unauthenticated with blobs := badBlob.blobs } - storeError "record authentication precedes blob authentication" limits bothBad - (.mismatch .records 0 wrong) - let .error reason := Ixon.Canonical.deConstant limits.maxRecordBytes - limits.maxRecordUnivNodes malformed.2 | throw "malformed fixture unexpectedly decodes" - storeError "decode error retains its address and position" limits bad - (.decode 0 malformed.1 reason) - let cachedRecord : LazyConstant := - { buf := malformed.2, len := malformed.2.size, cache := some (constant 0) } - let cached := { bad with consts := bad.consts.insert malformed.1 cachedRecord } - storeError "a valid materialized cache cannot rescue malformed bytes" limits cached - (.decode 0 malformed.1 reason) - -private def unit (label : String) (check : Unit → Except String Unit) : LSpec.TestSeq := - .individualIO label none (do - let result ← IO.lazyPure fun _ => check () - return match result with - | .ok _ => (true, 0, 0, none) - | .error message => (false, 0, 0, some message)) .done - -public def suite : List LSpec.TestSeq := [ - unit "canonical decoding: values, boundaries and exact first refusals" (fun _ => decodingChecks 769), - unit "record store: phase precedence, exact errors and authenticated orphans" (fun _ => storeChecks 769) -] - -end Tests.Ix.RecordDecoding diff --git a/Tests/Ix/Resource.lean b/Tests/Ix/Resource.lean index f0254ed16..5732515d3 100644 --- a/Tests/Ix/Resource.lean +++ b/Tests/Ix/Resource.lean @@ -267,8 +267,8 @@ def allCases : Array Case := cases ++ higherOrderCases def fixtureLine (test : Case) : String := s!"{test.name}\t{test.expected}\t{hexOfBytes (runPut (putExpr test.type))}\t{hexOfBytes (runPut (putExpr test.value))}" -/-- The shared `resource.tsv` fixture, also read by the Rust resource checker. -`lake exe ixon-v4-tests --export-fixtures` writes it. -/ +/-- The `resource.tsv` fixture. `lake exe ixon-v4-tests --export-fixtures` +writes it. -/ def fixtureText : String := allCases.foldl (fun text test => text ++ fixtureLine test ++ "\n") s!"# name\texpected\ttype ({Ixon.wireFormatId} hex)\tbody ({Ixon.wireFormatId} hex)\n" diff --git a/Tests/Ix/ResourceHints.lean b/Tests/Ix/ResourceHints.lean index d83f87c81..a29534345 100644 --- a/Tests/Ix/ResourceHints.lean +++ b/Tests/Ix/ResourceHints.lean @@ -1,9 +1,8 @@ import LSpec import Ix.Meta -import Ix.CompileDriver +import Ix.CompileCertified import Ix.EnvScope import Ix.Resource.Validate -import Ix.Cert.Statement import Tests.Ix.Kernel.SourceIngress import Tests.Ix.ContractPipeline import Tests.Ix.SourceContract.SyntaxFixture @@ -11,24 +10,16 @@ import Tests.Ix.Kernel.TierIndDefs /-! # The resource validator's typing half on compiled closures (`resource-hints`) -* **Hints and coverage.** `Ix.Resource.TypingInput.hint` copies - `Ix.Cert.hintsOf`'s rule, so that `Ix.Resource` does not import `Ix.Cert`. - On the compiled closure of the reader-fidelity and Source-ingress fixtures, - the two give the same hint at every member and constructor of every record, - through and around its projection record; hints are found both through a - projection record and at a record itself. The input lists every record - exactly once (`Ixon.CheckOrder.wholeEnv`). -* **Actual typing verdicts**, on compiled - closures the typing half accepts: those fixtures (with assumption axioms), - the source-contract syntax fixture (a contract input), the contract - pipeline's input (an assumption axiom, `ContractA`) and the typing tier's - inductive fixtures (unsafe inductives, `kernel-tier-ind`). Each reports the - size of its uncertified part. -/ +Actual typing verdicts (`Ix.Resource.typeCheck`) on compiled closures the typing +half accepts: the reader-fidelity and Source-ingress fixtures (with assumption +axioms), the source-contract syntax fixture (a contract input), the contract +pipeline's input (an assumption axiom, `ContractA`) and the typing tier's +inductive fixtures (unsafe inductives, `kernel-tier-ind`). Each reports the +size of its uncertified part. -/ namespace Tests.ResourceHints open LSpec -open _root_.Ix.Kernel (ConstRef) open Tests.Ix.Kernel.SourceIngress (defsModule ownModule preludeNames) /-- The constants of `modules` and everything they reach (as `cert-statement` @@ -45,30 +36,6 @@ def closureOf (leanEnv : Lean.Environment) (modules : Array Lean.Name) (extra : | none => none IO.ofExcept (Tests.Ix.Kernel.ReaderFidelity.closure leanEnv (seeds ++ extra ++ preludeNames)) -/-- Every reference a record of `ixe` denotes or holds: a singleton's member -0, a block's members and constructors, a projection's member. -/ -def refsOf (ixe : Ixon.Env) : Array (ConstRef Address) := Id.run do - let mut out : Array (ConstRef Address) := #[] - for (a, lc) in ixe.consts.toList do - let some c := lc.get? | continue - match c.info with - | .muts ms => - for (m, i) in ms.zipIdx do - out := out.push (.member a i) - if let .indc ind := m then - for j in [0:ind.ctors.size] do out := out.push (.ctor a i j) - | .dPrj p => out := out.push (.member p.block p.idx.toNat) - | .iPrj p => out := out.push (.member p.block p.idx.toNat) - | .rPrj p => out := out.push (.member p.block p.idx.toNat) - | .cPrj p => out := out.push (.ctor p.block p.idx.toNat p.cidx.toNat) - | _ => out := out.push (.member a 0) - return out - -/-- A reference, for messages. -/ -def spell : ConstRef Address → String - | .member b i => s!"{b} member {i}" - | .ctor b i c => s!"{b} member {i} constructor {c}" - /-- Size of the initial uncertified split. The typing fallback may expand this set to include all tier owners; this helper does not observe that path. -/ def uncertifiedSize (ixe : Ixon.Env) : Nat := @@ -89,39 +56,13 @@ def verdict (label : String) (ixe : Ixon.Env) : Array String × Array String := (errors, #[s!"{label}: {ixe.consts.size} records, {uncertifiedSize ixe} in the initial uncertified split, \ IxC {if ixc.toOption.isSome then "accepts" else "declines"}"]) -def hintsAndCoverage : IO (Array String × Array String) := do +def fixtureVerdict : IO (Array String × Array String) := do let leanEnv ← getCompileEnv #[defsModule, ownModule] let consts ← closureOf leanEnv #[defsModule, ownModule] #[`Nat.div] - let out ← match ← Ix.CompileM.compileLeanConsts consts (numWorkers := 4) with + let out ← match ← Ix.CompileM.compileCertified leanEnv consts 4 with | .ok out => pure out - | .error e => return (#[s!"compileLeanConsts failed: {e}"], #[]) - let ixe := out.env - let input ← match Ix.Resource.typingInput ixe with - | .ok input => pure input - | .error e => return (#[s!"typingInput failed: {e}"], #[]) - let mut errors : Array String := #[] - let mut viaProjection := 0 - let mut atRecord := 0 - let refs := refsOf ixe - for r in refs do - let mine := input.hint r - let theirs := Ix.Cert.hintsOf ixe r - unless mine == theirs do - errors := errors.push s!"hint at {spell r}: resource {repr mine}, Ix.Cert {repr theirs}" - if mine.isSome then - if input.projections.contains r then viaProjection := viaProjection + 1 - else atRecord := atRecord + 1 - if viaProjection == 0 then errors := errors.push "no hint found through a projection record" - if atRecord == 0 then errors := errors.push "no hint found at a record" - let listed := input.ordered.map (·.1) - let distinct : Std.HashSet Address := listed.foldl (·.insert ·) {} - unless listed.size == ixe.consts.size && distinct.size == listed.size do - errors := errors.push s!"typing input lists {listed.size} records ({distinct.size} distinct) \ - of {ixe.consts.size}" - let (vErrors, vLines) := verdict "reader-fidelity and Source-ingress fixtures" ixe - let lines := #[s!"resource-hints: {refs.size} references, {viaProjection} hints through a projection \ - record, {atRecord} at a record; {listed.size} records listed"] ++ vLines - return (errors ++ vErrors, lines) + | .error e => return (#[s!"compileCertified failed: {e}"], #[]) + return verdict "reader-fidelity and Source-ingress fixtures" out.env def compiledVerdicts : IO (Array String × Array String) := do let mut errors : Array String := #[] @@ -134,10 +75,10 @@ def compiledVerdicts : IO (Array String × Array String) := do let some syntaxIdx := leanEnv.getModuleIdx? syntaxModule | throw (IO.userError "fixture module not loaded") let seeds := leanEnv.constants.toList.filterMap fun (n, _) => if leanEnv.getModuleIdxFor? n == some syntaxIdx then some n else none - match Ix.Compile.compileInputFromEnv leanEnv (Ix.EnvScope.collectSelectedDeps leanEnv seeds) with - | .error e => errors := errors.push s!"source-contract syntax fixture: input: {e}" - | .ok input => - match ← Ix.CompileM.compileLeanInput input (numWorkers := 4) with + match Ix.CompileM.prepareCertified leanEnv (Ix.EnvScope.collectSelectedDeps leanEnv seeds) with + | .error e => errors := errors.push s!"source-contract syntax fixture: prepare: {e}" + | .ok (prepared, _) => + match ← Ix.CompileM.compileCertifiedPrepared prepared false 4 with | .error e => errors := errors.push s!"source-contract syntax fixture: compile: {e}" | .ok out => let (es, ls) := verdict "source-contract syntax fixture" out.env @@ -147,7 +88,7 @@ def compiledVerdicts : IO (Array String × Array String) := do match Tests.ContractPipeline.input with | .error e => errors := errors.push s!"contract pipeline: input: {e}" | .ok input => - match ← Ix.CompileM.compileLeanInput input (numWorkers := 1) with + match ← Tests.ContractPipeline.compileInput input with | .error e => errors := errors.push s!"contract pipeline: compile: {e}" | .ok out => let (es, ls) := verdict "contract pipeline (ContractA)" out.env @@ -157,7 +98,7 @@ def compiledVerdicts : IO (Array String × Array String) := do let tierModule := `Tests.Ix.Kernel.TierIndDefs let leanEnv ← getCompileEnv #[tierModule] let consts ← closureOf leanEnv #[tierModule] - match ← Ix.CompileM.compileLeanConsts consts (numWorkers := 4) with + match ← Ix.CompileM.compileCertified leanEnv consts 4 with | .error e => errors := errors.push s!"kernel-tier-ind fixtures: compile: {e}" | .ok out => let (es, ls) := verdict "kernel-tier-ind fixtures (unsafe inductives)" out.env @@ -173,7 +114,7 @@ def report (name : String) (action : IO (Array String × Array String)) : TestSe return (errors.isEmpty, 1, 0, msg)) .done def suite : List TestSeq := [ - report "the typing input's hints and coverage, and its verdict, on the compiled fixtures" hintsAndCoverage, + report "the typing half's verdict on the compiled fixtures" fixtureVerdict, report "actual typing verdicts: a contract input, an assumption axiom, unsafe inductives" compiledVerdicts] diff --git a/Tests/Ix/RoleArtifactConsumer.lean b/Tests/Ix/RoleArtifactConsumer.lean index c757164cc..c21d9c4fb 100644 --- a/Tests/Ix/RoleArtifactConsumer.lean +++ b/Tests/Ix/RoleArtifactConsumer.lean @@ -29,6 +29,189 @@ private def withTier (env : Env) (proof : Constant) : Except String (Env × Addr let (env, address) := insert env projection return (env, owner, address) +/-- Publish `name` at `address`. -/ +private def publish (env : Env) (name : String) (address : Address) : Env := + let n := _root_.Ix.Name.mkStr _root_.Ix.Name.mkAnon name + { env with named := env.named.insert n { addr := address }, names := RawEnv.addNameComponents env.names n } + +/-- `axiom cheat : False` and `theorem useCheat : False := cheat`, both published: +a user axiom, which strict checking declines, and a constant that depends on it. -/ +private def withCheat (env : Env) (falseAddress : Address) : Env × Address × Address := + let cheat : Constant := { + info := .axio ⟨false, 0, .ref 0 #[]⟩, sharing := #[], refs := #[falseAddress], univs := #[] } + let (env, cheatAddress) := insert env cheat + let useCheat : Constant := { + info := .defn ⟨.thm, .safe, 0, .ref 0 #[], .ref 1 #[]⟩, sharing := #[] + refs := #[falseAddress, cheatAddress], univs := #[] } + let (env, useAddress) := insert env useCheat + (publish (publish env "cheat" cheatAddress) "useCheat" useAddress, cheatAddress, useAddress) + +/-- `axiom badCheat : False False`, published: a user axiom that reads, but +whose type does not check (`False` is not a function). -/ +private def withBadCheat (env : Env) (falseAddress : Address) : Env × Address := + let bad : Constant := { + info := .axio ⟨false, 0, .app (.ref 0 #[]) (.ref 0 #[])⟩, sharing := #[], refs := #[falseAddress] + univs := #[] } + let (env, address) := insert env bad + (publish env "badCheat" address, address) + +/-- The user-axiom path: strict by default, `--allow-user-axioms` conditional, +listed and without a host statement, and a binding that leaves `sorryAx` +unbound refused. Library entry points first, then the `ix` and +`kernel-check-ixe` binaries over every driver path, the read cache's included. -/ +private def userAxiomChecks (env tierEnv : Env) (generated : _root_.Ix.RoleBinding.Generated) + (ix kernel dir : System.FilePath) : IO Nat := do + let cfg : CheckCfg := { silent := true, progressMs := 0, slowMs := 0 } + let some falseAddress := generated.table.addressOf? .false | throw (IO.userError "fixture has no False") + let (cheatEnv, cheatAddress, useAddress) := withCheat env falseAddress + let (tierCheatEnv, _, _) := withCheat tierEnv falseAddress + let (badCheatEnv, badAddress) := withBadCheat cheatEnv falseAddress + let table := generated.table + let noSorryTable : _root_.Ix.Kernel.RoleTable := + { table with bound := table.bound.filter (·.1 != _root_.Ix.Kernel.Role.sorryAx) } + let noSorryBytes := noSorryTable.toBytes + let noSorryEnv := { cheatEnv with roleBinding := some ⟨Address.admissionHash noSorryBytes, noSorryBytes⟩ } + let bytes ← IO.ofExcept (serEnv env) + let cheatBytes ← IO.ofExcept (serEnv cheatEnv) + let badCheatBytes ← IO.ofExcept (serEnv badCheatEnv) + let tierCheatBytes ← IO.ofExcept (serEnv tierCheatEnv) + let noSorry ← IO.ofExcept (serEnv noSorryEnv) + for anon in [false, true] do + let strict ← checked (← prepareInput cheatBytes anon) + let r ← checked (← runPrepared strict cfg .all) + ensure (!r.failures.isEmpty && r.artifactAdmission.isNone && r.conditionalOn.isEmpty && + runExit r true == _root_.Ix.Benchmark.Results.exitRejected) + "strict checking admitted a user axiom" + let p ← checked (← prepareInput cheatBytes anon (allowUserAxioms := true)) + let r ← checked (← runPrepared p cfg .all) + ensure (r.failures.isEmpty && r.hostStatement.isNone && + r.artifactAdmission == some (.conditional cheatEnv.consts.size none #[cheatAddress]) && + runExit r true == exitConditional && runExit r true true == _root_.Ix.Benchmark.Results.exitUsage && + runStatus r true == "conditional") + s!"the conditional whole run is not reported as conditional on cheat: {repr r.artifactAdmission}" + let r ← checked (← runPrepared p cfg (.only #[if anon then toString useAddress else "useCheat"])) + ensure (r.failures.isEmpty && r.userAxioms == #[cheatAddress] && runExit r false == exitConditional && + runStatus r false == "conditional") + "a selected run through a user axiom is not conditional on it" + -- a user axiom the run reads and then rejects admits nothing: the run lists + -- only the user axioms whose records it accepted (`admittedUserAxioms`) + let pBad ← checked (← prepareInput badCheatBytes anon (allowUserAxioms := true)) + let r ← checked (← runPrepared pBad cfg (.only (if anon then #[toString useAddress, toString badAddress] + else #["useCheat", "badCheat"]))) + let bad := r.targets.filter (·.target.address == badAddress) + ensure (bad.size == 1 && bad.all (fun t => t.outcome == "reject" && !t.reason.startsWith "reader:") && + r.failures.size == 1 && r.userAxioms == #[cheatAddress] && runStatus r false == "rejected") + s!"a rejected user axiom was listed as admitted: {r.userAxioms}, {bad.map (·.reason)}" + let natDiv := if anon then toString (generated.table.addressOf? .natDiv).get! else "Nat.div" + let r ← checked (← runPrepared p cfg (.only #[natDiv])) + ensure (r.failures.isEmpty && r.userAxioms.isEmpty && runExit r false == 0) + "a selected run that reads no user axiom became conditional" + -- with no user axiom among the declarations, the one run under the flag is + -- the certified entry's (T1, `ReceiptConditional.toReceipt`): the same + -- verdict and host statement as a strict run + let strict ← checked (← runPrepared (← checked (← prepareInput bytes anon)) cfg .all) + let p ← checked (← prepareInput bytes anon (allowUserAxioms := true)) + let r ← checked (← runPrepared p cfg .all) + ensure (r.artifactAdmission == some (.logical env.consts.size) && r.hostStatement.isSome && + r.hostStatement == strict.hostStatement && r.artifactAdmission == strict.artifactAdmission && + runExit r true true == 0) "the flag replaced the certified entry on an artifact without user axioms" + match ← prepareInput noSorry anon (allowUserAxioms := true) with + | .error (.artifact message) => + ensure (message.contains "leaves sorryAx unbound") s!"wrong refusal: {message}" + | _ => throw (IO.userError "a binding without sorryAx admitted user axioms") + let p ← checked (← prepareInput tierCheatBytes true (allowUserAxioms := true)) + let r ← checked (← runPrepared p cfg .all) + ensure (r.artifactAdmission == some (.conditional tierCheatEnv.consts.size (some 1) #[cheatAddress])) + s!"the tiered conditional entry did not run: {repr r.artifactAdmission}" + -- and the tiered entry with no user axiom: the certified tiered receipt + -- (`TierReceiptConditional.toTierReceipt`) and the strict run's statement + let tierBytes ← IO.ofExcept (serEnv tierEnv) + let strict ← checked (← runPrepared (← checked (← prepareInput tierBytes true)) cfg .all) + let r ← checked (← runPrepared (← checked (← prepareInput tierBytes true (allowUserAxioms := true))) cfg .all) + ensure (r.artifactAdmission == some (.tiered tierEnv.consts.size 1) && + r.artifactAdmission == strict.artifactAdmission && r.hostStatement.isSome && + r.hostStatement == strict.hostStatement) + s!"the flag replaced the certified tiered entry: {repr r.artifactAdmission} vs {repr strict.artifactAdmission}" + -- meta mode checks the published names against the binding even when strict + match ← prepareInput noSorry with + | .error (.artifact message) => + ensure (message.contains "role binding") s!"wrong meta refusal: {message}" + | _ => throw (IO.userError "a published sorryAx left unbound passed meta preparation") + let cheatPath := dir / "cheat.ixe" + let tierCheatPath := dir / "tier-cheat.ixe" + let noSorryPath := dir / "no-sorry.ixe" + let boundPath := dir / "bound-axioms.ixe" + let unused := dir / "conditional.ixhc" + IO.FS.writeBinFile cheatPath cheatBytes + IO.FS.writeBinFile tierCheatPath tierCheatBytes + IO.FS.writeBinFile noSorryPath noSorry + IO.FS.writeBinFile boundPath bytes + let vars := #[("IX_KERNEL_CHECK_PROGRESS_MS", some "0"), ("IX_KERNEL_CHECK_SLOW_MS", some "0")] + let listed := s!"[check-lean] {cheatAddress}" + let mut calls := 0 + for (file, args, code, texts) in [ + (cheatPath, #[], 3, #[]), + (cheatPath, #["--anon"], 3, #[]), + (cheatPath, #["--allow-user-axioms"], exitConditional, + #["whole artifact: CONDITIONAL on 1 user axioms:", s!"{listed} cheat", "no host statement"]), + (cheatPath, #["--anon", "--allow-user-axioms"], exitConditional, + #["whole artifact: CONDITIONAL on 1 user axioms:", listed]), + (cheatPath, #["--allow-user-axioms", "--consts", "useCheat"], exitConditional, + #["selected checking: CONDITIONAL on 1 user axioms:", s!"{listed} cheat"]), + (tierCheatPath, #["--anon", "--allow-user-axioms"], exitConditional, + #["whole artifact: CONDITIONAL on 1 user axioms:", "conditional logical entry and typing tier"]), + (cheatPath, #["--allow-user-axioms", "--host-out", unused.toString], 2, + #["CONDITIONAL on 1 user axioms and issues no host statement"]), + (boundPath, #["--allow-user-axioms"], 0, #["whole artifact: logical entry accepted all"]), + (noSorryPath, #["--anon", "--allow-user-axioms"], 3, #["leaves sorryAx unbound"]), + (noSorryPath, #[], 3, #["role binding"])] do + calls := calls + 1 + let out ← IO.Process.output { cmd := ix.toString, args := #["check-lean", file.toString] ++ args, env := vars } + let text := out.stdout ++ out.stderr + ensure (out.exitCode == code && texts.all (fun t => text.contains t) && + (code == 0 || !out.stdout.contains "accepted all")) + s!"user-axiom CLI control {args} failed: {out.exitCode}\n{out.stdout}\n{out.stderr}" + ensure (!(← unused.pathExists)) "a conditional run wrote a host statement" + -- kernel-check-ixe: strict by default and conditional with the flag, on the + -- streamed, eager, pooled and read-cache paths alike + let rowsOf := fun (rowsPath : System.FilePath) => do + let mut rows : Std.HashMap String String := {} + for line in (← IO.FS.readFile rowsPath).splitOn "\n" do + if line.isEmpty then continue + let row ← IO.ofExcept (Lean.Json.parse line) + rows := rows.insert (← IO.ofExcept (row.getObjValAs? String "address")) + (← IO.ofExcept (row.getObjValAs? String "outcome")) + return rows + let cache := dir / "read-cache" + for (args, cached) in [(#["--load", "stream"], false), (#["--load", "eager", "--jobs", "2"], false), + (#["--jobs", "2"], true), (#[], true), (#["--load", "eager"], true)] do + for allow in [true, false] do + calls := calls + 1 + let rowsPath := dir / "cheat.jsonl" + let result ← IO.Process.output { + cmd := kernel.toString + args := #[cheatPath.toString, rowsPath.toString] ++ args ++ (if allow then #["--allow-user-axioms"] else #[]) + env := #[("CHECK_IXE_ROOTS", none), ("CHECK_IXE_SKIP", none), + ("CHECK_IXE_READ_CACHE", if cached then some cache.toString else none)] } + let rows ← rowsOf rowsPath + let label := s!"kernel-check-ixe {args} allow={allow}" + ensure (result.exitCode == 0 && rows.contains (toString cheatAddress) && rows.contains (toString useAddress)) + s!"{label} failed: {result.exitCode}\n{result.stderr}" + if allow then + ensure (rows.toList.all (·.2 == "accept") && + result.stderr.contains s!"CONDITIONAL on 1 user axioms:\n {cheatAddress}") + s!"{label} did not admit and list the user axiom\n{result.stderr}" + else + ensure (rows[toString cheatAddress]? != some "accept" && rows[toString useAddress]? != some "accept" && + !result.stderr.contains "CONDITIONAL") + s!"{label} admitted a user axiom while strict\n{result.stderr}" + ensure (← cache.pathExists) "the read cache was never written" + let refused ← IO.Process.output { + cmd := kernel.toString, args := #[noSorryPath.toString, (dir / "no-sorry.jsonl").toString, "--allow-user-axioms"] } + ensure (refused.exitCode == 2 && refused.stderr.contains "leaves sorryAx unbound") + s!"kernel-check-ixe admitted user axioms under a binding without sorryAx\n{refused.stderr}" + return calls + 1 + private def orderedChecks (env tierEnv badEnv : Env) : IO Unit := do let prepared ← IO.ofExcept ((prepareRecords env artifactLimits).mapError toString) let order := (prepared.prepared.records.map Prod.fst).toArray @@ -193,12 +376,12 @@ private def checks : IO Nat := do let sorted := rows.qsort (fun a b => a.1 < b.1) if let some earlier := previous then ensure (earlier == sorted) "stream/eager bound diagnostic rows differ" previous := some sorted - return 7 + return 7 + (← userAxiomChecks env tierEnv generated ix kernel dir) finally IO.FS.removeDirAll dir public def units : LSpec.TestSeq := - .individualIO "bound artifact consumers: whole logical/tier entries and actual CLI routing" none (do + .individualIO "bound artifact consumers: whole logical/tier entries, the user-axiom path and actual CLI routing" none (do let count ← checks return (true, count, 0, none)) .done diff --git a/Tests/Ix/RoleCompile.lean b/Tests/Ix/RoleCompile.lean index 323b768ce..020190d95 100644 --- a/Tests/Ix/RoleCompile.lean +++ b/Tests/Ix/RoleCompile.lean @@ -5,6 +5,10 @@ public import Ix.RoleBinding.ArtifactTiered public import Ix.Cli.CompileCmd public import LSpec +/-! + Artifact role-binding compilation tests. +-/ + namespace Tests.Ix.RoleCompile private def require (ok : Bool) (message : String) : IO Unit := @@ -41,11 +45,10 @@ private def check : IO Bool := IO.FS.withTempDir fun dir => do let source := s!"Tests/Fixtures/{fixture}.lean" let result ← IO.Process.output { cmd := executable.toString - args := #["compile-lean", source, "--local", "--workers", "4", "--rust-check", "--out", path.toString] + args := #["compile-lean", source, "--local", "--workers", "4", "--out", path.toString] env := #[("LD_LIBRARY_PATH", none)] } require (result.exitCode == 0) s!"actual artifact compilation failed:\n{result.stdout}\n{result.stderr}" - require (result.stdout.contains "ALIGNED:") "the actual Rust/Lean artifact comparison did not run" let bytes ← IO.FS.readBinFile path let artifact ← IO.ofExcept (Ixon.deEnv bytes) require (bytes[0]? == some 0xE5 && artifact.roleBinding.isSome) "production output has no v5 binding" @@ -68,25 +71,25 @@ private def check : IO Bool := IO.FS.withTempDir fun dir => do require (artifact.named.contains (Ix.Name.fromLeanName `Nat.div.go._unsafe_rec)) "the original partial companion was removed to make logical-only checking pass" for anon in [false, true] do - let rustPath := dir / (if anon then "rust-anon.ixe" else "rust-named.ixe") - let args := #["compile", source, "--local", "--no-build", "--rust", "--out", rustPath.toString] + let outPath := dir / (if anon then "cert-anon.ixe" else "cert-named.ixe") + let args := #["compile", source, "--local", "--no-build", "--out", outPath.toString] let result ← IO.Process.output { cmd := executable.toString args := if anon then args.push "--anon" else args } - require (result.exitCode == 0) s!"Rust production artifact failed:\n{result.stdout}\n{result.stderr}" - let rustBytes ← IO.FS.readBinFile rustPath - let rustArtifact ← IO.ofExcept (Ixon.deEnv rustBytes) - checkArtifact rustArtifact roots + require (result.exitCode == 0) s!"production artifact failed:\n{result.stdout}\n{result.stderr}" + let outBytes ← IO.FS.readBinFile outPath + let outArtifact ← IO.ofExcept (Ixon.deEnv outBytes) + checkArtifact outArtifact roots if anon then -- The wire grammar always carries the anonymous name at index zero. - let onlyAnonymous := rustArtifact.names.size == 1 && rustArtifact.names.toList.all + let onlyAnonymous := outArtifact.names.size == 1 && outArtifact.names.toList.all (fun (_, name) => match name with | .anonymous _ => true | _ => false) - require (rustArtifact.named.isEmpty && onlyAnonymous && rustArtifact.comms.isEmpty) + require (outArtifact.named.isEmpty && onlyAnonymous && outArtifact.comms.isEmpty) "strict anonymous publication retained named metadata" else - require (rustBytes == bytes) - s!"Rust named publication differs from Lean: {rustBytes.size} versus {bytes.size} bytes" + require (outBytes == bytes) + s!"named publication differs from compile-lean: {outBytes.size} versus {bytes.size} bytes" IO.println s!"artifact compilation: {fixture}, {artifact.consts.size} records, {bytes.size} bytes, logical roots checked, all records covered and bytes aligned" let empty ← Lean.mkEmptyEnvironment require (!(Ix.RoleBinding.Compile.supportInput empty {}).isOk) diff --git a/Tests/Ix/RustCompile.lean b/Tests/Ix/RustCompile.lean deleted file mode 100644 index b975e37d4..000000000 --- a/Tests/Ix/RustCompile.lean +++ /dev/null @@ -1,87 +0,0 @@ -/- - `rust-compile`: the Rust compiler's compile → serialize → decompile - roundtrip over the test binary's ordinary declarations. Annotated fixtures - and their two harness wrappers are explicitly excluded; all remaining - input must be grounded and covered by the roundtrip. --/ -import Ix.Compile.SourceContract -import Lean - -namespace Tests.Ix.RustCompile - -/-- Runs the full compile → serialize → decompile roundtrip over the given - constants and returns the number of failures (0 = clean): decompile - mismatches, or a non-zero constant count if a phase aborted. -/ -@[extern "rs_tmp_decode_const_map"] -opaque tmpDecodeConstMap : @& List (Lean.Name × Lean.ConstantInfo) → USize - -structure Selection where - constants : List (Lean.Name × Lean.ConstantInfo) - total : Nat - sourceContractExcluded : Nat - -/-- The whole test input, excluding annotated fixtures and their two registry -wrappers. No other ungrounded declaration may be silently skipped by the gate. -/ -def selectInput (env : Lean.Environment) : Except String Selection := do - let all := env.constants.toList - -- Source binder annotations are the subject of the `source-contract` - -- suite, whose modules (`Tests.Ix.SourceContract.*`) declare annotated - -- fixtures; the Rust compiler's Phase 1 refuses them for the whole input. - -- Those modules are left out of this roundtrip, and an annotated constant - -- of any other module fails the suite rather than being dropped. - let sourceContract := `Tests.Ix.SourceContract - let moduleNames := env.allImportedModuleNames - let inSourceContract (n : Lean.Name) : Bool := - match env.getModuleIdxFor? n with - | some i => match moduleNames[i]? with - | some m => sourceContract.isPrefixOf m - | none => false - | none => false - -- Tests.Main's registry mentions the excluded suite and main mentions that - -- registry. They were previously dropped by grounding, while the roundtrip - -- omitted ungrounded inputs from its comparison. Name and module must both - -- match: this does not exclude another input's ordinary `main` declaration. - let harness : Array Lean.Name := #[`registry, `main] - for name in harness do - let some idx := env.getModuleIdxFor? name - | throw s!"[rust-compile] missing Tests.Main harness declaration {name}" - unless moduleNames[idx]? == some `Tests.Main do - throw s!"[rust-compile] {name} is not a Tests.Main harness declaration" - let withoutContracts := all.filter fun (n, _) => !inSourceContract n - let consts := withoutContracts.filter fun (n, _) => !harness.contains n - let annotated := consts.filter fun (_, c) => Ix.Compile.sourceHasAnnotations c - unless annotated.isEmpty do - throw s!"[rust-compile] FAILED: {annotated.length} constant(s) outside \ - {sourceContract}.* carry source binder annotations: \ - {(annotated.take 10).map (·.1)}" - return ⟨consts, all.length, all.length - withoutContracts.length⟩ - -def run (env : Lean.Environment) : IO UInt32 := do - let selection ← IO.ofExcept (selectInput env) - IO.println s!"Loaded environment with {selection.total} constants" - IO.println s!"[rust-compile] left out {selection.sourceContractExcluded} constants of \ - Tests.Ix.SourceContract.* and exactly Tests.Main's registry/main wrappers" - let failures := tmpDecodeConstMap selection.constants - if failures != 0 then - IO.eprintln s!"[rust-compile] FAILED: {failures} compile/decompile roundtrip failure(s)" - return 1 - IO.println "[rust-compile] OK: compile → decompile roundtrip clean" - return 0 - -/-- Exercise the real gate with a valid closed declaration and a missing -dependency. The latter must not disappear behind the grounding filter. -/ -def coverageControl : IO UInt32 := do - let validName := `Tests.Ix.RustCompile.Coverage.valid - let invalidName := `Tests.Ix.RustCompile.Coverage.invalid - let mk (name : Lean.Name) (type : Lean.Expr) : Lean.ConstantInfo := - .axiomInfo { name, levelParams := [], type, isUnsafe := false } - let valid := tmpDecodeConstMap [(validName, mk validName (.sort (.succ .zero)))] - let invalid := tmpDecodeConstMap [(invalidName, - mk invalidName (.const `Tests.Ix.RustCompile.Coverage.missing []))] - unless valid == 0 && invalid != 0 do - IO.eprintln s!"[rust-compile-coverage] expected closed=0 and missing-dependency>0, got {valid}/{invalid}" - return 1 - IO.println "[rust-compile-coverage] closed declaration passes; missing dependency fails the actual gate" - return 0 - -end Tests.Ix.RustCompile diff --git a/Tests/Ix/RustDecompile.lean b/Tests/Ix/RustDecompile.lean deleted file mode 100644 index 4222ddcad..000000000 --- a/Tests/Ix/RustDecompile.lean +++ /dev/null @@ -1,43 +0,0 @@ -/- - Rust decompile roundtrip test. - - Exercises `Lean env → compile → serialize → deserialize → decompile → - Lean` — the `ix decompile` pipeline over the serialized `.ixe` - boundary (demoted-at-parse metadata load, `Named.original` recovery - for shape-divergent aux blocks, expression interning) — and - hash-compares every constant against the original. - `kernel-ixon-roundtrip` covers the kernel ingress/egress leg instead. --/ -import Ix.Common -import Ix.Meta -import LSpec - -open LSpec - -namespace Tests.RustDecompile - -/-- FFI: run the serialized decompile roundtrip and collect per-constant - diff messages. Empty array = decompile agrees with the original Lean - env. - - Implemented in `crates/ffi/src/kernel.rs::rs_decompile_roundtrip`. -/ -@[extern "rs_decompile_roundtrip"] -opaque rsDecompileRoundtripFFI : - @& List (Lean.Name × Lean.ConstantInfo) → IO (Array String) - -def testRoundtrip : TestSeq := - .individualIO "rust decompile roundtrip" none (do - let leanEnv ← get_env! - let errors ← rsDecompileRoundtripFFI leanEnv.constants.toList - if errors.isEmpty then - return (true, 0, 0, none) - else - IO.println s!"[rust-decompile] {errors.size} errors:" - for msg in errors[:min 20 errors.size] do - IO.println s!" {msg}" - return (false, 0, 0, some s!"{errors.size} roundtrip mismatches") - ) .done - -def rustDecompileSuiteIO : List TestSeq := [testRoundtrip] - -end Tests.RustDecompile diff --git a/Tests/Ix/RustSerialize.lean b/Tests/Ix/RustSerialize.lean deleted file mode 100644 index 92ba6a9e8..000000000 --- a/Tests/Ix/RustSerialize.lean +++ /dev/null @@ -1,100 +0,0 @@ -/- - Rust serialization/deserialization tests. - Tests the Rust FFI endpoints for Ixon.Env serialization and deserialization - by roundtripping through Rust and comparing with the Lean serializer. --/ - -module -public import Ix.Ixon -public import Ix.Common -public import Ix.Meta -public import Ix.CompileM -public import Lean -public import LSpec -public import Tests.Ix.Fixtures - -open LSpec - -namespace Tests.RustSerialize - -/-- Test Rust serde roundtrip: compile → rsSerEnv → rsDeEnv → Lean serEnv → byte compare -/ -def testRustSerdeRoundtrip : TestSeq := - .individualIO "Rust Serialize/Deserialize Roundtrip" none (do - let leanEnv ← get_env! - let totalConsts := leanEnv.constants.toList.length - - IO.println s!"[Test] Rust Serialize/Deserialize Roundtrip Test" - IO.println s!"[Test] Environment has {totalConsts} constants" - IO.println "" - - -- Step 1: Compile with Rust to get an Ixon.Env - IO.println s!"[Step 1] Running Rust compilation pipeline..." - let compileStart ← IO.monoMsNow - let ixonEnv ← Ix.CompileM.rsCompileEnv leanEnv - let compileTime := (← IO.monoMsNow) - compileStart - IO.println s!"[Step 1] Compiled: {ixonEnv.constCount} constants in {compileTime}ms" - IO.println "" - - -- Step 2: Canonical Lean serialization (deterministic, sorted by key) - IO.println s!"[Step 2] Serializing with Lean (serEnv)..." - let leanSerStart ← IO.monoMsNow - let leanBytes ← IO.ofExcept (Ixon.serEnv ixonEnv) - let leanSerTime := (← IO.monoMsNow) - leanSerStart - IO.println s!"[Step 2] {leanBytes.size} bytes in {leanSerTime}ms" - IO.println "" - - -- Step 3: Serialize with Rust - IO.println s!"[Step 3] Serializing with Rust (rsSerEnv)..." - let rustSerStart ← IO.monoMsNow - let rustBytes ← IO.ofExcept (Ixon.rsSerEnv ixonEnv) - let rustSerTime := (← IO.monoMsNow) - rustSerStart - IO.println s!"[Step 3] {rustBytes.size} bytes in {rustSerTime}ms" - IO.println "" - - -- Step 4: Deserialize Rust bytes with Rust - IO.println s!"[Step 4] Deserializing Rust bytes with Rust (rsDeEnv)..." - let rustDesStart ← IO.monoMsNow - let roundtrippedFromRust ← match Ixon.rsDeEnv rustBytes with - | .ok env => pure env - | .error e => do - IO.println s!"[Step 4] FAILED: {e}" - return (false, 0, 0, some e) - let rustDesTime := (← IO.monoMsNow) - rustDesStart - IO.println s!"[Step 4] {roundtrippedFromRust.constCount} constants in {rustDesTime}ms" - IO.println "" - - -- Step 5: Re-serialize the roundtripped env with Lean (deterministic) - IO.println s!"[Step 5] Re-serializing roundtripped env with Lean..." - let reserStart ← IO.monoMsNow - let roundtrippedBytes ← IO.ofExcept (Ixon.serEnv roundtrippedFromRust) - let reserTime := (← IO.monoMsNow) - reserStart - IO.println s!"[Step 5] {roundtrippedBytes.size} bytes in {reserTime}ms" - IO.println "" - - -- Step 6: Byte-exact comparison - IO.println s!"[Step 6] Comparing Lean serialization vs roundtripped..." - if leanBytes == roundtrippedBytes then - IO.println s!"[Step 6] Byte-exact match! ({leanBytes.size} bytes) ✓" - IO.println "" - return (true, 0, 0, none) - else - IO.println s!"[Step 6] MISMATCH: {leanBytes.size} bytes vs {roundtrippedBytes.size} bytes" - -- Find first diff - let minLen := min leanBytes.size roundtrippedBytes.size - let mut firstDiff := minLen - for i in [:minLen] do - if leanBytes.get! i != roundtrippedBytes.get! i then - firstDiff := i - break - IO.println s!"[Step 6] First difference at byte {firstDiff}" - IO.println "" - return (false, 0, 0, some s!"Bytes differ at offset {firstDiff} (original {leanBytes.size} vs roundtripped {roundtrippedBytes.size})") - ) .done - -/-! ## Test Suite -/ - -public def rustSerializeSuiteIO : List TestSeq := [ - testRustSerdeRoundtrip, -] - -end Tests.RustSerialize diff --git a/Tests/Ix/SharingComponents.lean b/Tests/Ix/SharingComponents.lean index 30bb648b3..c69122937 100644 --- a/Tests/Ix/SharingComponents.lean +++ b/Tests/Ix/SharingComponents.lean @@ -121,9 +121,4 @@ def suiteOf (count : Nat) (wild : Bool) : List TestSeq := [ /-- The primary run: 15 generated inputs and parent arrays within range. -/ def suite : List TestSeq := suiteOf 15 false -/-- The full run, in a CI job: 40 generated inputs, a superset of the primary -ones, and parent arrays with pointers out of range, which the lookups report -as panics. -/ -def fullSuite : List TestSeq := suiteOf 40 true - end Tests.SharingComponents diff --git a/Tests/Ix/SharingContext.lean b/Tests/Ix/SharingContext.lean deleted file mode 100644 index 75e8d3f37..000000000 --- a/Tests/Ix/SharingContext.lean +++ /dev/null @@ -1,168 +0,0 @@ -import Tests.Ix.SharingExact -import Ix.Sharing.FamilyContext -import Tests.Ix.SharingScratch -import Lean.Compiler.CSimpAttr - -set_option compiler.extract_closed false - -namespace Tests.SharingContext - -open LSpec Ixon Ix.Sharing.Exact Ix.Sharing.Family Tests.SharingExact - -run_cmd do - let entries := (Lean.Compiler.CSimp.ext.getState (← Lean.getEnv)).map - let replacements : Array (Lean.Name × Lean.Name) := #[ - (``familyReferenceExpanded, ``LightContext.expanded), - (``familyExpanded, ``LightContext.expanded), - (``familyExpandedArea, ``LightContext.expanded), - (``normalizeFamily, ``LightContext.normalize), - (``normalizeFamilyArea, ``LightContext.normalize)] - for (source, target) in replacements do - let some entry := entries.find? source - | Lean.throwError m!"missing sharing runtime replacement for {source}" - unless entry.toDeclName == target do - Lean.throwError m!"sharing runtime replacement for {source} is {entry.toDeclName}, expected {target}" - -deriving instance BEq for CompResult -deriving instance BEq for ExactSharingResult -deriving instance BEq for UniformSharingResult -deriving instance BEq for TieredStats -deriving instance BEq for TieredSharingResult - -private def ensure (condition : Bool) (message : String) : IO Unit := - unless condition do throw (IO.userError message) - -private def sameOutcome [BEq α] : Except SharingError α → Except SharingError α → Bool - | .ok a, .ok b => a == b - | .error a, .error b => a == b - | _, _ => false - -/-- Keep the old native evaluator below the replaced entry: this call uses -the unchanged `familyAtWidthArea`, so csimp cannot turn both sides into the twin. -/ -private def originalNormalize (cfg : Config) (constant : Constant) (limits : Limits) : - Except SharingError (Constant × TieredSharingResult) := do - let ex ← expand limits constant.sharing (constantInfoRoots constant.info) true - uniformDagChecks ex - let prep := Prep.ofDag ex.dag - let facts := graphFacts ex.dag ex.roots - let parents := parentsOf ex.dag - let candidates := tieredCandidateCount ex.dag.size prep facts - let result ← tieredCandidates ex.dag.size - (familyAtWidthArea cfg .tagN limits ex prep facts parents candidates) - let info ← withRoots constant.info result.result.roots - return ({ constant with info, sharing := result.result.sharing }, withExpansionStats ex result) - -private def normalizationControls : IO Nat := do - let generated := runGen 1931 ((List.range 18).mapM fun _ => genRoots 6 6 4) - let roots := #[#[], #[.var 20, .var 20], #[.var 1048, .var 1048]] ++ generated.toArray - let mut tested := 0 - for (root, index) in roots.zipIdx do - let constant := wrapRoots root index - for cfg in [({ exactLimit := 0 } : Config), ⟨3⟩, canonicalFamilyConfig] do - for limits in [({} : Limits), { maxStates := 0 }, { maxCostEvals := 0 }, - { maxOutputBytes := 0 }, { maxMaterializeWork := 0 }, { maxNodes := 0 }, - { maxExprVisits := 0 }, { maxDepth := 0 }, { maxKnapsackCells := 0 }] do - let original := originalNormalize cfg constant limits - let candidate := normalizeFamily cfg .tagN constant limits - ensure (sameOutcome original candidate) "light context changed a result field, charge or refusal" - match original, candidate with - | .ok (before, _), .ok (after, _) => - ensure (serConstant before == serConstant after) "light context changed complete constant bytes" - | _, _ => pure () - tested := tested + 1 - let canonical := normalizeConstantSharingTiered .tagN constant - let report := (normalizeFamily canonicalFamilyConfig .tagN constant).map (·.1) - ensure (sameOutcome canonical report) "canonical and parameterized context entries disagree" - let malformed := { constant with sharing := #[.share 0] } - ensure (sameOutcome (originalNormalize canonicalFamilyConfig malformed {}) - (normalizeFamily canonicalFamilyConfig .tagN malformed)) "backward-sharing refusal changed" - return tested - -/-- These altered contexts test defensive branches and internal counters; -they are not claims that the corresponding values are admitted artifacts. -/ -private def componentControls : IO Nat := do - let shared : Expr := .app (.var 20) (.var 1048) - let roots : Array Expr := #[.app shared (.var 2), .app shared (.var 3), .var 20] - let ex ← IO.ofExcept ((expand {} #[] roots false).mapError toString) - let prep := Prep.ofDag ex.dag - let facts := graphFacts ex.dag ex.roots - let parents := parentsOf ex.dag - let zeroArea := Array.replicate prep.dag.size 0 - let zeroClosure := Array.replicate ex.dag.size 0 - let mut tested := 0 - let mut eligible := 0 - let mut longerClosure := 0 - for width in #[1, 2, 3] do - let stage := uniformStageF width ex prep facts - let base := TBase.ofPrep stage.up.prep stage.up.w stage.up.opaq - for members in stage.comps do - let area := componentArea stage.up stage.f members - let positions := setPositions zeroArea area - let (marks, found, stack) := upWalk parents members zeroClosure - if members.all (fun m => decide (m < ex.dag.size) && !stage.up.opaq[m]!) && - areaOK ex.dag.size stage.up.opaq parents members area positions && - stack.isEmpty && area.all (fun y => marks[y]! != 0) && - localSearchOK (LightContext.context stage members area) then - eligible := eligible + 1 - if found.size > area.size then longerClosure := longerClosure + 1 - let shortCandidates := { stage with cand := #[] } - let opaqueMembers := - { stage with up := { stage.up with opaq := Array.replicate prep.dag.size true } } - let charged := familyWork canonicalFamilyConfig members.size found.size - let configurations : Array (Limits × MeterState) := - #[({}, (#[], 0, 0)), ({ maxStates := 0 }, (#[], 0, 0)), - ({ maxCostEvals := 0 }, (#[], 0, 0)), ({}, (#[default], 7, 11)), - ({ maxCostEvals := charged - 1 }, (#[], 0, 0)), - ({ maxCostEvals := charged }, (#[], 0, 0)), - ({ maxStates := 2 ^ 100, maxCostEvals := 2 ^ 100 }, (#[], 2 ^ 80, 2 ^ 80))] - for contextStage in #[stage, shortCandidates, opaqueMembers] do - for (limits, initial) in configurations do - let original := familyStepArea canonicalFamilyConfig limits ex contextStage width parents base - (initial, zeroArea, zeroClosure) members - let candidate := LightContext.step canonicalFamilyConfig limits ex contextStage width parents base - (initial, zeroArea, zeroClosure) members - ensure (sameOutcome original candidate) "context fallback changed counters or cleared buffers" - tested := tested + 1 - let full := LightContext.fullContext ex stage members - let cached := Thunk.mk fun _ => LightContext.fullContext ex stage members - let light := LightContext.context stage members #[] - let zeroInline := { base with inl := Array.replicate ex.dag.size 0 } - let aux : AAux := { csize := full.1.closure.size, K := 0, rootsA := #[], storedA := #[0] } - for stored in #[#[], members] do - let expected := phiEL2 full.1 zeroArea full.2 (fun _ => false) stored - let actual := phiA light zeroArea zeroInline aux (LightContext.forceContext cached) - (fun _ => false) stored - ensure (actual == expected) "memoized opacity fallback changed the complete cost/work pair" - tested := tested + 1 - ensure (eligible > 0) "context controls did not reach the checked area path" - ensure (longerClosure > 0) "context controls did not distinguish closure work from area work" - return tested - -private def scratchControls : IO Nat := do - let mut tested := 0 - for depth in [8, 15] do - let prepared ← IO.ofExcept ((Tests.SharingScratch.prepare 4 depth).mapError toString) - ensure (prepared.stage.comps.size == 4 && prepared.stage.comps.all (·.size == 12)) - "scratch fixture lost its twelve-member components" - ensure (prepared.parallelComponents == if depth == 8 then 4 else 0) - "area scheduling did not preserve small-DAG parallelism and large-DAG buffer reuse" - for cfg in [({ exactLimit := 0 } : Config), ⟨16⟩, ⟨24⟩] do - for limits in [({} : Limits), { maxStates := 0 }, { maxCostEvals := 0 }, - { maxStates := 4096 }, { maxCostEvals := 200000 }] do - let expected := familyComponentsArea cfg limits prepared.expanded prepared.stage 3 prepared.parents - let actual := prepared.run cfg limits - ensure (sameOutcome actual expected) - "area task selection changed complete component rows, counters or refusals" - tested := tested + 1 - return tested - -def suite : List TestSeq := [ - .individualIO "checked light context preserves full results and exact refusals" none (do - let normalized ← normalizationControls - let components ← componentControls - return (true, normalized + components, 0, none)) .done, - .individualIO "area task selection preserves results and reuses large scratch buffers" none (do - let tested ← scratchControls - return (true, tested, 0, none)) .done] - -end Tests.SharingContext diff --git a/Tests/Ix/SharingExact.lean b/Tests/Ix/SharingExact.lean index 293763b6f..b14951bf7 100644 --- a/Tests/Ix/SharingExact.lean +++ b/Tests/Ix/SharingExact.lean @@ -1143,9 +1143,7 @@ def overrideFails (e : Except String Limits) : Bool := | .ok _ => false | .error _ => true -/-- Override specs and the `states` limit each sets (`none`: rejected). The -Rust test list `LIMIT_SPEC_PARITY` (`crates/ixon/src/sharing_exact/tests.rs`) -is the same list, so both parsers give every spec the same verdict. Values +/-- Override specs and the `states` limit each sets (`none`: rejected). Values are ASCII digits, `2^k` or `max` (no sign, no `_` separator, no other spelling); items and keys are trimmed of space, tab, line feed and carriage return only. -/ @@ -1209,10 +1207,9 @@ def limitTests (_ : Unit) : TestSeq := | .ok l => l.maxStates == 2048 && l.maxCostEvals == 12345 && l.maxOutputBytes == limitMax && l.maxDepth == d.maxDepth | .error _ => false : Bool) ++ - test "Rust-only keys are accepted and ignored" - (match d.withOverrides "height=5,work=2^3,layer_states=9,input_nodes=1,distinct_nodes=1,candidates=1" with - | .ok l => reprStr l == reprStr d - | .error _ => false : Bool) ++ + test "the retired Rust port's keys are unknown keys" + (["height=5", "work=2^3", "layer_states=9", "input_nodes=1", "distinct_nodes=1", + "candidates=1"].all fun s => overrideFails (d.withOverrides s)) ++ test "unbounded sets every production limit to max; a later item still applies" (match d.withOverrides "unbounded,states=2" with | .ok l => l.maxDepth == limitMax && l.maxMaterializeWork == limitMax && @@ -1221,7 +1218,7 @@ def limitTests (_ : Unit) : TestSeq := test "malformed overrides are errors" (["bogus=1", "states", "states=2^64", "states=-1", "states=1e3", "=3", "states=18446744073709551616"].all fun s => overrideFails (d.withOverrides s)) ++ - test s!"the {limitSpecParity.length} specs shared with Rust get the listed verdict" + test s!"the {limitSpecParity.length} grammar specs get the listed verdict" (limitSpecParity.length == 35 && limitSpecMismatches.isEmpty) ++ test "every production resource key names a limit" (resources.all fun r => (d.set? r.key 3).isSome) diff --git a/Tests/Ix/SharingExactFFI.lean b/Tests/Ix/SharingExactFFI.lean deleted file mode 100644 index 515c0c949..000000000 --- a/Tests/Ix/SharingExactFFI.lean +++ /dev/null @@ -1,636 +0,0 @@ -/- - Lean/Rust differential test of the exact sharing constructions. - - Every check normalizes one Constant twice: in Lean (`normalizeConstantSharing`, - `normalizeConstantSharingUniform`, `normalizeConstantSharingTiered` from - `Ix.Sharing.Exact`) and in Rust through the `test-ffi` hooks - `rs_exact_sharing_normalize`, `rs_uniform_sharing_normalize` and - `rs_tiered_sharing_normalize` (crate `ixon::sharing_exact`). The two sides - agree when both succeed with the same serialized bytes, or both fail with - the same error category (malformed / format / resource / internal). One - side exhausting a resource limit while the other succeeds is reported - separately: the two implementations count work differently, and limits - may change success but never the bytes of a success. These groups are - strict: every comparison must be a byte-identical success (the exact - search runs only on inputs with few candidates). - - The inputs of these groups: the §2 fixtures, 350 inputs of the generated - families of the uniform and tiered tests, the tiered outputs of the - fixtures and of generated inputs re-normalized from their stored tables, - the scale inputs of the tiered tests (a table past the 3-byte TagN rung, a - 1288-argument spine, 10000-deep inputs; uniform and tiered modes), and - doubling towers whose unshared lengths exceed `2^127` (tiered mode). - Malformed stored tables (forward, self and out-of-range references) must - be rejected as malformed by both sides, with the expected Lean error. - - A codec group checks the TagN Share codec: Lean and Rust serialize - Share-bearing Constants (tiered TagN outputs and synthetic tables up to - 66,600 entries) to the same bytes (`rs_eq_constant_serialization`). - - A compiler-route group checks the fourth hook, `rs_compiler_sharing_build` - (Rust `apply_sharing_to_*_with_limits`, crate `ix_compile`): it and Lean - `Ix.CompileM.buildConstantWithSharing` build the same bytes from the same - unshared Constant, equal to the tiered normalizer's. - - Modes: `tiered-tagN` is the canonical construction (the compiler's); - `uniform-w` is its phase-1 optimizer alone at Share width `k`; `exact` - is the width-state search for the global minimum, a test oracle for small - inputs (not the compiler path). - - Corpus mode: when `IX_SHARING_CORPUS` names an `.ixe` file, every constant - of that corpus (expanded from its stored table) is compared in the modes - listed in `IX_SHARING_CORPUS_MODES` (comma separated, default - `tiered-tagN`; also `uniform-w`, `exact`), only the constants whose - addresses are listed in the file `IX_SHARING_CORPUS_SELECT` when that is - set, and stops after `IX_SHARING_CORPUS_LIMIT` constants when that is set. - No CI job sets `IX_SHARING_CORPUS`. --/ -module - -public import Tests.Ix.SharingExact -public import Tests.Ix.SharingUniform -public import Tests.Ix.SharingTiered -public import Tests.FFI.Ixon - -public section - -open LSpec Ixon Ix.Sharing.Exact Tests.SharingExact - --- Groups are functions run from deferred IO actions. -set_option compiler.extract_closed false - -namespace Tests.SharingExactFFI - -/-! ## Rust entry points (`test-ffi`) -/ - -@[extern "rs_exact_sharing_normalize"] -opaque rsExactNormalize : @& ByteArray → Except String ByteArray - -@[extern "rs_uniform_sharing_normalize"] -opaque rsUniformNormalize : UInt64 → @& ByteArray → Except String ByteArray - -/-- The canonical tiered construction (TagN layout). -/ -@[extern "rs_tiered_sharing_normalize"] -opaque rsTieredNormalize : @& ByteArray → Except String ByteArray - -/-! ## Modes and outcomes -/ - -/-- A sharing construction compared across the two implementations. -/ -inductive Mode where - | exact - | uniform (w : Nat) - | tiered - deriving BEq, Repr, Inhabited - -def Mode.name : Mode → String - | .exact => "exact" - | .uniform w => s!"uniform-w{w}" - | .tiered => "tiered-tagN" - -def Mode.parse (s : String) : Option Mode := - if s == "exact" then some .exact - else if s == "tiered-tagN" then some .tiered - else if s.startsWith "uniform-w" then (String.ofList (s.toList.drop 9)).toNat?.map .uniform - else none - -/-- One side's result: the serialized Constant, or an error category with -its detail. -/ -inductive Outcome where - | ok (bytes : ByteArray) - | err (category detail : String) - deriving Inhabited - -def leanCategory : SharingError → String - | .shareInExpandedInput .. | .shareOutOfRange .. | .nonBackwardShare .. - | .rootCountMismatch .. => "malformed" - | .formatBound .. => "format" - | .resourceExhausted .. => "resource" - | .internal _ => "internal" - -def rustCategory (s : String) : String := - if s.startsWith "malformed sharing:" then "malformed" - else if s.startsWith "format bound:" then "format" - else if s.startsWith "resource exhausted:" then "resource" - else if s.startsWith "internal error:" then "internal" - else if s.startsWith "decode:" then "decode" - else "unknown" - -/-- The Lean construction under explicit limits. -/ -def leanNormalize (m : Mode) (c : Constant) (limits : Limits) : Except SharingError Constant := - match m with - | .exact => normalizeConstantSharing c limits - | .uniform w => normalizeConstantSharingUniform w c limits - | .tiered => normalizeConstantSharingTiered .tagN c limits - -@[noinline] def leanSide (m : Mode) (c : Constant) : Outcome := - match leanNormalize m c {} with - | .ok n => .ok (serConstant n) - | .error e => .err (leanCategory e) (reprStr e) - -@[noinline] def rustSide (m : Mode) (bytes : ByteArray) : Outcome := - let r := match m with - | .exact => rsExactNormalize bytes - | .uniform w => rsUniformNormalize w.toUInt64 bytes - | .tiered => rsTieredNormalize bytes - match r with - | .ok b => .ok b - | .error s => .err (rustCategory s) s - -/-- Index of the first differing byte (the shorter length if one is a -prefix of the other). -/ -def firstDiff (a b : ByteArray) : Nat := Id.run do - for i in [0:min a.size b.size] do - if a.get! i != b.get! i then return i - return min a.size b.size - -/-- Comparison of the two sides. -/ -inductive Verdict where - | sameBytes - | sameError (category : String) - /-- One side exhausted a resource limit, the other succeeded. -/ - | resourceOnly (leanFailed : Bool) - | differ (msg : String) - deriving Inhabited - -def compareOutcomes (lean rust : Outcome) : Verdict := - match lean, rust with - | .ok a, .ok b => - if a == b then .sameBytes - else .differ s!"bytes differ: Lean {a.size} B, Rust {b.size} B, first differing byte {firstDiff a b}" - | .err ca da, .err cb db => - if ca == cb then .sameError ca else .differ s!"error categories differ: Lean {ca} ({da}), Rust {cb} ({db})" - | .err "resource" _, .ok _ => .resourceOnly true - | .ok _, .err "resource" _ => .resourceOnly false - | .err ca da, .ok b => .differ s!"Lean error {ca} ({da}); Rust ok ({b.size} B)" - | .ok a, .err cb db => .differ s!"Lean ok ({a.size} B); Rust error {cb} ({db})" - -/-- Tallies of one batch of comparisons. -/ -structure Tally where - same : Nat := 0 - sameErr : Nat := 0 - resourceLean : Nat := 0 - resourceRust : Nat := 0 - differences : Array String := #[] - /-- Labels of same-category errors and of one-sided resource exhaustion. -/ - notes : Array String := #[] - leanNs : Nat := 0 - rustNs : Nat := 0 - deriving Inhabited - -def Tally.add (t : Tally) (label : String) : Verdict → Tally - | .sameBytes => { t with same := t.same + 1 } - | .sameError c => { t with sameErr := t.sameErr + 1, notes := t.notes.push s!"{label}: both {c}" } - | .resourceOnly true => { t with resourceLean := t.resourceLean + 1, notes := t.notes.push s!"{label}: Lean resource exhaustion, Rust ok" } - | .resourceOnly false => { t with resourceRust := t.resourceRust + 1, notes := t.notes.push s!"{label}: Rust resource exhaustion, Lean ok" } - | .differ msg => { t with differences := t.differences.push s!"{label}: {msg}" } - -def Tally.summary (t : Tally) : String := - s!"{t.same} same bytes, {t.sameErr} same error category, {t.resourceLean} Lean-only and {t.resourceRust} Rust-only resource exhaustion, {t.differences.size} disagreements" - -/-- Compare one Constant under one mode, timing both sides. -/ -def compareOne (m : Mode) (c : Constant) (bytes : ByteArray) : IO (Verdict × Nat × Nat) := do - let t0 ← IO.monoNanosNow - let l ← IO.lazyPure fun _ => leanSide m c - let t1 ← IO.monoNanosNow - let r ← IO.lazyPure fun _ => rustSide m bytes - let t2 ← IO.monoNanosNow - return (compareOutcomes l r, t1 - t0, t2 - t1) - -/-- A limit's name and value. -/ -def limitOf (l : Limits) : Resource → String × Nat - | .exprVisits => ("maxExprVisits", l.maxExprVisits) - | .depth => ("maxDepth", l.maxDepth) - | .nodes => ("maxNodes", l.maxNodes) - | .states => ("maxStates", l.maxStates) - | .transitions => ("maxTransitions", l.maxTransitions) - | .costEvals => ("maxCostEvals", l.maxCostEvals) - | .outputBytes => ("maxOutputBytes", l.maxOutputBytes) - | .materialize => ("maxMaterialize", l.maxMaterialize) - | .materializeWork => ("maxMaterializeWork", l.maxMaterializeWork) - | .oracleTables => ("maxOracleTables", l.maxOracleTables) - | .oracleVariants => ("maxOracleVariants", l.maxOracleVariants) - | .knapsackCells => ("maxKnapsackCells", l.maxKnapsackCells) - -/-- Double one limit. -/ -def doubleLimit (l : Limits) : Resource → Limits - | .exprVisits => { l with maxExprVisits := 2 * l.maxExprVisits } - | .depth => { l with maxDepth := 2 * l.maxDepth } - | .nodes => { l with maxNodes := 2 * l.maxNodes } - | .states => { l with maxStates := 2 * l.maxStates } - | .transitions => { l with maxTransitions := 2 * l.maxTransitions } - | .costEvals => { l with maxCostEvals := 2 * l.maxCostEvals } - | .outputBytes => { l with maxOutputBytes := 2 * l.maxOutputBytes } - | .materialize => { l with maxMaterialize := 2 * l.maxMaterialize } - | .materializeWork => { l with maxMaterializeWork := 2 * l.maxMaterializeWork } - | .oracleTables => { l with maxOracleTables := 2 * l.maxOracleTables } - | .oracleVariants => { l with maxOracleVariants := 2 * l.maxOracleVariants } - | .knapsackCells => { l with maxKnapsackCells := 2 * l.maxKnapsackCells } - -/-- For a Lean-only resource exhaustion: rerun Lean, doubling whichever -limit fires (at most 12 doublings in total), and report the limits that -fired, the values that sufficed, and whether the bytes then equal Rust's. -/ -def diagnose (m : Mode) (c : Constant) (rust : Outcome) : String := Id.run do - let mut limits : Limits := {} - let mut fired : Array String := #[] - let mut raised : Array Resource := #[] - for _ in [0:13] do - match leanNormalize m c limits with - | .ok n => - let same := match rust with - | .ok b => if serConstant n == b then "equal to Rust" else "DIFFERENT from Rust" - | .err .. => "Rust failed" - let final := raised.toList.map fun r => - let (name, v) := limitOf limits r - s!"{name}={v}" - return s!"Lean exhausted {fired.toList}; succeeded with {final}; bytes {same}" - | .error (.resourceExhausted r lim) => - let (name, _) := limitOf limits r - fired := fired.push s!"{name}={lim}" - unless raised.contains r do raised := raised.push r - limits := doubleLimit limits r - | .error e => return s!"Lean exhausted {fired.toList}; then error {reprStr e}" - return s!"Lean fired {fired.toList}; still exhausted after 12 doublings" - -def compareMany (cases : Array (String × Constant)) (modes : Constant → List Mode) : IO Tally := do - let mut t : Tally := {} - for (label, c) in cases do - let bytes := serConstant c - for m in modes c do - let (v, ln, rn) ← compareOne m c bytes - t := { t.add s!"{label} [{m.name}]" v with leanNs := t.leanNs + ln, rustNs := t.rustNs + rn } - return t - -/-- A test group whose body runs in `IO` when the suite executes. -/ -def ioGroup (descr : String) (run : IO (Bool × String)) : TestSeq := - .individualIO descr none (do - let start ← IO.monoMsNow - let (ok, msg) ← run - IO.println msg - IO.println s!" [{descr}: {(← IO.monoMsNow) - start} ms]" - return (ok, 0, 0, if ok then none else some s!"'{descr}' failed; see above")) .done - -/-- Pass/fail and the printed report of a batch: no disagreement and at least -one byte-identical success, so a batch cannot pass on matching errors alone. -With `expectAll`, every comparison must be a byte-identical success. -/ -def judge (descr : String) (t : Tally) (expectAll : Bool) : Bool × String := - let ok := t.differences.isEmpty && t.same > 0 && - (!expectAll || (t.resourceLean == 0 && t.resourceRust == 0 && t.sameErr == 0)) - let lines := (t.differences.toList.take 50).map (s!" DISAGREEMENT {·}") - (ok, String.intercalate "\n" - (s!" {descr}: {t.summary}; Lean {t.leanNs / 1000000} ms, Rust {t.rustNs / 1000000} ms" :: lines)) - -/-! ## Fixtures and generated families -/ - -def allModes : List Mode := - [.exact, .uniform 1, .uniform 2, .uniform 3, .uniform 5, .tiered] - -def fixtures : Array (String × Constant) := - let chains := #[1, 2, 3, 4, 7, 8, 9, 16, 32].map fun n => - (s!"T{n} → T{n}", axiomOf (arr (chain n) (chain n))) - #[("T2 → T2", witness2), ("T16 → T16", witness16), ("nine Refs", nineRef.1), - ("two 25-byte minima", axiomOf twoMinimaRoot #[.zero, .succ .zero])] ++ chains - -/-- The exact width-state search is exponential: skip it on T32. -/ -def fixtureModes (c : Constant) : List Mode := - if (serConstant c).size > 100 then allModes.filter (· != .exact) else allModes - -def fixtureTests : TestSeq := - ioGroup "Lean/Rust: §2 fixtures" do - let t ← compareMany fixtures fixtureModes - return judge s!"§2 fixtures × {allModes.map Mode.name}" t true - -/-- The generated families of the uniform and tiered tests. -/ -def genFamily (i : Nat) : Tests.SharingExact.RGen (Array Ixon.Expr) := - match i % 7 with - | 0 => Tests.SharingUniform.genPrefixes - | 1 => Tests.SharingUniform.genSmallPayload - | 2 => Tests.SharingUniform.genPairs - | 3 => Tests.SharingUniform.genChain - | 4 => Tests.SharingTiered.genHeavyParent - | _ => genRoots 5 12 4 - -def generatedCases (n : Nat) : Array (String × Constant) := - runGen 83 do - let mut out : Array (String × Constant) := #[] - for i in [0:n] do - let roots ← genFamily i - out := out.push (s!"generated #{i} (family {i % 7})", wrapRoots roots i) - return out - -/-- The exact width-state search only on inputs with few candidates. -/ -def generatedModes (c : Constant) : List Mode := - let small : Bool := match sharingProfile c with - | .ok p => p.candidates ≤ 12 - | .error _ => false - if small then allModes else allModes.filter (· != .exact) - -def generatedTests : TestSeq := - ioGroup "Lean/Rust: generated families" do - let t ← compareMany (generatedCases 350) generatedModes - return judge "350 generated inputs (prefixes, 2-byte payloads, pairs, chains, heavy parents, general) × modes" t true - -/-! ## TagN Share parity - -Lean and Rust serialize Share-bearing Constants to the same bytes, and Lean -decodes them back. Inputs: the tiered canonical sharing of every §2 fixture -and of generated inputs, plus synthetic tables whose roots reference the -indices at every TagN rung end (and indices beyond any table, which the codec -does not bound). -/ - -/-- A Constant with an `n`-entry table (`sharing[i] = app (var i) (Share (i-1))`) -whose root references the Share indices at the TagN rung ends below `n` (and -at the old byte-count boundaries), then -`n - 1`. -/ -def wideTable (n : Nat) : Constant := - let sharing := (Array.range n).map fun i => - if i == 0 then Ixon.Expr.var 0 else .app (.var i.toUInt64) (.share (i - 1).toUInt64) - let idxs := ([0, 7, 8, 255, 256, 1031, 1032, 65535, 65536, 66567, 66568].filter (· < n)) - ++ [n - 1] - let root := idxs.foldl (fun acc i => Ixon.Expr.app acc (.share i.toUInt64)) (.var 0) - { info := .axio ⟨false, 0, root⟩, sharing, refs := #[], univs := #[] } - -/-- Share indices in the 4-, 5- and 9-byte TagN rungs (no table backs them; the -codec does not bound Share indices). -/ -def farShares : Constant := - let root := [16843783, 16843784, 4294967296, 4311811079, 4311811080, 0xFFFFFFFFFFFFFFFF].foldl - (fun acc (i : UInt64) => Ixon.Expr.app acc (.share i)) (.var 0) - { info := .axio ⟨false, 0, root⟩, sharing := #[], refs := #[], univs := #[] } - -/-- Check one Constant: Rust writes Lean's bytes and Lean decodes them back. -/ -def codecCheck (label : String) (c : Constant) : Array String := Id.run do - let bytes := serConstant c - let mut errs : Array String := #[] - unless Tests.FFI.Ixon.rsEqConstantSerialization c bytes do - errs := errs.push s!"{label}: Rust bytes differ" - match deConstant bytes with - | .ok c' => unless c' == c do errs := errs.push s!"{label}: Lean decode differs" - | .error e => errs := errs.push s!"{label}: Lean decode failed: {e}" - return errs - -/-- The highest Share index of a Constant's table entries and roots. -/ -def maxShare (c : Constant) : Nat := - (c.sharing ++ constantInfoRoots c.info).foldl - (fun acc e => (shareIndices e #[]).foldl max acc) 0 - -/-- The tiered outputs of the fixtures and of 120 generated inputs that carry a -table, and the labels of the inputs whose normalization failed. -/ -def tieredOutputs : Array (String × Constant) × Array String := - (fixtures ++ generatedCases 120).foldl (init := (#[], #[])) fun (outs, errs) (label, c) => - match normalizeConstantSharingTiered .tagN c with - | .ok n => if n.sharing.isEmpty then (outs, errs) else (outs.push (s!"{label} [tiered-tagN]", n), errs) - | .error e => (outs, errs.push s!"{label}: tiered normalization failed: {reprStr e}") - -def codecTests : TestSeq := - ioGroup "Lean/Rust: TagN Share parity on Share-bearing Constants" do - let (normalized, failed) := tieredOutputs - let synthetic := #[("wide table 9", wideTable 9), ("wide table 300", wideTable 300), - ("wide table 1100", wideTable 1100), ("wide table 66600", wideTable 66600), - ("far Shares", farShares)] - let cases := normalized ++ synthetic - let mut errs : Array String := failed - for (label, c) in cases do - errs := errs ++ codecCheck label c - let ok := errs.isEmpty && normalized.size > 0 - let lines := (errs.toList.take 50).map (s!" DISAGREEMENT {·}") - return (ok, String.intercalate "\n" - (s!" {cases.size} Share-bearing Constants ({normalized.size} tiered-tagN outputs, max Share index {normalized.foldl (fun acc (_, c) => max acc (maxShare c)) 0}; {synthetic.size} synthetic); {errs.size} disagreements" :: lines)) - -/-! ## Inputs that carry a table - -Both constructions expand a stored table before sharing again. The tiered -outputs above are re-normalized by every mode on both sides, and must give -the same bytes; malformed tables (a forward, a self and two out-of-range -references) must be rejected by both sides as malformed. -/ - -def renormalizeTests : TestSeq := - ioGroup "Lean/Rust: re-normalization of stored tables" do - let t ← compareMany tieredOutputs.1 generatedModes - return judge s!"{tieredOutputs.1.size} tiered outputs (Share-bearing) × modes" t true - -/-- Constants whose stored table is malformed, with the expected Lean error. -/ -def malformedTables : Array (String × Constant × SharingError) := - let withTable (roots table : Array Ixon.Expr) : Constant := - { wrapRoots roots 0 with sharing := table } - #[("forward reference", withTable #[.share 0] #[.share 1, P], .nonBackwardShare 0 1), - ("self reference", withTable #[.share 0] #[.app (.share 0) P], .nonBackwardShare 0 0), - ("out-of-range root reference", withTable #[.share 3] #[], .shareOutOfRange none 3 0), - ("out-of-range entry reference", withTable #[P] #[.share 5], .shareOutOfRange (some 0) 5 1)] - -def malformedTests : TestSeq := - ioGroup "Lean/Rust: malformed stored tables" do - let mut errs : Array String := #[] - let mut checked := 0 - for (label, c, want) in malformedTables do - for m in allModes do - checked := checked + 1 - match leanNormalize m c {} with - | .error e => unless e == want do errs := errs.push s!"{label} [{m.name}]: Lean {reprStr e}" - | .ok _ => errs := errs.push s!"{label} [{m.name}]: Lean accepted it" - match compareOutcomes (leanSide m c) (rustSide m (serConstant c)) with - | .sameError "malformed" => pure () - | _ => errs := errs.push s!"{label} [{m.name}]: not rejected as malformed by both sides" - let ok := errs.isEmpty && checked == malformedTables.size * allModes.length - let lines := (errs.toList.take 50).map (s!" DISAGREEMENT {·}") - return (ok, String.intercalate "\n" - (s!" {malformedTables.size} malformed tables × {allModes.length} modes: {checked - errs.size} rejected as malformed by both sides with the expected Lean error" :: lines)) - -/-- The tiered scale inputs (`Tests.SharingTiered.scaleTests`): a table past -the 3-byte rung, a 1288-argument spine and 10000-deep inputs. -/ -def scaleCases : Array (String × Constant) := - let d := 10000 - let shared : Ixon.Expr := .ref 5 #[0, 0, 0, 0, 0] - #[("1040 fillers, 12 chains", wrapRoots (Tests.SharingTiered.scaleRoots 1040 12) 0), - ("1288-argument spine", wrapRoots (Tests.SharingTiered.spineRoots 1288) 0), - ("10000-deep inputs", wrapRoots #[ - (List.range d).foldl (fun acc i => .app (.var (i % 2).toUInt64) acc) shared, - (List.range d).foldl (fun acc i => .lam .many (.var (i % 3).toUInt64) acc) shared, - shared] 0)] - -def scaleParityTests : TestSeq := - ioGroup "Lean/Rust: the production path at scale" do - let t ← compareMany scaleCases fun _ => [.uniform 1, .uniform 2, .uniform 3, .tiered] - return judge "scale inputs (a table past the 3-byte rung, a long spine, deep inputs) × uniform-w1..3, tiered-tagN" t true - -/-- A doubling tower as a table: entry `0` is `Var 0`, entry `i + 1` is -`step (Share i)` and the root is the last entry, from entry `base` of -`table` on. -/ -def towerTable (table : Array Ixon.Expr) (depth : Nat) (step : Ixon.Expr → Ixon.Expr) : - Array Ixon.Expr := - let base := table.size - (List.range depth).foldl (fun acc i => acc.push (step (.share (base + i).toUInt64))) - (table.push (.var 0)) - -def towerOf (depth : Nat) (step : Ixon.Expr → Ixon.Expr) : Constant := - { axiomOf (.share depth.toUInt64) with sharing := towerTable #[] depth step } - -/-- Doubling towers whose unshared costs exceed `2^127` while their tables -have a few hundred entries: Rust computes phase 1 again in exact integers -where `u128` overflows, Lean in `Nat`. The production route only: the -uniform modes run the old reference optimizer, which refuses outside -`u128` in Rust. -/ -def wideCases : Array (String × Constant) := - let app : Ixon.Expr → Ixon.Expr := fun x => .app x x - let twin : Ixon.Expr → Ixon.Expr := fun x => .app x (.app (.var 1) x) - let pair := towerTable (towerTable #[] 128 app) 128 twin - #[("App(x, x) depth 127", towerOf 127 app), - ("App(x, x) depth 130", towerOf 130 app), - ("Lam(x, x) depth 130", towerOf 130 fun x => .lam .many x x), - ("two App towers depth 128", - { axiomOf (.app (.share 128) (.share 257)) with sharing := pair })] - -def wideParityTests : TestSeq := - ioGroup "Lean/Rust: lengths past u128" do - let t ← compareMany wideCases fun _ => [.tiered] - return judge "doubling towers past u128 × tiered-tagN" t true - -/-! ## Compiler sharing route - -The compiler builds every block through `Ix.CompileM.buildConstantWithSharing` -(Lean, under `CompileEnv.sharingLimits`) and `apply_sharing_to_*` (Rust): the -canonical construction, roots derived from the payload. Both languages -must build the same bytes from the same unshared Constant, and exactly what -the library normalizer builds. -/ - -/-- Rust `apply_sharing_to_*_with_limits` on one unshared Constant, under the -default limits. -/ -@[extern "rs_compiler_sharing_build"] -opaque rsCompilerSharingBuild : @& ByteArray → Except String ByteArray - -/-- The Lean compiler route on an unshared Constant. -/ -def leanRoute (c : Constant) : Except Ix.CompileM.CompileError Constant := - Ix.CompileM.buildConstantWithSharing Ix.CompileM.compilerSharingLimits c.info c.refs c.univs - -def routeUnits : TestSeq := - let tiny : Limits := { Ix.CompileM.compilerSharingLimits with maxNodes := 2 } - test "compiler route: T2 → T2 in the 17-byte minimum" - (match leanRoute witness2 with - | .ok c => hexOf (serConstant c) == "d200009117b0b001921700170000000100" - | .error _ => false : Bool) ++ - test "compiler route fails closed on its limits (resourceLimit naming the limit and its override)" - (match Ix.CompileM.buildConstantWithSharing tiny witness2.info witness2.refs - witness2.univs with - | .error (.resourceLimit msg) => - (msg.splitOn "resource exhausted: nodes (limit 2)").length > 1 && - (msg.splitOn "--sharing-limits nodes=N (IX_SHARING_LIMITS)").length > 1 - | _ => false : Bool) ++ - test "compiler route on a projection: no sharing table" - (match leanRoute { witness2 with info := .cPrj ⟨0, 0, Address.blake3 ByteArray.empty⟩ } with - | .ok c => c.sharing.isEmpty - | .error _ => false : Bool) - -def routeTests : TestSeq := - ioGroup "Lean/Rust: compiler sharing route" do - let cases := (fixtures ++ generatedCases 140).filterMap fun (label, c) => - match expandConstantSharing c with - | .ok e => some (label, e) - | .error _ => none - let mut errs : Array String := #[] - let mut same := 0 - let mut kinds : Array String := #[] - for (label, c) in cases do - let kind := match c.info with - | .defn _ => "defn" | .recr _ => "recr" | .axio _ => "axio" | .quot _ => "quot" - | .muts _ => "muts" | _ => "prj" - unless kinds.contains kind do kinds := kinds.push kind - match leanRoute c, rsCompilerSharingBuild (serConstant c) with - | .ok l, .ok r => - if serConstant l == r then same := same + 1 - else errs := errs.push s!"{label}: Lean and Rust bytes differ" - match normalizeConstantSharingTiered .tagN c with - | .ok n => - unless serConstant n == serConstant l do - errs := errs.push s!"{label}: route differs from normalizeConstantSharingTiered" - | .error e => errs := errs.push s!"{label}: normalizer failed: {reprStr e}" - | .error e, _ => errs := errs.push s!"{label}: Lean route failed: {e}" - | _, .error e => errs := errs.push s!"{label}: Rust route failed: {e}" - let ok := errs.isEmpty && same > 0 && same == cases.size - let lines := (errs.toList.take 50).map (s!" DISAGREEMENT {·}") - return (ok, String.intercalate "\n" - (s!" {cases.size} unshared Constants (kinds {kinds.toList}): {same} same bytes; {errs.size} disagreements" :: lines)) - -/-! ## Corpus mode -/ - -def corpusTests (_ : Unit) : TestSeq := - .individualIO "init.ixe corpus (IX_SHARING_CORPUS)" none (do - let some path ← IO.getEnv "IX_SHARING_CORPUS" - | IO.println " [corpus: IX_SHARING_CORPUS not set; skipped]" - return (true, 0, 0, none) - let modeNames := ((← IO.getEnv "IX_SHARING_CORPUS_MODES").getD "tiered-tagN").splitOn "," - let modes := modeNames.filterMap Mode.parse - if modes.length != modeNames.length then - return (false, 0, 0, some s!"unknown mode in {modeNames}") - let limit := ((← IO.getEnv "IX_SHARING_CORPUS_LIMIT").bind String.toNat?) - let t0 ← IO.monoMsNow - let bytes ← IO.FS.readBinFile path - let env ← IO.ofExcept (Ixon.deEnvAnon bytes) - let entries := env.consts.toArray.qsort fun a b => Address.cmpBytes a.1 b.1 == .lt - -- Optional selection: a file of hex addresses, one per line. - let select ← match ← IO.getEnv "IX_SHARING_CORPUS_SELECT" with - | some p => do - let lines := (← IO.FS.readFile p).splitOn "\n" |>.map (String.trimAscii · |>.toString) |>.filter (· != "") - pure (some (lines.foldl (fun (s : Std.HashSet String) l => s.insert l) {})) - | none => pure none - let entries := match select with - | some s => entries.filter fun (e : Address × Ixon.LazyConstant) => s.contains (toString e.1) - | none => entries - let entries := match limit with - | some n => entries.extract 0 n - | none => entries - IO.println s!" [corpus: {path}, {env.consts.size} constants, comparing {entries.size} under {modes.map Mode.name}; loaded in {(← IO.monoMsNow) - t0} ms]" - let mut tallies : Array Tally := Array.replicate modes.length {} - let mut decodeFailures := 0 - let mut done := 0 - for (addr, lc) in entries do - let c ← match lc.get with - | .ok c => pure c - | .error _ => - decodeFailures := decodeFailures + 1 - continue - let raw := lc.rawBytes - let label := match env.addrToName.get? addr with - | some n => s!"{n} ({addr})" - | none => s!"{addr}" - for h : k in [0:modes.length] do - let m := modes[k] - let (v, ln, rn) ← compareOne m c raw - -- Lean-only exhaustion: find the limit that fires and the value that suffices. - let v ← match v with - | .resourceOnly true => do - let d := diagnose m c (rustSide m raw) - IO.println s!" DIAGNOSIS {label} [{m.name}]: {d}" - pure v - | _ => pure v - let t := tallies[k]! - tallies := tallies.set! k - { t.add label v with leanNs := t.leanNs + ln, rustNs := t.rustNs + rn } - done := done + 1 - if done % 5000 == 0 then - IO.println s!" [corpus progress: {done}/{entries.size}]" - let mut ok := decodeFailures == 0 - for h : k in [0:modes.length] do - let t := tallies[k]! - IO.println s!" [corpus {modes[k].name}: {done} constants; {t.summary}; Lean {t.leanNs / 1000000} ms, Rust {t.rustNs / 1000000} ms]" - for d in t.notes do - IO.println s!" NOTE {d}" - for d in t.differences do - IO.println s!" DISAGREEMENT {d}" - unless t.differences.isEmpty do ok := false - IO.println s!" [corpus: decode failures {decodeFailures}; total {(← IO.monoMsNow) - t0} ms]" - return (ok, 0, 0, if ok then none else some "corpus disagreements; see above")) .done - -public def suite : List TestSeq := [ - fixtureTests, - generatedTests, - codecTests, - renormalizeTests, - malformedTests, - scaleParityTests, - wideParityTests, - routeUnits, - routeTests, - corpusTests (), -] - -end Tests.SharingExactFFI - -end diff --git a/Tests/Ix/SharingFamily.lean b/Tests/Ix/SharingFamily.lean deleted file mode 100644 index 451f0c71e..000000000 --- a/Tests/Ix/SharingFamily.lean +++ /dev/null @@ -1,213 +0,0 @@ -import Tests.Ix.SharingUniform -import Ix.Sharing.FamilyNormalizeArea - -namespace Tests.SharingFamily - -open LSpec Ixon Ix.Sharing.Exact Ix.Sharing.Family Tests.SharingExact - -private def ensure (condition : Bool) (why : String) : IO Unit := - unless condition do throw (IO.userError why) - -/-- Independent mask enumeration, used only on tiny test components. -/ -private def brute (cost : Cost) (members : Array Nat) : CTable := Id.run do - let mut out : CTable := Array.replicate (members.size + 1) none - for mask in [0:2 ^ members.size] do - let selected := (members.zipIdx.filter (fun (_, i) => mask.testBit i)).map (·.1) - let candidate := (cost selected, selected) - let improve := match out[selected.size]! with - | none => true - | some (d, s) => candidate.1 < d || (candidate.1 == d && setPrec selected s) - if improve then out := out.set! selected.size (some candidate) - return out - -private def interaction (s : Array Nat) : _root_.Int := - if s.size == 0 then 0 - else if s == #[0] then -5 else if s == #[1] then -4 else if s == #[2] then -3 - else if s == #[0, 1] then 10 else if s == #[0, 2] then 12 - else if s == #[1, 2] then -20 else -1 - -private def genericControls : IO Unit := do - let members := [0, 1, 2] - let zero : Cost := fun _ => 0 - ensure (greedyOrder zero members == [2, 1, 0]) "setPrec tie convention changed" - ensure (greedyOrder zero members.reverse == [2, 1, 0]) "tie result depends on scan order" - ensure (chain interaction members == [#[], #[0], #[0, 1], #[0, 1, 2]]) - "greedy chain stopped on an expensive extension or used initial gains" - let exactRows := table ⟨3⟩ interaction members - let chainRows := table ⟨2⟩ interaction members - ensure (exactRows == brute interaction members.toArray && exactRows[2]! == some (-20, #[1, 2])) - "exact family missed the interacting optimum" - ensure (chainRows[2]! == some (10, #[0, 1]) && chainRows[3]! == some (-1, #[0, 1, 2])) - "fallback lost per-count rows after a cost increase" - ensure (table ⟨0⟩ zero [] == #[some (0, #[])]) "empty component lost its endpoint" - for n in [1:7] do - let ms := List.range n - let cost : Cost := fun s => (s.foldl (fun a x => a + x * x) 0 : Nat) - (3 * s.size : Nat) - ensure (table ⟨n⟩ cost ms == brute cost ms.toArray) "mask and recursive enumeration disagree" - let rows := table ⟨0⟩ cost ms - ensure (rows.size == n + 1 && rows.zipIdx.all (fun (r, k) => - r.any fun (d, s) => s.size == k && strictInc s && d == cost s)) "greedy count/row invariant" - ensure (rows[n]!.any (fun (_, s) => s == ms.toArray)) "greedy endpoint is not the full component" - -private def modelControls : IO Nat := do - let inputs := runGen 71 ((List.range 60).mapM fun _ => Tests.SharingUniform.genSearchRoots) - let mut tested := 0 - let mut interacting := 0 - for roots in inputs do - let ex ← IO.ofExcept ((expand {} #[] roots false).mapError toString) - for width in [1, 2, 3] do - let stage := uniformStage width ex (Prep.ofDag ex.dag) - -- Repeated/reordered components and an empty endpoint expose stale - -- positions without requiring a large corpus or changing any family. - let repeated := { stage with comps := stage.comps ++ stage.comps.reverse ++ #[#[]] } - for exactLimit in [0, 3] do - let cfg : Config := ⟨exactLimit⟩ - let reference := repeated.comps.mapIdx fun j ms => component cfg ex repeated width j ms - let cached := componentsCachedWith cfg ex repeated width - ensure (cached.map (·.tsv "cached") == reference.map (·.tsv "cached")) - "cached stage changed full traces after repeated/reordered components" - let parents := parentsOf ex.dag - let zeroA := Array.replicate repeated.up.prep.dag.size 0 - let zeroC := Array.replicate ex.dag.size 0 - let mut state : CacheState := (#[], zeroA, zeroC) - for item in repeated.comps.zipIdx do - state := cachedStep cfg ex repeated width parents state item - ensure (state.2.1 == zeroA && state.2.2 == zeroC) - "cached component left touched positions uncleared" - ensure (state.1.map (·.tsv "cached") == reference.map (·.tsv "cached")) - "cached step fold differs from the stage loop" - let bad := { repeated with cand := #[] } - let badReference := bad.comps.mapIdx fun j ms => component cfg ex bad width j ms - ensure ((componentsCachedWith cfg ex bad width).map (·.tsv "fallback") == - badReference.map (·.tsv "fallback")) "cached representation fallback changed traces" - for (members, i) in stage.comps.zipIdx do - if members.size > 8 then continue - let cx := componentContext ex stage members - let positions := localPositions cx - ensure positions.isSome "valid initial context bypassed the closure-local evaluator" - let baseline := (cx.phiE (fun _ => false) #[]).1 - let cost := componentDelta cx baseline - ensure (cost #[] == 0) "empty-subset delta is not zero" - let precise := component ⟨members.size⟩ ex stage width i members - let fallback := component ⟨members.size - 1⟩ ex stage width i members - ensure (precise.exact && precise.rows == brute cost members) "exact component rows differ" - ensure (!fallback.exact && fallback.rows.size == members.size + 1) "K/K+1 gate or full chain failed" - for row in fallback.rows do - ensure (row.any fun (d, s) => d == cost s && s.all members.contains && strictInc s) - "fallback row cost/membership is not actual phiE" - if let some (_, set) := row then - ensure (localPhi cx positions set.contains set == cx.phiE set.contains set) - "local evaluator changed the cost/work pair" - let badSize := { cx with cand := #[] } - ensure ((localPositions badSize).isNone && - localPhi badSize (localPositions badSize) members.contains members == - badSize.phiE members.contains members) "bad-size evaluator fallback changed" - let missing := { cx with area := #[] } - ensure (localSearchOK missing && (localPositions missing).isNone && - localPhi missing (localPositions missing) members.contains members == - missing.phiE members.contains members) "missing-member evaluator fallback changed" - let (reference, _, _) ← IO.ofExcept ((searchComponentRef stage.up stage.f stage.opaq - ex.roots stage.slack {} members 0 0).mapError toString) - let kept (rows : CTable) := (rows.trim stage.slack).zipIdx.filterMap fun (r, k) => r.map (k, ·) - ensure (kept precise.rows == kept reference.bySize) - "full-closure delta differs from the existing independent area evaluator" - ensure ((precise.tsv "fixture").startsWith s!"component\tfixture\t{width}\t{i}\texact\t") - "shared trace schema changed" - tested := tested + 1 - if members.size > 1 then interacting := interacting + 1 - ensure (tested > 0 && interacting > 0) "model controls did not exercise uncertain interactions" - IO.println s!"family components: {tested} component/width cases; {interacting} non-singletons; exact/chain, actual deltas and area parity" - return tested - -private def meterImage (outcome : Except SharingError MeterState) := - outcome.map fun (results, states, work) => - (results.map (fun (r : CompResult) => (r.members, r.bestDelta, r.bestSet, r.bySize)), states, work) - -private def sameOutcome [BEq α] (a b : Except SharingError α) : Bool := - match a, b with - | .ok a, .ok b => a == b - | .error a, .error b => a == b - | _, _ => false - -private def areaControls : IO Nat := do - let inputs := runGen 712 ((List.range 12).mapM fun _ => Tests.SharingUniform.genSearchRoots) - let mut exercised := 0 - for roots in inputs do - let ex ← IO.ofExcept ((expand {} #[] roots false).mapError toString) - for width in [1, 2, 3] do - let sg := uniformStageF width ex (Prep.ofDag ex.dag) (graphFacts ex.dag ex.roots) - let sg := { sg with comps := sg.comps ++ sg.comps.reverse ++ #[#[]] } - let parents := parentsOf ex.dag - let tb := TBase.ofPrep sg.up.prep sg.up.w sg.up.opaq - for k in [0, 3, 8] do - let cfg : Config := ⟨k⟩ - for limits in [({} : Limits), { maxStates := 0 }, { maxCostEvals := 0 }] do - ensure (sameOutcome (meterImage (familyComponentsArea cfg limits ex sg width parents)) - (meterImage (familyComponents cfg limits ex sg width parents))) - "area fold changed retained rows, counters, or exact refusal after repeated components" - if ex.dag.size > 0 then - -- Preserve every array bound while violating the opacity premise: - -- the first DAG term is a leaf whose inline cost is below this width. - let badStage := { sg with up := { sg.up with - opaq := sg.up.opaq.set! 0 true, w := tb.inl[0]! + 1 } } - ensure (!tStageOK badStage.up.prep badStage.up.w badStage.up.opaq - (TBase.ofPrep badStage.up.prep badStage.up.w badStage.up.opaq)) "bad stage passed the guard" - ensure (sameOutcome (meterImage (familyComponentsArea cfg {} ex badStage width parents)) - (meterImage (familyComponents cfg {} ex badStage width parents))) "stage fallback changed result" - if !tStageOK sg.up.prep sg.up.w sg.up.opaq tb then continue - for (members, index) in sg.comps.zipIdx do - let cx := componentContext ex sg members - let zero := Array.replicate ex.dag.size 0 - let posA := setPositions zero cx.area - let posC := setPositions zero cx.closure - let (actual, a, c) := familyAreaEvaluation cfg ex cx tb parents width index members zero posC - let (expected, ea, ec) := cachedEvaluation cfg cx width index members zero posC - ensure (actual.tsv "area" == expected.tsv "area" && a == ea && c == ec && a == zero && c == zero) - "area evaluator changed a full trace or left touched positions uncleared" - if familyAreaOK ex cx parents posA posC && !members.isEmpty then - exercised := exercised + 1 - let aux := familyAreaAux ex cx posA tb - for row in expected.rows do - if let some (_, selected) := row then - ensure (phiA { cx with closure := #[], rootsC := #[], storedInC := #[] } - posA tb aux (fun _ => (cx, posC)) selected.contains selected == - phiEL2 cx posA posC selected.contains selected) "area cost/work pair changed" - let badArea := { cx with area := #[] } - ensure (!familyAreaOK ex badArea parents zero posC && - (familyTraceArea cfg ex badArea tb parents width index members zero posC).tsv "bad" == - (traceWithPositions cfg badArea width index members zero posC).tsv "bad") - "missing-member area did not retain the closure fallback" - let badCx := { cx with cand := #[] } - if ex.dag.size > 0 then - let (bad, _, _) := familyAreaEvaluation cfg ex badCx tb parents width index members zero posC - ensure (!localSearchOK badCx && bad.tsv "bad" == - (traceWithPhi cfg badCx width index members badCx.phiE).tsv "bad") - "malformed context bypassed the specification fallback" - -- A real leaf deliberately mislabeled opaque at width 3 must fail the - -- per-evaluation opacity test, independently of the outer stage guard. - let ex ← IO.ofExcept ((expand {} #[] #[.var 0] false).mapError toString) - let sg := uniformStageF 3 ex (Prep.ofDag ex.dag) (graphFacts ex.dag ex.roots) - let original := componentContext ex sg #[0] - let cx := { original with up := { original.up with opaq := #[true] } } - let tb := TBase.ofPrep cx.up.prep cx.up.w cx.up.opaq - let posA := setPositions #[0] cx.area - let posC := setPositions #[0] cx.closure - let aux := familyAreaAux ex cx posA tb - let (cost, inl, sides, _) := areaFold cx.up.prep cx.up.opaq tb cx.up.w cx.area posA (memberFlags cx posA (fun _ => false)) - ensure (!aux.storedA.all (tGuard cx.up.prep tb.tTail cx.up.w (rdA posA cost tb.cost) - (rdA posA inl tb.inl) (rdA posA sides tb.sides))) "opacity negative control did not reject" - ensure (phiA { cx with closure := #[], rootsC := #[], storedInC := #[] } posA tb aux - (fun _ => (cx, posC)) (fun _ => false) #[] == phiEL2 cx posA posC (fun _ => false) #[]) - "per-evaluation opacity fallback changed the full cost/work pair" - ensure (exercised > 0) "area controls never exercised a nonempty guarded area" - IO.println s!"family area: {exercised} nonempty guarded traces; full pairs, repeated folds, refusals and fallbacks" - return exercised - -def suite : List TestSeq := [ - .individualIO "component families preserve costs, counts and canonical ties" none (do - genericControls - let tested ← modelControls - let areaTested ← areaControls - return (true, tested + areaTested, 0, none)) .done] - -end Tests.SharingFamily diff --git a/Tests/Ix/SharingFamilyNormalize.lean b/Tests/Ix/SharingFamilyNormalize.lean deleted file mode 100644 index 1dfe85148..000000000 --- a/Tests/Ix/SharingFamilyNormalize.lean +++ /dev/null @@ -1,112 +0,0 @@ -import Tests.Ix.SharingUniform -import Ix.Sharing.FamilyNormalizeArea - -namespace Tests.SharingFamilyNormalize - -open LSpec Ixon Ix.Sharing.Exact Ix.Sharing.Family Tests.SharingExact - -private def ensure (b : Bool) (message : String) : IO Unit := - unless b do throw (IO.userError message) - -/-- Keep a native closure-evaluator reference: `familyAtWidth` has no csimp -replacement. Calling the public reference entry here would run the new twin. -/ -private def closureNormalize (cfg : Config) (constant : Constant) (limits : Limits) : - Except SharingError (Constant × TieredSharingResult) := do - let ex ← expand limits constant.sharing (constantInfoRoots constant.info) true - uniformDagChecks ex - let prep := Prep.ofDag ex.dag - let facts := graphFacts ex.dag ex.roots - let parents := parentsOf ex.dag - let candidates := tieredCandidateCount ex.dag.size prep facts - let result ← tieredCandidates ex.dag.size (familyAtWidth cfg .tagN limits ex prep facts parents candidates) - let info ← withRoots constant.info result.result.roots - return ({ constant with info, sharing := result.result.sharing }, withExpansionStats ex result) - -private def outcomeImage (outcome : Except SharingError (Constant × TieredSharingResult)) := - outcome.map fun (constant, report) => - (serConstant constant, report.phase1.statesVisited, report.phase1.result.stats.costEvals) - -private def sameOutcome [BEq α] (a b : Except SharingError α) : Bool := - match a, b with - | .ok a, .ok b => a == b - | .error a, .error b => a == b - | _, _ => false - -private def budgetControls : IO Unit := do - -- Independent small enumerations of calls and the actual phiE work field. - for m in [0:9] do - for k in [0, m, m + 1] do - for closure in [0, 3, 7] do - let mut calls := 0 - let mut work := closure -- empty baseline - if m ≤ k then - for mask in [0:2 ^ m] do - let size := ((List.range m).filter mask.testBit).length - calls := calls + 1 - work := work + closure + size - else - for q in [1:m + 1] do - for _ in [0:m - q + 1] do - calls := calls + 1 - work := work + closure + q - for q in [0:m + 1] do - calls := calls + 1 - work := work + closure + q - ensure (familyCalls ⟨k⟩ m == calls && familyWork ⟨k⟩ m closure == work) - "precharged mathematical calls/work differ from independent enumeration" - let witness := wrapRoots #[.var 20, .var 20] 0 - for limits in [({ maxStates := 0 } : Limits), { maxCostEvals := 0 }, { maxOutputBytes := 0 }] do - ensure (sameOutcome (outcomeImage (normalizeFamily ⟨16⟩ .tagN witness limits)) - (outcomeImage (closureNormalize ⟨16⟩ witness limits))) "area and closure refused differently" - ensure (isErr (normalizeFamily ⟨12⟩ .tagN witness { maxStates := 0 }) - (· == .resourceExhausted .states 0)) "family state budget did not refuse" - ensure (isErr (normalizeFamily ⟨12⟩ .tagN witness { maxCostEvals := 0 }) - (· == .resourceExhausted .costEvals 0)) "family phi-work budget did not refuse" - ensure (isErr (normalizeFamily ⟨12⟩ .tagN witness { maxOutputBytes := 0 }) - (· == .resourceExhausted .outputBytes 0)) "output budget did not refuse" - ensure (isErr (normalizeFamily ⟨12⟩ .tagN { witness with sharing := #[.share 0] }) (fun _ => true)) - "malformed backward-sharing input was accepted" - -private def normalizationControls : IO Nat := do - let samples := runGen 811 ((List.range 36).mapM fun _ => genRoots 5 6 4) - let mut tested := 0 - for (roots, index) in samples.zipIdx do - let constant := wrapRoots roots index - let before ← IO.ofExcept ((expand {} constant.sharing roots true).mapError toString) - let exact ← IO.ofExcept ((Reference.normalizeConstantSharingTiered .tagN constant).mapError toString) - let alternate ← IO.ofExcept ((normalizeConstantSharingUniform 1 constant).mapError toString) - for k in [0, 3, 24] do - let (candidate, report) ← IO.ofExcept ((normalizeFamily ⟨k⟩ .tagN constant).mapError toString) - let reference ← IO.ofExcept ((closureNormalize ⟨k⟩ constant {}).mapError toString) - ensure (sameOutcome (outcomeImage (.ok (candidate, report))) (outcomeImage (.ok reference))) - "area normalization changed complete serialized bytes or mathematical charges" - let after ← IO.ofExcept ((expand {} candidate.sharing (constantInfoRoots candidate.info) true).mapError toString) - ensure (after.dag.nodes == before.dag.nodes && after.roots == before.roots) - "family normalization changed the expanded terms" - ensure (candidate.refs == constant.refs && candidate.univs == constant.univs && - (withRoots candidate.info roots).toOption == some constant.info) - "family normalization changed fixed fields or source tables" - ensure (report.result.variableBytes == tag0Size candidate.sharing.size + - exprsSize candidate.sharing + exprsSize (constantInfoRoots candidate.info)) - "family output disagrees with serialized variable bytes" - if k == 24 then - ensure (serConstant candidate == serConstant exact) "all-exact bytes differ from the existing optimizer" - let (again, _) ← IO.ofExcept ((normalizeFamily ⟨k⟩ .tagN candidate).mapError toString) - ensure (serConstant again == serConstant candidate) "family normalization is not representation-stable" - let (alternateResult, _) ← IO.ofExcept ((normalizeFamily ⟨k⟩ .tagN alternate).mapError toString) - ensure (serConstant alternateResult == serConstant candidate) - "another valid sharing representation changed the family output" - tested := tested + 1 - let projection := wrapRoots #[] 0 - let (same, _) ← IO.ofExcept ((normalizeFamily ⟨0⟩ .tagN projection).mapError toString) - ensure (serConstant same == serConstant projection) "empty-root projection changed" - IO.println s!"family normalizer: {tested} whole constants; fixed fields, expansion, all-exact bytes and idempotence" - return tested - -def suite : List TestSeq := [ - .individualIO "family normalizer preserves bytes and checked failures" none (do - budgetControls - let tested ← normalizationControls - return (true, tested, 0, none)) .done] - -end Tests.SharingFamilyNormalize diff --git a/Tests/Ix/SharingTiered.lean b/Tests/Ix/SharingTiered.lean index f2b3d58ed..bb02a3db8 100644 --- a/Tests/Ix/SharingTiered.lean +++ b/Tests/Ix/SharingTiered.lean @@ -375,12 +375,11 @@ def failsWith (roots : Array Ixon.Expr) (l : Limits) (r : Resource) (v : Nat) : | _ => false /-- The compiler's builder on `c` under `l` fails with the `resourceLimit` -error naming the limit `key`, its value and its override. -/ +error naming the limit `key` and its value. -/ def compilerFailsWith (c : Constant) (l : Limits) (key : String) (v : Nat) : Bool := match Ix.CompileM.buildConstantWithSharing l c.info c.refs c.univs with | .error (.resourceLimit msg) => - (msg.splitOn s!"resource exhausted: {key} (limit {v})").length > 1 && - (msg.splitOn s!"--sharing-limits {key}=N (IX_SHARING_LIMITS)").length > 1 + (msg.splitOn s!"resource exhausted: {key} (limit {v})").length > 1 | _ => false def productionLimitTests (_ : Unit) : TestSeq := @@ -405,7 +404,7 @@ def productionLimitTests (_ : Unit) : TestSeq := ("128 atoms", atomsC, { d with maxKnapsackCells := 10 }, .knapsackCells, 10)] group "the production construction under its limits" <| cases.foldl (init := .done) (fun acc (name, c, l, r, v) => - acc ++ test s!"{name}: {r.key} = {v} → canonicalSharingTiered fails with resourceExhausted {r.key} {v}; the compiler's builder with resourceLimit naming --sharing-limits {r.key}=N" + acc ++ test s!"{name}: {r.key} = {v} → canonicalSharingTiered fails with resourceExhausted {r.key} {v}; the compiler's builder with resourceLimit naming {r.key}" (failsWith (constantInfoRoots c.info) l r v && compilerFailsWith c l r.key v)) ++ test "the transitions limit meters only the width-state oracle: maxTransitions = 0 leaves T16 and the 128 atoms unchanged" ((canonicalSharingTiered .tagN t16 { d with maxTransitions := 0 }).toOption.map diff --git a/Tests/Ix/SharingUniform.lean b/Tests/Ix/SharingUniform.lean index e322b3f0e..621652445 100644 --- a/Tests/Ix/SharingUniform.lean +++ b/Tests/Ix/SharingUniform.lean @@ -285,32 +285,68 @@ def componentCountsAgree (w : Nat) (roots : Array Ixon.Expr) : Bool := Id.run do return actual.size == reference.size && (actual.zip reference).all fun (a, b) => kept a.bySize == kept b.bySize +/-- One generated input's outcome in `searchTests`. -/ +inductive SearchOutcome where + /-- No component of ≥ 2 uncertain terms: not counted. -/ + | small + /-- Both searches agree; the size of the largest component. -/ + | agree (component : Nat) + /-- The enumeration exceeded its 2^16 states. -/ + | skipped + /-- The searches disagree on a counted input whose largest component has + size `component`, or (`none`) one of them failed. -/ + | failed (component : Option Nat) (message : String) + +/-- Both searches on input `i`, `roots` at width `w`. -/ +def searchOutcome (i w : Nat) (roots : Array Ixon.Expr) : SearchOutcome := + match optimizeSharingUniform w roots, + optimizeSharingUniform w roots { maxStates := 1 <<< 16, uniformSubsetSearch := true } with + | .ok u, .ok r => + let comp := u.components.foldl (fun acc c => max acc c.size) 0 + if comp < 2 then .small + else if u.stored == r.stored && u.result.modelBytes == r.result.modelBytes && + u.lowerBracket == r.lowerBracket && componentCountsAgree w roots then .agree comp + else .failed (some comp) s!"case {i} w={w}: branch and bound stored {u.stored} model {u.result.modelBytes}, subset enumeration stored {r.stored} model {r.result.modelBytes} roots={reprStr roots}" + | .ok _, .error (.resourceExhausted ..) => .skipped + | .error e, _ => .failed none s!"case {i} w={w}: branch and bound error {reprStr e} roots={reprStr roots}" + | _, .error e => .failed none s!"case {i} w={w}: subset enumeration error {reprStr e}" + /-- The reclassifying branch and bound returns exactly what the subset enumeration returns (same set, so the same tie-break), on inputs with larger -uncertain components. -/ +uncertain components. The inputs are drawn in order from one seed; both +searches run on batches of them in parallel, and the outcomes are read in +draw order, so the inputs checked are the first 600 with a component of +≥ 2 uncertain terms, as when the searches ran on one input at a time. -/ def searchTests (_ : Unit) : TestSeq := - let (checked, skipped, maxComp, err) := runGen 71 do + let inputs := runGen 71 do + let mut out : Array (Array Ixon.Expr × Nat) := #[] + for _ in [0:4000] do + let roots ← genSearchRoots + out := out.push (roots, [1, 2, 3, 5][← rand 4]!) + return out + let batch := 64 + let (checked, skipped, maxComp, err) := Id.run do let mut checked := 0 let mut skipped := 0 let mut maxComp := 0 let mut err : Option String := none - for i in [0:4000] do + for start in [0:inputs.size:batch] do if checked ≥ 600 || err.isSome then break - let roots ← genSearchRoots - let w := [1, 2, 3, 5][← rand 4]! - match optimizeSharingUniform w roots, - optimizeSharingUniform w roots { maxStates := 1 <<< 16, uniformSubsetSearch := true } with - | .ok u, .ok r => - let comp := u.components.foldl (fun acc c => max acc c.size) 0 - if comp < 2 then continue - checked := checked + 1 - maxComp := max maxComp comp - unless u.stored == r.stored && u.result.modelBytes == r.result.modelBytes && - u.lowerBracket == r.lowerBracket && componentCountsAgree w roots do - err := some s!"case {i} w={w}: branch and bound stored {u.stored} model {u.result.modelBytes}, subset enumeration stored {r.stored} model {r.result.modelBytes} roots={reprStr roots}" - | .ok _, .error (.resourceExhausted ..) => skipped := skipped + 1 - | .error e, _ => err := some s!"case {i} w={w}: branch and bound error {reprStr e} roots={reprStr roots}" - | _, .error e => err := some s!"case {i} w={w}: subset enumeration error {reprStr e}" + let tasks := (inputs.extract start (start + batch)).zipIdx.map fun ((roots, w), k) => + Task.spawn fun _ => searchOutcome (start + k) w roots + for task in tasks do + if checked ≥ 600 || err.isSome then break + match task.get with + | .small => pure () + | .agree comp => + checked := checked + 1 + maxComp := max maxComp comp + | .skipped => skipped := skipped + 1 + | .failed comp message => + if let some comp := comp then + checked := checked + 1 + maxComp := max maxComp comp + err := some message return (checked, skipped, maxComp, err) group "uniform branch and bound vs subset enumeration" <| test s!"{checked} generated inputs with a component of ≥ 2 uncertain terms (prefix chains, telescopes, App chains, long spines, prefixes, general; w ∈ 1,2,3,5; largest component {maxComp}; {skipped} skipped where the enumeration exceeded 2^16 states): identical retained per-count optima/ties, stored set, model length and bracket choice" diff --git a/Tests/Ix/SourceContract/Driver.lean b/Tests/Ix/SourceContract/Driver.lean index 208fc4f7f..755e70c16 100644 --- a/Tests/Ix/SourceContract/Driver.lean +++ b/Tests/Ix/SourceContract/Driver.lean @@ -1,10 +1,13 @@ module public import LSpec -public import Ix.CompileDriver -public import Ix.CompileResource +public import Ix.CompileCertified public import Ix.Meta +/-! + Source-contract driver tests (`Tests.Ix.SourceContract.Driver`). +-/ + public section namespace Tests.Ix.SourceContract.Driver @@ -34,6 +37,18 @@ private def unsupported (result : Except String α) : Bool := | .error message => (message.splitOn "external resource interface is absent").length > 1 | .ok _ => false +private def compileConsts (consts : List (Lean.Name × ConstantInfo)) : + IO (Except String Ix.CompileM.CertifiedOut) := do + match prepareSourceConstants consts >>= fun p => (checkPreparedSource p).map (p, ·) with + | .error e => return .error e + | .ok (prepared, annotated) => compileCertifiedPrepared prepared annotated 1 + +private def compileInput (input : CompileInput) : + IO (Except String Ix.CompileM.CertifiedOut) := do + match input.prepare >>= fun p => (checkPreparedSource p).map (p, ·) with + | .error e => return .error e + | .ok (prepared, annotated) => compileCertifiedPrepared prepared annotated 1 + private def placement : IO Bool := do let env ← getFileEnv "Tests/Ix/SourceContract/PlacementFixture.lean" let some source := env.find? `SemanticPlacement.etaExpanded | return false @@ -103,9 +118,9 @@ def suite : List TestSeq := [ if (checkOrdinaryConstants marked (fun _ => false)).toOption != some false then return false return true, ioTest "Lean constant-list driver rejects an unadmitted annotated axiom" do - return unsupported (← compileLeanConstsValidated (constants markedSource) (numWorkers := 1)), + return unsupported (← compileConsts (constants markedSource)), ioTest "explicit Lean input rejects a missing annotation registry" do - match ← compileLeanInput (.plain (constants markedSource)) (numWorkers := 1) with + match ← compileInput (.plain (constants markedSource)) with | .error message => return (message.splitOn "missingContract").length > 1 | .ok _ => return false, ioTest "explicit Lean input requires an assumption for an annotated axiom" do @@ -113,29 +128,17 @@ def suite : List TestSeq := [ { binder := .position 0, uses := .linear }] | return false let input : CompileInput := { constants := constants plainSource, contracts := #[contract] } - return unsupported (← compileLeanInputValidated input (numWorkers := 1)), + return unsupported (← compileInput input), ioTest "measure-only input preserves ordinary production bytes" do let input := CompileInput.plain (constants plainSource) - let .ok plain ← compileLeanInput input (numWorkers := 1) | return false + let .ok plain ← compileInput input | return false let hinted := { input with measureHints := #[⟨plainSource, .position 0, some 1⟩] } - let .ok measured ← compileLeanInput hinted (numWorkers := 1) | return false - return plain.bytes == measured.bytes, - ioTest "Rust FFI rejects source contracts without creating an artifact in either output mode" do - let directory ← IO.FS.createTempDir - try - for allowPartial in [false, true] do - let output := directory / s!"contracts-{allowPartial}.ixe" - let rejected ← try - let _ ← rsCompileEnvBytesFFI (constants markedSource) output.toString allowPartial - pure false - catch error => - pure ((error.toString.splitOn "unresolved source binder contracts").length > 1) - if !rejected || (← output.pathExists) || - (← (System.FilePath.mk (output.toString ++ ".tmp")).pathExists) then - return false - return true - finally - IO.FS.removeDirAll directory + let .ok measured ← compileInput hinted | return false + return (Ixon.serEnv plain.env).toOption == (Ixon.serEnv measured.env).toOption, + ioTest "prepared-source preflight rejects unresolved source binder contracts" do + match checkPreparedSource (constants markedSource) with + | .error message => return (message.splitOn "unresolved source binder contracts").length > 1 + | .ok _ => return false ] end Tests.Ix.SourceContract.Driver diff --git a/Tests/Ix/TruthMines.lean b/Tests/Ix/TruthMines.lean index 6c5734c42..d9ac6ee2c 100644 --- a/Tests/Ix/TruthMines.lean +++ b/Tests/Ix/TruthMines.lean @@ -1,4 +1,10 @@ -/- +module + +public import LSpec +public import Benchmarks.TruthMinesSpec.Projection +public import Ix.Cli.CatalogCmd + +/-! The TruthMines corpus tiers over the piece pipeline: drive `truthmines build` (per-member watchdogged `ix compile` straight into the self-contained `.ixc` directory → `ix catalog assemble` → @@ -14,7 +20,7 @@ * `truthmines` — the full corpus: `truthmines build` → assert the manifest (every member present with a recomputed root, catalog verify green over a non-empty union). - * `truthmines-check` — `truthmines check`: `ix check-rs --anon` over + * `truthmines-check` — `truthmines check`: `ix check-lean --anon` over every member piece in the `.ixc`, one subprocess per piece (the fat-profile rung-1 checking story: embarrassingly parallel, peak = max member). @@ -28,11 +34,6 @@ `lake test -- --ignored truthmines-mini` (or `truthmines truthmines-check` for the full corpus). -/ -module - -public import LSpec -public import Benchmarks.TruthMinesSpec.Projection -public import Ix.Cli.CatalogCmd public section @@ -100,7 +101,7 @@ private def driverBuild (mini : Bool) (ixc : System.FilePath) return (true, labels.length, n, none) | .error e => return (false, 0, 0, some s!"verify: {e}") -/-- Drive `truthmines check [--mini]`: the per-piece `ix check-rs +/-- Drive `truthmines check [--mini]`: the per-piece `ix check-lean --anon` sweep over the tier's `.ixc` — the fat-profile checking rung lives in the driver (bounded pool, output captured per piece, tails + solo repro surfaced on rejection); the suite is exit-code diff --git a/Tests/IxonV4Primitives.lean b/Tests/IxonV4Primitives.lean index dd2baf1d3..83923556e 100644 --- a/Tests/IxonV4Primitives.lean +++ b/Tests/IxonV4Primitives.lean @@ -1,7 +1,12 @@ import Tests.Ix.Kernel.BuildPrimitives import Tests.Ix.IxonV4Paths +import Ix.CompileCertified import Ix.EnvScope +/-! + Export and verify the Ixon v4 primitive dependency closure and addresses. +-/ + open Tests.Ix.Kernel.BuildPrimitives def usage : String := @@ -28,8 +33,8 @@ def main (args : List String) : IO Unit := do (kernelPrimitives.map parseNameToLean).toList).foldl (fun s (n, _) => s.insert n) {} let constants := leanEnv.constants.toList.filter fun (name, _) => needed.contains name IO.println s!"Compiling {constants.length} primitive dependency declarations" - let raw ← Ix.CompileM.rsCompileEnvFFI constants - let env := raw.toEnv + let out ← IO.ofExcept (← Ix.CompileM.compileCertified leanEnv constants) + let env := out.env let mut text := s!"# Ixon v{Ixon.Env.VERSION} canonical primitive addresses\n" for name in kernelPrimitives do let some named := env.named[parseIxName name]? diff --git a/Tests/Main.lean b/Tests/Main.lean index f4d3edb09..34a6684ad 100644 --- a/Tests/Main.lean +++ b/Tests/Main.lean @@ -3,7 +3,6 @@ import Tests.Aiur.IOBuffer import Tests.Aiur.RustSyntax import Tests.Ix.Ixon import Tests.Ix.IxonRejectParity -import Tests.Ix.IxonCorpus import Tests.Ix.IxonSyntax import Tests.Ix.IxVM import Tests.Ix.IxVM.Eval @@ -17,58 +16,34 @@ import Tests.Ix.Claim import Tests.Ix.Merkle import Tests.Ix.AssumptionTree import Tests.Ix.Commit -import Tests.Ix.Compile import Tests.Ix.Compile.ValidateAux import Tests.Ix.RoleCompile -import Tests.Ix.RecordDecoding import Tests.Ix.RoleEquations import Tests.Ix.LeanExport import Tests.Ix.HostAdmissionCli -import Tests.Ix.Compile.DecompileDiff -import Tests.Ix.Compile.AuxiliaryClosure import Tests.Ix.Compile.AuxiliaryClosureCanon import Tests.Ix.Compile.IndFlags -import Tests.Ix.Compile.ClaimConflict -import Tests.Ix.Compile.ExpressionIndex -import Tests.Ix.Compile.ExpressionFrame -import Tests.Ix.Compile.ExactKey -import Tests.Ix.Compile.UniverseCache +import Tests.Ix.CompileDeterminism +import Tests.Ix.Kernel.AdmissionHashDiff +import Tests.Ix.Kernel.UserAxioms import Tests.Ix.Compile.KernelReportTests import Tests.Ix.Compile.LeanReportTests -import Tests.Ix.Compile.DriverInput -import Tests.Ix.Compile.LocalBlob -import Tests.Ix.Compile.IndexSafety -import Tests.Ix.Compile.TableWorklist -import Tests.Ix.Compile.HeaderCounts -import Tests.Ix.Compile.PayloadArrays -import Tests.Ix.Compile.UniverseWire import Tests.Ix.Compile.GroundRefusal -import Tests.Ix.Compile.WorkerSnapshot import Tests.Ix.Compile.SelectedClosure import Tests.Ix.Compile.ClosureWhole -import Tests.Ix.Compile.ClosurePhase4 import Tests.Ix.Compile.CheckerSupport import Tests.Ix.Compile.PackUnits -import Tests.Ix.Compile.ScheduleIdentity -import Tests.Ix.Compile.SourceGraph -import Tests.Ix.Compile.Performance import Tests.Ix.IxVM.SubstBoundaries -import Tests.Ix.GroundTests import Tests.Ix.Decompile import Tests.Ix.Kernel.BuildPrimitives -import Tests.Ix.Kernel.BuildPrimOrigs -import Tests.Ix.Kernel.CheckEnv import Tests.Ix.Kernel.CheckCompPoly import Tests.Ix.Kernel.CheckPrimeGaps import Tests.Ix.Kernel.CheckTauCeti import Tests.Ix.Kernel.CheckTauCetiReduction -import Tests.Ix.Kernel.Roundtrip -import Tests.Ix.Kernel.RoundtripNoCompile import Tests.Ix.Kernel.Tutorial import Tests.Ix.Kernel.Arena import Tests.Ix.Kernel.FastPaths import Tests.Ix.Kernel.PrimAddrs -import Tests.Ix.Kernel.AdmissionHashDiff import Tests.Ix.Kernel.ReaderRoundtrip import Tests.Ix.Kernel.FidelityAccess import Tests.Ix.Kernel.PreludeFidelity @@ -79,21 +54,14 @@ import Tests.Ix.Cert.Statement import Tests.Ix.InitStd import Tests.RoleTableWire import Tests.Ix.ResourceHints -import Tests.Ix.Kernel.ReadCache import Tests.Ix.Kernel.RecordCheck import Tests.Ix.Kernel.CheckLeanTargets import Tests.Ix.Kernel.CheckLeanCli import Tests.Ix.Kernel.ValidateReaderCli -import Tests.Ix.RustSerialize -import Tests.Ix.RustDecompile import Tests.Ix.SharingExact import Tests.Ix.SharingUniform -import Tests.Ix.SharingFamily -import Tests.Ix.SharingFamilyNormalize -import Tests.Ix.SharingContext import Tests.Ix.SharingComponents import Tests.Ix.SharingTiered -import Tests.Ix.SharingExactFFI import Tests.Ix.SourceContract import Tests.Ix.SourceContract.ImportCheck import Tests.Ix.SourceContract.SyntaxCheck @@ -102,9 +70,6 @@ import Tests.Ix.BenchMeasures import Tests.Ix.IxonUniverse import Tests.Ix.Kernel.Pins import Tests.Ix.Kernel.AccelSemantics -import Tests.Ix.CanonM -import Tests.Ix.GraphM -import Tests.Ix.CondenseM import Tests.FFI import Tests.Keccak import Tests.MultiStark @@ -117,21 +82,22 @@ import Tests.AggrActivation import Tests.Cli import Tests.CompressionCli import Tests.Ix.Ixes -import Tests.ShardMap import Tests.Ix.EnvBody import Tests.Ix.MetaEnv import Tests.Ix.Catalog import Tests.Ix.CatalogDedup -import Tests.Ix.CompileDeterminism import Tests.Ix.ImportIxe import Tests.Ix.TruthMinesRecords import Tests.Ix.TruthMines import Tests.Registry -import Tests.Ix.RustCompile import Ix.Common import Ix.Meta import Ix.IxVM +/-! + Main test runner and registry for `IxTests`. +-/ + open Tests.Registry in /-- Every `IxTests` entry with its tier (`Tests/Registry.lean`): primary entries run with no arguments; the others are named, or selected by tier @@ -144,54 +110,26 @@ def registry : List Entry := [ .suite "import-ixe" .primary Tests.Ix.ImportIxe.suite, .suite "lean-export" .primary Tests.Ix.LeanExport.suite, .suite "truthmines-spec" .primary Tests.Ix.TruthMinesRecords.suite, - .suite "ixon" .primary (Tests.Ixon.suite ++ Tests.Ix.IxonUniverse.suite), + -- `concurrently`: the long role and admission checks run at once + .suiteIO "ixon" .primary (concurrently (Tests.Ixon.suite ++ Tests.Ix.IxonUniverse.suite)), .suiteIO "ixon-reject-parity" .primary Tests.Ix.IxonRejectParity.suite, - .suite "record-decoding" .primary Tests.Ix.RecordDecoding.suite, .suite "role-equations" .primary Tests.Ix.RoleEquations.suite, .suite "ixon-syntax" .primary Tests.IxonSyntax.suite, .suite "claim" .primary Tests.Claim.suite, .suite "merkle" .primary Tests.Merkle.suite, .suite "assumption-tree" .primary Tests.AssumptionTree.suite, .suite "commit" .primary Tests.Commit.suite, - .suite "canon" .primary [Tests.CanonM.suite], .suite "keccak" .primary Tests.Keccak.suite, - .suite "exact-sharing" .primary (Tests.SharingExact.suite ++ Tests.SharingUniform.suite ++ Tests.SharingTiered.suite), - .suite "sharing-family" .primary Tests.SharingFamily.suite, - .suite "sharing-family-normalize" .primary Tests.SharingFamilyNormalize.suite, - .suite "sharing-context" .primary Tests.SharingContext.suite, + .suiteIO "exact-sharing" .primary (concurrently (Tests.SharingExact.suite ++ Tests.SharingUniform.suite ++ Tests.SharingTiered.suite)), .suite "sharing-components" .primary Tests.SharingComponents.suite, - .suite "exact-sharing-ffi" .primary Tests.SharingExactFFI.suite, .suite "source-contract" .primary (Tests.Ix.SourceContract.suite ++ Tests.Ix.SourceContract.Driver.suite), - .suite "graph-unit" .primary Tests.Ix.GraphM.suite, - .suite "condense-unit" .primary Tests.Ix.CondenseM.suite, .suite "bench-measures" .primary Tests.Ix.BenchMeasures.suite, - -- the scan, and the compile's named refusal of ungrounded input (Lean and Rust) - .suite "ground-unit" .primary (Tests.Ground.suite ++ Tests.Ix.Compile.GroundRefusal.suite), - -- expression wire indices that do not fit a UInt64 are refused, never wrapped - .suite "expression-index" .primary Tests.Ix.Compile.ExpressionIndex.suite, - -- the ordinary expression compiler's frame: spines, caching, failure effects, DAGs - .suite "expression-frame" .primary Tests.Ix.Compile.ExpressionFrame.suite, - -- exact expression-cache keys: digest aliases never identify terms; DAG comparison - .suite "exact-expression-key" .primary Tests.Ix.Compile.ExactKey.suite, - -- exact universe-cache keys: the level and its complete parameter context - .suite "universe-cache" .primary Tests.Ix.Compile.UniverseCache.suite, + -- the compile's named refusal of ungrounded input (the IO pipeline and the pure compile) + .suite "ground-unit" .primary Tests.Ix.Compile.GroundRefusal.suite, -- the strict readers of the checkers' reports (Tests.Ix.Compile.KernelReport) .suite "compiler-reports" .primary Tests.Ix.Compile.KernelReportTests.suite, .suite "compiler-lean-reports" .primary Tests.Ix.Compile.LeanReportTests.suite, - -- the compile drivers refuse a condensation block without a reference set - .suite "compile-driver-input" .primary Tests.Ix.Compile.DriverInput.suite, - .runner "compile-source-graph" .primary Tests.Ix.Compile.SourceGraph.run, - .runner "compiler-cache-regressions" .primary (do - Tests.Ix.Compile.Performance.run - return 0), .runner "ixvm-subst-boundaries" .primary Tests.Ix.IxVM.SubstBoundaries.runSuite, - .suite "local-blob" .primary Tests.Ix.Compile.LocalBlob.suite, - .suite "worker-snapshot" .primary Tests.Ix.Compile.WorkerSnapshot.suite, - .suite "compiler-index-safety" .primary Tests.Ix.Compile.IndexSafety.suite, - .suite "table-worklist" .primary Tests.Ix.Compile.TableWorklist.suite, - .suite "header-count" .primary Tests.Ix.Compile.HeaderCounts.suite, - .suite "payload-arrays" .primary Tests.Ix.Compile.PayloadArrays.suite, - .suite "universe-wire" .primary Tests.Ix.Compile.UniverseWire.suite, -- selected closures (`collectSelectedDeps`): whole logical units, a -- matcher's unit, mutual siblings, checker support from `Role.deps` .suite "compiler-selected-closure" .primary Tests.Ix.Compile.SelectedClosure.suite, @@ -212,21 +150,19 @@ def registry : List Entry := [ .suite "kernel-source-ingress" .primary Tests.Ix.Kernel.SourceIngress.suite, -- the typing tier's inductive install and its answer view .suite "kernel-tier-ind" .primary Tests.Ix.Kernel.TierInd.suite, - -- the resource validator's typing input (IxC's tiered entry): its hints agree with - -- `Ix.Cert.hintsOf`, every record is submitted once, and the compiled fixtures validate + -- the resource validator's typing half on the compiled fixtures .suite "resource-hints" .primary Tests.ResourceHints.suite, -- Source mode's evidence from the committed bundle (`Ix.RoleEvidence.bundleEvidence`), on -- the well-founded operations' Init closure without the certificate theorems .suite "kernel-bundle-evidence" .primary Tests.Ix.Kernel.BundleEvidence.suite, - -- the certification statements on the same fixtures: compile parity, and + -- the certification statements on the same fixtures: the compile's checks, and -- the per-root hypotheses and conclusion .suite "cert-statement" .primary Tests.Ix.Cert.Statement.suite, - -- the environment check's persistent read cache: a run from the mapped plan gives the live rows - .suite "kernel-read-cache" .primary Tests.Ix.Kernel.ReadCache.suite, .suite "kernel-record-check" .primary Tests.Ix.Kernel.RecordCheck.suite, + -- the fold and the role table's admission under each axiom policy (user axioms) + .suite "kernel-user-axioms" .primary Tests.Ix.Kernel.UserAxioms.suite, .suite "kernel-check-lean-targets" .primary Tests.Ix.Kernel.CheckLeanTargets.suite, - .suite "primitive-address-parity" .primary (Tests.Ix.Kernel.BuildPrimitives.paritySuite - ++ Tests.Ix.Kernel.BuildPrimOrigs.paritySuite), + .suite "primitive-address-parity" .primary Tests.Ix.Kernel.BuildPrimitives.paritySuite, .suite "decompile-unit" .primary Tests.Decompile.unitSuite, -- getFileEnv body-inclusion regression guard (IO: loads a fixture file) .suiteIO "getfileenv-body" .primary (do return [← Tests.Ix.EnvBody.suite]), @@ -238,7 +174,7 @@ def registry : List Entry := [ .runner "aiur-rust-syntax" .primary AiurTests.RustSyntax.run, -- Reductions no kernel may make beyond Lean's (class methods reduced -- without their instance, `numBits ≡ 64`, the `Fin.val` commute): every - -- probe's closure through the Rust kernel and IxC. + -- probe's closure through IxC. .runner "kernel-fast-paths" .primary Tests.Ix.Kernel.FastPaths.run, -- The circuit TagN codec against the host codec (about 16 s). .runner "ixvm-tagn" .primary Tests.Ix.IxVM.TagN.runSuite, @@ -289,66 +225,51 @@ def registry : List Entry := [ .runner "ix-aggr" .primary Tests.Aggr.convergedSuite, -- `.ixes` manifest parser: trailing tree/peaks sections and strictness. .runner "ixes-manifest" .primary Tests.Ix.Ixes.suite, + -- Non-canonical stored inductive flags and mixed mutual safety: refused by + -- both Lean drivers and the pure compiler (Tests.Ix.Compile.IndFlags). + .runner "compile-ind-flags" .primary Tests.Ix.Compile.IndFlags.run, + -- Differential comparison of Address.blake3, Address.blake3Pure, and + -- Address.admissionHash across multi-chunk boundaries and an .ixe artifact. + .runner "admission-hash-diff" .primary Tests.Ix.Kernel.AdmissionHashDiff.run, -- CLI integration tests (ci.yml's lean-test job) .runner "cli" (.ci .cli) Tests.Cli.suite, + .suite "compile-determinism" (.ci .cli) Tests.Ix.CompileDeterminism.suite, .suite "kernel-check-lean-cli" (.ci .cli) Tests.Ix.Kernel.CheckLeanCli.suite, .suite "artifact-compile-cli" (.ci .cli) Tests.Ix.RoleCompile.suite, .suite "host-admission-cli" (.ci .cli) Tests.Ix.HostAdmissionCli.suite, -- `ix prove-binding`, shard proofs that assume the binding, and their -- composition with the binding proof file and refusals without it, through - -- the `ix` binary (`aggregate-binding-reuse` without its two aggregates) - .runner "binding-proof-cli" (.ci .cli) Tests.Aggr.BindingReuse.cli, + -- the `ix` binary (`aggregate-binding-reuse` without its two aggregates). + -- Its 64 GiB proving budget would set lean-test's memory peak, so it runs + -- in the compiler-closures partition of the merge queue. + .runner "binding-proof-cli" (.ci .compilerClosures) Tests.Aggr.BindingReuse.cli, -- expensive suites and runners (these require significant RAM) - .suite "shard-map" (.ci .misc) Tests.ShardMap.suite, - .suite "sharing-components-full" (.ci .misc) Tests.SharingComponents.fullSuite, - .suite "compile-determinism" (.ci .compile) Tests.Ix.CompileDeterminism.suite, .suite "catalog-dedup" (.ci .misc) Tests.Ix.CatalogDedup.suite, .suite "truthmines" (.manual "builds the whole TruthMines corpus (network, Mathlib, FLT)") Tests.Ix.TruthMines.buildSuite, .suite "truthmines-check" (.manual "checks the whole TruthMines corpus built by `truthmines`") Tests.Ix.TruthMines.checkSuite, .suite "truthmines-mini" (.manual "builds the small TruthMines tier (network, Mathlib, FLT)") Tests.Ix.TruthMines.miniSuite, - .suite "rust-canon-roundtrip" (.ci .compilePipeline) Tests.CanonM.rustSuiteIO, - .suite "serial-canon-roundtrip" (.ci .compilePipeline) Tests.CanonM.serialSuiteIO, - .suite "parallel-canon-roundtrip" (.ci .compilePipeline) Tests.CanonM.parallelSuiteIO, - .suite "graph-cross" (.ci .compilePipeline) Tests.Ix.GraphM.suiteIO, - .suite "condense-cross" (.ci .compilePipeline) Tests.Ix.CondenseM.suiteIO, - .suite "compile" (.ci .compile) Tests.Compile.compileSuiteIO, - .suite "decompile" (.ci .decompile) Tests.Decompile.decompileSuiteIO, - .suite "rust-serialize" (.ci .compilePipeline) Tests.RustSerialize.rustSerializeSuiteIO, - .suite "ixon-corpus" (.ci .compilePipeline) Tests.Ixon.Corpus.suite, - .suite "rust-decompile" (.ci .compilePipeline) Tests.RustDecompile.rustDecompileSuiteIO, .suite "commit-io" (.ci .misc) Tests.Commit.suiteIO, - .suite "kernel-ixon-roundtrip" (.ci .misc) Tests.Ix.Kernel.Roundtrip.suite, - --.suite "kernel-lean-roundtrip" … Tests.Ix.Kernel.RoundtripNoCompile.suite, .suite "kernel-tutorial" (.ci .misc) Tests.Ix.Kernel.Tutorial.suite, - .suite "kernel-check-env" (.ci .misc) Tests.Ix.Kernel.CheckEnv.suite, - .suite "kernel-check-const" (.ci .misc) Tests.Ix.Kernel.CheckEnv.constSuite, .suite "kernel-check-comppoly" (.manual "needs comppoly.ixe at the repository root") Tests.Ix.Kernel.CheckCompPoly.suite, .suite "kernel-check-primegaps" (.manual "self-contained, but in no gate yet") Tests.Ix.Kernel.CheckPrimeGaps.suite, .suite "kernel-check-tauceti" (.manual "needs tauceti.ixe") Tests.Ix.Kernel.CheckTauCeti.suite, .suite "kernel-check-tauceti-reduction" (.manual "self-contained, but in no gate yet") Tests.Ix.Kernel.CheckTauCetiReduction.suite, - .suite "rust-kernel-build-primitives" (.ci .misc) Tests.Ix.Kernel.BuildPrimitives.suite, - .suite "rust-kernel-build-prim-origs" (.ci .misc) Tests.Ix.Kernel.BuildPrimOrigs.suite, - -- Lean's auxiliaries and mutual definitions on closure-only environments - -- (`collectDeps`), through the Rust compiler and kernel - .suite "auxiliary-closure" (.ci .compilePipeline) Tests.Ix.Compile.AuxiliaryClosure.suite, + .suite "rust-kernel-build-primitives" (.manual "dumps the primitive content-address table for PrimAddrs::new") Tests.Ix.Kernel.BuildPrimitives.suite, -- auxiliary addresses do not depend on the compile set (closure vs whole env) - .suite "canon-closure-aux" (.ci .compilePipeline) Tests.Ix.Compile.AuxiliaryClosureCanon.suite, - -- `Ix.Cert.compile` with its report checks against the IO pipeline and the - -- Rust compiler on Init+Std - .suite "cert-compile-parity-initstd" (.ci .landingInitStd) Tests.Ix.Cert.Statement.initStdSuite, - -- `ix compile-lean` on Init+Std: its report checks, the Rust compiler byte for - -- byte, the reference digest (Tests/Fixtures/initstd-reference.txt), and the - -- Rust kernel on the artifact with every bound role admitted + .suite "canon-closure-aux" (.ci .compilerClosures) Tests.Ix.Compile.AuxiliaryClosureCanon.suite, + -- `ix compile-lean` on Init+Std: its report checks, the reference digest + -- (Tests/Fixtures/initstd-reference.txt), and `ix check-lean` on the artifact + -- with every bound role admitted .runner "initstd-reference" (.ci .landingInitStdCli) Tests.Ix.InitStd.reference, + -- Address.blake3, Address.blake3Pure and Address.admissionHash on every + -- record, blob and the binding of the artifact `initstd-reference` leaves + .runner "admission-hash-diff-initstd" (.ci .landingInitStdCli) Tests.Ix.Kernel.AdmissionHashDiff.runInitStd, -- `ix validate-lean` on Init+Std: compile, serde, projection, reader and -- decompile fidelity .runner "fidelity-initstd" (.ci .landingInitStdCli) Tests.Ix.InitStd.fidelity, - .suite "cert-roles-initstd" (.ci .landingInitStd) Tests.Ix.Cert.Statement.rolesInitStdSuite, - .suite "cert-evidence-agreement" (.ci .landingInitStd) Tests.Ix.Cert.Statement.evidenceSuite, - .suite "cert-evidence-initstd" (.ci .landingInitStd) Tests.Ix.Cert.Statement.evidenceInitStdSuite, - .suite "cert-noctor-initstd" (.ci .landingInitStd) Tests.Ix.Cert.Statement.noCtorInitStdSuite, + .suite "cert-evidence-agreement" .primary Tests.Ix.Cert.Statement.evidenceSuite, .runner "aggregate-proof" (.manual "a proof round trip through the production ix_aggr circuit; minutes of proving") Tests.Aggr.proofRoundTrip, .runner "aggregate-binding-reuse" (.manual "one binding proof file discharges two aggregates, end to end through the `ix` binary; minutes of recursion and about 190 GB, needs `lake build ix`; `binding-proof-cli` runs it without the aggregates") Tests.Aggr.BindingReuse.run, .runner "aggregate-toolchains" (.manual "libraries from four Lean toolchains proven with one IxVM key and joined into one catalog root, end to end through the `ix` binary; tens of minutes of proving, needs `lake build ix` and `zstd`; IX_TOOLCHAINS_MAX_RAM sets the aggregate RAM budget in GiB (default 140)") Tests.Aggr.Toolchains.run, @@ -416,8 +337,11 @@ def registry : List Entry := [ -- this gate compares against a stale kernel. Reuses the -- `kernelChecks` cases (`runParityCase` ignores the FFT pins), so -- the per-constant witness setup runs once, not twice. - let paritySeq := kernelChecks.foldl (init := .done) fun s tc => - s ++ runParityCase vmEnv.compiled tc + let parityTasks ← kernelChecks.mapM fun tc => + IO.asTask (pure (runParityCase vmEnv.compiled tc)) + let paritySeq ← parityTasks.foldlM (init := .done) fun s task => do + let seq ← IO.ofExcept task.get + pure (s ++ seq) let fullSeq := [kernelUnitTests, serdeTest].foldl (init := .done) fun s tc => s ++ fullVmEnv.runTestCase tc -- Shard pipeline: witness built in Rust (thin-frontier claim, @@ -444,44 +368,23 @@ def registry : List Entry := [ let actual := (Aiur.computeStats vmEnv.compiled qc vmEnv.shapes).totalFftCost.round.toUInt64.toNat pure (LSpec.test - s!"Shard pipeline FFT matches: expected 85_826_059_884, got {actual}" - (actual = 85_826_059_884)) + s!"Shard pipeline FFT matches: expected 85_826_059_914, got {actual}" + (actual = 85_826_059_914)) LSpec.lspecIO (.ofList [("ixvm", [evalSeq, evalCompiledSeq, evalCommandSeq, evalDifficultSeq, evalDifferentialSeq, fullSeq, aiurSeq, arenaSeq, exploitSeq, dependencySeq, bindingAnchorSeq, bindingFrontierSeq, paritySeq, shardSeq])]) [], - -- The Rust compiler's output over the fixture corpus decompiled by the - -- pure-Lean decompiler, compared with the source (Tests.Ix.Compile.DecompileDiff). - .env "decompile-diff" (.ci .compilePipeline) Tests.Compile.DecompileDiff.run, - -- Non-canonical stored inductive flags: refused by both compilers - -- (Tests.Ix.Compile.IndFlags). - .runner "compile-ind-flags" (.ci .compilePipeline) Tests.Ix.Compile.IndFlags.run, - -- Single ownership of compiled names: conflicting claims refused by both - -- compilers, and a user constant named like Lean's auxiliary compiled as an - -- ordinary constant (Tests.Ix.Compile.ClaimConflict). - .runner "compile-claim-conflict" (.ci .compilePipeline) Tests.Ix.Compile.ClaimConflict.run, -- Selected closures (Tests.Ix.Compile.{SelectedClosure,ClosureWhole,CheckerSupport,PackUnits}): -- closure against whole, checker-support ground, and `ix pack` carrying the reference closure. .runner "compiler-selected-closure-e2e" (.ci .compilerClosures) Tests.Ix.Compile.SelectedClosure.run, .runner "compile-closure-whole" (.ci .compilerClosures) Tests.Ix.Compile.ClosureWhole.run, - .suite "compile-closure-phase4" .primary Tests.Ix.Compile.ClosurePhase4.suite, .runner "checker-support-regression" (.ci .compilerClosures) Tests.Ix.Compile.CheckerSupport.run, .runner "pack-units" (.ci .compilerClosures) Tests.Ix.Compile.PackUnits.run, - -- The Lean compiler's output does not depend on the schedule: the fold at - -- three widths, the reference schedule both ways and the pipeline at three - -- worker counts, byte-identical (Tests.Ix.Compile.ScheduleIdentity). - .runner "compile-schedule-identity" (.ci .landingCompilers) Tests.Ix.Compile.ScheduleIdentity.run, -- The existing eight IxC selected-producer pins and their subject budgets. .runner "tc-pins" (.ci .compilerClosures) Tests.Tc.Pins.run, -- Exact retained acceleration inputs, value/head witnesses and full closures. .runner "kernel-accel-semantics" (.ci .compilerClosures) Tests.Ix.Kernel.AccelSemantics.run, - .runner "admission-hash-diff" (.ci .landingInitStd) Tests.Ix.Kernel.AdmissionHashDiff.run, - -- the Rust compiler's compile → decompile roundtrip over this binary's - -- environment (Tests.Ix.RustCompile); before the registry, a special case - -- that ran alone and skipped every other name - .env "rust-compile" (.ci .landingCompilers) Tests.Ix.RustCompile.run, - .runner "rust-compile-coverage" .primary Tests.Ix.RustCompile.coverageControl, -- the 132-case, twice-executed `ix_aggr` activation matrix, kept out of the -- primary suite and independent of the large environment .runner "aggregate-activation" (.manual "a diagnostic matrix (132 cases, run twice)") Tests.Aggr.activationAudit, @@ -493,11 +396,12 @@ def registry : List Entry := [ /-- The entries that must stay registered once, in their tier (`Tests.Registry.coverageProblems`): the checks the certification, the -compilers' agreement and the proof system's claims rest on. Moving one to the +compiler and the proof system's claims rest on. Moving one to the manual tier, or deleting it, fails `ixtests-registry`; so does a CI tier with no required entry. -/ def required : List (String × String) := [ ("cert-statement", "primary"), + ("cert-evidence-agreement", "primary"), ("ix-aggr", "primary"), ("lean-export", "primary"), ("io-ingress", "primary"), @@ -511,63 +415,28 @@ def required : List (String × String) := [ ("role-table-wire", "primary"), ("exact-sharing", "primary"), ("ixon-reject-parity", "primary"), - ("record-decoding", "primary"), - ("worker-snapshot", "primary"), ("role-equations", "primary"), ("ixvm-tagn", "primary"), ("ixes-manifest", "primary"), - ("rust-compile-coverage", "primary"), + ("kernel-record-check", "primary"), + ("compile-ind-flags", "primary"), + ("admission-hash-diff", "primary"), ("cli", "ci:cli"), + ("compile-determinism", "ci:cli"), ("kernel-check-lean-cli", "ci:cli"), ("artifact-compile-cli", "ci:cli"), ("host-admission-cli", "ci:cli"), - ("binding-proof-cli", "ci:cli"), - ("rust-serialize", "ci:compile-pipeline"), - ("decompile-diff", "ci:compile-pipeline"), - ("compile-ind-flags", "ci:compile-pipeline"), - ("compile-claim-conflict", "ci:compile-pipeline"), + ("binding-proof-cli", "ci:compiler-closures"), + ("canon-closure-aux", "ci:compiler-closures"), ("compile-closure-whole", "ci:compiler-closures"), ("checker-support-regression", "ci:compiler-closures"), ("tc-pins", "ci:compiler-closures"), ("kernel-accel-semantics", "ci:compiler-closures"), - ("compile", "ci:compile"), - ("compile-determinism", "ci:compile"), - ("decompile", "ci:decompile"), ("ixvm", "ci:misc"), - ("kernel-check-env", "ci:misc"), ("kernel-tutorial", "ci:misc"), - ("compile-schedule-identity", "ci:landing-compilers"), - ("rust-compile", "ci:landing-compilers"), - ("cert-compile-parity-initstd", "ci:landing-initstd"), ("initstd-reference", "ci:landing-initstd-cli"), - ("fidelity-initstd", "ci:landing-initstd-cli"), - ("cert-roles-initstd", "ci:landing-initstd"), - ("cert-evidence-initstd", "ci:landing-initstd"), - ("cert-noctor-initstd", "ci:landing-initstd"), - ("admission-hash-diff", "ci:landing-initstd")] + ("admission-hash-diff-initstd", "ci:landing-initstd-cli"), + ("fidelity-initstd", "ci:landing-initstd-cli")] -def main (args : List String) : IO UInt32 := do - -- Namespace-filtered kernel ixon roundtrip diagnostic. - -- `kernel-roundtrip-ns=Nat.le` runs the same pipeline as the - -- `kernel-ixon-roundtrip` suite (compile → ingress → egress → - -- decompile → hash-compare) but only on the transitive closure of the - -- constants matching the given name prefixes (comma-separated), so a - -- single-family regression can be bisected in seconds instead of a - -- full-env pass. - if let some arg := args.find? (·.startsWith "kernel-roundtrip-ns=") then - let prefixes := (arg.drop "kernel-roundtrip-ns=".length).toString.splitOn "," - |>.filterMap fun s => if s.isEmpty then none else some s.toName - let env ← get_env! - let seeds := env.constants.toList.filterMap fun (n, _) => - if prefixes.any (·.isPrefixOf n) then some n else none - let closed := collectDeps env seeds - IO.println s!"[kernel-roundtrip-ns] {seeds.length} seeds, {closed.length} constants in closure" - let errors ← Tests.Ix.Kernel.Roundtrip.rsKernelRoundtripFFI closed - if errors.isEmpty then - IO.println "[kernel-roundtrip-ns] OK: roundtrip clean" - return 0 - IO.println s!"[kernel-roundtrip-ns] {errors.size} errors:" - for msg in errors[:min 50 errors.size] do - IO.println s!" {msg}" - return 1 +def main (args : List String) : IO UInt32 := Tests.Registry.run registry required get_env! args diff --git a/Tests/Registry.lean b/Tests/Registry.lean index c579adfd4..d6465d3d3 100644 --- a/Tests/Registry.lean +++ b/Tests/Registry.lean @@ -8,8 +8,8 @@ self-test (`ixtests-registry`) checks the registry against the workflows that name those jobs (comments removed), and checks that every required entry is registered once, in its tier, and that the required list names an - entry in every tier that runs in CI. The three landing jobs - hold the end-to-end checks of the compiler and of the certification, which + entry in every tier that runs in CI. The landing job + holds the end-to-end checks of the compiler and of the certification, which are too slow for every pull request and run in the merge queue. Command line (`lake test -- `, `lake exe IxTests `): @@ -17,7 +17,7 @@ - `…`: exactly the named entries, whatever their tier (names match exactly; an unknown name is an error); - `--tier ` (repeatable): every entry of that tier, where `` - is `primary`, `manual`, `ci:`, or `landing` for the three landing jobs; + is `primary`, `manual`, `ci:`, or `landing` for the landing job; - `--ignored`: with no name and no `--tier`, every entry that is not primary; with a name or a `--tier`, nothing more (kept so that `--ignored …` works as before); @@ -41,46 +41,28 @@ namespace Tests.Registry `ci.yml`'s `lean-test` job; the others are `merge-tests.yml` partitions. -/ inductive CiJob where | cli - | compilePipeline | compilerClosures - | compile - | decompile | misc - /-- The compilers on everything at once: the Lean compiler's output does - not depend on its schedule, and the Rust compiler's compile, serialize and - decompile roundtrip over the test environment. -/ - | landingCompilers - /-- End-to-end checks on Init+Std, in process: the certified compile with - its report checks against the pipeline and the Rust compiler, role binding - and evidence, a reading premise on every inductive, and the admission hash. -/ - | landingInitStd /-- Init+Std through the `ix` binary: `ix compile-lean` with its report - checks and the Rust comparison, whose artifact must match the reference - digest and which the Rust kernel checks with every bound role admitted, and - `ix validate-lean` fidelity. A job of its own, so that neither Init+Std - job approaches the merge queue's timeout. -/ + checks, whose artifact must match the reference digest and which + `ix check-lean` checks with every bound role admitted, and + `ix validate-lean` fidelity. -/ | landingInitStdCli deriving DecidableEq, Repr def CiJob.all : List CiJob := - [.cli, .compilePipeline, .compilerClosures, .compile, .decompile, .misc, - .landingCompilers, .landingInitStd, .landingInitStdCli] + [.cli, .compilerClosures, .misc, .landingInitStdCli] /-- The jobs that `--tier landing` selects together. -/ def CiJob.isLanding : CiJob → Bool - | .landingCompilers | .landingInitStd | .landingInitStdCli => true + | .landingInitStdCli => true | _ => false /-- The job's name on the command line (`--tier ci:`) and in the workflows. -/ def CiJob.key : CiJob → String | .cli => "cli" - | .compilePipeline => "compile-pipeline" | .compilerClosures => "compiler-closures" - | .compile => "compile" - | .decompile => "decompile" | .misc => "misc" - | .landingCompilers => "landing-compilers" - | .landingInitStd => "landing-initstd" | .landingInitStdCli => "landing-initstd-cli" /-- Where an entry runs. -/ @@ -135,6 +117,26 @@ def Entry.runner (name : String) (tier : Tier) (run : IO UInt32) : Entry := def Entry.env (name : String) (tier : Tier) (run : Lean.Environment → IO UInt32) : Entry := ⟨name, tier, .env run⟩ +/-- `tests` with every `individualIO` check started now, each on a thread of +its own with its output captured; each check of the result waits for its +thread and then prints that output, so results and output keep their order. +For a suite of independent long checks, the suite takes as long as its +longest check rather than their sum. -/ +def startChecks : LSpec.TestSeq → IO LSpec.TestSeq + | .individualIO descr shown check next => do + let task ← IO.asTask (IO.FS.withIsolatedStreams check) .dedicated + return .individualIO descr shown (do + let (output, result) ← IO.ofExcept (← IO.wait task) + IO.print output + return result) (← startChecks next) + | .individual descr p shown inst next => return .individual descr p shown inst (← startChecks next) + | .group descr tests next => return .group descr (← startChecks tests) (← startChecks next) + | .done => return .done + +/-- `suites`, their checks started at once (`startChecks`). -/ +def concurrently (suites : List LSpec.TestSeq) : IO (List LSpec.TestSeq) := + suites.mapM startChecks + /-- A workflow line without its YAML comment: from the first `#` at the line's start or after a space or a tab. A `#` inside a quoted string after a space is cut too, which can only hide a mention, never invent one. -/ @@ -347,8 +349,8 @@ def run (registry : List Entry) (required : List (String × String)) private def demo : List Entry := [.runner "a" .primary (pure 0), .runner "b" .primary (pure 0), - .runner "x" (.ci .compile) (pure 0), .runner "x-y" (.ci .compile) (pure 0), - .runner "g" (.ci .landingCompilers) (pure 0), .runner "m" (.manual "by hand") (pure 0)] + .runner "x" (.ci .misc) (pure 0), .runner "x-y" (.ci .misc) (pure 0), + .runner "g" (.ci .landingInitStdCli) (pure 0), .runner "m" (.manual "by hand") (pure 0)] private def picks (args : List String) : List String := match parseArgs args {} >>= select demo with @@ -365,29 +367,29 @@ private def picks (args : List String) : List String := #guard picks ["--ignored", "--exclude=m"] == ["x", "x-y", "g"] #guard picks ["--include-ignored"] == ["a", "b", "x", "x-y", "g", "m"] #guard picks ["--include-ignored", "g"] == ["a", "b", "g"] -#guard picks ["--tier", "ci:compile"] == ["x", "x-y"] +#guard picks ["--tier", "ci:misc"] == ["x", "x-y"] #guard picks ["--ignored", "--tier", "landing", "a"] == ["a", "g"] #guard picks ["nope"] == ["error: no test entry named 'nope'"] #guard picks ["--tier", "ci:nope"] == ["error: unknown tier 'ci:nope' (primary, landing, manual, ci:)"] #guard picks ["--tier", "ci:tc"] == ["error: unknown tier 'ci:tc' (primary, landing, manual, ci:)"] -#guard picks ["--tier", "ci:decompile"] == ["error: a --tier selects no entry"] +#guard picks ["--tier", "ci:compiler-closures"] == ["error: a --tier selects no entry"] #guard picks ["--ignored", "--exclude=x,x-y,g,m"] == ["error: nothing selected"] -#guard (problems demo [("w", "--tier ci:compile")]).length == CiJob.all.length - 2 + (CiJob.all.length - 1) +#guard (problems demo [("w", "--tier ci:misc")]).length == CiJob.all.length - 2 + (CiJob.all.length - 1) -#guard coverageProblems demo [("a", "primary"), ("x", "ci:compile")] == [] -#guard coverageProblems demo [("missing", "ci:compile")] == +#guard coverageProblems demo [("a", "primary"), ("x", "ci:misc")] == [] +#guard coverageProblems demo [("missing", "ci:misc")] == ["required entry missing is registered 0 times"] #guard coverageProblems (demo ++ demo) [("a", "primary")] == ["required entry a is registered 2 times"] -#guard coverageProblems demo [("g", "ci:compile-pipeline")] == - ["required entry g runs in ci:landing-compilers, expected ci:compile-pipeline"] +#guard coverageProblems demo [("g", "ci:compiler-closures")] == + ["required entry g runs in ci:landing-initstd-cli, expected ci:compiler-closures"] -- removing a required entry, or moving it to the manual tier, fails the coverage check -#guard (coverageProblems (demo.filter (·.name != "x")) [("x", "ci:compile")]) == +#guard (coverageProblems (demo.filter (·.name != "x")) [("x", "ci:misc")]) == ["required entry x is registered 0 times"] #guard (coverageProblems (demo.map fun e => if e.name == "x" then { e with tier := .manual "x" } else e) - [("x", "ci:compile")]) == ["required entry x runs in manual, expected ci:compile"] -#guard mutationProblems demo [("a", "primary"), ("x", "ci:compile")] == [] + [("x", "ci:misc")]) == ["required entry x runs in manual, expected ci:misc"] +#guard mutationProblems demo [("a", "primary"), ("x", "ci:misc")] == [] -- the required list names every tier that runs in CI, and only those #guard (requiredProblems []).length == ciTierKeys.length @@ -396,14 +398,14 @@ private def picks (args : List String) : List String := ["required entry m names manual, which is not a tier that runs in CI"] -- a commented-out step does not run a tier; a `#` inside a word is kept -#guard stripComment " # run: lake test -- --tier ci:compile" == " " -#guard stripComment "run: lake test -- --tier ci:compile # the compile tier" == - "run: lake test -- --tier ci:compile " +#guard stripComment " # run: lake test -- --tier ci:misc" == " " +#guard stripComment "run: lake test -- --tier ci:misc # the misc tier" == + "run: lake test -- --tier ci:misc " #guard stripComment "echo a#b" == "echo a#b" -#guard (problems demo [("w", "#--tier ci:compile\n # --tier ci:landing-compilers")]).length == +#guard (problems demo [("w", "#--tier ci:misc\n # --tier ci:landing-initstd-cli")]).length == CiJob.all.length - 2 + CiJob.all.length -- a longer key that a job's key is a prefix of does not run that job -#guard (problems demo [("w", "--tier ci:compile-pipeline")]).contains - "no workflow runs '--tier ci:compile'" +#guard (problems demo [("w", "--tier ci:misc-extra")]).contains + "no workflow runs '--tier ci:misc'" end Tests.Registry diff --git a/Tests/ShardMap.lean b/Tests/ShardMap.lean deleted file mode 100644 index 4fddeca8d..000000000 --- a/Tests/ShardMap.lean +++ /dev/null @@ -1,432 +0,0 @@ -module - -public import Ix.ShardMap -public import LSpec - -open LSpec Ix - -namespace Tests.ShardMap - -def testInsertAndGet : TestSeq := - .individualIO "basic insert and get" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - map.insert "hello" 42 - let v ← map.get? "hello" - pure (v == some 42, 0, 0, none)) .done - -def testGetNonExistent : TestSeq := - .individualIO "get non-existent returns none" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - let v ← map.get? "nonexistent" - pure (v == none, 0, 0, none)) .done - -def testMultipleInserts : TestSeq := - .individualIO "multiple inserts" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - map.insert "foo" 1 - map.insert "bar" 2 - map.insert "baz" 3 - let v1 ← map.get? "foo" - let v2 ← map.get? "bar" - let v3 ← map.get? "baz" - pure (v1 == some 1 && v2 == some 2 && v3 == some 3, 0, 0, none)) .done - -def testOverwrite : TestSeq := - .individualIO "overwrite existing key" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - map.insert "key" 1 - map.insert "key" 2 - let v ← map.get? "key" - pure (v == some 2, 0, 0, none)) .done - -def testSize : TestSeq := - .individualIO "size" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - map.insert "a" 1 - map.insert "b" 2 - map.insert "c" 3 - let sz ← map.size - pure (sz == 3, 0, 0, none)) .done - -def testContains : TestSeq := - .individualIO "contains" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - map.insert "exists" 1 - let c1 ← map.contains "exists" - let c2 ← map.contains "missing" - pure (c1 && !c2, 0, 0, none)) .done - -def testRemove : TestSeq := - .individualIO "remove" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - map.insert "key" 42 - let removed ← map.remove "key" - let after ← map.get? "key" - pure (removed == some 42 && after == none, 0, 0, none)) .done - -def testModify : TestSeq := - .individualIO "modify" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - map.insert "key" 10 - let modified ← map.modify "key" (· + 5) - let v ← map.get? "key" - pure (modified && v == some 15, 0, 0, none)) .done - -def testGetOrInsertExisting : TestSeq := - .individualIO "getOrInsert existing" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - map.insert "key" 42 - let v ← map.getOrInsert "key" (fun () => pure 999) - pure (v == 42, 0, 0, none)) .done - -def testGetOrInsertNew : TestSeq := - .individualIO "getOrInsert new" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - let v ← map.getOrInsert "key" (fun () => pure 999) - let check ← map.get? "key" - pure (v == 999 && check == some 999, 0, 0, none)) .done - -def testClear : TestSeq := - .individualIO "clear" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - map.insert "a" 1 - map.insert "b" 2 - map.clear - let sz ← map.size - pure (sz == 0, 0, 0, none)) .done - -def testToList : TestSeq := - .individualIO "toList" none (do - let map ← ShardMap.new (α := Nat) (β := String) (shardBits := 1) - map.insert 1 "one" - map.insert 2 "two" - let list ← map.toList - pure (list.length == 2, 0, 0, none)) .done - --- Concurrent tests - -/-- Test concurrent reads don't block each other with SharedMutex -/ -def testConcurrentReads : TestSeq := - .individualIO "concurrent reads" none (do - let map ← ShardMap.new (α := Nat) (β := Nat) (shardBits := 2) - -- Insert many values - for i in [:1000] do - map.insert i (i * 2) - -- Spawn many concurrent readers - let numReaders := 32 - let mut tasks : Array (Task (Except IO.Error Bool)) := #[] - for _ in [:numReaders] do - let task ← IO.asTask do - let mut allOk := true - for i in [:1000] do - let v ← map.get? i - if v != some (i * 2) then allOk := false - pure allOk - tasks := tasks.push task - -- Wait for all readers - let mut allPassed := true - for task in tasks do - match task.get with - | .ok ok => if !ok then allPassed := false - | .error _ => allPassed := false - pure (allPassed, 0, 0, none)) .done - -/-- Test concurrent writes to different keys -/ -def testConcurrentWritesDifferentKeys : TestSeq := - .individualIO "concurrent writes different keys" none (do - let map ← ShardMap.new (α := Nat) (β := Nat) (shardBits := 4) - let numWriters := 16 - let keysPerWriter := 100 - let mut tasks : Array (Task (Except IO.Error Unit)) := #[] - for w in [:numWriters] do - let task ← IO.asTask do - for k in [:keysPerWriter] do - let key := w * keysPerWriter + k - map.insert key (key * 3) - tasks := tasks.push task - -- Wait for all writers and check for errors - for task in tasks do - let _ ← IO.ofExcept task.get - -- Verify all values - let mut allCorrect := true - for i in [:(numWriters * keysPerWriter)] do - let v ← map.get? i - if v != some (i * 3) then allCorrect := false - pure (allCorrect, 0, 0, none)) .done - -/-- Test concurrent getOrInsert with same keys (race condition test) -/ -def testConcurrentGetOrInsert : TestSeq := - .individualIO "concurrent getOrInsert consistency" none (do - let map ← ShardMap.new (α := Nat) (β := Nat) (shardBits := 2) - let numWorkers := 32 - let numKeys := 100 - -- All workers try to getOrInsert the same keys - let mut tasks : Array (Task (Except IO.Error (Array Nat))) := #[] - for w in [:numWorkers] do - let task ← IO.asTask do - let mut results : Array Nat := #[] - for k in [:numKeys] do - let v ← map.getOrInsert k (fun () => pure (w * 1000 + k)) - results := results.push v - pure results - tasks := tasks.push task - -- Wait for all workers and collect results - let mut allResults : Array (Array Nat) := #[] - for task in tasks do - match task.get with - | .ok results => allResults := allResults.push results - | .error _ => pure () - -- For each key, all workers should get the same value (whoever inserted first wins) - let mut consistent := true - for k in [:numKeys] do - let firstVal := allResults[0]![k]! - for results in allResults do - if results[k]! != firstVal then consistent := false - pure (consistent, 0, 0, none)) .done - -/-- Test getOrInsertLazy works correctly -/ -def testGetOrInsertLazy : TestSeq := - .individualIO "getOrInsertLazy" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - let v1 ← map.getOrInsertLazy "key" (fun () => 42) - let v2 ← map.getOrInsertLazy "key" (fun () => 999) -- Should not be called - pure (v1 == 42 && v2 == 42, 0, 0, none)) .done - -/-- Test getOrInsertIO works correctly -/ -def testGetOrInsertIO : TestSeq := - .individualIO "getOrInsertIO" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - let counter ← IO.mkRef 0 - let v1 ← map.getOrInsertIO "key" (fun () => do counter.modify (· + 1); pure 42) - let v2 ← map.getOrInsertIO "key" (fun () => do counter.modify (· + 1); pure 999) - let calls ← counter.get - pure (v1 == 42 && v2 == 42 && calls == 1, 0, 0, none)) .done - -/-! ## Tests for try operations -/ - -/-- Test tryGet? returns value when unlocked -/ -def testTryGetUnlocked : TestSeq := - .individualIO "tryGet? unlocked" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - map.insert "key" 42 - let v ← map.tryGet? "key" - pure (v == some (some 42), 0, 0, none)) .done - -/-- Test tryGet? returns none for non-existent key -/ -def testTryGetNonExistent : TestSeq := - .individualIO "tryGet? non-existent" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - let v ← map.tryGet? "missing" - pure (v == some none, 0, 0, none)) .done - -/-- Test tryInsert succeeds when unlocked -/ -def testTryInsert : TestSeq := - .individualIO "tryInsert" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - let ok ← map.tryInsert "key" 42 - let v ← map.get? "key" - pure (ok && v == some 42, 0, 0, none)) .done - -/-- Test tryGetOrInsertLazy works correctly -/ -def testTryGetOrInsertLazy : TestSeq := - .individualIO "tryGetOrInsertLazy" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - let v1 ← map.tryGetOrInsertLazy "key" (fun () => 42) - let v2 ← map.tryGetOrInsertLazy "key" (fun () => 999) -- Should return existing - pure (v1 == some 42 && v2 == some 42, 0, 0, none)) .done - -/-! ## Tests for insertMany -/ - -/-- Test insertMany inserts all items correctly -/ -def testInsertMany : TestSeq := - .individualIO "insertMany" none (do - let map ← ShardMap.new (α := Nat) (β := String) (shardBits := 2) - let items := #[(1, "one"), (2, "two"), (3, "three"), (4, "four"), (5, "five")] - map.insertMany items - let v1 ← map.get? 1 - let v2 ← map.get? 2 - let v3 ← map.get? 3 - let v4 ← map.get? 4 - let v5 ← map.get? 5 - let sz ← map.size - pure (v1 == some "one" && v2 == some "two" && v3 == some "three" - && v4 == some "four" && v5 == some "five" && sz == 5, 0, 0, none)) .done - -/-- Test insertMany with empty array -/ -def testInsertManyEmpty : TestSeq := - .individualIO "insertMany empty" none (do - let map ← ShardMap.new (α := Nat) (β := Nat) (shardBits := 2) - map.insertMany #[] - let sz ← map.size - pure (sz == 0, 0, 0, none)) .done - -/-- Test insertMany overwrites existing keys -/ -def testInsertManyOverwrite : TestSeq := - .individualIO "insertMany overwrite" none (do - let map ← ShardMap.new (α := Nat) (β := Nat) (shardBits := 2) - map.insert 1 100 - map.insertMany #[(1, 200), (2, 300)] - let v1 ← map.get? 1 - let v2 ← map.get? 2 - pure (v1 == some 200 && v2 == some 300, 0, 0, none)) .done - -/-- Test concurrent insertMany operations -/ -def testConcurrentInsertMany : TestSeq := - .individualIO "concurrent insertMany" none (do - let map ← ShardMap.new (α := Nat) (β := Nat) (shardBits := 4) - let numWorkers := 8 - let itemsPerWorker := 100 - let mut tasks : Array (Task (Except IO.Error Unit)) := #[] - for w in [:numWorkers] do - let task ← IO.asTask do - let items : Array (Nat × Nat) := Array.range itemsPerWorker |>.map fun i => - let key := w * itemsPerWorker + i - (key, key * 2) - map.insertMany items - tasks := tasks.push task - -- Wait for all workers - for task in tasks do - let _ ← IO.ofExcept task.get - -- Verify all values - let mut allCorrect := true - for i in [:(numWorkers * itemsPerWorker)] do - let v ← map.get? i - if v != some (i * 2) then allCorrect := false - let sz ← map.size - pure (allCorrect && sz == numWorkers * itemsPerWorker, 0, 0, none)) .done - -/-! ## Tests for modifyGet -/ - -/-- Test modifyGet returns result and updates value -/ -def testModifyGet : TestSeq := - .individualIO "modifyGet" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - map.insert "counter" 10 - let result ← map.modifyGet "counter" fun v => (v, v + 1) - let newVal ← map.get? "counter" - pure (result == some 10 && newVal == some 11, 0, 0, none)) .done - -/-- Test modifyGet returns none for non-existent key -/ -def testModifyGetNonExistent : TestSeq := - .individualIO "modifyGet non-existent" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - let result ← map.modifyGet "missing" fun v => (v, v + 1) - pure (result == none, 0, 0, none)) .done - -/-- Test modifyGet can return different type than value -/ -def testModifyGetDifferentType : TestSeq := - .individualIO "modifyGet different type" none (do - let map ← ShardMap.new (α := String) (β := Nat) (shardBits := 2) - map.insert "key" 42 - let result ← map.modifyGet "key" fun v => (s!"was {v}", v * 2) - let newVal ← map.get? "key" - pure (result == some "was 42" && newVal == some 84, 0, 0, none)) .done - -/-- Test concurrent modifyGet operations -/ -def testConcurrentModifyGet : TestSeq := - .individualIO "concurrent modifyGet" none (do - let map ← ShardMap.new (α := Nat) (β := Nat) (shardBits := 2) - -- Initialize counters - for i in [:10] do - map.insert i 0 - -- Many workers increment counters - let numWorkers := 32 - let incrementsPerWorker := 100 - let mut tasks : Array (Task (Except IO.Error Unit)) := #[] - for _ in [:numWorkers] do - let task ← IO.asTask do - for _ in [:incrementsPerWorker] do - for i in [:10] do - let _ ← map.modifyGet i fun v => ((), v + 1) - tasks := tasks.push task - -- Wait for all workers - for task in tasks do - let _ ← IO.ofExcept task.get - -- Each counter should have been incremented numWorkers * incrementsPerWorker times - let expected := numWorkers * incrementsPerWorker - let mut allCorrect := true - for i in [:10] do - let v ← map.get? i - if v != some expected then allCorrect := false - pure (allCorrect, 0, 0, none)) .done - -/-! ## Tests for newWithCapacity -/ - -/-- Test newWithCapacity creates a working map -/ -def testNewWithCapacity : TestSeq := - .individualIO "newWithCapacity" none (do - let map ← ShardMap.newWithCapacity (α := Nat) (β := Nat) - (shardBits := 4) (capacityPerShard := 1000) - -- Insert many values - for i in [:500] do - map.insert i (i * 2) - -- Verify values - let mut allOk := true - for i in [:500] do - let v ← map.get? i - if v != some (i * 2) then allOk := false - let sz ← map.size - pure (allOk && sz == 500, 0, 0, none)) .done - -/-- Test concurrent tryGet? operations -/ -def testConcurrentTryGet : TestSeq := - .individualIO "concurrent tryGet?" none (do - let map ← ShardMap.new (α := Nat) (β := Nat) (shardBits := 2) -- 4 shards - for i in [:100] do - map.insert i i - -- Many readers trying tryGet? - let numReaders := 32 - let mut tasks : Array (Task (Except IO.Error Nat)) := #[] - for _ in [:numReaders] do - let task ← IO.asTask do - let mut successes := 0 - for i in [:100] do - match ← map.tryGet? i with - | some _ => successes := successes + 1 - | none => pure () -- Shard was locked (rare with reads) - pure successes - tasks := tasks.push task - -- Count after joining: IO.Ref.modify is not an atomic cross-task update. - let mut successes := 0 - for task in tasks do - successes := successes + (← IO.ofExcept task.get) - -- Most should succeed (reads don't block each other with SharedMutex) - -- With 32 readers × 100 keys = 3200 operations, expect at least 100 to succeed - -- (conservative threshold to avoid flakiness) - pure (successes > 100, 0, 0, none)) .done - -public def suite : List TestSeq := [ - testInsertAndGet, - testGetNonExistent, - testMultipleInserts, - testOverwrite, - testSize, - testContains, - testRemove, - testModify, - testGetOrInsertExisting, - testGetOrInsertNew, - testClear, - testToList, - testConcurrentReads, - testConcurrentWritesDifferentKeys, - testConcurrentGetOrInsert, - testGetOrInsertLazy, - testGetOrInsertIO, - testTryGetUnlocked, - testTryGetNonExistent, - testTryInsert, - testTryGetOrInsertLazy, - testNewWithCapacity, - testConcurrentTryGet, - testInsertMany, - testInsertManyEmpty, - testInsertManyOverwrite, - testConcurrentInsertMany, - testModifyGet, - testModifyGetNonExistent, - testModifyGetDifferentType, - testConcurrentModifyGet, -] - -end Tests.ShardMap diff --git a/crates/aiur/cuda/generated/production/ix_aggr.cu b/crates/aiur/cuda/generated/production/ix_aggr.cu index 97da9aa3c..8ae8a5ac8 100644 --- a/crates/aiur/cuda/generated/production/ix_aggr.cu +++ b/crates/aiur/cuda/generated/production/ix_aggr.cu @@ -2790,12 +2790,236 @@ extern "C" int aiur_trace_ix_aggr_233(int device, const uint8_t* seeds, uint32_t return upload(device, seeds, encoding, stride, real, rows, {width, selectors, auxiliaries}, output, launch_ix_aggr_233); } +__device__ __constant__ uint64_t inverse_261_0[] = {0ULL, 1ULL}; +__device__ __constant__ uint64_t inverse_261_1[] = {18446744069414584320ULL, 0ULL}; +template struct Seed_ix_aggr_261 { + const uint8_t* data; + __device__ __forceinline__ uint64_t word(size_t index) const { + if constexpr (!Typed) return reinterpret_cast(data)[index]; + switch (index) { + case 0: return load_u64(data + 0); + case 1: return load_u32(data + 8); + case 2: return load_u32(data + 12); + case 3: return load_u32(data + 16); + case 4: return load_u8(data + 44); + case 5: return load_u32(data + 20); + case 6: return load_u32(data + 24); + case 7: return load_u8(data + 45); + case 8: return load_u32(data + 28); + case 9: return load_u32(data + 32); + case 10: return load_u8(data + 46); + case 11: return load_u32(data + 36); + case 12: return load_u32(data + 40); + case 13: return load_u8(data + 47); + case 14: return load_u8(data + 48); + default: return 0; + } + } +}; +template __device__ __forceinline__ uint32_t row_ix_aggr_261(Seed_ix_aggr_261 seed, Layout layout, uint64_t* row) { + const uint64_t v_0 = seed.word(1); + row[0] = v_0; + const uint64_t v_1 = seed.word(2); + row[1] = v_1; + const uint64_t v_2 = seed.word(3); + row[2] = v_2; + const uint64_t v_3 = seed.word(4); + const uint64_t v_4 = seed.word(5); + const uint64_t v_5 = seed.word(6); + row[layout.auxiliaries + 1] = v_3; + row[layout.auxiliaries + 2] = v_4; + row[layout.auxiliaries + 3] = v_5; + const uint64_t v_6 = seed.word(7); + const uint64_t v_7 = seed.word(8); + const uint64_t v_8 = seed.word(9); + row[layout.auxiliaries + 4] = v_6; + row[layout.auxiliaries + 5] = v_7; + row[layout.auxiliaries + 6] = v_8; + switch (v_3) { + case 1ULL: { + row[layout.selectors + 0] = 1; + return 0; + } + case 0ULL: { + switch (v_6) { + case 1ULL: { + row[layout.selectors + 1] = 1; + return 0; + } + case 0ULL: { + const uint64_t v_9 = seed.word(10); + const uint64_t v_10 = seed.word(11); + const uint64_t v_11 = seed.word(12); + row[layout.auxiliaries + 7] = v_9; + row[layout.auxiliaries + 8] = v_10; + row[layout.auxiliaries + 9] = v_11; + switch (v_9) { + case 0ULL: { + const uint64_t v_12 = seed.word(13); + row[layout.auxiliaries + 10] = v_12; + switch (v_12) { + case 0ULL: { + const uint64_t v_13 = seed.word(14); + row[layout.auxiliaries + 11] = v_13; + row[layout.selectors + 2] = 1; + return 0; + } + case 1ULL: { + row[layout.selectors + 3] = 1; + return 0; + } + default: { + row[layout.auxiliaries + 11] = (v_12 <= 1ULL ? inverse_261_0[v_12] : inverse(goldilocks_sub(v_12, 0ULL))); + row[layout.auxiliaries + 12] = (v_12 <= 1ULL ? inverse_261_1[v_12] : inverse(goldilocks_sub(v_12, 1ULL))); + const uint64_t v_17 = seed.word(14); + row[layout.auxiliaries + 13] = v_17; + row[layout.selectors + 4] = 1; + return 0; + } + } + } + default: { + return 2; + } + } + } + default: { + return 2; + } + } + } + default: { + return 2; + } + } +} +template __global__ void kernel_ix_aggr_261(const uint8_t* __restrict__ seeds, size_t real, size_t rows, Layout layout, uint64_t* __restrict__ output, uint32_t* error) { + const size_t r = size_t(blockIdx.x) * blockDim.x + threadIdx.x; + if (r >= real) return; + uint64_t* row = output + r * layout.width; + constexpr size_t stride = Typed ? 56 : 120; + const Seed_ix_aggr_261 seed{seeds + r * stride}; + row[layout.auxiliaries] = seed.word(0); + const uint32_t status = row_ix_aggr_261(seed, layout, row); + if (status) atomicCAS(error, 0U, status); +} +cudaError_t launch_ix_aggr_261(const uint8_t* seeds, uint32_t encoding, size_t real, size_t rows, Layout layout, uint64_t* output, uint32_t* error) { + if (encoding == 1) kernel_ix_aggr_261<<>>(seeds, real, rows, layout, output, error); + else kernel_ix_aggr_261<<>>(seeds, real, rows, layout, output, error); + return cudaGetLastError(); +} +extern "C" int aiur_trace_ix_aggr_261(int device, const uint8_t* seeds, uint32_t encoding, size_t real, size_t rows, size_t width, size_t selectors, size_t auxiliaries, uint64_t* output) { + if (selectors < 3 || auxiliaries < selectors || auxiliaries - selectors < 5 || width < auxiliaries || width - auxiliaries < 14) return int(cudaErrorInvalidValue); + const size_t stride = encoding == 1 ? 56 : encoding == 0 ? 120 : 0; + return upload(device, seeds, encoding, stride, real, rows, {width, selectors, auxiliaries}, output, launch_ix_aggr_261); +} + +__device__ __constant__ uint64_t inverse_262_0[] = {0ULL, 1ULL}; +__device__ __constant__ uint64_t inverse_262_1[] = {18446744069414584320ULL, 0ULL}; +template struct Seed_ix_aggr_262 { + const uint8_t* data; + __device__ __forceinline__ uint64_t word(size_t index) const { + if constexpr (!Typed) return reinterpret_cast(data)[index]; + switch (index) { + case 0: return load_u64(data + 0); + case 1: return load_u32(data + 8); + case 2: return load_u32(data + 12); + case 3: return load_u8(data + 32); + case 4: return load_u32(data + 16); + case 5: return load_u32(data + 20); + case 6: return load_u8(data + 33); + case 7: return load_u32(data + 24); + case 8: return load_u32(data + 28); + case 9: return load_u8(data + 34); + default: return 0; + } + } +}; +template __device__ __forceinline__ uint32_t row_ix_aggr_262(Seed_ix_aggr_262 seed, Layout layout, uint64_t* row) { + const uint64_t v_0 = seed.word(1); + row[0] = v_0; + const uint64_t v_1 = seed.word(2); + row[1] = v_1; + const uint64_t v_2 = seed.word(3); + const uint64_t v_3 = seed.word(4); + const uint64_t v_4 = seed.word(5); + row[layout.auxiliaries + 1] = v_2; + row[layout.auxiliaries + 2] = v_3; + row[layout.auxiliaries + 3] = v_4; + const uint64_t v_5 = seed.word(6); + const uint64_t v_6 = seed.word(7); + const uint64_t v_7 = seed.word(8); + row[layout.auxiliaries + 4] = v_5; + row[layout.auxiliaries + 5] = v_6; + row[layout.auxiliaries + 6] = v_7; + switch (v_2) { + case 1ULL: { + row[layout.selectors + 0] = 1; + return 0; + } + case 0ULL: { + switch (v_5) { + case 1ULL: { + row[layout.selectors + 1] = 1; + return 0; + } + case 0ULL: { + const uint64_t v_8 = seed.word(9); + row[layout.auxiliaries + 7] = v_8; + switch (v_8) { + case 0ULL: { + row[layout.selectors + 2] = 1; + return 0; + } + case 1ULL: { + row[layout.selectors + 3] = 1; + return 0; + } + default: { + row[layout.auxiliaries + 8] = (v_8 <= 1ULL ? inverse_262_0[v_8] : inverse(goldilocks_sub(v_8, 0ULL))); + row[layout.auxiliaries + 9] = (v_8 <= 1ULL ? inverse_262_1[v_8] : inverse(goldilocks_sub(v_8, 1ULL))); + row[layout.selectors + 4] = 1; + return 0; + } + } + } + default: { + return 2; + } + } + } + default: { + return 2; + } + } +} +template __global__ void kernel_ix_aggr_262(const uint8_t* __restrict__ seeds, size_t real, size_t rows, Layout layout, uint64_t* __restrict__ output, uint32_t* error) { + const size_t r = size_t(blockIdx.x) * blockDim.x + threadIdx.x; + if (r >= real) return; + uint64_t* row = output + r * layout.width; + constexpr size_t stride = Typed ? 40 : 80; + const Seed_ix_aggr_262 seed{seeds + r * stride}; + row[layout.auxiliaries] = seed.word(0); + const uint32_t status = row_ix_aggr_262(seed, layout, row); + if (status) atomicCAS(error, 0U, status); +} +cudaError_t launch_ix_aggr_262(const uint8_t* seeds, uint32_t encoding, size_t real, size_t rows, Layout layout, uint64_t* output, uint32_t* error) { + if (encoding == 1) kernel_ix_aggr_262<<>>(seeds, real, rows, layout, output, error); + else kernel_ix_aggr_262<<>>(seeds, real, rows, layout, output, error); + return cudaGetLastError(); +} +extern "C" int aiur_trace_ix_aggr_262(int device, const uint8_t* seeds, uint32_t encoding, size_t real, size_t rows, size_t width, size_t selectors, size_t auxiliaries, uint64_t* output) { + if (selectors < 2 || auxiliaries < selectors || auxiliaries - selectors < 5 || width < auxiliaries || width - auxiliaries < 10) return int(cudaErrorInvalidValue); + const size_t stride = encoding == 1 ? 40 : encoding == 0 ? 80 : 0; + return upload(device, seeds, encoding, stride, real, rows, {width, selectors, auxiliaries}, output, launch_ix_aggr_262); +} + extern "C" const uint8_t* aiur_trace_ix_aggr_contract() { - static const uint8_t hash[32] = {145, 162, 10, 199, 28, 39, 21, 135, 139, 129, 230, 115, 149, 94, 62, 28, 138, 138, 88, 46, 132, 161, 227, 117, 182, 88, 126, 99, 227, 41, 96, 70}; + static const uint8_t hash[32] = {16, 192, 139, 155, 202, 145, 36, 243, 93, 246, 216, 189, 250, 221, 54, 36, 149, 240, 11, 40, 210, 134, 183, 238, 67, 74, 234, 134, 107, 96, 246, 23}; return hash; } extern "C" const uint8_t* aiur_trace_ix_aggr_schema() { - static const uint8_t hash[32] = {239, 176, 66, 178, 174, 211, 250, 22, 217, 65, 204, 136, 211, 31, 231, 21, 49, 200, 167, 74, 146, 184, 198, 118, 200, 232, 103, 43, 247, 11, 245, 163}; + static const uint8_t hash[32] = {171, 117, 94, 141, 24, 100, 81, 45, 119, 242, 237, 174, 47, 213, 20, 154, 88, 248, 88, 222, 223, 0, 127, 104, 180, 202, 215, 87, 114, 235, 156, 106}; return hash; } } diff --git a/crates/aiur/cuda/generated/production/ixvm.cu b/crates/aiur/cuda/generated/production/ixvm.cu index 30c34e869..ce5acc45c 100644 --- a/crates/aiur/cuda/generated/production/ixvm.cu +++ b/crates/aiur/cuda/generated/production/ixvm.cu @@ -17032,6 +17032,7 @@ template struct Seed_ixvm_710 { case 15: return load_u8(data + 78); case 16: return load_u8(data + 79); case 17: return load_u32(data + 72); + case 18: return load_u8(data + 80); default: return 0; } } @@ -17077,6 +17078,8 @@ template __device__ __forceinline__ uint32_t row_ixvm_710(Seed_ixvm_ return 0; } case 0ULL: { + const uint64_t v_17 = seed.word(18); + row[layout.auxiliaries + 4] = v_17; row[layout.selectors + 1] = 1; return 0; } @@ -17089,7 +17092,7 @@ template __global__ void kernel_ixvm_710(const uint8_t* __restrict__ const size_t r = size_t(blockIdx.x) * blockDim.x + threadIdx.x; if (r >= real) return; uint64_t* row = output + r * layout.width; - constexpr size_t stride = Typed ? 80 : 144; + constexpr size_t stride = Typed ? 88 : 152; const Seed_ixvm_710 seed{seeds + r * stride}; row[layout.auxiliaries] = seed.word(0); const uint32_t status = row_ixvm_710(seed, layout, row); @@ -17101,8 +17104,8 @@ cudaError_t launch_ixvm_710(const uint8_t* seeds, uint32_t encoding, size_t real return cudaGetLastError(); } extern "C" int aiur_trace_ixvm_710(int device, const uint8_t* seeds, uint32_t encoding, size_t real, size_t rows, size_t width, size_t selectors, size_t auxiliaries, uint64_t* output) { - if (selectors < 14 || auxiliaries < selectors || auxiliaries - selectors < 2 || width < auxiliaries || width - auxiliaries < 4) return int(cudaErrorInvalidValue); - const size_t stride = encoding == 1 ? 80 : encoding == 0 ? 144 : 0; + if (selectors < 14 || auxiliaries < selectors || auxiliaries - selectors < 2 || width < auxiliaries || width - auxiliaries < 5) return int(cudaErrorInvalidValue); + const size_t stride = encoding == 1 ? 88 : encoding == 0 ? 152 : 0; return upload(device, seeds, encoding, stride, real, rows, {width, selectors, auxiliaries}, output, launch_ixvm_710); } @@ -19957,11 +19960,11 @@ extern "C" int aiur_trace_ixvm_783(int device, const uint8_t* seeds, uint32_t en } extern "C" const uint8_t* aiur_trace_ixvm_contract() { - static const uint8_t hash[32] = {170, 92, 226, 63, 246, 182, 23, 209, 212, 212, 191, 239, 12, 22, 123, 170, 44, 208, 140, 255, 236, 216, 99, 67, 65, 3, 208, 61, 213, 142, 208, 36}; + static const uint8_t hash[32] = {134, 255, 142, 155, 247, 231, 0, 155, 156, 136, 201, 185, 23, 188, 13, 83, 187, 4, 113, 195, 47, 43, 101, 210, 29, 29, 27, 138, 47, 144, 112, 191}; return hash; } extern "C" const uint8_t* aiur_trace_ixvm_schema() { - static const uint8_t hash[32] = {215, 91, 140, 243, 211, 65, 176, 235, 26, 107, 64, 249, 107, 209, 195, 240, 90, 50, 241, 44, 255, 202, 183, 8, 43, 48, 100, 180, 9, 103, 132, 232}; + static const uint8_t hash[32] = {63, 91, 117, 50, 80, 90, 212, 4, 235, 24, 107, 94, 84, 249, 63, 203, 8, 1, 215, 241, 165, 238, 247, 222, 166, 172, 140, 73, 23, 5, 219, 38}; return hash; } } diff --git a/crates/aiur/cuda/generated/production/trace-manifest.json b/crates/aiur/cuda/generated/production/trace-manifest.json index b7a691440..620105930 100644 --- a/crates/aiur/cuda/generated/production/trace-manifest.json +++ b/crates/aiur/cuda/generated/production/trace-manifest.json @@ -1,38 +1,38 @@ {"units": [{"source": "cuda/generated/production/ixvm.cu", "digest": - [119, - 178, - 49, - 62, - 119, - 98, - 17, - 153, - 73, - 23, - 114, - 5, - 173, - 1, - 251, - 60, - 242, - 79, - 67, - 63, - 223, - 216, + [255, + 117, + 108, + 120, 141, + 113, + 152, + 172, + 182, + 253, + 63, + 31, + 100, + 71, + 188, + 162, + 220, + 224, + 98, 165, - 193, - 119, - 205, - 96, - 135, - 50, - 112, - 123]}, + 104, + 194, + 113, + 120, + 221, + 56, + 174, + 24, + 24, + 39, + 254, + 140]}, {"source": "cuda/generated/production/multi_stark.cu", "digest": [31, @@ -69,36 +69,36 @@ 7]}, {"source": "cuda/generated/production/ix_aggr.cu", "digest": - [123, - 214, - 120, - 50, - 232, - 219, - 127, - 141, - 238, + [181, + 196, + 202, + 67, + 23, + 117, + 184, + 45, + 86, + 231, + 44, 248, - 93, - 61, - 238, - 114, - 57, - 243, - 180, - 159, - 40, - 178, - 174, - 38, + 117, + 136, + 67, + 188, + 19, + 21, + 206, + 234, + 113, + 206, 209, - 64, - 3, - 238, - 110, - 134, - 141, - 183, - 120, - 72]}], + 140, + 179, + 0, + 200, + 198, + 29, + 159, + 17, + 226]}], "abi": 2} diff --git a/crates/aiur/src/trace_codegen/programs/ix_aggr.rs b/crates/aiur/src/trace_codegen/programs/ix_aggr.rs index 000c2f25e..0d6d2925d 100644 --- a/crates/aiur/src/trace_codegen/programs/ix_aggr.rs +++ b/crates/aiur/src/trace_codegen/programs/ix_aggr.rs @@ -36,6 +36,22 @@ fn write_233(seed: &[u64], offsets: RowOffsets, row: &mut [u64],) { let v_0 = G: static SCHEMA_233: SeedSchema = SeedSchema { widths: &[SeedWidth::Full, SeedWidth::U8, SeedWidth::U8, SeedWidth::U8, SeedWidth::U8, SeedWidth::U8, SeedWidth::U8, SeedWidth::U8, SeedWidth::U8, SeedWidth::U8, SeedWidth::U8, SeedWidth::U8, SeedWidth::U8, SeedWidth::U8, SeedWidth::U8, SeedWidth::U8, SeedWidth::U8, SeedWidth::Full, SeedWidth::Full, SeedWidth::Full, SeedWidth::Full, SeedWidth::Full, SeedWidth::Full, SeedWidth::Full, SeedWidth::Full], offsets: &[0, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 8, 16, 24, 32, 40, 48, 56, 64], bytes: 88 }; +fn pack_261(context: &SeedContext<'_>, seed: &mut [u64],) -> TraceResult<()> { let v_0 = context.inputs[0]; let v_1 = context.inputs[1]; let v_2 = context.inputs[2]; let [v_3, v_4, v_5]: [G; 3] = context.load::<3>(0, v_0)?; seed[4] = v_3.as_canonical_u64(); seed[5] = v_4.as_canonical_u64(); seed[6] = v_5.as_canonical_u64(); let [v_6, v_7, v_8]: [G; 3] = context.load::<3>(1, v_1)?; seed[7] = v_6.as_canonical_u64(); seed[8] = v_7.as_canonical_u64(); seed[9] = v_8.as_canonical_u64(); match v_3.as_canonical_u64() { 1u64 => { Ok(()) }, 0u64 => { match v_6.as_canonical_u64() { 1u64 => { Ok(()) }, 0u64 => { let [v_9, v_10, v_11]: [G; 3] = context.load::<3>(4, v_2)?; seed[10] = v_9.as_canonical_u64(); seed[11] = v_10.as_canonical_u64(); seed[12] = v_11.as_canonical_u64(); match v_9.as_canonical_u64() { 0u64 => { let [v_12]: [G; 1] = context.call::<1>(5, 250, &[v_4, v_7])?; seed[13] = v_12.as_canonical_u64(); match v_12.as_canonical_u64() { 0u64 => { let [v_13]: [G; 1] = context.call::<1>(6, 2, &[v_4, v_10])?; seed[14] = v_13.as_canonical_u64(); Ok(()) }, 1u64 => { Ok(()) }, _ => { let [v_17]: [G; 1] = context.call::<1>(13, 2, &[v_7, v_10])?; seed[14] = v_17.as_canonical_u64(); Ok(()) }, } }, _ => { Err(no_match(261, v_9)) }, } }, _ => { Err(no_match(261, v_6)) }, } }, _ => { Err(no_match(261, v_3)) }, } } + +fn pack_261_typed(context: &SeedContext<'_>, seed: &mut TypedSeed<'_>,) -> TraceResult<()> { let v_0 = context.inputs[0]; let v_1 = context.inputs[1]; let v_2 = context.inputs[2]; let [v_3, v_4, v_5]: [G; 3] = context.load::<3>(0, v_0)?; seed.u8(44, v_3); seed.u32(20, v_4); seed.u32(24, v_5); let [v_6, v_7, v_8]: [G; 3] = context.load::<3>(1, v_1)?; seed.u8(45, v_6); seed.u32(28, v_7); seed.u32(32, v_8); match v_3.as_canonical_u64() { 1u64 => { Ok(()) }, 0u64 => { match v_6.as_canonical_u64() { 1u64 => { Ok(()) }, 0u64 => { let [v_9, v_10, v_11]: [G; 3] = context.load::<3>(4, v_2)?; seed.u8(46, v_9); seed.u32(36, v_10); seed.u32(40, v_11); match v_9.as_canonical_u64() { 0u64 => { let [v_12]: [G; 1] = context.call::<1>(5, 250, &[v_4, v_7])?; seed.u8(47, v_12); match v_12.as_canonical_u64() { 0u64 => { let [v_13]: [G; 1] = context.call::<1>(6, 2, &[v_4, v_10])?; seed.u8(48, v_13); Ok(()) }, 1u64 => { Ok(()) }, _ => { let [v_17]: [G; 1] = context.call::<1>(13, 2, &[v_7, v_10])?; seed.u8(48, v_17); Ok(()) }, } }, _ => { Err(no_match(261, v_9)) }, } }, _ => { Err(no_match(261, v_6)) }, } }, _ => { Err(no_match(261, v_3)) }, } } + +fn write_261(seed: &[u64], offsets: RowOffsets, row: &mut [u64],) -> TraceResult<()> { let v_0 = G::from_u64(seed[1]); let v_1 = G::from_u64(seed[2]); let v_2 = G::from_u64(seed[3]); let [v_3, v_4, v_5]: [G; 3] = [G::from_u64(seed[4]), G::from_u64(seed[5]), G::from_u64(seed[6])]; row[offsets.auxiliaries + 1] = v_3.as_canonical_u64(); row[offsets.auxiliaries + 2] = v_4.as_canonical_u64(); row[offsets.auxiliaries + 3] = v_5.as_canonical_u64(); let [v_6, v_7, v_8]: [G; 3] = [G::from_u64(seed[7]), G::from_u64(seed[8]), G::from_u64(seed[9])]; row[offsets.auxiliaries + 4] = v_6.as_canonical_u64(); row[offsets.auxiliaries + 5] = v_7.as_canonical_u64(); row[offsets.auxiliaries + 6] = v_8.as_canonical_u64(); match v_3.as_canonical_u64() { 1u64 => { row[offsets.selectors] = G::ONE.as_canonical_u64(); Ok(()) }, 0u64 => { match v_6.as_canonical_u64() { 1u64 => { row[offsets.selectors + 1] = G::ONE.as_canonical_u64(); Ok(()) }, 0u64 => { let [v_9, v_10, v_11]: [G; 3] = [G::from_u64(seed[10]), G::from_u64(seed[11]), G::from_u64(seed[12])]; row[offsets.auxiliaries + 7] = v_9.as_canonical_u64(); row[offsets.auxiliaries + 8] = v_10.as_canonical_u64(); row[offsets.auxiliaries + 9] = v_11.as_canonical_u64(); match v_9.as_canonical_u64() { 0u64 => { let [v_12]: [G; 1] = [G::from_u64(seed[13])]; row[offsets.auxiliaries + 10] = v_12.as_canonical_u64(); match v_12.as_canonical_u64() { 0u64 => { let [v_13]: [G; 1] = [G::from_u64(seed[14])]; row[offsets.auxiliaries + 11] = v_13.as_canonical_u64(); row[offsets.selectors + 2] = G::ONE.as_canonical_u64(); Ok(()) }, 1u64 => { row[offsets.selectors + 3] = G::ONE.as_canonical_u64(); Ok(()) }, _ => { row[offsets.auxiliaries + 11] = (v_12 - G::from_u64(0)).inverse().as_canonical_u64(); row[offsets.auxiliaries + 12] = (v_12 - G::from_u64(1)).inverse().as_canonical_u64(); let [v_17]: [G; 1] = [G::from_u64(seed[14])]; row[offsets.auxiliaries + 13] = v_17.as_canonical_u64(); row[offsets.selectors + 4] = G::ONE.as_canonical_u64(); Ok(()) }, } }, _ => { Err(no_match(261, v_9)) }, } }, _ => { Err(no_match(261, v_6)) }, } }, _ => { Err(no_match(261, v_3)) }, } } + +static SCHEMA_261: SeedSchema = SeedSchema { widths: &[SeedWidth::Full, SeedWidth::U32, SeedWidth::U32, SeedWidth::U32, SeedWidth::U8, SeedWidth::U32, SeedWidth::U32, SeedWidth::U8, SeedWidth::U32, SeedWidth::U32, SeedWidth::U8, SeedWidth::U32, SeedWidth::U32, SeedWidth::U8, SeedWidth::U8], offsets: &[0, 8, 12, 16, 44, 20, 24, 45, 28, 32, 46, 36, 40, 47, 48], bytes: 56 }; + +fn pack_262(context: &SeedContext<'_>, seed: &mut [u64],) -> TraceResult<()> { let v_0 = context.inputs[0]; let v_1 = context.inputs[1]; let [v_2, v_3, v_4]: [G; 3] = context.load::<3>(0, v_0)?; seed[3] = v_2.as_canonical_u64(); seed[4] = v_3.as_canonical_u64(); seed[5] = v_4.as_canonical_u64(); let [v_5, v_6, v_7]: [G; 3] = context.load::<3>(1, v_1)?; seed[6] = v_5.as_canonical_u64(); seed[7] = v_6.as_canonical_u64(); seed[8] = v_7.as_canonical_u64(); match v_2.as_canonical_u64() { 1u64 => { Ok(()) }, 0u64 => { match v_5.as_canonical_u64() { 1u64 => { Ok(()) }, 0u64 => { let [v_8]: [G; 1] = context.call::<1>(2, 250, &[v_3, v_6])?; seed[9] = v_8.as_canonical_u64(); Ok(()) }, _ => { Err(no_match(262, v_5)) }, } }, _ => { Err(no_match(262, v_2)) }, } } + +fn pack_262_typed(context: &SeedContext<'_>, seed: &mut TypedSeed<'_>,) -> TraceResult<()> { let v_0 = context.inputs[0]; let v_1 = context.inputs[1]; let [v_2, v_3, v_4]: [G; 3] = context.load::<3>(0, v_0)?; seed.u8(32, v_2); seed.u32(16, v_3); seed.u32(20, v_4); let [v_5, v_6, v_7]: [G; 3] = context.load::<3>(1, v_1)?; seed.u8(33, v_5); seed.u32(24, v_6); seed.u32(28, v_7); match v_2.as_canonical_u64() { 1u64 => { Ok(()) }, 0u64 => { match v_5.as_canonical_u64() { 1u64 => { Ok(()) }, 0u64 => { let [v_8]: [G; 1] = context.call::<1>(2, 250, &[v_3, v_6])?; seed.u8(34, v_8); Ok(()) }, _ => { Err(no_match(262, v_5)) }, } }, _ => { Err(no_match(262, v_2)) }, } } + +fn write_262(seed: &[u64], offsets: RowOffsets, row: &mut [u64],) -> TraceResult<()> { let v_0 = G::from_u64(seed[1]); let v_1 = G::from_u64(seed[2]); let [v_2, v_3, v_4]: [G; 3] = [G::from_u64(seed[3]), G::from_u64(seed[4]), G::from_u64(seed[5])]; row[offsets.auxiliaries + 1] = v_2.as_canonical_u64(); row[offsets.auxiliaries + 2] = v_3.as_canonical_u64(); row[offsets.auxiliaries + 3] = v_4.as_canonical_u64(); let [v_5, v_6, v_7]: [G; 3] = [G::from_u64(seed[6]), G::from_u64(seed[7]), G::from_u64(seed[8])]; row[offsets.auxiliaries + 4] = v_5.as_canonical_u64(); row[offsets.auxiliaries + 5] = v_6.as_canonical_u64(); row[offsets.auxiliaries + 6] = v_7.as_canonical_u64(); match v_2.as_canonical_u64() { 1u64 => { row[offsets.selectors] = G::ONE.as_canonical_u64(); Ok(()) }, 0u64 => { match v_5.as_canonical_u64() { 1u64 => { row[offsets.selectors + 1] = G::ONE.as_canonical_u64(); Ok(()) }, 0u64 => { let [v_8]: [G; 1] = [G::from_u64(seed[9])]; row[offsets.auxiliaries + 7] = v_8.as_canonical_u64(); match v_8.as_canonical_u64() { 0u64 => { row[offsets.selectors + 2] = G::ONE.as_canonical_u64(); Ok(()) }, 1u64 => { row[offsets.selectors + 3] = G::ONE.as_canonical_u64(); Ok(()) }, _ => { row[offsets.auxiliaries + 8] = (v_8 - G::from_u64(0)).inverse().as_canonical_u64(); row[offsets.auxiliaries + 9] = (v_8 - G::from_u64(1)).inverse().as_canonical_u64(); row[offsets.selectors + 4] = G::ONE.as_canonical_u64(); Ok(()) }, } }, _ => { Err(no_match(262, v_5)) }, } }, _ => { Err(no_match(262, v_2)) }, } } + +static SCHEMA_262: SeedSchema = SeedSchema { widths: &[SeedWidth::Full, SeedWidth::U32, SeedWidth::U32, SeedWidth::U8, SeedWidth::U32, SeedWidth::U32, SeedWidth::U8, SeedWidth::U32, SeedWidth::U32, SeedWidth::U8], offsets: &[0, 8, 12, 32, 16, 20, 33, 24, 28, 34], bytes: 40 }; + #[inline(never)] fn expected_function_0() -> Function { Function { body: Block { ops: vec![Op::U32LessThan(0, 1)], ctrl: Ctrl::Return(0, vec![2]) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 7, lookups: 7 }, entry: false, constrained: true } @@ -463,7 +479,7 @@ fn expected_function_84() -> Function { #[inline(never)] fn expected_function_85() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::Const(G::from_u64(2)), Op::Const(G::from_u64(8)), Op::Call(82, vec![1], 64, false)], ctrl: Ctrl::Match(2, [(G::from_u64(1023), Block { ops: vec![Op::Call(320, vec![0], 1, false), Op::Mul(8, 73), Op::Load(8, 3), Op::Call(4, vec![75, 76, 77, 78, 79, 80, 81, 82], 1, false), Op::Mul(74, 83), Op::Add(84, 7), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Load(32, 4), Op::Const(G::from_u64(64)), Op::Const(G::from_u64(0)), Op::Call(89, vec![150, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 86, 87, 88, 89, 90, 91, 86, 92, 93, 94, 95, 96, 97, 98, 99, 100, 75, 76, 77, 78, 79, 80, 81, 82, 149, 150, 150, 150, 85, 150, 150, 150, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72], 32, false), Op::Store(vec![150, 5, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182]), Op::Const(G::from_u64(1)), Op::Store(vec![184, 184, 184]), Op::Call(13, vec![75, 76, 77, 78, 79, 80, 81, 82], 8, false), Op::Store(vec![186, 187, 188, 189, 190, 191, 192, 193]), Op::Store(vec![86, 87, 88, 89, 90, 91, 86, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116]), Op::Call(78, vec![0, 185, 150, 150, 194, 195, 183], 1, false)], ctrl: Ctrl::Return(0, vec![196]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(320, vec![0], 1, false), Op::Mul(73, 7), Op::Call(320, vec![0], 1, false), Op::Load(8, 3), Op::Call(4, vec![76, 77, 78, 79, 80, 81, 82, 83], 1, false), Op::Mul(75, 84), Op::Mul(85, 8), Op::Const(G::from_u64(63)), Op::Sub(2, 87), Op::EqZero(88), Op::Mul(89, 6), Op::Add(74, 86), Op::Add(91, 90), Op::Load(32, 4), Op::Const(G::from_u64(64)), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(0)), Op::Call(89, vec![141, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 126, 127, 128, 129, 130, 131, 126, 132, 133, 134, 135, 136, 137, 138, 139, 140, 76, 77, 78, 79, 80, 81, 82, 83, 125, 141, 141, 141, 92, 141, 141, 141, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72], 32, false), Op::Const(G::from_u64(1)), Op::Store(vec![174, 174, 174]), Op::Add(2, 174), Op::Store(vec![142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173]), Op::Call(78, vec![0, 175, 141, 176, 3, 177, 5], 1, false)], ctrl: Ctrl::Return(1, vec![178]) }))) }, layout: FunctionLayout { input_size: 6, selectors: 2, auxiliaries: 153, lookups: 13 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::Const(G::from_u64(2)), Op::Const(G::from_u64(8)), Op::Call(82, vec![1], 64, false)], ctrl: Ctrl::Match(2, [(G::from_u64(1023), Block { ops: vec![Op::Call(322, vec![0], 1, false), Op::Mul(8, 73), Op::Load(8, 3), Op::Call(4, vec![75, 76, 77, 78, 79, 80, 81, 82], 1, false), Op::Mul(74, 83), Op::Add(84, 7), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Load(32, 4), Op::Const(G::from_u64(64)), Op::Const(G::from_u64(0)), Op::Call(89, vec![150, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 86, 87, 88, 89, 90, 91, 86, 92, 93, 94, 95, 96, 97, 98, 99, 100, 75, 76, 77, 78, 79, 80, 81, 82, 149, 150, 150, 150, 85, 150, 150, 150, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72], 32, false), Op::Store(vec![150, 5, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182]), Op::Const(G::from_u64(1)), Op::Store(vec![184, 184, 184]), Op::Call(13, vec![75, 76, 77, 78, 79, 80, 81, 82], 8, false), Op::Store(vec![186, 187, 188, 189, 190, 191, 192, 193]), Op::Store(vec![86, 87, 88, 89, 90, 91, 86, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116]), Op::Call(78, vec![0, 185, 150, 150, 194, 195, 183], 1, false)], ctrl: Ctrl::Return(0, vec![196]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(322, vec![0], 1, false), Op::Mul(73, 7), Op::Call(322, vec![0], 1, false), Op::Load(8, 3), Op::Call(4, vec![76, 77, 78, 79, 80, 81, 82, 83], 1, false), Op::Mul(75, 84), Op::Mul(85, 8), Op::Const(G::from_u64(63)), Op::Sub(2, 87), Op::EqZero(88), Op::Mul(89, 6), Op::Add(74, 86), Op::Add(91, 90), Op::Load(32, 4), Op::Const(G::from_u64(64)), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(0)), Op::Call(89, vec![141, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 126, 127, 128, 129, 130, 131, 126, 132, 133, 134, 135, 136, 137, 138, 139, 140, 76, 77, 78, 79, 80, 81, 82, 83, 125, 141, 141, 141, 92, 141, 141, 141, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72], 32, false), Op::Const(G::from_u64(1)), Op::Store(vec![174, 174, 174]), Op::Add(2, 174), Op::Store(vec![142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173]), Op::Call(78, vec![0, 175, 141, 176, 3, 177, 5], 1, false)], ctrl: Ctrl::Return(1, vec![178]) }))) }, layout: FunctionLayout { input_size: 6, selectors: 2, auxiliaries: 153, lookups: 13 }, entry: false, constrained: true } } #[inline(never)] @@ -588,7 +604,7 @@ fn expected_function_109() -> Function { #[inline(never)] fn expected_function_110() -> Function { - Function { body: Block { ops: vec![Op::Call(93, vec![0], 10, false), Op::Call(93, vec![10], 10, false), Op::Call(93, vec![20], 10, false), Op::Call(93, vec![30], 10, false), Op::Call(93, vec![40], 10, false), Op::Call(93, vec![50], 10, false), Op::Call(93, vec![60], 10, false), Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::Store(vec![72, 72, 72, 72, 72, 72, 72, 72, 72, 72]), Op::Store(vec![71, 62, 63, 64, 65, 66, 67, 68, 69, 73]), Op::Store(vec![71, 52, 53, 54, 55, 56, 57, 58, 59, 74]), Op::Store(vec![71, 42, 43, 44, 45, 46, 47, 48, 49, 75]), Op::Store(vec![71, 32, 33, 34, 35, 36, 37, 38, 39, 76]), Op::Store(vec![71, 22, 23, 24, 25, 26, 27, 28, 29, 77]), Op::Store(vec![71, 12, 13, 14, 15, 16, 17, 18, 19, 78]), Op::Store(vec![71, 2, 3, 4, 5, 6, 7, 8, 9, 79]), Op::Call(93, vec![70], 10, false), Op::Call(106, vec![90, 81], 3, false), Op::Call(107, vec![93], 3, false), Op::Call(108, vec![96, 81], 2, false), Op::Store(vec![71, 82, 83, 84, 85, 86, 87, 88, 89, 92]), Op::Call(322, vec![80, 99], 1, false)], ctrl: Ctrl::Return(0, vec![1, 11, 21, 31, 41, 51, 61, 100, 91, 94, 95, 97, 98]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 99, lookups: 22 }, entry: false, constrained: false } + Function { body: Block { ops: vec![Op::Call(93, vec![0], 10, false), Op::Call(93, vec![10], 10, false), Op::Call(93, vec![20], 10, false), Op::Call(93, vec![30], 10, false), Op::Call(93, vec![40], 10, false), Op::Call(93, vec![50], 10, false), Op::Call(93, vec![60], 10, false), Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::Store(vec![72, 72, 72, 72, 72, 72, 72, 72, 72, 72]), Op::Store(vec![71, 62, 63, 64, 65, 66, 67, 68, 69, 73]), Op::Store(vec![71, 52, 53, 54, 55, 56, 57, 58, 59, 74]), Op::Store(vec![71, 42, 43, 44, 45, 46, 47, 48, 49, 75]), Op::Store(vec![71, 32, 33, 34, 35, 36, 37, 38, 39, 76]), Op::Store(vec![71, 22, 23, 24, 25, 26, 27, 28, 29, 77]), Op::Store(vec![71, 12, 13, 14, 15, 16, 17, 18, 19, 78]), Op::Store(vec![71, 2, 3, 4, 5, 6, 7, 8, 9, 79]), Op::Call(93, vec![70], 10, false), Op::Call(106, vec![90, 81], 3, false), Op::Call(107, vec![93], 3, false), Op::Call(108, vec![96, 81], 2, false), Op::Store(vec![71, 82, 83, 84, 85, 86, 87, 88, 89, 92]), Op::Call(324, vec![80, 99], 1, false)], ctrl: Ctrl::Return(0, vec![1, 11, 21, 31, 41, 51, 61, 100, 91, 94, 95, 97, 98]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 99, lookups: 22 }, entry: false, constrained: false } } #[inline(never)] @@ -678,7 +694,7 @@ fn expected_function_127() -> Function { #[inline(never)] fn expected_function_128() -> Function { - Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(4, [(G::from_u64(1), Block { ops: vec![Op::Call(312, vec![1], 1, false), Op::Const(G::from_u64(0)), Op::AssertEq(vec![7], vec![8], None), Op::Const(G::from_u64(1)), Op::Store(vec![9, 9, 9, 9, 9, 9])], ctrl: Ctrl::Return(0, vec![10]) }), (G::from_u64(0), Block { ops: vec![Op::Load(3, 1)], ctrl: Ctrl::Match(7, [(G::from_u64(0), Block { ops: vec![Op::Call(122, vec![8, 2, 3], 1, false), Op::Const(G::from_u64(0)), Op::Call(124, vec![5], 1, false), Op::Call(128, vec![6, 9, 2, 3], 1, false), Op::Store(vec![11, 12, 5, 8, 10, 13])], ctrl: Ctrl::Return(1, vec![14]) })].into_iter().collect(), None) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 4, selectors: 2, auxiliaries: 11, lookups: 7 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(4, [(G::from_u64(1), Block { ops: vec![Op::Call(314, vec![1], 1, false), Op::Const(G::from_u64(0)), Op::AssertEq(vec![7], vec![8], None), Op::Const(G::from_u64(1)), Op::Store(vec![9, 9, 9, 9, 9, 9])], ctrl: Ctrl::Return(0, vec![10]) }), (G::from_u64(0), Block { ops: vec![Op::Load(3, 1)], ctrl: Ctrl::Match(7, [(G::from_u64(0), Block { ops: vec![Op::Call(122, vec![8, 2, 3], 1, false), Op::Const(G::from_u64(0)), Op::Call(124, vec![5], 1, false), Op::Call(128, vec![6, 9, 2, 3], 1, false), Op::Store(vec![11, 12, 5, 8, 10, 13])], ctrl: Ctrl::Return(1, vec![14]) })].into_iter().collect(), None) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 4, selectors: 2, auxiliaries: 11, lookups: 7 }, entry: false, constrained: true } } #[inline(never)] @@ -693,7 +709,7 @@ fn expected_function_130() -> Function { #[inline(never)] fn expected_function_131() -> Function { - Function { body: Block { ops: vec![Op::Call(312, vec![1], 1, false), Op::Call(312, vec![3], 1, false), Op::Sub(6, 7), Op::EqZero(8), Op::Const(G::from_u64(1)), Op::AssertEq(vec![9], vec![10], None), Op::Call(128, vec![3, 1, 2, 5], 1, false), Op::Call(127, vec![11], 1, false), Op::Call(130, vec![12, 4, 2, 5], 2, false), Op::Call(312, vec![14], 1, false), Op::Const(G::from_u64(0)), Op::AssertEq(vec![15], vec![16], None), Op::Load(6, 13)], ctrl: Ctrl::Match(17, [(G::from_u64(0), Block { ops: vec![Op::Call(315, vec![22], 1, false), Op::Const(G::from_u64(0)), Op::AssertEq(vec![23], vec![24], None)], ctrl: Ctrl::Match(18, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(310, vec![0, 18], 1, false), Op::Sub(25, 21), Op::EqZero(26)], ctrl: Ctrl::Return(0, vec![27]) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 6, selectors: 1, auxiliaries: 20, lookups: 10 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(314, vec![1], 1, false), Op::Call(314, vec![3], 1, false), Op::Sub(6, 7), Op::EqZero(8), Op::Const(G::from_u64(1)), Op::AssertEq(vec![9], vec![10], None), Op::Call(128, vec![3, 1, 2, 5], 1, false), Op::Call(127, vec![11], 1, false), Op::Call(130, vec![12, 4, 2, 5], 2, false), Op::Call(314, vec![14], 1, false), Op::Const(G::from_u64(0)), Op::AssertEq(vec![15], vec![16], None), Op::Load(6, 13)], ctrl: Ctrl::Match(17, [(G::from_u64(0), Block { ops: vec![Op::Call(317, vec![22], 1, false), Op::Const(G::from_u64(0)), Op::AssertEq(vec![23], vec![24], None)], ctrl: Ctrl::Match(18, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(312, vec![0, 18], 1, false), Op::Sub(25, 21), Op::EqZero(26)], ctrl: Ctrl::Return(0, vec![27]) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 6, selectors: 1, auxiliaries: 20, lookups: 10 }, entry: false, constrained: true } } #[inline(never)] @@ -758,7 +774,7 @@ fn expected_function_143() -> Function { #[inline(never)] fn expected_function_144() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(2, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![5]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(310, vec![0, 3], 1, false), Op::Add(5, 1), Op::Sub(6, 4), Op::EqZero(7)], ctrl: Ctrl::Match(8, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(1, vec![9]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(2, 9), Op::Add(3, 9), Op::Call(144, vec![0, 1, 10, 11, 4], 1, false)], ctrl: Ctrl::Return(2, vec![12]) }))) }))) }, layout: FunctionLayout { input_size: 5, selectors: 3, auxiliaries: 7, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(2, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![5]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(312, vec![0, 3], 1, false), Op::Add(5, 1), Op::Sub(6, 4), Op::EqZero(7)], ctrl: Ctrl::Match(8, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(1, vec![9]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(2, 9), Op::Add(3, 9), Op::Call(144, vec![0, 1, 10, 11, 4], 1, false)], ctrl: Ctrl::Return(2, vec![12]) }))) }))) }, layout: FunctionLayout { input_size: 5, selectors: 3, auxiliaries: 7, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] @@ -768,7 +784,7 @@ fn expected_function_145() -> Function { #[inline(never)] fn expected_function_146() -> Function { - Function { body: Block { ops: vec![Op::Load(7, 0)], ctrl: Ctrl::Match(8, [(G::from_u64(1), Block { ops: vec![Op::Call(317, vec![2], 1, false), Op::Call(313, vec![3], 1, false), Op::Sub(15, 16), Op::EqZero(17), Op::Const(G::from_u64(1)), Op::AssertEq(vec![18], vec![19], None), Op::Store(vec![19, 19, 19, 19, 19, 19, 19])], ctrl: Ctrl::Return(0, vec![20]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(9, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Sub(9, 1), Op::EqZero(15)], ctrl: Ctrl::Match(16, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(136, vec![2, 3, 6, 7, 17, 17, 10, 11], 4, false), Op::Mul(4, 18), Op::Const(G::from_u64(7)), Op::Mul(5, 19), Op::Mul(23, 24), Op::Add(22, 25), Op::Mul(4, 19), Op::Mul(5, 18), Op::Add(27, 28), Op::Add(12, 26), Op::Add(13, 29), Op::Store(vec![17, 9, 20, 21, 30, 31, 14])], ctrl: Ctrl::Return(1, vec![32]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(146, vec![14, 1, 2, 3, 4, 5, 6, 7], 1, false), Op::Store(vec![17, 9, 10, 11, 12, 13, 18])], ctrl: Ctrl::Return(2, vec![19]) }))) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 8, selectors: 3, auxiliaries: 19, lookups: 5 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(7, 0)], ctrl: Ctrl::Match(8, [(G::from_u64(1), Block { ops: vec![Op::Call(319, vec![2], 1, false), Op::Call(315, vec![3], 1, false), Op::Sub(15, 16), Op::EqZero(17), Op::Const(G::from_u64(1)), Op::AssertEq(vec![18], vec![19], None), Op::Store(vec![19, 19, 19, 19, 19, 19, 19])], ctrl: Ctrl::Return(0, vec![20]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(9, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Sub(9, 1), Op::EqZero(15)], ctrl: Ctrl::Match(16, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(136, vec![2, 3, 6, 7, 17, 17, 10, 11], 4, false), Op::Mul(4, 18), Op::Const(G::from_u64(7)), Op::Mul(5, 19), Op::Mul(23, 24), Op::Add(22, 25), Op::Mul(4, 19), Op::Mul(5, 18), Op::Add(27, 28), Op::Add(12, 26), Op::Add(13, 29), Op::Store(vec![17, 9, 20, 21, 30, 31, 14])], ctrl: Ctrl::Return(1, vec![32]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(146, vec![14, 1, 2, 3, 4, 5, 6, 7], 1, false), Op::Store(vec![17, 9, 10, 11, 12, 13, 18])], ctrl: Ctrl::Return(2, vec![19]) }))) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 8, selectors: 3, auxiliaries: 19, lookups: 5 }, entry: false, constrained: true } } #[inline(never)] @@ -783,37 +799,37 @@ fn expected_function_148() -> Function { #[inline(never)] fn expected_function_149() -> Function { - Function { body: Block { ops: vec![Op::Sub(3, 1), Op::Call(324, vec![2, 10], 1, false), Op::Const(G::from_u64(7)), Op::Call(189, vec![1], 1, false), Op::Const(G::from_u64(1)), Op::Store(vec![14, 14, 14]), Op::Call(132, vec![11, 15], 1, false), Op::Call(133, vec![13, 16], 1, false), Op::Mul(12, 17), Op::Const(G::from_u64(0)), Op::Sub(4, 18), Op::Sub(5, 19), Op::Mul(20, 20), Op::Mul(21, 21), Op::Mul(12, 23), Op::Sub(22, 24), Op::UnconstrainedGInverse(25), Op::Mul(25, 26), Op::Sub(27, 14), Op::Mul(25, 28), Op::AssertEq(vec![29], vec![19], None), Op::Mul(26, 28), Op::AssertEq(vec![30], vec![19], None), Op::Mul(20, 26), Op::Sub(19, 21), Op::Mul(32, 26), Op::Call(146, vec![0, 1, 6, 7, 31, 33, 8, 9], 1, false)], ctrl: Ctrl::Return(0, vec![34]) }, layout: FunctionLayout { input_size: 10, selectors: 1, auxiliaries: 15, lookups: 7 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Sub(3, 1), Op::Call(326, vec![2, 10], 1, false), Op::Const(G::from_u64(7)), Op::Call(189, vec![1], 1, false), Op::Const(G::from_u64(1)), Op::Store(vec![14, 14, 14]), Op::Call(132, vec![11, 15], 1, false), Op::Call(133, vec![13, 16], 1, false), Op::Mul(12, 17), Op::Const(G::from_u64(0)), Op::Sub(4, 18), Op::Sub(5, 19), Op::Mul(20, 20), Op::Mul(21, 21), Op::Mul(12, 23), Op::Sub(22, 24), Op::UnconstrainedGInverse(25), Op::Mul(25, 26), Op::Sub(27, 14), Op::Mul(25, 28), Op::AssertEq(vec![29], vec![19], None), Op::Mul(26, 28), Op::AssertEq(vec![30], vec![19], None), Op::Mul(20, 26), Op::Sub(19, 21), Op::Mul(32, 26), Op::Call(146, vec![0, 1, 6, 7, 31, 33, 8, 9], 1, false)], ctrl: Ctrl::Return(0, vec![34]) }, layout: FunctionLayout { input_size: 10, selectors: 1, auxiliaries: 15, lookups: 7 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_150() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(310, vec![8, 11], 1, false), Op::Const(G::from_u64(1)), Op::Call(310, vec![8, 13], 1, false), Op::Call(189, vec![4], 1, false), Op::Mul(5, 15), Op::Const(G::from_u64(7)), Op::Mul(6, 11), Op::Mul(17, 18), Op::Add(16, 19), Op::Mul(5, 11), Op::Mul(6, 15), Op::Add(21, 22), Op::Call(149, vec![0, 3, 1, 2, 5, 6, 7, 12, 9, 10], 1, false), Op::Call(149, vec![24, 3, 1, 2, 20, 23, 7, 14, 9, 10], 1, false)], ctrl: Ctrl::Return(0, vec![25]) }, layout: FunctionLayout { input_size: 11, selectors: 1, auxiliaries: 8, lookups: 6 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(312, vec![8, 11], 1, false), Op::Const(G::from_u64(1)), Op::Call(312, vec![8, 13], 1, false), Op::Call(189, vec![4], 1, false), Op::Mul(5, 15), Op::Const(G::from_u64(7)), Op::Mul(6, 11), Op::Mul(17, 18), Op::Add(16, 19), Op::Mul(5, 11), Op::Mul(6, 15), Op::Add(21, 22), Op::Call(149, vec![0, 3, 1, 2, 5, 6, 7, 12, 9, 10], 1, false), Op::Call(149, vec![24, 3, 1, 2, 20, 23, 7, 14, 9, 10], 1, false)], ctrl: Ctrl::Return(0, vec![25]) }, layout: FunctionLayout { input_size: 11, selectors: 1, auxiliaries: 8, lookups: 6 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_151() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(310, vec![6, 4], 1, false), Op::Add(13, 3), Op::Call(310, vec![9, 4], 1, false), Op::Call(310, vec![10, 4], 1, false), Op::Call(150, vec![0, 1, 2, 14, 13, 7, 8, 15, 16, 11, 12], 1, false), Op::Const(G::from_u64(1)), Op::Add(4, 18), Op::Sub(5, 18), Op::Call(151, vec![17, 1, 2, 3, 19, 20, 6, 7, 8, 9, 10, 11, 12], 1, false)], ctrl: Ctrl::Return(1, vec![21]) }))) }, layout: FunctionLayout { input_size: 13, selectors: 2, auxiliaries: 7, lookups: 6 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(312, vec![6, 4], 1, false), Op::Add(13, 3), Op::Call(312, vec![9, 4], 1, false), Op::Call(312, vec![10, 4], 1, false), Op::Call(150, vec![0, 1, 2, 14, 13, 7, 8, 15, 16, 11, 12], 1, false), Op::Const(G::from_u64(1)), Op::Add(4, 18), Op::Sub(5, 18), Op::Call(151, vec![17, 1, 2, 3, 19, 20, 6, 7, 8, 9, 10, 11, 12], 1, false)], ctrl: Ctrl::Return(1, vec![21]) }))) }, layout: FunctionLayout { input_size: 13, selectors: 2, auxiliaries: 7, lookups: 6 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_152() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(310, vec![6, 4], 1, false), Op::Add(13, 3), Op::Call(310, vec![9, 4], 1, false), Op::Call(310, vec![10, 4], 1, false), Op::Const(G::from_u64(0)), Op::Call(310, vec![16, 17], 1, false), Op::Call(149, vec![0, 14, 1, 2, 7, 8, 15, 18, 11, 12], 1, false), Op::Const(G::from_u64(1)), Op::Add(4, 20), Op::Sub(5, 20), Op::Call(152, vec![19, 1, 2, 3, 21, 22, 6, 7, 8, 9, 10, 11, 12], 1, false)], ctrl: Ctrl::Return(1, vec![23]) }))) }, layout: FunctionLayout { input_size: 13, selectors: 2, auxiliaries: 8, lookups: 7 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(312, vec![6, 4], 1, false), Op::Add(13, 3), Op::Call(312, vec![9, 4], 1, false), Op::Call(312, vec![10, 4], 1, false), Op::Const(G::from_u64(0)), Op::Call(312, vec![16, 17], 1, false), Op::Call(149, vec![0, 14, 1, 2, 7, 8, 15, 18, 11, 12], 1, false), Op::Const(G::from_u64(1)), Op::Add(4, 20), Op::Sub(5, 20), Op::Call(152, vec![19, 1, 2, 3, 21, 22, 6, 7, 8, 9, 10, 11, 12], 1, false)], ctrl: Ctrl::Return(1, vec![23]) }))) }, layout: FunctionLayout { input_size: 13, selectors: 2, auxiliaries: 8, lookups: 7 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_153() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(321, vec![8, 4], 2, false)], ctrl: Ctrl::Match(15, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Add(4, 17), Op::Sub(5, 17), Op::Call(153, vec![0, 1, 2, 3, 18, 19, 6, 7, 8, 9, 10, 11, 12, 13, 14], 1, false)], ctrl: Ctrl::Return(1, vec![20]) }), (G::from_u64(1), Block { ops: vec![Op::Call(310, vec![7, 4], 1, false), Op::Add(17, 3), Op::Call(310, vec![11, 6], 1, false), Op::Call(310, vec![12, 6], 1, false), Op::Call(150, vec![0, 1, 2, 18, 17, 9, 10, 19, 20, 13, 14], 1, false), Op::Const(G::from_u64(1)), Op::Add(4, 22), Op::Sub(5, 22), Op::Add(6, 22), Op::Call(153, vec![21, 1, 2, 3, 23, 24, 25, 7, 8, 9, 10, 11, 12, 13, 14], 1, false)], ctrl: Ctrl::Return(2, vec![26]) })].into_iter().collect(), None) }))) }, layout: FunctionLayout { input_size: 15, selectors: 3, auxiliaries: 9, lookups: 7 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(323, vec![8, 4], 2, false)], ctrl: Ctrl::Match(15, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Add(4, 17), Op::Sub(5, 17), Op::Call(153, vec![0, 1, 2, 3, 18, 19, 6, 7, 8, 9, 10, 11, 12, 13, 14], 1, false)], ctrl: Ctrl::Return(1, vec![20]) }), (G::from_u64(1), Block { ops: vec![Op::Call(312, vec![7, 4], 1, false), Op::Add(17, 3), Op::Call(312, vec![11, 6], 1, false), Op::Call(312, vec![12, 6], 1, false), Op::Call(150, vec![0, 1, 2, 18, 17, 9, 10, 19, 20, 13, 14], 1, false), Op::Const(G::from_u64(1)), Op::Add(4, 22), Op::Sub(5, 22), Op::Add(6, 22), Op::Call(153, vec![21, 1, 2, 3, 23, 24, 25, 7, 8, 9, 10, 11, 12, 13, 14], 1, false)], ctrl: Ctrl::Return(2, vec![26]) })].into_iter().collect(), None) }))) }, layout: FunctionLayout { input_size: 15, selectors: 3, auxiliaries: 9, lookups: 7 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_154() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(3, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(3)), Op::Call(310, vec![1, 15], 1, false)], ctrl: Ctrl::Match(16, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(312, vec![16], 1, false), Op::Call(312, vec![4], 1, false), Op::Sub(17, 18), Op::EqZero(19), Op::Const(G::from_u64(1)), Op::AssertEq(vec![20], vec![21], None), Op::Const(G::from_u64(0)), Op::Call(157, vec![6, 10, 5, 7, 22], 1, false), Op::Call(156, vec![23], 1, false), Op::Call(153, vec![0, 8, 9, 10, 22, 7, 22, 6, 5, 11, 12, 16, 4, 13, 14], 1, false)], ctrl: Ctrl::Return(1, vec![25]) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 15, selectors: 2, auxiliaries: 9, lookups: 7 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(3, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(3)), Op::Call(312, vec![1, 15], 1, false)], ctrl: Ctrl::Match(16, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(314, vec![16], 1, false), Op::Call(314, vec![4], 1, false), Op::Sub(17, 18), Op::EqZero(19), Op::Const(G::from_u64(1)), Op::AssertEq(vec![20], vec![21], None), Op::Const(G::from_u64(0)), Op::Call(157, vec![6, 10, 5, 7, 22], 1, false), Op::Call(156, vec![23], 1, false), Op::Call(153, vec![0, 8, 9, 10, 22, 7, 22, 6, 5, 11, 12, 16, 4, 13, 14], 1, false)], ctrl: Ctrl::Return(1, vec![25]) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 15, selectors: 2, auxiliaries: 9, lookups: 7 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_155() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(2, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![4, 4, 4])], ctrl: Ctrl::Return(0, vec![5]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(310, vec![0, 3], 1, false), Op::Add(5, 1), Op::Const(G::from_u64(1)), Op::Sub(2, 7), Op::Add(3, 7), Op::Call(155, vec![0, 1, 8, 9], 1, false), Op::Store(vec![4, 6, 10])], ctrl: Ctrl::Return(1, vec![11]) }))) }, layout: FunctionLayout { input_size: 4, selectors: 2, auxiliaries: 5, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(2, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![4, 4, 4])], ctrl: Ctrl::Return(0, vec![5]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(312, vec![0, 3], 1, false), Op::Add(5, 1), Op::Const(G::from_u64(1)), Op::Sub(2, 7), Op::Add(3, 7), Op::Call(155, vec![0, 1, 8, 9], 1, false), Op::Store(vec![4, 6, 10])], ctrl: Ctrl::Return(1, vec![11]) }))) }, layout: FunctionLayout { input_size: 4, selectors: 2, auxiliaries: 5, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] @@ -823,7 +839,7 @@ fn expected_function_156() -> Function { #[inline(never)] fn expected_function_157() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(3, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![5, 5, 5])], ctrl: Ctrl::Return(0, vec![6]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(321, vec![2, 4], 2, false)], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(3, 7), Op::Add(4, 7), Op::Call(157, vec![0, 1, 2, 8, 9], 1, false)], ctrl: Ctrl::Return(1, vec![10]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(310, vec![0, 4], 1, false), Op::Add(8, 1), Op::Const(G::from_u64(1)), Op::Sub(3, 10), Op::Add(4, 10), Op::Call(157, vec![0, 1, 2, 11, 12], 1, false), Op::Store(vec![7, 9, 13])], ctrl: Ctrl::Return(2, vec![14]) })].into_iter().collect(), None) }))) }, layout: FunctionLayout { input_size: 5, selectors: 3, auxiliaries: 7, lookups: 5 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(3, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![5, 5, 5])], ctrl: Ctrl::Return(0, vec![6]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(323, vec![2, 4], 2, false)], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(3, 7), Op::Add(4, 7), Op::Call(157, vec![0, 1, 2, 8, 9], 1, false)], ctrl: Ctrl::Return(1, vec![10]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(312, vec![0, 4], 1, false), Op::Add(8, 1), Op::Const(G::from_u64(1)), Op::Sub(3, 10), Op::Add(4, 10), Op::Call(157, vec![0, 1, 2, 11, 12], 1, false), Op::Store(vec![7, 9, 13])], ctrl: Ctrl::Return(2, vec![14]) })].into_iter().collect(), None) }))) }, layout: FunctionLayout { input_size: 5, selectors: 3, auxiliaries: 7, lookups: 5 }, entry: false, constrained: true } } #[inline(never)] @@ -843,12 +859,12 @@ fn expected_function_160() -> Function { #[inline(never)] fn expected_function_161() -> Function { - Function { body: Block { ops: vec![Op::Load(4, 4)], ctrl: Ctrl::Match(9, [(G::from_u64(1), Block { ops: vec![Op::Sub(6, 8), Op::EqZero(13), Op::Const(G::from_u64(1)), Op::AssertEq(vec![14], vec![15], None), Op::Call(316, vec![7], 1, false), Op::Const(G::from_u64(0)), Op::AssertEq(vec![16], vec![17], None), Op::Store(vec![15, 15, 15])], ctrl: Ctrl::Return(0, vec![0, 1, 18]) }), (G::from_u64(0), Block { ops: vec![Op::Load(4, 2)], ctrl: Ctrl::Match(13, [(G::from_u64(0), Block { ops: vec![Op::Load(3, 3)], ctrl: Ctrl::Match(17, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(10, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(313, vec![11], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![20], vec![21], None), Op::Load(3, 5)], ctrl: Ctrl::Match(22, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(6, 25), Op::Const(G::from_u64(0)), Op::Call(321, vec![11, 27], 2, false)], ctrl: Ctrl::MatchContinue(23, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Yield(1, vec![0, 1, 28, 29]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::Yield(2, vec![28, 29, 0, 1]) })), 4, 1, 0, Box::new(Block { ops: vec![Op::Call(134, vec![24, 26, 14, 15, 30, 31, 32, 33], 2, false), Op::Call(160, vec![34, 35, 26, 14, 15, 7], 3, false), Op::Call(161, vec![36, 37, 16, 19, 12, 24, 26, 38, 8], 3, false), Op::UnconstrainedGToBytes(30), Op::U8RangeCheck(42, 43), Op::U8RangeCheck(44, 45), Op::U8RangeCheck(46, 47), Op::U8RangeCheck(48, 49), Op::Const(G::from_u64(256)), Op::Mul(50, 43), Op::Add(42, 51), Op::Const(G::from_u64(65536)), Op::Mul(53, 44), Op::Add(52, 54), Op::Const(G::from_u64(16777216)), Op::Mul(56, 45), Op::Add(55, 57), Op::Const(G::from_u64(4294967296)), Op::Mul(59, 46), Op::Add(58, 60), Op::Const(G::from_u64(1099511627776)), Op::Mul(62, 47), Op::Add(61, 63), Op::Const(G::from_u64(281474976710656)), Op::Mul(65, 48), Op::Add(64, 66), Op::Const(G::from_u64(72057594037927936)), Op::Mul(68, 49), Op::Add(67, 69), Op::AssertEq(vec![70], vec![30], None), Op::Add(46, 47), Op::Add(71, 48), Op::Add(72, 49), Op::Const(G::from_u64(1020)), Op::Sub(73, 74), Op::EqZero(75), Op::Add(42, 43), Op::Add(77, 44), Op::Add(78, 45), Op::EqZero(79), Op::Const(G::from_u64(1)), Op::Sub(81, 80), Op::Mul(76, 82), Op::Sub(81, 83), Op::AssertEq(vec![84], vec![81], None), Op::UnconstrainedGToBytes(31), Op::U8RangeCheck(85, 86), Op::U8RangeCheck(87, 88), Op::U8RangeCheck(89, 90), Op::U8RangeCheck(91, 92), Op::Mul(50, 86), Op::Add(85, 93), Op::Mul(53, 87), Op::Add(94, 95), Op::Mul(56, 88), Op::Add(96, 97), Op::Mul(59, 89), Op::Add(98, 99), Op::Mul(62, 90), Op::Add(100, 101), Op::Mul(65, 91), Op::Add(102, 103), Op::Mul(68, 92), Op::Add(104, 105), Op::AssertEq(vec![106], vec![31], None), Op::Add(89, 90), Op::Add(107, 91), Op::Add(108, 92), Op::Sub(109, 74), Op::EqZero(110), Op::Add(85, 86), Op::Add(112, 87), Op::Add(113, 88), Op::EqZero(114), Op::Sub(81, 115), Op::Mul(111, 116), Op::Sub(81, 117), Op::AssertEq(vec![118], vec![81], None), Op::UnconstrainedGToBytes(32), Op::U8RangeCheck(119, 120), Op::U8RangeCheck(121, 122), Op::U8RangeCheck(123, 124), Op::U8RangeCheck(125, 126), Op::Mul(50, 120), Op::Add(119, 127), Op::Mul(53, 121), Op::Add(128, 129), Op::Mul(56, 122), Op::Add(130, 131), Op::Mul(59, 123), Op::Add(132, 133), Op::Mul(62, 124), Op::Add(134, 135), Op::Mul(65, 125), Op::Add(136, 137), Op::Mul(68, 126), Op::Add(138, 139), Op::AssertEq(vec![140], vec![32], None), Op::Add(123, 124), Op::Add(141, 125), Op::Add(142, 126), Op::Sub(143, 74), Op::EqZero(144), Op::Add(119, 120), Op::Add(146, 121), Op::Add(147, 122), Op::EqZero(148), Op::Sub(81, 149), Op::Mul(145, 150), Op::Sub(81, 151), Op::AssertEq(vec![152], vec![81], None), Op::UnconstrainedGToBytes(33), Op::U8RangeCheck(153, 154), Op::U8RangeCheck(155, 156), Op::U8RangeCheck(157, 158), Op::U8RangeCheck(159, 160), Op::Mul(50, 154), Op::Add(153, 161), Op::Mul(53, 155), Op::Add(162, 163), Op::Mul(56, 156), Op::Add(164, 165), Op::Mul(59, 157), Op::Add(166, 167), Op::Mul(62, 158), Op::Add(168, 169), Op::Mul(65, 159), Op::Add(170, 171), Op::Mul(68, 160), Op::Add(172, 173), Op::AssertEq(vec![174], vec![33], None), Op::Add(157, 158), Op::Add(175, 159), Op::Add(176, 160), Op::Sub(177, 74), Op::EqZero(178), Op::Add(153, 154), Op::Add(180, 155), Op::Add(181, 156), Op::EqZero(182), Op::Sub(81, 183), Op::Mul(179, 184), Op::Sub(81, 185), Op::AssertEq(vec![186], vec![81], None), Op::Const(G::from_u64(0)), Op::Store(vec![81, 81, 81, 81, 81, 81, 81, 81, 81, 81]), Op::Store(vec![187, 153, 154, 155, 156, 157, 158, 159, 160, 188]), Op::Store(vec![187, 119, 120, 121, 122, 123, 124, 125, 126, 189]), Op::Store(vec![187, 85, 86, 87, 88, 89, 90, 91, 92, 190]), Op::Store(vec![187, 42, 43, 44, 45, 46, 47, 48, 49, 191]), Op::Store(vec![187, 192, 41])], ctrl: Ctrl::Return(3, vec![39, 40, 193]) })) })].into_iter().collect(), None) }))) })].into_iter().collect(), None) })].into_iter().collect(), None) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 9, selectors: 4, auxiliaries: 89, lookups: 32 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(4, 4)], ctrl: Ctrl::Match(9, [(G::from_u64(1), Block { ops: vec![Op::Sub(6, 8), Op::EqZero(13), Op::Const(G::from_u64(1)), Op::AssertEq(vec![14], vec![15], None), Op::Call(318, vec![7], 1, false), Op::Const(G::from_u64(0)), Op::AssertEq(vec![16], vec![17], None), Op::Store(vec![15, 15, 15])], ctrl: Ctrl::Return(0, vec![0, 1, 18]) }), (G::from_u64(0), Block { ops: vec![Op::Load(4, 2)], ctrl: Ctrl::Match(13, [(G::from_u64(0), Block { ops: vec![Op::Load(3, 3)], ctrl: Ctrl::Match(17, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(10, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(315, vec![11], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![20], vec![21], None), Op::Load(3, 5)], ctrl: Ctrl::Match(22, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(6, 25), Op::Const(G::from_u64(0)), Op::Call(323, vec![11, 27], 2, false)], ctrl: Ctrl::MatchContinue(23, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Yield(1, vec![0, 1, 28, 29]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::Yield(2, vec![28, 29, 0, 1]) })), 4, 1, 0, Box::new(Block { ops: vec![Op::Call(134, vec![24, 26, 14, 15, 30, 31, 32, 33], 2, false), Op::Call(160, vec![34, 35, 26, 14, 15, 7], 3, false), Op::Call(161, vec![36, 37, 16, 19, 12, 24, 26, 38, 8], 3, false), Op::UnconstrainedGToBytes(30), Op::U8RangeCheck(42, 43), Op::U8RangeCheck(44, 45), Op::U8RangeCheck(46, 47), Op::U8RangeCheck(48, 49), Op::Const(G::from_u64(256)), Op::Mul(50, 43), Op::Add(42, 51), Op::Const(G::from_u64(65536)), Op::Mul(53, 44), Op::Add(52, 54), Op::Const(G::from_u64(16777216)), Op::Mul(56, 45), Op::Add(55, 57), Op::Const(G::from_u64(4294967296)), Op::Mul(59, 46), Op::Add(58, 60), Op::Const(G::from_u64(1099511627776)), Op::Mul(62, 47), Op::Add(61, 63), Op::Const(G::from_u64(281474976710656)), Op::Mul(65, 48), Op::Add(64, 66), Op::Const(G::from_u64(72057594037927936)), Op::Mul(68, 49), Op::Add(67, 69), Op::AssertEq(vec![70], vec![30], None), Op::Add(46, 47), Op::Add(71, 48), Op::Add(72, 49), Op::Const(G::from_u64(1020)), Op::Sub(73, 74), Op::EqZero(75), Op::Add(42, 43), Op::Add(77, 44), Op::Add(78, 45), Op::EqZero(79), Op::Const(G::from_u64(1)), Op::Sub(81, 80), Op::Mul(76, 82), Op::Sub(81, 83), Op::AssertEq(vec![84], vec![81], None), Op::UnconstrainedGToBytes(31), Op::U8RangeCheck(85, 86), Op::U8RangeCheck(87, 88), Op::U8RangeCheck(89, 90), Op::U8RangeCheck(91, 92), Op::Mul(50, 86), Op::Add(85, 93), Op::Mul(53, 87), Op::Add(94, 95), Op::Mul(56, 88), Op::Add(96, 97), Op::Mul(59, 89), Op::Add(98, 99), Op::Mul(62, 90), Op::Add(100, 101), Op::Mul(65, 91), Op::Add(102, 103), Op::Mul(68, 92), Op::Add(104, 105), Op::AssertEq(vec![106], vec![31], None), Op::Add(89, 90), Op::Add(107, 91), Op::Add(108, 92), Op::Sub(109, 74), Op::EqZero(110), Op::Add(85, 86), Op::Add(112, 87), Op::Add(113, 88), Op::EqZero(114), Op::Sub(81, 115), Op::Mul(111, 116), Op::Sub(81, 117), Op::AssertEq(vec![118], vec![81], None), Op::UnconstrainedGToBytes(32), Op::U8RangeCheck(119, 120), Op::U8RangeCheck(121, 122), Op::U8RangeCheck(123, 124), Op::U8RangeCheck(125, 126), Op::Mul(50, 120), Op::Add(119, 127), Op::Mul(53, 121), Op::Add(128, 129), Op::Mul(56, 122), Op::Add(130, 131), Op::Mul(59, 123), Op::Add(132, 133), Op::Mul(62, 124), Op::Add(134, 135), Op::Mul(65, 125), Op::Add(136, 137), Op::Mul(68, 126), Op::Add(138, 139), Op::AssertEq(vec![140], vec![32], None), Op::Add(123, 124), Op::Add(141, 125), Op::Add(142, 126), Op::Sub(143, 74), Op::EqZero(144), Op::Add(119, 120), Op::Add(146, 121), Op::Add(147, 122), Op::EqZero(148), Op::Sub(81, 149), Op::Mul(145, 150), Op::Sub(81, 151), Op::AssertEq(vec![152], vec![81], None), Op::UnconstrainedGToBytes(33), Op::U8RangeCheck(153, 154), Op::U8RangeCheck(155, 156), Op::U8RangeCheck(157, 158), Op::U8RangeCheck(159, 160), Op::Mul(50, 154), Op::Add(153, 161), Op::Mul(53, 155), Op::Add(162, 163), Op::Mul(56, 156), Op::Add(164, 165), Op::Mul(59, 157), Op::Add(166, 167), Op::Mul(62, 158), Op::Add(168, 169), Op::Mul(65, 159), Op::Add(170, 171), Op::Mul(68, 160), Op::Add(172, 173), Op::AssertEq(vec![174], vec![33], None), Op::Add(157, 158), Op::Add(175, 159), Op::Add(176, 160), Op::Sub(177, 74), Op::EqZero(178), Op::Add(153, 154), Op::Add(180, 155), Op::Add(181, 156), Op::EqZero(182), Op::Sub(81, 183), Op::Mul(179, 184), Op::Sub(81, 185), Op::AssertEq(vec![186], vec![81], None), Op::Const(G::from_u64(0)), Op::Store(vec![81, 81, 81, 81, 81, 81, 81, 81, 81, 81]), Op::Store(vec![187, 153, 154, 155, 156, 157, 158, 159, 160, 188]), Op::Store(vec![187, 119, 120, 121, 122, 123, 124, 125, 126, 189]), Op::Store(vec![187, 85, 86, 87, 88, 89, 90, 91, 92, 190]), Op::Store(vec![187, 42, 43, 44, 45, 46, 47, 48, 49, 191]), Op::Store(vec![187, 192, 41])], ctrl: Ctrl::Return(3, vec![39, 40, 193]) })) })].into_iter().collect(), None) }))) })].into_iter().collect(), None) })].into_iter().collect(), None) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 9, selectors: 4, auxiliaries: 89, lookups: 32 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_162() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(313, vec![2], 1, false), Op::Sub(25, 24), Op::EqZero(26), Op::Const(G::from_u64(1)), Op::AssertEq(vec![27], vec![28], None), Op::Call(312, vec![1], 1, false), Op::Const(G::from_u64(3)), Op::Call(148, vec![8, 9], 1, false), Op::Add(30, 31), Op::Sub(29, 32), Op::EqZero(33), Op::AssertEq(vec![34], vec![28], None), Op::Call(145, vec![15, 19, 18, 20], 1, false), Op::Const(G::from_u64(0)), Op::Call(155, vec![15, 19, 18, 36], 1, false), Op::Call(310, vec![1, 36], 1, false)], ctrl: Ctrl::Match(38, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(312, vec![38], 1, false), Op::Sub(39, 18), Op::EqZero(40), Op::Const(G::from_u64(1)), Op::AssertEq(vec![41], vec![42], None), Op::Const(G::from_u64(0)), Op::Call(151, vec![35, 0, 20, 19, 43, 18, 15, 16, 17, 38, 5, 3, 4], 1, false), Op::Call(310, vec![1, 42], 1, false)], ctrl: Ctrl::Match(45, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(312, vec![45], 1, false), Op::Sub(46, 18), Op::EqZero(47), Op::Const(G::from_u64(1)), Op::AssertEq(vec![48], vec![49], None), Op::Const(G::from_u64(0)), Op::Call(151, vec![44, 0, 20, 19, 50, 18, 15, 16, 17, 45, 6, 3, 4], 1, false), Op::Const(G::from_u64(2)), Op::Call(310, vec![1, 52], 1, false)], ctrl: Ctrl::Match(53, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(312, vec![53], 1, false), Op::Sub(54, 18), Op::EqZero(55), Op::Const(G::from_u64(1)), Op::AssertEq(vec![56], vec![57], None), Op::Const(G::from_u64(0)), Op::Call(152, vec![51, 0, 20, 19, 58, 18, 15, 16, 17, 53, 7, 3, 4], 1, false), Op::Call(154, vec![59, 1, 13, 8, 9, 14, 15, 18, 0, 20, 19, 16, 17, 3, 4], 1, false), Op::Call(147, vec![60, 19], 1, false), Op::Load(7, 60)], ctrl: Ctrl::Match(62, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(63, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Sub(63, 20), Op::EqZero(69), Op::Const(G::from_u64(1)), Op::AssertEq(vec![70], vec![71], None), Op::Call(161, vec![66, 67, 21, 22, 2, 0, 20, 68, 19], 3, false), Op::Const(G::from_u64(0)), Op::Call(321, vec![23, 75], 2, false), Op::Sub(76, 72), Op::EqZero(78), Op::Sub(77, 73), Op::EqZero(80), Op::Mul(79, 81), Op::AssertEq(vec![82], vec![71], None)], ctrl: Ctrl::Return(0, vec![74]) }))) })].into_iter().collect(), None) }))) }))) }))) }))) }, layout: FunctionLayout { input_size: 25, selectors: 1, auxiliaries: 46, lookups: 20 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(315, vec![2], 1, false), Op::Sub(25, 24), Op::EqZero(26), Op::Const(G::from_u64(1)), Op::AssertEq(vec![27], vec![28], None), Op::Call(314, vec![1], 1, false), Op::Const(G::from_u64(3)), Op::Call(148, vec![8, 9], 1, false), Op::Add(30, 31), Op::Sub(29, 32), Op::EqZero(33), Op::AssertEq(vec![34], vec![28], None), Op::Call(145, vec![15, 19, 18, 20], 1, false), Op::Const(G::from_u64(0)), Op::Call(155, vec![15, 19, 18, 36], 1, false), Op::Call(312, vec![1, 36], 1, false)], ctrl: Ctrl::Match(38, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(314, vec![38], 1, false), Op::Sub(39, 18), Op::EqZero(40), Op::Const(G::from_u64(1)), Op::AssertEq(vec![41], vec![42], None), Op::Const(G::from_u64(0)), Op::Call(151, vec![35, 0, 20, 19, 43, 18, 15, 16, 17, 38, 5, 3, 4], 1, false), Op::Call(312, vec![1, 42], 1, false)], ctrl: Ctrl::Match(45, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(314, vec![45], 1, false), Op::Sub(46, 18), Op::EqZero(47), Op::Const(G::from_u64(1)), Op::AssertEq(vec![48], vec![49], None), Op::Const(G::from_u64(0)), Op::Call(151, vec![44, 0, 20, 19, 50, 18, 15, 16, 17, 45, 6, 3, 4], 1, false), Op::Const(G::from_u64(2)), Op::Call(312, vec![1, 52], 1, false)], ctrl: Ctrl::Match(53, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(314, vec![53], 1, false), Op::Sub(54, 18), Op::EqZero(55), Op::Const(G::from_u64(1)), Op::AssertEq(vec![56], vec![57], None), Op::Const(G::from_u64(0)), Op::Call(152, vec![51, 0, 20, 19, 58, 18, 15, 16, 17, 53, 7, 3, 4], 1, false), Op::Call(154, vec![59, 1, 13, 8, 9, 14, 15, 18, 0, 20, 19, 16, 17, 3, 4], 1, false), Op::Call(147, vec![60, 19], 1, false), Op::Load(7, 60)], ctrl: Ctrl::Match(62, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(63, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Sub(63, 20), Op::EqZero(69), Op::Const(G::from_u64(1)), Op::AssertEq(vec![70], vec![71], None), Op::Call(161, vec![66, 67, 21, 22, 2, 0, 20, 68, 19], 3, false), Op::Const(G::from_u64(0)), Op::Call(323, vec![23, 75], 2, false), Op::Sub(76, 72), Op::EqZero(78), Op::Sub(77, 73), Op::EqZero(80), Op::Mul(79, 81), Op::AssertEq(vec![82], vec![71], None)], ctrl: Ctrl::Return(0, vec![74]) }))) })].into_iter().collect(), None) }))) }))) }))) }))) }, layout: FunctionLayout { input_size: 25, selectors: 1, auxiliaries: 46, lookups: 20 }, entry: false, constrained: true } } #[inline(never)] @@ -863,12 +879,12 @@ fn expected_function_164() -> Function { #[inline(never)] fn expected_function_165() -> Function { - Function { body: Block { ops: vec![Op::Load(4, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![6, 6, 6])], ctrl: Ctrl::Return(0, vec![7]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(3, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(310, vec![3, 1], 1, false), Op::Call(165, vec![5, 1], 1, false), Op::Store(vec![6, 7, 8])], ctrl: Ctrl::Return(1, vec![9]) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 8, lookups: 5 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(4, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![6, 6, 6])], ctrl: Ctrl::Return(0, vec![7]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(3, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(312, vec![3, 1], 1, false), Op::Call(165, vec![5, 1], 1, false), Op::Store(vec![6, 7, 8])], ctrl: Ctrl::Return(1, vec![9]) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 8, lookups: 5 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_166() -> Function { - Function { body: Block { ops: vec![Op::Load(5, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![7, 7, 7, 7])], ctrl: Ctrl::Return(0, vec![8]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(3, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(310, vec![4, 1], 1, false), Op::Call(166, vec![6, 1], 1, false), Op::Store(vec![7, 3, 8, 9])], ctrl: Ctrl::Return(1, vec![10]) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 9, lookups: 5 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(5, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![7, 7, 7, 7])], ctrl: Ctrl::Return(0, vec![8]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(3, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(312, vec![4, 1], 1, false), Op::Call(166, vec![6, 1], 1, false), Op::Store(vec![7, 3, 8, 9])], ctrl: Ctrl::Return(1, vec![10]) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 9, lookups: 5 }, entry: false, constrained: true } } #[inline(never)] @@ -878,27 +894,27 @@ fn expected_function_167() -> Function { #[inline(never)] fn expected_function_168() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(155, vec![9, 11, 10, 13], 1, false), Op::Call(321, vec![0, 13], 2, false)], ctrl: Ctrl::Match(15, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Call(321, vec![0, 17], 2, false)], ctrl: Ctrl::Match(18, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(2)), Op::Call(321, vec![0, 20], 2, false)], ctrl: Ctrl::Match(21, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(131, vec![2, 15, 14, 1, 16, 12], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![23], vec![24], None), Op::Call(131, vec![3, 18, 14, 1, 19, 12], 1, false), Op::AssertEq(vec![25], vec![24], None), Op::Call(131, vec![4, 21, 14, 1, 22, 12], 1, false), Op::AssertEq(vec![26], vec![24], None)], ctrl: Ctrl::Match(6, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(0, vec![27]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(3)), Op::Call(321, vec![0, 27], 2, false)], ctrl: Ctrl::Match(28, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(157, vec![9, 11, 8, 10, 30], 1, false), Op::Call(156, vec![31], 1, false), Op::Sub(12, 32), Op::Call(169, vec![1, 33], 1, false), Op::Call(131, vec![5, 28, 31, 34, 29, 32], 1, false)], ctrl: Ctrl::Return(1, vec![35]) }))) })].into_iter().collect(), None) }))) }))) }))) }, layout: FunctionLayout { input_size: 13, selectors: 2, auxiliaries: 17, lookups: 13 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(155, vec![9, 11, 10, 13], 1, false), Op::Call(323, vec![0, 13], 2, false)], ctrl: Ctrl::Match(15, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Call(323, vec![0, 17], 2, false)], ctrl: Ctrl::Match(18, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(2)), Op::Call(323, vec![0, 20], 2, false)], ctrl: Ctrl::Match(21, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(131, vec![2, 15, 14, 1, 16, 12], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![23], vec![24], None), Op::Call(131, vec![3, 18, 14, 1, 19, 12], 1, false), Op::AssertEq(vec![25], vec![24], None), Op::Call(131, vec![4, 21, 14, 1, 22, 12], 1, false), Op::AssertEq(vec![26], vec![24], None)], ctrl: Ctrl::Match(6, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(0, vec![27]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(3)), Op::Call(323, vec![0, 27], 2, false)], ctrl: Ctrl::Match(28, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(157, vec![9, 11, 8, 10, 30], 1, false), Op::Call(156, vec![31], 1, false), Op::Sub(12, 32), Op::Call(169, vec![1, 33], 1, false), Op::Call(131, vec![5, 28, 31, 34, 29, 32], 1, false)], ctrl: Ctrl::Return(1, vec![35]) }))) })].into_iter().collect(), None) }))) }))) }))) }, layout: FunctionLayout { input_size: 13, selectors: 2, auxiliaries: 17, lookups: 13 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_169() -> Function { - Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(324, vec![3, 1], 1, false), Op::Call(169, vec![4, 1], 1, false), Op::Store(vec![5, 6, 7])], ctrl: Ctrl::Return(1, vec![8]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 7, lookups: 5 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(326, vec![3, 1], 1, false), Op::Call(169, vec![4, 1], 1, false), Op::Store(vec![5, 6, 7])], ctrl: Ctrl::Return(1, vec![8]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 7, lookups: 5 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_170() -> Function { - Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![5, 5, 5])], ctrl: Ctrl::Return(0, vec![6]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(310, vec![3, 1], 1, false), Op::Const(G::from_u64(1)), Op::Store(vec![7, 7, 7]), Op::Store(vec![5, 6, 8]), Op::Call(170, vec![4, 1], 1, false), Op::Store(vec![5, 9, 10])], ctrl: Ctrl::Return(1, vec![11]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 9, lookups: 7 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![5, 5, 5])], ctrl: Ctrl::Return(0, vec![6]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(312, vec![3, 1], 1, false), Op::Const(G::from_u64(1)), Op::Store(vec![7, 7, 7]), Op::Store(vec![5, 6, 8]), Op::Call(170, vec![4, 1], 1, false), Op::Store(vec![5, 9, 10])], ctrl: Ctrl::Return(1, vec![11]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 9, lookups: 7 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_171() -> Function { - Function { body: Block { ops: vec![Op::Load(5, 0)], ctrl: Ctrl::Match(6, [(G::from_u64(1), Block { ops: vec![Op::Call(312, vec![1], 1, false), Op::EqZero(11)], ctrl: Ctrl::Return(0, vec![12]) }), (G::from_u64(0), Block { ops: vec![Op::Load(3, 1)], ctrl: Ctrl::Match(11, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(7, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::AssertEq(vec![7], vec![14], None), Op::Call(312, vec![8], 1, false), Op::Call(312, vec![3], 1, false), Op::AssertEq(vec![15], vec![16], None), Op::Call(169, vec![3, 14], 1, false), Op::Sub(5, 14), Op::Call(170, vec![2, 4], 1, false), Op::Const(G::from_u64(0)), Op::Store(vec![14, 14, 14]), Op::Store(vec![20, 18, 21]), Op::Call(131, vec![12, 19, 22, 17, 9, 18], 1, false), Op::AssertEq(vec![23], vec![14], None), Op::Add(4, 14), Op::Call(171, vec![10, 13, 2, 17, 24, 18], 1, false)], ctrl: Ctrl::Return(1, vec![25]) }))) })].into_iter().collect(), None) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 6, selectors: 2, auxiliaries: 17, lookups: 11 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(5, 0)], ctrl: Ctrl::Match(6, [(G::from_u64(1), Block { ops: vec![Op::Call(314, vec![1], 1, false), Op::EqZero(11)], ctrl: Ctrl::Return(0, vec![12]) }), (G::from_u64(0), Block { ops: vec![Op::Load(3, 1)], ctrl: Ctrl::Match(11, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(7, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::AssertEq(vec![7], vec![14], None), Op::Call(314, vec![8], 1, false), Op::Call(314, vec![3], 1, false), Op::AssertEq(vec![15], vec![16], None), Op::Call(169, vec![3, 14], 1, false), Op::Sub(5, 14), Op::Call(170, vec![2, 4], 1, false), Op::Const(G::from_u64(0)), Op::Store(vec![14, 14, 14]), Op::Store(vec![20, 18, 21]), Op::Call(131, vec![12, 19, 22, 17, 9, 18], 1, false), Op::AssertEq(vec![23], vec![14], None), Op::Add(4, 14), Op::Call(171, vec![10, 13, 2, 17, 24, 18], 1, false)], ctrl: Ctrl::Return(1, vec![25]) }))) })].into_iter().collect(), None) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 6, selectors: 2, auxiliaries: 17, lookups: 11 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_172() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(6, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(312, vec![6], 1, false), Op::Call(317, vec![7], 1, false), Op::Sub(33, 32), Op::EqZero(34), Op::Const(G::from_u64(1)), Op::AssertEq(vec![35], vec![36], None), Op::Call(313, vec![8], 1, false), Op::Const(G::from_u64(3)), Op::Call(148, vec![4, 5], 1, false), Op::Add(38, 39), Op::AssertEq(vec![37], vec![40], None), Op::Call(323, vec![9], 1, false), Op::Sub(41, 32), Op::EqZero(42), Op::AssertEq(vec![43], vec![36], None), Op::Call(313, vec![10], 1, false), Op::AssertEq(vec![44], vec![36], None), Op::Store(vec![36, 36, 36])], ctrl: Ctrl::MatchContinue(4, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Yield(0, vec![45]) }), (G::from_u64(1), Block { ops: vec![Op::Call(139, vec![5, 45], 1, false)], ctrl: Ctrl::Yield(1, vec![46]) })].into_iter().collect(), None, 1, 1, 1, Box::new(Block { ops: vec![Op::Call(139, vec![3, 46], 1, false), Op::Call(139, vec![2, 47], 1, false), Op::Call(139, vec![1, 48], 1, false), Op::Call(314, vec![0, 49], 1, false), Op::Const(G::from_u64(1)), Op::Store(vec![51, 51, 51]), Op::Call(180, vec![50, 52], 18, false), Op::Const(G::from_u64(256)), Op::Mul(71, 54), Op::Add(53, 72), Op::Const(G::from_u64(65536)), Op::Mul(74, 55), Op::Add(73, 75), Op::Const(G::from_u64(16777216)), Op::Mul(77, 56), Op::Add(76, 78), Op::Const(G::from_u64(4294967296)), Op::Mul(80, 57), Op::Add(79, 81), Op::Const(G::from_u64(1099511627776)), Op::Mul(83, 58), Op::Add(82, 84), Op::Const(G::from_u64(281474976710656)), Op::Mul(86, 59), Op::Add(85, 87), Op::Const(G::from_u64(72057594037927936)), Op::Mul(89, 60), Op::Add(88, 90), Op::Mul(71, 62), Op::Add(61, 92), Op::Mul(74, 63), Op::Add(93, 94), Op::Mul(77, 64), Op::Add(95, 96), Op::Mul(80, 65), Op::Add(97, 98), Op::Mul(83, 66), Op::Add(99, 100), Op::Mul(86, 67), Op::Add(101, 102), Op::Mul(89, 68), Op::Add(103, 104), Op::Call(143, vec![69, 6, 7, 30], 2, false), Op::Call(137, vec![10, 52], 1, false), Op::Call(314, vec![107, 108], 1, false), Op::Call(141, vec![109, 6], 1, false), Op::Call(142, vec![110, 11, 12, 13, 14, 15, 16, 17, 18, 31], 2, false), Op::Add(32, 28), Op::Const(G::from_u64(33)), Op::U32LessThan(113, 114), Op::AssertEq(vec![115], vec![51], None), Op::Call(164, vec![111, 112, 29, 113], 1, false), Op::Call(168, vec![8, 116, 19, 20, 21, 22, 4, 5, 23, 24, 27, 28, 113], 1, false), Op::AssertEq(vec![117], vec![51], None), Op::Const(G::from_u64(0)), Op::Call(167, vec![8, 9, 118, 29], 1, false), Op::Call(163, vec![111, 112, 119, 91, 105, 1, 2, 3, 4, 5, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 113, 106, 6, 10, 32], 1, false), Op::Call(171, vec![9, 6, 120, 116, 118, 113], 1, false)], ctrl: Ctrl::Return(2, vec![121]) })) }))) }, layout: FunctionLayout { input_size: 32, selectors: 3, auxiliaries: 55, lookups: 31 }, entry: false, constrained: false } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(6, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(314, vec![6], 1, false), Op::Call(319, vec![7], 1, false), Op::Sub(33, 32), Op::EqZero(34), Op::Const(G::from_u64(1)), Op::AssertEq(vec![35], vec![36], None), Op::Call(315, vec![8], 1, false), Op::Const(G::from_u64(3)), Op::Call(148, vec![4, 5], 1, false), Op::Add(38, 39), Op::AssertEq(vec![37], vec![40], None), Op::Call(325, vec![9], 1, false), Op::Sub(41, 32), Op::EqZero(42), Op::AssertEq(vec![43], vec![36], None), Op::Call(315, vec![10], 1, false), Op::AssertEq(vec![44], vec![36], None), Op::Store(vec![36, 36, 36])], ctrl: Ctrl::MatchContinue(4, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Yield(0, vec![45]) }), (G::from_u64(1), Block { ops: vec![Op::Call(139, vec![5, 45], 1, false)], ctrl: Ctrl::Yield(1, vec![46]) })].into_iter().collect(), None, 1, 1, 1, Box::new(Block { ops: vec![Op::Call(139, vec![3, 46], 1, false), Op::Call(139, vec![2, 47], 1, false), Op::Call(139, vec![1, 48], 1, false), Op::Call(316, vec![0, 49], 1, false), Op::Const(G::from_u64(1)), Op::Store(vec![51, 51, 51]), Op::Call(180, vec![50, 52], 18, false), Op::Const(G::from_u64(256)), Op::Mul(71, 54), Op::Add(53, 72), Op::Const(G::from_u64(65536)), Op::Mul(74, 55), Op::Add(73, 75), Op::Const(G::from_u64(16777216)), Op::Mul(77, 56), Op::Add(76, 78), Op::Const(G::from_u64(4294967296)), Op::Mul(80, 57), Op::Add(79, 81), Op::Const(G::from_u64(1099511627776)), Op::Mul(83, 58), Op::Add(82, 84), Op::Const(G::from_u64(281474976710656)), Op::Mul(86, 59), Op::Add(85, 87), Op::Const(G::from_u64(72057594037927936)), Op::Mul(89, 60), Op::Add(88, 90), Op::Mul(71, 62), Op::Add(61, 92), Op::Mul(74, 63), Op::Add(93, 94), Op::Mul(77, 64), Op::Add(95, 96), Op::Mul(80, 65), Op::Add(97, 98), Op::Mul(83, 66), Op::Add(99, 100), Op::Mul(86, 67), Op::Add(101, 102), Op::Mul(89, 68), Op::Add(103, 104), Op::Call(143, vec![69, 6, 7, 30], 2, false), Op::Call(137, vec![10, 52], 1, false), Op::Call(316, vec![107, 108], 1, false), Op::Call(141, vec![109, 6], 1, false), Op::Call(142, vec![110, 11, 12, 13, 14, 15, 16, 17, 18, 31], 2, false), Op::Add(32, 28), Op::Const(G::from_u64(33)), Op::U32LessThan(113, 114), Op::AssertEq(vec![115], vec![51], None), Op::Call(164, vec![111, 112, 29, 113], 1, false), Op::Call(168, vec![8, 116, 19, 20, 21, 22, 4, 5, 23, 24, 27, 28, 113], 1, false), Op::AssertEq(vec![117], vec![51], None), Op::Const(G::from_u64(0)), Op::Call(167, vec![8, 9, 118, 29], 1, false), Op::Call(163, vec![111, 112, 119, 91, 105, 1, 2, 3, 4, 5, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 113, 106, 6, 10, 32], 1, false), Op::Call(171, vec![9, 6, 120, 116, 118, 113], 1, false)], ctrl: Ctrl::Return(2, vec![121]) })) }))) }, layout: FunctionLayout { input_size: 32, selectors: 3, auxiliaries: 55, lookups: 31 }, entry: false, constrained: false } } #[inline(never)] @@ -953,12 +969,12 @@ fn expected_function_182() -> Function { #[inline(never)] fn expected_function_183() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![9, 9, 9]), Op::Call(174, vec![1, 2, 3, 4, 5, 6, 7, 8, 10], 1, false), Op::Call(314, vec![0, 11], 1, false)], ctrl: Ctrl::Return(0, vec![12]) }, layout: FunctionLayout { input_size: 9, selectors: 1, auxiliaries: 4, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![9, 9, 9]), Op::Call(174, vec![1, 2, 3, 4, 5, 6, 7, 8, 10], 1, false), Op::Call(316, vec![0, 11], 1, false)], ctrl: Ctrl::Return(0, vec![12]) }, layout: FunctionLayout { input_size: 9, selectors: 1, auxiliaries: 4, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_184() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![2, 2, 2]), Op::Call(176, vec![1, 3], 1, false), Op::Call(314, vec![0, 4], 1, false)], ctrl: Ctrl::Return(0, vec![5]) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 4, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![2, 2, 2]), Op::Call(176, vec![1, 3], 1, false), Op::Call(316, vec![0, 4], 1, false)], ctrl: Ctrl::Return(0, vec![5]) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 4, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] @@ -1018,7 +1034,7 @@ fn expected_function_195() -> Function { #[inline(never)] fn expected_function_196() -> Function { - Function { body: Block { ops: vec![Op::Call(319, vec![0, 1], 4, false)], ctrl: Ctrl::Match(15, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![16, 19]) }), (G::from_u64(1), Block { ops: vec![Op::Add(16, 16), Op::Add(19, 17)], ctrl: Ctrl::Match(20, [(G::from_u64(0), Block { ops: vec![Op::Call(321, vec![4, 18], 2, false)], ctrl: Ctrl::Return(1, vec![21, 22]) }), (G::from_u64(1), Block { ops: vec![Op::Call(321, vec![5, 18], 2, false)], ctrl: Ctrl::Return(2, vec![21, 22]) }), (G::from_u64(2), Block { ops: vec![Op::Call(321, vec![2, 18], 2, false)], ctrl: Ctrl::Return(3, vec![21, 22]) }), (G::from_u64(3), Block { ops: vec![Op::Call(321, vec![3, 18], 2, false)], ctrl: Ctrl::Return(4, vec![21, 22]) }), (G::from_u64(4), Block { ops: vec![Op::Call(321, vec![6, 18], 2, false)], ctrl: Ctrl::Return(5, vec![21, 22]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(321, vec![7, 18], 2, false)], ctrl: Ctrl::Return(6, vec![21, 22]) }))) }), (G::from_u64(2), Block { ops: vec![Op::Call(321, vec![8, 16], 2, false)], ctrl: Ctrl::Return(7, vec![19, 20]) }), (G::from_u64(3), Block { ops: vec![], ctrl: Ctrl::Return(8, vec![9, 10]) }), (G::from_u64(4), Block { ops: vec![], ctrl: Ctrl::Return(9, vec![11, 12]) }), (G::from_u64(5), Block { ops: vec![], ctrl: Ctrl::Return(10, vec![13, 14]) }), (G::from_u64(6), Block { ops: vec![Op::Call(196, vec![0, 16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2, false), Op::Call(196, vec![0, 17, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2, false), Op::Add(19, 21), Op::Add(20, 22)], ctrl: Ctrl::Return(11, vec![23, 24]) }), (G::from_u64(7), Block { ops: vec![Op::Call(196, vec![0, 16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2, false), Op::Call(196, vec![0, 17, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2, false), Op::Sub(19, 21), Op::Sub(20, 22)], ctrl: Ctrl::Return(12, vec![23, 24]) }), (G::from_u64(8), Block { ops: vec![Op::Call(196, vec![0, 16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2, false), Op::Call(196, vec![0, 17, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2, false), Op::Mul(19, 21), Op::Const(G::from_u64(7)), Op::Mul(20, 22), Op::Mul(24, 25), Op::Add(23, 26), Op::Mul(19, 22), Op::Mul(20, 21), Op::Add(28, 29)], ctrl: Ctrl::Return(13, vec![27, 30]) }), (G::from_u64(9), Block { ops: vec![Op::Call(196, vec![0, 16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2, false), Op::Const(G::from_u64(0)), Op::Sub(21, 19), Op::Sub(21, 20)], ctrl: Ctrl::Return(14, vec![22, 23]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 15, selectors: 15, auxiliaries: 13, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(321, vec![0, 1], 4, false)], ctrl: Ctrl::Match(15, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![16, 19]) }), (G::from_u64(1), Block { ops: vec![Op::Add(16, 16), Op::Add(19, 17)], ctrl: Ctrl::Match(20, [(G::from_u64(0), Block { ops: vec![Op::Call(323, vec![4, 18], 2, false)], ctrl: Ctrl::Return(1, vec![21, 22]) }), (G::from_u64(1), Block { ops: vec![Op::Call(323, vec![5, 18], 2, false)], ctrl: Ctrl::Return(2, vec![21, 22]) }), (G::from_u64(2), Block { ops: vec![Op::Call(323, vec![2, 18], 2, false)], ctrl: Ctrl::Return(3, vec![21, 22]) }), (G::from_u64(3), Block { ops: vec![Op::Call(323, vec![3, 18], 2, false)], ctrl: Ctrl::Return(4, vec![21, 22]) }), (G::from_u64(4), Block { ops: vec![Op::Call(323, vec![6, 18], 2, false)], ctrl: Ctrl::Return(5, vec![21, 22]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(323, vec![7, 18], 2, false)], ctrl: Ctrl::Return(6, vec![21, 22]) }))) }), (G::from_u64(2), Block { ops: vec![Op::Call(323, vec![8, 16], 2, false)], ctrl: Ctrl::Return(7, vec![19, 20]) }), (G::from_u64(3), Block { ops: vec![], ctrl: Ctrl::Return(8, vec![9, 10]) }), (G::from_u64(4), Block { ops: vec![], ctrl: Ctrl::Return(9, vec![11, 12]) }), (G::from_u64(5), Block { ops: vec![], ctrl: Ctrl::Return(10, vec![13, 14]) }), (G::from_u64(6), Block { ops: vec![Op::Call(196, vec![0, 16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2, false), Op::Call(196, vec![0, 17, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2, false), Op::Add(19, 21), Op::Add(20, 22)], ctrl: Ctrl::Return(11, vec![23, 24]) }), (G::from_u64(7), Block { ops: vec![Op::Call(196, vec![0, 16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2, false), Op::Call(196, vec![0, 17, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2, false), Op::Sub(19, 21), Op::Sub(20, 22)], ctrl: Ctrl::Return(12, vec![23, 24]) }), (G::from_u64(8), Block { ops: vec![Op::Call(196, vec![0, 16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2, false), Op::Call(196, vec![0, 17, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2, false), Op::Mul(19, 21), Op::Const(G::from_u64(7)), Op::Mul(20, 22), Op::Mul(24, 25), Op::Add(23, 26), Op::Mul(19, 22), Op::Mul(20, 21), Op::Add(28, 29)], ctrl: Ctrl::Return(13, vec![27, 30]) }), (G::from_u64(9), Block { ops: vec![Op::Call(196, vec![0, 16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 2, false), Op::Const(G::from_u64(0)), Op::Sub(21, 19), Op::Sub(21, 20)], ctrl: Ctrl::Return(14, vec![22, 23]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 15, selectors: 15, auxiliaries: 13, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] @@ -1038,12 +1054,12 @@ fn expected_function_199() -> Function { #[inline(never)] fn expected_function_200() -> Function { - Function { body: Block { ops: vec![Op::Load(4, 4)], ctrl: Ctrl::Match(44, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0, 1]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(45, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(199, vec![46, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], 4, false), Op::Add(48, 22), Op::Add(49, 23), Op::Add(50, 24), Op::Add(51, 25), Op::Call(196, vec![30, 45, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], 2, false), Op::Call(198, vec![14, 15, 16, 17, 52, 53, 54, 55], 4, false), Op::Mul(56, 10), Op::Const(G::from_u64(7)), Op::Mul(57, 11), Op::Mul(63, 64), Op::Add(62, 65), Op::Mul(56, 11), Op::Mul(57, 10), Op::Add(67, 68), Op::Add(58, 66), Op::Add(59, 69), Op::Mul(56, 12), Op::Mul(57, 13), Op::Mul(63, 73), Op::Add(72, 74), Op::Mul(56, 13), Op::Mul(57, 12), Op::Add(76, 77), Op::Add(60, 75), Op::Add(61, 78), Op::Call(198, vec![10, 11, 12, 13, 52, 53, 54, 55], 4, false), Op::Const(G::from_u64(1)), Op::Add(5, 85), Op::Sub(86, 6)], ctrl: Ctrl::Match(87, [(G::from_u64(0), Block { ops: vec![Op::Add(7, 7), Op::Call(321, vec![35, 88], 2, false), Op::Const(G::from_u64(1)), Op::Add(88, 91), Op::Call(321, vec![35, 92], 2, false), Op::Const(G::from_u64(0)), Op::Call(321, vec![36, 95], 2, false), Op::Add(96, 18), Op::Add(97, 19), Op::Call(321, vec![36, 91], 2, false), Op::Add(100, 20), Op::Add(101, 21), Op::Sub(98, 89), Op::Sub(99, 90), Op::Sub(102, 93), Op::Sub(103, 94), Op::Call(198, vec![81, 82, 83, 84, 104, 105, 106, 107], 4, false), Op::Sub(108, 70), Op::Sub(109, 71), Op::Call(193, vec![0, 1, 2, 3, 112, 113], 2, false), Op::Sub(110, 79), Op::Sub(111, 80), Op::Call(193, vec![114, 115, 2, 3, 116, 117], 2, false)], ctrl: Ctrl::Return(1, vec![118, 119]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(8, 88)], ctrl: Ctrl::Match(89, [(G::from_u64(0), Block { ops: vec![Op::Add(7, 7), Op::Call(321, vec![35, 90], 2, false), Op::Const(G::from_u64(1)), Op::Add(90, 93), Op::Call(321, vec![35, 94], 2, false), Op::Const(G::from_u64(2)), Op::Add(90, 97), Op::Call(321, vec![35, 98], 2, false), Op::Const(G::from_u64(3)), Op::Add(90, 101), Op::Call(321, vec![35, 102], 2, false), Op::Sub(99, 91), Op::Sub(100, 92), Op::Sub(103, 95), Op::Sub(104, 96), Op::Call(198, vec![81, 82, 83, 84, 105, 106, 107, 108], 4, false), Op::Sub(109, 70), Op::Sub(110, 71), Op::Call(193, vec![0, 1, 2, 3, 113, 114], 2, false), Op::Sub(111, 79), Op::Sub(112, 80), Op::Call(193, vec![115, 116, 2, 3, 117, 118], 2, false), Op::Add(5, 93), Op::Add(7, 93), Op::Const(G::from_u64(0)), Op::Call(200, vec![119, 120, 2, 3, 47, 121, 6, 122, 9, 9, 93, 123, 123, 123, 123, 123, 123, 123, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], 2, false)], ctrl: Ctrl::Return(2, vec![124, 125]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Add(5, 90), Op::Sub(8, 90), Op::Call(200, vec![0, 1, 2, 3, 47, 91, 6, 7, 92, 9, 81, 82, 83, 84, 70, 71, 79, 80, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], 2, false)], ctrl: Ctrl::Return(3, vec![93, 94]) }))) }))) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 44, selectors: 4, auxiliaries: 46, lookups: 14 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(4, 4)], ctrl: Ctrl::Match(44, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0, 1]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(45, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(199, vec![46, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], 4, false), Op::Add(48, 22), Op::Add(49, 23), Op::Add(50, 24), Op::Add(51, 25), Op::Call(196, vec![30, 45, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], 2, false), Op::Call(198, vec![14, 15, 16, 17, 52, 53, 54, 55], 4, false), Op::Mul(56, 10), Op::Const(G::from_u64(7)), Op::Mul(57, 11), Op::Mul(63, 64), Op::Add(62, 65), Op::Mul(56, 11), Op::Mul(57, 10), Op::Add(67, 68), Op::Add(58, 66), Op::Add(59, 69), Op::Mul(56, 12), Op::Mul(57, 13), Op::Mul(63, 73), Op::Add(72, 74), Op::Mul(56, 13), Op::Mul(57, 12), Op::Add(76, 77), Op::Add(60, 75), Op::Add(61, 78), Op::Call(198, vec![10, 11, 12, 13, 52, 53, 54, 55], 4, false), Op::Const(G::from_u64(1)), Op::Add(5, 85), Op::Sub(86, 6)], ctrl: Ctrl::Match(87, [(G::from_u64(0), Block { ops: vec![Op::Add(7, 7), Op::Call(323, vec![35, 88], 2, false), Op::Const(G::from_u64(1)), Op::Add(88, 91), Op::Call(323, vec![35, 92], 2, false), Op::Const(G::from_u64(0)), Op::Call(323, vec![36, 95], 2, false), Op::Add(96, 18), Op::Add(97, 19), Op::Call(323, vec![36, 91], 2, false), Op::Add(100, 20), Op::Add(101, 21), Op::Sub(98, 89), Op::Sub(99, 90), Op::Sub(102, 93), Op::Sub(103, 94), Op::Call(198, vec![81, 82, 83, 84, 104, 105, 106, 107], 4, false), Op::Sub(108, 70), Op::Sub(109, 71), Op::Call(193, vec![0, 1, 2, 3, 112, 113], 2, false), Op::Sub(110, 79), Op::Sub(111, 80), Op::Call(193, vec![114, 115, 2, 3, 116, 117], 2, false)], ctrl: Ctrl::Return(1, vec![118, 119]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(8, 88)], ctrl: Ctrl::Match(89, [(G::from_u64(0), Block { ops: vec![Op::Add(7, 7), Op::Call(323, vec![35, 90], 2, false), Op::Const(G::from_u64(1)), Op::Add(90, 93), Op::Call(323, vec![35, 94], 2, false), Op::Const(G::from_u64(2)), Op::Add(90, 97), Op::Call(323, vec![35, 98], 2, false), Op::Const(G::from_u64(3)), Op::Add(90, 101), Op::Call(323, vec![35, 102], 2, false), Op::Sub(99, 91), Op::Sub(100, 92), Op::Sub(103, 95), Op::Sub(104, 96), Op::Call(198, vec![81, 82, 83, 84, 105, 106, 107, 108], 4, false), Op::Sub(109, 70), Op::Sub(110, 71), Op::Call(193, vec![0, 1, 2, 3, 113, 114], 2, false), Op::Sub(111, 79), Op::Sub(112, 80), Op::Call(193, vec![115, 116, 2, 3, 117, 118], 2, false), Op::Add(5, 93), Op::Add(7, 93), Op::Const(G::from_u64(0)), Op::Call(200, vec![119, 120, 2, 3, 47, 121, 6, 122, 9, 9, 93, 123, 123, 123, 123, 123, 123, 123, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], 2, false)], ctrl: Ctrl::Return(2, vec![124, 125]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Add(5, 90), Op::Sub(8, 90), Op::Call(200, vec![0, 1, 2, 3, 47, 91, 6, 7, 92, 9, 81, 82, 83, 84, 70, 71, 79, 80, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], 2, false)], ctrl: Ctrl::Return(3, vec![93, 94]) }))) }))) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 44, selectors: 4, auxiliaries: 46, lookups: 14 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_201() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(197, vec![28, 28, 25, 26, 1, 0, 4, 5, 6, 7, 8, 9, 10, 19, 20, 21, 22, 23, 24], 2, false), Op::Sub(17, 15), Op::Sub(28, 28), Op::Mul(31, 27), Op::Const(G::from_u64(7)), Op::Mul(32, 28), Op::Mul(34, 35), Op::Add(33, 36), Op::Mul(31, 28), Op::Mul(32, 27), Op::Add(38, 39), Op::Mul(21, 37), Op::Mul(22, 40), Op::Mul(34, 42), Op::Add(41, 43), Op::Mul(21, 40), Op::Mul(22, 37), Op::Add(45, 46), Op::Sub(18, 16), Op::Mul(48, 27), Op::Add(49, 36), Op::Mul(48, 28), Op::Add(51, 39), Op::Mul(21, 50), Op::Mul(22, 52), Op::Mul(34, 54), Op::Add(53, 55), Op::Mul(21, 52), Op::Mul(22, 50), Op::Add(57, 58), Op::Load(4, 2)], ctrl: Ctrl::Match(60, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(321, vec![9, 64], 2, false), Op::Call(321, vec![8, 64], 2, false), Op::Sub(65, 67), Op::Sub(66, 68), Op::Add(69, 44), Op::Add(70, 47), Op::Call(193, vec![29, 30, 25, 26, 71, 72], 2, false), Op::Const(G::from_u64(1)), Op::Call(321, vec![9, 75], 2, false), Op::Call(321, vec![8, 75], 2, false), Op::Sub(76, 78), Op::Sub(77, 79), Op::Add(80, 56), Op::Add(81, 59), Op::Call(193, vec![73, 74, 25, 26, 82, 83], 2, false)], ctrl: Ctrl::Return(0, vec![84, 85]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(313, vec![2], 1, false), Op::Const(G::from_u64(1)), Op::Call(200, vec![29, 30, 25, 26, 2, 64, 65, 64, 3, 3, 66, 64, 64, 64, 64, 64, 64, 64, 44, 47, 56, 59, 11, 28, 12, 28, 13, 28, 14, 28, 0, 4, 5, 6, 7, 8, 9, 10, 19, 20, 21, 22, 23, 24], 2, false)], ctrl: Ctrl::Return(1, vec![67, 68]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 28, selectors: 2, auxiliaries: 29, lookups: 9 }, entry: false, constrained: false } + Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(197, vec![28, 28, 25, 26, 1, 0, 4, 5, 6, 7, 8, 9, 10, 19, 20, 21, 22, 23, 24], 2, false), Op::Sub(17, 15), Op::Sub(28, 28), Op::Mul(31, 27), Op::Const(G::from_u64(7)), Op::Mul(32, 28), Op::Mul(34, 35), Op::Add(33, 36), Op::Mul(31, 28), Op::Mul(32, 27), Op::Add(38, 39), Op::Mul(21, 37), Op::Mul(22, 40), Op::Mul(34, 42), Op::Add(41, 43), Op::Mul(21, 40), Op::Mul(22, 37), Op::Add(45, 46), Op::Sub(18, 16), Op::Mul(48, 27), Op::Add(49, 36), Op::Mul(48, 28), Op::Add(51, 39), Op::Mul(21, 50), Op::Mul(22, 52), Op::Mul(34, 54), Op::Add(53, 55), Op::Mul(21, 52), Op::Mul(22, 50), Op::Add(57, 58), Op::Load(4, 2)], ctrl: Ctrl::Match(60, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(323, vec![9, 64], 2, false), Op::Call(323, vec![8, 64], 2, false), Op::Sub(65, 67), Op::Sub(66, 68), Op::Add(69, 44), Op::Add(70, 47), Op::Call(193, vec![29, 30, 25, 26, 71, 72], 2, false), Op::Const(G::from_u64(1)), Op::Call(323, vec![9, 75], 2, false), Op::Call(323, vec![8, 75], 2, false), Op::Sub(76, 78), Op::Sub(77, 79), Op::Add(80, 56), Op::Add(81, 59), Op::Call(193, vec![73, 74, 25, 26, 82, 83], 2, false)], ctrl: Ctrl::Return(0, vec![84, 85]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(315, vec![2], 1, false), Op::Const(G::from_u64(1)), Op::Call(200, vec![29, 30, 25, 26, 2, 64, 65, 64, 3, 3, 66, 64, 64, 64, 64, 64, 64, 64, 44, 47, 56, 59, 11, 28, 12, 28, 13, 28, 14, 28, 0, 4, 5, 6, 7, 8, 9, 10, 19, 20, 21, 22, 23, 24], 2, false)], ctrl: Ctrl::Return(1, vec![67, 68]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 28, selectors: 2, auxiliaries: 29, lookups: 9 }, entry: false, constrained: false } } #[inline(never)] @@ -1063,12 +1079,12 @@ fn expected_function_204() -> Function { #[inline(never)] fn expected_function_205() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(2, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![4, 4, 4, 4])], ctrl: Ctrl::Return(0, vec![5, 5]) }), (G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Match(0, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![4, 4, 4, 4])], ctrl: Ctrl::Return(1, vec![5, 5]) }), (G::from_u64(1), Block { ops: vec![Op::Call(310, vec![1, 3], 1, false), Op::Const(G::from_u64(0)), Op::Call(310, vec![4, 5], 1, false), Op::Const(G::from_u64(1)), Op::Call(310, vec![4, 7], 1, false)], ctrl: Ctrl::Return(2, vec![6, 8]) })].into_iter().collect(), None) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 4, selectors: 3, auxiliaries: 4, lookups: 4 }, entry: false, constrained: false } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(2, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![4, 4, 4, 4])], ctrl: Ctrl::Return(0, vec![5, 5]) }), (G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Match(0, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![4, 4, 4, 4])], ctrl: Ctrl::Return(1, vec![5, 5]) }), (G::from_u64(1), Block { ops: vec![Op::Call(312, vec![1, 3], 1, false), Op::Const(G::from_u64(0)), Op::Call(312, vec![4, 5], 1, false), Op::Const(G::from_u64(1)), Op::Call(312, vec![4, 7], 1, false)], ctrl: Ctrl::Return(2, vec![6, 8]) })].into_iter().collect(), None) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 4, selectors: 3, auxiliaries: 4, lookups: 4 }, entry: false, constrained: false } } #[inline(never)] fn expected_function_206() -> Function { - Function { body: Block { ops: vec![Op::Load(11, 0)], ctrl: Ctrl::Match(20, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(0, vec![31]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(21, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(310, vec![2, 9], 1, false), Op::Const(G::from_u64(32)), Op::U8LessThan(31, 32), Op::Const(G::from_u64(1)), Op::AssertEq(vec![33], vec![34], None), Op::Call(204, vec![24], 1, false), Op::Call(321, vec![3, 9], 2, false), Op::Call(310, vec![4, 9], 1, false), Op::Const(G::from_u64(0)), Op::Call(310, vec![38, 39], 1, false), Op::Call(310, vec![38, 34], 1, false), Op::Call(310, vec![5, 9], 1, false), Op::Call(310, vec![42, 39], 1, false), Op::Call(310, vec![42, 34], 1, false), Op::Call(321, vec![1, 9], 2, false)], ctrl: Ctrl::MatchContinue(45, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![47, 47, 47, 47])], ctrl: Ctrl::Yield(1, vec![48, 48]) }), (G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Match(6, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![47, 47, 47, 47])], ctrl: Ctrl::Yield(2, vec![48, 48]) }), (G::from_u64(1), Block { ops: vec![Op::Call(310, vec![7, 46], 1, false), Op::Const(G::from_u64(0)), Op::Call(310, vec![47, 48], 1, false), Op::Const(G::from_u64(1)), Op::Call(310, vec![47, 50], 1, false)], ctrl: Ctrl::Yield(3, vec![49, 51]) })].into_iter().collect(), None) })].into_iter().collect(), None, 2, 3, 3, Box::new(Block { ops: vec![Op::Call(313, vec![40], 1, false), Op::Sub(49, 27), Op::EqZero(50), Op::Const(G::from_u64(1)), Op::AssertEq(vec![51], vec![52], None), Op::Call(313, vec![41], 1, false), Op::Sub(53, 27), Op::EqZero(54), Op::AssertEq(vec![55], vec![52], None), Op::Call(313, vec![43], 1, false), Op::Sub(56, 29), Op::EqZero(57), Op::AssertEq(vec![58], vec![52], None), Op::Call(313, vec![44], 1, false), Op::Sub(59, 29), Op::EqZero(60), Op::AssertEq(vec![61], vec![52], None), Op::Call(313, vec![47], 1, false), Op::Sub(62, 28), Op::EqZero(63), Op::AssertEq(vec![64], vec![52], None), Op::Call(313, vec![48], 1, false), Op::Sub(65, 28), Op::EqZero(66), Op::AssertEq(vec![67], vec![52], None), Op::Call(188, vec![18, 19, 31], 2, false), Op::Const(G::from_u64(0)), Op::Sub(68, 52), Op::Sub(69, 70), Op::Call(189, vec![31], 1, false), Op::UnconstrainedGInverse(73), Op::Mul(73, 74), Op::Sub(75, 52), Op::Mul(73, 76), Op::AssertEq(vec![77], vec![70], None), Op::Mul(74, 76), Op::AssertEq(vec![78], vec![70], None), Op::Sub(18, 52), Op::Sub(19, 70), Op::Mul(79, 79), Op::Const(G::from_u64(7)), Op::Mul(80, 80), Op::Mul(82, 83), Op::Sub(81, 84), Op::UnconstrainedGInverse(85), Op::Mul(85, 86), Op::Sub(87, 52), Op::Mul(85, 88), Op::AssertEq(vec![89], vec![70], None), Op::Mul(86, 88), Op::AssertEq(vec![90], vec![70], None), Op::Mul(79, 86), Op::Sub(70, 80), Op::Mul(92, 86), Op::Mul(71, 91), Op::Mul(72, 93), Op::Mul(82, 95), Op::Add(94, 96), Op::Mul(71, 93), Op::Mul(72, 91), Op::Add(98, 99), Op::Sub(18, 74), Op::Mul(101, 101), Op::Sub(102, 84), Op::UnconstrainedGInverse(103), Op::Mul(103, 104), Op::Sub(105, 52), Op::Mul(103, 106), Op::AssertEq(vec![107], vec![70], None), Op::Mul(104, 106), Op::AssertEq(vec![108], vec![70], None), Op::Mul(101, 104), Op::Mul(92, 104), Op::Mul(71, 109), Op::Mul(72, 110), Op::Mul(82, 112), Op::Add(111, 113), Op::Mul(71, 110), Op::Mul(72, 109), Op::Add(115, 116), Op::Mul(71, 71), Op::Mul(72, 72), Op::Mul(82, 119), Op::Sub(118, 120), Op::UnconstrainedGInverse(121), Op::Mul(121, 122), Op::Sub(123, 52), Op::Mul(121, 124), Op::AssertEq(vec![125], vec![70], None), Op::Mul(122, 124), Op::AssertEq(vec![126], vec![70], None), Op::Mul(71, 122), Op::Sub(70, 72), Op::Mul(128, 122), Op::Store(vec![52, 52, 52, 52]), Op::Store(vec![70, 37, 70, 130]), Op::Store(vec![70, 36, 70, 131]), Op::Store(vec![70, 11, 70, 132]), Op::Store(vec![70, 10, 70, 133]), Op::Store(vec![70, 15, 70, 134]), Op::Store(vec![70, 14, 70, 135]), Op::Store(vec![70, 13, 70, 136]), Op::Store(vec![70, 12, 70, 137]), Op::Call(191, vec![31], 1, false), Op::Call(189, vec![31], 1, false), Op::Mul(139, 140), Op::UnconstrainedGInverse(141), Op::Mul(141, 142), Op::Sub(143, 52), Op::Mul(141, 144), Op::AssertEq(vec![145], vec![70], None), Op::Mul(142, 144), Op::AssertEq(vec![146], vec![70], None), Op::Call(197, vec![70, 70, 16, 17, 23, 21, 40, 41, 47, 48, 43, 44, 138, 97, 100, 114, 117, 101, 80], 2, false), Op::Sub(36, 10), Op::Sub(70, 70), Op::Mul(149, 142), Op::Mul(150, 70), Op::Mul(82, 152), Op::Add(151, 153), Op::Mul(149, 70), Op::Mul(150, 142), Op::Add(155, 156), Op::Mul(114, 154), Op::Mul(117, 157), Op::Mul(82, 159), Op::Add(158, 160), Op::Mul(114, 157), Op::Mul(117, 154), Op::Add(162, 163), Op::Sub(37, 11), Op::Mul(165, 142), Op::Add(166, 153), Op::Mul(165, 70), Op::Add(168, 156), Op::Mul(114, 167), Op::Mul(117, 169), Op::Mul(82, 171), Op::Add(170, 172), Op::Mul(114, 169), Op::Mul(117, 167), Op::Add(174, 175), Op::Load(4, 25)], ctrl: Ctrl::MatchContinue(177, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(321, vec![44, 181], 2, false), Op::Call(321, vec![43, 181], 2, false), Op::Sub(182, 184), Op::Sub(183, 185), Op::Add(186, 161), Op::Add(187, 164), Op::Call(193, vec![147, 148, 16, 17, 188, 189], 2, false), Op::Const(G::from_u64(1)), Op::Call(321, vec![44, 192], 2, false), Op::Call(321, vec![43, 192], 2, false), Op::Sub(193, 195), Op::Sub(194, 196), Op::Add(197, 173), Op::Add(198, 176), Op::Call(193, vec![190, 191, 16, 17, 199, 200], 2, false)], ctrl: Ctrl::Yield(4, vec![201, 202]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(313, vec![25], 1, false), Op::Const(G::from_u64(1)), Op::Call(200, vec![147, 148, 16, 17, 25, 181, 182, 181, 26, 26, 183, 181, 181, 181, 181, 181, 181, 181, 161, 164, 173, 176, 12, 70, 13, 70, 14, 70, 15, 70, 21, 40, 41, 47, 48, 43, 44, 138, 97, 100, 114, 117, 101, 80], 2, false)], ctrl: Ctrl::Yield(5, vec![184, 185]) })].into_iter().collect(), None, 2, 12, 6, Box::new(Block { ops: vec![Op::Call(310, vec![8, 9], 1, false), Op::Const(G::from_u64(0)), Op::Call(310, vec![183, 184], 1, false), Op::Call(195, vec![185], 1, false), Op::Call(313, vec![186], 1, false), Op::Sub(187, 35), Op::EqZero(188), Op::Const(G::from_u64(1)), Op::AssertEq(vec![189], vec![190], None), Op::Call(188, vec![18, 19, 31], 2, false), Op::Call(203, vec![186, 191, 192, 190, 184], 2, false), Op::Mul(181, 127), Op::Const(G::from_u64(7)), Op::Mul(182, 129), Op::Mul(196, 197), Op::Add(195, 198), Op::Mul(181, 129), Op::Mul(182, 127), Op::Add(200, 201), Op::Sub(199, 193), Op::EqZero(203), Op::Sub(202, 194), Op::EqZero(205), Op::Mul(204, 206), Op::AssertEq(vec![207], vec![190], None), Op::Add(9, 190), Op::Call(206, vec![30, 1, 2, 3, 4, 5, 6, 7, 8, 208, 36, 37, 12, 13, 14, 15, 16, 17, 18, 19], 1, false)], ctrl: Ctrl::Return(6, vec![209]) })) })) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 20, selectors: 7, auxiliaries: 152, lookups: 50 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(11, 0)], ctrl: Ctrl::Match(20, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(0, vec![31]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(21, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(312, vec![2, 9], 1, false), Op::Const(G::from_u64(32)), Op::U8LessThan(31, 32), Op::Const(G::from_u64(1)), Op::AssertEq(vec![33], vec![34], None), Op::Call(204, vec![24], 1, false), Op::Call(323, vec![3, 9], 2, false), Op::Call(312, vec![4, 9], 1, false), Op::Const(G::from_u64(0)), Op::Call(312, vec![38, 39], 1, false), Op::Call(312, vec![38, 34], 1, false), Op::Call(312, vec![5, 9], 1, false), Op::Call(312, vec![42, 39], 1, false), Op::Call(312, vec![42, 34], 1, false), Op::Call(323, vec![1, 9], 2, false)], ctrl: Ctrl::MatchContinue(45, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![47, 47, 47, 47])], ctrl: Ctrl::Yield(1, vec![48, 48]) }), (G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Match(6, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![47, 47, 47, 47])], ctrl: Ctrl::Yield(2, vec![48, 48]) }), (G::from_u64(1), Block { ops: vec![Op::Call(312, vec![7, 46], 1, false), Op::Const(G::from_u64(0)), Op::Call(312, vec![47, 48], 1, false), Op::Const(G::from_u64(1)), Op::Call(312, vec![47, 50], 1, false)], ctrl: Ctrl::Yield(3, vec![49, 51]) })].into_iter().collect(), None) })].into_iter().collect(), None, 2, 3, 3, Box::new(Block { ops: vec![Op::Call(315, vec![40], 1, false), Op::Sub(49, 27), Op::EqZero(50), Op::Const(G::from_u64(1)), Op::AssertEq(vec![51], vec![52], None), Op::Call(315, vec![41], 1, false), Op::Sub(53, 27), Op::EqZero(54), Op::AssertEq(vec![55], vec![52], None), Op::Call(315, vec![43], 1, false), Op::Sub(56, 29), Op::EqZero(57), Op::AssertEq(vec![58], vec![52], None), Op::Call(315, vec![44], 1, false), Op::Sub(59, 29), Op::EqZero(60), Op::AssertEq(vec![61], vec![52], None), Op::Call(315, vec![47], 1, false), Op::Sub(62, 28), Op::EqZero(63), Op::AssertEq(vec![64], vec![52], None), Op::Call(315, vec![48], 1, false), Op::Sub(65, 28), Op::EqZero(66), Op::AssertEq(vec![67], vec![52], None), Op::Call(188, vec![18, 19, 31], 2, false), Op::Const(G::from_u64(0)), Op::Sub(68, 52), Op::Sub(69, 70), Op::Call(189, vec![31], 1, false), Op::UnconstrainedGInverse(73), Op::Mul(73, 74), Op::Sub(75, 52), Op::Mul(73, 76), Op::AssertEq(vec![77], vec![70], None), Op::Mul(74, 76), Op::AssertEq(vec![78], vec![70], None), Op::Sub(18, 52), Op::Sub(19, 70), Op::Mul(79, 79), Op::Const(G::from_u64(7)), Op::Mul(80, 80), Op::Mul(82, 83), Op::Sub(81, 84), Op::UnconstrainedGInverse(85), Op::Mul(85, 86), Op::Sub(87, 52), Op::Mul(85, 88), Op::AssertEq(vec![89], vec![70], None), Op::Mul(86, 88), Op::AssertEq(vec![90], vec![70], None), Op::Mul(79, 86), Op::Sub(70, 80), Op::Mul(92, 86), Op::Mul(71, 91), Op::Mul(72, 93), Op::Mul(82, 95), Op::Add(94, 96), Op::Mul(71, 93), Op::Mul(72, 91), Op::Add(98, 99), Op::Sub(18, 74), Op::Mul(101, 101), Op::Sub(102, 84), Op::UnconstrainedGInverse(103), Op::Mul(103, 104), Op::Sub(105, 52), Op::Mul(103, 106), Op::AssertEq(vec![107], vec![70], None), Op::Mul(104, 106), Op::AssertEq(vec![108], vec![70], None), Op::Mul(101, 104), Op::Mul(92, 104), Op::Mul(71, 109), Op::Mul(72, 110), Op::Mul(82, 112), Op::Add(111, 113), Op::Mul(71, 110), Op::Mul(72, 109), Op::Add(115, 116), Op::Mul(71, 71), Op::Mul(72, 72), Op::Mul(82, 119), Op::Sub(118, 120), Op::UnconstrainedGInverse(121), Op::Mul(121, 122), Op::Sub(123, 52), Op::Mul(121, 124), Op::AssertEq(vec![125], vec![70], None), Op::Mul(122, 124), Op::AssertEq(vec![126], vec![70], None), Op::Mul(71, 122), Op::Sub(70, 72), Op::Mul(128, 122), Op::Store(vec![52, 52, 52, 52]), Op::Store(vec![70, 37, 70, 130]), Op::Store(vec![70, 36, 70, 131]), Op::Store(vec![70, 11, 70, 132]), Op::Store(vec![70, 10, 70, 133]), Op::Store(vec![70, 15, 70, 134]), Op::Store(vec![70, 14, 70, 135]), Op::Store(vec![70, 13, 70, 136]), Op::Store(vec![70, 12, 70, 137]), Op::Call(191, vec![31], 1, false), Op::Call(189, vec![31], 1, false), Op::Mul(139, 140), Op::UnconstrainedGInverse(141), Op::Mul(141, 142), Op::Sub(143, 52), Op::Mul(141, 144), Op::AssertEq(vec![145], vec![70], None), Op::Mul(142, 144), Op::AssertEq(vec![146], vec![70], None), Op::Call(197, vec![70, 70, 16, 17, 23, 21, 40, 41, 47, 48, 43, 44, 138, 97, 100, 114, 117, 101, 80], 2, false), Op::Sub(36, 10), Op::Sub(70, 70), Op::Mul(149, 142), Op::Mul(150, 70), Op::Mul(82, 152), Op::Add(151, 153), Op::Mul(149, 70), Op::Mul(150, 142), Op::Add(155, 156), Op::Mul(114, 154), Op::Mul(117, 157), Op::Mul(82, 159), Op::Add(158, 160), Op::Mul(114, 157), Op::Mul(117, 154), Op::Add(162, 163), Op::Sub(37, 11), Op::Mul(165, 142), Op::Add(166, 153), Op::Mul(165, 70), Op::Add(168, 156), Op::Mul(114, 167), Op::Mul(117, 169), Op::Mul(82, 171), Op::Add(170, 172), Op::Mul(114, 169), Op::Mul(117, 167), Op::Add(174, 175), Op::Load(4, 25)], ctrl: Ctrl::MatchContinue(177, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(323, vec![44, 181], 2, false), Op::Call(323, vec![43, 181], 2, false), Op::Sub(182, 184), Op::Sub(183, 185), Op::Add(186, 161), Op::Add(187, 164), Op::Call(193, vec![147, 148, 16, 17, 188, 189], 2, false), Op::Const(G::from_u64(1)), Op::Call(323, vec![44, 192], 2, false), Op::Call(323, vec![43, 192], 2, false), Op::Sub(193, 195), Op::Sub(194, 196), Op::Add(197, 173), Op::Add(198, 176), Op::Call(193, vec![190, 191, 16, 17, 199, 200], 2, false)], ctrl: Ctrl::Yield(4, vec![201, 202]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(315, vec![25], 1, false), Op::Const(G::from_u64(1)), Op::Call(200, vec![147, 148, 16, 17, 25, 181, 182, 181, 26, 26, 183, 181, 181, 181, 181, 181, 181, 181, 161, 164, 173, 176, 12, 70, 13, 70, 14, 70, 15, 70, 21, 40, 41, 47, 48, 43, 44, 138, 97, 100, 114, 117, 101, 80], 2, false)], ctrl: Ctrl::Yield(5, vec![184, 185]) })].into_iter().collect(), None, 2, 12, 6, Box::new(Block { ops: vec![Op::Call(312, vec![8, 9], 1, false), Op::Const(G::from_u64(0)), Op::Call(312, vec![183, 184], 1, false), Op::Call(195, vec![185], 1, false), Op::Call(315, vec![186], 1, false), Op::Sub(187, 35), Op::EqZero(188), Op::Const(G::from_u64(1)), Op::AssertEq(vec![189], vec![190], None), Op::Call(188, vec![18, 19, 31], 2, false), Op::Call(203, vec![186, 191, 192, 190, 184], 2, false), Op::Mul(181, 127), Op::Const(G::from_u64(7)), Op::Mul(182, 129), Op::Mul(196, 197), Op::Add(195, 198), Op::Mul(181, 129), Op::Mul(182, 127), Op::Add(200, 201), Op::Sub(199, 193), Op::EqZero(203), Op::Sub(202, 194), Op::EqZero(205), Op::Mul(204, 206), Op::AssertEq(vec![207], vec![190], None), Op::Add(9, 190), Op::Call(206, vec![30, 1, 2, 3, 4, 5, 6, 7, 8, 208, 36, 37, 12, 13, 14, 15, 16, 17, 18, 19], 1, false)], ctrl: Ctrl::Return(6, vec![209]) })) })) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 20, selectors: 7, auxiliaries: 152, lookups: 50 }, entry: false, constrained: true } } #[inline(never)] @@ -1103,17 +1119,17 @@ fn expected_function_212() -> Function { #[inline(never)] fn expected_function_213() -> Function { - Function { body: Block { ops: vec![Op::Call(312, vec![0], 1, false), Op::Call(214, vec![0, 1], 1, false), Op::Call(212, vec![2, 3], 1, false)], ctrl: Ctrl::Return(0, vec![4]) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 4, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(314, vec![0], 1, false), Op::Call(214, vec![0, 1], 1, false), Op::Call(212, vec![2, 3], 1, false)], ctrl: Ctrl::Return(0, vec![4]) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 4, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_214() -> Function { - Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![1]) }), (G::from_u64(0), Block { ops: vec![Op::Call(317, vec![3], 1, false), Op::Call(214, vec![4, 1], 1, false), Op::Call(186, vec![3, 6], 1, false), Op::Call(212, vec![5, 7], 1, false)], ctrl: Ctrl::Return(1, vec![8]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 8, lookups: 6 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![1]) }), (G::from_u64(0), Block { ops: vec![Op::Call(319, vec![3], 1, false), Op::Call(214, vec![4, 1], 1, false), Op::Call(186, vec![3, 6], 1, false), Op::Call(212, vec![5, 7], 1, false)], ctrl: Ctrl::Return(1, vec![8]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 8, lookups: 6 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_215() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(0, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(312, vec![2], 1, false), Op::Call(213, vec![3, 4], 1, false), Op::Call(177, vec![2, 6], 1, false), Op::Call(212, vec![5, 7], 1, false), Op::Call(176, vec![1, 8], 1, false), Op::Call(177, vec![0, 9], 1, false)], ctrl: Ctrl::Return(0, vec![10]) }))) }, layout: FunctionLayout { input_size: 5, selectors: 1, auxiliaries: 7, lookups: 7 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(0, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(314, vec![2], 1, false), Op::Call(213, vec![3, 4], 1, false), Op::Call(177, vec![2, 6], 1, false), Op::Call(212, vec![5, 7], 1, false), Op::Call(176, vec![1, 8], 1, false), Op::Call(177, vec![0, 9], 1, false)], ctrl: Ctrl::Return(0, vec![10]) }))) }, layout: FunctionLayout { input_size: 5, selectors: 1, auxiliaries: 7, lookups: 7 }, entry: false, constrained: true } } #[inline(never)] @@ -1123,22 +1139,22 @@ fn expected_function_216() -> Function { #[inline(never)] fn expected_function_217() -> Function { - Function { body: Block { ops: vec![Op::Call(318, vec![0], 1, false), Op::Call(218, vec![0, 1], 1, false), Op::Call(212, vec![2, 3], 1, false)], ctrl: Ctrl::Return(0, vec![4]) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 4, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(320, vec![0], 1, false), Op::Call(218, vec![0, 1], 1, false), Op::Call(212, vec![2, 3], 1, false)], ctrl: Ctrl::Return(0, vec![4]) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 4, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_218() -> Function { - Function { body: Block { ops: vec![Op::Load(11, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![1]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(3, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(317, vec![3], 1, false), Op::Call(218, vec![12, 1], 1, false), Op::Call(174, vec![4, 5, 6, 7, 8, 9, 10, 11, 14], 1, false), Op::Call(186, vec![3, 15], 1, false), Op::Call(212, vec![13, 16], 1, false)], ctrl: Ctrl::Return(1, vec![17]) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 17, lookups: 7 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(11, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![1]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(3, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(319, vec![3], 1, false), Op::Call(218, vec![12, 1], 1, false), Op::Call(174, vec![4, 5, 6, 7, 8, 9, 10, 11, 14], 1, false), Op::Call(186, vec![3, 15], 1, false), Op::Call(212, vec![13, 16], 1, false)], ctrl: Ctrl::Return(1, vec![17]) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 17, lookups: 7 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_219() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![4, 4, 4]), Op::Call(217, vec![3, 5], 1, false), Op::Call(216, vec![2, 6], 1, false), Op::Call(315, vec![2], 1, false), Op::Call(212, vec![8, 7], 1, false), Op::Call(176, vec![1, 9], 1, false), Op::Call(186, vec![0, 10], 1, false), Op::Const(G::from_u64(0)), Op::Const(G::from_u64(109)), Op::Const(G::from_u64(117)), Op::Const(G::from_u64(108)), Op::Const(G::from_u64(116)), Op::Const(G::from_u64(105)), Op::Const(G::from_u64(45)), Op::Const(G::from_u64(115)), Op::Const(G::from_u64(97)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(107)), Op::Const(G::from_u64(47)), Op::Const(G::from_u64(118)), Op::Const(G::from_u64(48)), Op::Store(vec![12, 25, 11]), Op::Store(vec![12, 24, 26]), Op::Store(vec![12, 23, 27]), Op::Store(vec![12, 22, 28]), Op::Store(vec![12, 21, 29]), Op::Store(vec![12, 20, 30]), Op::Store(vec![12, 16, 31]), Op::Store(vec![12, 19, 32]), Op::Store(vec![12, 18, 33]), Op::Store(vec![12, 17, 34]), Op::Store(vec![12, 16, 35]), Op::Store(vec![12, 15, 36]), Op::Store(vec![12, 14, 37]), Op::Store(vec![12, 13, 38]), Op::Call(180, vec![39, 5], 18, false), Op::Call(174, vec![48, 49, 50, 51, 52, 53, 54, 55, 5], 1, false), Op::Call(174, vec![40, 41, 42, 43, 44, 45, 46, 47, 58], 1, false), Op::Call(314, vec![56, 59], 1, false), Op::Call(180, vec![60, 5], 18, false), Op::Call(174, vec![69, 70, 71, 72, 73, 74, 75, 76, 5], 1, false), Op::Call(174, vec![61, 62, 63, 64, 65, 66, 67, 68, 79], 1, false), Op::Call(314, vec![77, 80], 1, false), Op::Const(G::from_u64(256)), Op::Mul(82, 41), Op::Add(40, 83), Op::Const(G::from_u64(65536)), Op::Mul(85, 42), Op::Add(84, 86), Op::Const(G::from_u64(16777216)), Op::Mul(88, 43), Op::Add(87, 89), Op::Const(G::from_u64(4294967296)), Op::Mul(91, 44), Op::Add(90, 92), Op::Const(G::from_u64(1099511627776)), Op::Mul(94, 45), Op::Add(93, 95), Op::Const(G::from_u64(281474976710656)), Op::Mul(97, 46), Op::Add(96, 98), Op::Const(G::from_u64(72057594037927936)), Op::Mul(100, 47), Op::Add(99, 101), Op::Mul(82, 49), Op::Add(48, 103), Op::Mul(85, 50), Op::Add(104, 105), Op::Mul(88, 51), Op::Add(106, 107), Op::Mul(91, 52), Op::Add(108, 109), Op::Mul(94, 53), Op::Add(110, 111), Op::Mul(97, 54), Op::Add(112, 113), Op::Mul(100, 55), Op::Add(114, 115), Op::Mul(82, 62), Op::Add(61, 117), Op::Mul(85, 63), Op::Add(118, 119), Op::Mul(88, 64), Op::Add(120, 121), Op::Mul(91, 65), Op::Add(122, 123), Op::Mul(94, 66), Op::Add(124, 125), Op::Mul(97, 67), Op::Add(126, 127), Op::Mul(100, 68), Op::Add(128, 129), Op::Mul(82, 70), Op::Add(69, 131), Op::Mul(85, 71), Op::Add(132, 133), Op::Mul(88, 72), Op::Add(134, 135), Op::Mul(91, 73), Op::Add(136, 137), Op::Mul(94, 74), Op::Add(138, 139), Op::Mul(97, 75), Op::Add(140, 141), Op::Mul(100, 76), Op::Add(142, 143)], ctrl: Ctrl::Return(0, vec![102, 116, 130, 144, 81]) }, layout: FunctionLayout { input_size: 4, selectors: 1, auxiliaries: 64, lookups: 30 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![4, 4, 4]), Op::Call(217, vec![3, 5], 1, false), Op::Call(216, vec![2, 6], 1, false), Op::Call(317, vec![2], 1, false), Op::Call(212, vec![8, 7], 1, false), Op::Call(176, vec![1, 9], 1, false), Op::Call(186, vec![0, 10], 1, false), Op::Const(G::from_u64(0)), Op::Const(G::from_u64(109)), Op::Const(G::from_u64(117)), Op::Const(G::from_u64(108)), Op::Const(G::from_u64(116)), Op::Const(G::from_u64(105)), Op::Const(G::from_u64(45)), Op::Const(G::from_u64(115)), Op::Const(G::from_u64(97)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(107)), Op::Const(G::from_u64(47)), Op::Const(G::from_u64(118)), Op::Const(G::from_u64(48)), Op::Store(vec![12, 25, 11]), Op::Store(vec![12, 24, 26]), Op::Store(vec![12, 23, 27]), Op::Store(vec![12, 22, 28]), Op::Store(vec![12, 21, 29]), Op::Store(vec![12, 20, 30]), Op::Store(vec![12, 16, 31]), Op::Store(vec![12, 19, 32]), Op::Store(vec![12, 18, 33]), Op::Store(vec![12, 17, 34]), Op::Store(vec![12, 16, 35]), Op::Store(vec![12, 15, 36]), Op::Store(vec![12, 14, 37]), Op::Store(vec![12, 13, 38]), Op::Call(180, vec![39, 5], 18, false), Op::Call(174, vec![48, 49, 50, 51, 52, 53, 54, 55, 5], 1, false), Op::Call(174, vec![40, 41, 42, 43, 44, 45, 46, 47, 58], 1, false), Op::Call(316, vec![56, 59], 1, false), Op::Call(180, vec![60, 5], 18, false), Op::Call(174, vec![69, 70, 71, 72, 73, 74, 75, 76, 5], 1, false), Op::Call(174, vec![61, 62, 63, 64, 65, 66, 67, 68, 79], 1, false), Op::Call(316, vec![77, 80], 1, false), Op::Const(G::from_u64(256)), Op::Mul(82, 41), Op::Add(40, 83), Op::Const(G::from_u64(65536)), Op::Mul(85, 42), Op::Add(84, 86), Op::Const(G::from_u64(16777216)), Op::Mul(88, 43), Op::Add(87, 89), Op::Const(G::from_u64(4294967296)), Op::Mul(91, 44), Op::Add(90, 92), Op::Const(G::from_u64(1099511627776)), Op::Mul(94, 45), Op::Add(93, 95), Op::Const(G::from_u64(281474976710656)), Op::Mul(97, 46), Op::Add(96, 98), Op::Const(G::from_u64(72057594037927936)), Op::Mul(100, 47), Op::Add(99, 101), Op::Mul(82, 49), Op::Add(48, 103), Op::Mul(85, 50), Op::Add(104, 105), Op::Mul(88, 51), Op::Add(106, 107), Op::Mul(91, 52), Op::Add(108, 109), Op::Mul(94, 53), Op::Add(110, 111), Op::Mul(97, 54), Op::Add(112, 113), Op::Mul(100, 55), Op::Add(114, 115), Op::Mul(82, 62), Op::Add(61, 117), Op::Mul(85, 63), Op::Add(118, 119), Op::Mul(88, 64), Op::Add(120, 121), Op::Mul(91, 65), Op::Add(122, 123), Op::Mul(94, 66), Op::Add(124, 125), Op::Mul(97, 67), Op::Add(126, 127), Op::Mul(100, 68), Op::Add(128, 129), Op::Mul(82, 70), Op::Add(69, 131), Op::Mul(85, 71), Op::Add(132, 133), Op::Mul(88, 72), Op::Add(134, 135), Op::Mul(91, 73), Op::Add(136, 137), Op::Mul(94, 74), Op::Add(138, 139), Op::Mul(97, 75), Op::Add(140, 141), Op::Mul(100, 76), Op::Add(142, 143)], ctrl: Ctrl::Return(0, vec![102, 116, 130, 144, 81]) }, layout: FunctionLayout { input_size: 4, selectors: 1, auxiliaries: 64, lookups: 30 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_220() -> Function { - Function { body: Block { ops: vec![Op::UnconstrainedGToBytes(1), Op::U8RangeCheck(5, 6), Op::U8RangeCheck(7, 8), Op::U8RangeCheck(9, 10), Op::U8RangeCheck(11, 12), Op::Const(G::from_u64(256)), Op::Mul(13, 6), Op::Add(5, 14), Op::Const(G::from_u64(65536)), Op::Mul(16, 7), Op::Add(15, 17), Op::Const(G::from_u64(16777216)), Op::Mul(19, 8), Op::Add(18, 20), Op::Const(G::from_u64(4294967296)), Op::Mul(22, 9), Op::Add(21, 23), Op::Const(G::from_u64(1099511627776)), Op::Mul(25, 10), Op::Add(24, 26), Op::Const(G::from_u64(281474976710656)), Op::Mul(28, 11), Op::Add(27, 29), Op::Const(G::from_u64(72057594037927936)), Op::Mul(31, 12), Op::Add(30, 32), Op::AssertEq(vec![33], vec![1], None), Op::Add(9, 10), Op::Add(34, 11), Op::Add(35, 12), Op::Const(G::from_u64(1020)), Op::Sub(36, 37), Op::EqZero(38), Op::Add(5, 6), Op::Add(40, 7), Op::Add(41, 8), Op::EqZero(42), Op::Const(G::from_u64(1)), Op::Sub(44, 43), Op::Mul(39, 45), Op::Sub(44, 46), Op::AssertEq(vec![47], vec![44], None), Op::Call(183, vec![0, 5, 6, 7, 8, 9, 10, 11, 12], 1, false), Op::Call(184, vec![48, 2], 1, false), Op::Store(vec![44, 44, 44]), Op::Call(137, vec![4, 50], 1, false), Op::Call(314, vec![49, 51], 1, false), Op::Call(180, vec![52, 50], 18, false), Op::Call(184, vec![69, 3], 1, false), Op::Call(180, vec![71, 50], 18, false), Op::Mul(13, 54), Op::Add(53, 90), Op::Mul(16, 55), Op::Add(91, 92), Op::Mul(19, 56), Op::Add(93, 94), Op::Mul(22, 57), Op::Add(95, 96), Op::Mul(25, 58), Op::Add(97, 98), Op::Mul(28, 59), Op::Add(99, 100), Op::Mul(31, 60), Op::Add(101, 102), Op::Mul(13, 62), Op::Add(61, 104), Op::Mul(16, 63), Op::Add(105, 106), Op::Mul(19, 64), Op::Add(107, 108), Op::Mul(22, 65), Op::Add(109, 110), Op::Mul(25, 66), Op::Add(111, 112), Op::Mul(28, 67), Op::Add(113, 114), Op::Mul(31, 68), Op::Add(115, 116), Op::Mul(13, 73), Op::Add(72, 118), Op::Mul(16, 74), Op::Add(119, 120), Op::Mul(19, 75), Op::Add(121, 122), Op::Mul(22, 76), Op::Add(123, 124), Op::Mul(25, 77), Op::Add(125, 126), Op::Mul(28, 78), Op::Add(127, 128), Op::Mul(31, 79), Op::Add(129, 130), Op::Mul(13, 81), Op::Add(80, 132), Op::Mul(16, 82), Op::Add(133, 134), Op::Mul(19, 83), Op::Add(135, 136), Op::Mul(22, 84), Op::Add(137, 138), Op::Mul(25, 85), Op::Add(139, 140), Op::Mul(28, 86), Op::Add(141, 142), Op::Mul(31, 87), Op::Add(143, 144)], ctrl: Ctrl::Return(0, vec![103, 117, 131, 145, 88]) }, layout: FunctionLayout { input_size: 5, selectors: 1, auxiliaries: 56, lookups: 13 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::UnconstrainedGToBytes(1), Op::U8RangeCheck(5, 6), Op::U8RangeCheck(7, 8), Op::U8RangeCheck(9, 10), Op::U8RangeCheck(11, 12), Op::Const(G::from_u64(256)), Op::Mul(13, 6), Op::Add(5, 14), Op::Const(G::from_u64(65536)), Op::Mul(16, 7), Op::Add(15, 17), Op::Const(G::from_u64(16777216)), Op::Mul(19, 8), Op::Add(18, 20), Op::Const(G::from_u64(4294967296)), Op::Mul(22, 9), Op::Add(21, 23), Op::Const(G::from_u64(1099511627776)), Op::Mul(25, 10), Op::Add(24, 26), Op::Const(G::from_u64(281474976710656)), Op::Mul(28, 11), Op::Add(27, 29), Op::Const(G::from_u64(72057594037927936)), Op::Mul(31, 12), Op::Add(30, 32), Op::AssertEq(vec![33], vec![1], None), Op::Add(9, 10), Op::Add(34, 11), Op::Add(35, 12), Op::Const(G::from_u64(1020)), Op::Sub(36, 37), Op::EqZero(38), Op::Add(5, 6), Op::Add(40, 7), Op::Add(41, 8), Op::EqZero(42), Op::Const(G::from_u64(1)), Op::Sub(44, 43), Op::Mul(39, 45), Op::Sub(44, 46), Op::AssertEq(vec![47], vec![44], None), Op::Call(183, vec![0, 5, 6, 7, 8, 9, 10, 11, 12], 1, false), Op::Call(184, vec![48, 2], 1, false), Op::Store(vec![44, 44, 44]), Op::Call(137, vec![4, 50], 1, false), Op::Call(316, vec![49, 51], 1, false), Op::Call(180, vec![52, 50], 18, false), Op::Call(184, vec![69, 3], 1, false), Op::Call(180, vec![71, 50], 18, false), Op::Mul(13, 54), Op::Add(53, 90), Op::Mul(16, 55), Op::Add(91, 92), Op::Mul(19, 56), Op::Add(93, 94), Op::Mul(22, 57), Op::Add(95, 96), Op::Mul(25, 58), Op::Add(97, 98), Op::Mul(28, 59), Op::Add(99, 100), Op::Mul(31, 60), Op::Add(101, 102), Op::Mul(13, 62), Op::Add(61, 104), Op::Mul(16, 63), Op::Add(105, 106), Op::Mul(19, 64), Op::Add(107, 108), Op::Mul(22, 65), Op::Add(109, 110), Op::Mul(25, 66), Op::Add(111, 112), Op::Mul(28, 67), Op::Add(113, 114), Op::Mul(31, 68), Op::Add(115, 116), Op::Mul(13, 73), Op::Add(72, 118), Op::Mul(16, 74), Op::Add(119, 120), Op::Mul(19, 75), Op::Add(121, 122), Op::Mul(22, 76), Op::Add(123, 124), Op::Mul(25, 77), Op::Add(125, 126), Op::Mul(28, 78), Op::Add(127, 128), Op::Mul(31, 79), Op::Add(129, 130), Op::Mul(13, 81), Op::Add(80, 132), Op::Mul(16, 82), Op::Add(133, 134), Op::Mul(19, 83), Op::Add(135, 136), Op::Mul(22, 84), Op::Add(137, 138), Op::Mul(25, 85), Op::Add(139, 140), Op::Mul(28, 86), Op::Add(141, 142), Op::Mul(31, 87), Op::Add(143, 144)], ctrl: Ctrl::Return(0, vec![103, 117, 131, 145, 88]) }, layout: FunctionLayout { input_size: 5, selectors: 1, auxiliaries: 56, lookups: 13 }, entry: false, constrained: true } } #[inline(never)] @@ -1213,7 +1229,7 @@ fn expected_function_234() -> Function { #[inline(never)] fn expected_function_235() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(0, [].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::Match(0, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0)), Op::AssertEq(vec![1], vec![46], None), Op::AssertEq(vec![2], vec![46], None), Op::Const(G::from_u64(1)), Op::AssertEq(vec![3], vec![47], None)], ctrl: Ctrl::Match(12, [].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::Match(17, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(313, vec![21], 1, false), Op::EqZero(48), Op::Const(G::from_u64(0)), Op::AssertEq(vec![49], vec![50], None), Op::Call(312, vec![39], 1, false), Op::AssertEq(vec![51], vec![48], None), Op::Call(312, vec![40], 1, false), Op::AssertEq(vec![52], vec![48], None), Op::Call(312, vec![36], 1, false), Op::AssertEq(vec![53], vec![48], None), Op::Call(209, vec![17], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![54], vec![55], None), Op::Call(312, vec![17], 1, false), Op::Call(318, vec![8], 1, false), Op::Sub(56, 57), Op::EqZero(58), Op::AssertEq(vec![59], vec![55], None), Op::Call(210, vec![8, 17], 1, false), Op::Call(211, vec![11, 17], 1, false), Op::Call(318, vec![60], 1, false), Op::Call(313, vec![21], 1, false), Op::Sub(62, 63), Op::EqZero(64), Op::AssertEq(vec![65], vec![55], None)], ctrl: Ctrl::Match(18, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(221, vec![12, 17], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![66], vec![67], None), Op::Call(224, vec![13, 18], 1, false), Op::AssertEq(vec![68], vec![67], None), Op::Call(221, vec![14, 22], 1, false), Op::AssertEq(vec![69], vec![67], None), Op::Call(228, vec![22], 1, false), Op::AssertEq(vec![70], vec![67], None), Op::Call(229, vec![17], 1, false), Op::Call(312, vec![22], 1, false), Op::AssertEq(vec![71], vec![72], None)], ctrl: Ctrl::MatchContinue(9, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![73, 73, 73])], ctrl: Ctrl::Yield(0, vec![74]) }), (G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Yield(1, vec![10]) })].into_iter().collect(), None, 1, 1, 1, Box::new(Block { ops: vec![Op::Call(220, vec![41, 16, 19, 20, 21], 5, false), Op::Const(G::from_u64(0)), Op::Call(208, vec![79, 79, 15, 42, 43, 44, 45], 2, false), Op::Call(206, vec![60, 61, 22, 21, 39, 40, 37, 38, 36, 79, 80, 81, 42, 43, 44, 45, 74, 75, 76, 77], 1, false), Op::Call(318, vec![60], 1, false)], ctrl: Ctrl::Match(23, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(312, vec![23], 1, false), Op::Call(317, vec![24], 1, false), Op::Sub(85, 84), Op::EqZero(86), Op::Const(G::from_u64(1)), Op::AssertEq(vec![87], vec![88], None), Op::Call(313, vec![25], 1, false), Op::Const(G::from_u64(3)), Op::Call(148, vec![37, 38], 1, false), Op::Add(90, 91), Op::AssertEq(vec![89], vec![92], None), Op::Call(323, vec![26], 1, false), Op::Sub(93, 84), Op::EqZero(94), Op::AssertEq(vec![95], vec![88], None), Op::Call(313, vec![27], 1, false), Op::AssertEq(vec![96], vec![88], None), Op::Store(vec![88, 88, 88])], ctrl: Ctrl::MatchContinue(37, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Yield(2, vec![97]) }), (G::from_u64(1), Block { ops: vec![Op::Call(139, vec![38, 97], 1, false)], ctrl: Ctrl::Yield(3, vec![98]) })].into_iter().collect(), None, 1, 1, 1, Box::new(Block { ops: vec![Op::Call(139, vec![36, 98], 1, false), Op::Call(139, vec![40, 99], 1, false), Op::Call(139, vec![39, 100], 1, false), Op::Call(314, vec![78, 101], 1, false), Op::Const(G::from_u64(1)), Op::Store(vec![103, 103, 103]), Op::Call(180, vec![102, 104], 18, false), Op::Const(G::from_u64(256)), Op::Mul(123, 106), Op::Add(105, 124), Op::Const(G::from_u64(65536)), Op::Mul(126, 107), Op::Add(125, 127), Op::Const(G::from_u64(16777216)), Op::Mul(129, 108), Op::Add(128, 130), Op::Const(G::from_u64(4294967296)), Op::Mul(132, 109), Op::Add(131, 133), Op::Const(G::from_u64(1099511627776)), Op::Mul(135, 110), Op::Add(134, 136), Op::Const(G::from_u64(281474976710656)), Op::Mul(138, 111), Op::Add(137, 139), Op::Const(G::from_u64(72057594037927936)), Op::Mul(141, 112), Op::Add(140, 142), Op::Mul(123, 114), Op::Add(113, 144), Op::Mul(126, 115), Op::Add(145, 146), Op::Mul(129, 116), Op::Add(147, 148), Op::Mul(132, 117), Op::Add(149, 150), Op::Mul(135, 118), Op::Add(151, 152), Op::Mul(138, 119), Op::Add(153, 154), Op::Mul(141, 120), Op::Add(155, 156), Op::Call(143, vec![121, 23, 24, 5], 2, false), Op::Call(137, vec![27, 104], 1, false), Op::Call(314, vec![159, 160], 1, false), Op::Call(141, vec![161, 23], 1, false), Op::Call(142, vec![162, 28, 29, 30, 31, 32, 33, 34, 35, 6], 2, false), Op::Add(84, 0), Op::Const(G::from_u64(33)), Op::U32LessThan(165, 166), Op::AssertEq(vec![167], vec![103], None), Op::Call(164, vec![163, 164, 4, 165], 1, false), Op::Call(168, vec![25, 168, 18, 19, 20, 73, 37, 38, 61, 22, 83, 0, 165], 1, false), Op::AssertEq(vec![169], vec![103], None), Op::Const(G::from_u64(0)), Op::Call(167, vec![25, 26, 170, 4], 1, false), Op::Call(163, vec![163, 164, 171, 143, 157, 39, 40, 36, 37, 38, 18, 19, 20, 73, 61, 22, 76, 77, 83, 0, 165, 158, 23, 27, 84], 1, false), Op::Call(171, vec![26, 23, 172, 168, 170, 165], 1, false), Op::AssertEq(vec![173], vec![103], None), Op::Call(225, vec![21], 2, false)], ctrl: Ctrl::Return(4, vec![174, 175]) })) }))) })) }))) }))) }))) }))) }))) }, layout: FunctionLayout { input_size: 46, selectors: 5, auxiliaries: 91, lookups: 54 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(0, [].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::Match(0, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0)), Op::AssertEq(vec![1], vec![46], None), Op::AssertEq(vec![2], vec![46], None), Op::Const(G::from_u64(1)), Op::AssertEq(vec![3], vec![47], None)], ctrl: Ctrl::Match(12, [].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::Match(17, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(315, vec![21], 1, false), Op::EqZero(48), Op::Const(G::from_u64(0)), Op::AssertEq(vec![49], vec![50], None), Op::Call(314, vec![39], 1, false), Op::AssertEq(vec![51], vec![48], None), Op::Call(314, vec![40], 1, false), Op::AssertEq(vec![52], vec![48], None), Op::Call(314, vec![36], 1, false), Op::AssertEq(vec![53], vec![48], None), Op::Call(209, vec![17], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![54], vec![55], None), Op::Call(314, vec![17], 1, false), Op::Call(320, vec![8], 1, false), Op::Sub(56, 57), Op::EqZero(58), Op::AssertEq(vec![59], vec![55], None), Op::Call(210, vec![8, 17], 1, false), Op::Call(211, vec![11, 17], 1, false), Op::Call(320, vec![60], 1, false), Op::Call(315, vec![21], 1, false), Op::Sub(62, 63), Op::EqZero(64), Op::AssertEq(vec![65], vec![55], None)], ctrl: Ctrl::Match(18, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(221, vec![12, 17], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![66], vec![67], None), Op::Call(224, vec![13, 18], 1, false), Op::AssertEq(vec![68], vec![67], None), Op::Call(221, vec![14, 22], 1, false), Op::AssertEq(vec![69], vec![67], None), Op::Call(228, vec![22], 1, false), Op::AssertEq(vec![70], vec![67], None), Op::Call(229, vec![17], 1, false), Op::Call(314, vec![22], 1, false), Op::AssertEq(vec![71], vec![72], None)], ctrl: Ctrl::MatchContinue(9, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![73, 73, 73])], ctrl: Ctrl::Yield(0, vec![74]) }), (G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Yield(1, vec![10]) })].into_iter().collect(), None, 1, 1, 1, Box::new(Block { ops: vec![Op::Call(220, vec![41, 16, 19, 20, 21], 5, false), Op::Const(G::from_u64(0)), Op::Call(208, vec![79, 79, 15, 42, 43, 44, 45], 2, false), Op::Call(206, vec![60, 61, 22, 21, 39, 40, 37, 38, 36, 79, 80, 81, 42, 43, 44, 45, 74, 75, 76, 77], 1, false), Op::Call(320, vec![60], 1, false)], ctrl: Ctrl::Match(23, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(314, vec![23], 1, false), Op::Call(319, vec![24], 1, false), Op::Sub(85, 84), Op::EqZero(86), Op::Const(G::from_u64(1)), Op::AssertEq(vec![87], vec![88], None), Op::Call(315, vec![25], 1, false), Op::Const(G::from_u64(3)), Op::Call(148, vec![37, 38], 1, false), Op::Add(90, 91), Op::AssertEq(vec![89], vec![92], None), Op::Call(325, vec![26], 1, false), Op::Sub(93, 84), Op::EqZero(94), Op::AssertEq(vec![95], vec![88], None), Op::Call(315, vec![27], 1, false), Op::AssertEq(vec![96], vec![88], None), Op::Store(vec![88, 88, 88])], ctrl: Ctrl::MatchContinue(37, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Yield(2, vec![97]) }), (G::from_u64(1), Block { ops: vec![Op::Call(139, vec![38, 97], 1, false)], ctrl: Ctrl::Yield(3, vec![98]) })].into_iter().collect(), None, 1, 1, 1, Box::new(Block { ops: vec![Op::Call(139, vec![36, 98], 1, false), Op::Call(139, vec![40, 99], 1, false), Op::Call(139, vec![39, 100], 1, false), Op::Call(316, vec![78, 101], 1, false), Op::Const(G::from_u64(1)), Op::Store(vec![103, 103, 103]), Op::Call(180, vec![102, 104], 18, false), Op::Const(G::from_u64(256)), Op::Mul(123, 106), Op::Add(105, 124), Op::Const(G::from_u64(65536)), Op::Mul(126, 107), Op::Add(125, 127), Op::Const(G::from_u64(16777216)), Op::Mul(129, 108), Op::Add(128, 130), Op::Const(G::from_u64(4294967296)), Op::Mul(132, 109), Op::Add(131, 133), Op::Const(G::from_u64(1099511627776)), Op::Mul(135, 110), Op::Add(134, 136), Op::Const(G::from_u64(281474976710656)), Op::Mul(138, 111), Op::Add(137, 139), Op::Const(G::from_u64(72057594037927936)), Op::Mul(141, 112), Op::Add(140, 142), Op::Mul(123, 114), Op::Add(113, 144), Op::Mul(126, 115), Op::Add(145, 146), Op::Mul(129, 116), Op::Add(147, 148), Op::Mul(132, 117), Op::Add(149, 150), Op::Mul(135, 118), Op::Add(151, 152), Op::Mul(138, 119), Op::Add(153, 154), Op::Mul(141, 120), Op::Add(155, 156), Op::Call(143, vec![121, 23, 24, 5], 2, false), Op::Call(137, vec![27, 104], 1, false), Op::Call(316, vec![159, 160], 1, false), Op::Call(141, vec![161, 23], 1, false), Op::Call(142, vec![162, 28, 29, 30, 31, 32, 33, 34, 35, 6], 2, false), Op::Add(84, 0), Op::Const(G::from_u64(33)), Op::U32LessThan(165, 166), Op::AssertEq(vec![167], vec![103], None), Op::Call(164, vec![163, 164, 4, 165], 1, false), Op::Call(168, vec![25, 168, 18, 19, 20, 73, 37, 38, 61, 22, 83, 0, 165], 1, false), Op::AssertEq(vec![169], vec![103], None), Op::Const(G::from_u64(0)), Op::Call(167, vec![25, 26, 170, 4], 1, false), Op::Call(163, vec![163, 164, 171, 143, 157, 39, 40, 36, 37, 38, 18, 19, 20, 73, 61, 22, 76, 77, 83, 0, 165, 158, 23, 27, 84], 1, false), Op::Call(171, vec![26, 23, 172, 168, 170, 165], 1, false), Op::AssertEq(vec![173], vec![103], None), Op::Call(225, vec![21], 2, false)], ctrl: Ctrl::Return(4, vec![174, 175]) })) }))) })) }))) }))) }))) }))) }))) }, layout: FunctionLayout { input_size: 46, selectors: 5, auxiliaries: 91, lookups: 54 }, entry: false, constrained: true } } #[inline(never)] @@ -1223,12 +1239,12 @@ fn expected_function_236() -> Function { #[inline(never)] fn expected_function_237() -> Function { - Function { body: Block { ops: vec![Op::Load(6, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![7, 7, 7])], ctrl: Ctrl::Return(0, vec![8]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(2, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(237, vec![6], 1, false), Op::Call(314, vec![5, 7], 1, false)], ctrl: Ctrl::Return(1, vec![8]) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 9, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(6, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![7, 7, 7])], ctrl: Ctrl::Return(0, vec![8]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(2, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(237, vec![6], 1, false), Op::Call(316, vec![5, 7], 1, false)], ctrl: Ctrl::Return(1, vec![8]) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 9, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_238() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(12, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(315, vec![12], 1, false), Op::Call(230, vec![15], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![16], vec![17], None), Op::Call(311, vec![14], 1, false), Op::Call(315, vec![12], 1, false), Op::AssertEq(vec![18], vec![19], None), Op::Const(G::from_u64(0)), Op::Call(234, vec![13, 20, 20], 1, false), Op::AssertEq(vec![21], vec![17], None)], ctrl: Ctrl::Match(0, [].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::MatchContinue(9, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![22, 22, 22])], ctrl: Ctrl::Yield(0, vec![23]) }), (G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Yield(1, vec![10]) })].into_iter().collect(), None, 1, 1, 1, Box::new(Block { ops: vec![Op::Call(219, vec![7, 22, 12, 13], 5, false), Op::Const(G::from_u64(0)), Op::Call(226, vec![28, 28, 13, 23, 24, 25, 26], 2, false), Op::Call(236, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 28, 27, 23, 24, 25, 26, 29, 30], 2, false), Op::EqZero(31), Op::EqZero(32), Op::Mul(33, 34), Op::Const(G::from_u64(1)), Op::AssertEq(vec![35], vec![36], None), Op::Call(237, vec![12], 1, false)], ctrl: Ctrl::Return(2, vec![37]) })) }))) }))) }, layout: FunctionLayout { input_size: 15, selectors: 3, auxiliaries: 23, lookups: 11 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(12, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(317, vec![12], 1, false), Op::Call(230, vec![15], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![16], vec![17], None), Op::Call(313, vec![14], 1, false), Op::Call(317, vec![12], 1, false), Op::AssertEq(vec![18], vec![19], None), Op::Const(G::from_u64(0)), Op::Call(234, vec![13, 20, 20], 1, false), Op::AssertEq(vec![21], vec![17], None)], ctrl: Ctrl::Match(0, [].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::MatchContinue(9, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![22, 22, 22])], ctrl: Ctrl::Yield(0, vec![23]) }), (G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Yield(1, vec![10]) })].into_iter().collect(), None, 1, 1, 1, Box::new(Block { ops: vec![Op::Call(219, vec![7, 22, 12, 13], 5, false), Op::Const(G::from_u64(0)), Op::Call(226, vec![28, 28, 13, 23, 24, 25, 26], 2, false), Op::Call(236, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 28, 27, 23, 24, 25, 26, 29, 30], 2, false), Op::EqZero(31), Op::EqZero(32), Op::Mul(33, 34), Op::Const(G::from_u64(1)), Op::AssertEq(vec![35], vec![36], None), Op::Call(237, vec![12], 1, false)], ctrl: Ctrl::Return(2, vec![37]) })) }))) }))) }, layout: FunctionLayout { input_size: 15, selectors: 3, auxiliaries: 23, lookups: 11 }, entry: false, constrained: true } } #[inline(never)] @@ -1298,7 +1314,7 @@ fn expected_function_251() -> Function { #[inline(never)] fn expected_function_252() -> Function { - Function { body: Block { ops: vec![Op::Call(90, vec![0], 2, false)], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![Op::Call(244, vec![2], 2, false), Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::Store(vec![6, 6, 6]), Op::Store(vec![5, 3, 7])], ctrl: Ctrl::Return(0, vec![8, 4]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![3, 3, 3])], ctrl: Ctrl::Return(1, vec![4, 2]) }), (G::from_u64(2), Block { ops: vec![Op::Call(252, vec![2], 2, false), Op::Call(252, vec![4], 2, false), Op::Call(314, vec![3, 5], 1, false)], ctrl: Ctrl::Return(2, vec![7, 6]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 3, auxiliaries: 8, lookups: 5 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(90, vec![0], 2, false)], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![Op::Call(244, vec![2], 2, false), Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::Store(vec![6, 6, 6]), Op::Store(vec![5, 3, 7])], ctrl: Ctrl::Return(0, vec![8, 4]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![3, 3, 3])], ctrl: Ctrl::Return(1, vec![4, 2]) }), (G::from_u64(2), Block { ops: vec![Op::Call(252, vec![2], 2, false), Op::Call(252, vec![4], 2, false), Op::Call(316, vec![3, 5], 1, false)], ctrl: Ctrl::Return(2, vec![7, 6]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 3, auxiliaries: 8, lookups: 5 }, entry: false, constrained: true } } #[inline(never)] @@ -1323,7 +1339,7 @@ fn expected_function_256() -> Function { #[inline(never)] fn expected_function_257() -> Function { - Function { body: Block { ops: vec![Op::Load(32, 0), Op::Const(G::from_u64(5)), Op::IOGetInfo(33, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]), Op::Call(3, vec![33, 34, 35], 1, false), Op::Call(90, vec![36], 2, false), Op::Const(G::from_u64(226)), Op::AssertEq(vec![37], vec![39], Some("aggr: tree has the wrong Ixon tag".into())), Op::Call(252, vec![38], 2, false), Op::Load(3, 41), Op::Const(G::from_u64(1)), Op::AssertEq(vec![42, 43, 44], vec![45, 45, 45], Some("aggr: trailing bytes after AssumptionTree".into())), Op::Call(320, vec![40], 1, false), Op::Const(G::from_u64(0)), Op::AssertEq(vec![46], vec![47], Some("aggr: a present tree must contain at least one leaf".into())), Op::Call(251, vec![40], 0, false), Op::Call(256, vec![40], 1, false), Op::Call(2, vec![48, 0], 1, false), Op::AssertEq(vec![49], vec![45], Some("aggr: tree leaves do not reproduce the canonical root".into()))], ctrl: Ctrl::Return(0, vec![40]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 46, lookups: 10 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(32, 0), Op::Const(G::from_u64(5)), Op::IOGetInfo(33, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]), Op::Call(3, vec![33, 34, 35], 1, false), Op::Call(90, vec![36], 2, false), Op::Const(G::from_u64(226)), Op::AssertEq(vec![37], vec![39], Some("aggr: tree has the wrong Ixon tag".into())), Op::Call(252, vec![38], 2, false), Op::Load(3, 41), Op::Const(G::from_u64(1)), Op::AssertEq(vec![42, 43, 44], vec![45, 45, 45], Some("aggr: trailing bytes after AssumptionTree".into())), Op::Call(322, vec![40], 1, false), Op::Const(G::from_u64(0)), Op::AssertEq(vec![46], vec![47], Some("aggr: a present tree must contain at least one leaf".into())), Op::Call(251, vec![40], 0, false), Op::Call(256, vec![40], 1, false), Op::Call(2, vec![48, 0], 1, false), Op::AssertEq(vec![49], vec![45], Some("aggr: tree leaves do not reproduce the canonical root".into()))], ctrl: Ctrl::Return(0, vec![40]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 46, lookups: 10 }, entry: false, constrained: true } } #[inline(never)] @@ -1343,348 +1359,358 @@ fn expected_function_260() -> Function { #[inline(never)] fn expected_function_261() -> Function { - Function { body: Block { ops: vec![Op::Load(3, 0), Op::Load(3, 1)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Match(5, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![8, 8, 8, 8]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(1, vec![8, 6, 0, 7]) })].into_iter().collect(), None) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(5, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(2, vec![8, 3, 4, 1]) }), (G::from_u64(0), Block { ops: vec![Op::Call(250, vec![3, 6], 1, false)], ctrl: Ctrl::Match(8, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(3, vec![9, 3, 4, 1]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(4, vec![9, 3, 4, 7]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(5, vec![9, 6, 0, 7]) }))) })].into_iter().collect(), None) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 6, auxiliaries: 10, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(3, 0), Op::Load(3, 1)], ctrl: Ctrl::Match(3, [(G::from_u64(1), Block { ops: vec![Op::Call(259, vec![1, 2], 0, false)], ctrl: Ctrl::Return(0, vec![]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(6, [(G::from_u64(1), Block { ops: vec![Op::Call(259, vec![0, 2], 0, false)], ctrl: Ctrl::Return(1, vec![]) }), (G::from_u64(0), Block { ops: vec![Op::Load(3, 2)], ctrl: Ctrl::Match(9, [(G::from_u64(0), Block { ops: vec![Op::Call(250, vec![4, 7], 1, false)], ctrl: Ctrl::Match(12, [(G::from_u64(0), Block { ops: vec![Op::Call(2, vec![4, 10], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![13], vec![14], Some("aggr: subject union mismatch".into())), Op::Call(261, vec![5, 1, 11], 0, false)], ctrl: Ctrl::Return(2, vec![]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::AssertEq(vec![13], vec![14], Some("aggr: child subject sets overlap".into()))], ctrl: Ctrl::Return(3, vec![]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(2, vec![7, 10], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![13], vec![14], Some("aggr: subject union mismatch".into())), Op::Call(261, vec![0, 8, 11], 0, false)], ctrl: Ctrl::Return(4, vec![]) }))) })].into_iter().collect(), None) })].into_iter().collect(), None) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 3, selectors: 5, auxiliaries: 14, lookups: 7 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_262() -> Function { - Function { body: Block { ops: vec![Op::Load(3, 1)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![5, 1]) }), (G::from_u64(0), Block { ops: vec![Op::Call(250, vec![3, 0], 1, false)], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![Op::Call(262, vec![0, 4], 2, false)], ctrl: Ctrl::Return(1, vec![6, 7]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(2, vec![6, 4]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(3, vec![6, 1]) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 4, auxiliaries: 7, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(3, 0), Op::Load(3, 1)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(5, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(1, vec![]) }), (G::from_u64(0), Block { ops: vec![Op::Call(250, vec![3, 6], 1, false)], ctrl: Ctrl::Match(8, [(G::from_u64(0), Block { ops: vec![Op::Call(262, vec![4, 1], 0, false)], ctrl: Ctrl::Return(2, vec![]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::AssertEq(vec![9], vec![10], Some("aggr: subject set overlaps assumption set".into()))], ctrl: Ctrl::Return(3, vec![]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(262, vec![0, 7], 0, false)], ctrl: Ctrl::Return(4, vec![]) }))) })].into_iter().collect(), None) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 5, auxiliaries: 10, lookups: 5 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_263() -> Function { - Function { body: Block { ops: vec![Op::Call(261, vec![0, 1], 4, false)], ctrl: Ctrl::Match(4, [(G::from_u64(0), Block { ops: vec![Op::Load(3, 3), Op::Const(G::from_u64(1)), Op::AssertEq(vec![8, 9, 10], vec![11, 11, 11], Some("aggr: output has an extra assumption".into()))], ctrl: Ctrl::Return(0, vec![]) }), (G::from_u64(1), Block { ops: vec![Op::Call(262, vec![5, 2], 2, false)], ctrl: Ctrl::Match(8, [(G::from_u64(1), Block { ops: vec![Op::Call(263, vec![6, 7, 9, 3], 0, false)], ctrl: Ctrl::Return(1, vec![]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Load(3, 3)], ctrl: Ctrl::Match(10, [(G::from_u64(0), Block { ops: vec![Op::Call(2, vec![5, 11], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![13], vec![14], Some("aggr: outstanding assumption mismatch".into())), Op::Call(263, vec![6, 7, 9, 12], 0, false)], ctrl: Ctrl::Return(2, vec![]) })].into_iter().collect(), None) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 4, selectors: 3, auxiliaries: 12, lookups: 6 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(3, 0), Op::Load(3, 1)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Match(5, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![8, 8, 8, 8, 8]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Const(G::from_u64(2))], ctrl: Ctrl::Return(1, vec![8, 6, 9, 0, 7]) })].into_iter().collect(), None) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(5, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(2, vec![8, 3, 9, 4, 1]) }), (G::from_u64(0), Block { ops: vec![Op::Call(250, vec![3, 6], 1, false)], ctrl: Ctrl::Match(8, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(3, vec![9, 3, 10, 4, 1]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(4, vec![9, 3, 9, 4, 7]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Const(G::from_u64(2))], ctrl: Ctrl::Return(5, vec![9, 6, 10, 0, 7]) }))) })].into_iter().collect(), None) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 6, auxiliaries: 10, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_264() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0, 2]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(90, vec![2], 2, false), Op::Const(G::from_u64(2)), Op::U8LessThan(3, 5), Op::Const(G::from_u64(1)), Op::AssertEq(vec![6], vec![7], Some("aggr structural: path side must be 0 or 1".into())), Op::Call(244, vec![4], 2, false)], ctrl: Ctrl::MatchContinue(3, [(G::from_u64(0), Block { ops: vec![Op::Call(247, vec![8, 0], 1, false)], ctrl: Ctrl::Yield(1, vec![10]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(247, vec![0, 8], 1, false)], ctrl: Ctrl::Yield(2, vec![10]) })), 1, 2, 1, Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(1, 11), Op::Call(264, vec![10, 12, 9], 2, false)], ctrl: Ctrl::Return(3, vec![13, 14]) })) }))) }, layout: FunctionLayout { input_size: 3, selectors: 4, auxiliaries: 12, lookups: 6 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(3, 1)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![5, 1]) }), (G::from_u64(0), Block { ops: vec![Op::Call(250, vec![3, 0], 1, false)], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![Op::Call(264, vec![0, 4], 2, false)], ctrl: Ctrl::Return(1, vec![6, 7]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(2, vec![6, 4]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(3, vec![6, 1]) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 4, auxiliaries: 7, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_265() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(6)), Op::Load(32, 0), Op::IOGetInfo(2, vec![3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]), Op::Call(3, vec![2, 35, 36], 1, false), Op::Call(90, vec![37], 2, false), Op::Const(G::from_u64(2)), Op::U8LessThan(38, 40), Op::Const(G::from_u64(1)), Op::AssertEq(vec![41], vec![42], Some("aggr structural: discharge choice must be 0 or 1".into()))], ctrl: Ctrl::Match(38, [(G::from_u64(0), Block { ops: vec![Op::Load(3, 39), Op::Const(G::from_u64(1)), Op::AssertEq(vec![43, 44, 45], vec![46, 46, 46], Some("aggr structural: carried choice has trailing bytes".into())), Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![47]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(90, vec![39], 2, false), Op::Const(G::from_u64(65)), Op::U8LessThan(43, 45), Op::Const(G::from_u64(1)), Op::AssertEq(vec![46], vec![47], Some("aggr structural: Merkle path exceeds 64 steps".into())), Op::Call(248, vec![0], 1, false), Op::Call(264, vec![48, 43, 44], 2, false), Op::Load(3, 50), Op::AssertEq(vec![51, 52, 53], vec![47, 47, 47], Some("aggr structural: trailing bytes after Merkle path".into())), Op::Call(2, vec![49, 1], 1, false), Op::AssertEq(vec![54], vec![47], Some("aggr structural: assumption path does not reach subject root".into()))], ctrl: Ctrl::Return(1, vec![47]) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 50, lookups: 11 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(263, vec![0, 1], 5, false)], ctrl: Ctrl::Match(4, [(G::from_u64(0), Block { ops: vec![Op::Load(3, 3), Op::Const(G::from_u64(1)), Op::AssertEq(vec![9, 10, 11], vec![12, 12, 12], Some("aggr: output has an extra assumption".into()))], ctrl: Ctrl::Return(0, vec![]) }), (G::from_u64(1), Block { ops: vec![Op::Call(264, vec![5, 2], 2, false)], ctrl: Ctrl::Match(9, [(G::from_u64(1), Block { ops: vec![Op::Call(265, vec![7, 8, 10, 3], 0, false)], ctrl: Ctrl::Return(1, vec![]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Load(3, 3)], ctrl: Ctrl::Match(11, [(G::from_u64(0), Block { ops: vec![Op::Call(2, vec![5, 12], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![14], vec![15], Some("aggr: outstanding assumption mismatch".into())), Op::Call(265, vec![7, 8, 10, 13], 0, false)], ctrl: Ctrl::Return(2, vec![]) })].into_iter().collect(), None) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 4, selectors: 3, auxiliaries: 13, lookups: 6 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_266() -> Function { - Function { body: Block { ops: vec![Op::Call(261, vec![0, 1], 4, false)], ctrl: Ctrl::Match(4, [(G::from_u64(0), Block { ops: vec![Op::Load(3, 3), Op::Const(G::from_u64(1)), Op::AssertEq(vec![8, 9, 10], vec![11, 11, 11], Some("aggr structural: output has an extra assumption".into()))], ctrl: Ctrl::Return(0, vec![]) }), (G::from_u64(1), Block { ops: vec![Op::Call(265, vec![5, 2], 1, false)], ctrl: Ctrl::Match(8, [(G::from_u64(1), Block { ops: vec![Op::Call(266, vec![6, 7, 2, 3], 0, false)], ctrl: Ctrl::Return(1, vec![]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Load(3, 3)], ctrl: Ctrl::Match(9, [(G::from_u64(0), Block { ops: vec![Op::Call(2, vec![5, 10], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![12], vec![13], Some("aggr structural: outstanding assumption mismatch".into())), Op::Call(266, vec![6, 7, 2, 11], 0, false)], ctrl: Ctrl::Return(2, vec![]) })].into_iter().collect(), None) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 4, selectors: 3, auxiliaries: 11, lookups: 6 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0, 2]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(90, vec![2], 2, false), Op::Const(G::from_u64(2)), Op::U8LessThan(3, 5), Op::Const(G::from_u64(1)), Op::AssertEq(vec![6], vec![7], Some("aggr structural: path side must be 0 or 1".into())), Op::Call(244, vec![4], 2, false)], ctrl: Ctrl::MatchContinue(3, [(G::from_u64(0), Block { ops: vec![Op::Call(247, vec![8, 0], 1, false)], ctrl: Ctrl::Yield(1, vec![10]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(247, vec![0, 8], 1, false)], ctrl: Ctrl::Yield(2, vec![10]) })), 1, 2, 1, Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(1, 11), Op::Call(266, vec![10, 12, 9], 2, false)], ctrl: Ctrl::Return(3, vec![13, 14]) })) }))) }, layout: FunctionLayout { input_size: 3, selectors: 4, auxiliaries: 12, lookups: 6 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_267() -> Function { - Function { body: Block { ops: vec![Op::Call(90, vec![0], 2, false)], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(0, vec![3, 3, 2]) }), (G::from_u64(1), Block { ops: vec![Op::Call(244, vec![2], 2, false), Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(1, vec![5, 3, 4]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 5, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(6)), Op::Load(32, 0), Op::IOGetInfo(4, vec![5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36]), Op::Call(3, vec![4, 37, 38], 1, false), Op::Call(90, vec![39], 2, false), Op::Const(G::from_u64(2)), Op::U8LessThan(40, 42), Op::Const(G::from_u64(1)), Op::AssertEq(vec![43], vec![44], Some("aggr structural: discharge choice must be 0 or 1".into()))], ctrl: Ctrl::Match(40, [(G::from_u64(0), Block { ops: vec![Op::Load(3, 41), Op::Const(G::from_u64(1)), Op::AssertEq(vec![45, 46, 47], vec![48, 48, 48], Some("aggr structural: carried choice has trailing bytes".into())), Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![49]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(90, vec![41], 2, false), Op::Const(G::from_u64(0)), Op::U8LessThan(47, 45), Op::Const(G::from_u64(1)), Op::AssertEq(vec![48], vec![49], Some("aggr structural: Merkle path must have at least 1 step".into())), Op::Const(G::from_u64(65)), Op::U8LessThan(45, 50), Op::AssertEq(vec![51], vec![49], Some("aggr structural: Merkle path exceeds 64 steps".into())), Op::Call(248, vec![0], 1, false), Op::Sub(45, 49), Op::Call(266, vec![52, 53, 46], 2, false), Op::Call(90, vec![55], 2, false), Op::Call(244, vec![57], 2, false), Op::Load(3, 59), Op::AssertEq(vec![60, 61, 62], vec![49, 49, 49], Some("aggr structural: trailing bytes after Merkle path".into()))], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0)), Op::AssertEq(vec![56], vec![63], Some("aggr structural: left assumption must be discharged by right child".into())), Op::Call(2, vec![58, 2], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![64], vec![65], Some("aggr structural: final step sibling is not left subject root".into())), Op::Call(2, vec![54, 3], 1, false), Op::AssertEq(vec![66], vec![65], Some("aggr structural: assumption path does not reach right subject root".into()))], ctrl: Ctrl::Return(1, vec![65]) }), (G::from_u64(2), Block { ops: vec![Op::Const(G::from_u64(1)), Op::AssertEq(vec![56], vec![63], Some("aggr structural: right assumption must be discharged by left child".into())), Op::Call(2, vec![58, 3], 1, false), Op::AssertEq(vec![64], vec![63], Some("aggr structural: final step sibling is not right subject root".into())), Op::Call(2, vec![54, 2], 1, false), Op::AssertEq(vec![65], vec![63], Some("aggr structural: assumption path does not reach left subject root".into()))], ctrl: Ctrl::Return(2, vec![63]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::AssertEq(vec![63], vec![64], Some("aggr structural: shared assumption cannot be discharged".into()))], ctrl: Ctrl::Return(3, vec![63]) }))) }))) }, layout: FunctionLayout { input_size: 4, selectors: 4, auxiliaries: 56, lookups: 15 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_268() -> Function { - Function { body: Block { ops: vec![Op::Call(90, vec![0], 2, false), Op::Const(G::from_u64(229)), Op::AssertEq(vec![1], vec![3], Some("aggr: claim is not CheckEnv".into())), Op::Call(90, vec![2], 2, false), Op::Const(G::from_u64(4)), Op::AssertEq(vec![4], vec![6], Some("aggr: unsupported object format".into())), Op::Call(90, vec![5], 2, false), Op::Const(G::from_u64(3)), Op::Sub(7, 9), Op::Sub(7, 6), Op::Mul(10, 11), Op::Const(G::from_u64(5)), Op::Sub(7, 13), Op::Mul(12, 14), Op::Const(G::from_u64(0)), Op::AssertEq(vec![15], vec![16], Some("aggr: wrong validator identity".into())), Op::Call(244, vec![8], 2, false), Op::Call(244, vec![18], 2, false), Op::Call(267, vec![20], 3, false), Op::Call(244, vec![23], 2, false), Op::Call(267, vec![25], 3, false), Op::Load(3, 28), Op::Const(G::from_u64(1)), Op::AssertEq(vec![29, 30, 31], vec![32, 32, 32], Some("aggr: trailing bytes after CheckEnv claim".into()))], ctrl: Ctrl::Return(0, vec![17, 19, 21, 22, 7, 24, 26, 27]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 24, lookups: 10 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(263, vec![0, 1], 5, false)], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![Op::Load(3, 4), Op::Const(G::from_u64(1)), Op::AssertEq(vec![10, 11, 12], vec![13, 13, 13], Some("aggr structural: output has an extra assumption".into()))], ctrl: Ctrl::Return(0, vec![]) }), (G::from_u64(1), Block { ops: vec![Op::Call(267, vec![6, 7, 2, 3], 1, false)], ctrl: Ctrl::Match(10, [(G::from_u64(1), Block { ops: vec![Op::Call(268, vec![8, 9, 2, 3, 4], 0, false)], ctrl: Ctrl::Return(1, vec![]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Load(3, 4)], ctrl: Ctrl::Match(11, [(G::from_u64(0), Block { ops: vec![Op::Call(2, vec![6, 12], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![14], vec![15], Some("aggr structural: outstanding assumption mismatch".into())), Op::Call(268, vec![8, 9, 2, 3, 13], 0, false)], ctrl: Ctrl::Return(2, vec![]) })].into_iter().collect(), None) }))) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 5, selectors: 3, auxiliaries: 12, lookups: 6 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_269() -> Function { - Function { body: Block { ops: vec![Op::Call(90, vec![0], 2, false), Op::Const(G::from_u64(232)), Op::AssertEq(vec![1], vec![3], Some("aggr: claim is not Binding".into())), Op::Call(90, vec![2], 2, false), Op::Const(G::from_u64(2)), Op::AssertEq(vec![4], vec![6], Some("aggr: claim is not Binding".into())), Op::Call(90, vec![5], 2, false), Op::Const(G::from_u64(4)), Op::AssertEq(vec![7], vec![9], Some("aggr: unsupported object format".into())), Op::Call(90, vec![8], 2, false), Op::Const(G::from_u64(3)), Op::AssertEq(vec![10], vec![12], Some("aggr: wrong validator identity".into())), Op::Call(244, vec![11], 2, false), Op::Call(244, vec![14], 2, false), Op::Load(3, 16), Op::Const(G::from_u64(1)), Op::AssertEq(vec![17, 18, 19], vec![20, 20, 20], Some("aggr: trailing bytes after Binding claim".into()))], ctrl: Ctrl::Return(0, vec![13, 15]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 16, lookups: 8 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(90, vec![0], 2, false)], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(0, vec![3, 3, 2]) }), (G::from_u64(1), Block { ops: vec![Op::Call(244, vec![2], 2, false), Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(1, vec![5, 3, 4]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 5, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_270() -> Function { - Function { body: Block { ops: vec![Op::Call(90, vec![0], 2, false), Op::Const(G::from_u64(232)), Op::AssertEq(vec![1], vec![3], Some("aggr: claim is not Catalog".into())), Op::Call(90, vec![2], 2, false), Op::Const(G::from_u64(0)), Op::AssertEq(vec![4], vec![6], Some("aggr: claim is not Catalog".into())), Op::Call(90, vec![5], 2, false), Op::Const(G::from_u64(4)), Op::AssertEq(vec![7], vec![9], Some("aggr: unsupported object format".into())), Op::Call(90, vec![8], 2, false), Op::Const(G::from_u64(3)), Op::AssertEq(vec![10], vec![12], Some("aggr: wrong validator identity".into())), Op::Call(244, vec![11], 2, false), Op::Load(3, 14), Op::Const(G::from_u64(1)), Op::AssertEq(vec![15, 16, 17], vec![18, 18, 18], Some("aggr: trailing bytes after Catalog claim".into()))], ctrl: Ctrl::Return(0, vec![13]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 14, lookups: 7 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(90, vec![0], 2, false), Op::Const(G::from_u64(229)), Op::AssertEq(vec![1], vec![3], Some("aggr: claim is not CheckEnv".into())), Op::Call(90, vec![2], 2, false), Op::Const(G::from_u64(4)), Op::AssertEq(vec![4], vec![6], Some("aggr: unsupported object format".into())), Op::Call(90, vec![5], 2, false), Op::Const(G::from_u64(3)), Op::Sub(7, 9), Op::Sub(7, 6), Op::Mul(10, 11), Op::Const(G::from_u64(5)), Op::Sub(7, 13), Op::Mul(12, 14), Op::Const(G::from_u64(0)), Op::AssertEq(vec![15], vec![16], Some("aggr: wrong validator identity".into())), Op::Call(244, vec![8], 2, false), Op::Call(244, vec![18], 2, false), Op::Call(269, vec![20], 3, false), Op::Call(244, vec![23], 2, false), Op::Call(269, vec![25], 3, false), Op::Load(3, 28), Op::Const(G::from_u64(1)), Op::AssertEq(vec![29, 30, 31], vec![32, 32, 32], Some("aggr: trailing bytes after CheckEnv claim".into()))], ctrl: Ctrl::Return(0, vec![17, 19, 21, 22, 7, 24, 26, 27]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 24, lookups: 10 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_271() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(0, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(0, vec![4]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(1, vec![4]) })].into_iter().collect(), None) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(2, vec![4]) }), (G::from_u64(0), Block { ops: vec![Op::Call(2, vec![1, 3], 1, false)], ctrl: Ctrl::Return(3, vec![4]) })].into_iter().collect(), None) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 4, selectors: 4, auxiliaries: 2, lookups: 2 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(90, vec![0], 2, false), Op::Const(G::from_u64(232)), Op::AssertEq(vec![1], vec![3], Some("aggr: claim is not Binding".into())), Op::Call(90, vec![2], 2, false), Op::Const(G::from_u64(2)), Op::AssertEq(vec![4], vec![6], Some("aggr: claim is not Binding".into())), Op::Call(90, vec![5], 2, false), Op::Const(G::from_u64(4)), Op::AssertEq(vec![7], vec![9], Some("aggr: unsupported object format".into())), Op::Call(90, vec![8], 2, false), Op::Const(G::from_u64(3)), Op::AssertEq(vec![10], vec![12], Some("aggr: wrong validator identity".into())), Op::Call(244, vec![11], 2, false), Op::Call(244, vec![14], 2, false), Op::Load(3, 16), Op::Const(G::from_u64(1)), Op::AssertEq(vec![17, 18, 19], vec![20, 20, 20], Some("aggr: trailing bytes after Binding claim".into()))], ctrl: Ctrl::Return(0, vec![13, 15]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 16, lookups: 8 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_272() -> Function { - Function { body: Block { ops: vec![Op::Call(2, vec![0, 5], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![10], vec![11], Some("aggr: claims differ in specification digest".into())), Op::Call(2, vec![1, 6], 1, false), Op::AssertEq(vec![12], vec![11], Some("aggr: claims differ in binding hash".into())), Op::Call(271, vec![2, 3, 7, 8], 1, false), Op::AssertEq(vec![13], vec![11], Some("aggr: claims differ in axioms".into())), Op::AssertEq(vec![4], vec![9], Some("aggr: claims differ in whether they assume their binding".into()))], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 10, selectors: 1, auxiliaries: 4, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(90, vec![0], 2, false), Op::Const(G::from_u64(232)), Op::AssertEq(vec![1], vec![3], Some("aggr: claim is not Catalog".into())), Op::Call(90, vec![2], 2, false), Op::Const(G::from_u64(0)), Op::AssertEq(vec![4], vec![6], Some("aggr: claim is not Catalog".into())), Op::Call(90, vec![5], 2, false), Op::Const(G::from_u64(4)), Op::AssertEq(vec![7], vec![9], Some("aggr: unsupported object format".into())), Op::Call(90, vec![8], 2, false), Op::Const(G::from_u64(3)), Op::AssertEq(vec![10], vec![12], Some("aggr: wrong validator identity".into())), Op::Call(244, vec![11], 2, false), Op::Load(3, 14), Op::Const(G::from_u64(1)), Op::AssertEq(vec![15, 16, 17], vec![18, 18, 18], Some("aggr: trailing bytes after Catalog claim".into()))], ctrl: Ctrl::Return(0, vec![13]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 14, lookups: 7 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_273() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(4)), Op::IOGetInfo(8, vec![0, 1, 2, 3, 4, 5, 6, 7]), Op::Call(3, vec![8, 9, 10], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Const(G::from_u64(1)), Op::Store(vec![43, 43, 43]), Op::Const(G::from_u64(0)), Op::Store(vec![45, 45, 45, 45, 45, 45, 45, 45]), Op::Store(vec![12, 13, 14, 15, 16, 17, 12, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42]), Op::Store(vec![43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43]), Op::Call(78, vec![11, 44, 45, 45, 46, 47, 48], 1, false), Op::Call(77, vec![49], 32, false), Op::Const(G::from_u64(256)), Op::Mul(82, 51), Op::Add(50, 83), Op::Const(G::from_u64(65536)), Op::Mul(85, 52), Op::Add(84, 86), Op::Const(G::from_u64(16777216)), Op::Mul(88, 53), Op::Add(87, 89), Op::Mul(82, 55), Op::Add(54, 91), Op::Mul(85, 56), Op::Add(92, 93), Op::Mul(88, 57), Op::Add(94, 95), Op::Mul(82, 59), Op::Add(58, 97), Op::Mul(85, 60), Op::Add(98, 99), Op::Mul(88, 61), Op::Add(100, 101), Op::Mul(82, 63), Op::Add(62, 103), Op::Mul(85, 64), Op::Add(104, 105), Op::Mul(88, 65), Op::Add(106, 107), Op::Mul(82, 67), Op::Add(66, 109), Op::Mul(85, 68), Op::Add(110, 111), Op::Mul(88, 69), Op::Add(112, 113), Op::Mul(82, 71), Op::Add(70, 115), Op::Mul(85, 72), Op::Add(116, 117), Op::Mul(88, 73), Op::Add(118, 119), Op::Mul(82, 75), Op::Add(74, 121), Op::Mul(85, 76), Op::Add(122, 123), Op::Mul(88, 77), Op::Add(124, 125), Op::Mul(82, 79), Op::Add(78, 127), Op::Mul(85, 80), Op::Add(128, 129), Op::Mul(88, 81), Op::Add(130, 131), Op::AssertEq(vec![90, 96, 102, 108, 114, 120, 126, 132], vec![0, 1, 2, 3, 4, 5, 6, 7], Some("aggr: claim preimage digest mismatch".into()))], ctrl: Ctrl::Return(0, vec![11]) }, layout: FunctionLayout { input_size: 8, selectors: 1, auxiliaries: 41, lookups: 8 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(0, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(0, vec![4]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(1, vec![4]) })].into_iter().collect(), None) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(2, vec![4]) }), (G::from_u64(0), Block { ops: vec![Op::Call(2, vec![1, 3], 1, false)], ctrl: Ctrl::Return(3, vec![4]) })].into_iter().collect(), None) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 4, selectors: 4, auxiliaries: 2, lookups: 2 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_274() -> Function { - Function { body: Block { ops: vec![Op::Call(326, vec![0, 1], 8, false), Op::Const(G::from_u64(256)), Op::Mul(10, 3), Op::Add(2, 11), Op::Const(G::from_u64(65536)), Op::Mul(13, 4), Op::Add(12, 14), Op::Const(G::from_u64(16777216)), Op::Mul(16, 5), Op::Add(15, 17), Op::Const(G::from_u64(4294967296)), Op::Mul(19, 6), Op::Add(18, 20), Op::Const(G::from_u64(1099511627776)), Op::Mul(22, 7), Op::Add(21, 23), Op::Const(G::from_u64(281474976710656)), Op::Mul(25, 8), Op::Add(24, 26), Op::Const(G::from_u64(72057594037927936)), Op::Mul(28, 9), Op::Add(27, 29)], ctrl: Ctrl::Return(0, vec![30]) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 9, lookups: 2 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(2, vec![0, 5], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![10], vec![11], Some("aggr: claims differ in specification digest".into())), Op::Call(2, vec![1, 6], 1, false), Op::AssertEq(vec![12], vec![11], Some("aggr: claims differ in binding hash".into())), Op::Call(273, vec![2, 3, 7, 8], 1, false), Op::AssertEq(vec![13], vec![11], Some("aggr: claims differ in axioms".into())), Op::AssertEq(vec![4], vec![9], Some("aggr: claims differ in whether they assume their binding".into()))], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 10, selectors: 1, auxiliaries: 4, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_275() -> Function { - Function { body: Block { ops: vec![Op::Call(274, vec![0, 1], 1, false), Op::Const(G::from_u64(1)), Op::Add(1, 3), Op::Call(274, vec![0, 4], 1, false), Op::Const(G::from_u64(2)), Op::Add(1, 6), Op::Call(274, vec![0, 7], 1, false), Op::Const(G::from_u64(3)), Op::Add(1, 9), Op::Call(274, vec![0, 10], 1, false), Op::Const(G::from_u64(4)), Op::Add(1, 12), Op::Call(274, vec![0, 13], 1, false), Op::Const(G::from_u64(5)), Op::Add(1, 15), Op::Call(274, vec![0, 16], 1, false), Op::Const(G::from_u64(6)), Op::Add(1, 18), Op::Call(274, vec![0, 19], 1, false), Op::Const(G::from_u64(7)), Op::Add(1, 21), Op::Call(274, vec![0, 22], 1, false)], ctrl: Ctrl::Return(0, vec![2, 5, 8, 11, 14, 17, 20, 23]) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 9, lookups: 9 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(4)), Op::IOGetInfo(8, vec![0, 1, 2, 3, 4, 5, 6, 7]), Op::Call(3, vec![8, 9, 10], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Const(G::from_u64(1)), Op::Store(vec![43, 43, 43]), Op::Const(G::from_u64(0)), Op::Store(vec![45, 45, 45, 45, 45, 45, 45, 45]), Op::Store(vec![12, 13, 14, 15, 16, 17, 12, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42]), Op::Store(vec![43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43]), Op::Call(78, vec![11, 44, 45, 45, 46, 47, 48], 1, false), Op::Call(77, vec![49], 32, false), Op::Const(G::from_u64(256)), Op::Mul(82, 51), Op::Add(50, 83), Op::Const(G::from_u64(65536)), Op::Mul(85, 52), Op::Add(84, 86), Op::Const(G::from_u64(16777216)), Op::Mul(88, 53), Op::Add(87, 89), Op::Mul(82, 55), Op::Add(54, 91), Op::Mul(85, 56), Op::Add(92, 93), Op::Mul(88, 57), Op::Add(94, 95), Op::Mul(82, 59), Op::Add(58, 97), Op::Mul(85, 60), Op::Add(98, 99), Op::Mul(88, 61), Op::Add(100, 101), Op::Mul(82, 63), Op::Add(62, 103), Op::Mul(85, 64), Op::Add(104, 105), Op::Mul(88, 65), Op::Add(106, 107), Op::Mul(82, 67), Op::Add(66, 109), Op::Mul(85, 68), Op::Add(110, 111), Op::Mul(88, 69), Op::Add(112, 113), Op::Mul(82, 71), Op::Add(70, 115), Op::Mul(85, 72), Op::Add(116, 117), Op::Mul(88, 73), Op::Add(118, 119), Op::Mul(82, 75), Op::Add(74, 121), Op::Mul(85, 76), Op::Add(122, 123), Op::Mul(88, 77), Op::Add(124, 125), Op::Mul(82, 79), Op::Add(78, 127), Op::Mul(85, 80), Op::Add(128, 129), Op::Mul(88, 81), Op::Add(130, 131), Op::AssertEq(vec![90, 96, 102, 108, 114, 120, 126, 132], vec![0, 1, 2, 3, 4, 5, 6, 7], Some("aggr: claim preimage digest mismatch".into()))], ctrl: Ctrl::Return(0, vec![11]) }, layout: FunctionLayout { input_size: 8, selectors: 1, auxiliaries: 41, lookups: 8 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_276() -> Function { - Function { body: Block { ops: vec![Op::AssertEq(vec![0], vec![8], None), Op::AssertEq(vec![1], vec![9], None), Op::AssertEq(vec![2], vec![10], None), Op::AssertEq(vec![3], vec![11], None), Op::AssertEq(vec![4], vec![12], None), Op::AssertEq(vec![5], vec![13], None), Op::AssertEq(vec![6], vec![14], None), Op::AssertEq(vec![7], vec![15], None)], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 16, selectors: 1, auxiliaries: 1, lookups: 1 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(328, vec![0, 1], 8, false), Op::Const(G::from_u64(256)), Op::Mul(10, 3), Op::Add(2, 11), Op::Const(G::from_u64(65536)), Op::Mul(13, 4), Op::Add(12, 14), Op::Const(G::from_u64(16777216)), Op::Mul(16, 5), Op::Add(15, 17), Op::Const(G::from_u64(4294967296)), Op::Mul(19, 6), Op::Add(18, 20), Op::Const(G::from_u64(1099511627776)), Op::Mul(22, 7), Op::Add(21, 23), Op::Const(G::from_u64(281474976710656)), Op::Mul(25, 8), Op::Add(24, 26), Op::Const(G::from_u64(72057594037927936)), Op::Mul(28, 9), Op::Add(27, 29)], ctrl: Ctrl::Return(0, vec![30]) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 9, lookups: 2 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_277() -> Function { - Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![Op::Load(3, 3), Op::Const(G::from_u64(1)), Op::AssertEq(vec![4, 5, 6], vec![7, 7, 7], Some("aggr: child proof must expose exactly one claim".into()))], ctrl: Ctrl::Return(0, vec![2]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 7, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(276, vec![0, 1], 1, false), Op::Const(G::from_u64(1)), Op::Add(1, 3), Op::Call(276, vec![0, 4], 1, false), Op::Const(G::from_u64(2)), Op::Add(1, 6), Op::Call(276, vec![0, 7], 1, false), Op::Const(G::from_u64(3)), Op::Add(1, 9), Op::Call(276, vec![0, 10], 1, false), Op::Const(G::from_u64(4)), Op::Add(1, 12), Op::Call(276, vec![0, 13], 1, false), Op::Const(G::from_u64(5)), Op::Add(1, 15), Op::Call(276, vec![0, 16], 1, false), Op::Const(G::from_u64(6)), Op::Add(1, 18), Op::Call(276, vec![0, 19], 1, false), Op::Const(G::from_u64(7)), Op::Add(1, 21), Op::Call(276, vec![0, 22], 1, false)], ctrl: Ctrl::Return(0, vec![2, 5, 8, 11, 14, 17, 20, 23]) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 9, lookups: 9 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_278() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::IOGetInfo(17, vec![0]), Op::Call(3, vec![17, 18, 19], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Store(vec![17, 17, 17]), Op::Const(G::from_u64(0)), Op::Store(vec![53, 53, 53, 53, 53, 53, 53, 53]), Op::Store(vec![21, 22, 23, 24, 25, 26, 21, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]), Op::Store(vec![17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17]), Op::Call(78, vec![20, 52, 53, 53, 54, 55, 56], 1, false), Op::Call(77, vec![57], 32, false), Op::Const(G::from_u64(256)), Op::Mul(90, 59), Op::Add(58, 91), Op::Const(G::from_u64(65536)), Op::Mul(93, 60), Op::Add(92, 94), Op::Const(G::from_u64(16777216)), Op::Mul(96, 61), Op::Add(95, 97), Op::Mul(90, 63), Op::Add(62, 99), Op::Mul(93, 64), Op::Add(100, 101), Op::Mul(96, 65), Op::Add(102, 103), Op::Mul(90, 67), Op::Add(66, 105), Op::Mul(93, 68), Op::Add(106, 107), Op::Mul(96, 69), Op::Add(108, 109), Op::Mul(90, 71), Op::Add(70, 111), Op::Mul(93, 72), Op::Add(112, 113), Op::Mul(96, 73), Op::Add(114, 115), Op::Mul(90, 75), Op::Add(74, 117), Op::Mul(93, 76), Op::Add(118, 119), Op::Mul(96, 77), Op::Add(120, 121), Op::Mul(90, 79), Op::Add(78, 123), Op::Mul(93, 80), Op::Add(124, 125), Op::Mul(96, 81), Op::Add(126, 127), Op::Mul(90, 83), Op::Add(82, 129), Op::Mul(93, 84), Op::Add(130, 131), Op::Mul(96, 85), Op::Add(132, 133), Op::Mul(90, 87), Op::Add(86, 135), Op::Mul(93, 88), Op::Add(136, 137), Op::Mul(96, 89), Op::Add(138, 139)], ctrl: Ctrl::MatchContinue(0, [(G::from_u64(0), Block { ops: vec![Op::Call(276, vec![98, 104, 110, 116, 122, 128, 134, 140, 1, 2, 3, 4, 5, 6, 7, 8], 0, false)], ctrl: Ctrl::Yield(0, vec![]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(276, vec![98, 104, 110, 116, 122, 128, 134, 140, 9, 10, 11, 12, 13, 14, 15, 16], 0, false)], ctrl: Ctrl::Yield(1, vec![]) })), 0, 1, 1, Box::new(Block { ops: vec![Op::Call(93, vec![20], 10, false), Op::Call(93, vec![150], 10, false), Op::Call(93, vec![160], 10, false), Op::Call(93, vec![170], 10, false), Op::Call(93, vec![180], 10, false), Op::Call(93, vec![190], 10, false), Op::Call(93, vec![200], 10, false), Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::Store(vec![212, 212, 212, 212, 212, 212, 212, 212, 212, 212]), Op::Store(vec![211, 202, 203, 204, 205, 206, 207, 208, 209, 213]), Op::Store(vec![211, 192, 193, 194, 195, 196, 197, 198, 199, 214]), Op::Store(vec![211, 182, 183, 184, 185, 186, 187, 188, 189, 215]), Op::Store(vec![211, 172, 173, 174, 175, 176, 177, 178, 179, 216]), Op::Store(vec![211, 162, 163, 164, 165, 166, 167, 168, 169, 217]), Op::Store(vec![211, 152, 153, 154, 155, 156, 157, 158, 159, 218]), Op::Store(vec![211, 142, 143, 144, 145, 146, 147, 148, 149, 219]), Op::Call(93, vec![210], 10, false), Op::Call(106, vec![230, 221], 3, false), Op::Call(107, vec![233], 3, false), Op::Call(108, vec![236, 221], 2, false), Op::Store(vec![211, 222, 223, 224, 225, 226, 227, 228, 229, 232]), Op::Call(322, vec![220, 239], 1, false), Op::Load(3, 238), Op::AssertEq(vec![241, 242, 243], vec![212, 212, 212], None)], ctrl: Ctrl::Return(2, vec![141, 151, 161, 171, 181, 191, 201, 240, 231, 234, 235, 237]) })) }, layout: FunctionLayout { input_size: 17, selectors: 3, auxiliaries: 143, lookups: 31 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::AssertEq(vec![0], vec![8], None), Op::AssertEq(vec![1], vec![9], None), Op::AssertEq(vec![2], vec![10], None), Op::AssertEq(vec![3], vec![11], None), Op::AssertEq(vec![4], vec![12], None), Op::AssertEq(vec![5], vec![13], None), Op::AssertEq(vec![6], vec![14], None), Op::AssertEq(vec![7], vec![15], None)], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 16, selectors: 1, auxiliaries: 1, lookups: 1 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_279() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::IOGetInfo(13, vec![12]), Op::Const(G::from_u64(2)), Op::IOGetInfo(16, vec![12]), Op::Call(3, vec![16, 17, 18], 1, false), Op::Call(28, vec![19], 2, false), Op::Call(241, vec![21, 20], 2, false), Op::Load(3, 23), Op::Const(G::from_u64(1)), Op::AssertEq(vec![24, 25, 26], vec![27, 27, 27], None), Op::Call(31, vec![14], 2, false), Op::Call(65, vec![29, 28], 2, false), Op::Call(31, vec![31], 2, false), Op::Call(68, vec![33, 32], 2, false), Op::Call(31, vec![35], 2, false), Op::Call(70, vec![37, 36], 2, false), Op::Add(14, 15), Op::AssertEq(vec![39], vec![40], None), Op::Call(238, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 30, 34, 38], 1, false), Op::Call(223, vec![41, 22], 1, false), Op::AssertEq(vec![42], vec![27], None), Op::AssertEq(vec![27], vec![27], None)], ctrl: Ctrl::Return(0, vec![22]) }, layout: FunctionLayout { input_size: 13, selectors: 1, auxiliaries: 27, lookups: 13 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![Op::Load(3, 3), Op::Const(G::from_u64(1)), Op::AssertEq(vec![4, 5, 6], vec![7, 7, 7], Some("aggr: child proof must expose exactly one claim".into()))], ctrl: Ctrl::Return(0, vec![2]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 7, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_280() -> Function { - Function { body: Block { ops: vec![Op::Call(278, vec![0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], 12, false), Op::Call(279, vec![28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 1], 1, false), Op::Call(277, vec![40], 1, false), Op::Const(G::from_u64(0)), Op::Call(274, vec![41, 42], 1, false), Op::AssertEq(vec![43], vec![42], Some("aggr: child has the wrong claim channel".into()))], ctrl: Ctrl::Match(0, [(G::from_u64(0), Block { ops: vec![Op::Call(317, vec![41], 1, false), Op::Const(G::from_u64(10)), Op::AssertEq(vec![44], vec![45], Some("aggr: IxVM child must expose a 10-word verify_claim claim".into())), Op::Const(G::from_u64(1)), Op::Call(274, vec![41, 46], 1, false), Op::AssertEq(vec![47], vec![18], Some("aggr: IxVM child has the wrong entrypoint".into())), Op::Const(G::from_u64(2)), Op::Call(275, vec![41, 48], 8, false)], ctrl: Ctrl::Return(0, vec![49, 50, 51, 52, 53, 54, 55, 56]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(317, vec![41], 1, false), Op::Const(G::from_u64(18)), Op::AssertEq(vec![44], vec![45], Some("aggr: recursive child must expose an 18-word ix_aggr claim".into())), Op::Const(G::from_u64(1)), Op::Call(274, vec![41, 46], 1, false), Op::AssertEq(vec![47], vec![19], Some("aggr: recursive child has the wrong entrypoint".into())), Op::Const(G::from_u64(2)), Op::Call(275, vec![41, 48], 8, false), Op::Call(276, vec![49, 50, 51, 52, 53, 54, 55, 56, 20, 21, 22, 23, 24, 25, 26, 27], 0, false), Op::Const(G::from_u64(10)), Op::Call(275, vec![41, 57], 8, false)], ctrl: Ctrl::Return(1, vec![58, 59, 60, 61, 62, 63, 64, 65]) }))) }, layout: FunctionLayout { input_size: 28, selectors: 2, auxiliaries: 35, lookups: 10 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::IOGetInfo(17, vec![0]), Op::Call(3, vec![17, 18, 19], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Store(vec![17, 17, 17]), Op::Const(G::from_u64(0)), Op::Store(vec![53, 53, 53, 53, 53, 53, 53, 53]), Op::Store(vec![21, 22, 23, 24, 25, 26, 21, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]), Op::Store(vec![17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17]), Op::Call(78, vec![20, 52, 53, 53, 54, 55, 56], 1, false), Op::Call(77, vec![57], 32, false), Op::Const(G::from_u64(256)), Op::Mul(90, 59), Op::Add(58, 91), Op::Const(G::from_u64(65536)), Op::Mul(93, 60), Op::Add(92, 94), Op::Const(G::from_u64(16777216)), Op::Mul(96, 61), Op::Add(95, 97), Op::Mul(90, 63), Op::Add(62, 99), Op::Mul(93, 64), Op::Add(100, 101), Op::Mul(96, 65), Op::Add(102, 103), Op::Mul(90, 67), Op::Add(66, 105), Op::Mul(93, 68), Op::Add(106, 107), Op::Mul(96, 69), Op::Add(108, 109), Op::Mul(90, 71), Op::Add(70, 111), Op::Mul(93, 72), Op::Add(112, 113), Op::Mul(96, 73), Op::Add(114, 115), Op::Mul(90, 75), Op::Add(74, 117), Op::Mul(93, 76), Op::Add(118, 119), Op::Mul(96, 77), Op::Add(120, 121), Op::Mul(90, 79), Op::Add(78, 123), Op::Mul(93, 80), Op::Add(124, 125), Op::Mul(96, 81), Op::Add(126, 127), Op::Mul(90, 83), Op::Add(82, 129), Op::Mul(93, 84), Op::Add(130, 131), Op::Mul(96, 85), Op::Add(132, 133), Op::Mul(90, 87), Op::Add(86, 135), Op::Mul(93, 88), Op::Add(136, 137), Op::Mul(96, 89), Op::Add(138, 139)], ctrl: Ctrl::MatchContinue(0, [(G::from_u64(0), Block { ops: vec![Op::Call(278, vec![98, 104, 110, 116, 122, 128, 134, 140, 1, 2, 3, 4, 5, 6, 7, 8], 0, false)], ctrl: Ctrl::Yield(0, vec![]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(278, vec![98, 104, 110, 116, 122, 128, 134, 140, 9, 10, 11, 12, 13, 14, 15, 16], 0, false)], ctrl: Ctrl::Yield(1, vec![]) })), 0, 1, 1, Box::new(Block { ops: vec![Op::Call(93, vec![20], 10, false), Op::Call(93, vec![150], 10, false), Op::Call(93, vec![160], 10, false), Op::Call(93, vec![170], 10, false), Op::Call(93, vec![180], 10, false), Op::Call(93, vec![190], 10, false), Op::Call(93, vec![200], 10, false), Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::Store(vec![212, 212, 212, 212, 212, 212, 212, 212, 212, 212]), Op::Store(vec![211, 202, 203, 204, 205, 206, 207, 208, 209, 213]), Op::Store(vec![211, 192, 193, 194, 195, 196, 197, 198, 199, 214]), Op::Store(vec![211, 182, 183, 184, 185, 186, 187, 188, 189, 215]), Op::Store(vec![211, 172, 173, 174, 175, 176, 177, 178, 179, 216]), Op::Store(vec![211, 162, 163, 164, 165, 166, 167, 168, 169, 217]), Op::Store(vec![211, 152, 153, 154, 155, 156, 157, 158, 159, 218]), Op::Store(vec![211, 142, 143, 144, 145, 146, 147, 148, 149, 219]), Op::Call(93, vec![210], 10, false), Op::Call(106, vec![230, 221], 3, false), Op::Call(107, vec![233], 3, false), Op::Call(108, vec![236, 221], 2, false), Op::Store(vec![211, 222, 223, 224, 225, 226, 227, 228, 229, 232]), Op::Call(324, vec![220, 239], 1, false), Op::Load(3, 238), Op::AssertEq(vec![241, 242, 243], vec![212, 212, 212], None)], ctrl: Ctrl::Return(2, vec![141, 151, 161, 171, 181, 191, 201, 240, 231, 234, 235, 237]) })) }, layout: FunctionLayout { input_size: 17, selectors: 3, auxiliaries: 143, lookups: 31 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_281() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(280, vec![0, 35, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26], 8, false), Op::Call(276, vec![36, 37, 38, 39, 40, 41, 42, 43, 27, 28, 29, 30, 31, 32, 33, 34], 0, false)], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 35, selectors: 1, auxiliaries: 9, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::IOGetInfo(13, vec![12]), Op::Const(G::from_u64(2)), Op::IOGetInfo(16, vec![12]), Op::Call(3, vec![16, 17, 18], 1, false), Op::Call(28, vec![19], 2, false), Op::Call(241, vec![21, 20], 2, false), Op::Load(3, 23), Op::Const(G::from_u64(1)), Op::AssertEq(vec![24, 25, 26], vec![27, 27, 27], None), Op::Call(31, vec![14], 2, false), Op::Call(65, vec![29, 28], 2, false), Op::Call(31, vec![31], 2, false), Op::Call(68, vec![33, 32], 2, false), Op::Call(31, vec![35], 2, false), Op::Call(70, vec![37, 36], 2, false), Op::Add(14, 15), Op::AssertEq(vec![39], vec![40], None), Op::Call(238, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 30, 34, 38], 1, false), Op::Call(223, vec![41, 22], 1, false), Op::AssertEq(vec![42], vec![27], None), Op::AssertEq(vec![27], vec![27], None)], ctrl: Ctrl::Return(0, vec![22]) }, layout: FunctionLayout { input_size: 13, selectors: 1, auxiliaries: 27, lookups: 13 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_282() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(280, vec![0, 36, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], 8, false), Op::Const(G::from_u64(1)), Op::Call(280, vec![1, 45, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], 8, false), Op::Call(273, vec![37, 38, 39, 40, 41, 42, 43, 44], 1, false), Op::Call(268, vec![54], 8, false), Op::Call(273, vec![46, 47, 48, 49, 50, 51, 52, 53], 1, false), Op::Call(268, vec![63], 8, false), Op::Const(G::from_u64(2)), Op::IOGetInfo(72, vec![72]), Op::Call(3, vec![72, 73, 74], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Store(vec![45, 45, 45]), Op::Store(vec![36, 36, 36, 36, 36, 36, 36, 36]), Op::Store(vec![76, 77, 78, 79, 80, 81, 76, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106]), Op::Store(vec![45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45]), Op::Call(78, vec![75, 107, 36, 36, 108, 109, 110], 1, false), Op::Call(77, vec![111], 32, false), Op::Const(G::from_u64(256)), Op::Mul(144, 113), Op::Add(112, 145), Op::Const(G::from_u64(65536)), Op::Mul(147, 114), Op::Add(146, 148), Op::Const(G::from_u64(16777216)), Op::Mul(150, 115), Op::Add(149, 151), Op::Mul(144, 117), Op::Add(116, 153), Op::Mul(147, 118), Op::Add(154, 155), Op::Mul(150, 119), Op::Add(156, 157), Op::Mul(144, 121), Op::Add(120, 159), Op::Mul(147, 122), Op::Add(160, 161), Op::Mul(150, 123), Op::Add(162, 163), Op::Mul(144, 125), Op::Add(124, 165), Op::Mul(147, 126), Op::Add(166, 167), Op::Mul(150, 127), Op::Add(168, 169), Op::Mul(144, 129), Op::Add(128, 171), Op::Mul(147, 130), Op::Add(172, 173), Op::Mul(150, 131), Op::Add(174, 175), Op::Mul(144, 133), Op::Add(132, 177), Op::Mul(147, 134), Op::Add(178, 179), Op::Mul(150, 135), Op::Add(180, 181), Op::Mul(144, 137), Op::Add(136, 183), Op::Mul(147, 138), Op::Add(184, 185), Op::Mul(150, 139), Op::Add(186, 187), Op::Mul(144, 141), Op::Add(140, 189), Op::Mul(147, 142), Op::Add(190, 191), Op::Mul(150, 143), Op::Add(192, 193), Op::AssertEq(vec![152, 158, 164, 170, 176, 182, 188, 194], vec![28, 29, 30, 31, 32, 33, 34, 35], Some("aggr: output claim digest mismatch".into())), Op::Call(268, vec![75], 8, false), Op::Call(272, vec![55, 56, 57, 58, 59, 64, 65, 66, 67, 68], 0, false), Op::Call(272, vec![55, 56, 57, 58, 59, 195, 196, 197, 198, 199], 0, false), Op::Call(257, vec![60], 1, false), Op::Call(257, vec![69], 1, false), Op::Call(258, vec![61, 62], 1, false), Op::Call(258, vec![70, 71], 1, false), Op::Call(257, vec![200], 1, false), Op::Call(258, vec![201, 202], 1, false), Op::Call(260, vec![203, 204, 207], 0, false), Op::Call(263, vec![205, 206, 207, 208], 0, false)], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 36, selectors: 1, auxiliaries: 89, lookups: 25 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(280, vec![0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], 12, false), Op::Call(281, vec![28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 1], 1, false), Op::Call(279, vec![40], 1, false), Op::Const(G::from_u64(0)), Op::Call(276, vec![41, 42], 1, false), Op::AssertEq(vec![43], vec![42], Some("aggr: child has the wrong claim channel".into()))], ctrl: Ctrl::Match(0, [(G::from_u64(0), Block { ops: vec![Op::Call(319, vec![41], 1, false), Op::Const(G::from_u64(10)), Op::AssertEq(vec![44], vec![45], Some("aggr: IxVM child must expose a 10-word verify_claim claim".into())), Op::Const(G::from_u64(1)), Op::Call(276, vec![41, 46], 1, false), Op::AssertEq(vec![47], vec![18], Some("aggr: IxVM child has the wrong entrypoint".into())), Op::Const(G::from_u64(2)), Op::Call(277, vec![41, 48], 8, false)], ctrl: Ctrl::Return(0, vec![49, 50, 51, 52, 53, 54, 55, 56]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(319, vec![41], 1, false), Op::Const(G::from_u64(18)), Op::AssertEq(vec![44], vec![45], Some("aggr: recursive child must expose an 18-word ix_aggr claim".into())), Op::Const(G::from_u64(1)), Op::Call(276, vec![41, 46], 1, false), Op::AssertEq(vec![47], vec![19], Some("aggr: recursive child has the wrong entrypoint".into())), Op::Const(G::from_u64(2)), Op::Call(277, vec![41, 48], 8, false), Op::Call(278, vec![49, 50, 51, 52, 53, 54, 55, 56, 20, 21, 22, 23, 24, 25, 26, 27], 0, false), Op::Const(G::from_u64(10)), Op::Call(277, vec![41, 57], 8, false)], ctrl: Ctrl::Return(1, vec![58, 59, 60, 61, 62, 63, 64, 65]) }))) }, layout: FunctionLayout { input_size: 28, selectors: 2, auxiliaries: 35, lookups: 10 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_283() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(280, vec![0, 36, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], 8, false), Op::Const(G::from_u64(1)), Op::Call(280, vec![1, 45, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], 8, false), Op::Call(273, vec![37, 38, 39, 40, 41, 42, 43, 44], 1, false), Op::Call(268, vec![54], 8, false), Op::Call(273, vec![46, 47, 48, 49, 50, 51, 52, 53], 1, false), Op::Call(268, vec![63], 8, false), Op::Const(G::from_u64(2)), Op::IOGetInfo(72, vec![72]), Op::Call(3, vec![72, 73, 74], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Store(vec![45, 45, 45]), Op::Store(vec![36, 36, 36, 36, 36, 36, 36, 36]), Op::Store(vec![76, 77, 78, 79, 80, 81, 76, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106]), Op::Store(vec![45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45]), Op::Call(78, vec![75, 107, 36, 36, 108, 109, 110], 1, false), Op::Call(77, vec![111], 32, false), Op::Const(G::from_u64(256)), Op::Mul(144, 113), Op::Add(112, 145), Op::Const(G::from_u64(65536)), Op::Mul(147, 114), Op::Add(146, 148), Op::Const(G::from_u64(16777216)), Op::Mul(150, 115), Op::Add(149, 151), Op::Mul(144, 117), Op::Add(116, 153), Op::Mul(147, 118), Op::Add(154, 155), Op::Mul(150, 119), Op::Add(156, 157), Op::Mul(144, 121), Op::Add(120, 159), Op::Mul(147, 122), Op::Add(160, 161), Op::Mul(150, 123), Op::Add(162, 163), Op::Mul(144, 125), Op::Add(124, 165), Op::Mul(147, 126), Op::Add(166, 167), Op::Mul(150, 127), Op::Add(168, 169), Op::Mul(144, 129), Op::Add(128, 171), Op::Mul(147, 130), Op::Add(172, 173), Op::Mul(150, 131), Op::Add(174, 175), Op::Mul(144, 133), Op::Add(132, 177), Op::Mul(147, 134), Op::Add(178, 179), Op::Mul(150, 135), Op::Add(180, 181), Op::Mul(144, 137), Op::Add(136, 183), Op::Mul(147, 138), Op::Add(184, 185), Op::Mul(150, 139), Op::Add(186, 187), Op::Mul(144, 141), Op::Add(140, 189), Op::Mul(147, 142), Op::Add(190, 191), Op::Mul(150, 143), Op::Add(192, 193), Op::AssertEq(vec![152, 158, 164, 170, 176, 182, 188, 194], vec![28, 29, 30, 31, 32, 33, 34, 35], Some("aggr structural: output claim digest mismatch".into())), Op::Call(268, vec![75], 8, false), Op::Call(272, vec![55, 56, 57, 58, 59, 64, 65, 66, 67, 68], 0, false), Op::Call(272, vec![55, 56, 57, 58, 59, 195, 196, 197, 198, 199], 0, false), Op::Call(247, vec![60, 69], 1, false), Op::Call(2, vec![200, 203], 1, false), Op::AssertEq(vec![204], vec![45], Some("aggr structural: output subject root is not nodeHash(left, right)".into())), Op::Call(258, vec![61, 62], 1, false), Op::Call(258, vec![70, 71], 1, false), Op::Call(258, vec![201, 202], 1, false), Op::Call(266, vec![205, 206, 200, 207], 0, false)], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 36, selectors: 1, auxiliaries: 88, lookups: 23 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(282, vec![0, 35, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26], 8, false), Op::Call(278, vec![36, 37, 38, 39, 40, 41, 42, 43, 27, 28, 29, 30, 31, 32, 33, 34], 0, false)], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 35, selectors: 1, auxiliaries: 9, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_284() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(280, vec![0, 35, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26], 8, false), Op::Const(G::from_u64(1)), Op::Call(280, vec![35, 44, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26], 8, false), Op::Call(273, vec![36, 37, 38, 39, 40, 41, 42, 43], 1, false), Op::Call(268, vec![53], 8, false), Op::Const(G::from_u64(4)), Op::AssertEq(vec![58], vec![62], Some("aggr discharge: the claim does not assume its binding".into())), Op::Call(273, vec![45, 46, 47, 48, 49, 50, 51, 52], 1, false), Op::Call(269, vec![63], 2, false), Op::Call(2, vec![54, 64], 1, false), Op::AssertEq(vec![66], vec![44], Some("aggr discharge: the binding claim is for another specification digest".into())), Op::Call(2, vec![55, 65], 1, false), Op::AssertEq(vec![67], vec![44], Some("aggr discharge: the binding claim is for another binding".into())), Op::Const(G::from_u64(2)), Op::IOGetInfo(68, vec![68]), Op::Call(3, vec![68, 69, 70], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Store(vec![44, 44, 44]), Op::Store(vec![35, 35, 35, 35, 35, 35, 35, 35]), Op::Store(vec![72, 73, 74, 75, 76, 77, 72, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102]), Op::Store(vec![44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44]), Op::Call(78, vec![71, 103, 35, 35, 104, 105, 106], 1, false), Op::Call(77, vec![107], 32, false), Op::Const(G::from_u64(256)), Op::Mul(140, 109), Op::Add(108, 141), Op::Const(G::from_u64(65536)), Op::Mul(143, 110), Op::Add(142, 144), Op::Const(G::from_u64(16777216)), Op::Mul(146, 111), Op::Add(145, 147), Op::Mul(140, 113), Op::Add(112, 149), Op::Mul(143, 114), Op::Add(150, 151), Op::Mul(146, 115), Op::Add(152, 153), Op::Mul(140, 117), Op::Add(116, 155), Op::Mul(143, 118), Op::Add(156, 157), Op::Mul(146, 119), Op::Add(158, 159), Op::Mul(140, 121), Op::Add(120, 161), Op::Mul(143, 122), Op::Add(162, 163), Op::Mul(146, 123), Op::Add(164, 165), Op::Mul(140, 125), Op::Add(124, 167), Op::Mul(143, 126), Op::Add(168, 169), Op::Mul(146, 127), Op::Add(170, 171), Op::Mul(140, 129), Op::Add(128, 173), Op::Mul(143, 130), Op::Add(174, 175), Op::Mul(146, 131), Op::Add(176, 177), Op::Mul(140, 133), Op::Add(132, 179), Op::Mul(143, 134), Op::Add(180, 181), Op::Mul(146, 135), Op::Add(182, 183), Op::Mul(140, 137), Op::Add(136, 185), Op::Mul(143, 138), Op::Add(186, 187), Op::Mul(146, 139), Op::Add(188, 189), Op::AssertEq(vec![148, 154, 160, 166, 172, 178, 184, 190], vec![27, 28, 29, 30, 31, 32, 33, 34], Some("aggr discharge: output claim digest mismatch".into())), Op::Call(268, vec![71], 8, false), Op::Const(G::from_u64(3)), Op::Call(272, vec![54, 55, 56, 57, 199, 191, 192, 193, 194, 195], 0, false), Op::Call(2, vec![59, 196], 1, false), Op::AssertEq(vec![200], vec![44], Some("aggr discharge: output subject root differs from the child's".into())), Op::Call(271, vec![60, 61, 197, 198], 1, false), Op::AssertEq(vec![201], vec![44], Some("aggr discharge: output assumptions differ from the child's".into()))], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 35, selectors: 1, auxiliaries: 81, lookups: 20 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(282, vec![0, 36, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], 8, false), Op::Const(G::from_u64(1)), Op::Call(282, vec![1, 45, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], 8, false), Op::Call(275, vec![37, 38, 39, 40, 41, 42, 43, 44], 1, false), Op::Call(270, vec![54], 8, false), Op::Call(275, vec![46, 47, 48, 49, 50, 51, 52, 53], 1, false), Op::Call(270, vec![63], 8, false), Op::Const(G::from_u64(2)), Op::IOGetInfo(72, vec![72]), Op::Call(3, vec![72, 73, 74], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Store(vec![45, 45, 45]), Op::Store(vec![36, 36, 36, 36, 36, 36, 36, 36]), Op::Store(vec![76, 77, 78, 79, 80, 81, 76, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106]), Op::Store(vec![45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45]), Op::Call(78, vec![75, 107, 36, 36, 108, 109, 110], 1, false), Op::Call(77, vec![111], 32, false), Op::Const(G::from_u64(256)), Op::Mul(144, 113), Op::Add(112, 145), Op::Const(G::from_u64(65536)), Op::Mul(147, 114), Op::Add(146, 148), Op::Const(G::from_u64(16777216)), Op::Mul(150, 115), Op::Add(149, 151), Op::Mul(144, 117), Op::Add(116, 153), Op::Mul(147, 118), Op::Add(154, 155), Op::Mul(150, 119), Op::Add(156, 157), Op::Mul(144, 121), Op::Add(120, 159), Op::Mul(147, 122), Op::Add(160, 161), Op::Mul(150, 123), Op::Add(162, 163), Op::Mul(144, 125), Op::Add(124, 165), Op::Mul(147, 126), Op::Add(166, 167), Op::Mul(150, 127), Op::Add(168, 169), Op::Mul(144, 129), Op::Add(128, 171), Op::Mul(147, 130), Op::Add(172, 173), Op::Mul(150, 131), Op::Add(174, 175), Op::Mul(144, 133), Op::Add(132, 177), Op::Mul(147, 134), Op::Add(178, 179), Op::Mul(150, 135), Op::Add(180, 181), Op::Mul(144, 137), Op::Add(136, 183), Op::Mul(147, 138), Op::Add(184, 185), Op::Mul(150, 139), Op::Add(186, 187), Op::Mul(144, 141), Op::Add(140, 189), Op::Mul(147, 142), Op::Add(190, 191), Op::Mul(150, 143), Op::Add(192, 193), Op::AssertEq(vec![152, 158, 164, 170, 176, 182, 188, 194], vec![28, 29, 30, 31, 32, 33, 34, 35], Some("aggr: output claim digest mismatch".into())), Op::Call(270, vec![75], 8, false), Op::Call(274, vec![55, 56, 57, 58, 59, 64, 65, 66, 67, 68], 0, false), Op::Call(274, vec![55, 56, 57, 58, 59, 195, 196, 197, 198, 199], 0, false), Op::Call(257, vec![60], 1, false), Op::Call(257, vec![69], 1, false), Op::Call(258, vec![61, 62], 1, false), Op::Call(258, vec![70, 71], 1, false), Op::Call(257, vec![200], 1, false), Op::Call(258, vec![201, 202], 1, false), Op::Call(262, vec![203, 205], 0, false), Op::Call(262, vec![204, 206], 0, false), Op::Call(261, vec![203, 204, 207], 0, false), Op::Call(265, vec![205, 206, 207, 208], 0, false)], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 36, selectors: 1, auxiliaries: 89, lookups: 27 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_285() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(0, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(0, vec![1]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(1, vec![1]) }), (G::from_u64(2), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(2, vec![1]) }), (G::from_u64(29), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(3, vec![1]) }), (G::from_u64(30), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(4, vec![1]) }), (G::from_u64(31), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(5, vec![1]) }), (G::from_u64(37), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(6, vec![1]) }), (G::from_u64(38), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(7, vec![1]) }), (G::from_u64(39), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(8, vec![1]) }), (G::from_u64(40), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(9, vec![1]) }), (G::from_u64(56), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(10, vec![1]) }), (G::from_u64(57), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(11, vec![1]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(12, vec![1]) }))) }, layout: FunctionLayout { input_size: 1, selectors: 13, auxiliaries: 13, lookups: 1 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(282, vec![0, 36, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], 8, false), Op::Const(G::from_u64(1)), Op::Call(282, vec![1, 45, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], 8, false), Op::Call(275, vec![37, 38, 39, 40, 41, 42, 43, 44], 1, false), Op::Call(270, vec![54], 8, false), Op::Call(275, vec![46, 47, 48, 49, 50, 51, 52, 53], 1, false), Op::Call(270, vec![63], 8, false), Op::Const(G::from_u64(2)), Op::IOGetInfo(72, vec![72]), Op::Call(3, vec![72, 73, 74], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Store(vec![45, 45, 45]), Op::Store(vec![36, 36, 36, 36, 36, 36, 36, 36]), Op::Store(vec![76, 77, 78, 79, 80, 81, 76, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106]), Op::Store(vec![45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45]), Op::Call(78, vec![75, 107, 36, 36, 108, 109, 110], 1, false), Op::Call(77, vec![111], 32, false), Op::Const(G::from_u64(256)), Op::Mul(144, 113), Op::Add(112, 145), Op::Const(G::from_u64(65536)), Op::Mul(147, 114), Op::Add(146, 148), Op::Const(G::from_u64(16777216)), Op::Mul(150, 115), Op::Add(149, 151), Op::Mul(144, 117), Op::Add(116, 153), Op::Mul(147, 118), Op::Add(154, 155), Op::Mul(150, 119), Op::Add(156, 157), Op::Mul(144, 121), Op::Add(120, 159), Op::Mul(147, 122), Op::Add(160, 161), Op::Mul(150, 123), Op::Add(162, 163), Op::Mul(144, 125), Op::Add(124, 165), Op::Mul(147, 126), Op::Add(166, 167), Op::Mul(150, 127), Op::Add(168, 169), Op::Mul(144, 129), Op::Add(128, 171), Op::Mul(147, 130), Op::Add(172, 173), Op::Mul(150, 131), Op::Add(174, 175), Op::Mul(144, 133), Op::Add(132, 177), Op::Mul(147, 134), Op::Add(178, 179), Op::Mul(150, 135), Op::Add(180, 181), Op::Mul(144, 137), Op::Add(136, 183), Op::Mul(147, 138), Op::Add(184, 185), Op::Mul(150, 139), Op::Add(186, 187), Op::Mul(144, 141), Op::Add(140, 189), Op::Mul(147, 142), Op::Add(190, 191), Op::Mul(150, 143), Op::Add(192, 193), Op::AssertEq(vec![152, 158, 164, 170, 176, 182, 188, 194], vec![28, 29, 30, 31, 32, 33, 34, 35], Some("aggr structural: output claim digest mismatch".into())), Op::Call(270, vec![75], 8, false), Op::Call(274, vec![55, 56, 57, 58, 59, 64, 65, 66, 67, 68], 0, false), Op::Call(274, vec![55, 56, 57, 58, 59, 195, 196, 197, 198, 199], 0, false), Op::Call(2, vec![60, 69], 1, false), Op::AssertEq(vec![203], vec![36], Some("aggr structural: child subject roots must be distinct".into())), Op::Call(247, vec![60, 69], 1, false), Op::Call(2, vec![200, 204], 1, false), Op::AssertEq(vec![205], vec![45], Some("aggr structural: output subject root is not nodeHash(left, right)".into())), Op::Call(258, vec![61, 62], 1, false), Op::Call(258, vec![70, 71], 1, false), Op::Call(258, vec![201, 202], 1, false), Op::Call(268, vec![206, 207, 60, 69, 208], 0, false)], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 36, selectors: 1, auxiliaries: 89, lookups: 24 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_286() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(2, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![3, 3, 3])], ctrl: Ctrl::Return(0, vec![4]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(90, vec![0], 2, false), Op::AssertEq(vec![3], vec![1], Some("aggr cross-binding: the binding does not bind every role in order".into())), Op::Call(244, vec![4], 2, false), Op::Const(G::from_u64(1)), Op::Add(1, 7), Op::Sub(2, 7), Op::Call(286, vec![6, 8, 9], 1, false), Op::Call(285, vec![1], 1, false)], ctrl: Ctrl::Match(11, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Store(vec![12, 5, 10])], ctrl: Ctrl::Return(1, vec![13]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::Return(2, vec![10]) }))) }))) }, layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 9, lookups: 6 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(282, vec![0, 35, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26], 8, false), Op::Const(G::from_u64(1)), Op::Call(282, vec![35, 44, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26], 8, false), Op::Call(275, vec![36, 37, 38, 39, 40, 41, 42, 43], 1, false), Op::Call(270, vec![53], 8, false), Op::Const(G::from_u64(4)), Op::AssertEq(vec![58], vec![62], Some("aggr discharge: the claim does not assume its binding".into())), Op::Call(275, vec![45, 46, 47, 48, 49, 50, 51, 52], 1, false), Op::Call(271, vec![63], 2, false), Op::Call(2, vec![54, 64], 1, false), Op::AssertEq(vec![66], vec![44], Some("aggr discharge: the binding claim is for another specification digest".into())), Op::Call(2, vec![55, 65], 1, false), Op::AssertEq(vec![67], vec![44], Some("aggr discharge: the binding claim is for another binding".into())), Op::Const(G::from_u64(2)), Op::IOGetInfo(68, vec![68]), Op::Call(3, vec![68, 69, 70], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Store(vec![44, 44, 44]), Op::Store(vec![35, 35, 35, 35, 35, 35, 35, 35]), Op::Store(vec![72, 73, 74, 75, 76, 77, 72, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102]), Op::Store(vec![44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44]), Op::Call(78, vec![71, 103, 35, 35, 104, 105, 106], 1, false), Op::Call(77, vec![107], 32, false), Op::Const(G::from_u64(256)), Op::Mul(140, 109), Op::Add(108, 141), Op::Const(G::from_u64(65536)), Op::Mul(143, 110), Op::Add(142, 144), Op::Const(G::from_u64(16777216)), Op::Mul(146, 111), Op::Add(145, 147), Op::Mul(140, 113), Op::Add(112, 149), Op::Mul(143, 114), Op::Add(150, 151), Op::Mul(146, 115), Op::Add(152, 153), Op::Mul(140, 117), Op::Add(116, 155), Op::Mul(143, 118), Op::Add(156, 157), Op::Mul(146, 119), Op::Add(158, 159), Op::Mul(140, 121), Op::Add(120, 161), Op::Mul(143, 122), Op::Add(162, 163), Op::Mul(146, 123), Op::Add(164, 165), Op::Mul(140, 125), Op::Add(124, 167), Op::Mul(143, 126), Op::Add(168, 169), Op::Mul(146, 127), Op::Add(170, 171), Op::Mul(140, 129), Op::Add(128, 173), Op::Mul(143, 130), Op::Add(174, 175), Op::Mul(146, 131), Op::Add(176, 177), Op::Mul(140, 133), Op::Add(132, 179), Op::Mul(143, 134), Op::Add(180, 181), Op::Mul(146, 135), Op::Add(182, 183), Op::Mul(140, 137), Op::Add(136, 185), Op::Mul(143, 138), Op::Add(186, 187), Op::Mul(146, 139), Op::Add(188, 189), Op::AssertEq(vec![148, 154, 160, 166, 172, 178, 184, 190], vec![27, 28, 29, 30, 31, 32, 33, 34], Some("aggr discharge: output claim digest mismatch".into())), Op::Call(270, vec![71], 8, false), Op::Const(G::from_u64(3)), Op::Call(274, vec![54, 55, 56, 57, 199, 191, 192, 193, 194, 195], 0, false), Op::Call(2, vec![59, 196], 1, false), Op::AssertEq(vec![200], vec![44], Some("aggr discharge: output subject root differs from the child's".into())), Op::Call(273, vec![60, 61, 197, 198], 1, false), Op::AssertEq(vec![201], vec![44], Some("aggr discharge: output assumptions differ from the child's".into()))], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 35, selectors: 1, auxiliaries: 81, lookups: 20 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_287() -> Function { - Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![1]) }), (G::from_u64(0), Block { ops: vec![Op::Call(287, vec![4, 1], 1, false), Op::Call(245, vec![3, 5], 1, false)], ctrl: Ctrl::Return(1, vec![6]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(0, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(0, vec![1]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(1, vec![1]) }), (G::from_u64(2), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(2, vec![1]) }), (G::from_u64(29), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(3, vec![1]) }), (G::from_u64(30), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(4, vec![1]) }), (G::from_u64(31), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(5, vec![1]) }), (G::from_u64(37), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(6, vec![1]) }), (G::from_u64(38), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(7, vec![1]) }), (G::from_u64(39), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(8, vec![1]) }), (G::from_u64(40), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(9, vec![1]) }), (G::from_u64(56), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(10, vec![1]) }), (G::from_u64(57), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(11, vec![1]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(12, vec![1]) }))) }, layout: FunctionLayout { input_size: 1, selectors: 13, auxiliaries: 13, lookups: 1 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_288() -> Function { - Function { body: Block { ops: vec![Op::Call(246, vec![0], 8, false), Op::Call(273, vec![1, 2, 3, 4, 5, 6, 7, 8], 1, false), Op::Call(90, vec![9], 2, false), Op::Const(G::from_u64(232)), Op::AssertEq(vec![10], vec![12], Some("aggr cross-binding: not a role binding section".into())), Op::Call(90, vec![11], 2, false), Op::Const(G::from_u64(3)), Op::AssertEq(vec![13], vec![15], Some("aggr cross-binding: not a role binding section".into())), Op::Call(90, vec![14], 2, false), Op::Call(90, vec![17], 2, false), Op::Const(G::from_u64(103)), Op::AssertEq(vec![18], vec![20], Some("aggr cross-binding: the binding does not bind every role".into())), Op::Const(G::from_u64(0)), Op::Call(286, vec![19, 21, 20], 1, false), Op::Const(G::from_u64(1)), Op::Store(vec![23, 23, 23]), Op::Call(287, vec![22, 24], 1, false), Op::Const(G::from_u64(4)), Op::Store(vec![21, 26, 25]), Op::Store(vec![21, 12, 27]), Op::Call(75, vec![28], 1, false)], ctrl: Ctrl::Return(0, vec![29]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 24, lookups: 13 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(2, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![3, 3, 3])], ctrl: Ctrl::Return(0, vec![4]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(90, vec![0], 2, false), Op::AssertEq(vec![3], vec![1], Some("aggr cross-binding: the binding does not bind every role in order".into())), Op::Call(244, vec![4], 2, false), Op::Const(G::from_u64(1)), Op::Add(1, 7), Op::Sub(2, 7), Op::Call(288, vec![6, 8, 9], 1, false), Op::Call(287, vec![1], 1, false)], ctrl: Ctrl::Match(11, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Store(vec![12, 5, 10])], ctrl: Ctrl::Return(1, vec![13]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::Return(2, vec![10]) }))) }))) }, layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 9, lookups: 6 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_289() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(3)), Op::Sub(4, 5), Op::Const(G::from_u64(5)), Op::Sub(4, 7), Op::Mul(6, 8), Op::Const(G::from_u64(0)), Op::AssertEq(vec![9], vec![10], Some("aggr cross-binding: the claim assumes its binding".into()))], ctrl: Ctrl::Match(4, [(G::from_u64(5), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![1]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(288, vec![1], 1, false)], ctrl: Ctrl::Return(1, vec![11]) }))) }, layout: FunctionLayout { input_size: 5, selectors: 2, auxiliaries: 4, lookups: 2 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![1]) }), (G::from_u64(0), Block { ops: vec![Op::Call(289, vec![4, 1], 1, false), Op::Call(245, vec![3, 5], 1, false)], ctrl: Ctrl::Return(1, vec![6]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_290() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::Const(G::from_u64(0)), Op::Call(280, vec![34, 35, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], 8, false), Op::Call(280, vec![34, 34, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], 8, false), Op::Call(273, vec![36, 37, 38, 39, 40, 41, 42, 43], 1, false), Op::Call(268, vec![52], 8, false), Op::Call(273, vec![44, 45, 46, 47, 48, 49, 50, 51], 1, false), Op::Call(268, vec![61], 8, false), Op::Call(289, vec![53, 54, 55, 56, 57], 1, false), Op::Call(289, vec![62, 63, 64, 65, 66], 1, false), Op::Call(2, vec![70, 71], 1, false), Op::AssertEq(vec![72], vec![34], Some("aggr cross-binding: the bindings' literal classes differ".into())), Op::Const(G::from_u64(2)), Op::IOGetInfo(73, vec![73]), Op::Call(3, vec![73, 74, 75], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Store(vec![34, 34, 34]), Op::Store(vec![35, 35, 35, 35, 35, 35, 35, 35]), Op::Store(vec![77, 78, 79, 80, 81, 82, 77, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107]), Op::Store(vec![34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34]), Op::Call(78, vec![76, 108, 35, 35, 109, 110, 111], 1, false), Op::Call(77, vec![112], 32, false), Op::Const(G::from_u64(256)), Op::Mul(145, 114), Op::Add(113, 146), Op::Const(G::from_u64(65536)), Op::Mul(148, 115), Op::Add(147, 149), Op::Const(G::from_u64(16777216)), Op::Mul(151, 116), Op::Add(150, 152), Op::Mul(145, 118), Op::Add(117, 154), Op::Mul(148, 119), Op::Add(155, 156), Op::Mul(151, 120), Op::Add(157, 158), Op::Mul(145, 122), Op::Add(121, 160), Op::Mul(148, 123), Op::Add(161, 162), Op::Mul(151, 124), Op::Add(163, 164), Op::Mul(145, 126), Op::Add(125, 166), Op::Mul(148, 127), Op::Add(167, 168), Op::Mul(151, 128), Op::Add(169, 170), Op::Mul(145, 130), Op::Add(129, 172), Op::Mul(148, 131), Op::Add(173, 174), Op::Mul(151, 132), Op::Add(175, 176), Op::Mul(145, 134), Op::Add(133, 178), Op::Mul(148, 135), Op::Add(179, 180), Op::Mul(151, 136), Op::Add(181, 182), Op::Mul(145, 138), Op::Add(137, 184), Op::Mul(148, 139), Op::Add(185, 186), Op::Mul(151, 140), Op::Add(187, 188), Op::Mul(145, 142), Op::Add(141, 190), Op::Mul(148, 143), Op::Add(191, 192), Op::Mul(151, 144), Op::Add(193, 194), Op::AssertEq(vec![153, 159, 165, 171, 177, 183, 189, 195], vec![26, 27, 28, 29, 30, 31, 32, 33], Some("aggr cross-binding: output claim digest mismatch".into())), Op::Call(268, vec![76], 8, false), Op::Call(272, vec![53, 70, 55, 56, 98, 196, 197, 198, 199, 200], 0, false), Op::Call(272, vec![62, 71, 64, 65, 98, 196, 197, 198, 199, 200], 0, false), Op::Call(247, vec![58, 67], 1, false), Op::Call(2, vec![201, 204], 1, false), Op::AssertEq(vec![205], vec![34], Some("aggr cross-binding: output subject root is not nodeHash(left, right)".into())), Op::Call(258, vec![59, 60], 1, false), Op::Call(258, vec![68, 69], 1, false), Op::Call(258, vec![202, 203], 1, false), Op::Call(266, vec![206, 207, 201, 208], 0, false)], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 34, selectors: 1, auxiliaries: 91, lookups: 26 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(246, vec![0], 8, false), Op::Call(275, vec![1, 2, 3, 4, 5, 6, 7, 8], 1, false), Op::Call(90, vec![9], 2, false), Op::Const(G::from_u64(232)), Op::AssertEq(vec![10], vec![12], Some("aggr cross-binding: not a role binding section".into())), Op::Call(90, vec![11], 2, false), Op::Const(G::from_u64(3)), Op::AssertEq(vec![13], vec![15], Some("aggr cross-binding: not a role binding section".into())), Op::Call(90, vec![14], 2, false), Op::Call(90, vec![17], 2, false), Op::Const(G::from_u64(103)), Op::AssertEq(vec![18], vec![20], Some("aggr cross-binding: the binding does not bind every role".into())), Op::Const(G::from_u64(0)), Op::Call(288, vec![19, 21, 20], 1, false), Op::Const(G::from_u64(1)), Op::Store(vec![23, 23, 23]), Op::Call(289, vec![22, 24], 1, false), Op::Const(G::from_u64(4)), Op::Store(vec![21, 26, 25]), Op::Store(vec![21, 12, 27]), Op::Call(75, vec![28], 1, false)], ctrl: Ctrl::Return(0, vec![29]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 24, lookups: 13 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_291() -> Function { - Function { body: Block { ops: vec![Op::Call(273, vec![0, 1, 2, 3, 4, 5, 6, 7], 1, false), Op::Call(90, vec![8], 2, false)], ctrl: Ctrl::Match(9, [(G::from_u64(229), Block { ops: vec![Op::Call(268, vec![8], 8, false), Op::Const(G::from_u64(3)), Op::Sub(15, 19), Op::Const(G::from_u64(5)), Op::Sub(15, 21), Op::Mul(20, 22), Op::Const(G::from_u64(0)), Op::AssertEq(vec![23], vec![24], Some("aggr catalog: a member claim assumes its binding".into())), Op::Const(G::from_u64(1)), Op::Call(271, vec![17, 18, 25, 25], 1, false), Op::AssertEq(vec![26], vec![25], Some("aggr catalog: a member claim has assumptions".into())), Op::Call(75, vec![8], 1, false), Op::Store(vec![25, 25, 25]), Op::Store(vec![24, 27, 28])], ctrl: Ctrl::Return(0, vec![29]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(270, vec![8], 1, false), Op::Call(257, vec![11], 1, false)], ctrl: Ctrl::Return(1, vec![12]) }))) }, layout: FunctionLayout { input_size: 8, selectors: 2, auxiliaries: 17, lookups: 8 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(3)), Op::Sub(4, 5), Op::Const(G::from_u64(5)), Op::Sub(4, 7), Op::Mul(6, 8), Op::Const(G::from_u64(0)), Op::AssertEq(vec![9], vec![10], Some("aggr cross-binding: the claim assumes its binding".into()))], ctrl: Ctrl::Match(4, [(G::from_u64(5), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![1]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(290, vec![1], 1, false)], ctrl: Ctrl::Return(1, vec![11]) }))) }, layout: FunctionLayout { input_size: 5, selectors: 2, auxiliaries: 4, lookups: 2 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_292() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::Const(G::from_u64(0)), Op::Call(280, vec![34, 35, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], 8, false), Op::Call(280, vec![34, 34, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], 8, false), Op::Call(291, vec![36, 37, 38, 39, 40, 41, 42, 43], 1, false), Op::Call(291, vec![44, 45, 46, 47, 48, 49, 50, 51], 1, false), Op::Const(G::from_u64(2)), Op::IOGetInfo(54, vec![54]), Op::Call(3, vec![54, 55, 56], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Store(vec![34, 34, 34]), Op::Store(vec![35, 35, 35, 35, 35, 35, 35, 35]), Op::Store(vec![58, 59, 60, 61, 62, 63, 58, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88]), Op::Store(vec![34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34]), Op::Call(78, vec![57, 89, 35, 35, 90, 91, 92], 1, false), Op::Call(77, vec![93], 32, false), Op::Const(G::from_u64(256)), Op::Mul(126, 95), Op::Add(94, 127), Op::Const(G::from_u64(65536)), Op::Mul(129, 96), Op::Add(128, 130), Op::Const(G::from_u64(16777216)), Op::Mul(132, 97), Op::Add(131, 133), Op::Mul(126, 99), Op::Add(98, 135), Op::Mul(129, 100), Op::Add(136, 137), Op::Mul(132, 101), Op::Add(138, 139), Op::Mul(126, 103), Op::Add(102, 141), Op::Mul(129, 104), Op::Add(142, 143), Op::Mul(132, 105), Op::Add(144, 145), Op::Mul(126, 107), Op::Add(106, 147), Op::Mul(129, 108), Op::Add(148, 149), Op::Mul(132, 109), Op::Add(150, 151), Op::Mul(126, 111), Op::Add(110, 153), Op::Mul(129, 112), Op::Add(154, 155), Op::Mul(132, 113), Op::Add(156, 157), Op::Mul(126, 115), Op::Add(114, 159), Op::Mul(129, 116), Op::Add(160, 161), Op::Mul(132, 117), Op::Add(162, 163), Op::Mul(126, 119), Op::Add(118, 165), Op::Mul(129, 120), Op::Add(166, 167), Op::Mul(132, 121), Op::Add(168, 169), Op::Mul(126, 123), Op::Add(122, 171), Op::Mul(129, 124), Op::Add(172, 173), Op::Mul(132, 125), Op::Add(174, 175), Op::AssertEq(vec![134, 140, 146, 152, 158, 164, 170, 176], vec![26, 27, 28, 29, 30, 31, 32, 33], Some("aggr catalog: output claim digest mismatch".into())), Op::Call(270, vec![57], 1, false), Op::Call(257, vec![177], 1, false), Op::Call(260, vec![52, 53, 178], 0, false)], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 34, selectors: 1, auxiliaries: 61, lookups: 15 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::Const(G::from_u64(0)), Op::Call(282, vec![34, 35, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], 8, false), Op::Call(282, vec![34, 34, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], 8, false), Op::Call(275, vec![36, 37, 38, 39, 40, 41, 42, 43], 1, false), Op::Call(270, vec![52], 8, false), Op::Call(275, vec![44, 45, 46, 47, 48, 49, 50, 51], 1, false), Op::Call(270, vec![61], 8, false), Op::Call(291, vec![53, 54, 55, 56, 57], 1, false), Op::Call(291, vec![62, 63, 64, 65, 66], 1, false), Op::Call(2, vec![70, 71], 1, false), Op::AssertEq(vec![72], vec![34], Some("aggr cross-binding: the bindings' literal classes differ".into())), Op::Const(G::from_u64(2)), Op::IOGetInfo(73, vec![73]), Op::Call(3, vec![73, 74, 75], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Store(vec![34, 34, 34]), Op::Store(vec![35, 35, 35, 35, 35, 35, 35, 35]), Op::Store(vec![77, 78, 79, 80, 81, 82, 77, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107]), Op::Store(vec![34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34]), Op::Call(78, vec![76, 108, 35, 35, 109, 110, 111], 1, false), Op::Call(77, vec![112], 32, false), Op::Const(G::from_u64(256)), Op::Mul(145, 114), Op::Add(113, 146), Op::Const(G::from_u64(65536)), Op::Mul(148, 115), Op::Add(147, 149), Op::Const(G::from_u64(16777216)), Op::Mul(151, 116), Op::Add(150, 152), Op::Mul(145, 118), Op::Add(117, 154), Op::Mul(148, 119), Op::Add(155, 156), Op::Mul(151, 120), Op::Add(157, 158), Op::Mul(145, 122), Op::Add(121, 160), Op::Mul(148, 123), Op::Add(161, 162), Op::Mul(151, 124), Op::Add(163, 164), Op::Mul(145, 126), Op::Add(125, 166), Op::Mul(148, 127), Op::Add(167, 168), Op::Mul(151, 128), Op::Add(169, 170), Op::Mul(145, 130), Op::Add(129, 172), Op::Mul(148, 131), Op::Add(173, 174), Op::Mul(151, 132), Op::Add(175, 176), Op::Mul(145, 134), Op::Add(133, 178), Op::Mul(148, 135), Op::Add(179, 180), Op::Mul(151, 136), Op::Add(181, 182), Op::Mul(145, 138), Op::Add(137, 184), Op::Mul(148, 139), Op::Add(185, 186), Op::Mul(151, 140), Op::Add(187, 188), Op::Mul(145, 142), Op::Add(141, 190), Op::Mul(148, 143), Op::Add(191, 192), Op::Mul(151, 144), Op::Add(193, 194), Op::AssertEq(vec![153, 159, 165, 171, 177, 183, 189, 195], vec![26, 27, 28, 29, 30, 31, 32, 33], Some("aggr cross-binding: output claim digest mismatch".into())), Op::Call(270, vec![76], 8, false), Op::Call(274, vec![53, 70, 55, 56, 98, 196, 197, 198, 199, 200], 0, false), Op::Call(274, vec![62, 71, 64, 65, 98, 196, 197, 198, 199, 200], 0, false), Op::Call(2, vec![58, 67], 1, false), Op::AssertEq(vec![204], vec![35], Some("aggr cross-binding: child subject roots must be distinct".into())), Op::Call(247, vec![58, 67], 1, false), Op::Call(2, vec![201, 205], 1, false), Op::AssertEq(vec![206], vec![34], Some("aggr cross-binding: output subject root is not nodeHash(left, right)".into())), Op::Call(258, vec![59, 60], 1, false), Op::Call(258, vec![68, 69], 1, false), Op::Call(258, vec![202, 203], 1, false), Op::Call(268, vec![207, 208, 58, 67, 209], 0, false)], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 34, selectors: 1, auxiliaries: 92, lookups: 27 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_293() -> Function { - Function { body: Block { ops: vec![Op::Call(90, vec![0], 2, false), Op::Const(G::from_u64(82)), Op::AssertEq(vec![1], vec![3], Some("aggr range: statement has the wrong tag".into())), Op::Call(244, vec![2], 2, false), Op::Call(26, vec![5], 2, false), Op::Call(26, vec![7], 2, false), Op::Call(26, vec![9], 2, false), Op::Call(26, vec![11], 2, false), Op::Call(26, vec![13], 2, false), Op::Call(26, vec![15], 2, false), Op::Call(26, vec![17], 2, false), Op::Call(26, vec![19], 2, false), Op::Call(26, vec![21], 2, false), Op::Call(26, vec![23], 2, false), Op::Call(26, vec![25], 2, false), Op::Call(26, vec![27], 2, false), Op::Call(26, vec![29], 2, false), Op::Call(26, vec![31], 2, false), Op::Call(26, vec![33], 2, false), Op::Call(26, vec![35], 2, false), Op::Call(26, vec![37], 2, false), Op::Call(26, vec![39], 2, false), Op::Call(26, vec![41], 2, false), Op::Call(26, vec![43], 2, false), Op::Call(26, vec![45], 2, false), Op::Call(26, vec![47], 2, false), Op::Call(26, vec![49], 2, false), Op::Call(26, vec![51], 2, false), Op::Call(26, vec![53], 2, false), Op::Call(26, vec![55], 2, false), Op::Call(26, vec![57], 2, false), Op::Call(26, vec![59], 2, false), Op::Call(26, vec![61], 2, false), Op::Call(26, vec![63], 2, false), Op::Call(26, vec![65], 2, false), Op::Call(26, vec![67], 2, false), Op::Load(3, 69), Op::Const(G::from_u64(1)), Op::AssertEq(vec![70, 71, 72], vec![73, 73, 73], Some("aggr range: trailing bytes after the statement".into())), Op::Const(G::from_u64(256)), Op::Mul(74, 40), Op::Add(38, 75), Op::Const(G::from_u64(65536)), Op::Mul(77, 42), Op::Add(76, 78), Op::Const(G::from_u64(16777216)), Op::Mul(80, 44), Op::Add(79, 81), Op::Const(G::from_u64(4294967296)), Op::Mul(83, 46), Op::Add(82, 84), Op::Const(G::from_u64(1099511627776)), Op::Mul(86, 48), Op::Add(85, 87), Op::Const(G::from_u64(281474976710656)), Op::Mul(89, 50), Op::Add(88, 90), Op::Const(G::from_u64(72057594037927936)), Op::Mul(92, 52), Op::Add(91, 93), Op::Mul(74, 56), Op::Add(54, 95), Op::Mul(77, 58), Op::Add(96, 97), Op::Mul(80, 60), Op::Add(98, 99), Op::Mul(83, 62), Op::Add(100, 101), Op::Mul(86, 64), Op::Add(102, 103), Op::Mul(89, 66), Op::Add(104, 105), Op::Mul(92, 68), Op::Add(106, 107)], ctrl: Ctrl::Return(0, vec![4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 94, 108]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 72, lookups: 36 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(275, vec![0, 1, 2, 3, 4, 5, 6, 7], 1, false), Op::Call(90, vec![8], 2, false)], ctrl: Ctrl::Match(9, [(G::from_u64(229), Block { ops: vec![Op::Call(270, vec![8], 8, false), Op::Const(G::from_u64(3)), Op::Sub(15, 19), Op::Const(G::from_u64(5)), Op::Sub(15, 21), Op::Mul(20, 22), Op::Const(G::from_u64(0)), Op::AssertEq(vec![23], vec![24], Some("aggr catalog: a member claim assumes its binding".into())), Op::Const(G::from_u64(1)), Op::Call(273, vec![17, 18, 25, 25], 1, false), Op::AssertEq(vec![26], vec![25], Some("aggr catalog: a member claim has assumptions".into())), Op::Call(75, vec![8], 1, false), Op::Store(vec![25, 25, 25]), Op::Store(vec![24, 27, 28])], ctrl: Ctrl::Return(0, vec![29]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(272, vec![8], 1, false), Op::Call(257, vec![11], 1, false)], ctrl: Ctrl::Return(1, vec![12]) }))) }, layout: FunctionLayout { input_size: 8, selectors: 2, auxiliaries: 17, lookups: 8 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_294() -> Function { - Function { body: Block { ops: vec![Op::Call(26, vec![0], 2, false), Op::Call(26, vec![2], 2, false), Op::Call(26, vec![4], 2, false), Op::Call(26, vec![6], 2, false), Op::Call(26, vec![8], 2, false), Op::Call(26, vec![10], 2, false), Op::Call(26, vec![12], 2, false), Op::Call(26, vec![14], 2, false), Op::Call(26, vec![16], 2, false), Op::Call(26, vec![18], 2, false), Op::Call(26, vec![20], 2, false), Op::Call(26, vec![22], 2, false), Op::Call(26, vec![24], 2, false), Op::Call(26, vec![26], 2, false), Op::Call(26, vec![28], 2, false), Op::Call(26, vec![30], 2, false), Op::Call(26, vec![32], 2, false), Op::Call(26, vec![34], 2, false), Op::Call(26, vec![36], 2, false), Op::Call(26, vec![38], 2, false), Op::Call(26, vec![40], 2, false), Op::Call(26, vec![42], 2, false), Op::Call(26, vec![44], 2, false), Op::Call(26, vec![46], 2, false), Op::Call(26, vec![48], 2, false), Op::Call(26, vec![50], 2, false), Op::Call(26, vec![52], 2, false), Op::Call(26, vec![54], 2, false), Op::Call(26, vec![56], 2, false), Op::Call(26, vec![58], 2, false), Op::Call(26, vec![60], 2, false), Op::Call(26, vec![62], 2, false), Op::Store(vec![1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63])], ctrl: Ctrl::Return(0, vec![65, 64]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 66, lookups: 34 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::Const(G::from_u64(0)), Op::Call(282, vec![34, 35, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], 8, false), Op::Call(282, vec![34, 34, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], 8, false), Op::Call(293, vec![36, 37, 38, 39, 40, 41, 42, 43], 1, false), Op::Call(293, vec![44, 45, 46, 47, 48, 49, 50, 51], 1, false), Op::Const(G::from_u64(2)), Op::IOGetInfo(54, vec![54]), Op::Call(3, vec![54, 55, 56], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Store(vec![34, 34, 34]), Op::Store(vec![35, 35, 35, 35, 35, 35, 35, 35]), Op::Store(vec![58, 59, 60, 61, 62, 63, 58, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88]), Op::Store(vec![34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34]), Op::Call(78, vec![57, 89, 35, 35, 90, 91, 92], 1, false), Op::Call(77, vec![93], 32, false), Op::Const(G::from_u64(256)), Op::Mul(126, 95), Op::Add(94, 127), Op::Const(G::from_u64(65536)), Op::Mul(129, 96), Op::Add(128, 130), Op::Const(G::from_u64(16777216)), Op::Mul(132, 97), Op::Add(131, 133), Op::Mul(126, 99), Op::Add(98, 135), Op::Mul(129, 100), Op::Add(136, 137), Op::Mul(132, 101), Op::Add(138, 139), Op::Mul(126, 103), Op::Add(102, 141), Op::Mul(129, 104), Op::Add(142, 143), Op::Mul(132, 105), Op::Add(144, 145), Op::Mul(126, 107), Op::Add(106, 147), Op::Mul(129, 108), Op::Add(148, 149), Op::Mul(132, 109), Op::Add(150, 151), Op::Mul(126, 111), Op::Add(110, 153), Op::Mul(129, 112), Op::Add(154, 155), Op::Mul(132, 113), Op::Add(156, 157), Op::Mul(126, 115), Op::Add(114, 159), Op::Mul(129, 116), Op::Add(160, 161), Op::Mul(132, 117), Op::Add(162, 163), Op::Mul(126, 119), Op::Add(118, 165), Op::Mul(129, 120), Op::Add(166, 167), Op::Mul(132, 121), Op::Add(168, 169), Op::Mul(126, 123), Op::Add(122, 171), Op::Mul(129, 124), Op::Add(172, 173), Op::Mul(132, 125), Op::Add(174, 175), Op::AssertEq(vec![134, 140, 146, 152, 158, 164, 170, 176], vec![26, 27, 28, 29, 30, 31, 32, 33], Some("aggr catalog: output claim digest mismatch".into())), Op::Call(272, vec![57], 1, false), Op::Call(257, vec![177], 1, false), Op::Call(260, vec![52, 53, 178], 0, false)], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 34, selectors: 1, auxiliaries: 61, lookups: 15 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_295() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![2, 2, 2])], ctrl: Ctrl::Return(0, vec![3, 0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(294, vec![0], 2, false), Op::Const(G::from_u64(1)), Op::Sub(1, 4), Op::Call(295, vec![3, 5], 2, false), Op::Const(G::from_u64(0)), Op::Store(vec![8, 2, 6])], ctrl: Ctrl::Return(1, vec![9, 7]) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 7, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(90, vec![0], 2, false), Op::Const(G::from_u64(82)), Op::AssertEq(vec![1], vec![3], Some("aggr range: statement has the wrong tag".into())), Op::Call(244, vec![2], 2, false), Op::Call(26, vec![5], 2, false), Op::Call(26, vec![7], 2, false), Op::Call(26, vec![9], 2, false), Op::Call(26, vec![11], 2, false), Op::Call(26, vec![13], 2, false), Op::Call(26, vec![15], 2, false), Op::Call(26, vec![17], 2, false), Op::Call(26, vec![19], 2, false), Op::Call(26, vec![21], 2, false), Op::Call(26, vec![23], 2, false), Op::Call(26, vec![25], 2, false), Op::Call(26, vec![27], 2, false), Op::Call(26, vec![29], 2, false), Op::Call(26, vec![31], 2, false), Op::Call(26, vec![33], 2, false), Op::Call(26, vec![35], 2, false), Op::Call(26, vec![37], 2, false), Op::Call(26, vec![39], 2, false), Op::Call(26, vec![41], 2, false), Op::Call(26, vec![43], 2, false), Op::Call(26, vec![45], 2, false), Op::Call(26, vec![47], 2, false), Op::Call(26, vec![49], 2, false), Op::Call(26, vec![51], 2, false), Op::Call(26, vec![53], 2, false), Op::Call(26, vec![55], 2, false), Op::Call(26, vec![57], 2, false), Op::Call(26, vec![59], 2, false), Op::Call(26, vec![61], 2, false), Op::Call(26, vec![63], 2, false), Op::Call(26, vec![65], 2, false), Op::Call(26, vec![67], 2, false), Op::Load(3, 69), Op::Const(G::from_u64(1)), Op::AssertEq(vec![70, 71, 72], vec![73, 73, 73], Some("aggr range: trailing bytes after the statement".into())), Op::Const(G::from_u64(256)), Op::Mul(74, 40), Op::Add(38, 75), Op::Const(G::from_u64(65536)), Op::Mul(77, 42), Op::Add(76, 78), Op::Const(G::from_u64(16777216)), Op::Mul(80, 44), Op::Add(79, 81), Op::Const(G::from_u64(4294967296)), Op::Mul(83, 46), Op::Add(82, 84), Op::Const(G::from_u64(1099511627776)), Op::Mul(86, 48), Op::Add(85, 87), Op::Const(G::from_u64(281474976710656)), Op::Mul(89, 50), Op::Add(88, 90), Op::Const(G::from_u64(72057594037927936)), Op::Mul(92, 52), Op::Add(91, 93), Op::Mul(74, 56), Op::Add(54, 95), Op::Mul(77, 58), Op::Add(96, 97), Op::Mul(80, 60), Op::Add(98, 99), Op::Mul(83, 62), Op::Add(100, 101), Op::Mul(86, 64), Op::Add(102, 103), Op::Mul(89, 66), Op::Add(104, 105), Op::Mul(92, 68), Op::Add(106, 107)], ctrl: Ctrl::Return(0, vec![4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 94, 108]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 72, lookups: 36 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_296() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![2, 2, 2])], ctrl: Ctrl::Return(0, vec![3, 0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(90, vec![0], 2, false), Op::Const(G::from_u64(1)), Op::Sub(1, 4), Op::Call(296, vec![3, 5], 2, false), Op::Const(G::from_u64(0)), Op::Store(vec![8, 2, 6])], ctrl: Ctrl::Return(1, vec![9, 7]) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 7, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(26, vec![0], 2, false), Op::Call(26, vec![2], 2, false), Op::Call(26, vec![4], 2, false), Op::Call(26, vec![6], 2, false), Op::Call(26, vec![8], 2, false), Op::Call(26, vec![10], 2, false), Op::Call(26, vec![12], 2, false), Op::Call(26, vec![14], 2, false), Op::Call(26, vec![16], 2, false), Op::Call(26, vec![18], 2, false), Op::Call(26, vec![20], 2, false), Op::Call(26, vec![22], 2, false), Op::Call(26, vec![24], 2, false), Op::Call(26, vec![26], 2, false), Op::Call(26, vec![28], 2, false), Op::Call(26, vec![30], 2, false), Op::Call(26, vec![32], 2, false), Op::Call(26, vec![34], 2, false), Op::Call(26, vec![36], 2, false), Op::Call(26, vec![38], 2, false), Op::Call(26, vec![40], 2, false), Op::Call(26, vec![42], 2, false), Op::Call(26, vec![44], 2, false), Op::Call(26, vec![46], 2, false), Op::Call(26, vec![48], 2, false), Op::Call(26, vec![50], 2, false), Op::Call(26, vec![52], 2, false), Op::Call(26, vec![54], 2, false), Op::Call(26, vec![56], 2, false), Op::Call(26, vec![58], 2, false), Op::Call(26, vec![60], 2, false), Op::Call(26, vec![62], 2, false), Op::Store(vec![1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63])], ctrl: Ctrl::Return(0, vec![65, 64]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 66, lookups: 34 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_297() -> Function { - Function { body: Block { ops: vec![Op::Call(27, vec![0], 9, false), Op::Call(14, vec![1, 2, 3, 4, 5, 6, 7, 8], 1, false), Op::Call(296, vec![9, 10], 2, false), Op::Call(27, vec![12], 9, false), Op::Call(14, vec![13, 14, 15, 16, 17, 18, 19, 20], 1, false), Op::Call(295, vec![21, 22], 2, false), Op::Call(27, vec![24], 9, false), Op::Call(14, vec![25, 26, 27, 28, 29, 30, 31, 32], 1, false), Op::Call(296, vec![33, 34], 2, false), Op::Call(28, vec![36], 2, false), Op::Call(241, vec![38, 37], 2, false)], ctrl: Ctrl::Return(0, vec![11, 23, 35, 39, 40]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 41, lookups: 12 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![2, 2, 2])], ctrl: Ctrl::Return(0, vec![3, 0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(296, vec![0], 2, false), Op::Const(G::from_u64(1)), Op::Sub(1, 4), Op::Call(297, vec![3, 5], 2, false), Op::Const(G::from_u64(0)), Op::Store(vec![8, 2, 6])], ctrl: Ctrl::Return(1, vec![9, 7]) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 7, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_298() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![2, 2, 2, 2, 2, 2])], ctrl: Ctrl::Return(0, vec![3, 0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(297, vec![0], 5, false), Op::Const(G::from_u64(1)), Op::Sub(1, 7), Op::Call(298, vec![6, 8], 2, false), Op::Const(G::from_u64(0)), Op::Store(vec![11, 2, 3, 4, 5, 9])], ctrl: Ctrl::Return(1, vec![12, 10]) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 10, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![2, 2, 2])], ctrl: Ctrl::Return(0, vec![3, 0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(90, vec![0], 2, false), Op::Const(G::from_u64(1)), Op::Sub(1, 4), Op::Call(298, vec![3, 5], 2, false), Op::Const(G::from_u64(0)), Op::Store(vec![8, 2, 6])], ctrl: Ctrl::Return(1, vec![9, 7]) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 7, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_299() -> Function { - Function { body: Block { ops: vec![Op::Call(28, vec![0], 2, false), Op::Call(243, vec![2, 1], 2, false), Op::Call(26, vec![4], 2, false), Op::Call(26, vec![6], 2, false), Op::Call(26, vec![8], 2, false), Op::Call(26, vec![10], 2, false), Op::Call(26, vec![12], 2, false), Op::Call(26, vec![14], 2, false), Op::Call(26, vec![16], 2, false), Op::Call(26, vec![18], 2, false)], ctrl: Ctrl::Return(0, vec![3, 5, 7, 9, 11, 13, 15, 17, 19, 20]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 21, lookups: 11 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(27, vec![0], 9, false), Op::Call(14, vec![1, 2, 3, 4, 5, 6, 7, 8], 1, false), Op::Call(298, vec![9, 10], 2, false), Op::Call(27, vec![12], 9, false), Op::Call(14, vec![13, 14, 15, 16, 17, 18, 19, 20], 1, false), Op::Call(297, vec![21, 22], 2, false), Op::Call(27, vec![24], 9, false), Op::Call(14, vec![25, 26, 27, 28, 29, 30, 31, 32], 1, false), Op::Call(298, vec![33, 34], 2, false), Op::Call(28, vec![36], 2, false), Op::Call(241, vec![38, 37], 2, false)], ctrl: Ctrl::Return(0, vec![11, 23, 35, 39, 40]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 41, lookups: 12 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_300() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])], ctrl: Ctrl::Return(0, vec![3, 0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(299, vec![0], 10, false), Op::Const(G::from_u64(1)), Op::Sub(1, 12), Op::Call(300, vec![11, 13], 2, false), Op::Const(G::from_u64(0)), Op::Store(vec![16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 14])], ctrl: Ctrl::Return(1, vec![17, 15]) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 15, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![2, 2, 2, 2, 2, 2])], ctrl: Ctrl::Return(0, vec![3, 0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(299, vec![0], 5, false), Op::Const(G::from_u64(1)), Op::Sub(1, 7), Op::Call(300, vec![6, 8], 2, false), Op::Const(G::from_u64(0)), Op::Store(vec![11, 2, 3, 4, 5, 9])], ctrl: Ctrl::Return(1, vec![12, 10]) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 10, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_301() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::IOGetInfo(2, vec![0]), Op::Call(3, vec![2, 3, 4], 1, false), Op::Call(75, vec![5], 1, false), Op::Call(2, vec![6, 1], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![7], vec![8], Some("aggr range: preamble does not hash to the statement's batch digest".into())), Op::Call(27, vec![5], 9, false), Op::Call(14, vec![9, 10, 11, 12, 13, 14, 15, 16], 1, false), Op::Call(298, vec![17, 18], 2, false), Op::Call(27, vec![20], 9, false), Op::Call(14, vec![21, 22, 23, 24, 25, 26, 27, 28], 1, false), Op::Call(300, vec![29, 30], 2, false), Op::Load(3, 32), Op::AssertEq(vec![33, 34, 35], vec![8, 8, 8], Some("aggr range: trailing bytes after the preamble".into()))], ctrl: Ctrl::Return(0, vec![19, 31]) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 33, lookups: 11 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(28, vec![0], 2, false), Op::Call(243, vec![2, 1], 2, false), Op::Call(26, vec![4], 2, false), Op::Call(26, vec![6], 2, false), Op::Call(26, vec![8], 2, false), Op::Call(26, vec![10], 2, false), Op::Call(26, vec![12], 2, false), Op::Call(26, vec![14], 2, false), Op::Call(26, vec![16], 2, false), Op::Call(26, vec![18], 2, false)], ctrl: Ctrl::Return(0, vec![3, 5, 7, 9, 11, 13, 15, 17, 19, 20]) }, layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 21, lookups: 11 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_302() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(0, [].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::MatchContinue(9, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![14, 14, 14])], ctrl: Ctrl::Yield(0, vec![15]) }), (G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Yield(1, vec![10]) })].into_iter().collect(), None, 1, 1, 1, Box::new(Block { ops: vec![Op::Call(219, vec![7, 14, 12, 13], 5, false)], ctrl: Ctrl::Return(2, vec![15, 16, 17, 18, 19]) })) }))) }, layout: FunctionLayout { input_size: 14, selectors: 3, auxiliaries: 8, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])], ctrl: Ctrl::Return(0, vec![3, 0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(301, vec![0], 10, false), Op::Const(G::from_u64(1)), Op::Sub(1, 12), Op::Call(302, vec![11, 13], 2, false), Op::Const(G::from_u64(0)), Op::Store(vec![16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 14])], ctrl: Ctrl::Return(1, vec![17, 15]) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 15, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_303() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(15, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![21, 22, 13]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Load(6, 12)], ctrl: Ctrl::Match(23, [(G::from_u64(0), Block { ops: vec![Op::Call(62, vec![13], 25, false), Op::Call(235, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24, 25, 26, 27, 14, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 16, 17, 18, 19, 20], 2, false), Op::Const(G::from_u64(1)), Op::Add(14, 56), Op::Sub(15, 56), Op::Add(21, 54), Op::Add(22, 55), Op::Call(303, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 28, 53, 57, 58, 16, 17, 18, 19, 20, 59, 60], 3, false)], ctrl: Ctrl::Return(1, vec![61, 62, 63]) })].into_iter().collect(), None) }))) }, layout: FunctionLayout { input_size: 23, selectors: 2, auxiliaries: 38, lookups: 5 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::IOGetInfo(2, vec![0]), Op::Call(3, vec![2, 3, 4], 1, false), Op::Call(75, vec![5], 1, false), Op::Call(2, vec![6, 1], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![7], vec![8], Some("aggr range: preamble does not hash to the statement's batch digest".into())), Op::Call(27, vec![5], 9, false), Op::Call(14, vec![9, 10, 11, 12, 13, 14, 15, 16], 1, false), Op::Call(300, vec![17, 18], 2, false), Op::Call(27, vec![20], 9, false), Op::Call(14, vec![21, 22, 23, 24, 25, 26, 27, 28], 1, false), Op::Call(302, vec![29, 30], 2, false), Op::Load(3, 32), Op::AssertEq(vec![33, 34, 35], vec![8, 8, 8], Some("aggr range: trailing bytes after the preamble".into()))], ctrl: Ctrl::Return(0, vec![19, 31]) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 33, lookups: 11 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_304() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(2)), Op::IOGetInfo(8, vec![8]), Op::Call(3, vec![8, 9, 10], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Const(G::from_u64(1)), Op::Store(vec![43, 43, 43]), Op::Const(G::from_u64(0)), Op::Store(vec![45, 45, 45, 45, 45, 45, 45, 45]), Op::Store(vec![12, 13, 14, 15, 16, 17, 12, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42]), Op::Store(vec![43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43]), Op::Call(78, vec![11, 44, 45, 45, 46, 47, 48], 1, false), Op::Call(77, vec![49], 32, false), Op::Const(G::from_u64(256)), Op::Mul(82, 51), Op::Add(50, 83), Op::Const(G::from_u64(65536)), Op::Mul(85, 52), Op::Add(84, 86), Op::Const(G::from_u64(16777216)), Op::Mul(88, 53), Op::Add(87, 89), Op::Mul(82, 55), Op::Add(54, 91), Op::Mul(85, 56), Op::Add(92, 93), Op::Mul(88, 57), Op::Add(94, 95), Op::Mul(82, 59), Op::Add(58, 97), Op::Mul(85, 60), Op::Add(98, 99), Op::Mul(88, 61), Op::Add(100, 101), Op::Mul(82, 63), Op::Add(62, 103), Op::Mul(85, 64), Op::Add(104, 105), Op::Mul(88, 65), Op::Add(106, 107), Op::Mul(82, 67), Op::Add(66, 109), Op::Mul(85, 68), Op::Add(110, 111), Op::Mul(88, 69), Op::Add(112, 113), Op::Mul(82, 71), Op::Add(70, 115), Op::Mul(85, 72), Op::Add(116, 117), Op::Mul(88, 73), Op::Add(118, 119), Op::Mul(82, 75), Op::Add(74, 121), Op::Mul(85, 76), Op::Add(122, 123), Op::Mul(88, 77), Op::Add(124, 125), Op::Mul(82, 79), Op::Add(78, 127), Op::Mul(85, 80), Op::Add(128, 129), Op::Mul(88, 81), Op::Add(130, 131), Op::AssertEq(vec![90, 96, 102, 108, 114, 120, 126, 132], vec![0, 1, 2, 3, 4, 5, 6, 7], Some("aggr range: output statement digest mismatch".into())), Op::Call(293, vec![11], 19, false)], ctrl: Ctrl::Return(0, vec![133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151]) }, layout: FunctionLayout { input_size: 8, selectors: 1, auxiliaries: 60, lookups: 9 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(0, [].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::MatchContinue(9, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Store(vec![14, 14, 14])], ctrl: Ctrl::Yield(0, vec![15]) }), (G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Yield(1, vec![10]) })].into_iter().collect(), None, 1, 1, 1, Box::new(Block { ops: vec![Op::Call(219, vec![7, 14, 12, 13], 5, false)], ctrl: Ctrl::Return(2, vec![15, 16, 17, 18, 19]) })) }))) }, layout: FunctionLayout { input_size: 14, selectors: 3, auxiliaries: 8, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_305() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::Call(280, vec![27, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26], 8, false), Op::Call(273, vec![28, 29, 30, 31, 32, 33, 34, 35], 1, false), Op::Call(293, vec![36], 19, false)], ctrl: Ctrl::Return(0, vec![37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55]) }, layout: FunctionLayout { input_size: 27, selectors: 1, auxiliaries: 29, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(15, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![21, 22, 13]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Load(6, 12)], ctrl: Ctrl::Match(23, [(G::from_u64(0), Block { ops: vec![Op::Call(62, vec![13], 25, false), Op::Call(235, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24, 25, 26, 27, 14, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 16, 17, 18, 19, 20], 2, false), Op::Const(G::from_u64(1)), Op::Add(14, 56), Op::Sub(15, 56), Op::Add(21, 54), Op::Add(22, 55), Op::Call(305, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 28, 53, 57, 58, 16, 17, 18, 19, 20, 59, 60], 3, false)], ctrl: Ctrl::Return(1, vec![61, 62, 63]) })].into_iter().collect(), None) }))) }, layout: FunctionLayout { input_size: 23, selectors: 2, auxiliaries: 38, lookups: 5 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_306() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(278, vec![24, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 12, false), Op::Call(304, vec![16, 17, 18, 19, 20, 21, 22, 23], 19, false), Op::Call(301, vec![24, 37], 2, false), Op::Call(302, vec![25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 56, 57], 5, false), Op::Call(233, vec![38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![63], vec![64], Some("aggr range: the range ends before it starts".into())), Op::Call(14, vec![38, 39, 40, 41, 42, 43, 44, 45], 1, false), Op::Call(14, vec![46, 47, 48, 49, 50, 51, 52, 53], 1, false), Op::Sub(66, 65), Op::IOGetInfo(24, vec![64]), Op::Call(31, vec![68], 2, false), Op::AssertEq(vec![70], vec![67], Some("aggr range: proof count differs from the range".into())), Op::Call(325, vec![56, 65], 1, false), Op::Call(303, vec![25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 72, 71, 65, 67, 62, 58, 59, 60, 61, 24, 24], 3, false), Op::Add(68, 69), Op::AssertEq(vec![75], vec![76], Some("aggr range: trailing bytes after the proofs".into())), Op::Sub(73, 54), Op::EqZero(77), Op::Sub(74, 55), Op::EqZero(79), Op::Mul(78, 80), Op::AssertEq(vec![81], vec![64], Some("aggr range: residual sum differs from the statement".into()))], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 24, selectors: 1, auxiliaries: 55, lookups: 11 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(2)), Op::IOGetInfo(8, vec![8]), Op::Call(3, vec![8, 9, 10], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Const(G::from_u64(1)), Op::Store(vec![43, 43, 43]), Op::Const(G::from_u64(0)), Op::Store(vec![45, 45, 45, 45, 45, 45, 45, 45]), Op::Store(vec![12, 13, 14, 15, 16, 17, 12, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42]), Op::Store(vec![43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43]), Op::Call(78, vec![11, 44, 45, 45, 46, 47, 48], 1, false), Op::Call(77, vec![49], 32, false), Op::Const(G::from_u64(256)), Op::Mul(82, 51), Op::Add(50, 83), Op::Const(G::from_u64(65536)), Op::Mul(85, 52), Op::Add(84, 86), Op::Const(G::from_u64(16777216)), Op::Mul(88, 53), Op::Add(87, 89), Op::Mul(82, 55), Op::Add(54, 91), Op::Mul(85, 56), Op::Add(92, 93), Op::Mul(88, 57), Op::Add(94, 95), Op::Mul(82, 59), Op::Add(58, 97), Op::Mul(85, 60), Op::Add(98, 99), Op::Mul(88, 61), Op::Add(100, 101), Op::Mul(82, 63), Op::Add(62, 103), Op::Mul(85, 64), Op::Add(104, 105), Op::Mul(88, 65), Op::Add(106, 107), Op::Mul(82, 67), Op::Add(66, 109), Op::Mul(85, 68), Op::Add(110, 111), Op::Mul(88, 69), Op::Add(112, 113), Op::Mul(82, 71), Op::Add(70, 115), Op::Mul(85, 72), Op::Add(116, 117), Op::Mul(88, 73), Op::Add(118, 119), Op::Mul(82, 75), Op::Add(74, 121), Op::Mul(85, 76), Op::Add(122, 123), Op::Mul(88, 77), Op::Add(124, 125), Op::Mul(82, 79), Op::Add(78, 127), Op::Mul(85, 80), Op::Add(128, 129), Op::Mul(88, 81), Op::Add(130, 131), Op::AssertEq(vec![90, 96, 102, 108, 114, 120, 126, 132], vec![0, 1, 2, 3, 4, 5, 6, 7], Some("aggr range: output statement digest mismatch".into())), Op::Call(295, vec![11], 19, false)], ctrl: Ctrl::Return(0, vec![133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151]) }, layout: FunctionLayout { input_size: 8, selectors: 1, auxiliaries: 60, lookups: 9 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_307() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(305, vec![34, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], 19, false), Op::Const(G::from_u64(1)), Op::Call(305, vec![54, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], 19, false), Op::Call(304, vec![26, 27, 28, 29, 30, 31, 32, 33], 19, false), Op::Call(2, vec![35, 55], 1, false), Op::AssertEq(vec![93], vec![54], Some("aggr range: children verify different batches".into())), Op::Call(2, vec![74, 35], 1, false), Op::AssertEq(vec![94], vec![54], Some("aggr range: output names a different batch than the children".into())), Op::Call(14, vec![44, 45, 46, 47, 48, 49, 50, 51], 1, false), Op::Call(14, vec![56, 57, 58, 59, 60, 61, 62, 63], 1, false), Op::AssertEq(vec![95], vec![96], Some("aggr range: children are not adjacent".into())), Op::Call(14, vec![75, 76, 77, 78, 79, 80, 81, 82], 1, false), Op::Call(14, vec![36, 37, 38, 39, 40, 41, 42, 43], 1, false), Op::AssertEq(vec![97], vec![98], Some("aggr range: output starts elsewhere than the left child".into())), Op::Call(14, vec![83, 84, 85, 86, 87, 88, 89, 90], 1, false), Op::Call(14, vec![64, 65, 66, 67, 68, 69, 70, 71], 1, false), Op::AssertEq(vec![99], vec![100], Some("aggr range: output ends elsewhere than the right child".into())), Op::Add(52, 72), Op::Add(53, 73), Op::Sub(101, 91), Op::EqZero(103), Op::Sub(102, 92), Op::EqZero(105), Op::Mul(104, 106), Op::AssertEq(vec![107], vec![54], Some("aggr range: output residual is not the children's sum".into()))], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 34, selectors: 1, auxiliaries: 71, lookups: 12 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(1)), Op::Call(282, vec![27, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26], 8, false), Op::Call(275, vec![28, 29, 30, 31, 32, 33, 34, 35], 1, false), Op::Call(295, vec![36], 19, false)], ctrl: Ctrl::Return(0, vec![37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55]) }, layout: FunctionLayout { input_size: 27, selectors: 1, auxiliaries: 29, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_308() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(305, vec![34, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], 19, false), Op::Call(278, vec![34, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 12, false), Op::Const(G::from_u64(1)), Op::Call(301, vec![66, 35], 2, false), Op::Call(315, vec![67], 1, false), Op::Call(230, vec![69], 1, false), Op::AssertEq(vec![70], vec![66], None), Op::Call(14, vec![36, 37, 38, 39, 40, 41, 42, 43], 1, false), Op::AssertEq(vec![71], vec![34], Some("aggr range: root range does not start at shard 0".into())), Op::Call(14, vec![44, 45, 46, 47, 48, 49, 50, 51], 1, false), Op::AssertEq(vec![72], vec![69], Some("aggr range: root range does not end at the last shard".into())), Op::Call(234, vec![68, 34, 34], 1, false), Op::AssertEq(vec![73], vec![66], None), Op::Call(302, vec![54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 67, 68], 5, false), Op::Call(226, vec![52, 53, 68, 74, 75, 76, 77], 2, false), Op::EqZero(79), Op::EqZero(80), Op::Mul(81, 82), Op::AssertEq(vec![83], vec![66], Some("aggr range: the batch does not balance".into())), Op::Call(237, vec![67], 1, false), Op::Call(277, vec![84], 1, false), Op::Call(317, vec![85], 1, false), Op::Const(G::from_u64(10)), Op::AssertEq(vec![86], vec![87], Some("aggr range: batch must carry a 10-word verify_claim claim".into())), Op::Call(274, vec![85, 34], 1, false), Op::AssertEq(vec![88], vec![34], Some("aggr range: batch claim has the wrong channel".into())), Op::Call(274, vec![85, 66], 1, false), Op::AssertEq(vec![89], vec![16], Some("aggr range: batch claim has the wrong entrypoint".into())), Op::Const(G::from_u64(2)), Op::Call(275, vec![85, 90], 8, false), Op::Call(276, vec![91, 92, 93, 94, 95, 96, 97, 98, 26, 27, 28, 29, 30, 31, 32, 33], 0, false)], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 34, selectors: 1, auxiliaries: 64, lookups: 18 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(280, vec![24, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 12, false), Op::Call(306, vec![16, 17, 18, 19, 20, 21, 22, 23], 19, false), Op::Call(303, vec![24, 37], 2, false), Op::Call(304, vec![25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 56, 57], 5, false), Op::Call(233, vec![38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![63], vec![64], Some("aggr range: the range ends before it starts".into())), Op::Call(14, vec![38, 39, 40, 41, 42, 43, 44, 45], 1, false), Op::Call(14, vec![46, 47, 48, 49, 50, 51, 52, 53], 1, false), Op::Sub(66, 65), Op::IOGetInfo(24, vec![64]), Op::Call(31, vec![68], 2, false), Op::AssertEq(vec![70], vec![67], Some("aggr range: proof count differs from the range".into())), Op::Call(327, vec![56, 65], 1, false), Op::Call(305, vec![25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 72, 71, 65, 67, 62, 58, 59, 60, 61, 24, 24], 3, false), Op::Add(68, 69), Op::AssertEq(vec![75], vec![76], Some("aggr range: trailing bytes after the proofs".into())), Op::Sub(73, 54), Op::EqZero(77), Op::Sub(74, 55), Op::EqZero(79), Op::Mul(78, 80), Op::AssertEq(vec![81], vec![64], Some("aggr range: residual sum differs from the statement".into()))], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 24, selectors: 1, auxiliaries: 55, lookups: 11 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_309() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(3)), Op::Const(G::from_u64(0)), Op::IOGetInfo(16, vec![17]), Op::Const(G::from_u64(80)), Op::AssertEq(vec![19], vec![20], Some("aggr: allowed blob must be exactly 80 bytes".into())), Op::Call(3, vec![16, 18, 19], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Const(G::from_u64(1)), Op::Store(vec![53, 53, 53]), Op::Store(vec![17, 17, 17, 17, 17, 17, 17, 17]), Op::Store(vec![22, 23, 24, 25, 26, 27, 22, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52]), Op::Store(vec![53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53]), Op::Call(78, vec![21, 54, 17, 17, 55, 56, 57], 1, false), Op::Call(77, vec![58], 32, false), Op::Const(G::from_u64(256)), Op::Mul(91, 60), Op::Add(59, 92), Op::Const(G::from_u64(65536)), Op::Mul(94, 61), Op::Add(93, 95), Op::Const(G::from_u64(16777216)), Op::Mul(97, 62), Op::Add(96, 98), Op::Mul(91, 64), Op::Add(63, 100), Op::Mul(94, 65), Op::Add(101, 102), Op::Mul(97, 66), Op::Add(103, 104), Op::Mul(91, 68), Op::Add(67, 106), Op::Mul(94, 69), Op::Add(107, 108), Op::Mul(97, 70), Op::Add(109, 110), Op::Mul(91, 72), Op::Add(71, 112), Op::Mul(94, 73), Op::Add(113, 114), Op::Mul(97, 74), Op::Add(115, 116), Op::Mul(91, 76), Op::Add(75, 118), Op::Mul(94, 77), Op::Add(119, 120), Op::Mul(97, 78), Op::Add(121, 122), Op::Mul(91, 80), Op::Add(79, 124), Op::Mul(94, 81), Op::Add(125, 126), Op::Mul(97, 82), Op::Add(127, 128), Op::Mul(91, 84), Op::Add(83, 130), Op::Mul(94, 85), Op::Add(131, 132), Op::Mul(97, 86), Op::Add(133, 134), Op::Mul(91, 88), Op::Add(87, 136), Op::Mul(94, 89), Op::Add(137, 138), Op::Mul(97, 90), Op::Add(139, 140), Op::AssertEq(vec![99, 105, 111, 117, 123, 129, 135, 141], vec![0, 1, 2, 3, 4, 5, 6, 7], Some("aggr: allowed blob digest mismatch".into())), Op::Call(244, vec![21], 2, false), Op::Call(26, vec![143], 2, false), Op::Call(26, vec![145], 2, false), Op::Call(26, vec![147], 2, false), Op::Call(26, vec![149], 2, false), Op::Call(26, vec![151], 2, false), Op::Call(26, vec![153], 2, false), Op::Call(26, vec![155], 2, false), Op::Call(26, vec![157], 2, false), Op::Call(244, vec![159], 2, false), Op::Call(26, vec![161], 2, false), Op::Call(26, vec![163], 2, false), Op::Call(26, vec![165], 2, false), Op::Call(26, vec![167], 2, false), Op::Call(26, vec![169], 2, false), Op::Call(26, vec![171], 2, false), Op::Call(26, vec![173], 2, false), Op::Call(26, vec![175], 2, false), Op::Load(3, 177), Op::AssertEq(vec![178, 179, 180], vec![53, 53, 53], Some("aggr: allowed blob must be exactly 80 bytes".into())), Op::Call(246, vec![142], 8, false), Op::Call(246, vec![160], 8, false), Op::Call(14, vec![144, 146, 148, 150, 152, 154, 156, 158], 1, false), Op::Call(14, vec![162, 164, 166, 168, 170, 172, 174, 176], 1, false), Op::Const(G::from_u64(6)), Op::IOGetInfo(199, vec![17]), Op::AssertEq(vec![201], vec![53], Some("aggr: shape hint must be exactly one byte".into())), Op::Call(3, vec![199, 200, 201], 1, false), Op::Call(90, vec![202], 2, false), Op::Load(3, 204), Op::AssertEq(vec![205, 206, 207], vec![53, 53, 53], Some("aggr: trailing bytes after shape hint".into()))], ctrl: Ctrl::Match(203, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(281, vec![208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(0, vec![]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Call(281, vec![208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(1, vec![]) }), (G::from_u64(2), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(282, vec![208, 208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(2, vec![]) }), (G::from_u64(3), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::Call(282, vec![208, 209, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(3, vec![]) }), (G::from_u64(4), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Const(G::from_u64(0)), Op::Call(282, vec![208, 209, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(4, vec![]) }), (G::from_u64(5), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Call(282, vec![208, 208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(5, vec![]) }), (G::from_u64(6), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(283, vec![208, 208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(6, vec![]) }), (G::from_u64(7), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::Call(283, vec![208, 209, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(7, vec![]) }), (G::from_u64(8), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Const(G::from_u64(0)), Op::Call(283, vec![208, 209, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(8, vec![]) }), (G::from_u64(9), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Call(283, vec![208, 208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(9, vec![]) }), (G::from_u64(10), Block { ops: vec![Op::Call(306, vec![181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(10, vec![]) }), (G::from_u64(11), Block { ops: vec![Op::Call(307, vec![181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(11, vec![]) }), (G::from_u64(12), Block { ops: vec![Op::Call(308, vec![181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(12, vec![]) }), (G::from_u64(13), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(284, vec![208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(13, vec![]) }), (G::from_u64(14), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Call(284, vec![208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(14, vec![]) }), (G::from_u64(15), Block { ops: vec![Op::Call(290, vec![181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(15, vec![]) }), (G::from_u64(16), Block { ops: vec![Op::Call(292, vec![181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(16, vec![]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 16, selectors: 17, auxiliaries: 106, lookups: 35 }, entry: true, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(307, vec![34, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], 19, false), Op::Const(G::from_u64(1)), Op::Call(307, vec![54, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], 19, false), Op::Call(306, vec![26, 27, 28, 29, 30, 31, 32, 33], 19, false), Op::Call(2, vec![35, 55], 1, false), Op::AssertEq(vec![93], vec![54], Some("aggr range: children verify different batches".into())), Op::Call(2, vec![74, 35], 1, false), Op::AssertEq(vec![94], vec![54], Some("aggr range: output names a different batch than the children".into())), Op::Call(14, vec![44, 45, 46, 47, 48, 49, 50, 51], 1, false), Op::Call(14, vec![56, 57, 58, 59, 60, 61, 62, 63], 1, false), Op::AssertEq(vec![95], vec![96], Some("aggr range: children are not adjacent".into())), Op::Call(14, vec![75, 76, 77, 78, 79, 80, 81, 82], 1, false), Op::Call(14, vec![36, 37, 38, 39, 40, 41, 42, 43], 1, false), Op::AssertEq(vec![97], vec![98], Some("aggr range: output starts elsewhere than the left child".into())), Op::Call(14, vec![83, 84, 85, 86, 87, 88, 89, 90], 1, false), Op::Call(14, vec![64, 65, 66, 67, 68, 69, 70, 71], 1, false), Op::AssertEq(vec![99], vec![100], Some("aggr range: output ends elsewhere than the right child".into())), Op::Add(52, 72), Op::Add(53, 73), Op::Sub(101, 91), Op::EqZero(103), Op::Sub(102, 92), Op::EqZero(105), Op::Mul(104, 106), Op::AssertEq(vec![107], vec![54], Some("aggr range: output residual is not the children's sum".into()))], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 34, selectors: 1, auxiliaries: 71, lookups: 12 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_310() -> Function { - Function { body: Block { ops: vec![Op::Call(324, vec![0, 1], 1, false), Op::Load(3, 2)], ctrl: Ctrl::Match(3, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![4]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 5, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(307, vec![34, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], 19, false), Op::Call(280, vec![34, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 12, false), Op::Const(G::from_u64(1)), Op::Call(303, vec![66, 35], 2, false), Op::Call(317, vec![67], 1, false), Op::Call(230, vec![69], 1, false), Op::AssertEq(vec![70], vec![66], None), Op::Call(14, vec![36, 37, 38, 39, 40, 41, 42, 43], 1, false), Op::AssertEq(vec![71], vec![34], Some("aggr range: root range does not start at shard 0".into())), Op::Call(14, vec![44, 45, 46, 47, 48, 49, 50, 51], 1, false), Op::AssertEq(vec![72], vec![69], Some("aggr range: root range does not end at the last shard".into())), Op::Call(234, vec![68, 34, 34], 1, false), Op::AssertEq(vec![73], vec![66], None), Op::Call(304, vec![54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 67, 68], 5, false), Op::Call(226, vec![52, 53, 68, 74, 75, 76, 77], 2, false), Op::EqZero(79), Op::EqZero(80), Op::Mul(81, 82), Op::AssertEq(vec![83], vec![66], Some("aggr range: the batch does not balance".into())), Op::Call(237, vec![67], 1, false), Op::Call(279, vec![84], 1, false), Op::Call(319, vec![85], 1, false), Op::Const(G::from_u64(10)), Op::AssertEq(vec![86], vec![87], Some("aggr range: batch must carry a 10-word verify_claim claim".into())), Op::Call(276, vec![85, 34], 1, false), Op::AssertEq(vec![88], vec![34], Some("aggr range: batch claim has the wrong channel".into())), Op::Call(276, vec![85, 66], 1, false), Op::AssertEq(vec![89], vec![16], Some("aggr range: batch claim has the wrong entrypoint".into())), Op::Const(G::from_u64(2)), Op::Call(277, vec![85, 90], 8, false), Op::Call(278, vec![91, 92, 93, 94, 95, 96, 97, 98, 26, 27, 28, 29, 30, 31, 32, 33], 0, false)], ctrl: Ctrl::Return(0, vec![]) }, layout: FunctionLayout { input_size: 34, selectors: 1, auxiliaries: 64, lookups: 18 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_311() -> Function { - Function { body: Block { ops: vec![Op::Load(26, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![27]) }), (G::from_u64(0), Block { ops: vec![Op::Call(311, vec![26], 1, false), Op::Const(G::from_u64(1)), Op::Add(27, 28)], ctrl: Ctrl::Return(1, vec![29]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 28, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(3)), Op::Const(G::from_u64(0)), Op::IOGetInfo(16, vec![17]), Op::Const(G::from_u64(80)), Op::AssertEq(vec![19], vec![20], Some("aggr: allowed blob must be exactly 80 bytes".into())), Op::Call(3, vec![16, 18, 19], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Const(G::from_u64(1)), Op::Store(vec![53, 53, 53]), Op::Store(vec![17, 17, 17, 17, 17, 17, 17, 17]), Op::Store(vec![22, 23, 24, 25, 26, 27, 22, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52]), Op::Store(vec![53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53]), Op::Call(78, vec![21, 54, 17, 17, 55, 56, 57], 1, false), Op::Call(77, vec![58], 32, false), Op::Const(G::from_u64(256)), Op::Mul(91, 60), Op::Add(59, 92), Op::Const(G::from_u64(65536)), Op::Mul(94, 61), Op::Add(93, 95), Op::Const(G::from_u64(16777216)), Op::Mul(97, 62), Op::Add(96, 98), Op::Mul(91, 64), Op::Add(63, 100), Op::Mul(94, 65), Op::Add(101, 102), Op::Mul(97, 66), Op::Add(103, 104), Op::Mul(91, 68), Op::Add(67, 106), Op::Mul(94, 69), Op::Add(107, 108), Op::Mul(97, 70), Op::Add(109, 110), Op::Mul(91, 72), Op::Add(71, 112), Op::Mul(94, 73), Op::Add(113, 114), Op::Mul(97, 74), Op::Add(115, 116), Op::Mul(91, 76), Op::Add(75, 118), Op::Mul(94, 77), Op::Add(119, 120), Op::Mul(97, 78), Op::Add(121, 122), Op::Mul(91, 80), Op::Add(79, 124), Op::Mul(94, 81), Op::Add(125, 126), Op::Mul(97, 82), Op::Add(127, 128), Op::Mul(91, 84), Op::Add(83, 130), Op::Mul(94, 85), Op::Add(131, 132), Op::Mul(97, 86), Op::Add(133, 134), Op::Mul(91, 88), Op::Add(87, 136), Op::Mul(94, 89), Op::Add(137, 138), Op::Mul(97, 90), Op::Add(139, 140), Op::AssertEq(vec![99, 105, 111, 117, 123, 129, 135, 141], vec![0, 1, 2, 3, 4, 5, 6, 7], Some("aggr: allowed blob digest mismatch".into())), Op::Call(244, vec![21], 2, false), Op::Call(26, vec![143], 2, false), Op::Call(26, vec![145], 2, false), Op::Call(26, vec![147], 2, false), Op::Call(26, vec![149], 2, false), Op::Call(26, vec![151], 2, false), Op::Call(26, vec![153], 2, false), Op::Call(26, vec![155], 2, false), Op::Call(26, vec![157], 2, false), Op::Call(244, vec![159], 2, false), Op::Call(26, vec![161], 2, false), Op::Call(26, vec![163], 2, false), Op::Call(26, vec![165], 2, false), Op::Call(26, vec![167], 2, false), Op::Call(26, vec![169], 2, false), Op::Call(26, vec![171], 2, false), Op::Call(26, vec![173], 2, false), Op::Call(26, vec![175], 2, false), Op::Load(3, 177), Op::AssertEq(vec![178, 179, 180], vec![53, 53, 53], Some("aggr: allowed blob must be exactly 80 bytes".into())), Op::Call(246, vec![142], 8, false), Op::Call(246, vec![160], 8, false), Op::Call(14, vec![144, 146, 148, 150, 152, 154, 156, 158], 1, false), Op::Call(14, vec![162, 164, 166, 168, 170, 172, 174, 176], 1, false), Op::Const(G::from_u64(6)), Op::IOGetInfo(199, vec![17]), Op::AssertEq(vec![201], vec![53], Some("aggr: shape hint must be exactly one byte".into())), Op::Call(3, vec![199, 200, 201], 1, false), Op::Call(90, vec![202], 2, false), Op::Load(3, 204), Op::AssertEq(vec![205, 206, 207], vec![53, 53, 53], Some("aggr: trailing bytes after shape hint".into()))], ctrl: Ctrl::Match(203, [(G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(283, vec![208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(0, vec![]) }), (G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Call(283, vec![208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(1, vec![]) }), (G::from_u64(2), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(284, vec![208, 208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(2, vec![]) }), (G::from_u64(3), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::Call(284, vec![208, 209, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(3, vec![]) }), (G::from_u64(4), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Const(G::from_u64(0)), Op::Call(284, vec![208, 209, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(4, vec![]) }), (G::from_u64(5), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Call(284, vec![208, 208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(5, vec![]) }), (G::from_u64(6), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(285, vec![208, 208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(6, vec![]) }), (G::from_u64(7), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::Call(285, vec![208, 209, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(7, vec![]) }), (G::from_u64(8), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Const(G::from_u64(0)), Op::Call(285, vec![208, 209, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(8, vec![]) }), (G::from_u64(9), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Call(285, vec![208, 208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(9, vec![]) }), (G::from_u64(10), Block { ops: vec![Op::Call(308, vec![181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(10, vec![]) }), (G::from_u64(11), Block { ops: vec![Op::Call(309, vec![181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(11, vec![]) }), (G::from_u64(12), Block { ops: vec![Op::Call(310, vec![181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(12, vec![]) }), (G::from_u64(13), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(286, vec![208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(13, vec![]) }), (G::from_u64(14), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Call(286, vec![208, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(14, vec![]) }), (G::from_u64(15), Block { ops: vec![Op::Call(292, vec![181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(15, vec![]) }), (G::from_u64(16), Block { ops: vec![Op::Call(294, vec![181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 0, false)], ctrl: Ctrl::Return(16, vec![]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 16, selectors: 17, auxiliaries: 106, lookups: 35 }, entry: true, constrained: true } } #[inline(never)] fn expected_function_312() -> Function { - Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![4]) }), (G::from_u64(0), Block { ops: vec![Op::Call(312, vec![3], 1, false), Op::Const(G::from_u64(1)), Op::Add(4, 5)], ctrl: Ctrl::Return(1, vec![6]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 5, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(326, vec![0, 1], 1, false), Op::Load(3, 2)], ctrl: Ctrl::Match(3, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![4]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 5, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_313() -> Function { - Function { body: Block { ops: vec![Op::Load(4, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![5]) }), (G::from_u64(0), Block { ops: vec![Op::Call(313, vec![4], 1, false), Op::Const(G::from_u64(1)), Op::Add(5, 6)], ctrl: Ctrl::Return(1, vec![7]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 6, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(26, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![27]) }), (G::from_u64(0), Block { ops: vec![Op::Call(313, vec![26], 1, false), Op::Const(G::from_u64(1)), Op::Add(27, 28)], ctrl: Ctrl::Return(1, vec![29]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 28, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_314() -> Function { - Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![1]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(314, vec![4, 1], 1, false), Op::Store(vec![5, 3, 6])], ctrl: Ctrl::Return(1, vec![7]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![4]) }), (G::from_u64(0), Block { ops: vec![Op::Call(314, vec![3], 1, false), Op::Const(G::from_u64(1)), Op::Add(4, 5)], ctrl: Ctrl::Return(1, vec![6]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 5, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_315() -> Function { - Function { body: Block { ops: vec![Op::Load(6, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![7]) }), (G::from_u64(0), Block { ops: vec![Op::Call(315, vec![6], 1, false), Op::Const(G::from_u64(1)), Op::Add(7, 8)], ctrl: Ctrl::Return(1, vec![9]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 8, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(4, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![5]) }), (G::from_u64(0), Block { ops: vec![Op::Call(315, vec![4], 1, false), Op::Const(G::from_u64(1)), Op::Add(5, 6)], ctrl: Ctrl::Return(1, vec![7]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 6, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_316() -> Function { - Function { body: Block { ops: vec![Op::Load(7, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![8]) }), (G::from_u64(0), Block { ops: vec![Op::Call(316, vec![7], 1, false), Op::Const(G::from_u64(1)), Op::Add(8, 9)], ctrl: Ctrl::Return(1, vec![10]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 9, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![1]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(316, vec![4, 1], 1, false), Op::Store(vec![5, 3, 6])], ctrl: Ctrl::Return(1, vec![7]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_317() -> Function { - Function { body: Block { ops: vec![Op::Load(10, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![11]) }), (G::from_u64(0), Block { ops: vec![Op::Call(317, vec![10], 1, false), Op::Const(G::from_u64(1)), Op::Add(11, 12)], ctrl: Ctrl::Return(1, vec![13]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 12, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(6, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![7]) }), (G::from_u64(0), Block { ops: vec![Op::Call(317, vec![6], 1, false), Op::Const(G::from_u64(1)), Op::Add(7, 8)], ctrl: Ctrl::Return(1, vec![9]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 8, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_318() -> Function { - Function { body: Block { ops: vec![Op::Load(11, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![12]) }), (G::from_u64(0), Block { ops: vec![Op::Call(318, vec![11], 1, false), Op::Const(G::from_u64(1)), Op::Add(12, 13)], ctrl: Ctrl::Return(1, vec![14]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 13, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(7, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![8]) }), (G::from_u64(0), Block { ops: vec![Op::Call(318, vec![7], 1, false), Op::Const(G::from_u64(1)), Op::Add(8, 9)], ctrl: Ctrl::Return(1, vec![10]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 9, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_319() -> Function { - Function { body: Block { ops: vec![Op::Call(325, vec![0, 1], 1, false), Op::Load(6, 2)], ctrl: Ctrl::Match(3, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![4, 5, 6, 7]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 8, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(10, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![11]) }), (G::from_u64(0), Block { ops: vec![Op::Call(319, vec![10], 1, false), Op::Const(G::from_u64(1)), Op::Add(11, 12)], ctrl: Ctrl::Return(1, vec![13]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 12, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_320() -> Function { - Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(0, vec![4]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(1, vec![4]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 4, lookups: 2 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(11, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![12]) }), (G::from_u64(0), Block { ops: vec![Op::Call(320, vec![11], 1, false), Op::Const(G::from_u64(1)), Op::Add(12, 13)], ctrl: Ctrl::Return(1, vec![14]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 13, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_321() -> Function { - Function { body: Block { ops: vec![Op::Call(327, vec![0, 1], 1, false), Op::Load(4, 2)], ctrl: Ctrl::Match(3, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![4, 5]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 6, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(327, vec![0, 1], 1, false), Op::Load(6, 2)], ctrl: Ctrl::Match(3, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![4, 5, 6, 7]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 8, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_322() -> Function { - Function { body: Block { ops: vec![Op::Load(10, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![1]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(322, vec![11, 1], 1, false), Op::Store(vec![12, 3, 4, 5, 6, 7, 8, 9, 10, 13])], ctrl: Ctrl::Return(1, vec![14]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 13, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(3, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(1))], ctrl: Ctrl::Return(0, vec![4]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(1, vec![4]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 4, lookups: 2 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_323() -> Function { - Function { body: Block { ops: vec![Op::Load(5, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![6]) }), (G::from_u64(0), Block { ops: vec![Op::Call(323, vec![5], 1, false), Op::Const(G::from_u64(1)), Op::Add(6, 7)], ctrl: Ctrl::Return(1, vec![8]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 7, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(329, vec![0, 1], 1, false), Op::Load(4, 2)], ctrl: Ctrl::Match(3, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![4, 5]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 6, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_324() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(1, 2), Op::Call(324, vec![0, 3], 1, false), Op::Load(3, 4)], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(1, vec![7]) })].into_iter().collect(), None) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(10, 0)], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![1]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(324, vec![11, 1], 1, false), Op::Store(vec![12, 3, 4, 5, 6, 7, 8, 9, 10, 13])], ctrl: Ctrl::Return(1, vec![14]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 13, lookups: 4 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_325() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(1, 2), Op::Call(325, vec![0, 3], 1, false), Op::Load(6, 4)], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(1, vec![10]) })].into_iter().collect(), None) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 9, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(5, 0)], ctrl: Ctrl::Match(1, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Return(0, vec![6]) }), (G::from_u64(0), Block { ops: vec![Op::Call(325, vec![5], 1, false), Op::Const(G::from_u64(1)), Op::Add(6, 7)], ctrl: Ctrl::Return(1, vec![8]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 7, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_326() -> Function { - Function { body: Block { ops: vec![Op::Call(328, vec![0, 1], 1, false), Op::Load(10, 2)], ctrl: Ctrl::Match(3, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![4, 5, 6, 7, 8, 9, 10, 11]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 12, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(1, 2), Op::Call(326, vec![0, 3], 1, false), Op::Load(3, 4)], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(1, vec![7]) })].into_iter().collect(), None) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_327() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(1, 2), Op::Call(327, vec![0, 3], 1, false), Op::Load(4, 4)], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(1, vec![8]) })].into_iter().collect(), None) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 7, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(1, 2), Op::Call(327, vec![0, 3], 1, false), Op::Load(6, 4)], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(1, vec![10]) })].into_iter().collect(), None) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 9, lookups: 3 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_328() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(1, 2), Op::Call(328, vec![0, 3], 1, false), Op::Load(10, 4)], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(1, vec![14]) })].into_iter().collect(), None) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 13, lookups: 3 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(330, vec![0, 1], 1, false), Op::Load(10, 2)], ctrl: Ctrl::Match(3, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![4, 5, 6, 7, 8, 9, 10, 11]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 12, lookups: 3 }, entry: false, constrained: true } +} + +#[inline(never)] +fn expected_function_329() -> Function { + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(1, 2), Op::Call(329, vec![0, 3], 1, false), Op::Load(4, 4)], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(1, vec![8]) })].into_iter().collect(), None) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 7, lookups: 3 }, entry: false, constrained: true } +} + +#[inline(never)] +fn expected_function_330() -> Function { + Function { body: Block { ops: vec![], ctrl: Ctrl::Match(1, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![0]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(1)), Op::Sub(1, 2), Op::Call(330, vec![0, 3], 1, false), Op::Load(10, 4)], ctrl: Ctrl::Match(5, [(G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Return(1, vec![14]) })].into_iter().collect(), None) }))) }, layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 13, lookups: 3 }, entry: false, constrained: true } } fn expected_program() -> Toplevel { - Toplevel { functions: vec![expected_function_0(), expected_function_1(), expected_function_2(), expected_function_3(), expected_function_4(), expected_function_5(), expected_function_6(), expected_function_7(), expected_function_8(), expected_function_9(), expected_function_10(), expected_function_11(), expected_function_12(), expected_function_13(), expected_function_14(), expected_function_15(), expected_function_16(), expected_function_17(), expected_function_18(), expected_function_19(), expected_function_20(), expected_function_21(), expected_function_22(), expected_function_23(), expected_function_24(), expected_function_25(), expected_function_26(), expected_function_27(), expected_function_28(), expected_function_29(), expected_function_30(), expected_function_31(), expected_function_32(), expected_function_33(), expected_function_34(), expected_function_35(), expected_function_36(), expected_function_37(), expected_function_38(), expected_function_39(), expected_function_40(), expected_function_41(), expected_function_42(), expected_function_43(), expected_function_44(), expected_function_45(), expected_function_46(), expected_function_47(), expected_function_48(), expected_function_49(), expected_function_50(), expected_function_51(), expected_function_52(), expected_function_53(), expected_function_54(), expected_function_55(), expected_function_56(), expected_function_57(), expected_function_58(), expected_function_59(), expected_function_60(), expected_function_61(), expected_function_62(), expected_function_63(), expected_function_64(), expected_function_65(), expected_function_66(), expected_function_67(), expected_function_68(), expected_function_69(), expected_function_70(), expected_function_71(), expected_function_72(), expected_function_73(), expected_function_74(), expected_function_75(), expected_function_76(), expected_function_77(), expected_function_78(), expected_function_79(), expected_function_80(), expected_function_81(), expected_function_82(), expected_function_83(), expected_function_84(), expected_function_85(), expected_function_86(), expected_function_87(), expected_function_88(), expected_function_89(), expected_function_90(), expected_function_91(), expected_function_92(), expected_function_93(), expected_function_94(), expected_function_95(), expected_function_96(), expected_function_97(), expected_function_98(), expected_function_99(), expected_function_100(), expected_function_101(), expected_function_102(), expected_function_103(), expected_function_104(), expected_function_105(), expected_function_106(), expected_function_107(), expected_function_108(), expected_function_109(), expected_function_110(), expected_function_111(), expected_function_112(), expected_function_113(), expected_function_114(), expected_function_115(), expected_function_116(), expected_function_117(), expected_function_118(), expected_function_119(), expected_function_120(), expected_function_121(), expected_function_122(), expected_function_123(), expected_function_124(), expected_function_125(), expected_function_126(), expected_function_127(), expected_function_128(), expected_function_129(), expected_function_130(), expected_function_131(), expected_function_132(), expected_function_133(), expected_function_134(), expected_function_135(), expected_function_136(), expected_function_137(), expected_function_138(), expected_function_139(), expected_function_140(), expected_function_141(), expected_function_142(), expected_function_143(), expected_function_144(), expected_function_145(), expected_function_146(), expected_function_147(), expected_function_148(), expected_function_149(), expected_function_150(), expected_function_151(), expected_function_152(), expected_function_153(), expected_function_154(), expected_function_155(), expected_function_156(), expected_function_157(), expected_function_158(), expected_function_159(), expected_function_160(), expected_function_161(), expected_function_162(), expected_function_163(), expected_function_164(), expected_function_165(), expected_function_166(), expected_function_167(), expected_function_168(), expected_function_169(), expected_function_170(), expected_function_171(), expected_function_172(), expected_function_173(), expected_function_174(), expected_function_175(), expected_function_176(), expected_function_177(), expected_function_178(), expected_function_179(), expected_function_180(), expected_function_181(), expected_function_182(), expected_function_183(), expected_function_184(), expected_function_185(), expected_function_186(), expected_function_187(), expected_function_188(), expected_function_189(), expected_function_190(), expected_function_191(), expected_function_192(), expected_function_193(), expected_function_194(), expected_function_195(), expected_function_196(), expected_function_197(), expected_function_198(), expected_function_199(), expected_function_200(), expected_function_201(), expected_function_202(), expected_function_203(), expected_function_204(), expected_function_205(), expected_function_206(), expected_function_207(), expected_function_208(), expected_function_209(), expected_function_210(), expected_function_211(), expected_function_212(), expected_function_213(), expected_function_214(), expected_function_215(), expected_function_216(), expected_function_217(), expected_function_218(), expected_function_219(), expected_function_220(), expected_function_221(), expected_function_222(), expected_function_223(), expected_function_224(), expected_function_225(), expected_function_226(), expected_function_227(), expected_function_228(), expected_function_229(), expected_function_230(), expected_function_231(), expected_function_232(), expected_function_233(), expected_function_234(), expected_function_235(), expected_function_236(), expected_function_237(), expected_function_238(), expected_function_239(), expected_function_240(), expected_function_241(), expected_function_242(), expected_function_243(), expected_function_244(), expected_function_245(), expected_function_246(), expected_function_247(), expected_function_248(), expected_function_249(), expected_function_250(), expected_function_251(), expected_function_252(), expected_function_253(), expected_function_254(), expected_function_255(), expected_function_256(), expected_function_257(), expected_function_258(), expected_function_259(), expected_function_260(), expected_function_261(), expected_function_262(), expected_function_263(), expected_function_264(), expected_function_265(), expected_function_266(), expected_function_267(), expected_function_268(), expected_function_269(), expected_function_270(), expected_function_271(), expected_function_272(), expected_function_273(), expected_function_274(), expected_function_275(), expected_function_276(), expected_function_277(), expected_function_278(), expected_function_279(), expected_function_280(), expected_function_281(), expected_function_282(), expected_function_283(), expected_function_284(), expected_function_285(), expected_function_286(), expected_function_287(), expected_function_288(), expected_function_289(), expected_function_290(), expected_function_291(), expected_function_292(), expected_function_293(), expected_function_294(), expected_function_295(), expected_function_296(), expected_function_297(), expected_function_298(), expected_function_299(), expected_function_300(), expected_function_301(), expected_function_302(), expected_function_303(), expected_function_304(), expected_function_305(), expected_function_306(), expected_function_307(), expected_function_308(), expected_function_309(), expected_function_310(), expected_function_311(), expected_function_312(), expected_function_313(), expected_function_314(), expected_function_315(), expected_function_316(), expected_function_317(), expected_function_318(), expected_function_319(), expected_function_320(), expected_function_321(), expected_function_322(), expected_function_323(), expected_function_324(), expected_function_325(), expected_function_326(), expected_function_327(), expected_function_328()], memory_sizes: vec![3, 4, 5, 6, 7, 8, 10, 11, 26, 32, 34], circuits: vec![Circuit { members: vec![0, 4, 13, 14, 102, 125, 126, 127, 128, 170, 186, 194], layout: FunctionLayout { input_size: 8, selectors: 40, auxiliaries: 20, lookups: 7 } }, Circuit { members: vec![1, 2, 250, 257, 259, 260, 268, 273, 275, 276, 277, 280], layout: FunctionLayout { input_size: 28, selectors: 40, auxiliaries: 73, lookups: 10 } }, Circuit { members: vec![3], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![12], layout: FunctionLayout { input_size: 16, selectors: 9, auxiliaries: 2, lookups: 1 } }, Circuit { members: vec![26, 40, 94, 96, 103, 147, 188, 195, 209, 210, 211, 318], layout: FunctionLayout { input_size: 3, selectors: 25, auxiliaries: 18, lookups: 6 } }, Circuit { members: vec![27, 76, 77, 95, 131, 142, 154, 163, 171, 179, 180, 183, 200], layout: FunctionLayout { input_size: 44, selectors: 26, auxiliaries: 137, lookups: 14 } }, Circuit { members: vec![28, 48, 56, 59, 61, 75, 114, 143, 161, 176, 182, 207, 243, 316, 322], layout: FunctionLayout { input_size: 9, selectors: 27, auxiliaries: 98, lookups: 32 } }, Circuit { members: vec![31, 325], layout: FunctionLayout { input_size: 2, selectors: 3, auxiliaries: 11, lookups: 3 } }, Circuit { members: vec![35, 45, 49, 111, 130, 144, 155, 157, 165, 166, 169, 181], layout: FunctionLayout { input_size: 5, selectors: 25, auxiliaries: 9, lookups: 5 } }, Circuit { members: vec![36, 312], layout: FunctionLayout { input_size: 1, selectors: 3, auxiliaries: 5, lookups: 3 } }, Circuit { members: vec![37], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 14, lookups: 3 } }, Circuit { members: vec![38, 46, 47, 83, 93, 116, 124, 133, 138, 139, 177, 314], layout: FunctionLayout { input_size: 2, selectors: 23, auxiliaries: 8, lookups: 4 } }, Circuit { members: vec![39, 198], layout: FunctionLayout { input_size: 8, selectors: 3, auxiliaries: 23, lookups: 3 } }, Circuit { members: vec![41, 52, 108, 122, 134, 141, 145, 160, 164, 167, 184, 189, 191, 203, 323], layout: FunctionLayout { input_size: 8, selectors: 31, auxiliaries: 42, lookups: 9 } }, Circuit { members: vec![43, 100], layout: FunctionLayout { input_size: 2, selectors: 4, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![62], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 51, lookups: 21 } }, Circuit { members: vec![65], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 19, lookups: 10 } }, Circuit { members: vec![68], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 16, lookups: 4 } }, Circuit { members: vec![70], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 30, lookups: 4 } }, Circuit { members: vec![78], layout: FunctionLayout { input_size: 7, selectors: 9, auxiliaries: 22, lookups: 10 } }, Circuit { members: vec![82, 84, 85, 106, 178, 206, 244, 245, 293, 294], layout: FunctionLayout { input_size: 20, selectors: 29, auxiliaries: 193, lookups: 65 } }, Circuit { members: vec![89], layout: FunctionLayout { input_size: 129, selectors: 2, auxiliaries: 402, lookups: 194 } }, Circuit { members: vec![90], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 4, lookups: 2 } }, Circuit { members: vec![91], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 3, lookups: 2 } }, Circuit { members: vec![92], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 5, lookups: 3 } }, Circuit { members: vec![98, 107, 148, 156, 162, 168, 204, 208, 241, 246, 315], layout: FunctionLayout { input_size: 25, selectors: 38, auxiliaries: 46, lookups: 20 } }, Circuit { members: vec![99], layout: FunctionLayout { input_size: 3, selectors: 17, auxiliaries: 24, lookups: 8 } }, Circuit { members: vec![115, 193, 320], layout: FunctionLayout { input_size: 6, selectors: 5, auxiliaries: 5, lookups: 2 } }, Circuit { members: vec![117], layout: FunctionLayout { input_size: 3, selectors: 4, auxiliaries: 23, lookups: 6 } }, Circuit { members: vec![118, 129, 137], layout: FunctionLayout { input_size: 4, selectors: 10, auxiliaries: 34, lookups: 13 } }, Circuit { members: vec![119], layout: FunctionLayout { input_size: 2, selectors: 3, auxiliaries: 25, lookups: 6 } }, Circuit { members: vec![120], layout: FunctionLayout { input_size: 6, selectors: 15, auxiliaries: 205, lookups: 21 } }, Circuit { members: vec![123, 132, 321, 327], layout: FunctionLayout { input_size: 4, selectors: 7, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![136], layout: FunctionLayout { input_size: 8, selectors: 2, auxiliaries: 27, lookups: 4 } }, Circuit { members: vec![146], layout: FunctionLayout { input_size: 8, selectors: 3, auxiliaries: 19, lookups: 5 } }, Circuit { members: vec![149], layout: FunctionLayout { input_size: 10, selectors: 1, auxiliaries: 15, lookups: 7 } }, Circuit { members: vec![150, 151, 152, 153, 197, 199], layout: FunctionLayout { input_size: 19, selectors: 12, auxiliaries: 14, lookups: 7 } }, Circuit { members: vec![174], layout: FunctionLayout { input_size: 9, selectors: 1, auxiliaries: 9, lookups: 9 } }, Circuit { members: vec![196], layout: FunctionLayout { input_size: 15, selectors: 15, auxiliaries: 13, lookups: 4 } }, Circuit { members: vec![212], layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 15, lookups: 6 } }, Circuit { members: vec![213], layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 4, lookups: 4 } }, Circuit { members: vec![214], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 8, lookups: 6 } }, Circuit { members: vec![215], layout: FunctionLayout { input_size: 5, selectors: 1, auxiliaries: 7, lookups: 7 } }, Circuit { members: vec![216], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 9, lookups: 4 } }, Circuit { members: vec![217], layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 4, lookups: 4 } }, Circuit { members: vec![218], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 17, lookups: 7 } }, Circuit { members: vec![219], layout: FunctionLayout { input_size: 4, selectors: 1, auxiliaries: 64, lookups: 30 } }, Circuit { members: vec![220], layout: FunctionLayout { input_size: 5, selectors: 1, auxiliaries: 56, lookups: 13 } }, Circuit { members: vec![221], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 8, lookups: 4 } }, Circuit { members: vec![222], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 23, lookups: 5 } }, Circuit { members: vec![223], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 9, lookups: 5 } }, Circuit { members: vec![224], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 76, lookups: 10 } }, Circuit { members: vec![225], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 12, lookups: 4 } }, Circuit { members: vec![226], layout: FunctionLayout { input_size: 7, selectors: 2, auxiliaries: 26, lookups: 4 } }, Circuit { members: vec![227], layout: FunctionLayout { input_size: 1, selectors: 34, auxiliaries: 34, lookups: 1 } }, Circuit { members: vec![228], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 6, lookups: 4 } }, Circuit { members: vec![229], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 5, lookups: 3 } }, Circuit { members: vec![230], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 18, lookups: 5 } }, Circuit { members: vec![231], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 41, lookups: 5 } }, Circuit { members: vec![232], layout: FunctionLayout { input_size: 3, selectors: 1, auxiliaries: 5, lookups: 2 } }, Circuit { members: vec![233], layout: FunctionLayout { input_size: 16, selectors: 1, auxiliaries: 9, lookups: 9 } }, Circuit { members: vec![234], layout: FunctionLayout { input_size: 3, selectors: 2, auxiliaries: 156, lookups: 33 } }, Circuit { members: vec![235], layout: FunctionLayout { input_size: 46, selectors: 5, auxiliaries: 91, lookups: 54 } }, Circuit { members: vec![236], layout: FunctionLayout { input_size: 22, selectors: 2, auxiliaries: 37, lookups: 5 } }, Circuit { members: vec![237], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 9, lookups: 4 } }, Circuit { members: vec![238], layout: FunctionLayout { input_size: 15, selectors: 3, auxiliaries: 23, lookups: 11 } }, Circuit { members: vec![247, 248, 251, 252, 253, 254, 255, 256, 258, 261, 262, 263, 267, 274, 326, 328, 286, 287, 295, 296, 298], layout: FunctionLayout { input_size: 4, selectors: 48, auxiliaries: 13, lookups: 7 } }, Circuit { members: vec![264, 265, 266, 278, 279, 281, 282, 283, 284, 290, 292, 306, 307, 308, 304, 305, 271, 289, 297, 303, 301, 288, 269, 270, 272, 291, 299, 300, 302, 285], layout: FunctionLayout { input_size: 36, selectors: 59, auxiliaries: 143, lookups: 31 } }, Circuit { members: vec![309], layout: FunctionLayout { input_size: 16, selectors: 17, auxiliaries: 106, lookups: 35 } }, Circuit { members: vec![310, 313, 324], layout: FunctionLayout { input_size: 2, selectors: 5, auxiliaries: 6, lookups: 3 } }, Circuit { members: vec![311], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 28, lookups: 3 } }, Circuit { members: vec![317], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 12, lookups: 3 } }, Circuit { members: vec![319], layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 8, lookups: 3 } }] } + Toplevel { functions: vec![expected_function_0(), expected_function_1(), expected_function_2(), expected_function_3(), expected_function_4(), expected_function_5(), expected_function_6(), expected_function_7(), expected_function_8(), expected_function_9(), expected_function_10(), expected_function_11(), expected_function_12(), expected_function_13(), expected_function_14(), expected_function_15(), expected_function_16(), expected_function_17(), expected_function_18(), expected_function_19(), expected_function_20(), expected_function_21(), expected_function_22(), expected_function_23(), expected_function_24(), expected_function_25(), expected_function_26(), expected_function_27(), expected_function_28(), expected_function_29(), expected_function_30(), expected_function_31(), expected_function_32(), expected_function_33(), expected_function_34(), expected_function_35(), expected_function_36(), expected_function_37(), expected_function_38(), expected_function_39(), expected_function_40(), expected_function_41(), expected_function_42(), expected_function_43(), expected_function_44(), expected_function_45(), expected_function_46(), expected_function_47(), expected_function_48(), expected_function_49(), expected_function_50(), expected_function_51(), expected_function_52(), expected_function_53(), expected_function_54(), expected_function_55(), expected_function_56(), expected_function_57(), expected_function_58(), expected_function_59(), expected_function_60(), expected_function_61(), expected_function_62(), expected_function_63(), expected_function_64(), expected_function_65(), expected_function_66(), expected_function_67(), expected_function_68(), expected_function_69(), expected_function_70(), expected_function_71(), expected_function_72(), expected_function_73(), expected_function_74(), expected_function_75(), expected_function_76(), expected_function_77(), expected_function_78(), expected_function_79(), expected_function_80(), expected_function_81(), expected_function_82(), expected_function_83(), expected_function_84(), expected_function_85(), expected_function_86(), expected_function_87(), expected_function_88(), expected_function_89(), expected_function_90(), expected_function_91(), expected_function_92(), expected_function_93(), expected_function_94(), expected_function_95(), expected_function_96(), expected_function_97(), expected_function_98(), expected_function_99(), expected_function_100(), expected_function_101(), expected_function_102(), expected_function_103(), expected_function_104(), expected_function_105(), expected_function_106(), expected_function_107(), expected_function_108(), expected_function_109(), expected_function_110(), expected_function_111(), expected_function_112(), expected_function_113(), expected_function_114(), expected_function_115(), expected_function_116(), expected_function_117(), expected_function_118(), expected_function_119(), expected_function_120(), expected_function_121(), expected_function_122(), expected_function_123(), expected_function_124(), expected_function_125(), expected_function_126(), expected_function_127(), expected_function_128(), expected_function_129(), expected_function_130(), expected_function_131(), expected_function_132(), expected_function_133(), expected_function_134(), expected_function_135(), expected_function_136(), expected_function_137(), expected_function_138(), expected_function_139(), expected_function_140(), expected_function_141(), expected_function_142(), expected_function_143(), expected_function_144(), expected_function_145(), expected_function_146(), expected_function_147(), expected_function_148(), expected_function_149(), expected_function_150(), expected_function_151(), expected_function_152(), expected_function_153(), expected_function_154(), expected_function_155(), expected_function_156(), expected_function_157(), expected_function_158(), expected_function_159(), expected_function_160(), expected_function_161(), expected_function_162(), expected_function_163(), expected_function_164(), expected_function_165(), expected_function_166(), expected_function_167(), expected_function_168(), expected_function_169(), expected_function_170(), expected_function_171(), expected_function_172(), expected_function_173(), expected_function_174(), expected_function_175(), expected_function_176(), expected_function_177(), expected_function_178(), expected_function_179(), expected_function_180(), expected_function_181(), expected_function_182(), expected_function_183(), expected_function_184(), expected_function_185(), expected_function_186(), expected_function_187(), expected_function_188(), expected_function_189(), expected_function_190(), expected_function_191(), expected_function_192(), expected_function_193(), expected_function_194(), expected_function_195(), expected_function_196(), expected_function_197(), expected_function_198(), expected_function_199(), expected_function_200(), expected_function_201(), expected_function_202(), expected_function_203(), expected_function_204(), expected_function_205(), expected_function_206(), expected_function_207(), expected_function_208(), expected_function_209(), expected_function_210(), expected_function_211(), expected_function_212(), expected_function_213(), expected_function_214(), expected_function_215(), expected_function_216(), expected_function_217(), expected_function_218(), expected_function_219(), expected_function_220(), expected_function_221(), expected_function_222(), expected_function_223(), expected_function_224(), expected_function_225(), expected_function_226(), expected_function_227(), expected_function_228(), expected_function_229(), expected_function_230(), expected_function_231(), expected_function_232(), expected_function_233(), expected_function_234(), expected_function_235(), expected_function_236(), expected_function_237(), expected_function_238(), expected_function_239(), expected_function_240(), expected_function_241(), expected_function_242(), expected_function_243(), expected_function_244(), expected_function_245(), expected_function_246(), expected_function_247(), expected_function_248(), expected_function_249(), expected_function_250(), expected_function_251(), expected_function_252(), expected_function_253(), expected_function_254(), expected_function_255(), expected_function_256(), expected_function_257(), expected_function_258(), expected_function_259(), expected_function_260(), expected_function_261(), expected_function_262(), expected_function_263(), expected_function_264(), expected_function_265(), expected_function_266(), expected_function_267(), expected_function_268(), expected_function_269(), expected_function_270(), expected_function_271(), expected_function_272(), expected_function_273(), expected_function_274(), expected_function_275(), expected_function_276(), expected_function_277(), expected_function_278(), expected_function_279(), expected_function_280(), expected_function_281(), expected_function_282(), expected_function_283(), expected_function_284(), expected_function_285(), expected_function_286(), expected_function_287(), expected_function_288(), expected_function_289(), expected_function_290(), expected_function_291(), expected_function_292(), expected_function_293(), expected_function_294(), expected_function_295(), expected_function_296(), expected_function_297(), expected_function_298(), expected_function_299(), expected_function_300(), expected_function_301(), expected_function_302(), expected_function_303(), expected_function_304(), expected_function_305(), expected_function_306(), expected_function_307(), expected_function_308(), expected_function_309(), expected_function_310(), expected_function_311(), expected_function_312(), expected_function_313(), expected_function_314(), expected_function_315(), expected_function_316(), expected_function_317(), expected_function_318(), expected_function_319(), expected_function_320(), expected_function_321(), expected_function_322(), expected_function_323(), expected_function_324(), expected_function_325(), expected_function_326(), expected_function_327(), expected_function_328(), expected_function_329(), expected_function_330()], memory_sizes: vec![3, 4, 5, 6, 7, 8, 10, 11, 26, 32, 34], circuits: vec![Circuit { members: vec![0, 4, 13, 14, 102, 125, 126, 127, 128, 170, 186, 194], layout: FunctionLayout { input_size: 8, selectors: 40, auxiliaries: 20, lookups: 7 } }, Circuit { members: vec![1, 2, 250, 257, 259, 260, 270, 275, 277, 278, 279, 282], layout: FunctionLayout { input_size: 28, selectors: 40, auxiliaries: 73, lookups: 10 } }, Circuit { members: vec![3], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![12], layout: FunctionLayout { input_size: 16, selectors: 9, auxiliaries: 2, lookups: 1 } }, Circuit { members: vec![26, 40, 94, 96, 103, 147, 188, 195, 209, 210, 211, 320], layout: FunctionLayout { input_size: 3, selectors: 25, auxiliaries: 18, lookups: 6 } }, Circuit { members: vec![27, 76, 77, 95, 131, 142, 154, 163, 171, 179, 180, 183, 200], layout: FunctionLayout { input_size: 44, selectors: 26, auxiliaries: 137, lookups: 14 } }, Circuit { members: vec![28, 48, 56, 59, 61, 75, 114, 143, 161, 176, 182, 207, 243, 318, 324], layout: FunctionLayout { input_size: 9, selectors: 27, auxiliaries: 98, lookups: 32 } }, Circuit { members: vec![31, 327], layout: FunctionLayout { input_size: 2, selectors: 3, auxiliaries: 11, lookups: 3 } }, Circuit { members: vec![35, 45, 49, 111, 130, 144, 155, 157, 165, 166, 169, 181], layout: FunctionLayout { input_size: 5, selectors: 25, auxiliaries: 9, lookups: 5 } }, Circuit { members: vec![36, 314], layout: FunctionLayout { input_size: 1, selectors: 3, auxiliaries: 5, lookups: 3 } }, Circuit { members: vec![37], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 14, lookups: 3 } }, Circuit { members: vec![38, 46, 47, 83, 93, 116, 124, 133, 138, 139, 177, 316], layout: FunctionLayout { input_size: 2, selectors: 23, auxiliaries: 8, lookups: 4 } }, Circuit { members: vec![39, 198], layout: FunctionLayout { input_size: 8, selectors: 3, auxiliaries: 23, lookups: 3 } }, Circuit { members: vec![41, 52, 108, 122, 134, 141, 145, 160, 164, 167, 184, 189, 191, 203, 325], layout: FunctionLayout { input_size: 8, selectors: 31, auxiliaries: 42, lookups: 9 } }, Circuit { members: vec![43, 100], layout: FunctionLayout { input_size: 2, selectors: 4, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![62], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 51, lookups: 21 } }, Circuit { members: vec![65], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 19, lookups: 10 } }, Circuit { members: vec![68], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 16, lookups: 4 } }, Circuit { members: vec![70], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 30, lookups: 4 } }, Circuit { members: vec![78], layout: FunctionLayout { input_size: 7, selectors: 9, auxiliaries: 22, lookups: 10 } }, Circuit { members: vec![82, 84, 85, 106, 178, 206, 244, 245, 295, 296], layout: FunctionLayout { input_size: 20, selectors: 29, auxiliaries: 193, lookups: 65 } }, Circuit { members: vec![89], layout: FunctionLayout { input_size: 129, selectors: 2, auxiliaries: 402, lookups: 194 } }, Circuit { members: vec![90], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 4, lookups: 2 } }, Circuit { members: vec![91], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 3, lookups: 2 } }, Circuit { members: vec![92], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 5, lookups: 3 } }, Circuit { members: vec![98, 107, 148, 156, 162, 168, 204, 208, 241, 246, 317], layout: FunctionLayout { input_size: 25, selectors: 38, auxiliaries: 46, lookups: 20 } }, Circuit { members: vec![99], layout: FunctionLayout { input_size: 3, selectors: 17, auxiliaries: 24, lookups: 8 } }, Circuit { members: vec![115, 193, 322], layout: FunctionLayout { input_size: 6, selectors: 5, auxiliaries: 5, lookups: 2 } }, Circuit { members: vec![117], layout: FunctionLayout { input_size: 3, selectors: 4, auxiliaries: 23, lookups: 6 } }, Circuit { members: vec![118, 129, 137], layout: FunctionLayout { input_size: 4, selectors: 10, auxiliaries: 34, lookups: 13 } }, Circuit { members: vec![119], layout: FunctionLayout { input_size: 2, selectors: 3, auxiliaries: 25, lookups: 6 } }, Circuit { members: vec![120], layout: FunctionLayout { input_size: 6, selectors: 15, auxiliaries: 205, lookups: 21 } }, Circuit { members: vec![123, 132, 323, 329], layout: FunctionLayout { input_size: 4, selectors: 7, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![136], layout: FunctionLayout { input_size: 8, selectors: 2, auxiliaries: 27, lookups: 4 } }, Circuit { members: vec![146], layout: FunctionLayout { input_size: 8, selectors: 3, auxiliaries: 19, lookups: 5 } }, Circuit { members: vec![149], layout: FunctionLayout { input_size: 10, selectors: 1, auxiliaries: 15, lookups: 7 } }, Circuit { members: vec![150, 151, 152, 153, 197, 199], layout: FunctionLayout { input_size: 19, selectors: 12, auxiliaries: 14, lookups: 7 } }, Circuit { members: vec![174], layout: FunctionLayout { input_size: 9, selectors: 1, auxiliaries: 9, lookups: 9 } }, Circuit { members: vec![196], layout: FunctionLayout { input_size: 15, selectors: 15, auxiliaries: 13, lookups: 4 } }, Circuit { members: vec![212], layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 15, lookups: 6 } }, Circuit { members: vec![213], layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 4, lookups: 4 } }, Circuit { members: vec![214], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 8, lookups: 6 } }, Circuit { members: vec![215], layout: FunctionLayout { input_size: 5, selectors: 1, auxiliaries: 7, lookups: 7 } }, Circuit { members: vec![216], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 9, lookups: 4 } }, Circuit { members: vec![217], layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 4, lookups: 4 } }, Circuit { members: vec![218], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 17, lookups: 7 } }, Circuit { members: vec![219], layout: FunctionLayout { input_size: 4, selectors: 1, auxiliaries: 64, lookups: 30 } }, Circuit { members: vec![220], layout: FunctionLayout { input_size: 5, selectors: 1, auxiliaries: 56, lookups: 13 } }, Circuit { members: vec![221], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 8, lookups: 4 } }, Circuit { members: vec![222], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 23, lookups: 5 } }, Circuit { members: vec![223], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 9, lookups: 5 } }, Circuit { members: vec![224], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 76, lookups: 10 } }, Circuit { members: vec![225], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 12, lookups: 4 } }, Circuit { members: vec![226], layout: FunctionLayout { input_size: 7, selectors: 2, auxiliaries: 26, lookups: 4 } }, Circuit { members: vec![227], layout: FunctionLayout { input_size: 1, selectors: 34, auxiliaries: 34, lookups: 1 } }, Circuit { members: vec![228], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 6, lookups: 4 } }, Circuit { members: vec![229], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 5, lookups: 3 } }, Circuit { members: vec![230], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 18, lookups: 5 } }, Circuit { members: vec![231], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 41, lookups: 5 } }, Circuit { members: vec![232], layout: FunctionLayout { input_size: 3, selectors: 1, auxiliaries: 5, lookups: 2 } }, Circuit { members: vec![233], layout: FunctionLayout { input_size: 16, selectors: 1, auxiliaries: 9, lookups: 9 } }, Circuit { members: vec![234], layout: FunctionLayout { input_size: 3, selectors: 2, auxiliaries: 156, lookups: 33 } }, Circuit { members: vec![235], layout: FunctionLayout { input_size: 46, selectors: 5, auxiliaries: 91, lookups: 54 } }, Circuit { members: vec![236], layout: FunctionLayout { input_size: 22, selectors: 2, auxiliaries: 37, lookups: 5 } }, Circuit { members: vec![237], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 9, lookups: 4 } }, Circuit { members: vec![238], layout: FunctionLayout { input_size: 15, selectors: 3, auxiliaries: 23, lookups: 11 } }, Circuit { members: vec![247, 248, 251, 252, 253, 254, 255, 256, 258, 263, 264, 265, 269, 276, 328, 330, 288, 289, 297, 298, 300], layout: FunctionLayout { input_size: 4, selectors: 48, auxiliaries: 13, lookups: 7 } }, Circuit { members: vec![261], layout: FunctionLayout { input_size: 3, selectors: 5, auxiliaries: 14, lookups: 7 } }, Circuit { members: vec![262], layout: FunctionLayout { input_size: 2, selectors: 5, auxiliaries: 10, lookups: 5 } }, Circuit { members: vec![266, 267, 268, 280, 281, 283, 284, 285, 286, 292, 294, 308, 309, 310, 306, 307, 273, 291, 299, 305, 303, 290, 271, 272, 274, 293, 301, 302, 304, 287], layout: FunctionLayout { input_size: 36, selectors: 61, auxiliaries: 143, lookups: 31 } }, Circuit { members: vec![311], layout: FunctionLayout { input_size: 16, selectors: 17, auxiliaries: 106, lookups: 35 } }, Circuit { members: vec![312, 315, 326], layout: FunctionLayout { input_size: 2, selectors: 5, auxiliaries: 6, lookups: 3 } }, Circuit { members: vec![313], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 28, lookups: 3 } }, Circuit { members: vec![319], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 12, lookups: 3 } }, Circuit { members: vec![321], layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 8, lookups: 3 } }] } } -pub static PROGRAM: GeneratedProgram = GeneratedProgram { fingerprint: [145, 162, 10, 199, 28, 39, 21, 135, 139, 129, 230, 115, 149, 94, 62, 28, 138, 138, 88, 46, 132, 161, 227, 117, 182, 88, 126, 99, 227, 41, 96, 70], complete: false, expected: expected_program, functions: &[ +pub static PROGRAM: GeneratedProgram = GeneratedProgram { fingerprint: [16, 192, 139, 155, 202, 145, 36, 243, 93, 246, 216, 189, 250, 221, 54, 36, 149, 240, 11, 40, 210, 134, 183, 238, 67, 74, 234, 134, 107, 96, 246, 23], complete: false, expected: expected_program, functions: &[ None, None, None, @@ -1946,6 +1972,8 @@ None, None, None, None, +Some(FunctionWriter { layout: FunctionLayout { input_size: 3, selectors: 5, auxiliaries: 14, lookups: 7 }, output_size: 0, seed_words: 15, schema: &SCHEMA_261, pack: pack_261, pack_typed: pack_261_typed, write: write_261 }), +Some(FunctionWriter { layout: FunctionLayout { input_size: 2, selectors: 5, auxiliaries: 10, lookups: 5 }, output_size: 0, seed_words: 10, schema: &SCHEMA_262, pack: pack_262, pack_typed: pack_262_typed, write: write_262 }), None, None, None, diff --git a/crates/aiur/src/trace_codegen/programs/ix_aggr_cuda.rs b/crates/aiur/src/trace_codegen/programs/ix_aggr_cuda.rs index bb68b4559..cb07c9241 100644 --- a/crates/aiur/src/trace_codegen/programs/ix_aggr_cuda.rs +++ b/crates/aiur/src/trace_codegen/programs/ix_aggr_cuda.rs @@ -7,8 +7,10 @@ unsafe extern "C" { pub(super) fn aiur_trace_ix_aggr_120(device: i32, seeds: *const u8, encoding: u32, real: usize, rows: usize, width: usize, selectors: usize, auxiliaries: usize, output: *mut u64) -> i32; pub(super) fn aiur_trace_ix_aggr_223(device: i32, seeds: *const u8, encoding: u32, real: usize, rows: usize, width: usize, selectors: usize, auxiliaries: usize, output: *mut u64) -> i32; pub(super) fn aiur_trace_ix_aggr_233(device: i32, seeds: *const u8, encoding: u32, real: usize, rows: usize, width: usize, selectors: usize, auxiliaries: usize, output: *mut u64) -> i32; + pub(super) fn aiur_trace_ix_aggr_261(device: i32, seeds: *const u8, encoding: u32, real: usize, rows: usize, width: usize, selectors: usize, auxiliaries: usize, output: *mut u64) -> i32; + pub(super) fn aiur_trace_ix_aggr_262(device: i32, seeds: *const u8, encoding: u32, real: usize, rows: usize, width: usize, selectors: usize, auxiliaries: usize, output: *mut u64) -> i32; } -pub static CUDA: CudaLibrary = unsafe { CudaLibrary::new([145, 162, 10, 199, 28, 39, 21, 135, 139, 129, 230, 115, 149, 94, 62, 28, 138, 138, 88, 46, 132, 161, 227, 117, 182, 88, 126, 99, 227, 41, 96, 70], aiur_trace_ix_aggr_contract, aiur_trace_ix_aggr_schema, &[ +pub static CUDA: CudaLibrary = unsafe { CudaLibrary::new([16, 192, 139, 155, 202, 145, 36, 243, 93, 246, 216, 189, 250, 221, 54, 36, 149, 240, 11, 40, 210, 134, 183, 238, 67, 74, 234, 134, 107, 96, 246, 23], aiur_trace_ix_aggr_contract, aiur_trace_ix_aggr_schema, &[ None, None, None, @@ -270,6 +272,8 @@ None, None, None, None, +Some(aiur_trace_ix_aggr_261), +Some(aiur_trace_ix_aggr_262), None, None, None, @@ -338,4 +342,4 @@ None, None, None, None -], &[3, 67, 68, 9, 9, 0, 0, 0, 0, 0, 0, 0, 17, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 18, 12, 0, 0, 12, 0, 0, 0, 8, 6, 15, 6, 24, 6, 43, 0, 8, 0, 10, 6, 8, 6, 8, 0, 0, 8, 0, 0, 0, 12, 0, 0, 14, 0, 6, 52, 0, 0, 20, 0, 0, 17, 0, 31, 0, 0, 0, 0, 40, 170, 137, 25, 0, 0, 0, 194, 5, 146, 158, 0, 0, 0, 162, 5, 4, 6, 6, 10, 18, 11, 0, 43, 15, 8, 0, 12, 5, 0, 0, 69, 8, 8, 0, 0, 10, 0, 0, 100, 5, 7, 22, 22, 27, 201, 0, 43, 8, 6, 18, 18, 19, 15, 33, 9, 22, 8, 7, 13, 0, 27, 10, 8, 8, 0, 8, 17, 42, 8, 8, 21, 11, 3, 17, 17, 19, 20, 23, 22, 8, 6, 11, 0, 0, 16, 41, 54, 36, 10, 10, 11, 9, 30, 9, 11, 23, 0, 0, 18, 0, 43, 8, 76, 23, 23, 8, 20, 13, 6, 0, 15, 0, 6, 3, 0, 3, 0, 7, 5, 14, 24, 29, 9, 33, 81, 0, 0, 12, 2, 0, 94, 16, 15, 6, 19, 12, 4, 6, 10, 12, 11, 6, 19, 68, 48, 10, 25, 11, 78, 12, 23, 2, 7, 6, 2, 42, 4, 25, 78, 119, 59, 10, 32, 0, 0, 10, 0, 15, 99, 67, 34, 8, 6, 0, 67, 9, 9, 8, 12, 10, 4, 47, 4, 10, 15, 10, 9, 15, 11, 49, 14, 6, 23, 17, 15, 6, 14, 49, 11, 11, 17, 8, 159, 40, 62, 44, 125, 124, 116, 2, 11, 8, 25, 7, 125, 24, 95, 73, 67, 8, 8, 42, 11, 22, 16, 35, 21, 60, 68, 56, 74, 100, 93, 122, 7, 29, 6, 7, 8, 9, 10, 13, 14, 10, 5, 8, 15, 8, 7, 10, 14, 8, 14], &[16, 528, 536, 48, 72, 0, 0, 0, 0, 0, 0, 0, 136, 72, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 112, 88, 0, 0, 96, 0, 0, 0, 56, 48, 112, 48, 184, 48, 336, 0, 56, 0, 72, 48, 56, 48, 56, 0, 0, 56, 0, 0, 0, 80, 0, 0, 96, 0, 48, 352, 0, 0, 136, 0, 0, 128, 0, 176, 0, 0, 0, 0, 296, 432, 840, 160, 0, 0, 0, 848, 40, 416, 272, 0, 0, 0, 176, 32, 32, 40, 40, 64, 112, 88, 0, 312, 96, 48, 0, 72, 40, 0, 0, 304, 48, 48, 0, 0, 64, 0, 0, 120, 32, 40, 136, 128, 128, 544, 0, 312, 40, 40, 96, 112, 120, 72, 176, 56, 120, 48, 48, 96, 0, 192, 64, 48, 48, 0, 48, 120, 280, 64, 56, 144, 72, 24, 112, 112, 144, 144, 168, 160, 56, 32, 80, 0, 0, 120, 224, 336, 216, 56, 64, 64, 48, 160, 56, 64, 112, 0, 0, 112, 0, 312, 48, 464, 168, 168, 48, 104, 96, 40, 0, 104, 0, 48, 24, 0, 24, 0, 56, 40, 88, 192, 224, 72, 256, 632, 0, 0, 88, 16, 0, 616, 120, 104, 32, 120, 64, 32, 40, 64, 72, 64, 40, 128, 432, 344, 48, 160, 40, 552, 72, 168, 16, 32, 40, 16, 232, 24, 88, 576, 688, 320, 64, 184, 0, 0, 56, 0, 104, 440, 408, 272, 40, 32, 0, 528, 40, 56, 40, 56, 56, 24, 304, 24, 40, 56, 48, 40, 56, 64, 312, 48, 32, 80, 56, 48, 24, 56, 360, 88, 88, 136, 32, 784, 200, 432, 352, 840, 832, 784, 16, 64, 48, 136, 48, 824, 144, 712, 416, 408, 48, 48, 272, 56, 136, 112, 200, 144, 344, 512, 440, 408, 736, 656, 784, 48, 152, 40, 48, 48, 64, 72, 96, 96, 72, 32, 56, 104, 48, 48, 72, 104, 56, 104]) }; +], &[3, 67, 68, 9, 9, 0, 0, 0, 0, 0, 0, 0, 17, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 18, 12, 0, 0, 12, 0, 0, 0, 8, 6, 15, 6, 24, 6, 43, 0, 8, 0, 10, 6, 8, 6, 8, 0, 0, 8, 0, 0, 0, 12, 0, 0, 14, 0, 6, 52, 0, 0, 20, 0, 0, 17, 0, 31, 0, 0, 0, 0, 40, 170, 137, 25, 0, 0, 0, 194, 5, 146, 158, 0, 0, 0, 162, 5, 4, 6, 6, 10, 18, 11, 0, 43, 15, 8, 0, 12, 5, 0, 0, 69, 8, 8, 0, 0, 10, 0, 0, 100, 5, 7, 22, 22, 27, 201, 0, 43, 8, 6, 18, 18, 19, 15, 33, 9, 22, 8, 7, 13, 0, 27, 10, 8, 8, 0, 8, 17, 42, 8, 8, 21, 11, 3, 17, 17, 19, 20, 23, 22, 8, 6, 11, 0, 0, 16, 41, 54, 36, 10, 10, 11, 9, 30, 9, 11, 23, 0, 0, 18, 0, 43, 8, 76, 23, 23, 8, 20, 13, 6, 0, 15, 0, 6, 3, 0, 3, 0, 7, 5, 14, 24, 29, 9, 33, 81, 0, 0, 12, 2, 0, 94, 16, 15, 6, 19, 12, 4, 6, 10, 12, 11, 6, 19, 68, 48, 10, 25, 11, 78, 12, 23, 2, 7, 6, 2, 42, 4, 25, 78, 119, 59, 10, 32, 0, 0, 10, 0, 15, 99, 67, 34, 8, 6, 0, 67, 9, 9, 8, 12, 10, 4, 47, 4, 10, 15, 15, 10, 10, 9, 16, 11, 56, 16, 6, 23, 17, 15, 6, 14, 49, 11, 11, 17, 8, 159, 40, 62, 44, 125, 125, 116, 2, 11, 8, 25, 7, 126, 24, 95, 73, 67, 8, 8, 42, 11, 22, 16, 35, 21, 60, 68, 56, 74, 100, 93, 122, 7, 29, 6, 7, 8, 9, 10, 13, 14, 10, 5, 8, 15, 8, 7, 10, 14, 8, 14], &[16, 528, 536, 48, 72, 0, 0, 0, 0, 0, 0, 0, 136, 72, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 112, 88, 0, 0, 96, 0, 0, 0, 56, 48, 112, 48, 184, 48, 336, 0, 56, 0, 72, 48, 56, 48, 56, 0, 0, 56, 0, 0, 0, 80, 0, 0, 96, 0, 48, 352, 0, 0, 136, 0, 0, 128, 0, 176, 0, 0, 0, 0, 296, 432, 840, 160, 0, 0, 0, 848, 40, 416, 272, 0, 0, 0, 176, 32, 32, 40, 40, 64, 112, 88, 0, 312, 96, 48, 0, 72, 40, 0, 0, 304, 48, 48, 0, 0, 64, 0, 0, 120, 32, 40, 136, 128, 128, 544, 0, 312, 40, 40, 96, 112, 120, 72, 176, 56, 120, 48, 48, 96, 0, 192, 64, 48, 48, 0, 48, 120, 280, 64, 56, 144, 72, 24, 112, 112, 144, 144, 168, 160, 56, 32, 80, 0, 0, 120, 224, 336, 216, 56, 64, 64, 48, 160, 56, 64, 112, 0, 0, 112, 0, 312, 48, 464, 168, 168, 48, 104, 96, 40, 0, 104, 0, 48, 24, 0, 24, 0, 56, 40, 88, 192, 224, 72, 256, 632, 0, 0, 88, 16, 0, 616, 120, 104, 32, 120, 64, 32, 40, 64, 72, 64, 40, 128, 432, 344, 48, 160, 40, 552, 72, 168, 16, 32, 40, 16, 232, 24, 88, 576, 688, 320, 64, 184, 0, 0, 56, 0, 104, 440, 408, 272, 40, 32, 0, 528, 40, 56, 40, 56, 56, 24, 304, 24, 40, 56, 56, 40, 48, 40, 56, 64, 336, 56, 32, 80, 56, 48, 24, 56, 360, 88, 88, 136, 32, 784, 200, 432, 352, 840, 832, 784, 16, 64, 48, 136, 48, 824, 144, 712, 416, 408, 48, 48, 272, 56, 136, 112, 200, 144, 344, 512, 440, 408, 736, 656, 784, 48, 152, 40, 48, 48, 64, 72, 96, 96, 72, 32, 56, 104, 48, 48, 72, 104, 56, 104]) }; diff --git a/crates/aiur/src/trace_codegen/programs/ixvm.rs b/crates/aiur/src/trace_codegen/programs/ixvm.rs index acab682b7..5136beabd 100644 --- a/crates/aiur/src/trace_codegen/programs/ixvm.rs +++ b/crates/aiur/src/trace_codegen/programs/ixvm.rs @@ -676,13 +676,13 @@ fn write_709(seed: &[u64], offsets: RowOffsets, row: &mut [u64],) -> TraceResult static SCHEMA_709: SeedSchema = SeedSchema { widths: &[SeedWidth::Full, SeedWidth::U32, SeedWidth::U32, SeedWidth::U32, SeedWidth::Full, SeedWidth::U8, SeedWidth::Full, SeedWidth::U32, SeedWidth::Full, SeedWidth::U32, SeedWidth::U32, SeedWidth::U8, SeedWidth::Full, SeedWidth::U32, SeedWidth::Full, SeedWidth::U32, SeedWidth::U32, SeedWidth::Full, SeedWidth::U8, SeedWidth::U8, SeedWidth::U32, SeedWidth::U8, SeedWidth::U8, SeedWidth::U32], offsets: &[0, 56, 60, 64, 8, 100, 16, 68, 24, 72, 76, 101, 32, 80, 40, 84, 88, 48, 102, 103, 92, 104, 105, 96], bytes: 112 }; -fn pack_710(context: &SeedContext<'_>, seed: &mut [u64],) -> TraceResult<()> { let v_0 = context.inputs[0]; let v_1 = context.inputs[1]; let v_2 = context.inputs[2]; let v_3 = context.inputs[3]; let v_4 = context.inputs[4]; let v_5 = context.inputs[5]; let v_6 = context.inputs[6]; let v_7 = context.inputs[7]; let v_8 = context.inputs[8]; let v_9 = context.inputs[9]; let v_10 = context.inputs[10]; let v_11 = context.inputs[11]; let v_12 = context.inputs[12]; let v_13 = context.inputs[13]; let [v_14, v_15, v_16]: [G; 3] = context.load::<3>(0, v_1)?; seed[15] = v_14.as_canonical_u64(); seed[16] = v_15.as_canonical_u64(); seed[17] = v_16.as_canonical_u64(); Ok(()) } +fn pack_710(context: &SeedContext<'_>, seed: &mut [u64],) -> TraceResult<()> { let v_0 = context.inputs[0]; let v_1 = context.inputs[1]; let v_2 = context.inputs[2]; let v_3 = context.inputs[3]; let v_4 = context.inputs[4]; let v_5 = context.inputs[5]; let v_6 = context.inputs[6]; let v_7 = context.inputs[7]; let v_8 = context.inputs[8]; let v_9 = context.inputs[9]; let v_10 = context.inputs[10]; let v_11 = context.inputs[11]; let v_12 = context.inputs[12]; let v_13 = context.inputs[13]; let [v_14, v_15, v_16]: [G; 3] = context.load::<3>(0, v_1)?; seed[15] = v_14.as_canonical_u64(); seed[16] = v_15.as_canonical_u64(); seed[17] = v_16.as_canonical_u64(); match v_14.as_canonical_u64() { 1u64 => { Ok(()) }, 0u64 => { let [v_17]: [G; 1] = context.call::<1>(1, 707, &[v_15, v_8, v_9, v_10, v_11, v_12, v_13])?; seed[18] = v_17.as_canonical_u64(); Ok(()) }, _ => { Err(no_match(710, v_14)) }, } } -fn pack_710_typed(context: &SeedContext<'_>, seed: &mut TypedSeed<'_>,) -> TraceResult<()> { let v_0 = context.inputs[0]; let v_1 = context.inputs[1]; let v_2 = context.inputs[2]; let v_3 = context.inputs[3]; let v_4 = context.inputs[4]; let v_5 = context.inputs[5]; let v_6 = context.inputs[6]; let v_7 = context.inputs[7]; let v_8 = context.inputs[8]; let v_9 = context.inputs[9]; let v_10 = context.inputs[10]; let v_11 = context.inputs[11]; let v_12 = context.inputs[12]; let v_13 = context.inputs[13]; let [v_14, v_15, v_16]: [G; 3] = context.load::<3>(0, v_1)?; seed.u8(78, v_14); seed.u8(79, v_15); seed.u32(72, v_16); Ok(()) } +fn pack_710_typed(context: &SeedContext<'_>, seed: &mut TypedSeed<'_>,) -> TraceResult<()> { let v_0 = context.inputs[0]; let v_1 = context.inputs[1]; let v_2 = context.inputs[2]; let v_3 = context.inputs[3]; let v_4 = context.inputs[4]; let v_5 = context.inputs[5]; let v_6 = context.inputs[6]; let v_7 = context.inputs[7]; let v_8 = context.inputs[8]; let v_9 = context.inputs[9]; let v_10 = context.inputs[10]; let v_11 = context.inputs[11]; let v_12 = context.inputs[12]; let v_13 = context.inputs[13]; let [v_14, v_15, v_16]: [G; 3] = context.load::<3>(0, v_1)?; seed.u8(78, v_14); seed.u8(79, v_15); seed.u32(72, v_16); match v_14.as_canonical_u64() { 1u64 => { Ok(()) }, 0u64 => { let [v_17]: [G; 1] = context.call::<1>(1, 707, &[v_15, v_8, v_9, v_10, v_11, v_12, v_13])?; seed.u8(80, v_17); Ok(()) }, _ => { Err(no_match(710, v_14)) }, } } -fn write_710(seed: &[u64], offsets: RowOffsets, row: &mut [u64],) -> TraceResult<()> { let v_0 = G::from_u64(seed[1]); let v_1 = G::from_u64(seed[2]); let v_2 = G::from_u64(seed[3]); let v_3 = G::from_u64(seed[4]); let v_4 = G::from_u64(seed[5]); let v_5 = G::from_u64(seed[6]); let v_6 = G::from_u64(seed[7]); let v_7 = G::from_u64(seed[8]); let v_8 = G::from_u64(seed[9]); let v_9 = G::from_u64(seed[10]); let v_10 = G::from_u64(seed[11]); let v_11 = G::from_u64(seed[12]); let v_12 = G::from_u64(seed[13]); let v_13 = G::from_u64(seed[14]); let [v_14, v_15, v_16]: [G; 3] = [G::from_u64(seed[15]), G::from_u64(seed[16]), G::from_u64(seed[17])]; row[offsets.auxiliaries + 1] = v_14.as_canonical_u64(); row[offsets.auxiliaries + 2] = v_15.as_canonical_u64(); row[offsets.auxiliaries + 3] = v_16.as_canonical_u64(); match v_14.as_canonical_u64() { 1u64 => { row[offsets.selectors] = G::ONE.as_canonical_u64(); Ok(()) }, 0u64 => { row[offsets.selectors + 1] = G::ONE.as_canonical_u64(); Ok(()) }, _ => { Err(no_match(710, v_14)) }, } } +fn write_710(seed: &[u64], offsets: RowOffsets, row: &mut [u64],) -> TraceResult<()> { let v_0 = G::from_u64(seed[1]); let v_1 = G::from_u64(seed[2]); let v_2 = G::from_u64(seed[3]); let v_3 = G::from_u64(seed[4]); let v_4 = G::from_u64(seed[5]); let v_5 = G::from_u64(seed[6]); let v_6 = G::from_u64(seed[7]); let v_7 = G::from_u64(seed[8]); let v_8 = G::from_u64(seed[9]); let v_9 = G::from_u64(seed[10]); let v_10 = G::from_u64(seed[11]); let v_11 = G::from_u64(seed[12]); let v_12 = G::from_u64(seed[13]); let v_13 = G::from_u64(seed[14]); let [v_14, v_15, v_16]: [G; 3] = [G::from_u64(seed[15]), G::from_u64(seed[16]), G::from_u64(seed[17])]; row[offsets.auxiliaries + 1] = v_14.as_canonical_u64(); row[offsets.auxiliaries + 2] = v_15.as_canonical_u64(); row[offsets.auxiliaries + 3] = v_16.as_canonical_u64(); match v_14.as_canonical_u64() { 1u64 => { row[offsets.selectors] = G::ONE.as_canonical_u64(); Ok(()) }, 0u64 => { let [v_17]: [G; 1] = [G::from_u64(seed[18])]; row[offsets.auxiliaries + 4] = v_17.as_canonical_u64(); row[offsets.selectors + 1] = G::ONE.as_canonical_u64(); Ok(()) }, _ => { Err(no_match(710, v_14)) }, } } -static SCHEMA_710: SeedSchema = SeedSchema { widths: &[SeedWidth::Full, SeedWidth::U32, SeedWidth::U32, SeedWidth::U8, SeedWidth::Full, SeedWidth::U32, SeedWidth::Full, SeedWidth::U32, SeedWidth::U32, SeedWidth::U8, SeedWidth::Full, SeedWidth::U32, SeedWidth::Full, SeedWidth::U32, SeedWidth::U32, SeedWidth::U8, SeedWidth::U8, SeedWidth::U32], offsets: &[0, 40, 44, 76, 8, 48, 16, 52, 56, 77, 24, 60, 32, 64, 68, 78, 79, 72], bytes: 80 }; +static SCHEMA_710: SeedSchema = SeedSchema { widths: &[SeedWidth::Full, SeedWidth::U32, SeedWidth::U32, SeedWidth::U8, SeedWidth::Full, SeedWidth::U32, SeedWidth::Full, SeedWidth::U32, SeedWidth::U32, SeedWidth::U8, SeedWidth::Full, SeedWidth::U32, SeedWidth::Full, SeedWidth::U32, SeedWidth::U32, SeedWidth::U8, SeedWidth::U8, SeedWidth::U32, SeedWidth::U8], offsets: &[0, 40, 44, 76, 8, 48, 16, 52, 56, 77, 24, 60, 32, 64, 68, 78, 79, 72, 80], bytes: 88 }; fn pack_712(context: &SeedContext<'_>, seed: &mut [u64],) -> TraceResult<()> { let v_0 = context.inputs[0]; let v_1 = context.inputs[1]; let [v_2, v_3, v_4]: [G; 3] = context.load::<3>(0, v_1)?; seed[3] = v_2.as_canonical_u64(); seed[4] = v_3.as_canonical_u64(); seed[5] = v_4.as_canonical_u64(); Ok(()) } @@ -4362,7 +4362,7 @@ fn expected_function_707() -> Function { #[inline(never)] fn expected_function_708() -> Function { - Function { body: Block { ops: vec![Op::Call(707, vec![1, 8, 9, 10, 11, 12, 13], 1, false)], ctrl: Ctrl::Match(15, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::MatchContinue(14, [(G::from_u64(1), Block { ops: vec![Op::Call(707, vec![1, 2, 3, 4, 5, 6, 7], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![16], vec![17], Some("CheckEnv: walk reached a constant outside the owned set".into()))], ctrl: Ctrl::Yield(1, vec![]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::Yield(2, vec![]) })), 0, 1, 1, Box::new(Block { ops: vec![Op::Call(228, vec![1], 48, false)], ctrl: Ctrl::Match(16, [].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::MatchContinue(16, [(G::from_u64(8), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(700, vec![0, 1, 17, 17, 64], 0, false)], ctrl: Ctrl::Yield(3, vec![]) }), (G::from_u64(7), Block { ops: vec![], ctrl: Ctrl::Match(17, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(690, vec![0, 1], 0, false), Op::Call(708, vec![0, 25, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 0, false)], ctrl: Ctrl::Yield(4, vec![]) }))) }), (G::from_u64(6), Block { ops: vec![], ctrl: Ctrl::Match(17, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(690, vec![0, 1], 0, false), Op::Call(708, vec![0, 25, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 0, false)], ctrl: Ctrl::Yield(5, vec![]) }))) }), (G::from_u64(4), Block { ops: vec![], ctrl: Ctrl::Match(17, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(690, vec![0, 1], 0, false), Op::Call(708, vec![0, 33, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 0, false)], ctrl: Ctrl::Yield(6, vec![]) }))) }), (G::from_u64(5), Block { ops: vec![], ctrl: Ctrl::Match(17, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(690, vec![0, 1], 0, false), Op::Call(708, vec![0, 25, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 0, false)], ctrl: Ctrl::Yield(7, vec![]) }))) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(690, vec![0, 1], 0, false)], ctrl: Ctrl::Yield(8, vec![]) })), 0, 5, 2, Box::new(Block { ops: vec![Op::Call(696, vec![16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63], 1, false), Op::Const(G::from_u64(0)), Op::Call(709, vec![0, 62, 64, 65, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 0, false)], ctrl: Ctrl::Return(9, vec![]) })) }))) })) }))) }, layout: FunctionLayout { input_size: 15, selectors: 10, auxiliaries: 58, lookups: 8 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(707, vec![1, 8, 9, 10, 11, 12, 13], 1, false)], ctrl: Ctrl::Match(15, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(2)), Op::Sub(14, 16), Op::EqZero(17), Op::Const(G::from_u64(0)), Op::AssertEq(vec![18], vec![19], Some("Check: target is in the assumption set".into()))], ctrl: Ctrl::Return(0, vec![]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::MatchContinue(14, [(G::from_u64(1), Block { ops: vec![Op::Call(707, vec![1, 2, 3, 4, 5, 6, 7], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![16], vec![17], Some("CheckEnv: walk reached a constant outside the owned set".into()))], ctrl: Ctrl::Yield(1, vec![17]) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Const(G::from_u64(0))], ctrl: Ctrl::Yield(2, vec![16]) })), 1, 1, 1, Box::new(Block { ops: vec![Op::Call(228, vec![1], 48, false)], ctrl: Ctrl::Match(17, [].into_iter().collect(), Some(Box::new(Block { ops: vec![], ctrl: Ctrl::MatchContinue(17, [(G::from_u64(8), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Call(700, vec![0, 1, 18, 18, 65], 0, false)], ctrl: Ctrl::Yield(3, vec![]) }), (G::from_u64(7), Block { ops: vec![], ctrl: Ctrl::Match(18, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(690, vec![0, 1], 0, false), Op::Call(708, vec![0, 26, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16], 0, false)], ctrl: Ctrl::Yield(4, vec![]) }))) }), (G::from_u64(6), Block { ops: vec![], ctrl: Ctrl::Match(18, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(690, vec![0, 1], 0, false), Op::Call(708, vec![0, 26, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16], 0, false)], ctrl: Ctrl::Yield(5, vec![]) }))) }), (G::from_u64(4), Block { ops: vec![], ctrl: Ctrl::Match(18, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(690, vec![0, 1], 0, false), Op::Call(708, vec![0, 34, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16], 0, false)], ctrl: Ctrl::Yield(6, vec![]) }))) }), (G::from_u64(5), Block { ops: vec![], ctrl: Ctrl::Match(18, [].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(690, vec![0, 1], 0, false), Op::Call(708, vec![0, 26, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16], 0, false)], ctrl: Ctrl::Yield(7, vec![]) }))) })].into_iter().collect(), Some(Box::new(Block { ops: vec![Op::Call(690, vec![0, 1], 0, false)], ctrl: Ctrl::Yield(8, vec![]) })), 0, 5, 2, Box::new(Block { ops: vec![Op::Call(696, vec![17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64], 1, false), Op::Const(G::from_u64(0)), Op::Call(709, vec![0, 63, 65, 66, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16], 0, false)], ctrl: Ctrl::Return(9, vec![]) })) }))) })) }))) }, layout: FunctionLayout { input_size: 15, selectors: 10, auxiliaries: 59, lookups: 8 }, entry: false, constrained: true } } #[inline(never)] @@ -4372,12 +4372,12 @@ fn expected_function_709() -> Function { #[inline(never)] fn expected_function_710() -> Function { - Function { body: Block { ops: vec![Op::Load(3, 1)], ctrl: Ctrl::Match(14, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![]) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Call(708, vec![0, 15, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 17], 0, false), Op::Call(710, vec![0, 16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], 0, false)], ctrl: Ctrl::Return(1, vec![]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 14, selectors: 2, auxiliaries: 4, lookups: 4 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Load(3, 1)], ctrl: Ctrl::Match(14, [(G::from_u64(1), Block { ops: vec![], ctrl: Ctrl::Return(0, vec![]) }), (G::from_u64(0), Block { ops: vec![Op::Call(707, vec![15, 8, 9, 10, 11, 12, 13], 1, false), Op::Const(G::from_u64(0)), Op::AssertEq(vec![17], vec![18], Some("CheckEnv: owned constant is in the assumption set".into())), Op::Const(G::from_u64(1)), Op::Call(708, vec![0, 15, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 19], 0, false), Op::Call(710, vec![0, 16, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], 0, false)], ctrl: Ctrl::Return(1, vec![]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 14, selectors: 2, auxiliaries: 5, lookups: 5 }, entry: false, constrained: true } } #[inline(never)] fn expected_function_711() -> Function { - Function { body: Block { ops: vec![], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![Op::Call(705, vec![1], 1, false), Op::Call(471, vec![0], 6, false)], ctrl: Ctrl::Match(5, [(G::from_u64(1), Block { ops: vec![Op::Call(713, vec![0, 4], 0, false)], ctrl: Ctrl::Return(0, vec![]) }), (G::from_u64(0), Block { ops: vec![Op::Call(712, vec![0, 4], 0, false)], ctrl: Ctrl::Return(1, vec![]) })].into_iter().collect(), None) }), (G::from_u64(0), Block { ops: vec![Op::Call(705, vec![1], 1, false), Op::Const(G::from_u64(1)), Op::Call(706, vec![4, 5, 5, 5, 5, 5, 5], 6, false), Op::Call(705, vec![3], 1, false), Op::Call(706, vec![12, 5, 5, 5, 5, 5, 5], 6, false), Op::Call(710, vec![0, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18], 0, false)], ctrl: Ctrl::Return(2, vec![]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 4, selectors: 3, auxiliaries: 15, lookups: 6 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Call(705, vec![1], 1, false), Op::Load(3, 4)], ctrl: Ctrl::MatchContinue(5, [(G::from_u64(1), Block { ops: vec![Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::AssertEq(vec![8], vec![9], Some("CheckEnv: owned tree has no leaves".into()))], ctrl: Ctrl::Yield(0, vec![]) }), (G::from_u64(0), Block { ops: vec![], ctrl: Ctrl::Yield(1, vec![]) })].into_iter().collect(), None, 0, 0, 0, Box::new(Block { ops: vec![], ctrl: Ctrl::Match(2, [(G::from_u64(1), Block { ops: vec![Op::Call(471, vec![0], 6, false)], ctrl: Ctrl::Match(8, [(G::from_u64(1), Block { ops: vec![Op::Call(713, vec![0, 4], 0, false)], ctrl: Ctrl::Return(2, vec![]) }), (G::from_u64(0), Block { ops: vec![Op::Call(712, vec![0, 4], 0, false)], ctrl: Ctrl::Return(3, vec![]) })].into_iter().collect(), None) }), (G::from_u64(0), Block { ops: vec![Op::Const(G::from_u64(1)), Op::Call(706, vec![4, 8, 8, 8, 8, 8, 8], 6, false), Op::Call(705, vec![3], 1, false), Op::Call(706, vec![15, 8, 8, 8, 8, 8, 8], 6, false), Op::Call(710, vec![0, 4, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21], 0, false)], ctrl: Ctrl::Return(4, vec![]) })].into_iter().collect(), None) })) }, layout: FunctionLayout { input_size: 4, selectors: 5, auxiliaries: 18, lookups: 7 }, entry: false, constrained: true } } #[inline(never)] @@ -4582,7 +4582,7 @@ fn expected_function_751() -> Function { #[inline(never)] fn expected_function_752() -> Function { - Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::IOGetInfo(8, vec![0, 1, 2, 3, 4, 5, 6, 7]), Op::Call(5, vec![8, 9, 10], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Const(G::from_u64(1)), Op::Store(vec![43, 43, 43]), Op::Store(vec![8, 8, 8, 8, 8, 8, 8, 8]), Op::Store(vec![12, 13, 14, 15, 16, 17, 12, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42]), Op::Store(vec![43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43]), Op::Call(26, vec![11, 44, 8, 8, 45, 46, 47], 1, false), Op::Call(25, vec![48], 32, false), Op::Const(G::from_u64(256)), Op::Mul(81, 50), Op::Add(49, 82), Op::Const(G::from_u64(65536)), Op::Mul(84, 51), Op::Add(83, 85), Op::Const(G::from_u64(16777216)), Op::Mul(87, 52), Op::Add(86, 88), Op::Mul(81, 54), Op::Add(53, 90), Op::Mul(84, 55), Op::Add(91, 92), Op::Mul(87, 56), Op::Add(93, 94), Op::Mul(81, 58), Op::Add(57, 96), Op::Mul(84, 59), Op::Add(97, 98), Op::Mul(87, 60), Op::Add(99, 100), Op::Mul(81, 62), Op::Add(61, 102), Op::Mul(84, 63), Op::Add(103, 104), Op::Mul(87, 64), Op::Add(105, 106), Op::Mul(81, 66), Op::Add(65, 108), Op::Mul(84, 67), Op::Add(109, 110), Op::Mul(87, 68), Op::Add(111, 112), Op::Mul(81, 70), Op::Add(69, 114), Op::Mul(84, 71), Op::Add(115, 116), Op::Mul(87, 72), Op::Add(117, 118), Op::Mul(81, 74), Op::Add(73, 120), Op::Mul(84, 75), Op::Add(121, 122), Op::Mul(87, 76), Op::Add(123, 124), Op::Mul(81, 78), Op::Add(77, 126), Op::Mul(84, 79), Op::Add(127, 128), Op::Mul(87, 80), Op::Add(129, 130), Op::AssertEq(vec![89, 95, 101, 107, 113, 119, 125, 131], vec![0, 1, 2, 3, 4, 5, 6, 7], Some("claim: bytes do not hash to the public claim digest".into())), Op::Call(85, vec![11], 10, false), Op::AssertEq(vec![132], vec![29], Some("claim: wrong TagN flag".into())), Op::Add(134, 135), Op::Add(142, 136), Op::Add(143, 137), Op::Add(144, 138), Op::Add(145, 139), Op::Add(146, 140), Op::AssertEq(vec![147], vec![8], Some("claim: TagN size exceeds a single byte".into())), Op::Call(83, vec![141], 2, false), Op::Const(G::from_u64(4)), Op::AssertEq(vec![148], vec![150], Some("claim: unsupported object format".into())), Op::Call(83, vec![149], 2, false)], ctrl: Ctrl::Match(133, [(G::from_u64(3), Block { ops: vec![Op::Const(G::from_u64(3)), Op::AssertEq(vec![151], vec![153], Some("claim: wrong validator identity".into())), Op::Call(718, vec![152], 4, false), Op::Call(468, vec![154, 153, 155, 156], 1, false), Op::Call(102, vec![157], 2, false), Op::Call(102, vec![160], 2, false), Op::Call(717, vec![162], 3, false), Op::Load(3, 165), Op::Const(G::from_u64(1)), Op::AssertEq(vec![166, 167, 168], vec![169, 169, 169], Some("claim: trailing bytes".into())), Op::Call(757, vec![158, 159, 161, 163, 164], 0, false)], ctrl: Ctrl::Return(0, vec![]) }), (G::from_u64(4), Block { ops: vec![Op::Call(718, vec![152], 4, false), Op::Call(468, vec![153, 151, 154, 155], 1, false), Op::Call(102, vec![156], 2, false), Op::Call(717, vec![159], 3, false), Op::Load(3, 162), Op::Const(G::from_u64(1)), Op::AssertEq(vec![163, 164, 165], vec![166, 166, 166], Some("claim: trailing bytes".into()))], ctrl: Ctrl::Match(160, [(G::from_u64(1), Block { ops: vec![Op::Call(698, vec![157, 158], 0, false)], ctrl: Ctrl::Return(1, vec![]) }), (G::from_u64(0), Block { ops: vec![Op::Call(705, vec![161], 1, false), Op::Const(G::from_u64(1)), Op::Call(706, vec![167, 168, 168, 168, 168, 168, 168], 6, false), Op::Const(G::from_u64(0)), Op::Call(708, vec![157, 158, 168, 168, 168, 168, 168, 168, 169, 170, 171, 172, 173, 174, 175], 0, false)], ctrl: Ctrl::Return(2, vec![]) })].into_iter().collect(), None) }), (G::from_u64(5), Block { ops: vec![Op::Call(718, vec![152], 4, false), Op::Call(468, vec![153, 151, 154, 155], 1, false), Op::Call(102, vec![156], 2, false), Op::Call(717, vec![159], 3, false), Op::Load(3, 162), Op::Const(G::from_u64(1)), Op::AssertEq(vec![163, 164, 165], vec![166, 166, 166], Some("claim: trailing bytes".into())), Op::Call(711, vec![157, 158, 160, 161], 0, false)], ctrl: Ctrl::Return(3, vec![]) }), (G::from_u64(6), Block { ops: vec![Op::Const(G::from_u64(0)), Op::AssertEq(vec![151], vec![153], Some("claim: wrong validator identity".into())), Op::Call(102, vec![152], 2, false), Op::Call(733, vec![155], 55, false), Op::Load(3, 210), Op::Const(G::from_u64(1)), Op::AssertEq(vec![211, 212, 213], vec![214, 214, 214], Some("claim: trailing bytes".into())), Op::Call(750, vec![154, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209], 0, false)], ctrl: Ctrl::Return(4, vec![]) }), (G::from_u64(7), Block { ops: vec![Op::Const(G::from_u64(0)), Op::AssertEq(vec![151], vec![153], Some("claim: wrong validator identity".into())), Op::Call(102, vec![152], 2, false), Op::Call(102, vec![155], 2, false), Op::Load(3, 157), Op::Const(G::from_u64(1)), Op::AssertEq(vec![158, 159, 160], vec![161, 161, 161], Some("claim: trailing bytes".into())), Op::Call(751, vec![154, 156], 0, false)], ctrl: Ctrl::Return(5, vec![]) }), (G::from_u64(9), Block { ops: vec![Op::Const(G::from_u64(2)), Op::AssertEq(vec![151], vec![153], Some("claim: wrong resource validator identity".into())), Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::AssertEq(vec![154], vec![155], Some("claim: resource-v1 is not implemented by IxVM".into()))], ctrl: Ctrl::Return(6, vec![]) }), (G::from_u64(10), Block { ops: vec![Op::Const(G::from_u64(3)), Op::AssertEq(vec![151], vec![153], Some("claim: wrong validator identity".into())), Op::Call(102, vec![152], 2, false), Op::Const(G::from_u64(109)), Op::Const(G::from_u64(2)), Op::Const(G::from_u64(21)), Op::Const(G::from_u64(62)), Op::Const(G::from_u64(163)), Op::Const(G::from_u64(17)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(239)), Op::Const(G::from_u64(209)), Op::Const(G::from_u64(119)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(73)), Op::Const(G::from_u64(178)), Op::Const(G::from_u64(238)), Op::Const(G::from_u64(95)), Op::Const(G::from_u64(49)), Op::Const(G::from_u64(231)), Op::Const(G::from_u64(154)), Op::Const(G::from_u64(11)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(149)), Op::Const(G::from_u64(229)), Op::Const(G::from_u64(51)), Op::Const(G::from_u64(160)), Op::Const(G::from_u64(141)), Op::Const(G::from_u64(20)), Op::Const(G::from_u64(252)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(87)), Op::Const(G::from_u64(45)), Op::Store(vec![156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 167, 181, 182, 183, 184, 185, 178]), Op::Call(2, vec![154, 186], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![187], vec![188], Some("claim: specification digest is not this circuit's".into())), Op::Call(102, vec![155], 2, false), Op::Load(3, 190), Op::AssertEq(vec![191, 192, 193], vec![188, 188, 188], Some("claim: trailing bytes".into())), Op::Call(488, vec![189], 0, false)], ctrl: Ctrl::Return(7, vec![]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 8, selectors: 8, auxiliaries: 115, lookups: 19 }, entry: false, constrained: true } + Function { body: Block { ops: vec![Op::Const(G::from_u64(0)), Op::IOGetInfo(8, vec![0, 1, 2, 3, 4, 5, 6, 7]), Op::Call(5, vec![8, 9, 10], 1, false), Op::Const(G::from_u64(103)), Op::Const(G::from_u64(230)), Op::Const(G::from_u64(9)), Op::Const(G::from_u64(106)), Op::Const(G::from_u64(133)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(187)), Op::Const(G::from_u64(114)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(110)), Op::Const(G::from_u64(60)), Op::Const(G::from_u64(58)), Op::Const(G::from_u64(245)), Op::Const(G::from_u64(79)), Op::Const(G::from_u64(165)), Op::Const(G::from_u64(127)), Op::Const(G::from_u64(82)), Op::Const(G::from_u64(14)), Op::Const(G::from_u64(81)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(104)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(155)), Op::Const(G::from_u64(171)), Op::Const(G::from_u64(217)), Op::Const(G::from_u64(131)), Op::Const(G::from_u64(31)), Op::Const(G::from_u64(25)), Op::Const(G::from_u64(205)), Op::Const(G::from_u64(224)), Op::Const(G::from_u64(91)), Op::Const(G::from_u64(1)), Op::Store(vec![43, 43, 43]), Op::Store(vec![8, 8, 8, 8, 8, 8, 8, 8]), Op::Store(vec![12, 13, 14, 15, 16, 17, 12, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42]), Op::Store(vec![43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43]), Op::Call(26, vec![11, 44, 8, 8, 45, 46, 47], 1, false), Op::Call(25, vec![48], 32, false), Op::Const(G::from_u64(256)), Op::Mul(81, 50), Op::Add(49, 82), Op::Const(G::from_u64(65536)), Op::Mul(84, 51), Op::Add(83, 85), Op::Const(G::from_u64(16777216)), Op::Mul(87, 52), Op::Add(86, 88), Op::Mul(81, 54), Op::Add(53, 90), Op::Mul(84, 55), Op::Add(91, 92), Op::Mul(87, 56), Op::Add(93, 94), Op::Mul(81, 58), Op::Add(57, 96), Op::Mul(84, 59), Op::Add(97, 98), Op::Mul(87, 60), Op::Add(99, 100), Op::Mul(81, 62), Op::Add(61, 102), Op::Mul(84, 63), Op::Add(103, 104), Op::Mul(87, 64), Op::Add(105, 106), Op::Mul(81, 66), Op::Add(65, 108), Op::Mul(84, 67), Op::Add(109, 110), Op::Mul(87, 68), Op::Add(111, 112), Op::Mul(81, 70), Op::Add(69, 114), Op::Mul(84, 71), Op::Add(115, 116), Op::Mul(87, 72), Op::Add(117, 118), Op::Mul(81, 74), Op::Add(73, 120), Op::Mul(84, 75), Op::Add(121, 122), Op::Mul(87, 76), Op::Add(123, 124), Op::Mul(81, 78), Op::Add(77, 126), Op::Mul(84, 79), Op::Add(127, 128), Op::Mul(87, 80), Op::Add(129, 130), Op::AssertEq(vec![89, 95, 101, 107, 113, 119, 125, 131], vec![0, 1, 2, 3, 4, 5, 6, 7], Some("claim: bytes do not hash to the public claim digest".into())), Op::Call(85, vec![11], 10, false), Op::AssertEq(vec![132], vec![29], Some("claim: wrong TagN flag".into())), Op::Add(134, 135), Op::Add(142, 136), Op::Add(143, 137), Op::Add(144, 138), Op::Add(145, 139), Op::Add(146, 140), Op::AssertEq(vec![147], vec![8], Some("claim: TagN size exceeds a single byte".into())), Op::Call(83, vec![141], 2, false), Op::Const(G::from_u64(4)), Op::AssertEq(vec![148], vec![150], Some("claim: unsupported object format".into())), Op::Call(83, vec![149], 2, false)], ctrl: Ctrl::Match(133, [(G::from_u64(3), Block { ops: vec![Op::Const(G::from_u64(3)), Op::AssertEq(vec![151], vec![153], Some("claim: wrong validator identity".into())), Op::Call(718, vec![152], 4, false), Op::Call(468, vec![154, 153, 155, 156], 1, false), Op::Call(102, vec![157], 2, false), Op::Call(102, vec![160], 2, false), Op::Call(717, vec![162], 3, false), Op::Load(3, 165), Op::Const(G::from_u64(1)), Op::AssertEq(vec![166, 167, 168], vec![169, 169, 169], Some("claim: trailing bytes".into())), Op::Call(757, vec![158, 159, 161, 163, 164], 0, false)], ctrl: Ctrl::Return(0, vec![]) }), (G::from_u64(4), Block { ops: vec![Op::Call(718, vec![152], 4, false), Op::Call(468, vec![153, 151, 154, 155], 1, false), Op::Call(102, vec![156], 2, false), Op::Call(717, vec![159], 3, false), Op::Load(3, 162), Op::Const(G::from_u64(1)), Op::AssertEq(vec![163, 164, 165], vec![166, 166, 166], Some("claim: trailing bytes".into()))], ctrl: Ctrl::Match(160, [(G::from_u64(1), Block { ops: vec![Op::Call(698, vec![157, 158], 0, false)], ctrl: Ctrl::Return(1, vec![]) }), (G::from_u64(0), Block { ops: vec![Op::Call(705, vec![161], 1, false), Op::Const(G::from_u64(1)), Op::Call(706, vec![167, 168, 168, 168, 168, 168, 168], 6, false), Op::Const(G::from_u64(2)), Op::Call(708, vec![157, 158, 168, 168, 168, 168, 168, 168, 169, 170, 171, 172, 173, 174, 175], 0, false)], ctrl: Ctrl::Return(2, vec![]) })].into_iter().collect(), None) }), (G::from_u64(5), Block { ops: vec![Op::Call(718, vec![152], 4, false), Op::Call(468, vec![153, 151, 154, 155], 1, false), Op::Call(102, vec![156], 2, false), Op::Call(717, vec![159], 3, false), Op::Load(3, 162), Op::Const(G::from_u64(1)), Op::AssertEq(vec![163, 164, 165], vec![166, 166, 166], Some("claim: trailing bytes".into())), Op::Call(711, vec![157, 158, 160, 161], 0, false)], ctrl: Ctrl::Return(3, vec![]) }), (G::from_u64(6), Block { ops: vec![Op::Const(G::from_u64(0)), Op::AssertEq(vec![151], vec![153], Some("claim: wrong validator identity".into())), Op::Call(102, vec![152], 2, false), Op::Call(733, vec![155], 55, false), Op::Load(3, 210), Op::Const(G::from_u64(1)), Op::AssertEq(vec![211, 212, 213], vec![214, 214, 214], Some("claim: trailing bytes".into())), Op::Call(750, vec![154, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209], 0, false)], ctrl: Ctrl::Return(4, vec![]) }), (G::from_u64(7), Block { ops: vec![Op::Const(G::from_u64(0)), Op::AssertEq(vec![151], vec![153], Some("claim: wrong validator identity".into())), Op::Call(102, vec![152], 2, false), Op::Call(102, vec![155], 2, false), Op::Load(3, 157), Op::Const(G::from_u64(1)), Op::AssertEq(vec![158, 159, 160], vec![161, 161, 161], Some("claim: trailing bytes".into())), Op::Call(751, vec![154, 156], 0, false)], ctrl: Ctrl::Return(5, vec![]) }), (G::from_u64(9), Block { ops: vec![Op::Const(G::from_u64(2)), Op::AssertEq(vec![151], vec![153], Some("claim: wrong resource validator identity".into())), Op::Const(G::from_u64(0)), Op::Const(G::from_u64(1)), Op::AssertEq(vec![154], vec![155], Some("claim: resource-v1 is not implemented by IxVM".into()))], ctrl: Ctrl::Return(6, vec![]) }), (G::from_u64(10), Block { ops: vec![Op::Const(G::from_u64(3)), Op::AssertEq(vec![151], vec![153], Some("claim: wrong validator identity".into())), Op::Call(102, vec![152], 2, false), Op::Const(G::from_u64(109)), Op::Const(G::from_u64(2)), Op::Const(G::from_u64(21)), Op::Const(G::from_u64(62)), Op::Const(G::from_u64(163)), Op::Const(G::from_u64(17)), Op::Const(G::from_u64(243)), Op::Const(G::from_u64(239)), Op::Const(G::from_u64(209)), Op::Const(G::from_u64(119)), Op::Const(G::from_u64(140)), Op::Const(G::from_u64(73)), Op::Const(G::from_u64(178)), Op::Const(G::from_u64(238)), Op::Const(G::from_u64(95)), Op::Const(G::from_u64(49)), Op::Const(G::from_u64(231)), Op::Const(G::from_u64(154)), Op::Const(G::from_u64(11)), Op::Const(G::from_u64(174)), Op::Const(G::from_u64(149)), Op::Const(G::from_u64(229)), Op::Const(G::from_u64(51)), Op::Const(G::from_u64(160)), Op::Const(G::from_u64(141)), Op::Const(G::from_u64(20)), Op::Const(G::from_u64(252)), Op::Const(G::from_u64(5)), Op::Const(G::from_u64(87)), Op::Const(G::from_u64(45)), Op::Store(vec![156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 167, 181, 182, 183, 184, 185, 178]), Op::Call(2, vec![154, 186], 1, false), Op::Const(G::from_u64(1)), Op::AssertEq(vec![187], vec![188], Some("claim: specification digest is not this circuit's".into())), Op::Call(102, vec![155], 2, false), Op::Load(3, 190), Op::AssertEq(vec![191, 192, 193], vec![188, 188, 188], Some("claim: trailing bytes".into())), Op::Call(488, vec![189], 0, false)], ctrl: Ctrl::Return(7, vec![]) })].into_iter().collect(), None) }, layout: FunctionLayout { input_size: 8, selectors: 8, auxiliaries: 115, lookups: 19 }, entry: false, constrained: true } } #[inline(never)] @@ -4746,9 +4746,9 @@ fn expected_function_784() -> Function { } fn expected_program() -> Toplevel { - Toplevel { functions: vec![expected_function_0(), expected_function_1(), expected_function_2(), expected_function_3(), expected_function_4(), expected_function_5(), expected_function_6(), expected_function_7(), expected_function_8(), expected_function_9(), expected_function_10(), expected_function_11(), expected_function_12(), expected_function_13(), expected_function_14(), expected_function_15(), expected_function_16(), expected_function_17(), expected_function_18(), expected_function_19(), expected_function_20(), expected_function_21(), expected_function_22(), expected_function_23(), expected_function_24(), expected_function_25(), expected_function_26(), expected_function_27(), expected_function_28(), expected_function_29(), expected_function_30(), expected_function_31(), expected_function_32(), expected_function_33(), expected_function_34(), expected_function_35(), expected_function_36(), expected_function_37(), expected_function_38(), expected_function_39(), expected_function_40(), expected_function_41(), expected_function_42(), expected_function_43(), expected_function_44(), expected_function_45(), expected_function_46(), expected_function_47(), expected_function_48(), expected_function_49(), expected_function_50(), expected_function_51(), expected_function_52(), expected_function_53(), expected_function_54(), expected_function_55(), expected_function_56(), expected_function_57(), expected_function_58(), expected_function_59(), expected_function_60(), expected_function_61(), expected_function_62(), expected_function_63(), expected_function_64(), expected_function_65(), expected_function_66(), expected_function_67(), expected_function_68(), expected_function_69(), expected_function_70(), expected_function_71(), expected_function_72(), expected_function_73(), expected_function_74(), expected_function_75(), expected_function_76(), expected_function_77(), expected_function_78(), expected_function_79(), expected_function_80(), expected_function_81(), expected_function_82(), expected_function_83(), expected_function_84(), expected_function_85(), expected_function_86(), expected_function_87(), expected_function_88(), expected_function_89(), expected_function_90(), expected_function_91(), expected_function_92(), expected_function_93(), expected_function_94(), expected_function_95(), expected_function_96(), expected_function_97(), expected_function_98(), expected_function_99(), expected_function_100(), expected_function_101(), expected_function_102(), expected_function_103(), expected_function_104(), expected_function_105(), expected_function_106(), expected_function_107(), expected_function_108(), expected_function_109(), expected_function_110(), expected_function_111(), expected_function_112(), expected_function_113(), expected_function_114(), expected_function_115(), expected_function_116(), expected_function_117(), expected_function_118(), expected_function_119(), expected_function_120(), expected_function_121(), expected_function_122(), expected_function_123(), expected_function_124(), expected_function_125(), expected_function_126(), expected_function_127(), expected_function_128(), expected_function_129(), expected_function_130(), expected_function_131(), expected_function_132(), expected_function_133(), expected_function_134(), expected_function_135(), expected_function_136(), expected_function_137(), expected_function_138(), expected_function_139(), expected_function_140(), expected_function_141(), expected_function_142(), expected_function_143(), expected_function_144(), expected_function_145(), expected_function_146(), expected_function_147(), expected_function_148(), expected_function_149(), expected_function_150(), expected_function_151(), expected_function_152(), expected_function_153(), expected_function_154(), expected_function_155(), expected_function_156(), expected_function_157(), expected_function_158(), expected_function_159(), expected_function_160(), expected_function_161(), expected_function_162(), expected_function_163(), expected_function_164(), expected_function_165(), expected_function_166(), expected_function_167(), expected_function_168(), expected_function_169(), expected_function_170(), expected_function_171(), expected_function_172(), expected_function_173(), expected_function_174(), expected_function_175(), expected_function_176(), expected_function_177(), expected_function_178(), expected_function_179(), expected_function_180(), expected_function_181(), expected_function_182(), expected_function_183(), expected_function_184(), expected_function_185(), expected_function_186(), expected_function_187(), expected_function_188(), expected_function_189(), expected_function_190(), expected_function_191(), expected_function_192(), expected_function_193(), expected_function_194(), expected_function_195(), expected_function_196(), expected_function_197(), expected_function_198(), expected_function_199(), expected_function_200(), expected_function_201(), expected_function_202(), expected_function_203(), expected_function_204(), expected_function_205(), expected_function_206(), expected_function_207(), expected_function_208(), expected_function_209(), expected_function_210(), expected_function_211(), expected_function_212(), expected_function_213(), expected_function_214(), expected_function_215(), expected_function_216(), expected_function_217(), expected_function_218(), expected_function_219(), expected_function_220(), expected_function_221(), expected_function_222(), expected_function_223(), expected_function_224(), expected_function_225(), expected_function_226(), expected_function_227(), expected_function_228(), expected_function_229(), expected_function_230(), expected_function_231(), expected_function_232(), expected_function_233(), expected_function_234(), expected_function_235(), expected_function_236(), expected_function_237(), expected_function_238(), expected_function_239(), expected_function_240(), expected_function_241(), expected_function_242(), expected_function_243(), expected_function_244(), expected_function_245(), expected_function_246(), expected_function_247(), expected_function_248(), expected_function_249(), expected_function_250(), expected_function_251(), expected_function_252(), expected_function_253(), expected_function_254(), expected_function_255(), expected_function_256(), expected_function_257(), expected_function_258(), expected_function_259(), expected_function_260(), expected_function_261(), expected_function_262(), expected_function_263(), expected_function_264(), expected_function_265(), expected_function_266(), expected_function_267(), expected_function_268(), expected_function_269(), expected_function_270(), expected_function_271(), expected_function_272(), expected_function_273(), expected_function_274(), expected_function_275(), expected_function_276(), expected_function_277(), expected_function_278(), expected_function_279(), expected_function_280(), expected_function_281(), expected_function_282(), expected_function_283(), expected_function_284(), expected_function_285(), expected_function_286(), expected_function_287(), expected_function_288(), expected_function_289(), expected_function_290(), expected_function_291(), expected_function_292(), expected_function_293(), expected_function_294(), expected_function_295(), expected_function_296(), expected_function_297(), expected_function_298(), expected_function_299(), expected_function_300(), expected_function_301(), expected_function_302(), expected_function_303(), expected_function_304(), expected_function_305(), expected_function_306(), expected_function_307(), expected_function_308(), expected_function_309(), expected_function_310(), expected_function_311(), expected_function_312(), expected_function_313(), expected_function_314(), expected_function_315(), expected_function_316(), expected_function_317(), expected_function_318(), expected_function_319(), expected_function_320(), expected_function_321(), expected_function_322(), expected_function_323(), expected_function_324(), expected_function_325(), expected_function_326(), expected_function_327(), expected_function_328(), expected_function_329(), expected_function_330(), expected_function_331(), expected_function_332(), expected_function_333(), expected_function_334(), expected_function_335(), expected_function_336(), expected_function_337(), expected_function_338(), expected_function_339(), expected_function_340(), expected_function_341(), expected_function_342(), expected_function_343(), expected_function_344(), expected_function_345(), expected_function_346(), expected_function_347(), expected_function_348(), expected_function_349(), expected_function_350(), expected_function_351(), expected_function_352(), expected_function_353(), expected_function_354(), expected_function_355(), expected_function_356(), expected_function_357(), expected_function_358(), expected_function_359(), expected_function_360(), expected_function_361(), expected_function_362(), expected_function_363(), expected_function_364(), expected_function_365(), expected_function_366(), expected_function_367(), expected_function_368(), expected_function_369(), expected_function_370(), expected_function_371(), expected_function_372(), expected_function_373(), expected_function_374(), expected_function_375(), expected_function_376(), expected_function_377(), expected_function_378(), expected_function_379(), expected_function_380(), expected_function_381(), expected_function_382(), expected_function_383(), expected_function_384(), expected_function_385(), expected_function_386(), expected_function_387(), expected_function_388(), expected_function_389(), expected_function_390(), expected_function_391(), expected_function_392(), expected_function_393(), expected_function_394(), expected_function_395(), expected_function_396(), expected_function_397(), expected_function_398(), expected_function_399(), expected_function_400(), expected_function_401(), expected_function_402(), expected_function_403(), expected_function_404(), expected_function_405(), expected_function_406(), expected_function_407(), expected_function_408(), expected_function_409(), expected_function_410(), expected_function_411(), expected_function_412(), expected_function_413(), expected_function_414(), expected_function_415(), expected_function_416(), expected_function_417(), expected_function_418(), expected_function_419(), expected_function_420(), expected_function_421(), expected_function_422(), expected_function_423(), expected_function_424(), expected_function_425(), expected_function_426(), expected_function_427(), expected_function_428(), expected_function_429(), expected_function_430(), expected_function_431(), expected_function_432(), expected_function_433(), expected_function_434(), expected_function_435(), expected_function_436(), expected_function_437(), expected_function_438(), expected_function_439(), expected_function_440(), expected_function_441(), expected_function_442(), expected_function_443(), expected_function_444(), expected_function_445(), expected_function_446(), expected_function_447(), expected_function_448(), expected_function_449(), expected_function_450(), expected_function_451(), expected_function_452(), expected_function_453(), expected_function_454(), expected_function_455(), expected_function_456(), expected_function_457(), expected_function_458(), expected_function_459(), expected_function_460(), expected_function_461(), expected_function_462(), expected_function_463(), expected_function_464(), expected_function_465(), expected_function_466(), expected_function_467(), expected_function_468(), expected_function_469(), expected_function_470(), expected_function_471(), expected_function_472(), expected_function_473(), expected_function_474(), expected_function_475(), expected_function_476(), expected_function_477(), expected_function_478(), expected_function_479(), expected_function_480(), expected_function_481(), expected_function_482(), expected_function_483(), expected_function_484(), expected_function_485(), expected_function_486(), expected_function_487(), expected_function_488(), expected_function_489(), expected_function_490(), expected_function_491(), expected_function_492(), expected_function_493(), expected_function_494(), expected_function_495(), expected_function_496(), expected_function_497(), expected_function_498(), expected_function_499(), expected_function_500(), expected_function_501(), expected_function_502(), expected_function_503(), expected_function_504(), expected_function_505(), expected_function_506(), expected_function_507(), expected_function_508(), expected_function_509(), expected_function_510(), expected_function_511(), expected_function_512(), expected_function_513(), expected_function_514(), expected_function_515(), expected_function_516(), expected_function_517(), expected_function_518(), expected_function_519(), expected_function_520(), expected_function_521(), expected_function_522(), expected_function_523(), expected_function_524(), expected_function_525(), expected_function_526(), expected_function_527(), expected_function_528(), expected_function_529(), expected_function_530(), expected_function_531(), expected_function_532(), expected_function_533(), expected_function_534(), expected_function_535(), expected_function_536(), expected_function_537(), expected_function_538(), expected_function_539(), expected_function_540(), expected_function_541(), expected_function_542(), expected_function_543(), expected_function_544(), expected_function_545(), expected_function_546(), expected_function_547(), expected_function_548(), expected_function_549(), expected_function_550(), expected_function_551(), expected_function_552(), expected_function_553(), expected_function_554(), expected_function_555(), expected_function_556(), expected_function_557(), expected_function_558(), expected_function_559(), expected_function_560(), expected_function_561(), expected_function_562(), expected_function_563(), expected_function_564(), expected_function_565(), expected_function_566(), expected_function_567(), expected_function_568(), expected_function_569(), expected_function_570(), expected_function_571(), expected_function_572(), expected_function_573(), expected_function_574(), expected_function_575(), expected_function_576(), expected_function_577(), expected_function_578(), expected_function_579(), expected_function_580(), expected_function_581(), expected_function_582(), expected_function_583(), expected_function_584(), expected_function_585(), expected_function_586(), expected_function_587(), expected_function_588(), expected_function_589(), expected_function_590(), expected_function_591(), expected_function_592(), expected_function_593(), expected_function_594(), expected_function_595(), expected_function_596(), expected_function_597(), expected_function_598(), expected_function_599(), expected_function_600(), expected_function_601(), expected_function_602(), expected_function_603(), expected_function_604(), expected_function_605(), expected_function_606(), expected_function_607(), expected_function_608(), expected_function_609(), expected_function_610(), expected_function_611(), expected_function_612(), expected_function_613(), expected_function_614(), expected_function_615(), expected_function_616(), expected_function_617(), expected_function_618(), expected_function_619(), expected_function_620(), expected_function_621(), expected_function_622(), expected_function_623(), expected_function_624(), expected_function_625(), expected_function_626(), expected_function_627(), expected_function_628(), expected_function_629(), expected_function_630(), expected_function_631(), expected_function_632(), expected_function_633(), expected_function_634(), expected_function_635(), expected_function_636(), expected_function_637(), expected_function_638(), expected_function_639(), expected_function_640(), expected_function_641(), expected_function_642(), expected_function_643(), expected_function_644(), expected_function_645(), expected_function_646(), expected_function_647(), expected_function_648(), expected_function_649(), expected_function_650(), expected_function_651(), expected_function_652(), expected_function_653(), expected_function_654(), expected_function_655(), expected_function_656(), expected_function_657(), expected_function_658(), expected_function_659(), expected_function_660(), expected_function_661(), expected_function_662(), expected_function_663(), expected_function_664(), expected_function_665(), expected_function_666(), expected_function_667(), expected_function_668(), expected_function_669(), expected_function_670(), expected_function_671(), expected_function_672(), expected_function_673(), expected_function_674(), expected_function_675(), expected_function_676(), expected_function_677(), expected_function_678(), expected_function_679(), expected_function_680(), expected_function_681(), expected_function_682(), expected_function_683(), expected_function_684(), expected_function_685(), expected_function_686(), expected_function_687(), expected_function_688(), expected_function_689(), expected_function_690(), expected_function_691(), expected_function_692(), expected_function_693(), expected_function_694(), expected_function_695(), expected_function_696(), expected_function_697(), expected_function_698(), expected_function_699(), expected_function_700(), expected_function_701(), expected_function_702(), expected_function_703(), expected_function_704(), expected_function_705(), expected_function_706(), expected_function_707(), expected_function_708(), expected_function_709(), expected_function_710(), expected_function_711(), expected_function_712(), expected_function_713(), expected_function_714(), expected_function_715(), expected_function_716(), expected_function_717(), expected_function_718(), expected_function_719(), expected_function_720(), expected_function_721(), expected_function_722(), expected_function_723(), expected_function_724(), expected_function_725(), expected_function_726(), expected_function_727(), expected_function_728(), expected_function_729(), expected_function_730(), expected_function_731(), expected_function_732(), expected_function_733(), expected_function_734(), expected_function_735(), expected_function_736(), expected_function_737(), expected_function_738(), expected_function_739(), expected_function_740(), expected_function_741(), expected_function_742(), expected_function_743(), expected_function_744(), expected_function_745(), expected_function_746(), expected_function_747(), expected_function_748(), expected_function_749(), expected_function_750(), expected_function_751(), expected_function_752(), expected_function_753(), expected_function_754(), expected_function_755(), expected_function_756(), expected_function_757(), expected_function_758(), expected_function_759(), expected_function_760(), expected_function_761(), expected_function_762(), expected_function_763(), expected_function_764(), expected_function_765(), expected_function_766(), expected_function_767(), expected_function_768(), expected_function_769(), expected_function_770(), expected_function_771(), expected_function_772(), expected_function_773(), expected_function_774(), expected_function_775(), expected_function_776(), expected_function_777(), expected_function_778(), expected_function_779(), expected_function_780(), expected_function_781(), expected_function_782(), expected_function_783(), expected_function_784()], memory_sizes: vec![2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 18, 19, 32, 34, 36, 47, 50, 64], circuits: vec![Circuit { members: vec![0, 290, 292, 425], layout: FunctionLayout { input_size: 2, selectors: 9, auxiliaries: 22, lookups: 7 } }, Circuit { members: vec![1, 113, 222, 223, 231, 234, 688, 603, 610, 619, 659], layout: FunctionLayout { input_size: 8, selectors: 39, auxiliaries: 72, lookups: 12 } }, Circuit { members: vec![2], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 66, lookups: 4 } }, Circuit { members: vec![3], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 2, lookups: 2 } }, Circuit { members: vec![5], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![6, 229], layout: FunctionLayout { input_size: 13, selectors: 11, auxiliaries: 2, lookups: 1 } }, Circuit { members: vec![14, 16, 116, 150, 220, 221], layout: FunctionLayout { input_size: 16, selectors: 25, auxiliaries: 3, lookups: 3 } }, Circuit { members: vec![15, 689], layout: FunctionLayout { input_size: 16, selectors: 12, auxiliaries: 20, lookups: 16 } }, Circuit { members: vec![17, 18], layout: FunctionLayout { input_size: 8, selectors: 10, auxiliaries: 2, lookups: 1 } }, Circuit { members: vec![22, 75, 99, 100, 213, 378, 593, 654, 663, 671, 678, 693], layout: FunctionLayout { input_size: 33, selectors: 35, auxiliaries: 38, lookups: 8 } }, Circuit { members: vec![23, 118, 123, 408, 687, 611, 612, 694], layout: FunctionLayout { input_size: 2, selectors: 26, auxiliaries: 51, lookups: 8 } }, Circuit { members: vec![24, 236, 237, 239, 241, 627, 752, 462, 463, 464], layout: FunctionLayout { input_size: 34, selectors: 32, auxiliaries: 142, lookups: 22 } }, Circuit { members: vec![25, 32, 127], layout: FunctionLayout { input_size: 6, selectors: 17, auxiliaries: 143, lookups: 9 } }, Circuit { members: vec![26], layout: FunctionLayout { input_size: 7, selectors: 9, auxiliaries: 22, lookups: 10 } }, Circuit { members: vec![30], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 193, lookups: 65 } }, Circuit { members: vec![31], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 4, lookups: 3 } }, Circuit { members: vec![33], layout: FunctionLayout { input_size: 6, selectors: 2, auxiliaries: 153, lookups: 13 } }, Circuit { members: vec![37], layout: FunctionLayout { input_size: 129, selectors: 2, auxiliaries: 402, lookups: 194 } }, Circuit { members: vec![38, 238, 626, 705, 708, 731, 732, 741, 749, 770, 772], layout: FunctionLayout { input_size: 19, selectors: 42, auxiliaries: 110, lookups: 9 } }, Circuit { members: vec![39, 173, 183, 212, 624, 740, 779], layout: FunctionLayout { input_size: 17, selectors: 12, auxiliaries: 7, lookups: 7 } }, Circuit { members: vec![42, 40, 74, 219, 280, 404, 428, 444, 591, 599, 764], layout: FunctionLayout { input_size: 17, selectors: 32, auxiliaries: 7, lookups: 5 } }, Circuit { members: vec![41, 59, 66, 76, 154, 155, 233, 620, 679, 706, 709, 710, 734, 783], layout: FunctionLayout { input_size: 46, selectors: 34, auxiliaries: 22, lookups: 9 } }, Circuit { members: vec![43], layout: FunctionLayout { input_size: 11, selectors: 2, auxiliaries: 13, lookups: 5 } }, Circuit { members: vec![44], layout: FunctionLayout { input_size: 11, selectors: 7, auxiliaries: 12, lookups: 3 } }, Circuit { members: vec![45, 67, 622, 623, 630, 725, 729], layout: FunctionLayout { input_size: 3, selectors: 21, auxiliaries: 14, lookups: 5 } }, Circuit { members: vec![46], layout: FunctionLayout { input_size: 2, selectors: 5, auxiliaries: 3, lookups: 1 } }, Circuit { members: vec![47], layout: FunctionLayout { input_size: 3, selectors: 5, auxiliaries: 3, lookups: 2 } }, Circuit { members: vec![48], layout: FunctionLayout { input_size: 4, selectors: 1, auxiliaries: 3, lookups: 3 } }, Circuit { members: vec![49, 159, 167, 702, 703, 707, 719, 720, 721, 722, 723, 736, 737, 738], layout: FunctionLayout { input_size: 7, selectors: 27, auxiliaries: 11, lookups: 6 } }, Circuit { members: vec![50, 51, 52, 53, 54, 55, 65, 71, 73, 178, 724, 743, 763, 782], layout: FunctionLayout { input_size: 35, selectors: 32, auxiliaries: 42, lookups: 11 } }, Circuit { members: vec![56, 141, 202, 650, 651], layout: FunctionLayout { input_size: 27, selectors: 29, auxiliaries: 73, lookups: 34 } }, Circuit { members: vec![64, 57, 172, 174, 735, 739, 744, 747, 751], layout: FunctionLayout { input_size: 3, selectors: 25, auxiliaries: 3, lookups: 3 } }, Circuit { members: vec![58, 60, 69, 70, 72, 77, 157, 158, 614, 628, 727, 746, 761, 766, 771], layout: FunctionLayout { input_size: 12, selectors: 34, auxiliaries: 53, lookups: 6 } }, Circuit { members: vec![61, 112, 120, 602, 631, 649, 661, 665, 681, 695, 762], layout: FunctionLayout { input_size: 14, selectors: 34, auxiliaries: 53, lookups: 10 } }, Circuit { members: vec![62, 63, 79, 139, 206, 240, 251, 356, 358, 405, 632, 635, 641, 648, 677], layout: FunctionLayout { input_size: 4, selectors: 38, auxiliaries: 16, lookups: 9 } }, Circuit { members: vec![68, 185, 726, 728, 742, 745, 748, 750], layout: FunctionLayout { input_size: 99, selectors: 26, auxiliaries: 66, lookups: 21 } }, Circuit { members: vec![78, 82, 224, 225, 227, 696, 701], layout: FunctionLayout { input_size: 50, selectors: 27, auxiliaries: 15, lookups: 8 } }, Circuit { members: vec![80, 81, 138, 448, 579, 589, 613, 634, 642, 658, 673, 683, 717, 780], layout: FunctionLayout { input_size: 6, selectors: 24, auxiliaries: 15, lookups: 5 } }, Circuit { members: vec![83, 143, 318, 418, 576], layout: FunctionLayout { input_size: 5, selectors: 9, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![84], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 14, lookups: 3 } }, Circuit { members: vec![85], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 21, lookups: 4 } }, Circuit { members: vec![86, 122, 272, 345, 347], layout: FunctionLayout { input_size: 3, selectors: 13, auxiliaries: 24, lookups: 9 } }, Circuit { members: vec![87], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 14, lookups: 4 } }, Circuit { members: vec![88], layout: FunctionLayout { input_size: 3, selectors: 2, auxiliaries: 11, lookups: 4 } }, Circuit { members: vec![89], layout: FunctionLayout { input_size: 3, selectors: 4, auxiliaries: 19, lookups: 3 } }, Circuit { members: vec![90], layout: FunctionLayout { input_size: 9, selectors: 2, auxiliaries: 22, lookups: 6 } }, Circuit { members: vec![91], layout: FunctionLayout { input_size: 10, selectors: 2, auxiliaries: 15, lookups: 6 } }, Circuit { members: vec![92, 93, 480], layout: FunctionLayout { input_size: 4, selectors: 13, auxiliaries: 4, lookups: 3 } }, Circuit { members: vec![94], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 14, lookups: 4 } }, Circuit { members: vec![95], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 16, lookups: 5 } }, Circuit { members: vec![96, 97], layout: FunctionLayout { input_size: 9, selectors: 6, auxiliaries: 23, lookups: 7 } }, Circuit { members: vec![98], layout: FunctionLayout { input_size: 1, selectors: 13, auxiliaries: 35, lookups: 6 } }, Circuit { members: vec![101, 133, 397], layout: FunctionLayout { input_size: 4, selectors: 19, auxiliaries: 42, lookups: 11 } }, Circuit { members: vec![102], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 98, lookups: 34 } }, Circuit { members: vec![103], layout: FunctionLayout { input_size: 9, selectors: 2, auxiliaries: 15, lookups: 6 } }, Circuit { members: vec![104, 276, 340, 363, 402, 420, 421], layout: FunctionLayout { input_size: 9, selectors: 29, auxiliaries: 24, lookups: 10 } }, Circuit { members: vec![105], layout: FunctionLayout { input_size: 9, selectors: 2, auxiliaries: 15, lookups: 6 } }, Circuit { members: vec![109, 114, 232, 291, 415, 423, 569, 601, 646, 664, 675], layout: FunctionLayout { input_size: 13, selectors: 37, auxiliaries: 3, lookups: 2 } }, Circuit { members: vec![110, 205, 217, 395], layout: FunctionLayout { input_size: 6, selectors: 16, auxiliaries: 19, lookups: 8 } }, Circuit { members: vec![115, 117, 162, 164, 193, 285, 331, 337, 353, 410], layout: FunctionLayout { input_size: 5, selectors: 32, auxiliaries: 52, lookups: 10 } }, Circuit { members: vec![119, 124, 228, 243, 700], layout: FunctionLayout { input_size: 9, selectors: 9, auxiliaries: 88, lookups: 6 } }, Circuit { members: vec![121, 147, 394, 406, 690], layout: FunctionLayout { input_size: 4, selectors: 17, auxiliaries: 14, lookups: 6 } }, Circuit { members: vec![125, 698], layout: FunctionLayout { input_size: 2, selectors: 15, auxiliaries: 55, lookups: 6 } }, Circuit { members: vec![128], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 8, lookups: 4 } }, Circuit { members: vec![129, 207, 215, 226, 262, 266, 653, 662], layout: FunctionLayout { input_size: 6, selectors: 34, auxiliaries: 29, lookups: 10 } }, Circuit { members: vec![130, 477], layout: FunctionLayout { input_size: 3, selectors: 6, auxiliaries: 41, lookups: 7 } }, Circuit { members: vec![131], layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 2, lookups: 2 } }, Circuit { members: vec![132, 211, 263, 594, 604, 609, 645, 652, 657, 682], layout: FunctionLayout { input_size: 19, selectors: 37, auxiliaries: 24, lookups: 16 } }, Circuit { members: vec![134, 203, 269, 273, 300, 341], layout: FunctionLayout { input_size: 3, selectors: 21, auxiliaries: 16, lookups: 10 } }, Circuit { members: vec![135], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 15, lookups: 9 } }, Circuit { members: vec![136], layout: FunctionLayout { input_size: 1, selectors: 4, auxiliaries: 25, lookups: 7 } }, Circuit { members: vec![137, 214, 277, 315, 617, 636, 639, 644, 667, 670, 684, 685, 759], layout: FunctionLayout { input_size: 4, selectors: 29, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![144], layout: FunctionLayout { input_size: 4, selectors: 2, auxiliaries: 15, lookups: 6 } }, Circuit { members: vec![145], layout: FunctionLayout { input_size: 17, selectors: 4, auxiliaries: 47, lookups: 7 } }, Circuit { members: vec![146], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 4, lookups: 3 } }, Circuit { members: vec![148, 152, 208, 580, 600, 668, 680], layout: FunctionLayout { input_size: 11, selectors: 21, auxiliaries: 21, lookups: 17 } }, Circuit { members: vec![149, 151, 160, 161, 334, 350, 352, 354, 607, 608], layout: FunctionLayout { input_size: 6, selectors: 27, auxiliaries: 19, lookups: 6 } }, Circuit { members: vec![153, 156, 170, 200, 335, 343], layout: FunctionLayout { input_size: 16, selectors: 38, auxiliaries: 31, lookups: 14 } }, Circuit { members: vec![163, 169, 182, 198, 336, 461], layout: FunctionLayout { input_size: 4, selectors: 33, auxiliaries: 31, lookups: 23 } }, Circuit { members: vec![165, 210, 246, 255, 453], layout: FunctionLayout { input_size: 6, selectors: 30, auxiliaries: 20, lookups: 16 } }, Circuit { members: vec![166, 177, 184, 196, 197, 252, 264, 711], layout: FunctionLayout { input_size: 5, selectors: 30, auxiliaries: 17, lookups: 12 } }, Circuit { members: vec![168, 176, 250, 253], layout: FunctionLayout { input_size: 5, selectors: 29, auxiliaries: 31, lookups: 25 } }, Circuit { members: vec![171, 189, 248, 257, 577, 586, 588, 640, 781], layout: FunctionLayout { input_size: 4, selectors: 30, auxiliaries: 9, lookups: 5 } }, Circuit { members: vec![175, 199, 249, 346, 595], layout: FunctionLayout { input_size: 11, selectors: 32, auxiliaries: 35, lookups: 27 } }, Circuit { members: vec![179, 278, 299], layout: FunctionLayout { input_size: 3, selectors: 6, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![181, 621, 704], layout: FunctionLayout { input_size: 2, selectors: 32, auxiliaries: 13, lookups: 8 } }, Circuit { members: vec![188, 235, 629, 660, 730, 733, 769], layout: FunctionLayout { input_size: 10, selectors: 40, auxiliaries: 109, lookups: 51 } }, Circuit { members: vec![190, 261, 328, 411, 583], layout: FunctionLayout { input_size: 4, selectors: 14, auxiliaries: 11, lookups: 6 } }, Circuit { members: vec![191, 242, 245, 254, 447, 573, 582, 590, 647, 676], layout: FunctionLayout { input_size: 4, selectors: 38, auxiliaries: 10, lookups: 5 } }, Circuit { members: vec![192, 287, 575, 596, 606, 633, 666], layout: FunctionLayout { input_size: 8, selectors: 28, auxiliaries: 19, lookups: 10 } }, Circuit { members: vec![194, 258, 296, 309, 351, 355, 357, 605], layout: FunctionLayout { input_size: 5, selectors: 35, auxiliaries: 11, lookups: 6 } }, Circuit { members: vec![195, 615, 616, 637, 774], layout: FunctionLayout { input_size: 6, selectors: 10, auxiliaries: 10, lookups: 6 } }, Circuit { members: vec![201], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 4 } }, Circuit { members: vec![204, 342, 416, 426, 454, 655, 656, 669, 672], layout: FunctionLayout { input_size: 14, selectors: 29, auxiliaries: 50, lookups: 26 } }, Circuit { members: vec![216, 268, 279, 305, 344, 393, 581, 584], layout: FunctionLayout { input_size: 6, selectors: 26, auxiliaries: 13, lookups: 7 } }, Circuit { members: vec![218], layout: FunctionLayout { input_size: 5, selectors: 12, auxiliaries: 25, lookups: 7 } }, Circuit { members: vec![244, 259, 281, 329, 409], layout: FunctionLayout { input_size: 4, selectors: 18, auxiliaries: 7, lookups: 5 } }, Circuit { members: vec![247, 260, 330, 349, 450], layout: FunctionLayout { input_size: 10, selectors: 36, auxiliaries: 43, lookups: 24 } }, Circuit { members: vec![256, 289, 308, 572], layout: FunctionLayout { input_size: 4, selectors: 12, auxiliaries: 13, lookups: 9 } }, Circuit { members: vec![265, 267, 270, 348, 376, 379, 419, 445, 449, 686, 778], layout: FunctionLayout { input_size: 8, selectors: 34, auxiliaries: 12, lookups: 6 } }, Circuit { members: vec![271, 319, 323, 413, 597, 598, 674], layout: FunctionLayout { input_size: 5, selectors: 35, auxiliaries: 13, lookups: 6 } }, Circuit { members: vec![274, 377, 399, 446], layout: FunctionLayout { input_size: 4, selectors: 21, auxiliaries: 11, lookups: 5 } }, Circuit { members: vec![275, 391, 398], layout: FunctionLayout { input_size: 4, selectors: 10, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![282, 392, 407], layout: FunctionLayout { input_size: 4, selectors: 8, auxiliaries: 9, lookups: 6 } }, Circuit { members: vec![283], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 3 } }, Circuit { members: vec![284], layout: FunctionLayout { input_size: 2, selectors: 9, auxiliaries: 9, lookups: 6 } }, Circuit { members: vec![286], layout: FunctionLayout { input_size: 2, selectors: 5, auxiliaries: 8, lookups: 4 } }, Circuit { members: vec![288], layout: FunctionLayout { input_size: 1, selectors: 9, auxiliaries: 9, lookups: 6 } }, Circuit { members: vec![293], layout: FunctionLayout { input_size: 3, selectors: 10, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![486, 487, 506, 508, 294], layout: FunctionLayout { input_size: 3, selectors: 15, auxiliaries: 30, lookups: 14 } }, Circuit { members: vec![295, 412, 571], layout: FunctionLayout { input_size: 4, selectors: 5, auxiliaries: 3, lookups: 3 } }, Circuit { members: vec![298, 424], layout: FunctionLayout { input_size: 3, selectors: 6, auxiliaries: 18, lookups: 10 } }, Circuit { members: vec![301, 339], layout: FunctionLayout { input_size: 2, selectors: 6, auxiliaries: 18, lookups: 13 } }, Circuit { members: vec![302], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 5, lookups: 4 } }, Circuit { members: vec![303], layout: FunctionLayout { input_size: 2, selectors: 11, auxiliaries: 9, lookups: 5 } }, Circuit { members: vec![304], layout: FunctionLayout { input_size: 3, selectors: 2, auxiliaries: 5, lookups: 4 } }, Circuit { members: vec![306], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 5, lookups: 4 } }, Circuit { members: vec![307], layout: FunctionLayout { input_size: 3, selectors: 9, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![310], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 5, lookups: 4 } }, Circuit { members: vec![311], layout: FunctionLayout { input_size: 3, selectors: 9, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![371, 432, 440, 312], layout: FunctionLayout { input_size: 5, selectors: 4, auxiliaries: 9, lookups: 7 } }, Circuit { members: vec![313], layout: FunctionLayout { input_size: 2, selectors: 9, auxiliaries: 11, lookups: 4 } }, Circuit { members: vec![314, 383], layout: FunctionLayout { input_size: 4, selectors: 4, auxiliaries: 4, lookups: 3 } }, Circuit { members: vec![316], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 6, lookups: 5 } }, Circuit { members: vec![317], layout: FunctionLayout { input_size: 3, selectors: 9, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![320], layout: FunctionLayout { input_size: 3, selectors: 4, auxiliaries: 8, lookups: 6 } }, Circuit { members: vec![321], layout: FunctionLayout { input_size: 3, selectors: 2, auxiliaries: 5, lookups: 5 } }, Circuit { members: vec![322], layout: FunctionLayout { input_size: 3, selectors: 9, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![324], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 12, lookups: 5 } }, Circuit { members: vec![325], layout: FunctionLayout { input_size: 4, selectors: 3, auxiliaries: 10, lookups: 7 } }, Circuit { members: vec![326], layout: FunctionLayout { input_size: 4, selectors: 6, auxiliaries: 11, lookups: 5 } }, Circuit { members: vec![327, 369], layout: FunctionLayout { input_size: 6, selectors: 6, auxiliaries: 25, lookups: 9 } }, Circuit { members: vec![332, 560, 561], layout: FunctionLayout { input_size: 3, selectors: 41, auxiliaries: 20, lookups: 16 } }, Circuit { members: vec![333], layout: FunctionLayout { input_size: 5, selectors: 6, auxiliaries: 9, lookups: 4 } }, Circuit { members: vec![338], layout: FunctionLayout { input_size: 6, selectors: 16, auxiliaries: 32, lookups: 10 } }, Circuit { members: vec![359], layout: FunctionLayout { input_size: 3, selectors: 6, auxiliaries: 14, lookups: 6 } }, Circuit { members: vec![361], layout: FunctionLayout { input_size: 3, selectors: 6, auxiliaries: 14, lookups: 6 } }, Circuit { members: vec![362], layout: FunctionLayout { input_size: 4, selectors: 8, auxiliaries: 14, lookups: 8 } }, Circuit { members: vec![364, 401, 558, 554], layout: FunctionLayout { input_size: 6, selectors: 43, auxiliaries: 19, lookups: 6 } }, Circuit { members: vec![365], layout: FunctionLayout { input_size: 3, selectors: 6, auxiliaries: 14, lookups: 6 } }, Circuit { members: vec![367], layout: FunctionLayout { input_size: 4, selectors: 6, auxiliaries: 11, lookups: 5 } }, Circuit { members: vec![368], layout: FunctionLayout { input_size: 4, selectors: 3, auxiliaries: 10, lookups: 7 } }, Circuit { members: vec![370], layout: FunctionLayout { input_size: 6, selectors: 19, auxiliaries: 38, lookups: 13 } }, Circuit { members: vec![372, 373], layout: FunctionLayout { input_size: 12, selectors: 16, auxiliaries: 1, lookups: 1 } }, Circuit { members: vec![374, 403], layout: FunctionLayout { input_size: 4, selectors: 19, auxiliaries: 48, lookups: 15 } }, Circuit { members: vec![380], layout: FunctionLayout { input_size: 4, selectors: 3, auxiliaries: 7, lookups: 6 } }, Circuit { members: vec![381], layout: FunctionLayout { input_size: 5, selectors: 2, auxiliaries: 12, lookups: 11 } }, Circuit { members: vec![382, 388], layout: FunctionLayout { input_size: 4, selectors: 6, auxiliaries: 16, lookups: 8 } }, Circuit { members: vec![384], layout: FunctionLayout { input_size: 4, selectors: 6, auxiliaries: 18, lookups: 9 } }, Circuit { members: vec![385], layout: FunctionLayout { input_size: 4, selectors: 5, auxiliaries: 10, lookups: 5 } }, Circuit { members: vec![386], layout: FunctionLayout { input_size: 2, selectors: 4, auxiliaries: 7, lookups: 3 } }, Circuit { members: vec![387], layout: FunctionLayout { input_size: 2, selectors: 12, auxiliaries: 22, lookups: 8 } }, Circuit { members: vec![390], layout: FunctionLayout { input_size: 4, selectors: 3, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![396, 417, 422, 578, 585, 592], layout: FunctionLayout { input_size: 6, selectors: 16, auxiliaries: 9, lookups: 4 } }, Circuit { members: vec![400], layout: FunctionLayout { input_size: 5, selectors: 25, auxiliaries: 16, lookups: 6 } }, Circuit { members: vec![414], layout: FunctionLayout { input_size: 5, selectors: 7, auxiliaries: 15, lookups: 5 } }, Circuit { members: vec![479, 427], layout: FunctionLayout { input_size: 2, selectors: 3, auxiliaries: 9, lookups: 4 } }, Circuit { members: vec![429], layout: FunctionLayout { input_size: 3, selectors: 2, auxiliaries: 4, lookups: 4 } }, Circuit { members: vec![430], layout: FunctionLayout { input_size: 4, selectors: 2, auxiliaries: 10, lookups: 9 } }, Circuit { members: vec![431], layout: FunctionLayout { input_size: 3, selectors: 9, auxiliaries: 22, lookups: 8 } }, Circuit { members: vec![434, 436, 439, 441], layout: FunctionLayout { input_size: 2, selectors: 18, auxiliaries: 10, lookups: 3 } }, Circuit { members: vec![438, 437, 442], layout: FunctionLayout { input_size: 5, selectors: 21, auxiliaries: 17, lookups: 11 } }, Circuit { members: vec![443], layout: FunctionLayout { input_size: 5, selectors: 8, auxiliaries: 21, lookups: 11 } }, Circuit { members: vec![451], layout: FunctionLayout { input_size: 3, selectors: 1, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![452], layout: FunctionLayout { input_size: 4, selectors: 1, auxiliaries: 3, lookups: 3 } }, Circuit { members: vec![455, 457, 460, 467, 478, 496], layout: FunctionLayout { input_size: 16, selectors: 35, auxiliaries: 36, lookups: 22 } }, Circuit { members: vec![468, 473, 474, 481, 484, 493, 501, 503, 505, 456], layout: FunctionLayout { input_size: 5, selectors: 31, auxiliaries: 22, lookups: 15 } }, Circuit { members: vec![466, 469, 476, 482, 488, 489, 490, 492, 494, 767, 773, 458], layout: FunctionLayout { input_size: 4, selectors: 21, auxiliaries: 6, lookups: 4 } }, Circuit { members: vec![459, 559, 564, 495, 497, 498, 499, 500, 502, 504], layout: FunctionLayout { input_size: 16, selectors: 36, auxiliaries: 11, lookups: 10 } }, Circuit { members: vec![485, 507, 555, 556, 465, 470, 471, 472, 475, 483, 491, 718], layout: FunctionLayout { input_size: 2, selectors: 40, auxiliaries: 10, lookups: 7 } }, Circuit { members: vec![549, 552, 553, 568], layout: FunctionLayout { input_size: 1, selectors: 248, auxiliaries: 100, lookups: 1 } }, Circuit { members: vec![550, 551], layout: FunctionLayout { input_size: 1, selectors: 131, auxiliaries: 74, lookups: 12 } }, Circuit { members: vec![557], layout: FunctionLayout { input_size: 2, selectors: 102, auxiliaries: 104, lookups: 40 } }, Circuit { members: vec![562], layout: FunctionLayout { input_size: 3, selectors: 19, auxiliaries: 68, lookups: 68 } }, Circuit { members: vec![563], layout: FunctionLayout { input_size: 1, selectors: 30, auxiliaries: 30, lookups: 1 } }, Circuit { members: vec![565], layout: FunctionLayout { input_size: 3, selectors: 56, auxiliaries: 58, lookups: 32 } }, Circuit { members: vec![566], layout: FunctionLayout { input_size: 3, selectors: 56, auxiliaries: 71, lookups: 71 } }, Circuit { members: vec![567], layout: FunctionLayout { input_size: 3, selectors: 66, auxiliaries: 82, lookups: 82 } }, Circuit { members: vec![570], layout: FunctionLayout { input_size: 1, selectors: 9, auxiliaries: 19, lookups: 5 } }, Circuit { members: vec![574], layout: FunctionLayout { input_size: 3, selectors: 9, auxiliaries: 20, lookups: 8 } }, Circuit { members: vec![691], layout: FunctionLayout { input_size: 1, selectors: 7, auxiliaries: 26, lookups: 7 } }, Circuit { members: vec![692], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 7, lookups: 5 } }, Circuit { members: vec![697], layout: FunctionLayout { input_size: 2, selectors: 3, auxiliaries: 6, lookups: 3 } }, Circuit { members: vec![699], layout: FunctionLayout { input_size: 4, selectors: 5, auxiliaries: 7, lookups: 8 } }, Circuit { members: vec![712], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 4, lookups: 4 } }, Circuit { members: vec![713], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 70, lookups: 8 } }, Circuit { members: vec![714], layout: FunctionLayout { input_size: 65, selectors: 1, auxiliaries: 4, lookups: 5 } }, Circuit { members: vec![715], layout: FunctionLayout { input_size: 2, selectors: 7, auxiliaries: 116, lookups: 10 } }, Circuit { members: vec![716], layout: FunctionLayout { input_size: 4, selectors: 6, auxiliaries: 73, lookups: 12 } }, Circuit { members: vec![753], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 10, lookups: 7 } }, Circuit { members: vec![754], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![755], layout: FunctionLayout { input_size: 1, selectors: 3, auxiliaries: 15, lookups: 4 } }, Circuit { members: vec![756], layout: FunctionLayout { input_size: 3, selectors: 1, auxiliaries: 53, lookups: 6 } }, Circuit { members: vec![757], layout: FunctionLayout { input_size: 5, selectors: 3, auxiliaries: 15, lookups: 10 } }, Circuit { members: vec![758], layout: FunctionLayout { input_size: 8, selectors: 1, auxiliaries: 1, lookups: 2 } }, Circuit { members: vec![760], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 4 } }, Circuit { members: vec![765], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 4 } }, Circuit { members: vec![768], layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 5, lookups: 3 } }, Circuit { members: vec![775], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 5, lookups: 3 } }, Circuit { members: vec![776], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![777], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 3 } }, Circuit { members: vec![784], layout: FunctionLayout { input_size: 14, selectors: 57, auxiliaries: 31, lookups: 8 } }] } + Toplevel { functions: vec![expected_function_0(), expected_function_1(), expected_function_2(), expected_function_3(), expected_function_4(), expected_function_5(), expected_function_6(), expected_function_7(), expected_function_8(), expected_function_9(), expected_function_10(), expected_function_11(), expected_function_12(), expected_function_13(), expected_function_14(), expected_function_15(), expected_function_16(), expected_function_17(), expected_function_18(), expected_function_19(), expected_function_20(), expected_function_21(), expected_function_22(), expected_function_23(), expected_function_24(), expected_function_25(), expected_function_26(), expected_function_27(), expected_function_28(), expected_function_29(), expected_function_30(), expected_function_31(), expected_function_32(), expected_function_33(), expected_function_34(), expected_function_35(), expected_function_36(), expected_function_37(), expected_function_38(), expected_function_39(), expected_function_40(), expected_function_41(), expected_function_42(), expected_function_43(), expected_function_44(), expected_function_45(), expected_function_46(), expected_function_47(), expected_function_48(), expected_function_49(), expected_function_50(), expected_function_51(), expected_function_52(), expected_function_53(), expected_function_54(), expected_function_55(), expected_function_56(), expected_function_57(), expected_function_58(), expected_function_59(), expected_function_60(), expected_function_61(), expected_function_62(), expected_function_63(), expected_function_64(), expected_function_65(), expected_function_66(), expected_function_67(), expected_function_68(), expected_function_69(), expected_function_70(), expected_function_71(), expected_function_72(), expected_function_73(), expected_function_74(), expected_function_75(), expected_function_76(), expected_function_77(), expected_function_78(), expected_function_79(), expected_function_80(), expected_function_81(), expected_function_82(), expected_function_83(), expected_function_84(), expected_function_85(), expected_function_86(), expected_function_87(), expected_function_88(), expected_function_89(), expected_function_90(), expected_function_91(), expected_function_92(), expected_function_93(), expected_function_94(), expected_function_95(), expected_function_96(), expected_function_97(), expected_function_98(), expected_function_99(), expected_function_100(), expected_function_101(), expected_function_102(), expected_function_103(), expected_function_104(), expected_function_105(), expected_function_106(), expected_function_107(), expected_function_108(), expected_function_109(), expected_function_110(), expected_function_111(), expected_function_112(), expected_function_113(), expected_function_114(), expected_function_115(), expected_function_116(), expected_function_117(), expected_function_118(), expected_function_119(), expected_function_120(), expected_function_121(), expected_function_122(), expected_function_123(), expected_function_124(), expected_function_125(), expected_function_126(), expected_function_127(), expected_function_128(), expected_function_129(), expected_function_130(), expected_function_131(), expected_function_132(), expected_function_133(), expected_function_134(), expected_function_135(), expected_function_136(), expected_function_137(), expected_function_138(), expected_function_139(), expected_function_140(), expected_function_141(), expected_function_142(), expected_function_143(), expected_function_144(), expected_function_145(), expected_function_146(), expected_function_147(), expected_function_148(), expected_function_149(), expected_function_150(), expected_function_151(), expected_function_152(), expected_function_153(), expected_function_154(), expected_function_155(), expected_function_156(), expected_function_157(), expected_function_158(), expected_function_159(), expected_function_160(), expected_function_161(), expected_function_162(), expected_function_163(), expected_function_164(), expected_function_165(), expected_function_166(), expected_function_167(), expected_function_168(), expected_function_169(), expected_function_170(), expected_function_171(), expected_function_172(), expected_function_173(), expected_function_174(), expected_function_175(), expected_function_176(), expected_function_177(), expected_function_178(), expected_function_179(), expected_function_180(), expected_function_181(), expected_function_182(), expected_function_183(), expected_function_184(), expected_function_185(), expected_function_186(), expected_function_187(), expected_function_188(), expected_function_189(), expected_function_190(), expected_function_191(), expected_function_192(), expected_function_193(), expected_function_194(), expected_function_195(), expected_function_196(), expected_function_197(), expected_function_198(), expected_function_199(), expected_function_200(), expected_function_201(), expected_function_202(), expected_function_203(), expected_function_204(), expected_function_205(), expected_function_206(), expected_function_207(), expected_function_208(), expected_function_209(), expected_function_210(), expected_function_211(), expected_function_212(), expected_function_213(), expected_function_214(), expected_function_215(), expected_function_216(), expected_function_217(), expected_function_218(), expected_function_219(), expected_function_220(), expected_function_221(), expected_function_222(), expected_function_223(), expected_function_224(), expected_function_225(), expected_function_226(), expected_function_227(), expected_function_228(), expected_function_229(), expected_function_230(), expected_function_231(), expected_function_232(), expected_function_233(), expected_function_234(), expected_function_235(), expected_function_236(), expected_function_237(), expected_function_238(), expected_function_239(), expected_function_240(), expected_function_241(), expected_function_242(), expected_function_243(), expected_function_244(), expected_function_245(), expected_function_246(), expected_function_247(), expected_function_248(), expected_function_249(), expected_function_250(), expected_function_251(), expected_function_252(), expected_function_253(), expected_function_254(), expected_function_255(), expected_function_256(), expected_function_257(), expected_function_258(), expected_function_259(), expected_function_260(), expected_function_261(), expected_function_262(), expected_function_263(), expected_function_264(), expected_function_265(), expected_function_266(), expected_function_267(), expected_function_268(), expected_function_269(), expected_function_270(), expected_function_271(), expected_function_272(), expected_function_273(), expected_function_274(), expected_function_275(), expected_function_276(), expected_function_277(), expected_function_278(), expected_function_279(), expected_function_280(), expected_function_281(), expected_function_282(), expected_function_283(), expected_function_284(), expected_function_285(), expected_function_286(), expected_function_287(), expected_function_288(), expected_function_289(), expected_function_290(), expected_function_291(), expected_function_292(), expected_function_293(), expected_function_294(), expected_function_295(), expected_function_296(), expected_function_297(), expected_function_298(), expected_function_299(), expected_function_300(), expected_function_301(), expected_function_302(), expected_function_303(), expected_function_304(), expected_function_305(), expected_function_306(), expected_function_307(), expected_function_308(), expected_function_309(), expected_function_310(), expected_function_311(), expected_function_312(), expected_function_313(), expected_function_314(), expected_function_315(), expected_function_316(), expected_function_317(), expected_function_318(), expected_function_319(), expected_function_320(), expected_function_321(), expected_function_322(), expected_function_323(), expected_function_324(), expected_function_325(), expected_function_326(), expected_function_327(), expected_function_328(), expected_function_329(), expected_function_330(), expected_function_331(), expected_function_332(), expected_function_333(), expected_function_334(), expected_function_335(), expected_function_336(), expected_function_337(), expected_function_338(), expected_function_339(), expected_function_340(), expected_function_341(), expected_function_342(), expected_function_343(), expected_function_344(), expected_function_345(), expected_function_346(), expected_function_347(), expected_function_348(), expected_function_349(), expected_function_350(), expected_function_351(), expected_function_352(), expected_function_353(), expected_function_354(), expected_function_355(), expected_function_356(), expected_function_357(), expected_function_358(), expected_function_359(), expected_function_360(), expected_function_361(), expected_function_362(), expected_function_363(), expected_function_364(), expected_function_365(), expected_function_366(), expected_function_367(), expected_function_368(), expected_function_369(), expected_function_370(), expected_function_371(), expected_function_372(), expected_function_373(), expected_function_374(), expected_function_375(), expected_function_376(), expected_function_377(), expected_function_378(), expected_function_379(), expected_function_380(), expected_function_381(), expected_function_382(), expected_function_383(), expected_function_384(), expected_function_385(), expected_function_386(), expected_function_387(), expected_function_388(), expected_function_389(), expected_function_390(), expected_function_391(), expected_function_392(), expected_function_393(), expected_function_394(), expected_function_395(), expected_function_396(), expected_function_397(), expected_function_398(), expected_function_399(), expected_function_400(), expected_function_401(), expected_function_402(), expected_function_403(), expected_function_404(), expected_function_405(), expected_function_406(), expected_function_407(), expected_function_408(), expected_function_409(), expected_function_410(), expected_function_411(), expected_function_412(), expected_function_413(), expected_function_414(), expected_function_415(), expected_function_416(), expected_function_417(), expected_function_418(), expected_function_419(), expected_function_420(), expected_function_421(), expected_function_422(), expected_function_423(), expected_function_424(), expected_function_425(), expected_function_426(), expected_function_427(), expected_function_428(), expected_function_429(), expected_function_430(), expected_function_431(), expected_function_432(), expected_function_433(), expected_function_434(), expected_function_435(), expected_function_436(), expected_function_437(), expected_function_438(), expected_function_439(), expected_function_440(), expected_function_441(), expected_function_442(), expected_function_443(), expected_function_444(), expected_function_445(), expected_function_446(), expected_function_447(), expected_function_448(), expected_function_449(), expected_function_450(), expected_function_451(), expected_function_452(), expected_function_453(), expected_function_454(), expected_function_455(), expected_function_456(), expected_function_457(), expected_function_458(), expected_function_459(), expected_function_460(), expected_function_461(), expected_function_462(), expected_function_463(), expected_function_464(), expected_function_465(), expected_function_466(), expected_function_467(), expected_function_468(), expected_function_469(), expected_function_470(), expected_function_471(), expected_function_472(), expected_function_473(), expected_function_474(), expected_function_475(), expected_function_476(), expected_function_477(), expected_function_478(), expected_function_479(), expected_function_480(), expected_function_481(), expected_function_482(), expected_function_483(), expected_function_484(), expected_function_485(), expected_function_486(), expected_function_487(), expected_function_488(), expected_function_489(), expected_function_490(), expected_function_491(), expected_function_492(), expected_function_493(), expected_function_494(), expected_function_495(), expected_function_496(), expected_function_497(), expected_function_498(), expected_function_499(), expected_function_500(), expected_function_501(), expected_function_502(), expected_function_503(), expected_function_504(), expected_function_505(), expected_function_506(), expected_function_507(), expected_function_508(), expected_function_509(), expected_function_510(), expected_function_511(), expected_function_512(), expected_function_513(), expected_function_514(), expected_function_515(), expected_function_516(), expected_function_517(), expected_function_518(), expected_function_519(), expected_function_520(), expected_function_521(), expected_function_522(), expected_function_523(), expected_function_524(), expected_function_525(), expected_function_526(), expected_function_527(), expected_function_528(), expected_function_529(), expected_function_530(), expected_function_531(), expected_function_532(), expected_function_533(), expected_function_534(), expected_function_535(), expected_function_536(), expected_function_537(), expected_function_538(), expected_function_539(), expected_function_540(), expected_function_541(), expected_function_542(), expected_function_543(), expected_function_544(), expected_function_545(), expected_function_546(), expected_function_547(), expected_function_548(), expected_function_549(), expected_function_550(), expected_function_551(), expected_function_552(), expected_function_553(), expected_function_554(), expected_function_555(), expected_function_556(), expected_function_557(), expected_function_558(), expected_function_559(), expected_function_560(), expected_function_561(), expected_function_562(), expected_function_563(), expected_function_564(), expected_function_565(), expected_function_566(), expected_function_567(), expected_function_568(), expected_function_569(), expected_function_570(), expected_function_571(), expected_function_572(), expected_function_573(), expected_function_574(), expected_function_575(), expected_function_576(), expected_function_577(), expected_function_578(), expected_function_579(), expected_function_580(), expected_function_581(), expected_function_582(), expected_function_583(), expected_function_584(), expected_function_585(), expected_function_586(), expected_function_587(), expected_function_588(), expected_function_589(), expected_function_590(), expected_function_591(), expected_function_592(), expected_function_593(), expected_function_594(), expected_function_595(), expected_function_596(), expected_function_597(), expected_function_598(), expected_function_599(), expected_function_600(), expected_function_601(), expected_function_602(), expected_function_603(), expected_function_604(), expected_function_605(), expected_function_606(), expected_function_607(), expected_function_608(), expected_function_609(), expected_function_610(), expected_function_611(), expected_function_612(), expected_function_613(), expected_function_614(), expected_function_615(), expected_function_616(), expected_function_617(), expected_function_618(), expected_function_619(), expected_function_620(), expected_function_621(), expected_function_622(), expected_function_623(), expected_function_624(), expected_function_625(), expected_function_626(), expected_function_627(), expected_function_628(), expected_function_629(), expected_function_630(), expected_function_631(), expected_function_632(), expected_function_633(), expected_function_634(), expected_function_635(), expected_function_636(), expected_function_637(), expected_function_638(), expected_function_639(), expected_function_640(), expected_function_641(), expected_function_642(), expected_function_643(), expected_function_644(), expected_function_645(), expected_function_646(), expected_function_647(), expected_function_648(), expected_function_649(), expected_function_650(), expected_function_651(), expected_function_652(), expected_function_653(), expected_function_654(), expected_function_655(), expected_function_656(), expected_function_657(), expected_function_658(), expected_function_659(), expected_function_660(), expected_function_661(), expected_function_662(), expected_function_663(), expected_function_664(), expected_function_665(), expected_function_666(), expected_function_667(), expected_function_668(), expected_function_669(), expected_function_670(), expected_function_671(), expected_function_672(), expected_function_673(), expected_function_674(), expected_function_675(), expected_function_676(), expected_function_677(), expected_function_678(), expected_function_679(), expected_function_680(), expected_function_681(), expected_function_682(), expected_function_683(), expected_function_684(), expected_function_685(), expected_function_686(), expected_function_687(), expected_function_688(), expected_function_689(), expected_function_690(), expected_function_691(), expected_function_692(), expected_function_693(), expected_function_694(), expected_function_695(), expected_function_696(), expected_function_697(), expected_function_698(), expected_function_699(), expected_function_700(), expected_function_701(), expected_function_702(), expected_function_703(), expected_function_704(), expected_function_705(), expected_function_706(), expected_function_707(), expected_function_708(), expected_function_709(), expected_function_710(), expected_function_711(), expected_function_712(), expected_function_713(), expected_function_714(), expected_function_715(), expected_function_716(), expected_function_717(), expected_function_718(), expected_function_719(), expected_function_720(), expected_function_721(), expected_function_722(), expected_function_723(), expected_function_724(), expected_function_725(), expected_function_726(), expected_function_727(), expected_function_728(), expected_function_729(), expected_function_730(), expected_function_731(), expected_function_732(), expected_function_733(), expected_function_734(), expected_function_735(), expected_function_736(), expected_function_737(), expected_function_738(), expected_function_739(), expected_function_740(), expected_function_741(), expected_function_742(), expected_function_743(), expected_function_744(), expected_function_745(), expected_function_746(), expected_function_747(), expected_function_748(), expected_function_749(), expected_function_750(), expected_function_751(), expected_function_752(), expected_function_753(), expected_function_754(), expected_function_755(), expected_function_756(), expected_function_757(), expected_function_758(), expected_function_759(), expected_function_760(), expected_function_761(), expected_function_762(), expected_function_763(), expected_function_764(), expected_function_765(), expected_function_766(), expected_function_767(), expected_function_768(), expected_function_769(), expected_function_770(), expected_function_771(), expected_function_772(), expected_function_773(), expected_function_774(), expected_function_775(), expected_function_776(), expected_function_777(), expected_function_778(), expected_function_779(), expected_function_780(), expected_function_781(), expected_function_782(), expected_function_783(), expected_function_784()], memory_sizes: vec![2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 18, 19, 32, 34, 36, 47, 50, 64], circuits: vec![Circuit { members: vec![0, 290, 292, 425], layout: FunctionLayout { input_size: 2, selectors: 9, auxiliaries: 22, lookups: 7 } }, Circuit { members: vec![1, 113, 222, 223, 231, 234, 688, 603, 610, 619, 659], layout: FunctionLayout { input_size: 8, selectors: 39, auxiliaries: 72, lookups: 12 } }, Circuit { members: vec![2], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 66, lookups: 4 } }, Circuit { members: vec![3], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 2, lookups: 2 } }, Circuit { members: vec![5], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![6, 229], layout: FunctionLayout { input_size: 13, selectors: 11, auxiliaries: 2, lookups: 1 } }, Circuit { members: vec![14, 16, 116, 150, 220, 221], layout: FunctionLayout { input_size: 16, selectors: 25, auxiliaries: 3, lookups: 3 } }, Circuit { members: vec![15, 689], layout: FunctionLayout { input_size: 16, selectors: 12, auxiliaries: 20, lookups: 16 } }, Circuit { members: vec![17, 18], layout: FunctionLayout { input_size: 8, selectors: 10, auxiliaries: 2, lookups: 1 } }, Circuit { members: vec![22, 75, 99, 100, 213, 378, 593, 654, 663, 671, 678, 693], layout: FunctionLayout { input_size: 33, selectors: 35, auxiliaries: 38, lookups: 8 } }, Circuit { members: vec![23, 118, 123, 408, 687, 611, 612, 694], layout: FunctionLayout { input_size: 2, selectors: 26, auxiliaries: 51, lookups: 8 } }, Circuit { members: vec![24, 236, 237, 239, 241, 627, 752, 462, 463, 464], layout: FunctionLayout { input_size: 34, selectors: 32, auxiliaries: 142, lookups: 22 } }, Circuit { members: vec![25, 32, 127], layout: FunctionLayout { input_size: 6, selectors: 17, auxiliaries: 143, lookups: 9 } }, Circuit { members: vec![26], layout: FunctionLayout { input_size: 7, selectors: 9, auxiliaries: 22, lookups: 10 } }, Circuit { members: vec![30], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 193, lookups: 65 } }, Circuit { members: vec![31], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 4, lookups: 3 } }, Circuit { members: vec![33], layout: FunctionLayout { input_size: 6, selectors: 2, auxiliaries: 153, lookups: 13 } }, Circuit { members: vec![37], layout: FunctionLayout { input_size: 129, selectors: 2, auxiliaries: 402, lookups: 194 } }, Circuit { members: vec![38, 238, 626, 705, 708, 731, 732, 741, 749, 770, 772], layout: FunctionLayout { input_size: 19, selectors: 42, auxiliaries: 110, lookups: 9 } }, Circuit { members: vec![39, 173, 183, 212, 624, 740, 779], layout: FunctionLayout { input_size: 17, selectors: 12, auxiliaries: 7, lookups: 7 } }, Circuit { members: vec![42, 40, 74, 219, 280, 404, 428, 444, 591, 599, 764], layout: FunctionLayout { input_size: 17, selectors: 32, auxiliaries: 7, lookups: 5 } }, Circuit { members: vec![41, 59, 66, 76, 154, 155, 233, 620, 679, 706, 709, 710, 734, 783], layout: FunctionLayout { input_size: 46, selectors: 34, auxiliaries: 22, lookups: 9 } }, Circuit { members: vec![43], layout: FunctionLayout { input_size: 11, selectors: 2, auxiliaries: 13, lookups: 5 } }, Circuit { members: vec![44], layout: FunctionLayout { input_size: 11, selectors: 7, auxiliaries: 12, lookups: 3 } }, Circuit { members: vec![45, 67, 622, 623, 630, 725, 729], layout: FunctionLayout { input_size: 3, selectors: 21, auxiliaries: 14, lookups: 5 } }, Circuit { members: vec![46], layout: FunctionLayout { input_size: 2, selectors: 5, auxiliaries: 3, lookups: 1 } }, Circuit { members: vec![47], layout: FunctionLayout { input_size: 3, selectors: 5, auxiliaries: 3, lookups: 2 } }, Circuit { members: vec![48], layout: FunctionLayout { input_size: 4, selectors: 1, auxiliaries: 3, lookups: 3 } }, Circuit { members: vec![49, 159, 167, 702, 703, 707, 719, 720, 721, 722, 723, 736, 737, 738], layout: FunctionLayout { input_size: 7, selectors: 27, auxiliaries: 11, lookups: 6 } }, Circuit { members: vec![50, 51, 52, 53, 54, 55, 65, 71, 73, 178, 724, 743, 763, 782], layout: FunctionLayout { input_size: 35, selectors: 32, auxiliaries: 42, lookups: 11 } }, Circuit { members: vec![56, 141, 202, 650, 651], layout: FunctionLayout { input_size: 27, selectors: 29, auxiliaries: 73, lookups: 34 } }, Circuit { members: vec![64, 57, 172, 174, 735, 739, 744, 747, 751], layout: FunctionLayout { input_size: 3, selectors: 25, auxiliaries: 3, lookups: 3 } }, Circuit { members: vec![58, 60, 69, 70, 72, 77, 157, 158, 614, 628, 727, 746, 761, 766, 771], layout: FunctionLayout { input_size: 12, selectors: 34, auxiliaries: 53, lookups: 6 } }, Circuit { members: vec![61, 112, 120, 602, 631, 649, 661, 665, 681, 695, 762], layout: FunctionLayout { input_size: 14, selectors: 34, auxiliaries: 53, lookups: 10 } }, Circuit { members: vec![62, 63, 79, 139, 206, 240, 251, 356, 358, 405, 632, 635, 641, 648, 677], layout: FunctionLayout { input_size: 4, selectors: 38, auxiliaries: 16, lookups: 9 } }, Circuit { members: vec![68, 185, 726, 728, 742, 745, 748, 750], layout: FunctionLayout { input_size: 99, selectors: 26, auxiliaries: 66, lookups: 21 } }, Circuit { members: vec![78, 82, 224, 225, 227, 696, 701], layout: FunctionLayout { input_size: 50, selectors: 27, auxiliaries: 15, lookups: 8 } }, Circuit { members: vec![80, 81, 138, 448, 579, 589, 613, 634, 642, 658, 673, 683, 717, 780], layout: FunctionLayout { input_size: 6, selectors: 24, auxiliaries: 15, lookups: 5 } }, Circuit { members: vec![83, 143, 318, 418, 576], layout: FunctionLayout { input_size: 5, selectors: 9, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![84], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 14, lookups: 3 } }, Circuit { members: vec![85], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 21, lookups: 4 } }, Circuit { members: vec![86, 122, 272, 345, 347], layout: FunctionLayout { input_size: 3, selectors: 13, auxiliaries: 24, lookups: 9 } }, Circuit { members: vec![87], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 14, lookups: 4 } }, Circuit { members: vec![88], layout: FunctionLayout { input_size: 3, selectors: 2, auxiliaries: 11, lookups: 4 } }, Circuit { members: vec![89], layout: FunctionLayout { input_size: 3, selectors: 4, auxiliaries: 19, lookups: 3 } }, Circuit { members: vec![90], layout: FunctionLayout { input_size: 9, selectors: 2, auxiliaries: 22, lookups: 6 } }, Circuit { members: vec![91], layout: FunctionLayout { input_size: 10, selectors: 2, auxiliaries: 15, lookups: 6 } }, Circuit { members: vec![92, 93, 480], layout: FunctionLayout { input_size: 4, selectors: 13, auxiliaries: 4, lookups: 3 } }, Circuit { members: vec![94], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 14, lookups: 4 } }, Circuit { members: vec![95], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 16, lookups: 5 } }, Circuit { members: vec![96, 97], layout: FunctionLayout { input_size: 9, selectors: 6, auxiliaries: 23, lookups: 7 } }, Circuit { members: vec![98], layout: FunctionLayout { input_size: 1, selectors: 13, auxiliaries: 35, lookups: 6 } }, Circuit { members: vec![101, 133, 397], layout: FunctionLayout { input_size: 4, selectors: 19, auxiliaries: 42, lookups: 11 } }, Circuit { members: vec![102], layout: FunctionLayout { input_size: 1, selectors: 1, auxiliaries: 98, lookups: 34 } }, Circuit { members: vec![103], layout: FunctionLayout { input_size: 9, selectors: 2, auxiliaries: 15, lookups: 6 } }, Circuit { members: vec![104, 276, 340, 363, 402, 420, 421], layout: FunctionLayout { input_size: 9, selectors: 29, auxiliaries: 24, lookups: 10 } }, Circuit { members: vec![105], layout: FunctionLayout { input_size: 9, selectors: 2, auxiliaries: 15, lookups: 6 } }, Circuit { members: vec![109, 114, 232, 291, 415, 423, 569, 601, 646, 664, 675], layout: FunctionLayout { input_size: 13, selectors: 37, auxiliaries: 3, lookups: 2 } }, Circuit { members: vec![110, 205, 217, 395], layout: FunctionLayout { input_size: 6, selectors: 16, auxiliaries: 19, lookups: 8 } }, Circuit { members: vec![115, 117, 162, 164, 193, 285, 331, 337, 353, 410], layout: FunctionLayout { input_size: 5, selectors: 32, auxiliaries: 52, lookups: 10 } }, Circuit { members: vec![119, 124, 228, 243, 700], layout: FunctionLayout { input_size: 9, selectors: 9, auxiliaries: 88, lookups: 6 } }, Circuit { members: vec![121, 147, 394, 406, 690], layout: FunctionLayout { input_size: 4, selectors: 17, auxiliaries: 14, lookups: 6 } }, Circuit { members: vec![125, 698], layout: FunctionLayout { input_size: 2, selectors: 15, auxiliaries: 55, lookups: 6 } }, Circuit { members: vec![128], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 8, lookups: 4 } }, Circuit { members: vec![129, 207, 215, 226, 262, 266, 653, 662], layout: FunctionLayout { input_size: 6, selectors: 34, auxiliaries: 29, lookups: 10 } }, Circuit { members: vec![130, 477], layout: FunctionLayout { input_size: 3, selectors: 6, auxiliaries: 41, lookups: 7 } }, Circuit { members: vec![131], layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 2, lookups: 2 } }, Circuit { members: vec![132, 211, 263, 594, 604, 609, 645, 652, 657, 682], layout: FunctionLayout { input_size: 19, selectors: 37, auxiliaries: 24, lookups: 16 } }, Circuit { members: vec![134, 203, 269, 273, 300, 341], layout: FunctionLayout { input_size: 3, selectors: 21, auxiliaries: 16, lookups: 10 } }, Circuit { members: vec![135], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 15, lookups: 9 } }, Circuit { members: vec![136], layout: FunctionLayout { input_size: 1, selectors: 4, auxiliaries: 25, lookups: 7 } }, Circuit { members: vec![137, 214, 277, 315, 617, 636, 639, 644, 667, 670, 684, 685, 759], layout: FunctionLayout { input_size: 4, selectors: 29, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![144], layout: FunctionLayout { input_size: 4, selectors: 2, auxiliaries: 15, lookups: 6 } }, Circuit { members: vec![145], layout: FunctionLayout { input_size: 17, selectors: 4, auxiliaries: 47, lookups: 7 } }, Circuit { members: vec![146], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 4, lookups: 3 } }, Circuit { members: vec![148, 152, 208, 580, 600, 668, 680], layout: FunctionLayout { input_size: 11, selectors: 21, auxiliaries: 21, lookups: 17 } }, Circuit { members: vec![149, 151, 160, 161, 334, 350, 352, 354, 607, 608], layout: FunctionLayout { input_size: 6, selectors: 27, auxiliaries: 19, lookups: 6 } }, Circuit { members: vec![153, 156, 170, 200, 335, 343], layout: FunctionLayout { input_size: 16, selectors: 38, auxiliaries: 31, lookups: 14 } }, Circuit { members: vec![163, 169, 182, 198, 336, 461], layout: FunctionLayout { input_size: 4, selectors: 33, auxiliaries: 31, lookups: 23 } }, Circuit { members: vec![165, 210, 246, 255, 453], layout: FunctionLayout { input_size: 6, selectors: 30, auxiliaries: 20, lookups: 16 } }, Circuit { members: vec![166, 177, 184, 196, 197, 252, 264, 711], layout: FunctionLayout { input_size: 5, selectors: 32, auxiliaries: 18, lookups: 12 } }, Circuit { members: vec![168, 176, 250, 253], layout: FunctionLayout { input_size: 5, selectors: 29, auxiliaries: 31, lookups: 25 } }, Circuit { members: vec![171, 189, 248, 257, 577, 586, 588, 640, 781], layout: FunctionLayout { input_size: 4, selectors: 30, auxiliaries: 9, lookups: 5 } }, Circuit { members: vec![175, 199, 249, 346, 595], layout: FunctionLayout { input_size: 11, selectors: 32, auxiliaries: 35, lookups: 27 } }, Circuit { members: vec![179, 278, 299], layout: FunctionLayout { input_size: 3, selectors: 6, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![181, 621, 704], layout: FunctionLayout { input_size: 2, selectors: 32, auxiliaries: 13, lookups: 8 } }, Circuit { members: vec![188, 235, 629, 660, 730, 733, 769], layout: FunctionLayout { input_size: 10, selectors: 40, auxiliaries: 109, lookups: 51 } }, Circuit { members: vec![190, 261, 328, 411, 583], layout: FunctionLayout { input_size: 4, selectors: 14, auxiliaries: 11, lookups: 6 } }, Circuit { members: vec![191, 242, 245, 254, 447, 573, 582, 590, 647, 676], layout: FunctionLayout { input_size: 4, selectors: 38, auxiliaries: 10, lookups: 5 } }, Circuit { members: vec![192, 287, 575, 596, 606, 633, 666], layout: FunctionLayout { input_size: 8, selectors: 28, auxiliaries: 19, lookups: 10 } }, Circuit { members: vec![194, 258, 296, 309, 351, 355, 357, 605], layout: FunctionLayout { input_size: 5, selectors: 35, auxiliaries: 11, lookups: 6 } }, Circuit { members: vec![195, 615, 616, 637, 774], layout: FunctionLayout { input_size: 6, selectors: 10, auxiliaries: 10, lookups: 6 } }, Circuit { members: vec![201], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 4 } }, Circuit { members: vec![204, 342, 416, 426, 454, 655, 656, 669, 672], layout: FunctionLayout { input_size: 14, selectors: 29, auxiliaries: 50, lookups: 26 } }, Circuit { members: vec![216, 268, 279, 305, 344, 393, 581, 584], layout: FunctionLayout { input_size: 6, selectors: 26, auxiliaries: 13, lookups: 7 } }, Circuit { members: vec![218], layout: FunctionLayout { input_size: 5, selectors: 12, auxiliaries: 25, lookups: 7 } }, Circuit { members: vec![244, 259, 281, 329, 409], layout: FunctionLayout { input_size: 4, selectors: 18, auxiliaries: 7, lookups: 5 } }, Circuit { members: vec![247, 260, 330, 349, 450], layout: FunctionLayout { input_size: 10, selectors: 36, auxiliaries: 43, lookups: 24 } }, Circuit { members: vec![256, 289, 308, 572], layout: FunctionLayout { input_size: 4, selectors: 12, auxiliaries: 13, lookups: 9 } }, Circuit { members: vec![265, 267, 270, 348, 376, 379, 419, 445, 449, 686, 778], layout: FunctionLayout { input_size: 8, selectors: 34, auxiliaries: 12, lookups: 6 } }, Circuit { members: vec![271, 319, 323, 413, 597, 598, 674], layout: FunctionLayout { input_size: 5, selectors: 35, auxiliaries: 13, lookups: 6 } }, Circuit { members: vec![274, 377, 399, 446], layout: FunctionLayout { input_size: 4, selectors: 21, auxiliaries: 11, lookups: 5 } }, Circuit { members: vec![275, 391, 398], layout: FunctionLayout { input_size: 4, selectors: 10, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![282, 392, 407], layout: FunctionLayout { input_size: 4, selectors: 8, auxiliaries: 9, lookups: 6 } }, Circuit { members: vec![283], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 3 } }, Circuit { members: vec![284], layout: FunctionLayout { input_size: 2, selectors: 9, auxiliaries: 9, lookups: 6 } }, Circuit { members: vec![286], layout: FunctionLayout { input_size: 2, selectors: 5, auxiliaries: 8, lookups: 4 } }, Circuit { members: vec![288], layout: FunctionLayout { input_size: 1, selectors: 9, auxiliaries: 9, lookups: 6 } }, Circuit { members: vec![293], layout: FunctionLayout { input_size: 3, selectors: 10, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![486, 487, 506, 508, 294], layout: FunctionLayout { input_size: 3, selectors: 15, auxiliaries: 30, lookups: 14 } }, Circuit { members: vec![295, 412, 571], layout: FunctionLayout { input_size: 4, selectors: 5, auxiliaries: 3, lookups: 3 } }, Circuit { members: vec![298, 424], layout: FunctionLayout { input_size: 3, selectors: 6, auxiliaries: 18, lookups: 10 } }, Circuit { members: vec![301, 339], layout: FunctionLayout { input_size: 2, selectors: 6, auxiliaries: 18, lookups: 13 } }, Circuit { members: vec![302], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 5, lookups: 4 } }, Circuit { members: vec![303], layout: FunctionLayout { input_size: 2, selectors: 11, auxiliaries: 9, lookups: 5 } }, Circuit { members: vec![304], layout: FunctionLayout { input_size: 3, selectors: 2, auxiliaries: 5, lookups: 4 } }, Circuit { members: vec![306], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 5, lookups: 4 } }, Circuit { members: vec![307], layout: FunctionLayout { input_size: 3, selectors: 9, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![310], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 5, lookups: 4 } }, Circuit { members: vec![311], layout: FunctionLayout { input_size: 3, selectors: 9, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![371, 432, 440, 312], layout: FunctionLayout { input_size: 5, selectors: 4, auxiliaries: 9, lookups: 7 } }, Circuit { members: vec![313], layout: FunctionLayout { input_size: 2, selectors: 9, auxiliaries: 11, lookups: 4 } }, Circuit { members: vec![314, 383], layout: FunctionLayout { input_size: 4, selectors: 4, auxiliaries: 4, lookups: 3 } }, Circuit { members: vec![316], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 6, lookups: 5 } }, Circuit { members: vec![317], layout: FunctionLayout { input_size: 3, selectors: 9, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![320], layout: FunctionLayout { input_size: 3, selectors: 4, auxiliaries: 8, lookups: 6 } }, Circuit { members: vec![321], layout: FunctionLayout { input_size: 3, selectors: 2, auxiliaries: 5, lookups: 5 } }, Circuit { members: vec![322], layout: FunctionLayout { input_size: 3, selectors: 9, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![324], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 12, lookups: 5 } }, Circuit { members: vec![325], layout: FunctionLayout { input_size: 4, selectors: 3, auxiliaries: 10, lookups: 7 } }, Circuit { members: vec![326], layout: FunctionLayout { input_size: 4, selectors: 6, auxiliaries: 11, lookups: 5 } }, Circuit { members: vec![327, 369], layout: FunctionLayout { input_size: 6, selectors: 6, auxiliaries: 25, lookups: 9 } }, Circuit { members: vec![332, 560, 561], layout: FunctionLayout { input_size: 3, selectors: 41, auxiliaries: 20, lookups: 16 } }, Circuit { members: vec![333], layout: FunctionLayout { input_size: 5, selectors: 6, auxiliaries: 9, lookups: 4 } }, Circuit { members: vec![338], layout: FunctionLayout { input_size: 6, selectors: 16, auxiliaries: 32, lookups: 10 } }, Circuit { members: vec![359], layout: FunctionLayout { input_size: 3, selectors: 6, auxiliaries: 14, lookups: 6 } }, Circuit { members: vec![361], layout: FunctionLayout { input_size: 3, selectors: 6, auxiliaries: 14, lookups: 6 } }, Circuit { members: vec![362], layout: FunctionLayout { input_size: 4, selectors: 8, auxiliaries: 14, lookups: 8 } }, Circuit { members: vec![364, 401, 558, 554], layout: FunctionLayout { input_size: 6, selectors: 43, auxiliaries: 19, lookups: 6 } }, Circuit { members: vec![365], layout: FunctionLayout { input_size: 3, selectors: 6, auxiliaries: 14, lookups: 6 } }, Circuit { members: vec![367], layout: FunctionLayout { input_size: 4, selectors: 6, auxiliaries: 11, lookups: 5 } }, Circuit { members: vec![368], layout: FunctionLayout { input_size: 4, selectors: 3, auxiliaries: 10, lookups: 7 } }, Circuit { members: vec![370], layout: FunctionLayout { input_size: 6, selectors: 19, auxiliaries: 38, lookups: 13 } }, Circuit { members: vec![372, 373], layout: FunctionLayout { input_size: 12, selectors: 16, auxiliaries: 1, lookups: 1 } }, Circuit { members: vec![374, 403], layout: FunctionLayout { input_size: 4, selectors: 19, auxiliaries: 48, lookups: 15 } }, Circuit { members: vec![380], layout: FunctionLayout { input_size: 4, selectors: 3, auxiliaries: 7, lookups: 6 } }, Circuit { members: vec![381], layout: FunctionLayout { input_size: 5, selectors: 2, auxiliaries: 12, lookups: 11 } }, Circuit { members: vec![382, 388], layout: FunctionLayout { input_size: 4, selectors: 6, auxiliaries: 16, lookups: 8 } }, Circuit { members: vec![384], layout: FunctionLayout { input_size: 4, selectors: 6, auxiliaries: 18, lookups: 9 } }, Circuit { members: vec![385], layout: FunctionLayout { input_size: 4, selectors: 5, auxiliaries: 10, lookups: 5 } }, Circuit { members: vec![386], layout: FunctionLayout { input_size: 2, selectors: 4, auxiliaries: 7, lookups: 3 } }, Circuit { members: vec![387], layout: FunctionLayout { input_size: 2, selectors: 12, auxiliaries: 22, lookups: 8 } }, Circuit { members: vec![390], layout: FunctionLayout { input_size: 4, selectors: 3, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![396, 417, 422, 578, 585, 592], layout: FunctionLayout { input_size: 6, selectors: 16, auxiliaries: 9, lookups: 4 } }, Circuit { members: vec![400], layout: FunctionLayout { input_size: 5, selectors: 25, auxiliaries: 16, lookups: 6 } }, Circuit { members: vec![414], layout: FunctionLayout { input_size: 5, selectors: 7, auxiliaries: 15, lookups: 5 } }, Circuit { members: vec![479, 427], layout: FunctionLayout { input_size: 2, selectors: 3, auxiliaries: 9, lookups: 4 } }, Circuit { members: vec![429], layout: FunctionLayout { input_size: 3, selectors: 2, auxiliaries: 4, lookups: 4 } }, Circuit { members: vec![430], layout: FunctionLayout { input_size: 4, selectors: 2, auxiliaries: 10, lookups: 9 } }, Circuit { members: vec![431], layout: FunctionLayout { input_size: 3, selectors: 9, auxiliaries: 22, lookups: 8 } }, Circuit { members: vec![434, 436, 439, 441], layout: FunctionLayout { input_size: 2, selectors: 18, auxiliaries: 10, lookups: 3 } }, Circuit { members: vec![438, 437, 442], layout: FunctionLayout { input_size: 5, selectors: 21, auxiliaries: 17, lookups: 11 } }, Circuit { members: vec![443], layout: FunctionLayout { input_size: 5, selectors: 8, auxiliaries: 21, lookups: 11 } }, Circuit { members: vec![451], layout: FunctionLayout { input_size: 3, selectors: 1, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![452], layout: FunctionLayout { input_size: 4, selectors: 1, auxiliaries: 3, lookups: 3 } }, Circuit { members: vec![455, 457, 460, 467, 478, 496], layout: FunctionLayout { input_size: 16, selectors: 35, auxiliaries: 36, lookups: 22 } }, Circuit { members: vec![468, 473, 474, 481, 484, 493, 501, 503, 505, 456], layout: FunctionLayout { input_size: 5, selectors: 31, auxiliaries: 22, lookups: 15 } }, Circuit { members: vec![466, 469, 476, 482, 488, 489, 490, 492, 494, 767, 773, 458], layout: FunctionLayout { input_size: 4, selectors: 21, auxiliaries: 6, lookups: 4 } }, Circuit { members: vec![459, 559, 564, 495, 497, 498, 499, 500, 502, 504], layout: FunctionLayout { input_size: 16, selectors: 36, auxiliaries: 11, lookups: 10 } }, Circuit { members: vec![485, 507, 555, 556, 465, 470, 471, 472, 475, 483, 491, 718], layout: FunctionLayout { input_size: 2, selectors: 40, auxiliaries: 10, lookups: 7 } }, Circuit { members: vec![549, 552, 553, 568], layout: FunctionLayout { input_size: 1, selectors: 248, auxiliaries: 100, lookups: 1 } }, Circuit { members: vec![550, 551], layout: FunctionLayout { input_size: 1, selectors: 131, auxiliaries: 74, lookups: 12 } }, Circuit { members: vec![557], layout: FunctionLayout { input_size: 2, selectors: 102, auxiliaries: 104, lookups: 40 } }, Circuit { members: vec![562], layout: FunctionLayout { input_size: 3, selectors: 19, auxiliaries: 68, lookups: 68 } }, Circuit { members: vec![563], layout: FunctionLayout { input_size: 1, selectors: 30, auxiliaries: 30, lookups: 1 } }, Circuit { members: vec![565], layout: FunctionLayout { input_size: 3, selectors: 56, auxiliaries: 58, lookups: 32 } }, Circuit { members: vec![566], layout: FunctionLayout { input_size: 3, selectors: 56, auxiliaries: 71, lookups: 71 } }, Circuit { members: vec![567], layout: FunctionLayout { input_size: 3, selectors: 66, auxiliaries: 82, lookups: 82 } }, Circuit { members: vec![570], layout: FunctionLayout { input_size: 1, selectors: 9, auxiliaries: 19, lookups: 5 } }, Circuit { members: vec![574], layout: FunctionLayout { input_size: 3, selectors: 9, auxiliaries: 20, lookups: 8 } }, Circuit { members: vec![691], layout: FunctionLayout { input_size: 1, selectors: 7, auxiliaries: 26, lookups: 7 } }, Circuit { members: vec![692], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 7, lookups: 5 } }, Circuit { members: vec![697], layout: FunctionLayout { input_size: 2, selectors: 3, auxiliaries: 6, lookups: 3 } }, Circuit { members: vec![699], layout: FunctionLayout { input_size: 4, selectors: 5, auxiliaries: 7, lookups: 8 } }, Circuit { members: vec![712], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 4, lookups: 4 } }, Circuit { members: vec![713], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 70, lookups: 8 } }, Circuit { members: vec![714], layout: FunctionLayout { input_size: 65, selectors: 1, auxiliaries: 4, lookups: 5 } }, Circuit { members: vec![715], layout: FunctionLayout { input_size: 2, selectors: 7, auxiliaries: 116, lookups: 10 } }, Circuit { members: vec![716], layout: FunctionLayout { input_size: 4, selectors: 6, auxiliaries: 73, lookups: 12 } }, Circuit { members: vec![753], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 10, lookups: 7 } }, Circuit { members: vec![754], layout: FunctionLayout { input_size: 3, selectors: 3, auxiliaries: 8, lookups: 5 } }, Circuit { members: vec![755], layout: FunctionLayout { input_size: 1, selectors: 3, auxiliaries: 15, lookups: 4 } }, Circuit { members: vec![756], layout: FunctionLayout { input_size: 3, selectors: 1, auxiliaries: 53, lookups: 6 } }, Circuit { members: vec![757], layout: FunctionLayout { input_size: 5, selectors: 3, auxiliaries: 15, lookups: 10 } }, Circuit { members: vec![758], layout: FunctionLayout { input_size: 8, selectors: 1, auxiliaries: 1, lookups: 2 } }, Circuit { members: vec![760], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 4 } }, Circuit { members: vec![765], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 4 } }, Circuit { members: vec![768], layout: FunctionLayout { input_size: 2, selectors: 1, auxiliaries: 5, lookups: 3 } }, Circuit { members: vec![775], layout: FunctionLayout { input_size: 1, selectors: 2, auxiliaries: 5, lookups: 3 } }, Circuit { members: vec![776], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 7, lookups: 4 } }, Circuit { members: vec![777], layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 6, lookups: 3 } }, Circuit { members: vec![784], layout: FunctionLayout { input_size: 14, selectors: 57, auxiliaries: 31, lookups: 8 } }] } } -pub static PROGRAM: GeneratedProgram = GeneratedProgram { fingerprint: [170, 92, 226, 63, 246, 182, 23, 209, 212, 212, 191, 239, 12, 22, 123, 170, 44, 208, 140, 255, 236, 216, 99, 67, 65, 3, 208, 61, 213, 142, 208, 36], complete: false, expected: expected_program, functions: &[ +pub static PROGRAM: GeneratedProgram = GeneratedProgram { fingerprint: [134, 255, 142, 155, 247, 231, 0, 155, 156, 136, 201, 185, 23, 188, 13, 83, 187, 4, 113, 195, 47, 43, 101, 210, 29, 29, 27, 138, 47, 144, 112, 191], complete: false, expected: expected_program, functions: &[ None, None, None, @@ -5459,7 +5459,7 @@ Some(FunctionWriter { layout: FunctionLayout { input_size: 7, selectors: 2, auxi None, None, Some(FunctionWriter { layout: FunctionLayout { input_size: 17, selectors: 5, auxiliaries: 7, lookups: 8 }, output_size: 0, seed_words: 24, schema: &SCHEMA_709, pack: pack_709, pack_typed: pack_709_typed, write: write_709 }), -Some(FunctionWriter { layout: FunctionLayout { input_size: 14, selectors: 2, auxiliaries: 4, lookups: 4 }, output_size: 0, seed_words: 18, schema: &SCHEMA_710, pack: pack_710, pack_typed: pack_710_typed, write: write_710 }), +Some(FunctionWriter { layout: FunctionLayout { input_size: 14, selectors: 2, auxiliaries: 5, lookups: 5 }, output_size: 0, seed_words: 19, schema: &SCHEMA_710, pack: pack_710, pack_typed: pack_710_typed, write: write_710 }), None, Some(FunctionWriter { layout: FunctionLayout { input_size: 2, selectors: 2, auxiliaries: 4, lookups: 4 }, output_size: 0, seed_words: 6, schema: &SCHEMA_712, pack: pack_712, pack_typed: pack_712_typed, write: write_712 }), None, diff --git a/crates/aiur/src/trace_codegen/programs/ixvm_cuda.rs b/crates/aiur/src/trace_codegen/programs/ixvm_cuda.rs index 2d779e50a..6b6b8302a 100644 --- a/crates/aiur/src/trace_codegen/programs/ixvm_cuda.rs +++ b/crates/aiur/src/trace_codegen/programs/ixvm_cuda.rs @@ -106,7 +106,7 @@ unsafe extern "C" { pub(super) fn aiur_trace_ixvm_782(device: i32, seeds: *const u8, encoding: u32, real: usize, rows: usize, width: usize, selectors: usize, auxiliaries: usize, output: *mut u64) -> i32; pub(super) fn aiur_trace_ixvm_783(device: i32, seeds: *const u8, encoding: u32, real: usize, rows: usize, width: usize, selectors: usize, auxiliaries: usize, output: *mut u64) -> i32; } -pub static CUDA: CudaLibrary = unsafe { CudaLibrary::new([170, 92, 226, 63, 246, 182, 23, 209, 212, 212, 191, 239, 12, 22, 123, 170, 44, 208, 140, 255, 236, 216, 99, 67, 65, 3, 208, 61, 213, 142, 208, 36], aiur_trace_ixvm_contract, aiur_trace_ixvm_schema, &[ +pub static CUDA: CudaLibrary = unsafe { CudaLibrary::new([134, 255, 142, 155, 247, 231, 0, 155, 156, 136, 201, 185, 23, 188, 13, 83, 187, 4, 113, 195, 47, 43, 101, 210, 29, 29, 27, 138, 47, 144, 112, 191], aiur_trace_ixvm_contract, aiur_trace_ixvm_schema, &[ None, None, None, @@ -892,4 +892,4 @@ None, Some(aiur_trace_ixvm_782), Some(aiur_trace_ixvm_783), None -], &[3, 67, 68, 3, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 17, 17, 9, 9, 9, 0, 0, 0, 71, 40, 170, 137, 25, 0, 0, 0, 194, 5, 146, 158, 0, 0, 0, 162, 79, 13, 12, 12, 11, 23, 22, 15, 3, 5, 7, 10, 53, 53, 53, 58, 59, 59, 67, 3, 35, 28, 32, 26, 17, 8, 4, 55, 31, 16, 81, 33, 33, 60, 41, 62, 13, 22, 49, 52, 48, 13, 13, 13, 63, 5, 15, 14, 14, 14, 13, 22, 31, 25, 3, 7, 7, 9, 31, 31, 35, 21, 37, 34, 99, 24, 33, 24, 0, 0, 0, 2, 19, 0, 33, 62, 2, 15, 2, 16, 42, 57, 44, 13, 22, 49, 68, 48, 0, 91, 9, 23, 44, 4, 17, 45, 4, 16, 26, 6, 14, 5, 0, 17, 0, 5, 19, 63, 5, 13, 16, 5, 5, 6, 14, 17, 17, 17, 33, 33, 33, 7, 7, 6, 16, 13, 13, 12, 19, 8, 19, 16, 15, 7, 3, 9, 2, 15, 20, 16, 33, 10, 0, 9, 21, 9, 18, 22, 0, 0, 11, 9, 12, 3, 17, 13, 9, 10, 13, 16, 21, 15, 17, 8, 8, 17, 19, 19, 13, 24, 19, 0, 16, 13, 11, 22, 7, 28, 14, 18, 30, 21, 17, 17, 39, 73, 39, 46, 22, 58, 89, 15, 0, 37, 3, 17, 67, 119, 110, 145, 110, 95, 3, 60, 4, 55, 7, 11, 10, 10, 10, 9, 11, 14, 8, 15, 8, 14, 17, 12, 14, 9, 24, 12, 20, 13, 9, 14, 10, 12, 11, 7, 10, 7, 8, 13, 11, 8, 9, 7, 8, 12, 8, 9, 9, 7, 11, 25, 10, 6, 10, 10, 3, 2, 3, 10, 4, 7, 9, 0, 6, 7, 6, 8, 6, 10, 7, 10, 7, 11, 5, 10, 7, 11, 10, 9, 6, 8, 8, 11, 6, 10, 9, 8, 11, 10, 15, 13, 13, 30, 9, 10, 16, 18, 7, 11, 9, 12, 12, 14, 32, 13, 12, 15, 26, 35, 10, 27, 36, 23, 11, 49, 14, 9, 17, 53, 23, 8, 9, 10, 8, 12, 0, 12, 17, 30, 24, 12, 0, 13, 13, 30, 36, 5, 13, 13, 47, 0, 14, 13, 13, 11, 10, 16, 6, 7, 21, 13, 8, 24, 15, 0, 12, 8, 10, 12, 16, 17, 11, 23, 9, 15, 19, 15, 15, 44, 13, 14, 14, 10, 34, 9, 18, 12, 6, 16, 15, 13, 41, 12, 12, 15, 28, 28, 13, 13, 21, 23, 40, 9, 19, 7, 13, 25, 11, 0, 10, 0, 10, 16, 14, 8, 14, 11, 22, 23, 7, 14, 12, 8, 10, 16, 44, 10, 7, 10, 18, 27, 20, 29, 6, 14, 26, 27, 73, 51, 28, 10, 4, 44, 14, 3, 11, 11, 11, 22, 24, 11, 5, 21, 21, 11, 6, 15, 4, 7, 2, 4, 23, 20, 3, 4, 3, 7, 7, 21, 6, 14, 39, 14, 18, 19, 19, 24, 21, 25, 22, 25, 18, 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 8, 13, 2, 2, 2, 2, 2, 42, 2, 2, 19, 19, 71, 2, 3, 35, 74, 85, 2, 13, 20, 4, 6, 6, 23, 5, 8, 10, 12, 18, 20, 16, 6, 12, 12, 11, 9, 0, 8, 17, 10, 8, 11, 12, 22, 37, 26, 11, 10, 12, 27, 14, 64, 73, 21, 12, 17, 8, 11, 33, 53, 50, 51, 17, 25, 16, 10, 7, 0, 71, 13, 14, 11, 11, 15, 0, 81, 120, 54, 89, 13, 16, 13, 7, 14, 16, 8, 10, 0, 7, 8, 17, 10, 0, 7, 20, 6, 8, 8, 29, 54, 58, 32, 24, 33, 37, 43, 27, 13, 55, 88, 23, 23, 34, 8, 33, 9, 8, 22, 37, 6, 10, 33, 10, 8, 8, 7, 19, 16, 13, 22, 29, 35, 15, 6, 9, 13, 51, 52, 34, 16, 25, 8, 16, 41, 54, 54, 7, 52, 11, 53, 64, 6, 8, 12, 77, 23, 9, 67, 24, 18, 19, 6, 72, 69, 118, 77, 6, 11, 12, 5, 5, 5, 5, 40, 14, 57, 52, 71, 14, 75, 66, 85, 75, 23, 2, 6, 6, 6, 4, 18, 68, 69, 49, 4, 84, 53, 4, 100, 112, 104, 5, 123, 13, 11, 16, 56, 20, 9, 8, 8, 28, 21, 22, 4, 8, 54, 7, 7, 111, 65, 29, 89, 5, 14, 6, 8, 7, 8, 15, 9, 8, 33, 22, 42], &[16, 528, 536, 16, 0, 48, 72, 0, 0, 0, 0, 0, 0, 0, 136, 24, 72, 72, 72, 0, 0, 0, 544, 296, 432, 840, 160, 0, 0, 0, 848, 40, 416, 272, 0, 0, 0, 176, 152, 104, 40, 40, 32, 64, 56, 48, 16, 16, 32, 32, 360, 360, 360, 96, 96, 96, 408, 16, 272, 216, 64, 64, 56, 48, 24, 96, 64, 56, 152, 72, 64, 112, 88, 120, 40, 48, 80, 88, 72, 40, 40, 40, 120, 32, 112, 48, 48, 48, 40, 40, 184, 184, 16, 32, 24, 32, 208, 224, 160, 120, 288, 200, 440, 176, 240, 168, 0, 0, 0, 16, 80, 0, 200, 200, 16, 64, 16, 64, 144, 272, 152, 56, 80, 184, 320, 192, 0, 304, 56, 48, 80, 24, 24, 168, 24, 48, 176, 32, 96, 24, 0, 136, 0, 32, 120, 208, 40, 96, 64, 24, 24, 32, 64, 24, 24, 24, 64, 64, 64, 40, 32, 40, 72, 64, 80, 64, 88, 48, 128, 80, 88, 32, 24, 48, 16, 88, 112, 80, 184, 56, 0, 32, 96, 48, 88, 160, 0, 0, 40, 48, 64, 24, 88, 64, 48, 48, 56, 64, 80, 72, 96, 48, 40, 96, 96, 96, 72, 128, 104, 0, 88, 80, 64, 168, 48, 200, 80, 112, 136, 144, 128, 128, 136, 240, 280, 336, 104, 416, 456, 120, 0, 280, 24, 112, 272, 440, 400, 512, 400, 336, 24, 200, 24, 200, 24, 72, 48, 40, 48, 48, 56, 80, 40, 64, 56, 64, 80, 64, 80, 48, 120, 72, 88, 48, 48, 56, 48, 40, 48, 40, 56, 32, 48, 80, 72, 48, 40, 48, 56, 80, 48, 56, 48, 48, 64, 160, 56, 32, 64, 56, 16, 16, 16, 40, 24, 32, 32, 0, 32, 40, 32, 40, 32, 56, 40, 48, 40, 64, 32, 64, 40, 64, 64, 40, 32, 32, 40, 56, 32, 56, 48, 48, 56, 56, 88, 72, 72, 192, 48, 48, 88, 88, 40, 56, 48, 64, 64, 80, 184, 80, 72, 80, 144, 184, 64, 152, 208, 136, 72, 288, 88, 64, 104, 192, 144, 56, 40, 32, 32, 72, 0, 72, 104, 200, 144, 72, 0, 72, 72, 192, 200, 40, 104, 104, 312, 0, 80, 72, 88, 56, 48, 72, 32, 32, 104, 64, 40, 136, 48, 0, 56, 32, 40, 48, 72, 48, 48, 104, 40, 64, 104, 64, 40, 248, 80, 40, 72, 40, 216, 40, 80, 56, 32, 72, 64, 104, 256, 64, 64, 72, 160, 160, 64, 104, 112, 144, 224, 56, 136, 40, 72, 168, 64, 0, 72, 0, 64, 88, 96, 56, 88, 80, 128, 128, 40, 72, 72, 40, 48, 96, 256, 56, 32, 64, 96, 136, 96, 136, 40, 80, 136, 144, 440, 280, 136, 56, 24, 240, 88, 16, 80, 80, 80, 136, 136, 64, 32, 128, 112, 64, 40, 96, 24, 40, 16, 24, 128, 120, 16, 24, 16, 32, 32, 80, 32, 104, 200, 96, 128, 120, 120, 144, 120, 136, 112, 136, 96, 16, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 40, 64, 16, 16, 16, 16, 16, 264, 16, 16, 112, 112, 416, 16, 24, 232, 424, 568, 16, 104, 136, 24, 32, 32, 144, 24, 56, 56, 72, 112, 112, 72, 32, 72, 56, 64, 56, 0, 32, 104, 32, 32, 72, 72, 136, 184, 168, 56, 56, 80, 168, 104, 272, 320, 144, 72, 88, 32, 56, 208, 184, 184, 184, 104, 168, 104, 48, 48, 0, 304, 56, 80, 64, 64, 112, 0, 384, 464, 200, 424, 72, 80, 96, 48, 88, 88, 56, 64, 0, 48, 48, 104, 56, 0, 48, 120, 40, 56, 48, 208, 344, 328, 200, 160, 224, 216, 272, 168, 72, 208, 416, 144, 56, 224, 56, 208, 56, 56, 136, 224, 40, 64, 200, 56, 56, 64, 40, 104, 80, 56, 144, 176, 192, 104, 40, 56, 88, 184, 184, 200, 80, 104, 48, 64, 144, 184, 400, 40, 160, 40, 192, 448, 32, 40, 72, 560, 120, 56, 232, 112, 80, 104, 24, 544, 544, 680, 560, 32, 48, 56, 32, 40, 32, 32, 216, 64, 168, 152, 296, 64, 264, 232, 368, 280, 48, 16, 16, 16, 16, 32, 144, 536, 536, 240, 24, 456, 152, 24, 624, 304, 440, 24, 576, 72, 56, 104, 184, 88, 72, 56, 48, 216, 160, 120, 24, 48, 256, 48, 48, 456, 304, 176, 360, 32, 96, 40, 48, 48, 56, 88, 64, 48, 184, 144, 312]) }; +], &[3, 67, 68, 3, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 17, 17, 9, 9, 9, 0, 0, 0, 71, 40, 170, 137, 25, 0, 0, 0, 194, 5, 146, 158, 0, 0, 0, 162, 79, 13, 12, 12, 11, 23, 22, 15, 3, 5, 7, 10, 53, 53, 53, 58, 59, 59, 67, 3, 35, 28, 32, 26, 17, 8, 4, 55, 31, 16, 81, 33, 33, 60, 41, 62, 13, 22, 49, 52, 48, 13, 13, 13, 63, 5, 15, 14, 14, 14, 13, 22, 31, 25, 3, 7, 7, 9, 31, 31, 35, 21, 37, 34, 99, 24, 33, 24, 0, 0, 0, 2, 19, 0, 33, 62, 2, 15, 2, 16, 42, 57, 44, 13, 22, 49, 68, 48, 0, 91, 9, 23, 44, 4, 17, 45, 4, 16, 26, 6, 14, 5, 0, 17, 0, 5, 19, 63, 5, 13, 16, 5, 5, 6, 14, 17, 17, 17, 33, 33, 33, 7, 7, 6, 16, 13, 13, 12, 19, 8, 19, 16, 15, 7, 3, 9, 2, 15, 20, 16, 33, 10, 0, 9, 21, 9, 18, 22, 0, 0, 11, 9, 12, 3, 17, 13, 9, 10, 13, 16, 21, 15, 17, 8, 8, 17, 19, 19, 13, 24, 19, 0, 16, 13, 11, 22, 7, 28, 14, 18, 30, 21, 17, 17, 39, 73, 39, 46, 22, 58, 89, 15, 0, 37, 3, 17, 67, 119, 110, 145, 110, 95, 3, 60, 4, 55, 7, 11, 10, 10, 10, 9, 11, 14, 8, 15, 8, 14, 17, 12, 14, 9, 24, 12, 20, 13, 9, 14, 10, 12, 11, 7, 10, 7, 8, 13, 11, 8, 9, 7, 8, 12, 8, 9, 9, 7, 11, 25, 10, 6, 10, 10, 3, 2, 3, 10, 4, 7, 9, 0, 6, 7, 6, 8, 6, 10, 7, 10, 7, 11, 5, 10, 7, 11, 10, 9, 6, 8, 8, 11, 6, 10, 9, 8, 11, 10, 15, 13, 13, 30, 9, 10, 16, 18, 7, 11, 9, 12, 12, 14, 32, 13, 12, 15, 26, 35, 10, 27, 36, 23, 11, 49, 14, 9, 17, 53, 23, 8, 9, 10, 8, 12, 0, 12, 17, 30, 24, 12, 0, 13, 13, 30, 36, 5, 13, 13, 47, 0, 14, 13, 13, 11, 10, 16, 6, 7, 21, 13, 8, 24, 15, 0, 12, 8, 10, 12, 16, 17, 11, 23, 9, 15, 19, 15, 15, 44, 13, 14, 14, 10, 34, 9, 18, 12, 6, 16, 15, 13, 41, 12, 12, 15, 28, 28, 13, 13, 21, 23, 40, 9, 19, 7, 13, 25, 11, 0, 10, 0, 10, 16, 14, 8, 14, 11, 22, 23, 7, 14, 12, 8, 10, 16, 44, 10, 7, 10, 18, 27, 20, 29, 6, 14, 26, 27, 73, 51, 28, 10, 4, 44, 14, 3, 11, 11, 11, 22, 24, 11, 5, 21, 21, 11, 6, 15, 4, 7, 2, 4, 23, 20, 3, 4, 3, 7, 7, 21, 6, 14, 39, 14, 18, 19, 19, 24, 21, 25, 22, 25, 18, 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 8, 13, 2, 2, 2, 2, 2, 42, 2, 2, 19, 19, 71, 2, 3, 35, 74, 85, 2, 13, 20, 4, 6, 6, 23, 5, 8, 10, 12, 18, 20, 16, 6, 12, 12, 11, 9, 0, 8, 17, 10, 8, 11, 12, 22, 37, 26, 11, 10, 12, 27, 14, 64, 73, 21, 12, 17, 8, 11, 33, 53, 50, 51, 17, 25, 16, 10, 7, 0, 71, 13, 14, 11, 11, 15, 0, 81, 120, 54, 89, 13, 16, 13, 7, 14, 16, 8, 10, 0, 7, 8, 17, 10, 0, 7, 20, 6, 8, 8, 29, 54, 58, 32, 24, 33, 37, 43, 27, 13, 55, 88, 23, 23, 34, 8, 33, 9, 8, 22, 37, 6, 10, 33, 10, 8, 8, 7, 19, 16, 13, 22, 29, 35, 15, 6, 9, 13, 51, 52, 34, 16, 25, 8, 16, 41, 54, 54, 7, 52, 11, 53, 64, 6, 8, 12, 77, 23, 9, 67, 24, 19, 22, 6, 72, 69, 118, 77, 6, 11, 12, 5, 5, 5, 5, 40, 14, 57, 52, 71, 14, 75, 66, 85, 75, 23, 2, 6, 6, 6, 4, 18, 68, 69, 49, 4, 84, 53, 4, 100, 112, 104, 5, 123, 13, 11, 16, 56, 20, 9, 8, 8, 28, 21, 22, 4, 8, 54, 7, 7, 111, 65, 29, 89, 5, 14, 6, 8, 7, 8, 15, 9, 8, 33, 22, 42], &[16, 528, 536, 16, 0, 48, 72, 0, 0, 0, 0, 0, 0, 0, 136, 24, 72, 72, 72, 0, 0, 0, 544, 296, 432, 840, 160, 0, 0, 0, 848, 40, 416, 272, 0, 0, 0, 176, 152, 104, 40, 40, 32, 64, 56, 48, 16, 16, 32, 32, 360, 360, 360, 96, 96, 96, 408, 16, 272, 216, 64, 64, 56, 48, 24, 96, 64, 56, 152, 72, 64, 112, 88, 120, 40, 48, 80, 88, 72, 40, 40, 40, 120, 32, 112, 48, 48, 48, 40, 40, 184, 184, 16, 32, 24, 32, 208, 224, 160, 120, 288, 200, 440, 176, 240, 168, 0, 0, 0, 16, 80, 0, 200, 200, 16, 64, 16, 64, 144, 272, 152, 56, 80, 184, 320, 192, 0, 304, 56, 48, 80, 24, 24, 168, 24, 48, 176, 32, 96, 24, 0, 136, 0, 32, 120, 208, 40, 96, 64, 24, 24, 32, 64, 24, 24, 24, 64, 64, 64, 40, 32, 40, 72, 64, 80, 64, 88, 48, 128, 80, 88, 32, 24, 48, 16, 88, 112, 80, 184, 56, 0, 32, 96, 48, 88, 160, 0, 0, 40, 48, 64, 24, 88, 64, 48, 48, 56, 64, 80, 72, 96, 48, 40, 96, 96, 96, 72, 128, 104, 0, 88, 80, 64, 168, 48, 200, 80, 112, 136, 144, 128, 128, 136, 240, 280, 336, 104, 416, 456, 120, 0, 280, 24, 112, 272, 440, 400, 512, 400, 336, 24, 200, 24, 200, 24, 72, 48, 40, 48, 48, 56, 80, 40, 64, 56, 64, 80, 64, 80, 48, 120, 72, 88, 48, 48, 56, 48, 40, 48, 40, 56, 32, 48, 80, 72, 48, 40, 48, 56, 80, 48, 56, 48, 48, 64, 160, 56, 32, 64, 56, 16, 16, 16, 40, 24, 32, 32, 0, 32, 40, 32, 40, 32, 56, 40, 48, 40, 64, 32, 64, 40, 64, 64, 40, 32, 32, 40, 56, 32, 56, 48, 48, 56, 56, 88, 72, 72, 192, 48, 48, 88, 88, 40, 56, 48, 64, 64, 80, 184, 80, 72, 80, 144, 184, 64, 152, 208, 136, 72, 288, 88, 64, 104, 192, 144, 56, 40, 32, 32, 72, 0, 72, 104, 200, 144, 72, 0, 72, 72, 192, 200, 40, 104, 104, 312, 0, 80, 72, 88, 56, 48, 72, 32, 32, 104, 64, 40, 136, 48, 0, 56, 32, 40, 48, 72, 48, 48, 104, 40, 64, 104, 64, 40, 248, 80, 40, 72, 40, 216, 40, 80, 56, 32, 72, 64, 104, 256, 64, 64, 72, 160, 160, 64, 104, 112, 144, 224, 56, 136, 40, 72, 168, 64, 0, 72, 0, 64, 88, 96, 56, 88, 80, 128, 128, 40, 72, 72, 40, 48, 96, 256, 56, 32, 64, 96, 136, 96, 136, 40, 80, 136, 144, 440, 280, 136, 56, 24, 240, 88, 16, 80, 80, 80, 136, 136, 64, 32, 128, 112, 64, 40, 96, 24, 40, 16, 24, 128, 120, 16, 24, 16, 32, 32, 80, 32, 104, 200, 96, 128, 120, 120, 144, 120, 136, 112, 136, 96, 16, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 40, 64, 16, 16, 16, 16, 16, 264, 16, 16, 112, 112, 416, 16, 24, 232, 424, 568, 16, 104, 136, 24, 32, 32, 144, 24, 56, 56, 72, 112, 112, 72, 32, 72, 56, 64, 56, 0, 32, 104, 32, 32, 72, 72, 136, 184, 168, 56, 56, 80, 168, 104, 272, 320, 144, 72, 88, 32, 56, 208, 184, 184, 184, 104, 168, 104, 48, 48, 0, 304, 56, 80, 64, 64, 112, 0, 384, 464, 200, 424, 72, 80, 96, 48, 88, 88, 56, 64, 0, 48, 48, 104, 56, 0, 48, 120, 40, 56, 48, 208, 344, 328, 200, 160, 224, 216, 272, 168, 72, 208, 416, 144, 56, 224, 56, 208, 56, 56, 136, 224, 40, 64, 200, 56, 56, 64, 40, 104, 80, 56, 144, 176, 192, 104, 40, 56, 88, 184, 184, 200, 80, 104, 48, 64, 144, 184, 400, 40, 160, 40, 192, 448, 32, 40, 72, 560, 120, 56, 232, 112, 88, 120, 24, 544, 544, 680, 560, 32, 48, 56, 32, 40, 32, 32, 216, 64, 168, 152, 296, 64, 264, 232, 368, 280, 48, 16, 16, 16, 16, 32, 144, 536, 536, 240, 24, 456, 152, 24, 624, 304, 440, 24, 576, 72, 56, 104, 184, 88, 72, 56, 48, 216, 160, 120, 24, 48, 256, 48, 48, 456, 304, 176, 360, 32, 96, 40, 48, 48, 56, 88, 64, 48, 184, 144, 312]) }; diff --git a/crates/bench/Cargo.toml b/crates/bench/Cargo.toml deleted file mode 100644 index 527e17f7f..000000000 --- a/crates/bench/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "ix-bench" -version.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -serde_json = { workspace = true } -tracing-texray = { workspace = true } - -[lints] -workspace = true diff --git a/crates/bench/src/lib.rs b/crates/bench/src/lib.rs deleted file mode 100644 index 99e02ef40..000000000 --- a/crates/bench/src/lib.rs +++ /dev/null @@ -1,220 +0,0 @@ -//! The benchmark row contract shared by every measured tool. -//! -//! Each tool (`bench-typecheck`, `ix check-rs`, -//! `ix compile`) reports its results as one JSON object per benchmark name in -//! a single file: -//! -//! ```json -//! { "": { "status": "ok", "": , ..., -//! "phase:": , ... } } -//! ``` -//! -//! Rows are flushed to disk after every name, so a killed process still -//! leaves a complete file of the rows measured so far. The orchestrator -//! (`ix bench run`) identifies a killed run's in-flight name as the first -//! requested name without a row, and merges `"status": "oom"` into whatever -//! partial metrics that row holds — tools themselves only ever write `ok` or -//! `rejected`. -//! -//! Exit codes are the failure channel (no stdout markers, no log grepping): -//! `0` — every row `ok`; [`EXIT_USAGE`] — bad invocation; [`EXIT_REJECTED`] — -//! the kernel rejected a constant (its `rejected` row is on disk; the tool -//! fails fast, so later names have no rows). Any other exit is an -//! infrastructure failure. - -use std::collections::HashSet; -use std::io; -use std::path::Path; - -use serde_json::{Map, Value}; - -/// Bad invocation (unknown name, missing input, conflicting flags). -pub const EXIT_USAGE: u8 = 2; -/// The kernel rejected at least one constant; its row is on disk. -pub const EXIT_REJECTED: u8 = 3; - -/// Row outcome as written by a tool (`Oom` is only ever set by the -/// orchestrator, after the fact — a tool never observes its own OOM kill). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Status { - Ok, - Rejected, - Oom, -} - -impl Status { - pub fn as_str(self) -> &'static str { - match self { - Status::Ok => "ok", - Status::Rejected => "rejected", - Status::Oom => "oom", - } - } -} - -/// Typed kernel-rejection error. Binaries write the `rejected` row, propagate -/// this through their error chain, and map it to [`EXIT_REJECTED`] in `main` — -/// keeping the failure channel an exit code rather than an error-message -/// format some consumer has to grep. -#[derive(Debug)] -pub struct Rejection { - pub failures: u64, - pub ctx: String, -} - -impl std::fmt::Display for Rejection { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "kernel typecheck rejected {} item(s) in {}", - self.failures, self.ctx - ) - } -} - -impl std::error::Error for Rejection {} - -/// Peak resident set size (bytes) across this process *and its children*, -/// from tracing-texray's tree sampler. `None` until the sampler has started -/// or off Linux. Unlike a bare `/proc/self/status` read this includes child -/// processes. -pub fn peak_rss_bytes() -> Option { - match tracing_texray::rss_sampler::peak_tree_rss_bytes() { - 0 => None, - n => Some(n), - } -} - -/// Constants checked per second — the one meaning `throughput` has on -/// every backend. Zero when the timed window is empty. -/// -/// The count converts through u32, the widest type with a lossless -/// `f64::from`. A count beyond u32 (no env comes within orders of -/// magnitude) panics rather than reporting a wrong number. -pub fn throughput(constants: usize, secs: f64) -> f64 { - let constants = - u32::try_from(constants).expect("constant count exceeds u32::MAX"); - if secs > 0.0 { f64::from(constants) / secs } else { 0.0 } -} - -/// Write the row `{ "": { "status": …, …metrics } }` into the results -/// file at `path`, merging into any existing object (overwriting on -/// collision) so a multi-name run accumulates one map with a row per name. -pub fn write_row( - path: &Path, - name: &str, - status: Status, - metrics: Value, -) -> io::Result<()> { - let mut row = match metrics { - Value::Object(m) => m, - Value::Null => Map::new(), - other => { - let mut m = Map::new(); - m.insert("value".to_string(), other); - m - }, - }; - row.insert("status".to_string(), Value::String(status.as_str().into())); - write_json_entry(path, name, Value::Object(row)) -} - -/// Append the entry `{ "": }` to the JSON object at `path`, -/// creating the file if absent. The write is ATOMIC (temp file + rename): -/// the accumulator is shared by sequential per-constant processes and -/// their orchestrator, and a watchdog KILL landing mid-write would -/// otherwise truncate it — which the tolerant readers on both sides would -/// silently reset to `{}`, losing every prior row. serde_json handles key -/// escaping, so arbitrary Lean names are safe. -pub fn write_json_entry( - path: &Path, - name: &str, - value: Value, -) -> io::Result<()> { - let mut map: Map = match std::fs::read(path) { - Ok(bytes) => serde_json::from_slice(&bytes).unwrap_or_default(), - Err(e) if e.kind() == io::ErrorKind::NotFound => Map::new(), - Err(e) => return Err(e), - }; - map.insert(name.to_string(), value); - let mut tmp = path.as_os_str().to_owned(); - tmp.push(".tmp"); - std::fs::write(&tmp, serde_json::to_string(&Value::Object(map))?)?; - std::fs::rename(&tmp, path) -} - -/// Union comma-separated `--consts` values with names read from a -/// `--consts-file` (one per line, `#` comments and blank lines dropped), -/// preserving first-seen order so the same name is never measured twice. -pub fn collect_consts( - consts: &[String], - consts_file: Option<&Path>, -) -> io::Result> { - let mut seen: HashSet = HashSet::new(); - let mut out: Vec = Vec::new(); - for name in consts { - let trimmed = name.trim(); - if !trimmed.is_empty() && seen.insert(trimmed.to_string()) { - out.push(trimmed.to_string()); - } - } - if let Some(path) = consts_file { - let contents = std::fs::read_to_string(path)?; - for line in contents.lines() { - let name = line.split('#').next().unwrap_or("").trim(); - if !name.is_empty() && seen.insert(name.to_string()) { - out.push(name.to_string()); - } - } - } - Ok(out) -} - -#[cfg(test)] -mod tests { - use serde_json::json; - - use super::*; - - #[test] - fn collect_unions_and_dedups() { - let path = std::env::temp_dir().join("ix_bench_test_consts.txt"); - std::fs::write(&path, "a\nb\n# comment\n c \n\na\n").expect("write"); - let consts = vec!["a".to_string(), "d".to_string()]; - let got = collect_consts(&consts, Some(&path)).expect("collect"); - assert_eq!(got, vec!["a", "d", "b", "c"]); - let _ = std::fs::remove_file(&path); - } - - #[test] - fn collect_trims_and_skips_empty() { - let consts = vec![" a ".to_string(), "".to_string(), "a".to_string()]; - let got = collect_consts(&consts, None).expect("collect"); - assert_eq!(got, vec!["a"]); - } - - #[test] - fn rows_accumulate_and_overwrite() { - let path = std::env::temp_dir().join("ix_bench_test_rows.json"); - let _ = std::fs::remove_file(&path); - write_row(&path, "a", Status::Ok, json!({"cycles": 1})).expect("write a"); - write_row(&path, "b", Status::Rejected, json!({})).expect("write b"); - write_row(&path, "a", Status::Ok, json!({"cycles": 2})).expect("rewrite"); - let v: Value = serde_json::from_slice(&std::fs::read(&path).expect("read")) - .expect("parse"); - assert_eq!(v["a"], json!({"status": "ok", "cycles": 2})); - assert_eq!(v["b"], json!({"status": "rejected"})); - let _ = std::fs::remove_file(&path); - } - - #[test] - fn row_survives_preexisting_garbage() { - let path = std::env::temp_dir().join("ix_bench_test_garbage.json"); - std::fs::write(&path, "not json").expect("write"); - write_row(&path, "a", Status::Ok, json!({"t": 1.5})).expect("write"); - let v: Value = serde_json::from_slice(&std::fs::read(&path).expect("read")) - .expect("parse"); - assert_eq!(v["a"]["status"], "ok"); - let _ = std::fs::remove_file(&path); - } -} diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 6ff7109c3..9c66a2881 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -7,13 +7,11 @@ license.workspace = true [dependencies] bignat = { workspace = true } blake3 = { workspace = true } -indexmap = { workspace = true } quickcheck = { workspace = true, optional = true } rustc-hash = { workspace = true } [dev-dependencies] quickcheck = { workspace = true } -quickcheck_macros = { workspace = true } [features] quickcheck = ["dep:quickcheck"] diff --git a/crates/common/src/address.rs b/crates/common/src/address.rs index 54182db3b..7115bca95 100644 --- a/crates/common/src/address.rs +++ b/crates/common/src/address.rs @@ -55,64 +55,6 @@ impl Address { .map(|c| Address::from_slice(c).expect("malformed address chunk")) } - /// Build a deterministic, collision-resistant `Name` for this address: - /// `Ix._#.`. Mirrors Lean-side `Ix.Address.toUniqueName`. - /// - /// Use this when you need to register a KId/Named entry at a synthetic - /// name that can't collide with any Lean-originated name (e.g. for - /// scratch `KEnv` entries that should not participate in the - /// `name_to_addr` namespace). - pub fn to_unique_name(&self) -> crate::env::Name { - use crate::env::Name; - Name::str( - Name::str(Name::str(Name::anon(), "Ix".to_string()), "_#".to_string()), - self.hex(), - ) - } - - /// Inverse of `to_unique_name`. Returns `Some(Address)` iff `name` has - /// shape `Ix._#.` with valid 64-char hex; otherwise `None`. - pub fn from_unique_name(name: &crate::env::Name) -> Option { - use crate::env::NameData; - let (parent, hex) = match name.as_data() { - NameData::Str(parent, s, _) => (parent.clone(), s.clone()), - _ => return None, - }; - let parent = match parent.as_data() { - NameData::Str(pp, s, _) if s == "_#" => pp.clone(), - _ => return None, - }; - match parent.as_data() { - NameData::Str(ppp, s, _) if s == "Ix" => match ppp.as_data() { - NameData::Anonymous(_) => Address::from_hex(&hex), - _ => None, - }, - _ => None, - } - } - - /// Build a synthetic `Name` for a mutual block's `Named` entry: - /// `Ix..`. Disambiguates alpha-equivalent blocks - /// that share an `addr` but have different member names. - /// - /// Used by `compile_mutual` to register each mutual block under a - /// Muts-tagged meta so kernel ingress can discover and process it via - /// `ingress_muts_block`. - pub fn muts_name(&self, first_member: &crate::env::Name) -> crate::env::Name { - use crate::env::{Name, NameData}; - let base = Name::str(Name::str(Name::anon(), "Ix".to_string()), self.hex()); - // Append each component of `first_member` to the base, preserving - // numeric vs string parts. - fn go(base: Name, name: &Name) -> Name { - match name.as_data() { - NameData::Anonymous(_) => base, - NameData::Str(parent, s, _) => Name::str(go(base, parent), s.clone()), - NameData::Num(parent, n, _) => Name::num(go(base, parent), n.clone()), - } - } - go(base, first_member) - } - /// Constructs an address from a 64-character hexadecimal string. pub fn from_hex(hex: &str) -> Option { if hex.len() != 64 { diff --git a/crates/common/src/env.rs b/crates/common/src/env.rs deleted file mode 100644 index 7520bdfcd..000000000 --- a/crates/common/src/env.rs +++ /dev/null @@ -1,2112 +0,0 @@ -//! LEON (Lean Environment Objective Notation) types. -//! -//! This module defines a content-addressed representation of the Lean 4 kernel -//! type system. Every term, name, universe level, and constant is hashed with -//! Blake3 to produce a deterministic content address. -//! -//! Constructor tags (`NANON`, `NSTR`, `EVAR`, `DEFN`, etc.) are single-byte -//! discriminants prepended to the hasher input so that structurally identical -//! subtrees in different syntactic categories never collide. - -use blake3::Hash; -use std::{ - hash::{Hash as StdHash, Hasher}, - sync::Arc, -}; - -use bignat::Nat; -use rustc_hash::{FxHashMap, FxHashSet}; - -// -- Name tags ---------------------------------------------------------------- - -/// Tag for the anonymous (root) name. -pub const NANON: u8 = 0x00; -/// Tag for a string name component. -pub const NSTR: u8 = 0x01; -/// Tag for a numeric name component. -pub const NNUM: u8 = 0x02; - -// -- Level tags --------------------------------------------------------------- - -/// Tag for universe level zero. -pub const UZERO: u8 = 0x03; -/// Tag for universe level successor. -pub const USUCC: u8 = 0x04; -/// Tag for universe level max. -pub const UMAX: u8 = 0x05; -/// Tag for universe level imax. -pub const UIMAX: u8 = 0x06; -/// Tag for a universe level parameter. -pub const UPARAM: u8 = 0x10; -/// Tag for a universe level metavariable. -pub const UMVAR: u8 = 0x70; - -// -- Expr tags ---------------------------------------------------------------- - -/// Tag for a bound variable (de Bruijn index). -pub const EVAR: u8 = 0x20; -/// Tag for a sort expression. -pub const ESORT: u8 = 0x80; -/// Tag for a constant reference. -pub const EREF: u8 = 0x30; -/// Tag for a projection expression. -pub const EPRJ: u8 = 0x50; -/// Tag for a string literal expression. -pub const ESTR: u8 = 0x81; -/// Tag for a natural number literal expression. -pub const ENAT: u8 = 0x82; -/// Tag for a function application. -pub const EAPP: u8 = 0x83; -/// Tag for a lambda abstraction. -pub const ELAM: u8 = 0x84; -/// Tag for a dependent function type (forall / Pi). -pub const EALL: u8 = 0x85; -/// Tag for a let-binding. -pub const ELET: u8 = 0x86; -/// Tag for a free variable. -pub const EFVAR: u8 = 0x72; -/// Tag for an expression metavariable. -pub const EMVAR: u8 = 0x73; -/// Tag for metadata-annotated expressions. -pub const EMDATA: u8 = 0x74; - -// -- Constant tags ------------------------------------------------------------ - -/// Tag for a definition constant. -pub const DEFN: u8 = 0xA0; -/// Tag for a recursor constant. -pub const RECR: u8 = 0xA1; -/// Tag for an axiom constant. -pub const AXIO: u8 = 0xA2; -/// Tag for a quotient constant. -pub const QUOT: u8 = 0xA3; -/// Tag for an inductive type constant. -pub const INDC: u8 = 0xA6; -/// Tag for a constructor constant. -pub const CTOR: u8 = 0xC0; -/// Tag for a theorem constant. -pub const THEO: u8 = 0xC1; -/// Tag for an opaque constant. -pub const OPAQ: u8 = 0xC2; - -// -- Metadata tags ------------------------------------------------------------ - -/// Tag for an integer metadata value. -pub const MINT: u8 = 0xF1; -/// Tag for a substring metadata value. -pub const MSSTR: u8 = 0xF2; -/// Tag for source info metadata. -pub const MSINFO: u8 = 0xF3; -/// Tag for syntax pre-resolution metadata. -pub const MSPRE: u8 = 0xF4; -/// Tag for syntax tree metadata. -pub const MSYN: u8 = 0xF5; -/// Tag for a generic data value in metadata. -pub const MDVAL: u8 = 0xF6; - -/// A content-addressed hierarchical name. -/// -/// Names are interned via `Arc` and compared/hashed by their Blake3 digest. -#[derive(PartialEq, Eq, Debug, Clone)] -pub struct Name(pub Arc); - -impl PartialOrd for Name { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Name { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.get_hash().as_bytes().cmp(other.get_hash().as_bytes()) - } -} - -/// The underlying data for a [`Name`]. -/// -/// A single component of a hierarchical name. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum NameComponent { - Str(String), - Num(Nat), -} - -/// Each variant carries its precomputed Blake3 hash as the last field. -#[derive(PartialEq, Eq, Debug)] -pub enum NameData { - /// The root (empty) name. - Anonymous(Hash), - /// A string component appended to a prefix name. - Str(Name, String, Hash), - /// A numeric component appended to a prefix name. - Num(Name, Nat, Hash), -} - -impl Name { - /// Component equality, independent of every cached digest. - pub fn same_structure(&self, other: &Self) -> bool { - let (mut a, mut b) = (self, other); - loop { - if Arc::ptr_eq(&a.0, &b.0) { - return true; - } - match (a.as_data(), b.as_data()) { - (NameData::Anonymous(_), NameData::Anonymous(_)) => return true, - (NameData::Str(ap, av, _), NameData::Str(bp, bv, _)) if av == bv => { - (a, b) = (ap, bp); - }, - (NameData::Num(ap, av, _), NameData::Num(bp, bv, _)) if av == bv => { - (a, b) = (ap, bp); - }, - _ => return false, - } - } - } - - /// Raw equality: the same components and the same stored digest at every - /// node, which is the derived `==`, iteratively and pointer-first. The - /// exact cache keys (`expr_key`) and the inductive-flag transform memos - /// confirm hits with it: a stored digest is data of the compiled - /// metadata, so raw equality, - /// not the digest-blind [`Name::same_structure`], is what a hit must keep. - pub fn same_raw(&self, other: &Self) -> bool { - let (mut a, mut b) = (self, other); - loop { - if Arc::ptr_eq(&a.0, &b.0) { - return true; - } - if a.get_hash() != b.get_hash() { - return false; - } - match (a.as_data(), b.as_data()) { - (NameData::Anonymous(_), NameData::Anonymous(_)) => return true, - (NameData::Str(ap, av, _), NameData::Str(bp, bv, _)) if av == bv => { - (a, b) = (ap, bp); - }, - (NameData::Num(ap, av, _), NameData::Num(bp, bv, _)) if av == bv => { - (a, b) = (ap, bp); - }, - _ => return false, - } - } - } - - /// Returns a reference to the inner [`NameData`]. - pub fn as_data(&self) -> &NameData { - &self.0 - } - - /// Returns the precomputed Blake3 hash of this name. - pub fn get_hash(&self) -> &Hash { - match self.0.as_ref() { - NameData::Anonymous(h) | NameData::Str(.., h) | NameData::Num(.., h) => h, - } - } - /// Constructs the anonymous (root) name. - pub fn anon() -> Self { - let hash = blake3::hash(&[NANON]); - Name(Arc::new(NameData::Anonymous(hash))) - } - - /// Constructs a name by appending a string component to `pre`. - pub fn str(pre: Name, s: String) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[NSTR]); - hasher.update(pre.get_hash().as_bytes()); - hasher.update(s.as_bytes()); - let hash = hasher.finalize(); - Name(Arc::new(NameData::Str(pre, s, hash))) - } - - /// Constructs a name by appending a numeric component to `pre`. - pub fn num(pre: Name, n: Nat) -> Name { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[NNUM]); - hasher.update(pre.get_hash().as_bytes()); - hasher.update(&n.to_le_bytes()); - let hash = hasher.finalize(); - Name(Arc::new(NameData::Num(pre, n, hash))) - } - /// Decompose this name into its components (from root to leaf). - pub fn components(&self) -> Vec { - let mut components = Vec::new(); - let mut current = self; - loop { - match current.as_data() { - NameData::Anonymous(_) => break, - NameData::Str(pre, s, _) => { - components.push(NameComponent::Str(s.clone())); - current = pre; - }, - NameData::Num(pre, n, _) => { - components.push(NameComponent::Num(n.clone())); - current = pre; - }, - } - } - components.reverse(); - components - } - - /// Strip a prefix from this name, returning the suffix components. - pub fn strip_prefix(&self, prefix: &Name) -> Option> { - let self_components = self.components(); - let prefix_components = prefix.components(); - if self_components.len() < prefix_components.len() { - return None; - } - if self_components[..prefix_components.len()] != prefix_components[..] { - return None; - } - Some(self_components[prefix_components.len()..].to_vec()) - } - - /// Append suffix components to this name. - pub fn append_components(&self, suffix: &[NameComponent]) -> Name { - let mut result = self.clone(); - for component in suffix { - match component { - NameComponent::Str(s) => result = Name::str(result, s.clone()), - NameComponent::Num(n) => result = Name::num(result, n.clone()), - } - } - result - } - - /// Get the last string component of this name, if any. - pub fn last_str(&self) -> Option<&str> { - match self.as_data() { - NameData::Str(_, s, _) => Some(s.as_str()), - _ => None, - } - } - - /// Returns a dot-separated human-readable representation of this name. - pub fn pretty(&self) -> String { - let mut components = Vec::new(); - let mut current = self; - - loop { - match current.as_data() { - NameData::Anonymous(_) => break, - NameData::Str(pre, s, _) => { - components.push(s.as_str().to_owned()); - current = pre; - }, - NameData::Num(pre, n, _) => { - components.push(n.0.to_string()); - current = pre; - }, - } - } - - components.reverse(); - components.join(".") - } -} - -impl StdHash for Name { - fn hash(&self, state: &mut H) { - self.get_hash().hash(state); - } -} - -impl std::fmt::Display for Name { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.pretty()) - } -} - -/// Canonical key for cross-kernel displayed-name matching: Lean's -/// `Name.toString` wraps non-identifier components in `«»` -/// (`_private.….«0».…`, `Lean.Order.«term_⊑_»`) while [`Name::pretty`] -/// renders them bare, so a fail-out file written by one side misses a -/// string-keyed lookup on the other. Stripping the guillemets from BOTH -/// the request and the candidate collapses the two renderings onto one -/// key, at the cost of the same flattening ambiguity `pretty` already -/// has. Mirrors `Ix.Cli.ConstsFile.normalizeName` on the Lean side. -pub fn normalize_displayed_name(s: &str) -> String { - if s.contains(['«', '»']) { - s.chars().filter(|c| !matches!(c, '«' | '»')).collect() - } else { - s.to_string() - } -} - -/// A content-addressed universe level. -/// -/// Levels are interned via `Arc` and compared/hashed by their Blake3 digest. -#[derive(PartialEq, Eq, Debug, Clone)] -pub struct Level(pub Arc); - -/// The underlying data for a [`Level`]. -/// -/// Each variant carries its precomputed Blake3 hash as the last field. -#[derive(Debug, PartialEq, Eq)] -pub enum LevelData { - /// Universe level 0 (Prop). - Zero(Hash), - /// Successor of a universe level. - Succ(Level, Hash), - /// Maximum of two universe levels. - Max(Level, Level, Hash), - /// Impredicative maximum of two universe levels. - Imax(Level, Level, Hash), - /// A named universe parameter. - Param(Name, Hash), - /// A universe-level metavariable. - Mvar(Name, Hash), -} - -impl Level { - /// Structural equality, independent of all level and name cached digests: - /// no digest is an equality witness. - pub fn same_structure(&self, other: &Self) -> bool { - Self::compare(self, other, false) - } - - /// Raw equality: the same structure and the same stored digest at every - /// level and name node (the derived `==`, iteratively, pointer-first, and - /// linear in the DAG). See [`Name::same_raw`]. - pub fn same_raw(&self, other: &Self) -> bool { - Self::compare(self, other, true) - } - - /// The one level comparison (R03-21): an explicit stack, and a set of - /// completed pairs, so a DAG-shared pair is compared once. The set is made - /// on the first completed composite pair only, as `max_smart` compares - /// small levels on a hot path. Both borrowed roots keep every node alive, - /// so a node pointer cannot be reused during the comparison. With - /// `digests`, the stored digests of every level and name node must agree - /// too. - fn compare(a: &Level, b: &Level, digests: bool) -> bool { - type Pair = (*const LevelData, *const LevelData); - let mut completed: Option> = None; - let mut pending = vec![(a, b, false)]; - while let Some((a, b, finish)) = pending.pop() { - let pair = (Arc::as_ptr(&a.0), Arc::as_ptr(&b.0)); - if finish { - completed.get_or_insert_with(FxHashSet::default).insert(pair); - continue; - } - if pair.0 == pair.1 - || completed.as_ref().is_some_and(|c| c.contains(&pair)) - { - continue; - } - if digests && a.get_hash() != b.get_hash() { - return false; - } - match (a.as_data(), b.as_data()) { - (LevelData::Zero(_), LevelData::Zero(_)) => {}, - (LevelData::Succ(x, _), LevelData::Succ(y, _)) => { - pending.extend([(a, b, true), (x, y, false)]); - }, - (LevelData::Max(x, y, _), LevelData::Max(z, w, _)) - | (LevelData::Imax(x, y, _), LevelData::Imax(z, w, _)) => { - pending.extend([(a, b, true), (y, w, false), (x, z, false)]); - }, - (LevelData::Param(x, _), LevelData::Param(y, _)) - | (LevelData::Mvar(x, _), LevelData::Mvar(y, _)) => { - let same = if digests { x.same_raw(y) } else { x.same_structure(y) }; - if !same { - return false; - } - }, - _ => return false, - } - } - true - } - - /// Returns a reference to the inner [`LevelData`]. - pub fn as_data(&self) -> &LevelData { - &self.0 - } - - /// Returns the precomputed Blake3 hash of this level. - pub fn get_hash(&self) -> &Hash { - match &*self.0 { - LevelData::Zero(h) - | LevelData::Succ(_, h) - | LevelData::Max(.., h) - | LevelData::Imax(.., h) - | LevelData::Param(_, h) - | LevelData::Mvar(_, h) => h, - } - } - /// Constructs universe level 0. - pub fn zero() -> Self { - Level(Arc::new(LevelData::Zero(blake3::hash(&[UZERO])))) - } - /// Constructs the successor of universe level `x`. - pub fn succ(x: Level) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[USUCC]); - hasher.update(x.get_hash().as_bytes()); - Level(Arc::new(LevelData::Succ(x, hasher.finalize()))) - } - /// Constructs `max x y`. - pub fn max(x: Level, y: Level) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[UMAX]); - hasher.update(x.get_hash().as_bytes()); - hasher.update(y.get_hash().as_bytes()); - Level(Arc::new(LevelData::Max(x, y, hasher.finalize()))) - } - /// Smart `max x y` constructor mirroring the kernel's `KUniv::max`. Applies - /// Lean-style level simplifications so substituted levels match the - /// canonical form the kernel sees post-ingress: `max(a,a)=a`, zero - /// absorption, same-base offset, and `Max` absorption. Used by the - /// compiler's inductive-flags check when it substitutes levels, so that - /// its `Sort` levels agree with the kernel's (see - /// `kernel/level.rs:KUniv::max`). - pub fn max_smart(x: Level, y: Level) -> Self { - if let (Some((bx, ox)), Some((by, oy))) = - (x.explicit_offset(), y.explicit_offset()) - { - // Both explicit numerals (Succ^n(Zero)): take the larger. - let _ = (bx, by); - return if ox >= oy { x } else { y }; - } - if x.same_structure(&y) { - return x; - } - if matches!(x.as_data(), LevelData::Zero(_)) { - return y; - } - if matches!(y.as_data(), LevelData::Zero(_)) { - return x; - } - // max(a, max(a, b')) = max(a, b'), max(a, max(b', a)) = max(b', a) - if let LevelData::Max(bl, br, _) = y.as_data() - && (bl.same_structure(&x) || br.same_structure(&x)) - { - return y; - } - // max(max(a', b), b) = max(a', b), max(max(b, a'), b) = max(b, a') - if let LevelData::Max(al, ar, _) = x.as_data() - && (al.same_structure(&y) || ar.same_structure(&y)) - { - return x; - } - // Same base, different offsets: succ^n(x) vs succ^m(x) → take larger. - let (base_x, off_x) = x.peel_succ(); - let (base_y, off_y) = y.peel_succ(); - if base_x.same_structure(&base_y) { - return if off_x >= off_y { x } else { y }; - } - Self::max(x, y) - } - /// Smart `imax x y` constructor mirroring the kernel's `KUniv::imax`. - /// Applies Lean-style simplifications: when `y` is provably never zero - /// (succ-headed), `imax = max`; `imax(_, 0) = 0`; `imax(0, b) = b`; - /// `imax(1, b) = b`; `imax(a, a) = a`. Used in the same canonical-sort - /// path as [`Level::max_smart`]. - pub fn imax_smart(x: Level, y: Level) -> Self { - // y "never zero" cases: succ-headed levels are always > 0, so - // imax(a, succ _) = max(a, succ _). - if matches!(y.as_data(), LevelData::Succ(_, _)) { - return Self::max_smart(x, y); - } - if matches!(y.as_data(), LevelData::Zero(_)) { - return y; // imax(a, 0) = 0 - } - if matches!(x.as_data(), LevelData::Zero(_)) { - return y; // imax(0, b) = b - } - // imax(1, b) = b - if let LevelData::Succ(inner, _) = x.as_data() - && matches!(inner.as_data(), LevelData::Zero(_)) - { - return y; - } - if x.same_structure(&y) { - return x; - } - Self::imax(x, y) - } - /// Peel a chain of `Succ` constructors. Returns `(base, n)` where - /// `level == Succ^n(base)` and `base` is not a `Succ`. - pub fn peel_succ(&self) -> (Level, u64) { - let mut cur = self.clone(); - let mut n: u64 = 0; - while let LevelData::Succ(inner, _) = cur.as_data() { - n += 1; - cur = inner.clone(); - } - (cur, n) - } - /// If this level is an explicit numeral `Succ^n(Zero)`, returns - /// `Some((Zero, n))`. Otherwise returns `None`. - pub fn explicit_offset(&self) -> Option<(Level, u64)> { - let (base, n) = self.peel_succ(); - if matches!(base.as_data(), LevelData::Zero(_)) { - Some((base, n)) - } else { - None - } - } - /// Constructs `imax x y` (impredicative max). - pub fn imax(x: Level, y: Level) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[UIMAX]); - hasher.update(x.get_hash().as_bytes()); - hasher.update(y.get_hash().as_bytes()); - Level(Arc::new(LevelData::Imax(x, y, hasher.finalize()))) - } - /// Constructs a universe parameter with the given name. - pub fn param(x: Name) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[UPARAM]); - hasher.update(x.get_hash().as_bytes()); - Level(Arc::new(LevelData::Param(x, hasher.finalize()))) - } - /// Constructs a universe-level metavariable with the given name. - pub fn mvar(x: Name) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[UMVAR]); - hasher.update(x.get_hash().as_bytes()); - Level(Arc::new(LevelData::Mvar(x, hasher.finalize()))) - } -} - -impl Level { - /// Human-readable representation of a universe level. - /// - /// Collapses chains of `Succ` into numeric literals and uses Lean-style - /// syntax: `0`, `1`, `u`, `max u v`, `imax u v`, `?m`. - pub fn pretty(&self) -> String { - // Peel Succ chains into a base + offset. - let (base, offset) = { - let mut cur = self; - let mut n: u64 = 0; - loop { - match cur.as_data() { - LevelData::Succ(inner, _) => { - n += 1; - cur = inner; - }, - _ => break (cur, n), - } - } - }; - - match base.as_data() { - LevelData::Zero(_) => format!("{offset}"), - LevelData::Param(name, _) if offset == 0 => name.pretty(), - LevelData::Param(name, _) => { - let n = name.pretty(); - // u+1 → just show the additions - (0..offset).fold(n, |acc, _| format!("{acc}+1")) - }, - LevelData::Mvar(name, _) if offset == 0 => format!("?{}", name.pretty()), - LevelData::Mvar(name, _) => { - let n = format!("?{}", name.pretty()); - (0..offset).fold(n, |acc, _| format!("{acc}+1")) - }, - LevelData::Max(a, b, _) if offset == 0 => { - format!("max {} {}", a.pretty_atom(), b.pretty_atom()) - }, - LevelData::Imax(a, b, _) if offset == 0 => { - format!("imax {} {}", a.pretty_atom(), b.pretty_atom()) - }, - // Succ(Max/Imax): wrap in parens - LevelData::Max(..) | LevelData::Imax(..) => { - let inner = base.pretty(); - (0..offset).fold(inner, |acc, _| format!("({acc})+1")) - }, - // Succ was already peeled; this arm is unreachable. - LevelData::Succ(..) => unreachable!(), - } - } - - /// Pretty-print as an atom: parenthesise compound levels (max, imax) - /// so they can appear as arguments without ambiguity. - fn pretty_atom(&self) -> String { - match self.as_data() { - LevelData::Max(..) | LevelData::Imax(..) => { - format!("({})", self.pretty()) - }, - _ => self.pretty(), - } - } -} - -impl std::fmt::Display for Level { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.pretty()) - } -} - -impl StdHash for Level { - fn hash(&self, state: &mut H) { - self.get_hash().as_bytes().hash(state); - } -} - -/// A literal value embedded in an expression. -#[derive(Debug, PartialEq, Eq)] -pub enum Literal { - /// A natural number literal. - NatVal(Nat), - /// A string literal. - StrVal(String), -} - -impl PartialOrd for Literal { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -// should match Literal.lt here https://github.com/leanprover/lean4/blob/fe21b950586cde248dae4b2a0f59d43c1f19cd87/src/Lean/Expr.lean#L34 -impl Ord for Literal { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - match (self, other) { - (Literal::NatVal(a), Literal::NatVal(b)) => a.cmp(b), - (Literal::StrVal(a), Literal::StrVal(b)) => a.cmp(b), - (Literal::NatVal(_), Literal::StrVal(_)) => std::cmp::Ordering::Less, - (Literal::StrVal(_), Literal::NatVal(_)) => std::cmp::Ordering::Greater, - } - } -} - -/// Binder annotation kind, mirroring Lean 4's `BinderInfo`. -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub enum BinderInfo { - /// Explicit binder `(x : A)`. - Default, - /// Implicit binder `{x : A}`. - Implicit, - /// Strict implicit binder `{{x : A}}`. - StrictImplicit, - /// Instance implicit binder `[x : A]`. - InstImplicit, -} - -fn binder_info_tag(bi: &BinderInfo) -> u8 { - match bi { - BinderInfo::Default => 0, - BinderInfo::Implicit => 1, - BinderInfo::StrictImplicit => 2, - BinderInfo::InstImplicit => 3, - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub enum Int { - OfNat(Nat), - NegSucc(Nat), -} - -fn hash_int(i: &Int, hasher: &mut blake3::Hasher) { - hasher.update(&[MINT]); - match i { - Int::OfNat(n) => { - hasher.update(&[0]); - hasher.update(&n.to_le_bytes()); - }, - Int::NegSucc(n) => { - hasher.update(&[1]); - hasher.update(&n.to_le_bytes()); - }, - } -} - -/// A substring reference: a string together with start and stop byte positions. -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub struct Substring { - /// The underlying string. - pub str: String, - /// The start byte position (inclusive). - pub start_pos: Nat, - /// The stop byte position (exclusive). - pub stop_pos: Nat, -} - -fn hash_substring(ss: &Substring, hasher: &mut blake3::Hasher) { - hasher.update(&[MSSTR]); - hasher.update(ss.str.as_bytes()); - hasher.update(&ss.start_pos.to_le_bytes()); - hasher.update(&ss.stop_pos.to_le_bytes()); -} - -/// Source location metadata attached to syntax nodes. -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub enum SourceInfo { - /// Original source with leading whitespace, leading position, trailing whitespace, trailing position. - Original(Substring, Nat, Substring, Nat), - /// Synthetic source span with start position, stop position, and canonical flag. - Synthetic(Nat, Nat, bool), - /// No source information available. - None, -} - -fn hash_source_info(si: &SourceInfo, hasher: &mut blake3::Hasher) { - hasher.update(&[MSINFO]); - match si { - SourceInfo::Original(leading, leading_pos, trailing, trailing_pos) => { - hasher.update(&[0]); - hash_substring(leading, hasher); - hasher.update(&leading_pos.to_le_bytes()); - hash_substring(trailing, hasher); - hasher.update(&trailing_pos.to_le_bytes()); - }, - SourceInfo::Synthetic(start, stop, canonical) => { - hasher.update(&[1]); - hasher.update(&start.to_le_bytes()); - hasher.update(&stop.to_le_bytes()); - hasher.update(&[u8::from(*canonical)]); - }, - SourceInfo::None => { - hasher.update(&[2]); - }, - } -} - -/// Pre-resolved reference attached to a syntax identifier. -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub enum SyntaxPreresolved { - /// A pre-resolved namespace reference. - Namespace(Name), - /// A pre-resolved declaration reference with alias strings. - Decl(Name, Vec), -} - -fn hash_syntax_preresolved( - sp: &SyntaxPreresolved, - hasher: &mut blake3::Hasher, -) { - hasher.update(&[MSPRE]); - match sp { - SyntaxPreresolved::Namespace(name) => { - hasher.update(&[0]); - hasher.update(name.get_hash().as_bytes()); - }, - SyntaxPreresolved::Decl(name, aliases) => { - hasher.update(&[1]); - hasher.update(name.get_hash().as_bytes()); - for alias in aliases { - hasher.update(alias.as_bytes()); - hasher.update(&[0]); // null terminator for each string - } - }, - } -} - -/// A Lean 4 concrete syntax tree node. -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub enum Syntax { - /// Placeholder for missing syntax. - Missing, - /// An interior syntax node with a kind name and child nodes. - Node(SourceInfo, Name, Vec), - /// An atomic token (keyword, symbol, etc.). - Atom(SourceInfo, String), - /// An identifier with optional pre-resolved references. - Ident(SourceInfo, Substring, Name, Vec), -} - -fn hash_syntax(syn: &Syntax, hasher: &mut blake3::Hasher) { - hasher.update(&[MSYN]); - match syn { - Syntax::Missing => { - hasher.update(&[0]); - }, - Syntax::Node(info, kind, args) => { - hasher.update(&[1]); - hash_source_info(info, hasher); - hasher.update(kind.get_hash().as_bytes()); - hasher.update(&Nat::from(args.len() as u64).to_le_bytes()); - for arg in args { - hash_syntax(arg, hasher); - } - }, - Syntax::Atom(info, val) => { - hasher.update(&[2]); - hash_source_info(info, hasher); - hasher.update(val.as_bytes()); - }, - Syntax::Ident(info, raw_val, val, preresolved) => { - hasher.update(&[3]); - hash_source_info(info, hasher); - hash_substring(raw_val, hasher); - hasher.update(val.get_hash().as_bytes()); - hasher.update(&Nat::from(preresolved.len() as u64).to_le_bytes()); - for pr in preresolved { - hash_syntax_preresolved(pr, hasher); - } - }, - } -} - -/// A dynamically-typed value stored in expression metadata (`KVMap` entries). -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub enum DataValue { - /// A string value. - OfString(String), - /// A boolean value. - OfBool(bool), - /// A name value. - OfName(Name), - /// A natural number value. - OfNat(Nat), - /// An integer value. - OfInt(Int), - /// A syntax tree value. - OfSyntax(Box), -} - -pub fn hash_data_value(dv: &DataValue, hasher: &mut blake3::Hasher) { - hasher.update(&[MDVAL]); - match dv { - DataValue::OfString(s) => { - hasher.update(&[0]); - hasher.update(s.as_bytes()); - }, - DataValue::OfBool(b) => { - hasher.update(&[1]); - hasher.update(&[u8::from(*b)]); - }, - DataValue::OfName(name) => { - hasher.update(&[2]); - hasher.update(name.get_hash().as_bytes()); - }, - DataValue::OfNat(n) => { - hasher.update(&[3]); - hasher.update(&n.to_le_bytes()); - }, - DataValue::OfInt(i) => { - hasher.update(&[4]); - hash_int(i, hasher); - }, - DataValue::OfSyntax(syn) => { - hasher.update(&[5]); - hash_syntax(syn, hasher); - }, - } -} - -/// A content-addressed Lean 4 kernel expression. -/// -/// Expressions are interned via `Arc` and compared/hashed by their Blake3 digest. -#[derive(PartialEq, Eq, Debug, Clone)] -pub struct Expr(pub Arc); - -/// The underlying data for an [`Expr`]. -/// -/// Each variant carries its precomputed Blake3 hash as the last field. -#[derive(Debug, PartialEq, Eq)] -pub enum ExprData { - /// Bound variable (de Bruijn index). - Bvar(Nat, Hash), - /// Free variable. - Fvar(Name, Hash), - /// Metavariable. - Mvar(Name, Hash), - /// Sort (universe). - Sort(Level, Hash), - /// Reference to a named constant with universe level arguments. - Const(Name, Vec, Hash), - /// Function application. - App(Expr, Expr, Hash), - /// Lambda abstraction. - Lam(Name, Expr, Expr, BinderInfo, Hash), - /// Dependent function type (Pi / forall). - ForallE(Name, Expr, Expr, BinderInfo, Hash), - /// Let-binding (name, type, value, body, non-dep flag). - LetE(Name, Expr, Expr, Expr, bool, Hash), - /// Literal value (nat or string). - Lit(Literal, Hash), - /// Metadata-annotated expression with key-value pairs. - Mdata(Vec<(Name, DataValue)>, Expr, Hash), - /// Projection from a structure (type name, field index, struct expr). - Proj(Name, Nat, Expr, Hash), -} - -impl Expr { - /// Raw equality: the derived `==` (structure, binder information, - /// literals, metadata and every stored digest of the expression, level - /// and name nodes), iteratively, pointer-first, and linear in the DAG, - /// where the derived recursion is exponential on distinct allocations of a - /// shared DAG. The only expression comparison of the exact cache keys - /// (`expr_key::ExprKey`, `PreseedKey`) and the inductive-flag transform - /// memo confirmations. - pub fn same_raw(&self, other: &Self) -> bool { - if Arc::ptr_eq(&self.0, &other.0) { - return true; - } - if self.get_hash() != other.get_hash() { - return false; - } - // Both borrowed roots retain all nodes until the comparison ends. The - // set contains only pairs whose children have already compared equal. - let mut completed = FxHashSet::default(); - let mut pending = vec![(self, other, false)]; - while let Some((a, b, finish)) = pending.pop() { - let pair = (Arc::as_ptr(&a.0), Arc::as_ptr(&b.0)); - if finish { - completed.insert(pair); - continue; - } - if pair.0 == pair.1 || completed.contains(&pair) { - continue; - } - if a.get_hash() != b.get_hash() { - return false; - } - pending.push((a, b, true)); - match (a.as_data(), b.as_data()) { - (ExprData::Bvar(x, _), ExprData::Bvar(y, _)) if x == y => {}, - (ExprData::Fvar(x, _), ExprData::Fvar(y, _)) - | (ExprData::Mvar(x, _), ExprData::Mvar(y, _)) - if x.same_raw(y) => {}, - (ExprData::Sort(x, _), ExprData::Sort(y, _)) if x.same_raw(y) => {}, - (ExprData::Const(x, us, _), ExprData::Const(y, vs, _)) - if x.same_raw(y) - && us.len() == vs.len() - && us.iter().zip(vs).all(|(u, v)| u.same_raw(v)) => {}, - (ExprData::App(f, x, _), ExprData::App(g, y, _)) => { - pending.extend([(x, y, false), (f, g, false)]); - }, - ( - ExprData::Lam(n, t, body, info, _), - ExprData::Lam(m, u, other, other_info, _), - ) - | ( - ExprData::ForallE(n, t, body, info, _), - ExprData::ForallE(m, u, other, other_info, _), - ) if n.same_raw(m) && info == other_info => { - pending.extend([(body, other, false), (t, u, false)]); - }, - ( - ExprData::LetE(n, t, v, body, nd, _), - ExprData::LetE(m, u, w, other, other_nd, _), - ) if n.same_raw(m) && nd == other_nd => { - pending.extend([(body, other, false), (v, w, false), (t, u, false)]); - }, - (ExprData::Lit(x, _), ExprData::Lit(y, _)) if x == y => {}, - (ExprData::Mdata(md, x, _), ExprData::Mdata(other_md, y, _)) - if md == other_md => - { - // Syntax metadata is owned Vec/Box data, so its derived equality - // walks its stored tree and includes names and hashes. - pending.push((x, y, false)); - }, - (ExprData::Proj(n, i, x, _), ExprData::Proj(m, j, y, _)) - if n.same_raw(m) && i == j => - { - pending.push((x, y, false)); - }, - _ => return false, - } - } - true - } - - /// Returns a reference to the inner [`ExprData`]. - pub fn as_data(&self) -> &ExprData { - &self.0 - } - - /// Returns the precomputed Blake3 hash of this expression. - pub fn get_hash(&self) -> &Hash { - match &*self.0 { - ExprData::Bvar(_, h) - | ExprData::Fvar(_, h) - | ExprData::Mvar(_, h) - | ExprData::Sort(_, h) - | ExprData::Const(.., h) - | ExprData::App(.., h) - | ExprData::Lam(.., h) - | ExprData::ForallE(.., h) - | ExprData::LetE(.., h) - | ExprData::Lit(_, h) - | ExprData::Mdata(.., h) - | ExprData::Proj(.., h) => h, - } - } - /// Constructs a bound variable expression from a de Bruijn index. - pub fn bvar(x: Nat) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[EVAR]); - hasher.update(&x.to_le_bytes()); - Expr(Arc::new(ExprData::Bvar(x, hasher.finalize()))) - } - - /// Constructs a free variable expression. - pub fn fvar(x: Name) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[EFVAR]); - hasher.update(x.get_hash().as_bytes()); - Expr(Arc::new(ExprData::Fvar(x, hasher.finalize()))) - } - - /// Constructs a metavariable expression. - pub fn mvar(x: Name) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[EMVAR]); - hasher.update(x.get_hash().as_bytes()); - Expr(Arc::new(ExprData::Mvar(x, hasher.finalize()))) - } - - /// Constructs a sort expression from a universe level. - pub fn sort(x: Level) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[ESORT]); - hasher.update(x.get_hash().as_bytes()); - Expr(Arc::new(ExprData::Sort(x, hasher.finalize()))) - } - - /// Constructs a constant reference with universe level arguments. - pub fn cnst(x: Name, us: Vec) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[EREF]); - hasher.update(x.get_hash().as_bytes()); - for u in &us { - hasher.update(u.get_hash().as_bytes()); - } - Expr(Arc::new(ExprData::Const(x, us, hasher.finalize()))) - } - - /// Constructs a function application `f a`. - pub fn app(f: Expr, a: Expr) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[EAPP]); - hasher.update(f.get_hash().as_bytes()); - hasher.update(a.get_hash().as_bytes()); - Expr(Arc::new(ExprData::App(f, a, hasher.finalize()))) - } - - /// Constructs a lambda abstraction `fun (n : t) => b`. - pub fn lam(n: Name, t: Expr, b: Expr, bi: BinderInfo) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[ELAM]); - hasher.update(n.get_hash().as_bytes()); - hasher.update(t.get_hash().as_bytes()); - hasher.update(b.get_hash().as_bytes()); - hasher.update(&[binder_info_tag(&bi)]); - Expr(Arc::new(ExprData::Lam(n, t, b, bi, hasher.finalize()))) - } - - /// Constructs a dependent function type (forall / Pi). - pub fn all(n: Name, t: Expr, b: Expr, bi: BinderInfo) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[EALL]); - hasher.update(n.get_hash().as_bytes()); - hasher.update(t.get_hash().as_bytes()); - hasher.update(b.get_hash().as_bytes()); - hasher.update(&[binder_info_tag(&bi)]); - Expr(Arc::new(ExprData::ForallE(n, t, b, bi, hasher.finalize()))) - } - - /// Constructs a let-binding `let n : t := v in b`. - #[allow(non_snake_case)] - pub fn letE(n: Name, t: Expr, v: Expr, b: Expr, nd: bool) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[ELET]); - hasher.update(n.get_hash().as_bytes()); - hasher.update(t.get_hash().as_bytes()); - hasher.update(v.get_hash().as_bytes()); - hasher.update(b.get_hash().as_bytes()); - hasher.update(&[u8::from(nd)]); - Expr(Arc::new(ExprData::LetE(n, t, v, b, nd, hasher.finalize()))) - } - - /// Constructs a literal expression (nat or string). - pub fn lit(x: Literal) -> Self { - let mut hasher = blake3::Hasher::new(); - match &x { - Literal::NatVal(n) => { - hasher.update(&[ENAT]); - hasher.update(&n.to_le_bytes()); - }, - Literal::StrVal(s) => { - hasher.update(&[ESTR]); - hasher.update(s.as_bytes()); - }, - }; - Expr(Arc::new(ExprData::Lit(x, hasher.finalize()))) - } - - /// Constructs a metadata-annotated expression. - pub fn mdata(xs: Vec<(Name, DataValue)>, e: Expr) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[EMDATA]); - hasher.update(&Nat::from(xs.len() as u64).to_le_bytes()); - for (name, dv) in &xs { - hasher.update(name.get_hash().as_bytes()); - hash_data_value(dv, &mut hasher); - } - hasher.update(e.get_hash().as_bytes()); - Expr(Arc::new(ExprData::Mdata(xs, e, hasher.finalize()))) - } - - /// Constructs a projection expression (type name, field index, struct expr). - pub fn proj(n: Name, i: Nat, e: Expr) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[EPRJ]); - hasher.update(n.get_hash().as_bytes()); - hasher.update(&i.to_le_bytes()); - hasher.update(e.get_hash().as_bytes()); - Expr(Arc::new(ExprData::Proj(n, i, e, hasher.finalize()))) - } - - /// Pretty-print an expression for debugging. - pub fn pretty(&self) -> String { - fn short_name(name: &Name) -> String { - let s = name.pretty(); - let parts: Vec<&str> = s.rsplitn(3, '.').collect(); - match parts.as_slice() { - [a, b, _] | [a, b] => format!("{b}.{a}"), - [a] => a.to_string(), - _ => s, - } - } - fn go(e: &Expr, ctx: &mut Vec) -> String { - match e.as_data() { - ExprData::Bvar(idx, _) => { - let i = usize::try_from(idx.to_u64().unwrap_or(0)).unwrap_or(0); - let pos = ctx.len().checked_sub(1 + i); - let name = pos.and_then(|p| ctx.get(p)).cloned().unwrap_or_default(); - if name.is_empty() { format!("V{i}") } else { format!("{name}@{i}") } - }, - ExprData::App(f, a, _) => format!("({} {})", go(f, ctx), go(a, ctx)), - ExprData::Const(n, us, _) => { - if us.is_empty() { - short_name(n) - } else { - let us_s: Vec = us.iter().map(|u| u.pretty()).collect(); - format!("{}.{{{}}}", short_name(n), us_s.join(", ")) - } - }, - ExprData::ForallE(n, d, b, bi, _) => { - let nm = short_name(n); - let d_s = go(d, ctx); - ctx.push(nm.clone()); - let b_s = go(b, ctx); - ctx.pop(); - let (bi_s, bi_e) = match bi { - BinderInfo::Default => ("", ""), - BinderInfo::Implicit => ("{", "}"), - BinderInfo::StrictImplicit => ("⦃", "⦄"), - BinderInfo::InstImplicit => ("[", "]"), - }; - format!("∀{bi_s}{nm}:{d_s}{bi_e}. {b_s}") - }, - ExprData::Lam(n, d, b, bi, _) => { - let nm = short_name(n); - let d_s = go(d, ctx); - ctx.push(nm.clone()); - let b_s = go(b, ctx); - ctx.pop(); - let (bi_s, bi_e) = match bi { - BinderInfo::Default => ("", ""), - BinderInfo::Implicit => ("{", "}"), - BinderInfo::StrictImplicit => ("⦃", "⦄"), - BinderInfo::InstImplicit => ("[", "]"), - }; - format!("λ{bi_s}{nm}:{d_s}{bi_e}. {b_s}") - }, - ExprData::Sort(_, _) => "Sort".to_string(), - ExprData::LetE(n, _, v, b, _, _) => { - let nm = short_name(n); - let v_s = go(v, ctx); - ctx.push(nm.clone()); - let b_s = go(b, ctx); - ctx.pop(); - format!("let {nm} := {v_s} in {b_s}") - }, - ExprData::Mdata(_, e, _) => go(e, ctx), - ExprData::Proj(n, i, e, _) => { - format!("{}.{}{}", go(e, ctx), short_name(n), i.to_u64().unwrap_or(0)) - }, - ExprData::Lit(_, _) => "lit".to_string(), - ExprData::Fvar(n, _) => format!("fvar({})", short_name(n)), - ExprData::Mvar(n, _) => format!("?{}", short_name(n)), - } - } - let mut ctx = Vec::new(); - go(self, &mut ctx) - } -} - -impl StdHash for Expr { - fn hash(&self, state: &mut H) { - self.get_hash().as_bytes().hash(state); - } -} - -impl StdHash for ExprData { - fn hash(&self, state: &mut H) { - match self { - ExprData::Bvar(_, h) - | ExprData::Fvar(_, h) - | ExprData::Mvar(_, h) - | ExprData::Sort(_, h) - | ExprData::Const(.., h) - | ExprData::App(.., h) - | ExprData::Lam(.., h) - | ExprData::ForallE(.., h) - | ExprData::LetE(.., h) - | ExprData::Lit(_, h) - | ExprData::Mdata(.., h) - | ExprData::Proj(.., h) => h.as_bytes().hash(state), - } - } -} - -/// Hints that control how aggressively the kernel unfolds a definition. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum ReducibilityHints { - /// Never unfold. - #[default] - Opaque, - /// Always unfold (abbreviation). - Abbrev, - /// Unfold with the given priority height. - Regular(u32), -} - -fn hash_reducibility_hints( - hints: &ReducibilityHints, - hasher: &mut blake3::Hasher, -) { - match hints { - ReducibilityHints::Opaque => { - hasher.update(&[0]); - }, - ReducibilityHints::Abbrev => { - hasher.update(&[1]); - }, - ReducibilityHints::Regular(h) => { - hasher.update(&[2]); - hasher.update(&h.to_le_bytes()); - }, - }; -} - -/// Safety classification of a definition. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum DefinitionSafety { - /// Marked `unsafe`; no termination or totality guarantees. - Unsafe, - /// Fully safe and total. - Safe, - /// Partial definition; may not terminate on all inputs. - Partial, -} - -fn hash_definition_safety( - safety: &DefinitionSafety, - hasher: &mut blake3::Hasher, -) { - match safety { - DefinitionSafety::Unsafe => hasher.update(&[0]), - DefinitionSafety::Safe => hasher.update(&[1]), - DefinitionSafety::Partial => hasher.update(&[2]), - }; -} - -/// Fields common to every constant declaration. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ConstantVal { - /// The fully-qualified name of the constant. - pub name: Name, - /// Universe-polymorphic level parameter names. - pub level_params: Vec, - /// The type of the constant. - pub typ: Expr, -} - -fn hash_constant_val(cv: &ConstantVal, hasher: &mut blake3::Hasher) { - hasher.update(cv.name.get_hash().as_bytes()); - hasher.update(&(cv.level_params.len() as u64).to_le_bytes()); - for lp in &cv.level_params { - hasher.update(lp.get_hash().as_bytes()); - } - hasher.update(cv.typ.get_hash().as_bytes()); -} - -/// An axiom declaration (no definitional body). -#[derive(Debug, Clone)] -pub struct AxiomVal { - /// Common constant fields. - pub cnst: ConstantVal, - /// Whether this axiom is marked `unsafe`. - pub is_unsafe: bool, -} - -/// A definition with a computable body. -#[derive(Debug, Clone)] -pub struct DefinitionVal { - /// Common constant fields. - pub cnst: ConstantVal, - /// The definition body. - pub value: Expr, - /// Reducibility hints for the kernel. - pub hints: ReducibilityHints, - /// Safety classification. - pub safety: DefinitionSafety, - /// Names of all constants in the same mutual block. - pub all: Vec, -} - -/// A theorem declaration (proof-irrelevant; body is never reduced). -#[derive(Debug, Clone)] -pub struct TheoremVal { - /// Common constant fields. - pub cnst: ConstantVal, - /// The proof term. - pub value: Expr, - /// Names of all constants in the same mutual block. - pub all: Vec, -} - -/// An opaque constant (body exists but is not unfolded by the kernel). -#[derive(Debug, Clone)] -pub struct OpaqueVal { - /// Common constant fields. - pub cnst: ConstantVal, - /// The opaque body. - pub value: Expr, - /// Whether this opaque constant is marked `unsafe`. - pub is_unsafe: bool, - /// Names of all constants in the same mutual block. - pub all: Vec, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum QuotKind { - Type, - Ctor, - Lift, - Ind, -} - -fn hash_quot_kind(kind: &QuotKind, hasher: &mut blake3::Hasher) { - match kind { - QuotKind::Type => hasher.update(&[0]), - QuotKind::Ctor => hasher.update(&[1]), - QuotKind::Lift => hasher.update(&[2]), - QuotKind::Ind => hasher.update(&[3]), - }; -} - -/// A quotient-type related constant. -#[derive(Debug, Clone)] -pub struct QuotVal { - /// Common constant fields. - pub cnst: ConstantVal, - /// Which quotient primitive this constant represents. - pub kind: QuotKind, -} - -/// An inductive type declaration. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct InductiveVal { - /// Common constant fields. - pub cnst: ConstantVal, - /// Number of parameters. - pub num_params: Nat, - /// Number of indices. - pub num_indices: Nat, - /// Names of all types in the same mutual inductive block. - pub all: Vec, - /// Names of the constructors for this type. - pub ctors: Vec, - /// Number of nested (non-mutual) inductives. - pub num_nested: Nat, - /// Whether this inductive type is recursive. - pub is_rec: bool, - /// Whether this inductive type is marked `unsafe`. - pub is_unsafe: bool, - /// Whether this inductive type is reflexive. - pub is_reflexive: bool, -} - -/// A constructor of an inductive type. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ConstructorVal { - /// Common constant fields. - pub cnst: ConstantVal, - /// Name of the parent inductive type. - pub induct: Name, - /// Constructor index within the inductive type. - pub cidx: Nat, - /// Number of parameters inherited from the inductive type. - pub num_params: Nat, - /// Number of fields (non-parameter arguments). - pub num_fields: Nat, - /// Whether this constructor is marked `unsafe`. - pub is_unsafe: bool, -} - -/// A single reduction rule for a recursor, mapping a constructor to its branch. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RecursorRule { - /// The constructor this rule applies to. - pub ctor: Name, - /// Number of fields the constructor has. - pub n_fields: Nat, - /// The right-hand side expression for this branch. - pub rhs: Expr, -} - -fn hash_recursor_rule(rule: &RecursorRule, hasher: &mut blake3::Hasher) { - hasher.update(rule.ctor.get_hash().as_bytes()); - hasher.update(&rule.n_fields.to_le_bytes()); - hasher.update(rule.rhs.get_hash().as_bytes()); -} - -/// A recursor (eliminator) for an inductive type. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RecursorVal { - /// Common constant fields. - pub cnst: ConstantVal, - /// Names of all types in the same mutual inductive block. - pub all: Vec, - /// Number of parameters. - pub num_params: Nat, - /// Number of indices. - pub num_indices: Nat, - /// Number of motive arguments. - pub num_motives: Nat, - /// Number of minor premise arguments. - pub num_minors: Nat, - /// Reduction rules, one per constructor. - pub rules: Vec, - /// Whether this is a K-like recursor (proof-irrelevant elimination). - pub k: bool, - /// Whether this recursor is marked `unsafe`. - pub is_unsafe: bool, -} - -/// A top-level constant declaration in the Lean environment. -#[derive(Debug, Clone)] -pub enum ConstantInfo { - /// An axiom. - AxiomInfo(AxiomVal), - /// A definition with a computable body. - DefnInfo(DefinitionVal), - /// A theorem (proof-irrelevant). - ThmInfo(TheoremVal), - /// An opaque constant. - OpaqueInfo(OpaqueVal), - /// A quotient primitive. - QuotInfo(QuotVal), - /// An inductive type. - InductInfo(InductiveVal), - /// A constructor of an inductive type. - CtorInfo(ConstructorVal), - /// A recursor (eliminator). - RecInfo(RecursorVal), -} - -impl ConstantInfo { - /// Computes the Blake3 content hash of this constant declaration. - pub fn get_hash(&self) -> Hash { - let mut hasher = blake3::Hasher::new(); - match self { - ConstantInfo::AxiomInfo(v) => { - hasher.update(&[AXIO]); - hash_constant_val(&v.cnst, &mut hasher); - hasher.update(&[u8::from(v.is_unsafe)]); - }, - ConstantInfo::DefnInfo(v) => { - hasher.update(&[DEFN]); - hash_constant_val(&v.cnst, &mut hasher); - hasher.update(v.value.get_hash().as_bytes()); - hash_reducibility_hints(&v.hints, &mut hasher); - hash_definition_safety(&v.safety, &mut hasher); - hasher.update(&(v.all.len() as u64).to_le_bytes()); - for name in &v.all { - hasher.update(name.get_hash().as_bytes()); - } - }, - ConstantInfo::ThmInfo(v) => { - hasher.update(&[THEO]); - hash_constant_val(&v.cnst, &mut hasher); - hasher.update(v.value.get_hash().as_bytes()); - hasher.update(&(v.all.len() as u64).to_le_bytes()); - for name in &v.all { - hasher.update(name.get_hash().as_bytes()); - } - }, - ConstantInfo::OpaqueInfo(v) => { - hasher.update(&[OPAQ]); - hash_constant_val(&v.cnst, &mut hasher); - hasher.update(v.value.get_hash().as_bytes()); - hasher.update(&[u8::from(v.is_unsafe)]); - hasher.update(&(v.all.len() as u64).to_le_bytes()); - for name in &v.all { - hasher.update(name.get_hash().as_bytes()); - } - }, - ConstantInfo::QuotInfo(v) => { - hasher.update(&[QUOT]); - hash_constant_val(&v.cnst, &mut hasher); - hash_quot_kind(&v.kind, &mut hasher); - }, - ConstantInfo::InductInfo(v) => { - hasher.update(&[INDC]); - hash_constant_val(&v.cnst, &mut hasher); - hasher.update(&v.num_params.to_le_bytes()); - hasher.update(&v.num_indices.to_le_bytes()); - hasher.update(&(v.all.len() as u64).to_le_bytes()); - for name in &v.all { - hasher.update(name.get_hash().as_bytes()); - } - hasher.update(&(v.ctors.len() as u64).to_le_bytes()); - for name in &v.ctors { - hasher.update(name.get_hash().as_bytes()); - } - hasher.update(&v.num_nested.to_le_bytes()); - hasher.update(&[u8::from(v.is_rec)]); - hasher.update(&[u8::from(v.is_unsafe)]); - hasher.update(&[u8::from(v.is_reflexive)]); - }, - ConstantInfo::CtorInfo(v) => { - hasher.update(&[CTOR]); - hash_constant_val(&v.cnst, &mut hasher); - hasher.update(v.induct.get_hash().as_bytes()); - hasher.update(&v.cidx.to_le_bytes()); - hasher.update(&v.num_params.to_le_bytes()); - hasher.update(&v.num_fields.to_le_bytes()); - hasher.update(&[u8::from(v.is_unsafe)]); - }, - ConstantInfo::RecInfo(v) => { - hasher.update(&[RECR]); - hash_constant_val(&v.cnst, &mut hasher); - hasher.update(&(v.all.len() as u64).to_le_bytes()); - for name in &v.all { - hasher.update(name.get_hash().as_bytes()); - } - hasher.update(&v.num_params.to_le_bytes()); - hasher.update(&v.num_indices.to_le_bytes()); - hasher.update(&v.num_motives.to_le_bytes()); - hasher.update(&v.num_minors.to_le_bytes()); - hasher.update(&(v.rules.len() as u64).to_le_bytes()); - for rule in &v.rules { - hash_recursor_rule(rule, &mut hasher); - } - hasher.update(&[u8::from(v.k)]); - hasher.update(&[u8::from(v.is_unsafe)]); - }, - } - hasher.finalize() - } - - /// Returns the name of this constant. - pub fn get_name(&self) -> &Name { - match self { - ConstantInfo::AxiomInfo(v) => &v.cnst.name, - ConstantInfo::DefnInfo(v) => &v.cnst.name, - ConstantInfo::ThmInfo(v) => &v.cnst.name, - ConstantInfo::OpaqueInfo(v) => &v.cnst.name, - ConstantInfo::QuotInfo(v) => &v.cnst.name, - ConstantInfo::InductInfo(v) => &v.cnst.name, - ConstantInfo::CtorInfo(v) => &v.cnst.name, - ConstantInfo::RecInfo(v) => &v.cnst.name, - } - } - - /// Returns the type of this constant. - pub fn get_type(&self) -> &Expr { - match self { - ConstantInfo::AxiomInfo(v) => &v.cnst.typ, - ConstantInfo::DefnInfo(v) => &v.cnst.typ, - ConstantInfo::ThmInfo(v) => &v.cnst.typ, - ConstantInfo::OpaqueInfo(v) => &v.cnst.typ, - ConstantInfo::QuotInfo(v) => &v.cnst.typ, - ConstantInfo::InductInfo(v) => &v.cnst.typ, - ConstantInfo::CtorInfo(v) => &v.cnst.typ, - ConstantInfo::RecInfo(v) => &v.cnst.typ, - } - } - - /// Returns the value of this constant, if it has one (definitions, theorems, opaques). - pub fn get_value(&self) -> Option<&Expr> { - match self { - ConstantInfo::DefnInfo(v) => Some(&v.value), - ConstantInfo::ThmInfo(v) => Some(&v.value), - ConstantInfo::OpaqueInfo(v) => Some(&v.value), - _ => None, - } - } - - /// Returns the universe level parameter names of this constant. - pub fn get_level_params(&self) -> &Vec { - match self { - ConstantInfo::AxiomInfo(v) => &v.cnst.level_params, - ConstantInfo::DefnInfo(v) => &v.cnst.level_params, - ConstantInfo::ThmInfo(v) => &v.cnst.level_params, - ConstantInfo::OpaqueInfo(v) => &v.cnst.level_params, - ConstantInfo::QuotInfo(v) => &v.cnst.level_params, - ConstantInfo::InductInfo(v) => &v.cnst.level_params, - ConstantInfo::CtorInfo(v) => &v.cnst.level_params, - ConstantInfo::RecInfo(v) => &v.cnst.level_params, - } - } - - /// Returns a short kind name for this constant (for diagnostics). - pub fn kind_name(&self) -> &'static str { - match self { - ConstantInfo::AxiomInfo(_) => "axiom", - ConstantInfo::DefnInfo(_) => "def", - ConstantInfo::ThmInfo(_) => "thm", - ConstantInfo::OpaqueInfo(_) => "opaque", - ConstantInfo::QuotInfo(_) => "quot", - ConstantInfo::InductInfo(_) => "induct", - ConstantInfo::CtorInfo(_) => "ctor", - ConstantInfo::RecInfo(_) => "rec", - } - } -} - -/// Guard returned by [`Env::get`]: derefs to [`ConstantInfo`]. -/// -/// Eager environments hand out plain borrows (zero cost); lazy -/// environments hand out `Arc`s so the entry stays alive even if the -/// backing cache evicts it concurrently. -pub enum EnvEntry<'a> { - Borrowed(&'a ConstantInfo), - Owned(Arc), -} - -impl std::ops::Deref for EnvEntry<'_> { - type Target = ConstantInfo; - fn deref(&self) -> &ConstantInfo { - match self { - EnvEntry::Borrowed(c) => c, - EnvEntry::Owned(c) => c, - } - } -} - -impl EnvEntry<'_> { - /// Owned copy of the constant. Cheap only when a caller genuinely - /// needs ownership — prefer deref for reads. - pub fn cloned(&self) -> ConstantInfo { - (**self).clone() - } -} - -/// Host-only lazy backing for [`Env`]: a name index plus an injected -/// fetch that decodes one constant on demand (in practice from Lean -/// objects held as `LeanShared` handles — see `decode_env_lazy` in the -/// ffi crate), fronted by a lock-partitioned bounded cache. Avoids materializing -/// the full owned copy of the environment up front, which costs a large -/// multiple of the working set the compile pipeline actually touches at -/// any one time. -#[cfg(not(target_arch = "riscv64"))] -pub struct LazyEnv { - /// All names, in the source env's iteration order. - names: Vec, - /// Membership index into `names`. - index: FxHashMap, - /// Decode one constant. Must be pure and thread-safe. - fetch: Box Option + Send + Sync>, - /// Independently locked cache segments so concurrent - /// `get`s rarely contend; per-segment capacity bounds resident - /// decoded constants. Eviction is `swap_remove_index(0)` — cheap - /// oldest-biased pseudo-FIFO, adequate for the scheduler's - /// topological locality. - segments: Vec>>>, - cap_per_segment: usize, - /// Never-evicted overlay for hot names, installed once via - /// [`Env::pin`] before concurrent readers start. Each slot decodes - /// on first access and stays resident; reads after that are - /// lock-free. Names outside the overlay fall through to the - /// segments. - pinned: std::sync::OnceLock< - FxHashMap>>>, - >, -} - -#[cfg(not(target_arch = "riscv64"))] -impl LazyEnv { - /// Segment count: 4× the hardware threads (the scheduler runs at - /// most one worker per thread), rounded up to a power of two so the - /// hash mask in `segment_for` stays uniform. The 4× headroom keeps - /// expected pairwise collisions low when every worker is in `get` - /// at once; a mutexed segment costs ~40 bytes, so overprovisioning - /// is free. - fn segment_count() -> usize { - let threads = std::thread::available_parallelism().map_or(1, usize::from); - // Capped at the two-byte selector's range (`segment_for`). - (threads * 4).next_power_of_two().min(1 << 16) - } - - /// Two low hash bytes masked down to the segment count: uniform by - /// hash uniformity because the count is a power of two. - fn segment_for(&self, name: &Name) -> usize { - let bytes = name.get_hash().as_bytes(); - let window = usize::from(u16::from_le_bytes([bytes[0], bytes[1]])); - window & (self.segments.len() - 1) - } - - fn get(&self, name: &Name) -> Option> { - if !self.index.contains_key(name) { - return None; - } - if let Some(pinned) = self.pinned.get() - && let Some(slot) = pinned.get(name) - { - return slot.get_or_init(|| (self.fetch)(name).map(Arc::new)).clone(); - } - let segment_idx = self.segment_for(name); - if let Some(hit) = self.segments[segment_idx].lock().unwrap().get(name) { - return Some(hit.clone()); - } - // Decode outside the segment lock: fetches can be slow and other - // names hashing to this segment shouldn't wait on them. - let decoded = Arc::new((self.fetch)(name)?); - let mut segment = self.segments[segment_idx].lock().unwrap(); - if segment.len() >= self.cap_per_segment { - segment.swap_remove_index(0); - } - segment.insert(name.clone(), decoded.clone()); - Some(decoded) - } -} - -/// The Lean kernel environment: a map from names to their constant -/// declarations. Eager (an owned map — the default, and the only -/// variant on the guest) or, on the host, a lazy on-demand view (see -/// [`LazyEnv`]). -/// -/// Keeps the plain-map API shape, with [`Env::get`] returning the -/// [`EnvEntry`] guard instead of a plain borrow, so both variants -/// serve reads through one signature. -#[derive(Default)] -pub struct Env { - eager: FxHashMap, - #[cfg(not(target_arch = "riscv64"))] - lazy: Option, -} - -impl std::fmt::Debug for Env { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Env").field("len", &self.len()).finish() - } -} - -impl Clone for Env { - /// Clones the eager map. Lazy environments are read-only views and - /// are never cloned (the compile pipeline shares them via `Arc`). - fn clone(&self) -> Self { - #[cfg(not(target_arch = "riscv64"))] - assert!( - self.lazy.is_none(), - "Env::clone on a lazy environment (share via Arc instead)" - ); - Env { - eager: self.eager.clone(), - #[cfg(not(target_arch = "riscv64"))] - lazy: None, - } - } -} - -impl Env { - /// Build a lazy env from a name list and a fetch function. - /// `cache_entries` bounds resident decoded constants (total across - /// segments; minimum one per segment). - #[cfg(not(target_arch = "riscv64"))] - pub fn new_lazy( - names: Vec, - fetch: Box Option + Send + Sync>, - cache_entries: usize, - ) -> Self { - let index: FxHashMap = - names.iter().enumerate().map(|(i, n)| (n.clone(), i)).collect(); - let segment_count = LazyEnv::segment_count(); - let cap_per_segment = (cache_entries / segment_count).max(1); - let segments = (0..segment_count) - .map(|_| { - std::sync::Mutex::new(indexmap::IndexMap::with_capacity( - cap_per_segment.min(4096), - )) - }) - .collect(); - Env { - eager: FxHashMap::default(), - lazy: Some(LazyEnv { - names, - index, - fetch, - segments, - cap_per_segment, - pinned: std::sync::OnceLock::new(), - }), - } - } - - /// Pin `names` in the lazy cache: they decode on first access and - /// are never evicted. Install once, before concurrent readers start - /// (later calls are ignored); no-op on eager envs. - #[cfg(not(target_arch = "riscv64"))] - pub fn pin(&self, names: impl IntoIterator) { - if let Some(lazy) = &self.lazy { - let overlay: FxHashMap<_, _> = - names.into_iter().map(|n| (n, std::sync::OnceLock::new())).collect(); - let _ = lazy.pinned.set(overlay); - } - } - - pub fn get(&self, name: &Name) -> Option> { - #[cfg(not(target_arch = "riscv64"))] - if let Some(lazy) = &self.lazy { - return lazy.get(name).map(EnvEntry::Owned); - } - self.eager.get(name).map(EnvEntry::Borrowed) - } - - pub fn contains_key(&self, name: &Name) -> bool { - #[cfg(not(target_arch = "riscv64"))] - if let Some(lazy) = &self.lazy { - return lazy.index.contains_key(name); - } - self.eager.contains_key(name) - } - - pub fn len(&self) -> usize { - #[cfg(not(target_arch = "riscv64"))] - if let Some(lazy) = &self.lazy { - return lazy.names.len(); - } - self.eager.len() - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Insert a constant. Eager only — lazy environments are read-only - /// views. - pub fn insert( - &mut self, - name: Name, - info: ConstantInfo, - ) -> Option { - #[cfg(not(target_arch = "riscv64"))] - assert!(self.lazy.is_none(), "Env::insert on a lazy environment"); - self.eager.insert(name, info) - } - - pub fn get_mut(&mut self, name: &Name) -> Option<&mut ConstantInfo> { - #[cfg(not(target_arch = "riscv64"))] - assert!(self.lazy.is_none(), "Env::get_mut on a lazy environment"); - self.eager.get_mut(name) - } - - /// All names, in map order (eager) or source order (lazy). No - /// decode. - pub fn keys(&self) -> Box + '_> { - #[cfg(not(target_arch = "riscv64"))] - if let Some(lazy) = &self.lazy { - return Box::new(lazy.names.iter()); - } - Box::new(self.eager.keys()) - } - - /// Iterate `(name, constant)`. Lazy mode decodes each entry fresh, - /// bypassing the cache — whole-env passes are single-visit, and - /// caching them would just churn the segments. - pub fn iter(&self) -> Box)> + '_> { - #[cfg(not(target_arch = "riscv64"))] - if let Some(lazy) = &self.lazy { - return Box::new(lazy.names.iter().filter_map(move |n| { - (lazy.fetch)(n).map(|ci| (n, EnvEntry::Owned(Arc::new(ci)))) - })); - } - Box::new(self.eager.iter().map(|(n, c)| (n, EnvEntry::Borrowed(c)))) - } -} - -impl FromIterator<(Name, ConstantInfo)> for Env { - fn from_iter>(it: T) -> Self { - Env { - eager: it.into_iter().collect(), - #[cfg(not(target_arch = "riscv64"))] - lazy: None, - } - } -} - -#[cfg(any(test, feature = "quickcheck"))] -pub mod arbitrary { - use super::*; - use quickcheck::{Arbitrary, Gen}; - - impl Arbitrary for DefinitionSafety { - fn arbitrary(g: &mut Gen) -> Self { - match u8::arbitrary(g) % 3 { - 0 => DefinitionSafety::Unsafe, - 1 => DefinitionSafety::Safe, - _ => DefinitionSafety::Partial, - } - } - } - - impl Arbitrary for QuotKind { - fn arbitrary(g: &mut Gen) -> Self { - match u8::arbitrary(g) % 4 { - 0 => QuotKind::Type, - 1 => QuotKind::Ctor, - 2 => QuotKind::Lift, - _ => QuotKind::Ind, - } - } - } - - impl Arbitrary for BinderInfo { - fn arbitrary(g: &mut Gen) -> Self { - match u8::arbitrary(g) % 4 { - 0 => Self::Default, - 1 => Self::Implicit, - 2 => Self::StrictImplicit, - _ => Self::InstImplicit, - } - } - } - - impl Arbitrary for ReducibilityHints { - fn arbitrary(g: &mut Gen) -> Self { - match u8::arbitrary(g) % 3 { - 0 => Self::Opaque, - 1 => Self::Abbrev, - _ => Self::Regular(u32::arbitrary(g)), - } - } - } -} - -#[cfg(test)] -mod scalar_structure_tests { - use super::*; - - /// Name tables (`name_to_addr`, `MutCtx`) hash a - /// name by its digest and confirm a hit with the derived `==`, which - /// compares the components: two names that share a digest are two keys. - /// The Lean compiler's tables are keyed the same way - /// (`Ix.Compile.ExactKey.ExactName`, `Ix.nameCompareExact`). - #[test] - fn name_tables_keep_names_with_one_digest_apart() { - let x = Name::str(Name::anon(), "x".into()); - let forged = - Name(Arc::new(NameData::Str(Name::anon(), "y".into(), *x.get_hash()))); - assert_eq!(x.get_hash(), forged.get_hash()); - assert_ne!(x, forged); - let mut table: FxHashMap = FxHashMap::default(); - table.insert(x.clone(), 1); - table.insert(forged.clone(), 2); - assert_eq!(table.len(), 2); - assert_eq!(table.get(&x), Some(&1)); - assert_eq!(table.get(&forged), Some(&2)); - let absent = - Name(Arc::new(NameData::Str(Name::anon(), "z".into(), *x.get_hash()))); - assert!(!table.contains_key(&absent)); - // Valid neighbour: an equal name built separately is found. - assert_eq!(table.get(&Name::str(Name::anon(), "x".into())), Some(&1)); - } - - #[test] - fn cache_fields_do_not_decide_smart_equalities() { - let u = Name::str(Name::anon(), "u".into()); - let v = Name::str(Name::anon(), "v".into()); - let a = Level::param(u.clone()); - let digest = blake3::hash(b"deliberately unrelated cache"); - let rehashed_name = Name(Arc::new(NameData::Str( - Name(Arc::new(NameData::Anonymous(digest))), - "u".into(), - digest, - ))); - let b = Level(Arc::new(LevelData::Param(rehashed_name, digest))); - let collision = Level(Arc::new(LevelData::Param(v, *a.get_hash()))); - assert_ne!(a, b); // Existing derived equality includes the cached fields. - assert!(a.same_structure(&b)); - assert!(!a.same_structure(&collision)); - assert_eq!(Level::max_smart(a.clone(), b.clone()), a); - assert_eq!(Level::imax_smart(a.clone(), b), a); - assert!(matches!( - Level::max_smart(a.clone(), collision).as_data(), - LevelData::Max(..) - )); - let valid_neighbour = Level::param(u); - assert_eq!(Level::max_smart(a.clone(), valid_neighbour), a); - } - - #[test] - fn separate_shared_level_dags_keep_structural_leaf_differences() { - fn dag(depth: usize, name: &str, cache: Hash) -> Level { - let mut value = Level(Arc::new(LevelData::Param( - Name::str(Name::anon(), name.into()), - cache, - ))); - for _ in 0..depth { - value = Level(Arc::new(LevelData::Max(value.clone(), value, cache))); - } - value - } - let a = dag(18, "u", blake3::hash(b"left")); - let b = dag(18, "u", blake3::hash(b"right")); - let different = dag(18, "v", blake3::hash(b"left")); - assert!(a.same_structure(&b)); - assert!(!a.same_structure(&different)); - // Raw comparison of separately allocated shared DAGs (2^18 paths) is - // linear: equal digests and structure agree, differing caches do not. - let a2 = dag(18, "u", blake3::hash(b"left")); - assert!(a.same_raw(&a2)); - assert!(!a.same_raw(&b)); - assert!(!a.same_raw(&different)); - } - - /// The raw comparisons are the derived `==`: structure plus every stored - /// digest, for names, levels and expressions (R03-21). - #[test] - fn raw_comparisons_are_derived_equality() { - let u = Name::str(Name::anon(), "u".into()); - let digest = blake3::hash(b"deliberately unrelated cache"); - let rehashed = Name(Arc::new(NameData::Str( - Name(Arc::new(NameData::Anonymous(digest))), - "u".into(), - digest, - ))); - let names = - [u.clone(), rehashed.clone(), Name::str(Name::anon(), "v".into())]; - for a in &names { - for b in &names { - assert_eq!(a.same_raw(b), a == b); - } - } - let levels = [ - Level::param(u.clone()), - Level(Arc::new(LevelData::Param(rehashed.clone(), digest))), - Level::max(Level::param(u.clone()), Level::zero()), - Level(Arc::new(LevelData::Max( - Level::param(u.clone()), - Level::zero(), - digest, - ))), - Level::succ(Level::zero()), - ]; - for a in &levels { - for b in &levels { - assert_eq!(a.same_raw(b), a == b); - } - } - let exprs = [ - Expr::sort(Level::param(u.clone())), - Expr::sort(Level(Arc::new(LevelData::Param(rehashed, digest)))), - Expr::app(Expr::sort(Level::zero()), Expr::sort(Level::param(u))), - ]; - for a in &exprs { - for b in &exprs { - assert_eq!(a.same_raw(b), a == b); - } - } - } -} diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index ccb07956f..eeb55cfcb 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,22 +1,306 @@ -//! Foundational types shared across the Ix project. +//! Shared foundation types for the Ix toolkit. //! -//! Contains: -//! - `address`: 32-byte content-address newtype. -//! - `env`: the Lean kernel type representation (`Name`, `Level`, `Expr`, -//! `ConstantInfo`, `Env`, …) and the tag-byte constants used throughout. -//! - `name_table`: generated-name tables with confirmed structural lookup. -//! - `prim_addrs`: hard-coded primitive content addresses. -//! -//! Plus the `FxIndexMap` / `FxIndexSet` aliases that the rest of the project -//! uses. - -use indexmap::{IndexMap, IndexSet}; -use rustc_hash::FxBuildHasher; +//! Provides content-addressed identifiers ([`Address`](address::Address)), +//! interned hierarchical names and scalar binder/constant metadata ([`env`]), +//! and primitive constant addresses ([`prim_addrs::PrimAddrs`]). pub mod address; -pub mod env; -pub mod name_table; pub mod prim_addrs; -pub type FxIndexMap = IndexMap; -pub type FxIndexSet = IndexSet; +/// Hierarchical names and scalar metadata shared across crates. +pub mod env { + use std::hash::{Hash as StdHash, Hasher}; + use std::sync::Arc; + + use bignat::Nat; + use blake3::Hash; + + /// Tag byte for [`NameData::Anonymous`]. + pub const NANON: u8 = 0x00; + /// Tag byte for [`NameData::Str`]. + pub const NSTR: u8 = 0x01; + /// Tag byte for [`NameData::Num`]. + pub const NNUM: u8 = 0x02; + + #[derive(PartialEq, Eq, Debug, Clone)] + pub struct Name(pub Arc); + + impl PartialOrd for Name { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl Ord for Name { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.get_hash().as_bytes().cmp(other.get_hash().as_bytes()) + } + } + + /// A single component of a hierarchical name. + #[derive(Debug, Clone, PartialEq, Eq)] + pub enum NameComponent { + Str(String), + Num(Nat), + } + + /// The underlying data for a [`Name`]. + /// + /// Each variant carries its precomputed Blake3 hash as the last field. + #[derive(PartialEq, Eq, Debug)] + pub enum NameData { + /// The root (empty) name. + Anonymous(Hash), + /// A string component appended to a prefix name. + Str(Name, String, Hash), + /// A numeric component appended to a prefix name. + Num(Name, Nat, Hash), + } + + impl Name { + /// Returns a reference to the inner [`NameData`]. + pub fn as_data(&self) -> &NameData { + &self.0 + } + + /// Returns the precomputed Blake3 hash of this name. + pub fn get_hash(&self) -> &Hash { + match self.0.as_ref() { + NameData::Anonymous(h) + | NameData::Str(.., h) + | NameData::Num(.., h) => h, + } + } + /// Constructs the anonymous (root) name. + pub fn anon() -> Self { + let hash = blake3::hash(&[NANON]); + Name(Arc::new(NameData::Anonymous(hash))) + } + + /// Constructs a name by appending a string component to `pre`. + pub fn str(pre: Name, s: String) -> Self { + let mut hasher = blake3::Hasher::new(); + hasher.update(&[NSTR]); + hasher.update(pre.get_hash().as_bytes()); + hasher.update(s.as_bytes()); + let hash = hasher.finalize(); + Name(Arc::new(NameData::Str(pre, s, hash))) + } + + /// Constructs a name by appending a numeric component to `pre`. + pub fn num(pre: Name, n: Nat) -> Name { + let mut hasher = blake3::Hasher::new(); + hasher.update(&[NNUM]); + hasher.update(pre.get_hash().as_bytes()); + hasher.update(&n.to_le_bytes()); + let hash = hasher.finalize(); + Name(Arc::new(NameData::Num(pre, n, hash))) + } + + /// Returns a dot-separated human-readable representation of this name. + pub fn pretty(&self) -> String { + let mut components = Vec::new(); + let mut current = self; + + loop { + match current.as_data() { + NameData::Anonymous(_) => break, + NameData::Str(pre, s, _) => { + components.push(s.as_str().to_owned()); + current = pre; + }, + NameData::Num(pre, n, _) => { + components.push(n.0.to_string()); + current = pre; + }, + } + } + + components.reverse(); + components.join(".") + } + } + + impl StdHash for Name { + fn hash(&self, state: &mut H) { + self.get_hash().hash(state); + } + } + + impl std::fmt::Display for Name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.pretty()) + } + } + + /// Canonical key for cross-kernel displayed-name matching: Lean's + /// `Name.toString` wraps non-identifier components in `«»` + /// (`_private.….«0».…`, `Lean.Order.«term_⊑_»`) while [`Name::pretty`] + /// renders them bare, so a fail-out file written by one side misses a + /// string-keyed lookup on the other. Stripping the guillemets from BOTH + /// the request and the candidate collapses the two renderings onto one + /// key, at the cost of the same flattening ambiguity `pretty` already + /// has. Mirrors `Ix.Cli.ConstsFile.normalizeName` on the Lean side. + pub fn normalize_displayed_name(s: &str) -> String { + if s.contains(['«', '»']) { + s.chars().filter(|c| !matches!(c, '«' | '»')).collect() + } else { + s.to_string() + } + } + + /// How a binder parameter is supplied at call sites. + #[derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, StdHash, Default, + )] + pub enum BinderInfo { + /// Explicit `(x : α)`. + #[default] + Default, + /// Implicit `{x : α}`. + Implicit, + /// Strict-implicit `⦃x : α⦄`. + StrictImplicit, + /// Typeclass instance `[x : α]`. + InstImplicit, + } + + /// Hints that control how aggressively the kernel unfolds a definition. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] + pub enum ReducibilityHints { + /// Never unfold. + #[default] + Opaque, + /// Always unfold (abbreviation). + Abbrev, + /// Unfold with the given priority height. + Regular(u32), + } + + /// Safety classification of a definition. + #[derive(Debug, Clone, Copy, PartialEq, Eq, StdHash)] + pub enum DefinitionSafety { + /// Marked `unsafe`; no termination or totality guarantees. + Unsafe, + /// Fully safe and total. + Safe, + /// Partial definition; may not terminate on all inputs. + Partial, + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq, StdHash)] + pub enum QuotKind { + Type, + Ctor, + Lift, + Ind, + } + + /// QuickCheck `Arbitrary` implementations for scalar environment types. + #[cfg(any(test, feature = "quickcheck"))] + pub mod arbitrary { + use quickcheck::{Arbitrary, Gen}; + + use super::*; + + const SHORT_ASCII: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789_"; + + pub(crate) fn arbitrary_ident(g: &mut Gen) -> String { + let len = (usize::arbitrary(g) % 8) + 1; + (0..len) + .map(|_| { + let idx = usize::arbitrary(g) % SHORT_ASCII.len(); + SHORT_ASCII[idx] as char + }) + .collect() + } + + impl Arbitrary for Name { + fn arbitrary(g: &mut Gen) -> Self { + let depth = usize::arbitrary(g) % 4; + let mut name = Name::anon(); + for _ in 0..depth { + if bool::arbitrary(g) { + name = Name::str(name, arbitrary_ident(g)); + } else { + name = Name::num(name, Nat::from(u64::arbitrary(g) % 100)); + } + } + name + } + } + + impl Arbitrary for BinderInfo { + fn arbitrary(g: &mut Gen) -> Self { + match u8::arbitrary(g) % 4 { + 0 => BinderInfo::Default, + 1 => BinderInfo::Implicit, + 2 => BinderInfo::StrictImplicit, + _ => BinderInfo::InstImplicit, + } + } + } + + impl Arbitrary for ReducibilityHints { + fn arbitrary(g: &mut Gen) -> Self { + match u8::arbitrary(g) % 3 { + 0 => ReducibilityHints::Opaque, + 1 => ReducibilityHints::Abbrev, + _ => ReducibilityHints::Regular(u32::arbitrary(g) % 100), + } + } + } + + impl Arbitrary for DefinitionSafety { + fn arbitrary(g: &mut Gen) -> Self { + match u8::arbitrary(g) % 3 { + 0 => DefinitionSafety::Unsafe, + 1 => DefinitionSafety::Safe, + _ => DefinitionSafety::Partial, + } + } + } + + impl Arbitrary for QuotKind { + fn arbitrary(g: &mut Gen) -> Self { + match u8::arbitrary(g) % 4 { + 0 => QuotKind::Type, + 1 => QuotKind::Ctor, + 2 => QuotKind::Lift, + _ => QuotKind::Ind, + } + } + } + } + + #[cfg(test)] + mod scalar_structure_tests { + use rustc_hash::FxHashMap; + + use super::*; + + /// Name tables hash a name by its digest and confirm a hit with the + /// derived `==`, which compares the components: two names that share a + /// digest are two keys. The Lean compiler's tables are keyed the same way + /// (`Ix.Compile.ExactKey.ExactName`, `Ix.nameCompareExact`). + #[test] + fn name_tables_keep_names_with_one_digest_apart() { + let x = Name::str(Name::anon(), "x".into()); + let forged = + Name(Arc::new(NameData::Str(Name::anon(), "y".into(), *x.get_hash()))); + assert_eq!(x.get_hash(), forged.get_hash()); + assert_ne!(x, forged); + let mut table: FxHashMap = FxHashMap::default(); + table.insert(x.clone(), 1); + table.insert(forged.clone(), 2); + assert_eq!(table.len(), 2); + assert_eq!(table.get(&x), Some(&1)); + assert_eq!(table.get(&forged), Some(&2)); + let absent = + Name(Arc::new(NameData::Str(Name::anon(), "z".into(), *x.get_hash()))); + assert!(!table.contains_key(&absent)); + // Valid neighbour: an equal name built separately is found. + assert_eq!(table.get(&Name::str(Name::anon(), "x".into())), Some(&1)); + } + } +} diff --git a/crates/common/src/name_table.rs b/crates/common/src/name_table.rs deleted file mode 100644 index 6dbd5b62c..000000000 --- a/crates/common/src/name_table.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Generated-name tables with confirmed structural lookup. -//! Cached digests select candidates; they never establish name equality. - -use crate::env::Name; -use blake3::Hash; -use rustc_hash::FxHashMap; - -/// The shared cache-insensitive name relation. Its implementation lives on Name. -pub fn name_eq(a: &Name, b: &Name) -> bool { - a.same_structure(b) -} - -/// Generated-name lookup with exactly the structural name equality used -/// by Lean. Derived `Name::eq` also compares cached fields and is not this -/// relation, even for a constructor-built name above a rehashed prefix. -#[derive(Clone)] -pub struct NameTable { - entries: Vec<(Name, T)>, - cache: FxHashMap, -} - -impl Default for NameTable { - fn default() -> Self { - Self { entries: Vec::new(), cache: FxHashMap::default() } - } -} - -impl NameTable { - pub fn with_capacity(capacity: usize) -> Self { - Self { entries: Vec::with_capacity(capacity), cache: FxHashMap::default() } - } - - pub fn get(&self, name: &Name) -> Option<&T> { - if let Some(&index) = self.cache.get(name.get_hash()) - && let Some((stored, value)) = self.entries.get(index) - && name_eq(stored, name) - { - return Some(value); - } - self - .entries - .iter() - .rev() - .find_map(|(stored, value)| name_eq(stored, name).then_some(value)) - } - - pub fn contains(&self, name: &Name) -> bool { - self.get(name).is_some() - } - - pub fn contains_key(&self, name: &Name) -> bool { - self.contains(name) - } - - pub fn insert(&mut self, name: Name, value: T) { - if self.get(&name).is_some() { - self.entries.retain(|(stored, _)| !name_eq(stored, &name)); - self.cache.clear(); - } - self.cache.insert(*name.get_hash(), self.entries.len()); - self.entries.push((name, value)); - } - - pub fn is_empty(&self) -> bool { - self.entries.is_empty() - } - pub fn len(&self) -> usize { - self.entries.len() - } - pub fn iter(&self) -> std::iter::Rev> { - self.entries.iter().rev() - } -} - -impl<'a, T> IntoIterator for &'a NameTable { - type Item = &'a (Name, T); - type IntoIter = std::iter::Rev>; - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl FromIterator<(Name, T)> for NameTable { - fn from_iter>(iter: I) -> Self { - let mut table = Self::default(); - for (name, value) in iter { - table.insert(name, value); - } - table - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::env::NameData; - use std::sync::Arc; - - fn name(text: &str) -> Name { - Name::str(Name::anon(), text.into()) - } - - #[test] - fn last_write_and_forged_bucket_keep_structural_keys() { - let original = name("x"); - let other_cache = Name(Arc::new(NameData::Str( - Name(Arc::new(NameData::Anonymous(blake3::hash(b"prefix")))), - "x".into(), - blake3::hash(b"other leaf cache"), - ))); - assert!(name_eq(&original, &other_cache)); - assert_ne!(original, other_cache); - let collision = Name(Arc::new(NameData::Str( - Name::anon(), - "y".into(), - *other_cache.get_hash(), - ))); - assert!(!name_eq(&original, &collision)); - for capacity in [0, 1, 32] { - let mut table = NameTable::with_capacity(capacity); - table.insert(original.clone(), 0); - table.insert(other_cache.clone(), 1); - assert_eq!(table.len(), 1); - assert_eq!(table.get(&original), Some(&1)); - assert_eq!(table.get(&name("x")), Some(&1)); - table.insert(collision.clone(), 2); - assert_eq!(table.get(&original), Some(&1)); - assert_eq!(table.get(&other_cache), Some(&1)); - assert_eq!(table.get(&collision), Some(&2)); - assert_eq!(table.get(&name("y")), Some(&2)); - assert_eq!(table.len(), 2); - table.insert(original.clone(), 3); - assert_eq!(table.get(&other_cache), Some(&3)); - assert_eq!(table.get(&collision), Some(&2)); - let order: Vec<_> = table.iter().map(|(_, value)| *value).collect(); - assert_eq!(order, [3, 2]); - } - } - - #[test] - fn forward_collection_preserves_last_structural_position() { - let x = name("x"); - let x_other = Name(Arc::new(NameData::Str( - Name::anon(), - "x".into(), - blake3::hash(b"changed cache"), - ))); - let table: NameTable<_> = - [(x.clone(), 0), (name("y"), 1), (x_other, 2)].into_iter().collect(); - assert_eq!(table.get(&x), Some(&2)); - assert_eq!(table.get(&name("y")), Some(&1)); - assert_eq!(table.get(&name("absent")), None); - assert_eq!(table.len(), 2); - } -} diff --git a/crates/common/src/prim_addrs.rs b/crates/common/src/prim_addrs.rs index 13f2920ce..bf88d1591 100644 --- a/crates/common/src/prim_addrs.rs +++ b/crates/common/src/prim_addrs.rs @@ -1,18 +1,14 @@ //! Hardcoded primitive content-addresses. //! -//! Lives in `ix-common` rather than `ix-kernel` because several crates -//! need this table and `ixon` (and everything above it) cannot depend on -//! the kernel — `ix-kernel` already depends on `ixon`, so the edge would -//! be a cycle. Witness builders need it to seed primitives the kernel -//! FABRICATES during reduction rather than discovers by walking `refs`. +//! Lives in `ix-common` because several crates need this table: witness +//! builders need it to seed primitives the kernel FABRICATES during +//! reduction rather than discovers by walking `refs`. //! //! Addresses are blake3 hashes of each primitive's Ixon-compiled form. //! Regenerate with `lake test -- --ignored rust-kernel-build-primitives`, which //! dumps the current `(name, hex)` pairs — paste the updated lines into -//! `PrimAddrs::new`. `lake test -- prim-addrs` pins this table against -//! the Lean mirror in `Ix/Tc/Primitive.lean`. - -use std::sync::LazyLock; +//! `PrimAddrs::new`. The Lean mirror is `Ix/PrimAddrs.lean`, which the +//! `prim-addrs` test compares with the live addresses. use crate::address::Address; @@ -125,13 +121,9 @@ impl Default for PrimAddrs { impl PrimAddrs { /// Addresses reserved for kernel-only reduction markers. These are not /// Lean constants and must never be accepted as user environment entries. - pub fn reserved_marker_addrs() -> [(&'static str, Address); 2] { + pub fn reserved_marker_addrs() -> [(&'static str, Address); 1] { let canon = Self::new(); - let orig = Self::new_orig(); - [ - ("eager_reduce", canon.eager_reduce.clone()), - ("orig.eager_reduce", orig.eager_reduce.clone()), - ] + [("eager_reduce", canon.eager_reduce.clone())] } /// Canonical content-hash addresses, hardcoded from the Ixon-compiled @@ -451,13 +443,6 @@ impl PrimAddrs { Self::parity_table(&Self::new()) } - /// `(lean_name, original_address_hex)` table from `Self::new_orig()`. - /// The name order matches [`Self::lean_parity_table`] so the same live - /// primitive catalog can validate both address schemes. - pub fn lean_orig_parity_table() -> Vec<(&'static str, String)> { - Self::parity_table(&Self::new_orig()) - } - fn parity_table(p: &Self) -> Vec<(&'static str, String)> { vec![ ("Nat", p.nat.hex()), @@ -554,315 +539,4 @@ impl PrimAddrs { ("String.decEq", p.string_dec_eq.hex()), ] } - - /// LEON content-hash addresses, hardcoded from - /// `ConstantInfo::get_hash()` applied to each primitive's original - /// (pre-compile) Lean declaration. Used by `Primitives::from_env_orig` - /// to resolve primitives against `orig_kenv` — the direct-ingress - /// environment produced by `lean_ingress` where KIds live at LEON - /// addresses rather than canonical addresses. - /// - /// Regenerate with `lake test -- --ignored rust-kernel-build-prim-origs`. - /// The - /// failure mode when these drift is a synthetic `@` KId in every - /// primitive field of `orig_kenv.prims()`, which cascades into a - /// flood of `AppTypeMismatch` errors during original-constant - /// verification (any Nat literal reduction, Bool literal, `String` - /// coercion, or reducer-marker comparison will diverge from the real - /// `orig_kenv` entry for that primitive). - pub fn new_orig() -> Self { - let h = |hex: &str| -> Address { - Address::from_hex(hex).expect("invalid primitive address hex") - }; - PrimAddrs { - nat: h( - "0c0524ffa66fdbc0c9d3f12faf1a27b2ecd331ffa06da24a78f832e4f4145589", - ), - nat_zero: h( - "adc9f7ba6a90c3caacf0be308c2012437e9dd810bfc2b9b286b4934be4e86cb1", - ), - nat_succ: h( - "e4f2b35614ae2c6487084cb96e90852643a043296bc682b469ccfd430650cf8d", - ), - nat_add: h( - "ed99025afee9212ecf57c260d56d5dce9c901628cb0080421989da4fe979ede7", - ), - nat_pred: h( - "26245a09319bcf9d55a08431bce3b9d8a8d09e3dad25b9a83cc666e3736deeb4", - ), - nat_sub: h( - "858e3184f315fc8f85614d8ccd6f854a71bb8ff1d2e2c0f2819bf411ff25f294", - ), - nat_mul: h( - "ea30166fa0b64cf3da2d952ed470d6c3d33de3e85243f95026f3b5262367ec9f", - ), - nat_pow: h( - "5cf09dd7481dee82376ed65b09a08ea716f3575dd5b0b2d76c04c4fbb2a36a5d", - ), - nat_gcd: h( - "09ae07bc024bfb0317aa228d1274294b40aebb4229dc7014f7b22d56fa46a760", - ), - nat_mod: h( - "7ee6854a6ef5afb0e83f8aae9ccc2cbb457110bd1013a6f7615a98667a34322a", - ), - nat_div: h( - "acb405101f168dc08bf410d54a8f588893776ab61be81f2c7e5e1dd05685560e", - ), - nat_bitwise: h( - "21a51ddc3faeec42c0f3897955d5e24c40ffb1924824bd919da5db0346962a98", - ), - nat_beq: h( - "6a397f8ed945046604a856b84b14a683b23916d83e794fd8ecc3348be87a8486", - ), - nat_ble: h( - "25bacf590070ada376418c4eae60a90fa529d56951f451166cd6ba15cf0eeb68", - ), - nat_land: h( - "dd73c5c1552ff6ad35537b83f46c9e8c4c2c979eda612fe169e29f3028c63db9", - ), - nat_lor: h( - "8390650998cbee5ee2432a797635d7a331f623eb6fae9f26f17191fcdb880c60", - ), - nat_xor: h( - "04ffebfee34f36c46f63ef6aa347b0b81db8c1cbf3fb9a282799cac024310e69", - ), - nat_shift_left: h( - "e09f39bd5d0655d8a7844447e85c5a865bb0d233e7512f21e3de78d15808eb59", - ), - nat_shift_right: h( - "6be9556dba2dedb10ea06533ac97de79a1d4973f32f8d0657a1467ea1f746e32", - ), - bool_type: h( - "95fc5d28972d1472a12ddfc2f4a5eefec9a81652fcb63ef06c7f6f6d21a951ab", - ), - bool_true: h( - "fc3a88e4dc16055bc8b797f9544909043015a3a349f2b3fc3e86990b2b9f2999", - ), - bool_false: h( - "c595b2c899f6f0ef39cfab3ac2fbe3b826a7ed21318defc64bbb861d754f8bdf", - ), - string: h( - "3589e6266ed0703fb4008f1e134775dff6bc9a15619687e75222f44253ab8663", - ), - string_mk: h( - "22d668557ab1f800aaf7312f10d9f36ec4d24d0389ac8d0b6d66fd2daf0be903", - ), - char_type: h( - "16e10c6b75431ae16fc23ef43f07512a1f34cff2a33d85b44aae5898e002ac8d", - ), - char_mk: h( - "feb0d0ed724893b5d3d57bafee59ff3cfbe76f43e03fad2b2cf237198aca4457", - ), - char_of_nat: h( - "3ac41b61c538227409f133982435bc97d59489b9129a61d1c4baa14fdb1d6a6a", - ), - string_of_list: h( - "0422aae71a49fd82c87cc8493725a927c1205a9418dc648947d7fde8ed240625", - ), - string_to_byte_array: h( - "714e5b7ea77110a862699b662ecc0bc5a6d70e25bbf6b69dc0f0ec5feb2cfbb3", - ), - byte_array_empty: h( - "5e80d9c092e5fd25417a3a011632e0d060adf9cfd4c0a0bd6798868f067a7cb2", - ), - list: h( - "5886afc36363b59242671f7171bedb319d2a8fa514bc4dc322e3ebcadc85e8ad", - ), - list_nil: h( - "c912ac74d13fa61091059bdae32484e44aea05f439cbbfff7998ef0bfb0e3409", - ), - list_cons: h( - "40b5c0b66834f312bbe3afcadd07911be4182695313be33394eef53d0026e988", - ), - eq: h("bc3de4d3492ebcf56e98f63459ea705005c1a4216cfc57113617738ae4d84870"), - eq_refl: h( - "3b01e364067d2ce2ac308da57512992635212487359b62a3c75f60686febef26", - ), - quot_type: h( - "7f7b22596ffee865e1be503216e360ab7dcbd0de645987916484c264ce52f9fe", - ), - quot_ctor: h( - "f06cc3564d1d269e96a51a3f41f1fae1214884ab6d555a11213b8bb2e9e517ef", - ), - quot_lift: h( - "ce268528ab8fe6ec17039a37e73079e3453eae1675c6c76ef302ac87e9a0bd90", - ), - quot_ind: h( - "4ce41a11c66a351352ab27fdfbda9d980f6e296a2fa7f20fdd41377482ed3d52", - ), - reduce_bool: h( - "43875997e42a7c9ea04f24b924da2299aa68e4f2dfb626d67fccfcf5b5132660", - ), - reduce_nat: h( - "604dc8af16829c747638e4b6d58be2baf5280077f8de9db71acb6ef8bbc5f25d", - ), - // Synthetic kernel-only marker for the original Lean-addressed env. - eager_reduce: h( - "ff00000000000000000000000000000000000000000000000000000000000013", - ), - system_platform_num_bits: h( - "6fb004fbafb4b68446a57550e21ac08d7599cb157ab194c52fcd7ba1671f10da", - ), - system_platform_get_num_bits: h( - "b9fe4dfbc707ca46de307491541e35ad89a93115245bca3860b74ebcc96a1af2", - ), - subtype_val: h( - "1cf910601d9d86d741333d9547d69d0e299bfe2f99a23a9e838d207fd641eac0", - ), - nat_dec_le: h( - "e34083eb212a258b36374129f6170a9972adceb78356b6c83aa32284ad4edee3", - ), - nat_dec_eq: h( - "a466eec5433bc056803f38b897d9913f91d836260c6ba4176374d1b66f98acc8", - ), - nat_dec_lt: h( - "759a284b4f73e6aa405b409d741fa2b35642693bd041e74b790623121c5e1e33", - ), - decidable_rec: h( - "19e688c7cc2966eb4f79a58eb501c776689f515a7a4cb39fdf7482f1294a1511", - ), - decidable_is_true: h( - "d235a7033c457dfed0f1e34d1d50e97279893b63bdcab3c4490dd9da7d47327f", - ), - decidable_is_false: h( - "2c26576bf92a0d9c2d169be19317e587eec54945a5a241c30dd84908d534d5a1", - ), - nat_le_of_ble_eq_true: h( - "ce908a2c83164cb59df5afba6345e29fa1ae44032e2aed8ac4d7fc0c87951849", - ), - nat_not_le_of_not_ble_eq_true: h( - "adb3eaf42d5f4c368bb929b20cec07fa96f9c9fe70d372ec72b25e6510ae14d4", - ), - nat_eq_of_beq_eq_true: h( - "06f72598e90d27fd5ba7700a2920781048e9712693b5a2d20df885cb203aa2d3", - ), - nat_ne_of_beq_eq_false: h( - "0bc9d7d2a3d61217967bec2dbdfdbda85e6c41dcb5340d859d0177beeee18437", - ), - fin: h( - "aca8ccd74023a139175db5f1b5b4d037ba1559e25a5d091f2bdc797b23dbb275", - ), - bool_no_confusion: h( - "68bd3c3b59b4bf7285096a8a0b90308db6307b082d24a08b91924b5e6cdcb53a", - ), - // Int primitives — LEON content-hashes from - // `lake test -- rust-kernel-build-prim-origs`. These are the - // addresses KIds live at in `orig_kenv`. - int: h( - "2c073df1601a9c8c7f26bdc51f22b8b7c6072fe6acbea71f244b4f67ceb1472b", - ), - int_of_nat: h( - "c7804dff4a217f857cb6ff58e60d9cb405bc48caffba3240e3f5601d359f9f21", - ), - int_neg_succ: h( - "a8fa07b6cbfec95b534e33a342ef8812aeecd00fbbd2378d71be0d45b876331a", - ), - int_add: h( - "5ef343c73bd4a1c1c7de0701ee822797783a988f8c71965316c7f44a64d5a9c1", - ), - int_sub: h( - "fbfbdc2f6d22d80e3ffb43897dfffedaf5729d5923d412c9bf5cd63ee7790bde", - ), - int_mul: h( - "43b5d0d51e29a259302707a64508345354061bbf2249aba25bd9962d0cdd538e", - ), - int_neg: h( - "8cf21639a1d062be65fa2a475a9a9945d43aa07344dac30a3eacdc512bab14de", - ), - int_emod: h( - "f528f52cf0c85aa71a26f9ed88d11e488c110a7b0854c74ddd0c95ff8f8d1f72", - ), - int_ediv: h( - "8b7ec664a8781cb34ec3678d2ce7fe4e22574ab5605c4988d841c84d8c63d6b0", - ), - int_bmod: h( - "61b9e1d73ecf8dff84ed4e7499c7552211695c9cdfe4a432f17e36c432efc7b2", - ), - int_bdiv: h( - "db0b8bb87b0d4d9fd68fa5039c3657866e122f2dea5e891bd2a0eb16569596b7", - ), - int_nat_abs: h( - "cc43f34a58ce42dfedfdfb0c07a5f31dffa6ba3fb272f3c573ec547eaef722d6", - ), - int_pow: h( - "ae92f05449a4d67697f3649225f88703a6a928a815b7cf6448e92b3a787a1103", - ), - int_dec_eq: h( - "6dc280a4f5be950140e02d61f81ce01b1e21ec06f338a973039bcebf13e8e08b", - ), - int_dec_le: h( - "dcce6645b4b207f4805c7c6878b7704ebd840903387f7848a3e544fe196f6ee3", - ), - int_dec_lt: h( - "ecffd44f689ee7dd7462e3a4b4620ae72637bc59c38b91e8dd5c3d98d899623d", - ), - punit: h( - "e4d0247a1393397d7efa718dc31229b3592a522531595290683ca63dfe420e4d", - ), - pprod: h( - "ce996300ab608fc33ff251a16ac724b19f169dac8ba3fa1c5be2276158adcf5c", - ), - pprod_mk: h( - "0a9e6c68e0531826a4b7e6cb74c5dacb7689e7ef1b78fc21f56acaf65ea25add", - ), - // Names previously matched via `is_const_named` in whnf.rs. - // LEON content-hashes from `lake test -- rust-kernel-build-prim-origs`. - nat_rec: h( - "02af71bf807e615ee42b36d8d5b210329cddfd1e739fc11f6ba097a2bf74fe5a", - ), - nat_cases_on: h( - "df2e7a477bd8b2ac4936f22c6a60a98e9055759cbcb856895497ee02bbd4af67", - ), - bit_vec: h( - "6f450298341dec31bbbd159414a9193b437e8541e24304c9b680a7d5384643b3", - ), - bit_vec_to_nat: h( - "ae3d3b7ad4c1376fe9d30b335ee19a6e5397672a5b5800f2a0276f8d249d2ed9", - ), - bit_vec_of_nat: h( - "b685da004503283d7a3b2b73a3ad29100762de6eced0b305aede886af05cb3ee", - ), - bit_vec_ult: h( - "7d0fd8eb0e739c1643319a0e6554ee7070aa575416d54c80f8f3d2b166cb7ac8", - ), - decidable_decide: h( - "741a3a166dabcf41a357ad70893ac52feb84068a4bc9de54596bbe602648e3d0", - ), - lt_lt: h( - "3f3eff2353822391e4db7f2b403cb79d2fca36c5a9a0d2dc4fce20850bb8b355", - ), - of_nat_of_nat: h( - "f75083bb57a4a1c5ce0b83945e39da01e11fb9f28f2ab4b57d8f0615ccda8c9d", - ), - unit: h( - "a9be73125f8d296246aa55a183e74d49c420b79c852c36df4fbb87a2ca1d751b", - ), - punit_size_of_1: h( - "6f48fa355d342f1b035ef0777c1ad72e669978816c2c09a3048c4848de4ff443", - ), - size_of_size_of: h( - "ac6c0f1adb8f8f74235dab15b624902bdc0832ed77fae0d62242d0e7717cb06a", - ), - string_back: h( - "54317bf07a28017fbfccf7d9f11c97846c106be220ab98ce1e1b58a196c12be8", - ), - string_utf8_byte_size: h( - "06ba07154a1cd0e1e9eec2b6e27b195a6fc3ae20a70d1ced7643a61e4e3e6d0f", - ), - string_append: h( - "93faafad0b7eff95765986eb5f5cb10635818129b72d8e7fdddaca2a5fb45844", - ), - string_dec_eq: h( - "a53c141a7bbbbdf77d4a2cb049911fd4001f7d71b94ed5c3b877e837da94c349", - ), - } - } -} - -pub fn reserved_marker_name(addr: &Address) -> Option<&'static str> { - static MARKERS: LazyLock<[(&'static str, Address); 2]> = - LazyLock::new(PrimAddrs::reserved_marker_addrs); - MARKERS - .iter() - .find_map(|(name, marker_addr)| (marker_addr == addr).then_some(*name)) } diff --git a/crates/compile/Cargo.toml b/crates/compile/Cargo.toml deleted file mode 100644 index 3fff593a0..000000000 --- a/crates/compile/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "ix-compile" -version.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -anyhow = { workspace = true } -bignat = { workspace = true } -blake3 = { workspace = true } -dashmap = { workspace = true, features = ["rayon"] } -indexmap = { workspace = true, features = ["rayon"] } -itertools = { workspace = true } -ix-common = { workspace = true } -ixon = { workspace = true } -ix-kernel = { workspace = true } -num-bigint = { workspace = true } -rayon = { workspace = true } -rustc-hash = { workspace = true } -sha2 = { workspace = true } - -[lints] -workspace = true diff --git a/crates/compile/src/compile.rs b/crates/compile/src/compile.rs deleted file mode 100644 index 3d4e4d761..000000000 --- a/crates/compile/src/compile.rs +++ /dev/null @@ -1,5211 +0,0 @@ -//! Compilation from Lean environment to Ixon format. -//! -//! This module compiles Lean constants to alpha-invariant Ixon representations -//! with sharing analysis for deduplication within constants - -#![allow(clippy::cast_possible_truncation)] -#![allow(clippy::cast_precision_loss)] - -use dashmap::DashMap; -use rustc_hash::{FxHashMap, FxHashSet}; -use std::{cmp::Ordering, sync::Arc}; - -use bignat::Nat; - -use ix_common::address::Address; -use ix_common::env::{ - AxiomVal, BinderInfo, ConstantInfo as LeanConstantInfo, ConstructorVal, - DataValue as LeanDataValue, Env as LeanEnv, Expr as LeanExpr, ExprData, - InductiveVal, Level, LevelData, Literal, Name, NameData, QuotVal, - RecursorRule as LeanRecursorRule, ReducibilityHints, - SourceInfo as LeanSourceInfo, Substring as LeanSubstring, - Syntax as LeanSyntax, SyntaxPreresolved, -}; - -use ixon::{ - CompileError, TagN, - canon_univ::canon_univ, - constant::{ - Axiom, Constant, ConstantInfo, Constructor, Definition, Inductive, - MutConst as IxonMutConst, Quotient, Recursor, RecursorRule, - ctor_proj_constant, defn_proj_constant, indc_proj_constant, - recr_proj_constant, - }, - env::{Env as IxonEnv, Named}, - expr::Expr, - metadata::{ - ConstantMeta, ConstantMetaInfo, DataValue, ExprMeta, ExprMetaData, KVMap, - UnivPatch, - }, - sharing_exact::{ - ExactSharingLimits, ShareLayout, SharingError, canonical_sharing_tiered, - }, - univ::Univ, -}; - -use crate::{ - graph::NameSet, - mutual::{Def, Ind, MutConst, MutCtx, Rec, ctx_to_all}, -}; - -pub use expr_key::{ExprKey, UnivKey}; -use expr_key::{PreseedKey, UnivContext}; - -/// Whether to output timing diagnostics for slow blocks. -/// Set via IX_TIMING=1 environment variable. -pub static IX_TIMING: std::sync::LazyLock = - std::sync::LazyLock::new(|| std::env::var("IX_TIMING").is_ok()); - -/// Options controlling whole-environment compilation. -#[derive(Clone, Copy, Debug, Default)] -pub struct CompileOptions { - /// Override the scheduler worker ceiling. `None` uses available parallelism - /// or `IX_COMPILE_WORKERS`; adaptive admission may run fewer active blocks. - pub max_workers: Option, -} - -/// Compile state for building the Ixon environment. -#[derive(Default)] -pub struct CompileState { - /// Ixon environment being built - pub env: IxonEnv, - /// Serializes complete block publication and direct compiler writes. - /// Do not mutate the published tables concurrently outside these methods. - pub publication_lock: std::sync::Mutex<()>, - /// Map from Lean constant name to Ixon address - pub name_to_addr: DashMap, - /// Mutual block class ordering, keyed by any inductive name in the block. - /// Each entry preserves the serialized singleton groups in Lean's order - /// (`source_members`). - pub blocks: DashMap>>, - /// Constants that couldn't be compiled (name -> error description). - /// - /// Populated in two phases: - /// 1. Pre-compile grounding: `ground_consts` identifies constants unreachable - /// from axioms/primitives. - /// 2. During scheduling: per-block compile failures are recorded here - /// instead of aborting the scheduler, so the rest of the env still - /// compiles and callers can report each failure per-constant. - /// - /// `DashMap` (rather than `FxHashMap`) because scheduler workers insert - /// concurrently on per-block failure paths. - pub ungrounded: DashMap, - /// Reducibility hints per definition NAME, recorded by - /// `compile_definition` (the only place the Lean-side hints are in - /// scope — hints are not part of `ConstantMeta`). The constant - /// address a name resolves to isn't final until its `Named` entry is - /// registered, so [`Self::finalize_hints`] resolves this map through - /// `env.named` into `env.anon_hints` once compilation completes. - pub def_hints: DashMap, - /// Resource limits of every canonical sharing construction run against - /// this state. - /// `compile_env` sets them once, from [`SHARING_LIMITS_ENV`], before it - /// schedules any block; an entry point that builds a state from a - /// deserialized environment sets them with [`compiler_sharing_limits`]. - /// The default is [`ExactSharingLimits::default`]. - pub sharing_limits: ExactSharingLimits, -} - -/// Cached compiled expression with arena root index. -/// -/// Once exact key equality confirms a hit, reuse the compiled value and -/// arena root. The subtree's metadata nodes are already in the append-only -/// arena; shared source objects compare by identity. -#[derive(Clone, Debug)] -pub struct CachedExpr { - pub expr: Arc, - pub arena_root: u64, -} - -/// Per-block compilation cache. -#[derive(Default)] -pub struct BlockCache { - /// Cache indexed by stored digest and confirmed by complete source equality. - pub exprs: FxHashMap, - /// Cache for compiled universes, with exact source levels and parameter - /// contexts. Digests index candidates; complete equality confirms hits. A - /// `Level::Param` compiles to `Univ::Var(i)` where `i` is the - /// parameter's POSITION in `univ_params`, so the result depends on the - /// context. This cache lives for a whole block (unlike the expression - /// cache, which is cleared per constant) and `compile_definition` - /// compiles each member under its own `level_params`, so a shared key - /// would hand the second member the first member's index — a silently - /// wrong constant under a correct-looking name, with no abort. - /// The table collector's seen set is keyed the same way (`PreseedKey`). - pub univ_cache: FxHashMap>, - /// Arena for expression metadata (append-only within a constant) - pub arena: ExprMeta, - /// Arena root indices parallel to the results stack - pub arena_roots: Vec, - /// Reference table: unique addresses of constants referenced by Expr::Ref - pub refs: indexmap::IndexSet
, - /// Universe table: unique universes referenced by expressions. - /// Canonicity §10.6: every entry is `canon_univ`-fixed — - /// `compile_univ_idx` interns only canonical forms and - /// `preseed_expr_tables` canonicalizes before sorting. - pub univs: indexmap::IndexSet>, - /// `canon_univ` memo (positional trees — context-free, so the cache is - /// sound across constants and univ contexts). - pub canon_cache: FxHashMap, Arc>, - /// Set by `preseed_expr_tables` once the primary `univs` table is - /// final. From then on any on-the-fly intern of a canonical form must - /// HIT a preseeded entry — a miss would silently shift the virtual - /// indices in `univ_patches` (which are `univs.len() + slot`), so - /// `compile_univ_idx` refuses it. - pub univs_final: bool, - /// Extension univs of the CURRENT constant: original (non-canonical) - /// spellings referenced by `univ_patches`, in first-use order. - /// Canonicity §10.6 — drained into `ConstantMeta.meta_univs` alongside - /// the arena. - pub meta_univs: indexmap::IndexSet>, - /// Level-spelling patches of the CURRENT constant, keyed by the arena - /// root of each affected `sort`/`const` occurrence — drained into - /// `ConstantMeta.univ_patches` alongside the arena. - pub univ_patches: Vec, - /// Name of the constant currently being compiled (for error context). - pub compiling: Option, -} - -#[derive(Debug)] -pub struct CompileStateStats { - pub consts: usize, - pub names: usize, - pub blobs: usize, - pub blocks: usize, -} - -impl CompileState { - /// Create an empty compile state for testing (no environment). - pub fn new_empty() -> Self { - Self::default() - } - - pub fn stats(&self) -> CompileStateStats { - CompileStateStats { - consts: self.env.const_count(), - names: self.env.name_count(), - blobs: self.env.blob_count(), - blocks: self.blocks.len(), - } - } - - /// Claim `name` for the block that compiles it at `addr`. - /// - /// Returns whether the caller may stage `name`'s `Named` entry. A block's - /// claims remain private until commit, which rechecks concurrent claims. - /// An identical published claim keeps its existing owner. - pub fn claim_compiled_name( - &self, - name: &Name, - addr: &Address, - ) -> Result { - block_txn::with(|txn| { - if let Some(txn) = txn { - if let Some(existing) = txn.claims.get(name) - && existing != addr - { - return Err(name_claim_conflict(name, existing, addr)); - } - let owns = if let Some(existing) = self.name_to_addr.get(name) { - if existing.value() != addr { - return Err(name_claim_conflict(name, existing.value(), addr)); - } - false - } else { - true - }; - txn.claims.insert(name.clone(), addr.clone()); - return Ok(owns); - } - let _publication = self.publication_lock.lock().unwrap(); - match self.name_to_addr.entry(name.clone()) { - dashmap::mapref::entry::Entry::Occupied(e) => { - if e.get() != addr { - return Err(name_claim_conflict(name, e.get(), addr)); - } - Ok(false) - }, - dashmap::mapref::entry::Entry::Vacant(e) => { - e.insert(addr.clone()); - Ok(true) - }, - } - }) - } - - /// Stage `name`'s metadata for the active block, or publish it directly - /// outside a transaction. - /// - /// A claimed name is registered through `claim_and_register_compiled`, so - /// that only its owner writes the entry. The unclaimed synthetic `Muts` - /// entries are registered here directly. - pub fn register_named(&self, name: Name, mut named: Named) { - // Demotion can serialize a large arena; keep it outside the commit lock. - if *ixon::env::DEMOTE { - named.demote(); - } - block_txn::with(|txn| { - if let Some(txn) = txn { - txn.named.insert(name, named); - } else { - let _publication = self.publication_lock.lock().unwrap(); - self.env.named.insert(name, named); - } - }); - } - - /// Claim `name` at `named.addr` for the block that compiles it - /// (`claim_compiled_name`) and register `named` if the claim owns the - /// name. - /// - /// An identical re-claim of a name another block owns registers nothing: - /// the owner's entry is kept, which entry a name keeps does not depend on - /// which block registered last, and a later failure of the re-claiming - /// block has nothing of the owner's to roll back. The claim precedes the - /// registration, so a refused claim writes nothing either. Mirrors the - /// Lean drivers' `mergeCompiledBlock`. - pub fn claim_and_register_compiled( - &self, - name: &Name, - mut named: Named, - ) -> Result<(), CompileError> { - if block_txn::with(|txn| txn.is_some()) { - if self.claim_compiled_name(name, &named.addr)? { - self.register_named(name.clone(), named); - } - return Ok(()); - } - if *ixon::env::DEMOTE { - named.demote(); - } - let _publication = self.publication_lock.lock().unwrap(); - match self.name_to_addr.entry(name.clone()) { - dashmap::mapref::entry::Entry::Occupied(entry) => { - if entry.get() != &named.addr { - return Err(name_claim_conflict(name, entry.get(), &named.addr)); - } - }, - dashmap::mapref::entry::Entry::Vacant(entry) => { - let addr = named.addr.clone(); - self.env.named.insert(name.clone(), named); - entry.insert(addr); - }, - } - Ok(()) - } - - /// Look up a compiled constant's address by name. - pub fn resolve_addr(&self, name: &Name) -> Option
{ - block_txn::with(|txn| { - txn - .and_then(|txn| txn.claims.get(name).cloned()) - .or_else(|| self.name_to_addr.get(name).map(|r| r.value().clone())) - }) - } - - fn contains_name(&self, addr: &Address) -> bool { - block_txn::with(|txn| { - txn.is_some_and(|txn| txn.names.contains_key(addr)) - || self.env.names.contains_key(addr) - }) - } - - fn store_name(&self, addr: Address, name: Name) { - block_txn::with(|txn| { - if let Some(txn) = txn { - txn.names.insert(addr, name); - } else { - let _publication = self.publication_lock.lock().unwrap(); - self.env.names.entry(addr).or_insert(name); - } - }); - } - - pub(crate) fn register_block(&self, name: Name, classes: Vec>) { - block_txn::with(|txn| { - if let Some(txn) = txn { - txn.blocks.insert(name, classes); - } else { - let _publication = self.publication_lock.lock().unwrap(); - self.blocks.insert(name, classes); - } - }); - } - - /// Record `name`'s reducibility hint (`None` removes it). Scheduled - /// blocks publish hints only after the whole block has compiled - /// (`block_txn::finish`); direct callers write immediately. - pub fn record_hint(&self, name: &Name, hint: Option) { - block_txn::with(|txn| { - if let Some(txn) = txn { - txn.hints.insert(name.clone(), hint); - return; - } - let _publication = self.publication_lock.lock().unwrap(); - match hint { - Some(h) => { - self.def_hints.insert(name.clone(), h); - }, - None => { - self.def_hints.remove(name); - }, - } - }); - } - - /// The hint `name` carries for this thread: the active block's own last - /// write, else the published one. - pub fn recorded_hint(&self, name: &Name) -> Option { - block_txn::with(|txn| { - txn - .and_then(|txn| txn.hints.get(name).copied()) - .unwrap_or_else(|| self.def_hints.get(name).map(|r| *r)) - }) - } - - /// Resolve the per-name hints recorded by `compile_definition` into - /// the env. Runs once after the scheduler drains: addresses aren't - /// final until the `Named` entries are registered. Two channels: - /// - /// - `Named.hints` (exact, per name): alpha-identical definitions - /// under different names share one constant address but may carry - /// different hints; decompile reconstructs `DefinitionVal.hints` - /// from here, so no merge may touch it. - /// - `env.anon_hints` (advisory, per constant address — `Named.addr`, - /// the projection address for mutual-block members, i.e. exactly - /// the address the anon-mode kernel looks hints up under). Alias - /// collisions resolve through `Env::register_hint`'s - /// order-independent merge. - pub fn finalize_hints(&self) { - for entry in self.def_hints.iter() { - if let Some(mut named) = self.env.named.get_mut(entry.key()) { - named.set_hints(Some(*entry.value())); - self.env.register_hint(named.addr.clone(), *entry.value()); - } - } - } -} - -// =========================================================================== -// Helper functions -// =========================================================================== - -/// Two producers claim one name at different addresses. -pub fn name_claim_conflict( - name: &Name, - existing: &Address, - claimed: &Address, -) -> CompileError { - CompileError::InvalidMutualBlock { - reason: format!( - "conflicting claims for name '{}': already registered at {:.12}, \ - claimed again at {:.12}", - name.pretty(), - existing.hex(), - claimed.hex(), - ), - } -} - -/// Convert a Nat to u64, returning an error if the value is too large. -fn nat_to_u64(n: &Nat, context: &'static str) -> Result { - n.to_u64().ok_or(CompileError::UnsupportedExpr { desc: context.into() }) -} - -// =========================================================================== -// Name compilation -// =========================================================================== - -/// Store a string as a blob and return its address (compare-and-insert: -/// `CompileState::store_blob`). -pub fn store_string( - s: &str, - stt: &CompileState, -) -> Result { - stt.store_blob(s.as_bytes().to_vec()) -} - -/// Store a Nat as a blob and return its address (compare-and-insert). -pub fn store_nat(n: &Nat, stt: &CompileState) -> Result { - stt.store_blob(n.to_le_bytes()) -} - -/// Compile a Lean Name to an address (stored in env.names). -/// Uses the Name's internal hash as the address. -/// String components are stored in blobs. -pub fn compile_name( - name: &Name, - stt: &CompileState, -) -> Result { - // Use the Name's internal hash as the address - let addr = Address::from_blake3_hash(*name.get_hash()); - - // Check if already stored - if stt.contains_name(&addr) { - return Ok(addr); - } - - // Recurse on parent first (ensures parent is stored) - match name.as_data() { - NameData::Anonymous(_) => {}, - NameData::Str(parent, s, _) => { - compile_name(parent, stt)?; - store_string(s, stt)?; // string data in blobs - }, - NameData::Num(parent, _, _) => { - compile_name(parent, stt)?; - // Nat is inline in Name, no blob needed - }, - } - - // Store Name struct directly in env.names - stt.store_name(addr.clone(), name.clone()); - Ok(addr) -} - -// =========================================================================== -// Universe compilation -// =========================================================================== - -/// Compile a Lean Level to an Ixon Univ. -pub fn compile_univ( - level: &Level, - univ_params: &[Name], - cache: &mut BlockCache, -) -> Result, CompileError> { - compile_univ_in(level, &UnivContext::new(univ_params), cache) -} - -/// `compile_univ` under a parameter context built once for the whole level. -fn compile_univ_in( - level: &Level, - ctx: &UnivContext, - cache: &mut BlockCache, -) -> Result, CompileError> { - let key = UnivKey::new(level, ctx); - if let Some(cached) = cache.univ_cache.get(&key) { - return Ok(cached.clone()); - } - - let univ = match level.as_data() { - LevelData::Zero(_) => Univ::zero(), - LevelData::Succ(inner, _) => { - let inner_univ = compile_univ_in(inner, ctx, cache)?; - Univ::succ(inner_univ) - }, - LevelData::Max(a, b, _) => { - let a_univ = compile_univ_in(a, ctx, cache)?; - let b_univ = compile_univ_in(b, ctx, cache)?; - Univ::max(a_univ, b_univ) - }, - LevelData::Imax(a, b, _) => { - let a_univ = compile_univ_in(a, ctx, cache)?; - let b_univ = compile_univ_in(b, ctx, cache)?; - Univ::imax(a_univ, b_univ) - }, - LevelData::Param(name, _) => { - let idx = - ctx.params().iter().position(|n| n == name).ok_or_else(|| { - CompileError::UnknownUnivParam { - curr: String::new(), - param: name.pretty(), - } - })?; - Univ::var(idx as u64) - }, - LevelData::Mvar(_name, _) => { - return Err(CompileError::UnsupportedExpr { - desc: "level metavariable".into(), - }); - }, - }; - - cache.univ_cache.insert(key, univ.clone()); - Ok(univ) -} - -/// `canon_univ` through the block memo. -fn canon_univ_cached(u: &Arc, cache: &mut BlockCache) -> Arc { - if let Some(c) = cache.canon_cache.get(u) { - return c.clone(); - } - let c = canon_univ(u); - cache.canon_cache.insert(u.clone(), c.clone()); - c -} - -/// Compile a universe and intern its CANONICAL form into the primary -/// univs table (canonicity §10.6). Returns the canonical index plus, -/// when the source spelling differs, the VIRTUAL index of the original -/// spelling in the per-constant `meta_univs` extension -/// (`univs.len() + slot` — stable because the primary table is -/// preseed-final by the time expressions compile). -fn compile_univ_idx( - level: &Level, - univ_params: &[Name], - cache: &mut BlockCache, -) -> Result<(u64, Option), CompileError> { - let univ = compile_univ(level, univ_params, cache)?; - let canon = canon_univ_cached(&univ, cache); - // V3 tripwire (canonicity §10.6): the primary table must not grow after - // preseeding; a miss here would shift every virtual patch index minted so - // far. Lean's `compileAndInternUnivCanon` refuses the same way, in every - // build profile. - if cache.univs_final && !cache.univs.contains(&canon) { - return Err(CompileError::InvalidMutualBlock { - reason: format!( - "preseed missed canonical form {canon:?} — primary univ table grew \ - after preseeding, shifting univPatches virtual indices (canonicity \ - 10.6 V3)" - ), - }); - } - let (idx, _) = cache.univs.insert_full(canon.clone()); - if canon == univ { - return Ok((idx as u64, None)); - } - let (slot, _) = cache.meta_univs.insert_full(univ); - Ok((idx as u64, Some((cache.univs.len() + slot) as u64))) -} - -/// Compile a list of universes and add their canonical forms to the -/// univs table, returning `(canonical_idx, Option)` pairs (see [`compile_univ_idx`]). -fn compile_univ_indices( - levels: &[Level], - univ_params: &[Name], - cache: &mut BlockCache, -) -> Result)>, CompileError> { - levels.iter().map(|l| compile_univ_idx(l, univ_params, cache)).collect() -} - -fn univ_sort_key(univ: &Arc) -> Vec { - let mut buf = Vec::new(); - ixon::univ::put_univ(univ, &mut buf); - buf -} - -fn univ_params_key(univ_params: &[Name]) -> Address { - let mut hasher = blake3::Hasher::new(); - for name in univ_params { - hasher.update(name.get_hash().as_bytes()); - } - Address::from_blake3_hash(hasher.finalize()) -} - -fn collect_expr_tables( - expr: &LeanExpr, - univ_params: &[Name], - mut_ctx: &MutCtx, - cache: &mut BlockCache, - stt: &CompileState, - refs: &mut Vec
, - univs: &mut Vec>, - seen_exprs: &mut FxHashSet, - caller: &str, -) -> Result<(), CompileError> { - let ctx_key = univ_params_key(univ_params); - let params: Arc<[Name]> = Arc::from(univ_params); - let mut stack = vec![expr]; - while let Some(e) = stack.pop() { - let key = PreseedKey { - expr: e.clone(), - params: params.clone(), - context_hash: ctx_key.clone(), - }; - if !seen_exprs.insert(key) { - continue; - } - - match e.as_data() { - ExprData::Bvar(..) => {}, - ExprData::Sort(level, _) => { - univs.push(compile_univ(level, univ_params, cache)?); - }, - ExprData::Const(name, levels, _) => { - for level in levels { - univs.push(compile_univ(level, univ_params, cache)?); - } - if !mut_ctx.contains_key(name) { - let const_addr = stt.resolve_addr(name).ok_or_else(|| { - CompileError::MissingConstant { - name: name.pretty(), - caller: format!("{caller} @ preseed(Const)"), - } - })?; - refs.push(const_addr); - } - }, - ExprData::App(fun, arg, _) => { - stack.push(arg); - stack.push(fun); - }, - ExprData::Lam(_, ty, body, _, _) - | ExprData::ForallE(_, ty, body, _, _) => { - stack.push(body); - stack.push(ty); - }, - ExprData::LetE(_, ty, value, body, _, _) => { - stack.push(body); - stack.push(value); - stack.push(ty); - }, - ExprData::Lit(Literal::NatVal(n), _) => { - refs.push(store_nat(n, stt)?); - }, - ExprData::Lit(Literal::StrVal(s), _) => { - refs.push(store_string(s, stt)?); - }, - ExprData::Proj(type_name, _, struct_val, _) => { - let type_addr = stt.resolve_addr(type_name).ok_or_else(|| { - CompileError::MissingConstant { - name: type_name.pretty(), - caller: format!("{caller} @ preseed(Proj)"), - } - })?; - refs.push(type_addr); - stack.push(struct_val); - }, - ExprData::Mdata(_, inner, _) => { - stack.push(inner); - }, - ExprData::Fvar(..) => { - return Err(CompileError::UnsupportedExpr { - desc: "free variable".into(), - }); - }, - ExprData::Mvar(..) => { - return Err(CompileError::UnsupportedExpr { - desc: "metavariable".into(), - }); - }, - } - } - Ok(()) -} - -pub fn preseed_expr_tables( - exprs: &[(&LeanExpr, &[Name])], - mut_ctx: &MutCtx, - cache: &mut BlockCache, - stt: &CompileState, - caller: &str, -) -> Result<(), CompileError> { - let mut refs = Vec::new(); - let mut univs = Vec::new(); - let mut seen_exprs = FxHashSet::default(); - - for (expr, univ_params) in exprs { - collect_expr_tables( - expr, - univ_params, - mut_ctx, - cache, - stt, - &mut refs, - &mut univs, - &mut seen_exprs, - caller, - )?; - } - - refs.sort(); - refs.dedup(); - for addr in refs { - cache.refs.insert_full(addr); - } - - // Canonicalize before sorting (canonicity §10.6): the primary table - // holds only `canon_univ`-fixed forms; the on-the-fly - // `compile_univ_idx` then always finds the preseeded canonical entry. - let mut canon_univs: Vec> = Vec::with_capacity(univs.len()); - for u in univs { - canon_univs.push(canon_univ_cached(&u, cache)); - } - let mut keyed_univs: Vec<_> = - canon_univs.into_iter().map(|u| (univ_sort_key(&u), u)).collect(); - keyed_univs.sort_by(|(ak, _), (bk, _)| ak.cmp(bk)); - keyed_univs.dedup_by(|(ak, _), (bk, _)| ak == bk); - for (_, univ) in keyed_univs { - cache.univs.insert_full(univ); - } - cache.univs_final = true; - - Ok(()) -} - -pub fn collect_mut_const_exprs<'a>( - cnst: &'a MutConst, - exprs: &mut Vec<(&'a LeanExpr, &'a [Name])>, -) { - match cnst { - MutConst::Defn(def) => { - let lvls = def.level_params.as_slice(); - exprs.push((&def.typ, lvls)); - exprs.push((&def.value, lvls)); - }, - MutConst::Indc(ind) => { - exprs.push((&ind.ind.cnst.typ, ind.ind.cnst.level_params.as_slice())); - for ctor in &ind.ctors { - exprs.push((&ctor.cnst.typ, ctor.cnst.level_params.as_slice())); - } - }, - MutConst::Recr(rec) => { - let lvls = rec.cnst.level_params.as_slice(); - exprs.push((&rec.cnst.typ, lvls)); - for rule in &rec.rules { - exprs.push((&rule.rhs, lvls)); - } - }, - } -} - -// =========================================================================== -// Expression compilation -// =========================================================================== - -/// Intern an address into the block's refs table, returning its index. -fn intern_ref(cache: &mut BlockCache, addr: Address) -> usize { - cache.refs.insert_full(addr).0 -} - -/// Collect a Lean App telescope: peel App nodes to get `(head, [a1, ..., aN])`. -/// -/// Arguments are returned in application order (leftmost first). -fn collect_lean_telescope(e: &LeanExpr) -> (&LeanExpr, Vec<&LeanExpr>) { - let mut args: Vec<&LeanExpr> = Vec::new(); - let mut cur = e; - while let ExprData::App(f, a, _) = cur.as_data() { - args.push(a); - cur = f; - } - args.reverse(); - (cur, args) -} - -fn compile_const_expr_raw( - name: &Name, - levels: &[Level], - univ_params: &[Name], - mut_ctx: &MutCtx, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result<(Arc, u64), CompileError> { - let compiled = compile_univ_indices(levels, univ_params, cache)?; - let univ_indices: Vec = compiled.iter().map(|(c, _)| *c).collect(); - let patch_idxs: Option> = - if compiled.iter().any(|(_, o)| o.is_some()) { - Some(compiled.iter().map(|(c, o)| o.unwrap_or(*c)).collect()) - } else { - None - }; - let name_addr = compile_name(name, stt)?; - let expr = if let Some(idx) = mut_ctx.get(name) { - let idx_u64 = nat_to_u64(idx, "mutual index too large")?; - Expr::rec(idx_u64, univ_indices) - } else { - let const_addr = stt.resolve_addr(name).ok_or_else(|| { - let who = - cache.compiling.as_ref().map_or_else(|| "?".into(), |n| n.pretty()); - CompileError::MissingConstant { - name: name.pretty(), - caller: format!("{who} @ compile_expr(Const)"), - } - })?; - let ref_idx = intern_ref(cache, const_addr.clone()); - Expr::reference(ref_idx as u64, univ_indices) - }; - let root = cache.arena.alloc(ExprMetaData::Ref { name: name_addr }); - if let Some(univ_idxs) = patch_idxs { - cache.univ_patches.push(UnivPatch { arena_idx: root, univ_idxs }); - } - Ok((expr, root)) -} - -/// Compile a Lean expression to an Ixon expression. -/// Builds arena-based metadata in cache.arena with bottom-up allocation. -pub fn compile_expr( - expr: &LeanExpr, - univ_params: &[Name], - mut_ctx: &MutCtx, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result, CompileError> { - // Stack-based iterative compilation to avoid stack overflow - enum Frame { - Compile(LeanExpr), - BuildApp, - BuildLam(Address, BinderInfo), - BuildAll(Address, BinderInfo), - BuildLet(Address, bool), - BuildProj(u64, u64, Address), // type_ref_idx, field_idx, struct_name_addr - WrapMdata(Vec), - ApplyContract(crate::semantic_contract::Contract, LeanExpr), - Cache(LeanExpr), - } - - // Exact source keys keep distinct terms with colliding digests separate. - let expr_key = ExprKey(expr.clone()); - if let Some(cached) = cache.exprs.get(&expr_key).cloned() { - cache.arena_roots.push(cached.arena_root); - return Ok(cached.expr); - } - - let mut stack: Vec = vec![Frame::Compile(expr.clone())]; - let mut results: Vec> = Vec::new(); - - while let Some(frame) = stack.pop() { - match frame { - Frame::Compile(e) => { - let e_key = ExprKey(e.clone()); - if let Some(cached) = cache.exprs.get(&e_key).cloned() { - // Confirmed cache hit: arena root already valid. - results.push(cached.expr); - cache.arena_roots.push(cached.arena_root); - continue; - } - - stack.push(Frame::Cache(e.clone())); - - match e.as_data() { - ExprData::Bvar(idx, _) => { - let idx_u64 = nat_to_u64(idx, "bvar index too large")?; - results.push(Expr::var(idx_u64)); - cache.arena_roots.push(cache.arena.alloc(ExprMetaData::Leaf)); - }, - - ExprData::Sort(level, _) => { - let (univ_idx, orig) = compile_univ_idx(level, univ_params, cache)?; - results.push(Expr::sort(univ_idx)); - let root = cache.arena.alloc(ExprMetaData::Leaf); - cache.arena_roots.push(root); - // Canonicity §10.6: a spelling the canonicalization changed - // is restorable from the patch keyed by this occurrence's - // arena root. Cache hits on repeated subtrees reuse the same - // root, so the patch covers every occurrence. - if let Some(vidx) = orig { - cache - .univ_patches - .push(UnivPatch { arena_idx: root, univ_idxs: vec![vidx] }); - } - }, - - ExprData::Const(name, levels, _) => { - let (raw, root) = compile_const_expr_raw( - name, - levels, - univ_params, - mut_ctx, - cache, - stt, - )?; - results.push(raw); - cache.arena_roots.push(root); - }, - - ExprData::App(_, _, _) => { - // Collect the full App telescope in one pass (O(depth) pointer - // chase), then interleave BuildApp + Compile(arg) for each arg - // (right to left), then Compile(head). This compiles the same - // result as the recursive one-App-at-a-time approach, but avoids - // re-entering the App branch for inner nodes. - let (head_expr, args) = collect_lean_telescope(&e); - for &arg in args.iter().rev() { - stack.push(Frame::BuildApp); - stack.push(Frame::Compile(arg.clone())); - } - stack.push(Frame::Compile(head_expr.clone())); - }, - - ExprData::Lam(name, ty, body, info, _) => { - let name_addr = compile_name(name, stt)?; - stack.push(Frame::BuildLam(name_addr, info.clone())); - stack.push(Frame::Compile(body.clone())); - stack.push(Frame::Compile(ty.clone())); - }, - - ExprData::ForallE(name, ty, body, info, _) => { - let name_addr = compile_name(name, stt)?; - stack.push(Frame::BuildAll(name_addr, info.clone())); - stack.push(Frame::Compile(body.clone())); - stack.push(Frame::Compile(ty.clone())); - }, - - ExprData::LetE(name, ty, val, body, non_dep, _) => { - let name_addr = compile_name(name, stt)?; - stack.push(Frame::BuildLet(name_addr, *non_dep)); - stack.push(Frame::Compile(body.clone())); - stack.push(Frame::Compile(val.clone())); - stack.push(Frame::Compile(ty.clone())); - }, - - ExprData::Lit(Literal::NatVal(n), _) => { - let addr = store_nat(n, stt)?; - let ref_idx = intern_ref(cache, addr); - results.push(Expr::nat(ref_idx as u64)); - cache.arena_roots.push(cache.arena.alloc(ExprMetaData::Leaf)); - }, - - ExprData::Lit(Literal::StrVal(s), _) => { - let addr = store_string(s, stt)?; - let ref_idx = intern_ref(cache, addr); - results.push(Expr::str(ref_idx as u64)); - cache.arena_roots.push(cache.arena.alloc(ExprMetaData::Leaf)); - }, - - ExprData::Proj(type_name, idx, struct_val, _) => { - let idx_u64 = nat_to_u64(idx, "proj index too large")?; - - let type_addr = stt.resolve_addr(type_name).ok_or_else(|| { - let who = cache - .compiling - .as_ref() - .map_or_else(|| "?".into(), |n| n.pretty()); - CompileError::MissingConstant { - name: type_name.pretty(), - caller: format!("{who} @ compile_expr(Proj)"), - } - })?; - - let ref_idx = intern_ref(cache, type_addr.clone()); - let name_addr = compile_name(type_name, stt)?; - - stack.push(Frame::BuildProj(ref_idx as u64, idx_u64, name_addr)); - stack.push(Frame::Compile(struct_val.clone())); - }, - - ExprData::Mdata(kv, inner, _) => { - if crate::semantic_contract::has_metadata(kv) { - let contract = crate::semantic_contract::read(kv)?; - stack.push(Frame::ApplyContract(contract, inner.clone())); - stack.push(Frame::Compile(inner.clone())); - continue; - } - // Compile KV map - let mut pairs = Vec::new(); - for (k, v) in kv { - let k_addr = compile_name(k, stt)?; - let v_data = compile_data_value(v, stt)?; - pairs.push((k_addr, v_data)); - } - // Mdata becomes a separate arena node wrapping inner - stack.push(Frame::WrapMdata(vec![pairs])); - stack.push(Frame::Compile(inner.clone())); - }, - - ExprData::Fvar(n, _) => { - return Err(CompileError::UnsupportedExpr { - desc: format!("free variable '{}'", n.pretty()), - }); - }, - - ExprData::Mvar(..) => { - return Err(CompileError::UnsupportedExpr { - desc: "metavariable".into(), - }); - }, - } - }, - - Frame::BuildApp => { - let a_root = - cache.arena_roots.pop().expect("BuildApp missing arg root"); - let f_root = - cache.arena_roots.pop().expect("BuildApp missing fun root"); - let arg = results.pop().expect("BuildApp missing arg"); - let fun = results.pop().expect("BuildApp missing fun"); - results.push(Expr::app(fun, arg)); - cache.arena_roots.push( - cache.arena.alloc(ExprMetaData::App { children: [f_root, a_root] }), - ); - }, - - Frame::BuildLam(name_addr, info) => { - let body_root = - cache.arena_roots.pop().expect("BuildLam missing body root"); - let ty_root = - cache.arena_roots.pop().expect("BuildLam missing ty root"); - let body = results.pop().expect("BuildLam missing body"); - let ty = results.pop().expect("BuildLam missing ty"); - results.push(Expr::lam(ty, body)); - cache.arena_roots.push(cache.arena.alloc(ExprMetaData::Binder { - name: name_addr, - info, - children: [ty_root, body_root], - })); - }, - - Frame::BuildAll(name_addr, info) => { - let body_root = - cache.arena_roots.pop().expect("BuildAll missing body root"); - let ty_root = - cache.arena_roots.pop().expect("BuildAll missing ty root"); - let body = results.pop().expect("BuildAll missing body"); - let ty = results.pop().expect("BuildAll missing ty"); - results.push(Expr::all(ty, body)); - cache.arena_roots.push(cache.arena.alloc(ExprMetaData::Binder { - name: name_addr, - info, - children: [ty_root, body_root], - })); - }, - - Frame::BuildLet(name_addr, non_dep) => { - let body_root = - cache.arena_roots.pop().expect("BuildLet missing body root"); - let val_root = - cache.arena_roots.pop().expect("BuildLet missing val root"); - let ty_root = - cache.arena_roots.pop().expect("BuildLet missing ty root"); - let body = results.pop().expect("BuildLet missing body"); - let val = results.pop().expect("BuildLet missing val"); - let ty = results.pop().expect("BuildLet missing ty"); - results.push(Expr::let_(non_dep, ty, val, body)); - cache.arena_roots.push(cache.arena.alloc(ExprMetaData::LetBinder { - name: name_addr, - children: [ty_root, val_root, body_root], - })); - }, - - Frame::BuildProj(type_ref_idx, field_idx, struct_name_addr) => { - let child_root = - cache.arena_roots.pop().expect("BuildProj missing child root"); - let struct_val = results.pop().expect("BuildProj missing struct_val"); - results.push(Expr::prj(type_ref_idx, field_idx, struct_val)); - cache.arena_roots.push(cache.arena.alloc(ExprMetaData::Prj { - struct_name: struct_name_addr, - child: child_root, - })); - }, - - Frame::ApplyContract(contract, source) => { - let inner = results.pop().expect("ApplyContract missing expression"); - results.push(contract.lower(&source, &inner)?); - }, - - Frame::WrapMdata(mdata) => { - // Mdata doesn't change the Ixon expression — only wraps the arena node - let inner_root = - cache.arena_roots.pop().expect("WrapMdata missing inner root"); - cache.arena_roots.push( - cache.arena.alloc(ExprMetaData::Mdata { mdata, child: inner_root }), - ); - }, - - Frame::Cache(e) => { - let e_key = ExprKey(e); - if let Some(result) = results.last() { - let arena_root = - *cache.arena_roots.last().expect("Cache missing arena root"); - let entry = CachedExpr { expr: result.clone(), arena_root }; - cache.exprs.insert(e_key, entry); - } - }, - } - } - - results - .pop() - .ok_or(CompileError::UnsupportedExpr { desc: "empty result".into() }) -} - -/// Compile a Lean DataValue to Ixon DataValue. -fn compile_data_value( - dv: &LeanDataValue, - stt: &CompileState, -) -> Result { - Ok(match dv { - LeanDataValue::OfString(s) => DataValue::OfString(store_string(s, stt)?), - LeanDataValue::OfBool(b) => DataValue::OfBool(*b), - LeanDataValue::OfName(n) => DataValue::OfName(compile_name(n, stt)?), - LeanDataValue::OfNat(n) => DataValue::OfNat(store_nat(n, stt)?), - LeanDataValue::OfInt(i) => { - // Serialize Int and store as blob - let mut bytes = Vec::new(); - match i { - ix_common::env::Int::OfNat(n) => { - bytes.push(0); - bytes.extend_from_slice(&n.to_le_bytes()); - }, - ix_common::env::Int::NegSucc(n) => { - bytes.push(1); - bytes.extend_from_slice(&n.to_le_bytes()); - }, - } - DataValue::OfInt(stt.store_blob(bytes)?) - }, - LeanDataValue::OfSyntax(syn) => { - // Serialize syntax and store as blob - let bytes = serialize_syntax(syn, stt)?; - DataValue::OfSyntax(stt.store_blob(bytes)?) - }, - }) -} - -/// Serialize a Lean Syntax to bytes. -fn serialize_syntax( - syn: &LeanSyntax, - stt: &CompileState, -) -> Result, CompileError> { - let mut bytes = Vec::new(); - serialize_syntax_inner(syn, stt, &mut bytes)?; - Ok(bytes) -} - -fn serialize_syntax_inner( - syn: &LeanSyntax, - stt: &CompileState, - bytes: &mut Vec, -) -> Result<(), CompileError> { - match syn { - LeanSyntax::Missing => bytes.push(0), - LeanSyntax::Node(info, kind, args) => { - bytes.push(1); - serialize_source_info(info, stt, bytes)?; - bytes.extend_from_slice(compile_name(kind, stt)?.as_bytes()); - TagN::put(0, 0, args.len() as u64, bytes); - for arg in args { - serialize_syntax_inner(arg, stt, bytes)?; - } - }, - LeanSyntax::Atom(info, val) => { - bytes.push(2); - serialize_source_info(info, stt, bytes)?; - bytes.extend_from_slice(store_string(val, stt)?.as_bytes()); - }, - LeanSyntax::Ident(info, raw_val, val, preresolved) => { - bytes.push(3); - serialize_source_info(info, stt, bytes)?; - serialize_substring(raw_val, stt, bytes)?; - bytes.extend_from_slice(compile_name(val, stt)?.as_bytes()); - TagN::put(0, 0, preresolved.len() as u64, bytes); - for pr in preresolved { - serialize_preresolved(pr, stt, bytes)?; - } - }, - } - Ok(()) -} - -fn serialize_source_info( - info: &LeanSourceInfo, - stt: &CompileState, - bytes: &mut Vec, -) -> Result<(), CompileError> { - match info { - LeanSourceInfo::Original(leading, leading_pos, trailing, trailing_pos) => { - bytes.push(0); - serialize_substring(leading, stt, bytes)?; - // u64::MAX sentinel for positions that overflow u64 (should never happen in practice) - TagN::put(0, 0, leading_pos.to_u64().unwrap_or(u64::MAX), bytes); - serialize_substring(trailing, stt, bytes)?; - TagN::put(0, 0, trailing_pos.to_u64().unwrap_or(u64::MAX), bytes); - }, - LeanSourceInfo::Synthetic(start, end, canonical) => { - bytes.push(1); - TagN::put(0, 0, start.to_u64().unwrap_or(u64::MAX), bytes); - TagN::put(0, 0, end.to_u64().unwrap_or(u64::MAX), bytes); - bytes.push(if *canonical { 1 } else { 0 }); - }, - LeanSourceInfo::None => bytes.push(2), - } - Ok(()) -} - -fn serialize_substring( - ss: &LeanSubstring, - stt: &CompileState, - bytes: &mut Vec, -) -> Result<(), CompileError> { - bytes.extend_from_slice(store_string(&ss.str, stt)?.as_bytes()); - TagN::put(0, 0, ss.start_pos.to_u64().unwrap_or(u64::MAX), bytes); - TagN::put(0, 0, ss.stop_pos.to_u64().unwrap_or(u64::MAX), bytes); - Ok(()) -} - -fn serialize_preresolved( - pr: &SyntaxPreresolved, - stt: &CompileState, - bytes: &mut Vec, -) -> Result<(), CompileError> { - match pr { - SyntaxPreresolved::Namespace(n) => { - bytes.push(0); - bytes.extend_from_slice(compile_name(n, stt)?.as_bytes()); - }, - SyntaxPreresolved::Decl(n, fields) => { - bytes.push(1); - bytes.extend_from_slice(compile_name(n, stt)?.as_bytes()); - TagN::put(0, 0, fields.len() as u64, bytes); - for f in fields { - bytes.extend_from_slice(store_string(f, stt)?.as_bytes()); - } - }, - } - Ok(()) -} - -// =========================================================================== -// Canonical sharing -// =========================================================================== - -// The compiler shares every block with the canonical tiered construction -// (`canonical_sharing_tiered`, TagN layout). Every construction error, -// resource exhaustion included, is a compile error: there is no fallback. -// Mirrors `Ix.CompileM.buildConstantWithSharing`. - -/// Environment variable carrying a sharing-limit override in the format of -/// [`ExactSharingLimits::with_overrides`] (for example -/// `states=2^44,height=2^34` or `unbounded`). `ix compile --sharing-limits` -/// and `ix compile-lean --sharing-limits` set it. Mirrors Lean -/// `Ix.CompileM.sharingLimitsEnvVar`. -pub const SHARING_LIMITS_ENV: &str = "IX_SHARING_LIMITS"; - -/// The [`ExactSharingLimits`] defaults with the override `spec` applied. -pub fn compiler_sharing_limits_with( - spec: Option<&str>, -) -> Result { - let limits = ExactSharingLimits::default(); - match spec { - None => Ok(limits), - Some(spec) => limits - .with_overrides(spec) - .map_err(|e| format!("{SHARING_LIMITS_ENV}: {e}")), - } -} - -/// Resource limits of the compiler's canonical sharing construction: the -/// [`ExactSharingLimits`] defaults (a safety net far above every corpus -/// maximum) with the [`SHARING_LIMITS_ENV`] override. Each entry point reads -/// it once and carries the result ([`CompileState::sharing_limits`]), so an -/// invalid override fails the run once, before any block is shared. Mirrors -/// `Ix.CompileM.compilerSharingLimitsFromEnv`. -pub fn compiler_sharing_limits() -> Result { - compiler_sharing_limits_with( - std::env::var(SHARING_LIMITS_ENV).ok().as_deref(), - ) -} - -/// A sharing-construction failure as a compile error: resource exhaustion is -/// `ResourceLimit` (naming the limit and how to raise it), every other kind -/// `SharingConstruction`. Mirrors `Ix.CompileM.sharingCompileError`. -fn sharing_compile_error(e: SharingError) -> CompileError { - match e { - SharingError::ResourceExhausted(r) => CompileError::ResourceLimit { - reason: format!( - "canonical sharing: {e}; raise it with --sharing-limits {}=N \ - ({SHARING_LIMITS_ENV})", - r.resource.key() - ), - }, - other => CompileError::SharingConstruction { - reason: format!("canonical sharing: {other}"), - }, - } -} - -/// Share the ordered roots of one block: the rewritten roots (same order and -/// count) and the sharing table. -fn share_roots( - limits: &ExactSharingLimits, - exprs: &[Arc], -) -> Result<(Vec>, Vec>), CompileError> { - let res = canonical_sharing_tiered(ShareLayout::TagN, exprs, limits) - .map_err(sharing_compile_error)?; - if res.roots.len() != exprs.len() { - return Err(CompileError::SharingConstruction { - reason: format!( - "canonical sharing returned {} roots for {}", - res.roots.len(), - exprs.len() - ), - }); - } - Ok((res.roots, res.sharing)) -} - -/// Apply the compiler's sharing under `limits` (a compile passes -/// [`CompileState::sharing_limits`]) to a definition payload. -#[allow(clippy::needless_pass_by_value)] -pub fn apply_sharing_to_definition_with_limits( - limits: &ExactSharingLimits, - def: Definition, - refs: Vec
, - univs: Vec>, -) -> Result { - let (roots, sharing) = - share_roots(limits, &[def.typ.clone(), def.value.clone()])?; - let def = Definition { - kind: def.kind, - safety: def.safety, - lvls: def.lvls, - typ: roots[0].clone(), - value: roots[1].clone(), - }; - let constant = - Constant::with_tables(ConstantInfo::Defn(def), sharing, refs, univs); - Ok(constant) -} - -/// Apply the compiler's sharing under `limits` to an axiom payload. -#[allow(clippy::needless_pass_by_value)] -pub fn apply_sharing_to_axiom_with_limits( - limits: &ExactSharingLimits, - ax: Axiom, - refs: Vec
, - univs: Vec>, -) -> Result { - let (roots, sharing) = share_roots(limits, std::slice::from_ref(&ax.typ))?; - let ax = - Axiom { is_unsafe: ax.is_unsafe, lvls: ax.lvls, typ: roots[0].clone() }; - let constant = - Constant::with_tables(ConstantInfo::Axio(ax), sharing, refs, univs); - Ok(constant) -} - -/// Apply the compiler's sharing under `limits` to a quotient payload. -#[allow(clippy::needless_pass_by_value)] -pub fn apply_sharing_to_quotient_with_limits( - limits: &ExactSharingLimits, - quot: Quotient, - refs: Vec
, - univs: Vec>, -) -> Result { - let (roots, sharing) = share_roots(limits, std::slice::from_ref(".typ))?; - let quot = - Quotient { kind: quot.kind, lvls: quot.lvls, typ: roots[0].clone() }; - let constant = - Constant::with_tables(ConstantInfo::Quot(quot), sharing, refs, univs); - Ok(constant) -} - -/// Apply the compiler's sharing under `limits` to a recursor payload. -pub fn apply_sharing_to_recursor_with_limits( - limits: &ExactSharingLimits, - rec: Recursor, - refs: Vec
, - univs: Vec>, -) -> Result { - // The roots: typ, then every rule rhs. - let mut exprs = vec![rec.typ.clone()]; - for rule in &rec.rules { - exprs.push(rule.rhs.clone()); - } - - let (roots, sharing) = share_roots(limits, &exprs)?; - let typ = roots[0].clone(); - let rules: Vec = rec - .rules - .into_iter() - .zip(roots.into_iter().skip(1)) - .map(|(r, rhs)| RecursorRule { fields: r.fields, rhs }) - .collect(); - - let rec = Recursor { - k: rec.k, - is_unsafe: rec.is_unsafe, - lvls: rec.lvls, - params: rec.params, - indices: rec.indices, - motives: rec.motives, - minors: rec.minors, - typ, - rules, - }; - let constant = - Constant::with_tables(ConstantInfo::Recr(rec), sharing, refs, univs); - Ok(constant) -} - -/// Apply the compiler's sharing under `limits` to a mutual block: one sharing -/// table for the roots of every member, in member order. -pub fn apply_sharing_to_mutual_block_with_limits( - limits: &ExactSharingLimits, - mut_consts: Vec, - refs: Vec
, - univs: Vec>, -) -> Result { - // The roots of every member, in member order. - let mut all_exprs: Vec> = Vec::new(); - let mut layout: Vec<(MutConstKind, Vec)> = Vec::new(); - - for mc in &mut_consts { - let (kind, indices) = match mc { - IxonMutConst::Defn(def) => { - let start = all_exprs.len(); - all_exprs.push(def.typ.clone()); - all_exprs.push(def.value.clone()); - (MutConstKind::Defn, vec![start, start + 1]) - }, - IxonMutConst::Indc(ind) => { - let start = all_exprs.len(); - all_exprs.push(ind.typ.clone()); - let mut indices = vec![start]; - for ctor in &ind.ctors { - indices.push(all_exprs.len()); - all_exprs.push(ctor.typ.clone()); - } - (MutConstKind::Indc, indices) - }, - IxonMutConst::Recr(rec) => { - let start = all_exprs.len(); - all_exprs.push(rec.typ.clone()); - let mut indices = vec![start]; - for rule in &rec.rules { - indices.push(all_exprs.len()); - all_exprs.push(rule.rhs.clone()); - } - (MutConstKind::Recr, indices) - }, - }; - layout.push((kind, indices)); - } - - // One sharing table for the whole block. - let (rewritten, sharing) = share_roots(limits, &all_exprs)?; - - // Rebuild the constants with rewritten expressions - let mut new_consts = Vec::with_capacity(mut_consts.len()); - for (i, mc) in mut_consts.into_iter().enumerate() { - let (kind, indices) = &layout[i]; - let new_mc = match (kind, mc) { - (MutConstKind::Defn, IxonMutConst::Defn(def)) => { - IxonMutConst::Defn(Definition { - kind: def.kind, - safety: def.safety, - lvls: def.lvls, - typ: rewritten[indices[0]].clone(), - value: rewritten[indices[1]].clone(), - }) - }, - (MutConstKind::Indc, IxonMutConst::Indc(ind)) => { - let new_ctors: Vec = ind - .ctors - .into_iter() - .enumerate() - .map(|(ci, ctor)| Constructor { - is_unsafe: ctor.is_unsafe, - lvls: ctor.lvls, - cidx: ctor.cidx, - params: ctor.params, - fields: ctor.fields, - typ: rewritten[indices[ci + 1]].clone(), - }) - .collect(); - IxonMutConst::Indc(Inductive { - is_unsafe: ind.is_unsafe, - lvls: ind.lvls, - params: ind.params, - indices: ind.indices, - typ: rewritten[indices[0]].clone(), - ctors: new_ctors, - }) - }, - (MutConstKind::Recr, IxonMutConst::Recr(rec)) => { - let new_rules: Vec = rec - .rules - .into_iter() - .enumerate() - .map(|(ri, rule)| RecursorRule { - fields: rule.fields, - rhs: rewritten[indices[ri + 1]].clone(), - }) - .collect(); - IxonMutConst::Recr(Recursor { - k: rec.k, - is_unsafe: rec.is_unsafe, - lvls: rec.lvls, - params: rec.params, - indices: rec.indices, - motives: rec.motives, - minors: rec.minors, - typ: rewritten[indices[0]].clone(), - rules: new_rules, - }) - }, - _ => unreachable!("layout mismatch"), - }; - new_consts.push(new_mc); - } - - let constant = - Constant::with_tables(ConstantInfo::Muts(new_consts), sharing, refs, univs); - Ok(constant) -} - -/// Helper enum for tracking mutual constant layout during sharing. -#[derive(Clone, Copy)] -enum MutConstKind { - Defn, - Indc, - Recr, -} - -// =========================================================================== -// Constant compilation -// =========================================================================== - -/// Compile a Definition. -/// Arena persists across type + value within a constant. -pub fn compile_definition( - def: &Def, - mut_ctx: &MutCtx, - ctx_addrs: &[Address], - cache: &mut BlockCache, - stt: &CompileState, -) -> Result<(Definition, ConstantMeta), CompileError> { - cache.compiling = Some(def.name.clone()); - let univ_params = &def.level_params; - - // Compile type expression (arena grows) - let typ = compile_expr(&def.typ, univ_params, mut_ctx, cache, stt)?; - let type_root = *cache.arena_roots.last().expect("missing type arena root"); - - // Compile value expression (arena continues growing) - let value = compile_expr(&def.value, univ_params, mut_ctx, cache, stt)?; - let value_root = *cache.arena_roots.last().expect("missing value arena root"); - - // Take the arena and the level-spelling channels (canonicity §10.6), clear - // for next constant - let arena = std::mem::take(&mut cache.arena); - let meta_univs: Vec> = - std::mem::take(&mut cache.meta_univs).into_iter().collect(); - let univ_patches = std::mem::take(&mut cache.univ_patches); - cache.arena_roots.clear(); - cache.exprs.clear(); - - let name_addr = compile_name(&def.name, stt)?; - let lvl_addrs: Vec
= univ_params - .iter() - .map(|n| compile_name(n, stt)) - .collect::>()?; - let all_addrs: Vec
= - def.all.iter().map(|n| compile_name(n, stt)).collect::>()?; - let ctx_addrs: Vec
= ctx_addrs.to_vec(); - - let data = Definition { - kind: def.kind, - safety: def.safety, - lvls: def.level_params.len() as u64, - typ, - value, - }; - - let mut meta = ConstantMeta::new(ConstantMetaInfo::Def { - name: name_addr, - lvls: lvl_addrs, - all: all_addrs, - ctx: ctx_addrs, - arena, - type_root, - value_root, - }); - meta.meta_univs = meta_univs; - meta.univ_patches = univ_patches; - stt.record_hint(&def.name, Some(def.hints)); - - Ok((data, meta)) -} - -/// Compile a RecursorRule. -fn compile_recursor_rule( - rule: &LeanRecursorRule, - univ_params: &[Name], - mut_ctx: &MutCtx, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result<(RecursorRule, Address), CompileError> { - let rhs = compile_expr(&rule.rhs, univ_params, mut_ctx, cache, stt)?; - let ctor_addr = compile_name(&rule.ctor, stt)?; - let fields = nat_to_u64(&rule.n_fields, "n_fields too large")?; - - Ok((RecursorRule { fields, rhs }, ctor_addr)) -} - -/// Compile a Recursor. -/// Arena grows across type and all rule RHS expressions. -pub fn compile_recursor( - rec: &Rec, - mut_ctx: &MutCtx, - ctx_addrs: &[Address], - cache: &mut BlockCache, - stt: &CompileState, -) -> Result<(Recursor, ConstantMeta), CompileError> { - cache.compiling = Some(rec.cnst.name.clone()); - let univ_params = &rec.cnst.level_params; - - // Compile type expression - let typ = compile_expr(&rec.cnst.typ, univ_params, mut_ctx, cache, stt)?; - let type_root = - *cache.arena_roots.last().expect("missing recursor type arena root"); - - let mut rules = Vec::with_capacity(rec.rules.len()); - let mut rule_addrs = Vec::new(); - let mut rule_roots = Vec::new(); - for rule in &rec.rules { - let (r, ctor_addr) = - compile_recursor_rule(rule, univ_params, mut_ctx, cache, stt)?; - rule_roots - .push(*cache.arena_roots.last().expect("missing rule arena root")); - rule_addrs.push(ctor_addr); - rules.push(r); - } - - // Take the arena and the level-spelling channels (canonicity §10.6), clear - // for next constant: they belong to this recursor's meta. - let arena = std::mem::take(&mut cache.arena); - let meta_univs: Vec> = - std::mem::take(&mut cache.meta_univs).into_iter().collect(); - let univ_patches = std::mem::take(&mut cache.univ_patches); - cache.arena_roots.clear(); - cache.exprs.clear(); - - let name_addr = compile_name(&rec.cnst.name, stt)?; - let lvl_addrs: Vec
= univ_params - .iter() - .map(|n| compile_name(n, stt)) - .collect::>()?; - - let data = Recursor { - k: rec.k, - is_unsafe: rec.is_unsafe, - lvls: univ_params.len() as u64, - params: nat_to_u64(&rec.num_params, "num_params too large")?, - indices: nat_to_u64(&rec.num_indices, "num_indices too large")?, - motives: nat_to_u64(&rec.num_motives, "num_motives too large")?, - minors: nat_to_u64(&rec.num_minors, "num_minors too large")?, - typ, - rules, - }; - - let all_addrs: Vec
= - rec.all.iter().map(|n| compile_name(n, stt)).collect::>()?; - let ctx_addrs: Vec
= ctx_addrs.to_vec(); - - let mut meta = ConstantMeta::new(ConstantMetaInfo::Rec { - name: name_addr, - lvls: lvl_addrs, - rules: rule_addrs, - all: all_addrs, - ctx: ctx_addrs, - arena, - type_root, - rule_roots, - }); - meta.meta_univs = meta_univs; - meta.univ_patches = univ_patches; - - Ok((data, meta)) -} - -/// Compile a Constructor. -/// Each constructor gets its own arena. -fn compile_constructor( - ctor: &ConstructorVal, - mut_ctx: &MutCtx, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result<(Constructor, ConstantMeta), CompileError> { - cache.compiling = Some(ctor.cnst.name.clone()); - let univ_params = &ctor.cnst.level_params; - - let typ = compile_expr(&ctor.cnst.typ, univ_params, mut_ctx, cache, stt)?; - let type_root = - *cache.arena_roots.last().expect("missing ctor type arena root"); - - // Take the arena and the level-spelling channels (canonicity §10.6) for - // this constructor: the decompiler's ctor-scoped window installs them per - // constructor. - let arena = std::mem::take(&mut cache.arena); - let meta_univs: Vec> = - std::mem::take(&mut cache.meta_univs).into_iter().collect(); - let univ_patches = std::mem::take(&mut cache.univ_patches); - cache.arena_roots.clear(); - cache.exprs.clear(); - - let name_addr = compile_name(&ctor.cnst.name, stt)?; - let lvl_addrs: Vec
= univ_params - .iter() - .map(|n| compile_name(n, stt)) - .collect::>()?; - let induct_addr = compile_name(&ctor.induct, stt)?; - - let data = Constructor { - is_unsafe: ctor.is_unsafe, - lvls: univ_params.len() as u64, - cidx: nat_to_u64(&ctor.cidx, "cidx too large")?, - params: nat_to_u64(&ctor.num_params, "ctor num_params too large")?, - fields: nat_to_u64(&ctor.num_fields, "num_fields too large")?, - typ, - }; - - let mut meta = ConstantMeta::new(ConstantMetaInfo::Ctor { - name: name_addr, - lvls: lvl_addrs, - induct: induct_addr, - arena, - type_root, - }); - meta.meta_univs = meta_univs; - meta.univ_patches = univ_patches; - - Ok((data, meta)) -} - -/// Compile an Inductive. -/// The inductive type gets its own arena. Each constructor gets its own arena -/// via compile_constructor. No CtorMeta duplication — ConstantMeta::Indc only -/// stores constructor name addresses. -pub fn compile_inductive( - ind: &Ind, - mut_ctx: &MutCtx, - ctx_addrs: &[Address], - cache: &mut BlockCache, - stt: &CompileState, -) -> Result<(Inductive, ConstantMeta, Vec), CompileError> { - cache.compiling = Some(ind.ind.cnst.name.clone()); - let univ_params = &ind.ind.cnst.level_params; - - // Compile inductive type - let typ = compile_expr(&ind.ind.cnst.typ, univ_params, mut_ctx, cache, stt)?; - let type_root = - *cache.arena_roots.last().expect("missing indc type arena root"); - - // Take the arena and the level-spelling channels (canonicity §10.6) of the - // inductive's own type; each constructor takes its own in - // `compile_constructor` below. - let indc_arena = std::mem::take(&mut cache.arena); - let indc_meta_univs: Vec> = - std::mem::take(&mut cache.meta_univs).into_iter().collect(); - let indc_univ_patches = std::mem::take(&mut cache.univ_patches); - cache.arena_roots.clear(); - cache.exprs.clear(); - - let mut ctors = Vec::with_capacity(ind.ctors.len()); - let mut ctor_const_metas = Vec::new(); - let mut ctor_name_addrs = Vec::new(); - for ctor in &ind.ctors { - let (c, m) = compile_constructor(ctor, mut_ctx, cache, stt)?; - let ctor_name_addr = compile_name(&ctor.cnst.name, stt)?; - ctor_name_addrs.push(ctor_name_addr); - ctor_const_metas.push(m); - ctors.push(c); - } - - let name_addr = compile_name(&ind.ind.cnst.name, stt)?; - let lvl_addrs: Vec
= univ_params - .iter() - .map(|n| compile_name(n, stt)) - .collect::>()?; - - let data = Inductive { - is_unsafe: ind.ind.is_unsafe, - lvls: univ_params.len() as u64, - params: nat_to_u64(&ind.ind.num_params, "inductive num_params too large")?, - indices: nat_to_u64( - &ind.ind.num_indices, - "inductive num_indices too large", - )?, - typ, - ctors, - }; - - let all_addrs: Vec
= ind - .ind - .all - .iter() - .map(|n| compile_name(n, stt)) - .collect::>()?; - let ctx_addrs: Vec
= ctx_addrs.to_vec(); - - let mut meta = ConstantMeta::new(ConstantMetaInfo::Indc { - name: name_addr, - lvls: lvl_addrs, - ctors: ctor_name_addrs, - all: all_addrs, - ctx: ctx_addrs, - arena: indc_arena, - type_root, - }); - meta.meta_univs = indc_meta_univs; - meta.univ_patches = indc_univ_patches; - - Ok((data, meta, ctor_const_metas)) -} - -/// Compile an Axiom. -fn compile_axiom( - val: &AxiomVal, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result<(Axiom, ConstantMeta), CompileError> { - cache.compiling = Some(val.cnst.name.clone()); - let univ_params = &val.cnst.level_params; - - let typ = - compile_expr(&val.cnst.typ, univ_params, &MutCtx::default(), cache, stt)?; - let type_root = - *cache.arena_roots.last().expect("missing axiom type arena root"); - - // Take the arena and the level-spelling channels (canonicity §10.6) onto - // this axiom's meta, so they do not leak to the next constant. - let arena = std::mem::take(&mut cache.arena); - let meta_univs: Vec> = - std::mem::take(&mut cache.meta_univs).into_iter().collect(); - let univ_patches = std::mem::take(&mut cache.univ_patches); - cache.arena_roots.clear(); - cache.exprs.clear(); - - let name_addr = compile_name(&val.cnst.name, stt)?; - let lvl_addrs: Vec
= univ_params - .iter() - .map(|n| compile_name(n, stt)) - .collect::>()?; - - let data = - Axiom { is_unsafe: val.is_unsafe, lvls: univ_params.len() as u64, typ }; - - let mut meta = ConstantMeta::new(ConstantMetaInfo::Axio { - name: name_addr, - lvls: lvl_addrs, - arena, - type_root, - }); - meta.meta_univs = meta_univs; - meta.univ_patches = univ_patches; - - Ok((data, meta)) -} - -/// Compile a Quotient. -fn compile_quotient( - val: &QuotVal, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result<(Quotient, ConstantMeta), CompileError> { - cache.compiling = Some(val.cnst.name.clone()); - let univ_params = &val.cnst.level_params; - - let typ = - compile_expr(&val.cnst.typ, univ_params, &MutCtx::default(), cache, stt)?; - let type_root = - *cache.arena_roots.last().expect("missing quot type arena root"); - - // Take the arena and the level-spelling channels (canonicity §10.6) onto - // this quotient's meta, as in `compile_axiom`. - let arena = std::mem::take(&mut cache.arena); - let meta_univs: Vec> = - std::mem::take(&mut cache.meta_univs).into_iter().collect(); - let univ_patches = std::mem::take(&mut cache.univ_patches); - cache.arena_roots.clear(); - cache.exprs.clear(); - - let name_addr = compile_name(&val.cnst.name, stt)?; - let lvl_addrs: Vec
= univ_params - .iter() - .map(|n| compile_name(n, stt)) - .collect::>()?; - - let data = Quotient { kind: val.kind, lvls: univ_params.len() as u64, typ }; - - let mut meta = ConstantMeta::new(ConstantMetaInfo::Quot { - name: name_addr, - lvls: lvl_addrs, - arena, - type_root, - }); - meta.meta_univs = meta_univs; - meta.univ_patches = univ_patches; - - Ok((data, meta)) -} - -// =========================================================================== -// Mutual block compilation -// =========================================================================== - -/// Result of compiling a mutual block. -pub struct CompiledMutualBlock { - /// The compiled Constant - pub constant: Constant, - /// Content-addressed hash - pub addr: Address, - /// The serialization `addr` hashes - pub bytes: Vec, -} - -/// Compile a mutual block with block-level sharing under `limits`. -/// Returns the Constant and its content-addressed hash. -pub fn compile_mutual_block( - limits: &ExactSharingLimits, - mut_consts: Vec, - refs: Vec
, - univs: Vec>, -) -> Result { - // One sharing table across all expressions of the mutual block. - let constant = - apply_sharing_to_mutual_block_with_limits(limits, mut_consts, refs, univs)?; - - let mut bytes = Vec::new(); - constant.put(&mut bytes); - let addr = Address::hash(&bytes); - - Ok(CompiledMutualBlock { constant, addr, bytes }) -} - -/// Create Inductive from InductiveVal and Env. -pub fn mk_indc( - ind: &InductiveVal, - env: &Arc, -) -> Result { - let mut ctors = Vec::with_capacity(ind.ctors.len()); - for ctor_name in &ind.ctors { - if let Some(LeanConstantInfo::CtorInfo(c)) = - env.as_ref().get(ctor_name).as_deref() - { - ctors.push(c.clone()); - } else { - return Err(CompileError::MissingConstant { - name: ctor_name.pretty(), - caller: "mk_indc(ctor_lookup)".into(), - }); - } - } - Ok(Ind { ind: ind.clone(), ctors }) -} - -/// The declared flat position of a recursor in its family: `X.rec` of the -/// `i`-th member of `RecursorVal.all` is at `i`, and the nested auxiliary -/// recursor `all[0].rec_N` (`N >= 1` in plain decimal digits) at -/// `all.len() + N - 1`; `u64::MAX` otherwise, and for a position that does -/// not fit below it. Mirrors Lean `recursorFamilyPosition`. -pub fn recursor_family_position(rec: &ix_common::env::RecursorVal) -> u64 { - let NameData::Str(parent, last, _) = rec.cnst.name.as_data() else { - return u64::MAX; - }; - if last == "rec" { - return rec - .all - .iter() - .position(|n| n == parent) - .map_or(u64::MAX, |i| i as u64); - } - if let Some(digits) = last.strip_prefix("rec_") - && !digits.is_empty() - && digits.bytes().all(|b| b.is_ascii_digit()) - && let Ok(k) = digits.parse::() - && k >= 1 - && rec.all.first() == Some(parent) - && let Some(pos) = (rec.all.len() as u64).checked_add(k - 1) - && pos < u64::MAX - { - return pos; - } - u64::MAX -} - -/// Lean's name order, structural: a name's prefix first, then its last -/// component, a number before a string (`Lean.Name.cmp`). It reads no hash. -/// Mirrors Lean `Ix.Name.leanCmp`. -pub fn lean_name_cmp(a: &Name, b: &Name) -> Ordering { - match (a.as_data(), b.as_data()) { - (NameData::Anonymous(_), NameData::Anonymous(_)) => Ordering::Equal, - (NameData::Anonymous(_), _) | (NameData::Num(..), NameData::Str(..)) => { - Ordering::Less - }, - (_, NameData::Anonymous(_)) | (NameData::Str(..), NameData::Num(..)) => { - Ordering::Greater - }, - (NameData::Num(p1, i1, _), NameData::Num(p2, i2, _)) => { - lean_name_cmp(p1, p2).then_with(|| i1.cmp(i2)) - }, - (NameData::Str(p1, s1, _), NameData::Str(p2, s2, _)) => { - lean_name_cmp(p1, p2).then_with(|| s1.cmp(s2)) - }, - } -} - -/// A member's position in its Lean block: its index in the block's `all`, -/// or `all.len()` when `all` does not list it (only on input that is not -/// Lean's). Mirrors Lean `Ix.blockPosition`. -pub fn block_position(all: &[Name], name: &Name) -> u64 { - all.iter().position(|n| n == name).map_or(all.len() as u64, |p| p as u64) -} - -/// The member order of a block: Lean's order (Lean -/// `Ix.sourceOrderLt`). An inductive is at its position in Lean's `all`, a -/// recursor at its family's declared position (`recursor_family_position`), -/// a definition at its position in its Lean `all` (restricted to the block). -/// The order of a block is the source's, as the order of an inductive's -/// constructors is, and alpha-equal members are never merged. Members whose -/// positions and names tie (only on input that is not Lean's) keep their -/// order in `cs`. -pub fn source_members<'a>(cs: &[&'a MutConst]) -> Vec<&'a MutConst> { - fn position(c: &MutConst) -> u64 { - match c { - MutConst::Indc(i) => block_position(&i.ind.all, &i.ind.cnst.name), - MutConst::Recr(r) => recursor_family_position(r), - MutConst::Defn(d) => block_position(&d.all, &d.name), - } - } - let mut members: Vec<&'a MutConst> = cs.to_vec(); - members.sort_by(|a, b| { - position(a) - .cmp(&position(b)) - .then_with(|| lean_name_cmp(&a.name(), &b.name())) - }); - members -} - -// =========================================================================== -// Main compilation entry points -// =========================================================================== - -/// Compile a single definition, theorem or opaque standalone, storing and -/// registering nothing: its address, metadata, serialized bytes and constant -/// (the caller publishes them). -pub(crate) fn compile_single_def_parts( - name: &Name, - def: &Def, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result<(Address, ConstantMeta, Vec, Constant), CompileError> { - let _t0 = std::time::Instant::now(); - let mut_ctx = MutConst::single_ctx(def.name.clone()); - preseed_expr_tables( - &[ - (&def.typ, def.level_params.as_slice()), - (&def.value, def.level_params.as_slice()), - ], - &mut_ctx, - cache, - stt, - "compile_single_def", - )?; - let ctx_addrs: Vec
= ctx_to_all(&mut_ctx) - .iter() - .map(|n| compile_name(n, stt)) - .collect::>()?; - let (data, meta) = compile_definition(def, &mut_ctx, &ctx_addrs, cache, stt)?; - let _t_compile = _t0.elapsed(); - let n_unique_exprs = cache.exprs.len(); - let refs: Vec
= cache.refs.iter().cloned().collect(); - let univs: Vec> = cache.univs.iter().cloned().collect(); - let _t1 = std::time::Instant::now(); - let constant = apply_sharing_to_definition_with_limits( - &stt.sharing_limits, - data, - refs, - univs, - )?; - let _t_sharing = _t1.elapsed(); - let _t2 = std::time::Instant::now(); - let mut bytes = Vec::new(); - constant.put(&mut bytes); - let serialized_size = bytes.len(); - let addr = Address::hash(&bytes); - let _t_serial = _t2.elapsed(); - if *IX_TIMING && _t0.elapsed().as_secs_f32() > 1.0 { - eprintln!( - "[slow_single] {:?} compile={:.2}s sharing={:.2}s serial={:.2}s unique_exprs={} refs={} bytes={}", - name.pretty(), - _t_compile.as_secs_f32(), - _t_sharing.as_secs_f32(), - _t_serial.as_secs_f32(), - n_unique_exprs, - cache.refs.len(), - serialized_size, - ); - } - Ok((addr, meta, bytes, constant)) -} - -/// Compile a single constant together with the rest of its block `all`. -pub fn compile_const( - name: &Name, - all: &NameSet, - lean_env: &Arc, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result { - if let Some(cached) = stt.resolve_addr(name) { - return Ok(cached); - } - - // `lean_env.get(name)` is a plain `Option<&ConstantInfo>` from an - // `FxHashMap` (see `Env` alias in env.rs) — there's no guard to - // release, so we clone the value and let the borrow expire on the - // next line through NLL. - let cnst: LeanConstantInfo = lean_env - .get(name) - .ok_or_else(|| CompileError::MissingConstant { - name: name.pretty(), - caller: "compile_const".into(), - })? - .clone(); - - // Helper: compile a single definition/theorem/opaque (non-mutual case). - fn compile_single_def( - name: &Name, - def: &Def, - cache: &mut BlockCache, - stt: &CompileState, - ) -> Result { - let (addr, meta, bytes, constant) = - compile_single_def_parts(name, def, cache, stt)?; - stt.store_const_bytes(addr.clone(), bytes, constant)?; - // Claim before registering: a refused claim leaves the owner's - // `Named` entry as it was. - stt.claim_and_register_compiled(name, Named::new(addr.clone(), meta))?; - Ok(addr) - } - - // Handle each constant type - let addr = match &cnst { - LeanConstantInfo::DefnInfo(val) => { - if all.len() == 1 { - compile_single_def(name, &Def::mk_defn(val), cache, stt)? - } else { - compile_mutual(name, all, lean_env, cache, stt)? - } - }, - - LeanConstantInfo::ThmInfo(val) => { - if all.len() == 1 { - compile_single_def(name, &Def::mk_theo(val), cache, stt)? - } else { - compile_mutual(name, all, lean_env, cache, stt)? - } - }, - - LeanConstantInfo::OpaqueInfo(val) => { - if all.len() == 1 { - compile_single_def(name, &Def::mk_opaq(val), cache, stt)? - } else { - compile_mutual(name, all, lean_env, cache, stt)? - } - }, - - LeanConstantInfo::AxiomInfo(val) => { - preseed_expr_tables( - &[(&val.cnst.typ, val.cnst.level_params.as_slice())], - &MutCtx::default(), - cache, - stt, - "compile_axiom", - )?; - let (data, meta) = compile_axiom(val, cache, stt)?; - let refs: Vec
= cache.refs.iter().cloned().collect(); - let univs: Vec> = cache.univs.iter().cloned().collect(); - let constant = apply_sharing_to_axiom_with_limits( - &stt.sharing_limits, - data, - refs, - univs, - )?; - let mut bytes = Vec::new(); - constant.put(&mut bytes); - let addr = Address::hash(&bytes); - stt.store_const_bytes(addr.clone(), bytes, constant)?; - stt.claim_and_register_compiled(name, Named::new(addr.clone(), meta))?; - addr - }, - - LeanConstantInfo::QuotInfo(val) => { - preseed_expr_tables( - &[(&val.cnst.typ, val.cnst.level_params.as_slice())], - &MutCtx::default(), - cache, - stt, - "compile_quotient", - )?; - let (data, meta) = compile_quotient(val, cache, stt)?; - let refs: Vec
= cache.refs.iter().cloned().collect(); - let univs: Vec> = cache.univs.iter().cloned().collect(); - let constant = apply_sharing_to_quotient_with_limits( - &stt.sharing_limits, - data, - refs, - univs, - )?; - let mut bytes = Vec::new(); - constant.put(&mut bytes); - let addr = Address::hash(&bytes); - stt.store_const_bytes(addr.clone(), bytes, constant)?; - stt.claim_and_register_compiled(name, Named::new(addr.clone(), meta))?; - addr - }, - - LeanConstantInfo::InductInfo(_) => { - compile_mutual(name, all, lean_env, cache, stt)? - }, - - LeanConstantInfo::RecInfo(val) => { - if all.len() == 1 { - let mut_ctx = MutConst::single_ctx(val.cnst.name.clone()); - let mut exprs = vec![(&val.cnst.typ, val.cnst.level_params.as_slice())]; - for rule in &val.rules { - exprs.push((&rule.rhs, val.cnst.level_params.as_slice())); - } - preseed_expr_tables(&exprs, &mut_ctx, cache, stt, "compile_recursor")?; - let ctx_addrs: Vec
= ctx_to_all(&mut_ctx) - .iter() - .map(|n| compile_name(n, stt)) - .collect::>()?; - let (data, meta) = - compile_recursor(val, &mut_ctx, &ctx_addrs, cache, stt)?; - let refs: Vec
= cache.refs.iter().cloned().collect(); - let univs: Vec> = cache.univs.iter().cloned().collect(); - let constant = apply_sharing_to_recursor_with_limits( - &stt.sharing_limits, - data, - refs, - univs, - )?; - let mut bytes = Vec::new(); - constant.put(&mut bytes); - let addr = Address::hash(&bytes); - stt.store_const_bytes(addr.clone(), bytes, constant)?; - stt - .claim_and_register_compiled(name, Named::new(addr.clone(), meta))?; - addr - } else { - compile_mutual(name, all, lean_env, cache, stt)? - } - }, - - LeanConstantInfo::CtorInfo(val) => { - // Constructors are compiled as part of their inductive - if let Some(LeanConstantInfo::InductInfo(_)) = - lean_env.get(&val.induct).as_deref() - { - compile_mutual(name, all, lean_env, cache, stt)? - } else { - return Err(CompileError::MissingConstant { - name: val.induct.pretty(), - caller: "compile_const(ctor_induct)".into(), - }); - } - }, - }; - - stt.claim_compiled_name(name, &addr)?; - Ok(addr) -} - -/// Compile a mutual block. -fn compile_mutual( - name: &Name, - all: &NameSet, - lean_env: &Arc, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result { - // Collect all constants in the mutual block - let mut cs = Vec::new(); - for n in all { - // Clone out of the `EnvEntry` guard so the block owns its constants - // and no env borrow is held across the compile below. - let const_info = match lean_env.get(n).map(|e| e.cloned()) { - Some(c) => c, - None => { - return Err(CompileError::MissingConstant { - name: n.pretty(), - caller: "compile_mutual".into(), - }); - }, - }; - let mut_const = match &const_info { - LeanConstantInfo::InductInfo(val) => { - MutConst::Indc(mk_indc(val, lean_env)?) - }, - LeanConstantInfo::DefnInfo(val) => MutConst::Defn(Def::mk_defn(val)), - LeanConstantInfo::OpaqueInfo(val) => MutConst::Defn(Def::mk_opaq(val)), - LeanConstantInfo::ThmInfo(val) => MutConst::Defn(Def::mk_theo(val)), - LeanConstantInfo::RecInfo(val) => MutConst::Recr(val.clone()), - _ => continue, - }; - cs.push(mut_const); - } - - // The order of a block is the source's. - let members: Vec<&MutConst> = cs.iter().collect(); - let ordered_members = source_members(&members); - let mut_ctx = MutConst::ctx(&ordered_members); - - let mut exprs = Vec::new(); - for cnst in &cs { - collect_mut_const_exprs(cnst, &mut exprs); - } - preseed_expr_tables(&exprs, &mut_ctx, cache, stt, "compile_mutual")?; - - // Compile each constant. The block-context address list is identical - // for every member (a deterministic function of `mut_ctx`), so it is - // computed once here instead of sorted+re-interned per member. - let mut ixon_mutuals = Vec::new(); - let mut all_metas: FxHashMap = FxHashMap::default(); - let ctx_addrs: Vec
= ctx_to_all(&mut_ctx) - .iter() - .map(|n| compile_name(n, stt)) - .collect::>()?; - - for cnst in &ordered_members { - match cnst { - MutConst::Defn(def) => { - let (data, meta) = - compile_definition(def, &mut_ctx, &ctx_addrs, cache, stt)?; - ixon_mutuals.push(IxonMutConst::Defn(data)); - all_metas.insert(def.name.clone(), meta); - }, - MutConst::Indc(ind) => { - let (data, meta, ctor_metas_vec) = - compile_inductive(ind, &mut_ctx, &ctx_addrs, cache, stt)?; - ixon_mutuals.push(IxonMutConst::Indc(data)); - // Register per-constructor ConstantMeta::Ctor entries - for (ctor, ctor_meta) in ind.ctors.iter().zip(ctor_metas_vec) { - all_metas.insert(ctor.cnst.name.clone(), ctor_meta); - } - all_metas.insert(ind.ind.cnst.name.clone(), meta); - }, - MutConst::Recr(rec) => { - let (data, meta) = - compile_recursor(rec, &mut_ctx, &ctx_addrs, cache, stt)?; - ixon_mutuals.push(IxonMutConst::Recr(data)); - all_metas.insert(rec.cnst.name.clone(), meta); - }, - } - } - - // Create mutual block with sharing - let refs: Vec
= cache.refs.iter().cloned().collect(); - let univs: Vec> = cache.univs.iter().cloned().collect(); - - // Singleton non-inductive: emit as a standalone `Defn`/`Recr` - // Constant instead of wrapping in `Muts(vec![one])`. Self-reference - // inside the body still uses `Expr::Rec(0, …)`, which the kernel - // resolves the same way for a single-member block. Eliminating the - // wrapper keeps the env structurally uniform (no degenerate Muts - // wrappers, no extra projection constants) and matches what - // `compile_single_def` produces for a non-mutual Lean Defn. - // - // Inductives are never unwrapped — their projection scheme requires - // the block. - if ixon_mutuals.len() == 1 - && !matches!(&ixon_mutuals[0], IxonMutConst::Indc(_)) - { - let single = ixon_mutuals.pop().unwrap(); - let limits = &stt.sharing_limits; - let standalone_constant = match single { - IxonMutConst::Defn(def) => { - apply_sharing_to_definition_with_limits(limits, def, refs, univs)? - }, - IxonMutConst::Recr(rec) => { - apply_sharing_to_recursor_with_limits(limits, rec, refs, univs)? - }, - IxonMutConst::Indc(_) => unreachable!(), - }; - let mut bytes = Vec::new(); - standalone_constant.put(&mut bytes); - let addr = Address::hash(&bytes); - - stt.store_const_bytes(addr.clone(), bytes, standalone_constant)?; - for cnst in &ordered_members { - let n = cnst.name(); - // `remove`, not `get().cloned()`: each name is consumed exactly - // once, and ConstantMeta is an arena-sized deep clone. - let meta = all_metas.remove(&n).unwrap_or_default(); - stt.claim_and_register_compiled(&n, Named::new(addr.clone(), meta))?; - } - return Ok(addr); - } - - let compiled = - compile_mutual_block(&stt.sharing_limits, ixon_mutuals, refs, univs)?; - let block_addr = compiled.addr.clone(); - - stt.store_const_bytes( - block_addr.clone(), - compiled.bytes, - compiled.constant, - )?; - // Preserve singleton grouping at the metadata boundary. - let class_ordering: Vec> = - ordered_members.iter().map(|cnst| vec![cnst.name()]).collect(); - for cnst in &ordered_members { - stt.register_block(cnst.name(), class_ordering.clone()); - } - - // Create projections for each constant: store their Ixon blobs and - // register their Named entries. - let mut requested_addr = None; - for (index, cnst) in ordered_members.iter().enumerate() { - let idx = index as u64; - let n = cnst.name(); - // `remove`: consumed once per name, so no deep clone is needed. - let meta = all_metas.remove(&n).unwrap_or_default(); - - let proj = match cnst { - MutConst::Defn(_) => defn_proj_constant(idx, block_addr.clone()), - MutConst::Indc(ind) => { - // Inductive projection - let indc_proj = indc_proj_constant(idx, block_addr.clone()); - let mut proj_bytes = Vec::new(); - indc_proj.put(&mut proj_bytes); - let proj_addr = Address::hash(&proj_bytes); - if n == *name { - requested_addr = Some(proj_addr.clone()); - } - stt.store_const_bytes(proj_addr.clone(), proj_bytes, indc_proj)?; - stt.claim_and_register_compiled( - &n, - Named::new(proj_addr.clone(), meta), - )?; - - // Constructor projections - for (cidx, ctor) in ind.ctors.iter().enumerate() { - let ctor_meta = all_metas.remove(&ctor.cnst.name).unwrap_or_default(); - let ctor_proj = - ctor_proj_constant(idx, cidx as u64, block_addr.clone()); - let mut ctor_bytes = Vec::new(); - ctor_proj.put(&mut ctor_bytes); - let ctor_addr = Address::hash(&ctor_bytes); - if ctor.cnst.name == *name { - requested_addr = Some(ctor_addr.clone()); - } - stt.store_const_bytes(ctor_addr.clone(), ctor_bytes, ctor_proj)?; - stt.claim_and_register_compiled( - &ctor.cnst.name, - Named::new(ctor_addr.clone(), ctor_meta), - )?; - } - - continue; - }, - MutConst::Recr(_) => recr_proj_constant(idx, block_addr.clone()), - }; - - let mut proj_bytes = Vec::new(); - proj.put(&mut proj_bytes); - let proj_addr = Address::hash(&proj_bytes); - if n == *name { - requested_addr = Some(proj_addr.clone()); - } - stt.store_const_bytes(proj_addr.clone(), proj_bytes, proj)?; - stt.claim_and_register_compiled(&n, Named::new(proj_addr.clone(), meta))?; - } - - // Register the synthetic Muts named entry for this block. `block_addr` - // stores an `IxonCI::Muts(...)` constant, but kernel ingress only - // discovers mutual blocks by scanning `ixon_env.named` for entries tagged - // `ConstantMetaInfo::Muts { all }` and routing them to - // `ingress_muts_block`. Without this entry, each member's projection-typed - // named entry falls through ingress silently and none of its content - // reaches the kernel env. - let first_name = ordered_members - .first() - .map(|c| c.name()) - .expect("compile_mutual invariant: at least one member"); - let muts_all: Vec> = ordered_members - .iter() - .map(|cnst| vec![Address::from_blake3_hash(*cnst.name().get_hash())]) - .collect(); - let muts_name = block_addr.muts_name(&first_name); - compile_name(&muts_name, stt)?; - stt.register_named( - muts_name, - Named::new( - block_addr.clone(), - ConstantMeta::new(ConstantMetaInfo::Muts { - all: muts_all, - aux_layout: None, - }), - ), - ); - - requested_addr.ok_or_else(|| CompileError::MissingConstant { - name: name.pretty(), - caller: "compile_mutual(result)".into(), - }) -} - -mod admission; -pub mod block_txn; -mod env; -pub mod expr_key; -pub mod ind_flags; -mod memory; -pub mod nat_conv; -mod publication; -pub(crate) mod validation; -pub use env::{ - compile_env, compile_env_with_options, compile_env_with_profile, -}; - -#[cfg(test)] -mod c6_flags_tests; - -#[cfg(test)] -mod source_recursor_tests; - -#[cfg(test)] -mod tests { - use super::*; - use ix_common::env::{BinderInfo, Expr as LeanExpr, Level}; - - #[test] - fn test_compile_univ_zero() { - let level = Level::zero(); - let mut cache = BlockCache::default(); - let univ = compile_univ(&level, &[], &mut cache).unwrap(); - assert!(matches!(univ.as_ref(), Univ::Zero)); - } - - #[test] - fn test_compile_univ_succ() { - let level = Level::succ(Level::zero()); - let mut cache = BlockCache::default(); - let univ = compile_univ(&level, &[], &mut cache).unwrap(); - match univ.as_ref() { - Univ::Succ(inner) => assert!(matches!(inner.as_ref(), Univ::Zero)), - _ => panic!("expected Succ"), - } - } - - #[test] - fn test_compile_univ_param() { - let name = Name::str(Name::anon(), "u".to_string()); - let level = Level::param(name.clone()); - let mut cache = BlockCache::default(); - let univ = compile_univ(&level, &[name], &mut cache).unwrap(); - assert!(matches!(univ.as_ref(), Univ::Var(0))); - } - - #[test] - fn test_collect_lean_telescope() { - let n = |s: &str| Name::str(Name::anon(), s.to_string()); - let f = LeanExpr::cnst(n("f"), vec![]); - let a1 = LeanExpr::bvar(Nat::from(0u64)); - let a2 = LeanExpr::bvar(Nat::from(1u64)); - let a3 = LeanExpr::bvar(Nat::from(2u64)); - let app = LeanExpr::app( - LeanExpr::app(LeanExpr::app(f.clone(), a1.clone()), a2.clone()), - a3.clone(), - ); - let (head, args) = collect_lean_telescope(&app); - assert_eq!(head.get_hash(), f.get_hash()); - assert_eq!(args.len(), 3); - assert_eq!(args[0].get_hash(), a1.get_hash()); - assert_eq!(args[1].get_hash(), a2.get_hash()); - assert_eq!(args[2].get_hash(), a3.get_hash()); - } - - #[test] - fn compile_univ_idx_refuses_growth_after_preseed() { - let mut cache = BlockCache::default(); - let zero = Level::zero(); - assert_eq!(compile_univ_idx(&zero, &[], &mut cache).unwrap(), (0, None)); - cache.univs_final = true; - // A preseeded canonical form still hits its entry. - assert_eq!(compile_univ_idx(&zero, &[], &mut cache).unwrap(), (0, None)); - // A canonical form the preseed missed is refused, as in Lean, in every - // build profile, not appended (which would shift every virtual patch - // index). - let one = Level::succ(Level::zero()); - match compile_univ_idx(&one, &[], &mut cache) { - Err(CompileError::InvalidMutualBlock { reason }) => { - assert!(reason.starts_with("preseed missed canonical form"), "{reason}") - }, - other => panic!("expected the V3 refusal, got {other:?}"), - } - assert_eq!(cache.univs.len(), 1, "the refused form was not interned"); - } - - /// A recursor's declared position is its family's flat order (`X.rec` at - /// its `all` position, `all0.rec_N` after the members), and a block of - /// recursors is laid out in it; malformed `rec_N` suffixes have no - /// position. - #[test] - fn recursor_family_position_lays_out_flat_order() { - use ix_common::env::{ConstantVal, RecursorVal}; - let nm = |s: &str| { - s.split('.').fold(Name::anon(), |p, c| Name::str(p, c.to_string())) - }; - let rec = |n: &str| { - MutConst::Recr(RecursorVal { - cnst: ConstantVal { - name: nm(n), - level_params: vec![], - typ: LeanExpr::sort(Level::zero()), - }, - all: vec![nm("A"), nm("B")], - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - num_motives: Nat::from(3u64), - num_minors: Nat::from(0u64), - rules: vec![], - k: false, - is_unsafe: false, - }) - }; - let pos = |n: &str| match rec(n) { - MutConst::Recr(r) => recursor_family_position(&r), - _ => unreachable!(), - }; - assert_eq!(pos("A.rec"), 0); - assert_eq!(pos("B.rec"), 1); - assert_eq!(pos("A.rec_1"), 2); - assert_eq!(pos("A.rec_3"), 4); - for odd in ["B.rec_1", "A.rec_0", "A.rec_", "A.rec_+1", "A.rec_1_0"] { - assert_eq!(pos(odd), u64::MAX, "{odd}"); - } - assert_eq!(pos("A.rec_18446744073709551615"), u64::MAX); - let (ar, br, a1) = (rec("A.rec"), rec("B.rec"), rec("A.rec_1")); - let members = source_members(&[&a1, &br, &ar]); - let order: Vec = - members.iter().map(|c| c.name().pretty()).collect(); - assert_eq!(order, ["A.rec", "B.rec", "A.rec_1"]); - } - - #[test] - fn test_compile_univ_max() { - let level = Level::max(Level::zero(), Level::succ(Level::zero())); - let mut cache = BlockCache::default(); - let univ = compile_univ(&level, &[], &mut cache).unwrap(); - match univ.as_ref() { - Univ::Max(a, b) => { - assert!(matches!(a.as_ref(), Univ::Zero)); - match b.as_ref() { - Univ::Succ(inner) => assert!(matches!(inner.as_ref(), Univ::Zero)), - _ => panic!("expected Succ"), - } - }, - _ => panic!("expected Max"), - } - } - - #[test] - fn test_store_string() { - let stt = CompileState::default(); - let addr1 = store_string("hello", &stt).unwrap(); - let addr2 = store_string("hello", &stt).unwrap(); - // Same content should give same address - assert_eq!(addr1, addr2); - // Check we can retrieve it - let bytes = stt.env.get_blob(&addr1).unwrap(); - assert_eq!(bytes, b"hello"); - } - - #[test] - fn test_store_nat() { - let stt = CompileState::default(); - let n = Nat::from(42u64); - let addr = store_nat(&n, &stt).unwrap(); - let bytes = stt.env.get_blob(&addr).unwrap(); - let n2 = Nat::from_le_bytes(&bytes); - assert_eq!(n, n2); - } - - #[test] - fn test_compile_name_anon() { - let stt = CompileState::default(); - let name = Name::anon(); - let addr = compile_name(&name, &stt).unwrap(); - // Name is stored in env.names, not blobs - let stored_name = stt.env.names.get(&addr).unwrap(); - assert_eq!(*stored_name, name); - } - - #[test] - fn test_compile_name_str() { - let stt = CompileState::default(); - let name = Name::str(Name::anon(), "foo".to_string()); - let addr = compile_name(&name, &stt).unwrap(); - // Name is stored in env.names - let stored_name = stt.env.names.get(&addr).unwrap(); - assert_eq!(*stored_name, name); - // String component should be in blobs - let foo_bytes = "foo".as_bytes(); - let foo_addr = Address::hash(foo_bytes); - assert!(stt.env.blobs.contains_key(&foo_addr)); - } - - #[test] - fn test_compile_expr_bvar() { - let stt = CompileState::default(); - let mut cache = BlockCache::default(); - let expr = LeanExpr::bvar(Nat::from(3u64)); - let result = - compile_expr(&expr, &[], &MutCtx::default(), &mut cache, &stt).unwrap(); - assert!(matches!(result.as_ref(), Expr::Var(3))); - } - - #[test] - fn expression_wire_bound_variable_limits() { - let stt = CompileState::default(); - let mut cache = BlockCache::default(); - let too_large = Nat::from_le_bytes(&[0, 0, 0, 0, 0, 0, 0, 0, 1]); - let source = LeanExpr::bvar(too_large); - let error = - compile_expr(&source, &[], &MutCtx::default(), &mut cache, &stt) - .expect_err("an oversized variable must not wrap"); - assert!(matches!(error, CompileError::UnsupportedExpr { .. })); - assert!(cache.exprs.is_empty()); - assert!(cache.arena.nodes.is_empty()); - let source = LeanExpr::bvar(Nat::from(u64::MAX)); - let result = - compile_expr(&source, &[], &MutCtx::default(), &mut cache, &stt).unwrap(); - assert!(matches!(result.as_ref(), Expr::Var(u64::MAX))); - } - - #[test] - fn expression_wire_projection_limits() { - let stt = CompileState::default(); - let mut cache = BlockCache::default(); - let name = Name::str(Name::anon(), "IndexBoundary".to_owned()); - let too_large = Nat::from_le_bytes(&[0, 0, 0, 0, 0, 0, 0, 0, 1]); - let source = - LeanExpr::proj(name.clone(), too_large, LeanExpr::bvar(Nat::from(0u64))); - let error = - compile_expr(&source, &[], &MutCtx::default(), &mut cache, &stt) - .expect_err("an oversized field must not wrap"); - assert!(matches!(error, CompileError::UnsupportedExpr { .. })); - assert!(cache.exprs.is_empty()); - assert!(cache.arena.nodes.is_empty()); - assert!(cache.refs.is_empty()); - assert!(stt.env.names.is_empty()); - stt.name_to_addr.insert(name.clone(), Address::hash(b"structure")); - let source = LeanExpr::proj( - name, - Nat::from(u64::MAX), - LeanExpr::bvar(Nat::from(0u64)), - ); - let result = - compile_expr(&source, &[], &MutCtx::default(), &mut cache, &stt).unwrap(); - assert!(matches!(result.as_ref(), Expr::Prj(_, u64::MAX, _))); - } - - #[test] - fn expression_wire_mutual_limits() { - let stt = CompileState::default(); - let mut cache = BlockCache::default(); - let name = Name::str(Name::anon(), "IndexBoundary".to_owned()); - let mut mutual = MutCtx::default(); - mutual - .insert(name.clone(), Nat::from_le_bytes(&[0, 0, 0, 0, 0, 0, 0, 0, 1])); - let source = LeanExpr::cnst(name.clone(), vec![]); - let error = compile_expr(&source, &[], &mutual, &mut cache, &stt) - .expect_err("an oversized mutual slot must not wrap"); - assert!(matches!(error, CompileError::UnsupportedExpr { .. })); - assert!(cache.exprs.is_empty()); - assert!(cache.arena.nodes.is_empty()); - mutual.insert(name, Nat::from(u64::MAX)); - let result = compile_expr(&source, &[], &mutual, &mut cache, &stt).unwrap(); - assert!(matches!(result.as_ref(), Expr::Rec(u64::MAX, _))); - } - - #[test] - fn test_compile_expr_sort() { - let stt = CompileState::default(); - let mut cache = BlockCache::default(); - let expr = LeanExpr::sort(Level::zero()); - let result = - compile_expr(&expr, &[], &MutCtx::default(), &mut cache, &stt).unwrap(); - match result.as_ref() { - Expr::Sort(idx) => { - assert_eq!(*idx, 0); - assert!(matches!( - cache.univs.get_index(0).unwrap().as_ref(), - Univ::Zero - )); - }, - _ => panic!("expected Sort"), - } - } - - #[test] - fn test_compile_expr_app() { - let stt = CompileState::default(); - let mut cache = BlockCache::default(); - let f = LeanExpr::bvar(Nat::from(0u64)); - let a = LeanExpr::bvar(Nat::from(1u64)); - let expr = LeanExpr::app(f, a); - let result = - compile_expr(&expr, &[], &MutCtx::default(), &mut cache, &stt).unwrap(); - match result.as_ref() { - Expr::App(f, a) => { - assert!(matches!(f.as_ref(), Expr::Var(0))); - assert!(matches!(a.as_ref(), Expr::Var(1))); - }, - _ => panic!("expected App"), - } - } - - #[test] - fn test_compile_expr_lam() { - let stt = CompileState::default(); - let mut cache = BlockCache::default(); - let ty = LeanExpr::sort(Level::zero()); - let body = LeanExpr::bvar(Nat::from(0u64)); - let expr = LeanExpr::lam(Name::anon(), ty, body, BinderInfo::Default); - let result = - compile_expr(&expr, &[], &MutCtx::default(), &mut cache, &stt).unwrap(); - match result.as_ref() { - Expr::Lam(contract, ty, body) => { - assert_eq!(*contract, ixon::contract::BinderContract::default()); - match ty.as_ref() { - Expr::Sort(idx) => { - assert_eq!(*idx, 0); - assert!(matches!( - cache.univs.get_index(0).unwrap().as_ref(), - Univ::Zero - )); - }, - _ => panic!("expected Sort for ty"), - } - assert!(matches!(body.as_ref(), Expr::Var(0))); - }, - _ => panic!("expected Lam"), - } - } - - #[test] - fn test_compile_expr_nat_lit() { - let stt = CompileState::default(); - let mut cache = BlockCache::default(); - let expr = LeanExpr::lit(Literal::NatVal(Nat::from(42u64))); - let result = - compile_expr(&expr, &[], &MutCtx::default(), &mut cache, &stt).unwrap(); - match result.as_ref() { - Expr::Nat(ref_idx) => { - let addr = cache.refs.get_index(*ref_idx as usize).unwrap(); - let bytes = stt.env.get_blob(addr).unwrap(); - let n = Nat::from_le_bytes(&bytes); - assert_eq!(n, Nat::from(42u64)); - }, - _ => panic!("expected Nat"), - } - } - - #[test] - fn test_compile_expr_str_lit() { - let stt = CompileState::default(); - let mut cache = BlockCache::default(); - let expr = LeanExpr::lit(Literal::StrVal("hello".to_string())); - let result = - compile_expr(&expr, &[], &MutCtx::default(), &mut cache, &stt).unwrap(); - match result.as_ref() { - Expr::Str(ref_idx) => { - let addr = cache.refs.get_index(*ref_idx as usize).unwrap(); - let bytes = stt.env.get_blob(addr).unwrap(); - assert_eq!(String::from_utf8(bytes).unwrap(), "hello"); - }, - _ => panic!("expected Str"), - } - } - - #[test] - fn test_compile_axiom() { - use ix_common::env::{AxiomVal, ConstantVal}; - - // Create a simple axiom: axiom myAxiom : Type - let name = Name::str(Name::anon(), "myAxiom".to_string()); - let typ = LeanExpr::sort(Level::succ(Level::zero())); // Type 0 - let cnst = ConstantVal { name: name.clone(), level_params: vec![], typ }; - let axiom = AxiomVal { cnst, is_unsafe: false }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(name.clone(), LeanConstantInfo::AxiomInfo(axiom)); - let lean_env = Arc::new(lean_env); - - let stt = CompileState::default(); - let mut cache = BlockCache::default(); - let mut all = NameSet::default(); - all.insert(name.clone()); - - let result = compile_const(&name, &all, &lean_env, &mut cache, &stt); - assert!(result.is_ok(), "compile_const failed: {:?}", result.err()); - - let addr = result.unwrap(); - assert!(stt.name_to_addr.contains_key(&name)); - assert!(stt.env.get_const(&addr).is_some()); - } - - #[test] - fn test_compile_simple_def() { - use ix_common::env::{ - ConstantVal, DefinitionSafety, DefinitionVal, ReducibilityHints, - }; - - // Create a simple definition: def myDef : Nat := 42 - let name = Name::str(Name::anon(), "myDef".to_string()); - let nat_name = Name::str(Name::anon(), "Nat".to_string()); - let typ = LeanExpr::cnst(nat_name.clone(), vec![]); - let value = LeanExpr::lit(Literal::NatVal(Nat::from(42u64))); - let cnst = ConstantVal { name: name.clone(), level_params: vec![], typ }; - let def = DefinitionVal { - cnst, - value, - hints: ReducibilityHints::Abbrev, - safety: DefinitionSafety::Safe, - all: vec![name.clone()], - }; - - let mut lean_env = LeanEnv::default(); - // Note: We also need Nat in the env for the reference to work, - // but for this test we just check the compile doesn't crash - lean_env.insert(name.clone(), LeanConstantInfo::DefnInfo(def)); - let lean_env = Arc::new(lean_env); - - let stt = CompileState::default(); - let mut cache = BlockCache::default(); - let mut all = NameSet::default(); - all.insert(name.clone()); - - // This will fail because nat_name isn't in name_to_addr, but let's see the error - let result = compile_const(&name, &all, &lean_env, &mut cache, &stt); - // We expect this to fail with MissingConstant for Nat - match result { - Err(CompileError::MissingConstant { name: missing, .. }) => { - assert!( - missing.contains("Nat"), - "Expected missing Nat, got: {}", - missing - ); - }, - Err(e) => panic!("Unexpected error: {:?}", e), - Ok(_) => panic!("Expected error for missing Nat reference"), - } - } - - #[test] - fn test_compile_self_referential_def() { - use ix_common::env::{ - ConstantInfo as LeanConstantInfo, ConstantVal, DefinitionSafety, - DefinitionVal, Env as LeanEnv, ReducibilityHints, - }; - use ixon::constant::ConstantInfo; - - // Create a self-referential definition (like a recursive function placeholder) - // def myDef : Type := myDef (this is silly but tests the mutual handling) - let name = Name::str(Name::anon(), "myDef".to_string()); - let typ = LeanExpr::sort(Level::succ(Level::zero())); // Type - let value = LeanExpr::cnst(name.clone(), vec![]); // self-reference - let cnst = ConstantVal { name: name.clone(), level_params: vec![], typ }; - let def = DefinitionVal { - cnst, - value, - hints: ReducibilityHints::Abbrev, - safety: DefinitionSafety::Safe, - all: vec![name.clone()], - }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(name.clone(), LeanConstantInfo::DefnInfo(def)); - let lean_env = Arc::new(lean_env); - - let stt = CompileState::default(); - let mut cache = BlockCache::default(); - let mut all = NameSet::default(); - all.insert(name.clone()); - - // This should work because it's a single self-referential def - let result = compile_const(&name, &all, &lean_env, &mut cache, &stt); - assert!(result.is_ok(), "compile_const failed: {:?}", result.err()); - - let addr = result.unwrap(); - assert!(stt.name_to_addr.contains_key(&name)); - - // Check the constant was stored - let cnst = stt.env.get_const(&addr); - assert!(cnst.is_some()); - match cnst.unwrap().as_ref() { - Constant { info: ConstantInfo::Defn(d), .. } => { - // Value should be a Rec(0) since it's self-referential in a single-element block - match d.value.as_ref() { - Expr::Rec(0, _) => {}, // Expected - other => panic!("Expected Rec(0), got {:?}", other), - } - }, - other => panic!("Expected Defn, got {:?}", other), - } - } - - #[test] - fn test_compile_env_single_axiom() { - use ix_common::env::{AxiomVal, ConstantVal}; - - // Create a minimal environment with just one axiom - let name = Name::str(Name::anon(), "myAxiom".to_string()); - let typ = LeanExpr::sort(Level::succ(Level::zero())); // Type 0 - let cnst = ConstantVal { name: name.clone(), level_params: vec![], typ }; - let axiom = AxiomVal { cnst, is_unsafe: false }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(name.clone(), LeanConstantInfo::AxiomInfo(axiom)); - let lean_env = Arc::new(lean_env); - - let result = compile_env(&lean_env); - assert!(result.is_ok(), "compile_env failed: {:?}", result.err()); - - let stt = result.unwrap(); - assert!(stt.name_to_addr.contains_key(&name), "name not in name_to_addr"); - assert_eq!(stt.env.const_count(), 1, "expected 1 constant"); - } - - #[test] - fn test_compile_env_two_independent_axioms() { - use ix_common::env::{AxiomVal, ConstantVal}; - - let name1 = Name::str(Name::anon(), "axiom1".to_string()); - let name2 = Name::str(Name::anon(), "axiom2".to_string()); - let typ = LeanExpr::sort(Level::succ(Level::zero())); - - let axiom1 = AxiomVal { - cnst: ConstantVal { - name: name1.clone(), - level_params: vec![], - typ: typ.clone(), - }, - is_unsafe: false, - }; - let axiom2 = AxiomVal { - cnst: ConstantVal { name: name2.clone(), level_params: vec![], typ }, - is_unsafe: false, - }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(name1.clone(), LeanConstantInfo::AxiomInfo(axiom1)); - lean_env.insert(name2.clone(), LeanConstantInfo::AxiomInfo(axiom2)); - let lean_env = Arc::new(lean_env); - - let result = compile_env(&lean_env); - assert!(result.is_ok(), "compile_env failed: {:?}", result.err()); - - let stt = result.unwrap(); - // Both names should be registered - assert!(stt.name_to_addr.contains_key(&name1), "name1 not in name_to_addr"); - assert!(stt.name_to_addr.contains_key(&name2), "name2 not in name_to_addr"); - // Both names point to the same constant (alpha-equivalent axioms) - let addr1 = stt.name_to_addr.get(&name1).unwrap().clone(); - let addr2 = stt.name_to_addr.get(&name2).unwrap().clone(); - assert_eq!( - addr1, addr2, - "alpha-equivalent axioms should have same address" - ); - // Only 1 unique constant in the store (alpha-equivalent axioms deduplicated) - assert_eq!(stt.env.const_count(), 1); - } - - #[test] - fn test_compile_env_def_referencing_axiom() { - use ix_common::env::{ - AxiomVal, ConstantVal, DefinitionSafety, DefinitionVal, ReducibilityHints, - }; - - let axiom_name = Name::str(Name::anon(), "myType".to_string()); - let def_name = Name::str(Name::anon(), "myDef".to_string()); - - // axiom myType : Type - let axiom = AxiomVal { - cnst: ConstantVal { - name: axiom_name.clone(), - level_params: vec![], - typ: LeanExpr::sort(Level::succ(Level::zero())), - }, - is_unsafe: false, - }; - - // def myDef : myType := myType (referencing the axiom in the value) - let def = DefinitionVal { - cnst: ConstantVal { - name: def_name.clone(), - level_params: vec![], - typ: LeanExpr::cnst(axiom_name.clone(), vec![]), - }, - value: LeanExpr::cnst(axiom_name.clone(), vec![]), // reference the axiom - hints: ReducibilityHints::Abbrev, - safety: DefinitionSafety::Safe, - all: vec![def_name.clone()], - }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(axiom_name.clone(), LeanConstantInfo::AxiomInfo(axiom)); - lean_env.insert(def_name.clone(), LeanConstantInfo::DefnInfo(def)); - let lean_env = Arc::new(lean_env); - - let result = compile_env(&lean_env); - assert!(result.is_ok(), "compile_env failed: {:?}", result.err()); - - let stt = result.unwrap(); - assert!(stt.name_to_addr.contains_key(&axiom_name)); - assert!(stt.name_to_addr.contains_key(&def_name)); - assert_eq!(stt.env.const_count(), 2); - } - - #[test] - fn test_compile_env_worker_limits_preserve_serialized_dependency_graph() { - use ix_common::env::{ - AxiomVal, ConstantVal, DefinitionSafety, DefinitionVal, - }; - let base = Name::str(Name::anon(), "Base".into()); - let typ = LeanExpr::sort(Level::succ(Level::zero())); - let mut source = LeanEnv::default(); - source.insert( - base.clone(), - LeanConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: base.clone(), - level_params: vec![], - typ: typ.clone(), - }, - is_unsafe: false, - }), - ); - let mut previous = vec![base; 8]; - for layer in 0..5 { - for (column, prior) in previous.iter_mut().enumerate() { - let name = Name::str(Name::anon(), format!("alias_{layer}_{column}")); - source.insert( - name.clone(), - LeanConstantInfo::DefnInfo(DefinitionVal { - cnst: ConstantVal { - name: name.clone(), - level_params: vec![], - typ: typ.clone(), - }, - value: LeanExpr::cnst(prior.clone(), vec![]), - hints: ReducibilityHints::Abbrev, - safety: DefinitionSafety::Safe, - all: vec![name.clone()], - }), - ); - *prior = name; - } - } - // A final block depends on every branch, testing dependency publication - // as well as byte-identical metadata for alpha-equivalent aliases. - let join = Name::str(Name::anon(), "Join".into()); - let mut value = LeanExpr::sort(Level::zero()); - for name in previous { - value = LeanExpr::all( - Name::anon(), - LeanExpr::cnst(name, vec![]), - value, - BinderInfo::Default, - ); - } - source.insert( - join.clone(), - LeanConstantInfo::DefnInfo(DefinitionVal { - cnst: ConstantVal { name: join.clone(), level_params: vec![], typ }, - value, - hints: ReducibilityHints::Abbrev, - safety: DefinitionSafety::Safe, - all: vec![join], - }), - ); - let source = Arc::new(source); - let mut outputs = Vec::new(); - for max_workers in [1, 4] { - let compiled = compile_env_with_options( - &source, - CompileOptions { max_workers: Some(max_workers) }, - ) - .unwrap(); - assert!(compiled.ungrounded.is_empty()); - assert_eq!(compiled.name_to_addr.len(), 42); - let mut bytes = Vec::new(); - compiled.env.put(&mut bytes).unwrap(); - outputs.push(bytes); - } - assert_eq!(outputs[0], outputs[1]); - } - - /// Test that alpha-equivalent mutual definitions keep one class each. Two - /// definitions with identical type/value structure (but different names) - /// are two members of one block, in Lean's order, each with its own - /// projection. - #[test] - fn test_compile_mutual_alpha_equivalent_defs() { - use ix_common::env::{ - ConstantVal, DefinitionSafety, DefinitionVal, ReducibilityHints, - }; - - // Create two mutually recursive definitions with identical structure. - // Both: def X : Type := Type (referencing each other, same shape) - let name_f = Name::str(Name::anon(), "f".to_string()); - let name_g = Name::str(Name::anon(), "g".to_string()); - - let typ = LeanExpr::sort(Level::succ(Level::zero())); // Type - - // f and g reference each other but with identical structure: - // f : Type := g and g : Type := f - // After alpha-normalization (mutual refs become recur indices), - // both become: recur(0) since they're in the same class. - let def_f = DefinitionVal { - cnst: ConstantVal { - name: name_f.clone(), - level_params: vec![], - typ: typ.clone(), - }, - value: LeanExpr::cnst(name_g.clone(), vec![]), - hints: ReducibilityHints::Opaque, - safety: DefinitionSafety::Safe, - all: vec![name_f.clone(), name_g.clone()], - }; - - let def_g = DefinitionVal { - cnst: ConstantVal { - name: name_g.clone(), - level_params: vec![], - typ: typ.clone(), - }, - value: LeanExpr::cnst(name_f.clone(), vec![]), - hints: ReducibilityHints::Opaque, - safety: DefinitionSafety::Safe, - all: vec![name_f.clone(), name_g.clone()], - }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(name_f.clone(), LeanConstantInfo::DefnInfo(def_f)); - lean_env.insert(name_g.clone(), LeanConstantInfo::DefnInfo(def_g)); - let lean_env = Arc::new(lean_env); - - let result = compile_env(&lean_env); - assert!(result.is_ok(), "compile_env failed: {:?}", result.err()); - - let stt = result.unwrap(); - - // Both names should be registered - assert!(stt.name_to_addr.contains_key(&name_f), "f not in name_to_addr"); - assert!(stt.name_to_addr.contains_key(&name_g), "g not in name_to_addr"); - - // The block is compiled as Lean gives it: alpha-equivalent members are - // not merged, so each keeps its own position and projection address. - let addr_f = stt.name_to_addr.get(&name_f).unwrap().clone(); - let addr_g = stt.name_to_addr.get(&name_g).unwrap().clone(); - assert_ne!( - addr_f, addr_g, - "alpha-equivalent mutual defs keep their own projection addresses" - ); - - // One block of two members, in Lean's order: an entry per member. - assert_eq!(stt.blocks.len(), 2, "expected an entry per member"); - for entry in stt.blocks.iter() { - assert_eq!( - entry.value(), - &vec![vec![name_f.clone()], vec![name_g.clone()]], - "the block lists its members in Lean's order, one per class" - ); - } - } - - /// Test that alpha-equivalent defs in a mutual block with a non-equivalent - /// third definition keep one class each: 3 members → 3 Muts entries, in - /// Lean's order, with projections indexing correctly into the array. - #[test] - fn test_compile_mutual_alpha_equiv_with_different_third() { - use ix_common::env::{ - ConstantVal, DefinitionSafety, DefinitionVal, ReducibilityHints, - }; - - let name_f = Name::str(Name::anon(), "f".to_string()); - let name_g = Name::str(Name::anon(), "g".to_string()); - let name_h = Name::str(Name::anon(), "h".to_string()); - - let typ = LeanExpr::sort(Level::succ(Level::zero())); // Type - - // f and g are alpha-equivalent to each other: - // f : Type := App(g, h) g : Type := App(f, h) - // After alpha-normalization, both become App(recur(class_of_fg), recur(class_of_h)) - // h is structurally different: - // h : Type := f - // After alpha-normalization: recur(class_of_fg) - // All three form one SCC: f→g,h g→f,h h→f - let def_f = DefinitionVal { - cnst: ConstantVal { - name: name_f.clone(), - level_params: vec![], - typ: typ.clone(), - }, - value: LeanExpr::app( - LeanExpr::cnst(name_g.clone(), vec![]), - LeanExpr::cnst(name_h.clone(), vec![]), - ), - hints: ReducibilityHints::Opaque, - safety: DefinitionSafety::Safe, - all: vec![name_f.clone(), name_g.clone(), name_h.clone()], - }; - - let def_g = DefinitionVal { - cnst: ConstantVal { - name: name_g.clone(), - level_params: vec![], - typ: typ.clone(), - }, - value: LeanExpr::app( - LeanExpr::cnst(name_f.clone(), vec![]), - LeanExpr::cnst(name_h.clone(), vec![]), - ), - hints: ReducibilityHints::Opaque, - safety: DefinitionSafety::Safe, - all: vec![name_f.clone(), name_g.clone(), name_h.clone()], - }; - - let def_h = DefinitionVal { - cnst: ConstantVal { - name: name_h.clone(), - level_params: vec![], - typ: typ.clone(), - }, - value: LeanExpr::cnst(name_f.clone(), vec![]), - hints: ReducibilityHints::Opaque, - safety: DefinitionSafety::Safe, - all: vec![name_f.clone(), name_g.clone(), name_h.clone()], - }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(name_f.clone(), LeanConstantInfo::DefnInfo(def_f)); - lean_env.insert(name_g.clone(), LeanConstantInfo::DefnInfo(def_g)); - lean_env.insert(name_h.clone(), LeanConstantInfo::DefnInfo(def_h)); - let lean_env = Arc::new(lean_env); - - let result = compile_env(&lean_env); - assert!(result.is_ok(), "compile_env failed: {:?}", result.err()); - - let stt = result.unwrap(); - - // All three should be registered - assert!(stt.name_to_addr.contains_key(&name_f)); - assert!(stt.name_to_addr.contains_key(&name_g)); - assert!(stt.name_to_addr.contains_key(&name_h)); - - // f and g are alpha-equivalent, but members of one block are not - // merged: each keeps its own projection address - let addr_f = stt.name_to_addr.get(&name_f).unwrap().clone(); - let addr_g = stt.name_to_addr.get(&name_g).unwrap().clone(); - assert_ne!( - addr_f, addr_g, - "alpha-equivalent f and g keep their own projection addresses" - ); - - // h is different → different projection address - let addr_h = stt.name_to_addr.get(&name_h).unwrap().clone(); - assert_ne!( - addr_f, addr_h, - "h should have a different projection address than f/g" - ); - - // One block of three members, one per class, in Lean's order: an entry - // per member - assert_eq!(stt.blocks.len(), 3, "expected an entry per member"); - let expected = - vec![vec![name_f.clone()], vec![name_g.clone()], vec![name_h.clone()]]; - for entry in stt.blocks.iter() { - assert_eq!( - entry.value(), - &expected, - "the block lists its members in Lean's order, one per class" - ); - } - } - - // ========================================================================= - // Sharing tests - // ========================================================================= - - #[test] - fn test_mutual_block_roundtrip() { - use ix_common::env::DefinitionSafety; - use ixon::constant::{DefKind, Definition}; - - // Create a mutual block and verify it roundtrips through serialization - let sort0 = Expr::sort(0); - let ty = Expr::all(sort0.clone(), Expr::var(0)); - - let def1 = IxonMutConst::Defn(Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: ty.clone(), - value: Expr::var(0), - }); - - let def2 = IxonMutConst::Defn(Definition { - kind: DefKind::Theorem, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: ty, - value: Expr::var(1), - }); - - let compiled = compile_mutual_block( - &ExactSharingLimits::default(), - vec![def1, def2], - vec![], - vec![], - ) - .unwrap(); - let constant = compiled.constant; - let addr = compiled.addr; - - // Serialize - let mut buf = Vec::new(); - constant.put(&mut buf); - - // Deserialize - let recovered = Constant::get(&mut buf.as_slice()).unwrap(); - - // Re-serialize to check determinism - let mut buf2 = Vec::new(); - recovered.put(&mut buf2); - - assert_eq!(buf, buf2, "Serialization should be deterministic"); - - // Re-hash to check address stability - let addr2 = Address::hash(&buf2); - assert_eq!(addr, addr2, "Content address should be stable"); - } - - // ========================================================================= - // Constant-level sharing tests - // ========================================================================= - - /// The compiler's sharing (the canonical construction) of `exprs` under - /// the default limits: the rewritten roots and the table. - #[allow(clippy::needless_pass_by_value)] - fn apply_sharing(exprs: Vec>) -> (Vec>, Vec>) { - share_roots(&ExactSharingLimits::default(), &exprs).unwrap() - } - - #[test] - fn test_apply_sharing_basic() { - // Test the apply_sharing helper function with a repeated subterm - let sort0 = Expr::sort(0); - let var0 = Expr::var(0); - // Create term: App(Lam(Sort0, Var0), Lam(Sort0, Var0)) - // Lam(Sort0, Var0) is repeated and should be shared - let lam = Expr::lam(sort0.clone(), var0); - let app = Expr::app(lam.clone(), lam); - - let (rewritten, sharing) = apply_sharing(vec![app]); - - // Should have sharing since lam is used twice - assert!(!sharing.is_empty(), "Expected sharing for repeated subterm"); - // The sharing vector should contain the shared Lam - assert!(sharing.iter().any(|e| matches!(e.as_ref(), Expr::Lam(_, _, _)))); - // The rewritten expression should have Share references - assert!(matches!(rewritten[0].as_ref(), Expr::App(_, _))); - } - - #[test] - fn test_definition_with_sharing() { - use ixon::constant::{DefKind, Definition}; - - // Create a definition where typ and value share structure - let sort0 = Expr::sort(0); - let shared_subterm = Expr::all(sort0.clone(), Expr::var(0)); - // typ = App(shared, shared) -- shared twice - let typ = Expr::app(shared_subterm.clone(), shared_subterm.clone()); - // value = shared - let value = shared_subterm; - - let (rewritten, sharing) = apply_sharing(vec![typ, value]); - - // shared_subterm appears 3 times total, should definitely be shared - assert!( - !sharing.is_empty(), - "Expected sharing for definition with repeated subterms" - ); - - // Create constant with sharing at Constant level - let def = Definition { - kind: DefKind::Definition, - safety: ix_common::env::DefinitionSafety::Safe, - lvls: 0, - typ: rewritten[0].clone(), - value: rewritten[1].clone(), - }; - - let constant = Constant::with_tables( - ConstantInfo::Defn(def), - sharing.clone(), - vec![], - vec![], - ); - - let mut buf = Vec::new(); - constant.put(&mut buf); - let recovered = Constant::get(&mut buf.as_slice()).unwrap(); - - assert_eq!(sharing.len(), recovered.sharing.len()); - assert!(matches!(recovered.info, ConstantInfo::Defn(_))); - } - - #[test] - fn test_axiom_with_sharing() { - use ixon::constant::Axiom; - - // Axiom with repeated subterms in its type - let sort0 = Expr::sort(0); - let shared = Expr::all(sort0.clone(), Expr::var(0)); - // typ = All(shared, All(shared, Var(0))) - let typ = - Expr::all(shared.clone(), Expr::all(shared.clone(), Expr::var(0))); - - let (rewritten, sharing) = apply_sharing(vec![typ]); - - // shared appears twice, should be shared - assert!( - !sharing.is_empty(), - "Expected sharing for axiom with repeated subterms" - ); - - let axiom = Axiom { is_unsafe: false, lvls: 0, typ: rewritten[0].clone() }; - let constant = Constant::with_tables( - ConstantInfo::Axio(axiom), - sharing.clone(), - vec![], - vec![], - ); - - let mut buf = Vec::new(); - constant.put(&mut buf); - let recovered = Constant::get(&mut buf.as_slice()).unwrap(); - - assert_eq!(sharing.len(), recovered.sharing.len()); - assert!(matches!(recovered.info, ConstantInfo::Axio(_))); - } - - #[test] - fn test_recursor_with_sharing() { - use ixon::constant::{Recursor, RecursorRule}; - - // Recursor with shared subterms across typ and rules - let sort0 = Expr::sort(0); - let shared = Expr::lam(sort0.clone(), Expr::var(0)); - - // typ uses shared twice - let typ = Expr::app(shared.clone(), shared.clone()); - - // rules also use shared - let rules = vec![ - RecursorRule { fields: 0, rhs: shared.clone() }, - RecursorRule { fields: 1, rhs: shared }, - ]; - - // Collect all expressions - let mut all_exprs = vec![typ]; - for r in &rules { - all_exprs.push(r.rhs.clone()); - } - - let (rewritten, sharing) = apply_sharing(all_exprs); - - // shared appears 4 times, should definitely be shared - assert!( - !sharing.is_empty(), - "Expected sharing for recursor with repeated subterms" - ); - - let rec = Recursor { - k: false, - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - motives: 1, - minors: 2, - typ: rewritten[0].clone(), - rules: rules - .into_iter() - .zip(rewritten.into_iter().skip(1)) - .map(|(r, rhs)| RecursorRule { fields: r.fields, rhs }) - .collect(), - }; - - let constant = Constant::with_tables( - ConstantInfo::Recr(rec), - sharing.clone(), - vec![], - vec![], - ); - - let mut buf = Vec::new(); - constant.put(&mut buf); - let recovered = Constant::get(&mut buf.as_slice()).unwrap(); - - assert_eq!(sharing.len(), recovered.sharing.len()); - if let ConstantInfo::Recr(rec2) = &recovered.info { - assert_eq!(2, rec2.rules.len()); - } else { - panic!("Expected Recursor"); - } - } - - #[test] - fn test_inductive_with_sharing() { - use ixon::constant::{Constructor, Inductive}; - - // Inductive with shared subterms across type and constructors - let sort0 = Expr::sort(0); - let shared = Expr::all(sort0.clone(), Expr::var(0)); - - let typ = Expr::app(shared.clone(), shared.clone()); - - let ctors = vec![ - Constructor { - is_unsafe: false, - lvls: 0, - cidx: 0, - params: 0, - fields: 0, - typ: shared.clone(), - }, - Constructor { - is_unsafe: false, - lvls: 0, - cidx: 1, - params: 0, - fields: 1, - typ: shared, - }, - ]; - - // Collect all expressions - let mut all_exprs = vec![typ]; - for c in &ctors { - all_exprs.push(c.typ.clone()); - } - - let (rewritten, sharing) = apply_sharing(all_exprs); - - // shared appears 4 times, should be shared - assert!( - !sharing.is_empty(), - "Expected sharing for inductive with repeated subterms" - ); - - let ind = Inductive { - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - typ: rewritten[0].clone(), - ctors: ctors - .into_iter() - .zip(rewritten.into_iter().skip(1)) - .map(|(c, typ)| Constructor { - is_unsafe: c.is_unsafe, - lvls: c.lvls, - cidx: c.cidx, - params: c.params, - fields: c.fields, - typ, - }) - .collect(), - }; - - // Wrap in MutConst for serialization with sharing at Constant level - let constant = Constant::with_tables( - ConstantInfo::Muts(vec![IxonMutConst::Indc(ind)]), - sharing.clone(), - vec![], - vec![], - ); - - let mut buf = Vec::new(); - constant.put(&mut buf); - let recovered = Constant::get(&mut buf.as_slice()).unwrap(); - - assert_eq!(sharing.len(), recovered.sharing.len()); - if let ConstantInfo::Muts(mutuals) = &recovered.info { - if let Some(IxonMutConst::Indc(ind2)) = mutuals.first() { - assert_eq!(2, ind2.ctors.len()); - } else { - panic!("Expected Inductive in Muts"); - } - } else { - panic!("Expected Muts"); - } - } - - #[test] - fn test_no_sharing_when_not_repeated() { - // When a subterm only appears once, it shouldn't be shared - let _sort0 = Expr::sort(0); - let var0 = Expr::var(0); - let var1 = Expr::var(1); - let app = Expr::app(var0, var1); - - let (rewritten, sharing) = apply_sharing(vec![app.clone()]); - - // No repeated subterms, so no sharing - assert!(sharing.is_empty(), "Expected no sharing when nothing is repeated"); - // Rewritten should be identical to original - assert_eq!(rewritten[0].as_ref(), app.as_ref()); - } - - // ========================================================================= - // Compile/Decompile Roundtrip Tests - // ========================================================================= - - #[test] - fn test_roundtrip_axiom() { - use crate::decompile::decompile_env; - use ix_common::env::{AxiomVal, ConstantVal}; - - // Create an axiom: axiom myAxiom : Type - let name = Name::str(Name::anon(), "myAxiom".to_string()); - let typ = LeanExpr::sort(Level::succ(Level::zero())); // Type 0 - let cnst = ConstantVal { name: name.clone(), level_params: vec![], typ }; - let axiom = AxiomVal { cnst, is_unsafe: false }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(name.clone(), LeanConstantInfo::AxiomInfo(axiom.clone())); - let lean_env = Arc::new(lean_env); - - // Compile - let stt = compile_env(&lean_env).expect("compile_env failed"); - - // Decompile - let dstt = decompile_env(&stt).expect("decompile_env failed"); - - // Check roundtrip - let recovered = - dstt.env.get(&name).expect("name not found in decompiled env"); - match &*recovered { - LeanConstantInfo::AxiomInfo(ax) => { - assert_eq!(ax.cnst.name, axiom.cnst.name); - assert_eq!(ax.is_unsafe, axiom.is_unsafe); - assert_eq!(ax.cnst.level_params.len(), axiom.cnst.level_params.len()); - }, - _ => panic!("Expected AxiomInfo"), - } - } - - /// Canonicity §10.6 end-to-end at unit scale: compile emits - /// `canon_univ`-fixed primary tables plus `univ_patches`/`meta_univs` - /// for changed spellings, and decompile replays the patches so every - /// source spelling roundtrips EXACTLY (content-hash equality). - #[test] - fn test_level_canonicalization_tables_patches_and_replay() { - use crate::decompile::decompile_env; - use ix_common::env::{AxiomVal, ConstantVal, Env as LeanEnv}; - - let u = Name::str(Name::anon(), "u".to_string()); - let v = Name::str(Name::anon(), "v".to_string()); - let lu = || Level::param(u.clone()); - let lv = || Level::param(v.clone()); - // Succ-lifted twin: `(max u v)+1` — the Géran-canonical form - // distributes the succ (`max (u+1) (v+1)`), so this spelling must - // be patched. - let succ_dist = || Level::succ(Level::max(lu(), lv())); - - let mk_axiom = |name: &Name, typ: LeanExpr| { - LeanConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: name.clone(), - level_params: vec![u.clone(), v.clone()], - typ, - }, - is_unsafe: false, - }) - }; - - let twin_name = Name::str(Name::anon(), "twinAx".to_string()); - let ctrl_name = Name::str(Name::anon(), "ctrlAx".to_string()); - let use_name = Name::str(Name::anon(), "useAx".to_string()); - - let mut lean_env = LeanEnv::default(); - lean_env.insert( - twin_name.clone(), - mk_axiom(&twin_name, LeanExpr::sort(succ_dist())), - ); - // Control: an already-canonical spelling must emit NO patches. - lean_env.insert( - ctrl_name.clone(), - mk_axiom(&ctrl_name, LeanExpr::sort(Level::max(lu(), lv()))), - ); - // Const-arm coverage: a reference with a noncanonical level ARG — - // the patch must carry the full arg list. - lean_env.insert( - use_name.clone(), - mk_axiom( - &use_name, - LeanExpr::cnst(twin_name.clone(), vec![succ_dist(), lv()]), - ), - ); - let lean_env = Arc::new(lean_env); - - let stt = compile_env(&lean_env).expect("compile_env failed"); - - // (1) Every stored primary univ-table entry is canon_univ-fixed. - for entry in stt.env.consts.iter() { - let c = stt.env.get_const(entry.key()).expect("stored constant"); - for uv in &c.univs { - assert_eq!( - &canon_univ(uv), - uv, - "non-canonical entry in a stored constant's univ table" - ); - } - } - - // (2) Patch emission: the twins carry patches + extension spellings, - // the control carries none. - let twin_meta = stt.env.named.get(&twin_name).expect("twin named"); - assert_eq!(twin_meta.meta().univ_patches.len(), 1, "twin sort patch"); - assert_eq!(twin_meta.meta().univ_patches[0].univ_idxs.len(), 1); - assert_eq!(twin_meta.meta().meta_univs.len(), 1, "twin extension"); - drop(twin_meta); - let use_meta = stt.env.named.get(&use_name).expect("use named"); - assert_eq!(use_meta.meta().univ_patches.len(), 1, "const-arg patch"); - assert_eq!( - use_meta.meta().univ_patches[0].univ_idxs.len(), - 2, - "const patch carries the FULL level-arg list" - ); - drop(use_meta); - let ctrl_meta = stt.env.named.get(&ctrl_name).expect("ctrl named"); - assert!(ctrl_meta.meta().univ_patches.is_empty(), "control patchless"); - assert!(ctrl_meta.meta().meta_univs.is_empty(), "control no extension"); - drop(ctrl_meta); - - // (3) Decompile replays every spelling exactly. - let dstt = decompile_env(&stt).expect("decompile_env failed"); - for (name, orig_ci) in lean_env.iter() { - let rec = dstt.env.get(name).expect("decompiled constant"); - assert_eq!( - orig_ci.get_type().get_hash(), - rec.get_type().get_hash(), - "type spelling roundtrip for {name}" - ); - } - } - - /// Canonicity §10.6, constructor window: an inductive whose ctor TYPES - /// carry noncanonical spellings exercises the per-ctor - /// `meta_univs`/`univ_patches` swap in the decompiler (extensions - /// installed at the PRIMARY offset per ctor, restored between - /// siblings). Multiple ctors make the window cycle; the strict - /// roundtrip pins that no sibling's extension leaks into another's - /// replay. - #[test] - fn test_level_canonicalization_inductive_ctor_windows() { - use crate::decompile::decompile_env; - use ix_common::env::{ - ConstantVal, ConstructorVal, Env as LeanEnv, InductiveVal, - }; - - let u = Name::str(Name::anon(), "u".to_string()); - let v = Name::str(Name::anon(), "v".to_string()); - let lps = vec![u.clone(), v.clone()]; - let lu = || Level::param(u.clone()); - let lv = || Level::param(v.clone()); - - let ind_name = Name::str(Name::anon(), "Twine".to_string()); - let c0_name = Name::str(ind_name.clone(), "lift".to_string()); - let c1_name = Name::str(ind_name.clone(), "flip".to_string()); - let c2_name = Name::str(ind_name.clone(), "plain".to_string()); - - // Inductive type itself carries a noncanonical sort spelling. - let ind_typ = LeanExpr::sort(Level::succ(Level::max(lu(), lv()))); - let ind_ref = || LeanExpr::cnst(ind_name.clone(), vec![lu(), lv()]); - // Ctor 0: succ-lifted twin `(max u v)+1` (patched). - let c0_typ = LeanExpr::all( - Name::str(Name::anon(), "x".to_string()), - LeanExpr::sort(Level::succ(Level::max(lu(), lv()))), - ind_ref(), - ix_common::env::BinderInfo::Default, - ); - // Ctor 1: commuted twin `max v u` (patched, DIFFERENT spelling). - let c1_typ = LeanExpr::all( - Name::str(Name::anon(), "y".to_string()), - LeanExpr::sort(Level::max(lv(), lu())), - ind_ref(), - ix_common::env::BinderInfo::Default, - ); - // Ctor 2: canonical control (no patch). - let c2_typ = LeanExpr::all( - Name::str(Name::anon(), "z".to_string()), - LeanExpr::sort(Level::max(lu(), lv())), - ind_ref(), - ix_common::env::BinderInfo::Default, - ); - - let inductive = InductiveVal { - cnst: ConstantVal { - name: ind_name.clone(), - level_params: lps.clone(), - typ: ind_typ, - }, - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: vec![ind_name.clone()], - ctors: vec![c0_name.clone(), c1_name.clone(), c2_name.clone()], - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }; - let mk_ctor = |name: &Name, cidx: u64, typ: LeanExpr| ConstructorVal { - cnst: ConstantVal { name: name.clone(), level_params: lps.clone(), typ }, - induct: ind_name.clone(), - cidx: Nat::from(cidx), - num_params: Nat::from(0u64), - num_fields: Nat::from(1u64), - is_unsafe: false, - }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(ind_name.clone(), LeanConstantInfo::InductInfo(inductive)); - lean_env.insert( - c0_name.clone(), - LeanConstantInfo::CtorInfo(mk_ctor(&c0_name, 0, c0_typ)), - ); - lean_env.insert( - c1_name.clone(), - LeanConstantInfo::CtorInfo(mk_ctor(&c1_name, 1, c1_typ)), - ); - lean_env.insert( - c2_name.clone(), - LeanConstantInfo::CtorInfo(mk_ctor(&c2_name, 2, c2_typ)), - ); - let lean_env = Arc::new(lean_env); - - let stt = compile_env(&lean_env).expect("compile_env failed"); - - // The two twin ctors carry their own patches; the control does not. - for (name, want_patch) in - [(&c0_name, true), (&c1_name, true), (&c2_name, false)] - { - let named = stt.env.named.get(name).expect("ctor named"); - assert_eq!( - !named.meta().univ_patches.is_empty(), - want_patch, - "patch presence for {name}" - ); - } - - let dstt = decompile_env(&stt).expect("decompile_env failed"); - for (name, orig_ci) in lean_env.iter() { - let rec = dstt.env.get(name).expect("decompiled constant"); - assert_eq!( - orig_ci.get_type().get_hash(), - rec.get_type().get_hash(), - "ctor-window spelling roundtrip for {name}" - ); - } - } - - #[test] - fn test_roundtrip_axiom_with_level_params() { - use crate::decompile::decompile_env; - use ix_common::env::{AxiomVal, ConstantVal, Env as LeanEnv}; - - // Create an axiom with universe params: axiom myAxiom.{u, v} : Sort (max u v) - let name = Name::str(Name::anon(), "myAxiom".to_string()); - let u = Name::str(Name::anon(), "u".to_string()); - let v = Name::str(Name::anon(), "v".to_string()); - let typ = LeanExpr::sort(Level::max( - Level::param(u.clone()), - Level::param(v.clone()), - )); - let cnst = ConstantVal { - name: name.clone(), - level_params: vec![u.clone(), v.clone()], - typ, - }; - let axiom = AxiomVal { cnst, is_unsafe: false }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(name.clone(), LeanConstantInfo::AxiomInfo(axiom.clone())); - let lean_env = Arc::new(lean_env); - - // Compile - let stt = compile_env(&lean_env).expect("compile_env failed"); - - // Decompile - let dstt = decompile_env(&stt).expect("decompile_env failed"); - - // Check roundtrip - let recovered = dstt.env.get(&name).expect("name not found"); - match &*recovered { - LeanConstantInfo::AxiomInfo(ax) => { - assert_eq!(ax.cnst.name, name); - assert_eq!(ax.cnst.level_params.len(), 2); - assert_eq!(ax.cnst.level_params[0], u); - assert_eq!(ax.cnst.level_params[1], v); - }, - _ => panic!("Expected AxiomInfo"), - } - } - - #[test] - fn test_roundtrip_definition() { - use crate::decompile::decompile_env; - use ix_common::env::{ - ConstantVal, DefinitionSafety, DefinitionVal, ReducibilityHints, - }; - - // Create a definition: def id : Type -> Type := fun x => x - let name = Name::str(Name::anon(), "id".to_string()); - let type1 = LeanExpr::sort(Level::succ(Level::zero())); // Type - let typ = LeanExpr::all( - Name::str(Name::anon(), "x".to_string()), - type1.clone(), - type1.clone(), - ix_common::env::BinderInfo::Default, - ); - let value = LeanExpr::lam( - Name::str(Name::anon(), "x".to_string()), - type1, - LeanExpr::bvar(Nat::from(0u64)), - ix_common::env::BinderInfo::Default, - ); - let def = DefinitionVal { - cnst: ConstantVal { name: name.clone(), level_params: vec![], typ }, - value, - hints: ReducibilityHints::Abbrev, - safety: DefinitionSafety::Safe, - all: vec![name.clone()], - }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(name.clone(), LeanConstantInfo::DefnInfo(def.clone())); - let lean_env = Arc::new(lean_env); - - // Compile - let stt = compile_env(&lean_env).expect("compile_env failed"); - - // Decompile - let dstt = decompile_env(&stt).expect("decompile_env failed"); - - // Check roundtrip - let recovered = dstt.env.get(&name).expect("name not found"); - match &*recovered { - LeanConstantInfo::DefnInfo(d) => { - assert_eq!(d.cnst.name, name); - assert_eq!(d.hints, def.hints); - assert_eq!(d.safety, def.safety); - assert_eq!(d.all.len(), def.all.len()); - }, - _ => panic!("Expected DefnInfo"), - } - } - - #[test] - fn test_roundtrip_def_referencing_axiom() { - use crate::decompile::decompile_env; - use ix_common::env::{ - AxiomVal, ConstantVal, DefinitionSafety, DefinitionVal, Env as LeanEnv, - ReducibilityHints, - }; - - // Create axiom A : Type and def B : A := A - let axiom_name = Name::str(Name::anon(), "A".to_string()); - let def_name = Name::str(Name::anon(), "B".to_string()); - - let type0 = LeanExpr::sort(Level::succ(Level::zero())); - let axiom = AxiomVal { - cnst: ConstantVal { - name: axiom_name.clone(), - level_params: vec![], - typ: type0, - }, - is_unsafe: false, - }; - - let def = DefinitionVal { - cnst: ConstantVal { - name: def_name.clone(), - level_params: vec![], - typ: LeanExpr::cnst(axiom_name.clone(), vec![]), - }, - value: LeanExpr::cnst(axiom_name.clone(), vec![]), - hints: ReducibilityHints::Abbrev, - safety: DefinitionSafety::Safe, - all: vec![def_name.clone()], - }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(axiom_name.clone(), LeanConstantInfo::AxiomInfo(axiom)); - lean_env.insert(def_name.clone(), LeanConstantInfo::DefnInfo(def)); - let lean_env = Arc::new(lean_env); - - // Compile - let stt = compile_env(&lean_env).expect("compile_env failed"); - - // Decompile - let dstt = decompile_env(&stt).expect("decompile_env failed"); - - // Check both roundtrip - assert!(dstt.env.contains_key(&axiom_name)); - assert!(dstt.env.contains_key(&def_name)); - - match &*dstt.env.get(&def_name).unwrap() { - LeanConstantInfo::DefnInfo(d) => { - assert_eq!(d.cnst.name, def_name); - }, - _ => panic!("Expected DefnInfo"), - } - } - - #[test] - fn test_roundtrip_quotient() { - use crate::decompile::decompile_env; - use ix_common::env::{ConstantVal, Env as LeanEnv, QuotKind, QuotVal}; - - // Create quotient constants - let quot_name = Name::str(Name::anon(), "Quot".to_string()); - let u = Name::str(Name::anon(), "u".to_string()); - - // Quot.{u} : (α : Sort u) → (α → α → Prop) → Sort u - let alpha = Name::str(Name::anon(), "α".to_string()); - let sort_u = LeanExpr::sort(Level::param(u.clone())); - let prop = LeanExpr::sort(Level::zero()); - - // Build: (α : Sort u) → (α → α → Prop) → Sort u - let rel_type = LeanExpr::all( - Name::anon(), - LeanExpr::bvar(Nat::from(0u64)), - LeanExpr::all( - Name::anon(), - LeanExpr::bvar(Nat::from(1u64)), - prop.clone(), - ix_common::env::BinderInfo::Default, - ), - ix_common::env::BinderInfo::Default, - ); - let typ = LeanExpr::all( - alpha, - sort_u.clone(), - LeanExpr::all( - Name::anon(), - rel_type, - sort_u.clone(), - ix_common::env::BinderInfo::Default, - ), - ix_common::env::BinderInfo::Default, - ); - - let quot = QuotVal { - cnst: ConstantVal { - name: quot_name.clone(), - level_params: vec![u.clone()], - typ, - }, - kind: QuotKind::Type, - }; - - let mut lean_env = LeanEnv::default(); - lean_env - .insert(quot_name.clone(), LeanConstantInfo::QuotInfo(quot.clone())); - let lean_env = Arc::new(lean_env); - - // Compile - let stt = compile_env(&lean_env).expect("compile_env failed"); - - // Decompile - let dstt = decompile_env(&stt).expect("decompile_env failed"); - - // Check roundtrip - let recovered = dstt.env.get("_name).expect("name not found"); - match &*recovered { - LeanConstantInfo::QuotInfo(q) => { - assert_eq!(q.cnst.name, quot_name); - assert_eq!(q.kind, QuotKind::Type); - assert_eq!(q.cnst.level_params.len(), 1); - }, - _ => panic!("Expected QuotInfo"), - } - } - - #[test] - fn test_roundtrip_theorem() { - use crate::decompile::decompile_env; - use ix_common::env::{ConstantVal, Env as LeanEnv, TheoremVal}; - - // Create a theorem: theorem trivial : True := True.intro - let name = Name::str(Name::anon(), "trivial".to_string()); - let prop = LeanExpr::sort(Level::zero()); // Prop - - // For simplicity, just use Prop as both type and value - let thm = TheoremVal { - cnst: ConstantVal { - name: name.clone(), - level_params: vec![], - typ: prop.clone(), - }, - value: prop, - all: vec![name.clone()], - }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(name.clone(), LeanConstantInfo::ThmInfo(thm.clone())); - let lean_env = Arc::new(lean_env); - - // Compile - let stt = compile_env(&lean_env).expect("compile_env failed"); - - // Decompile - let dstt = decompile_env(&stt).expect("decompile_env failed"); - - // Check roundtrip - let recovered = dstt.env.get(&name).expect("name not found"); - match &*recovered { - LeanConstantInfo::ThmInfo(t) => { - assert_eq!(t.cnst.name, name); - assert_eq!(t.all.len(), 1); - }, - _ => panic!("Expected ThmInfo"), - } - } - - #[test] - fn test_roundtrip_opaque() { - use crate::decompile::decompile_env; - use ix_common::env::{ConstantVal, Env as LeanEnv, OpaqueVal}; - - // Create an opaque: opaque secret : Nat := 42 - let name = Name::str(Name::anon(), "secret".to_string()); - let nat_type = LeanExpr::sort(Level::zero()); // Using Prop as placeholder - - let opaq = OpaqueVal { - cnst: ConstantVal { - name: name.clone(), - level_params: vec![], - typ: nat_type.clone(), - }, - value: nat_type, - is_unsafe: false, - all: vec![name.clone()], - }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(name.clone(), LeanConstantInfo::OpaqueInfo(opaq.clone())); - let lean_env = Arc::new(lean_env); - - // Compile - let stt = compile_env(&lean_env).expect("compile_env failed"); - - // Decompile - let dstt = decompile_env(&stt).expect("decompile_env failed"); - - // Check roundtrip - let recovered = dstt.env.get(&name).expect("name not found"); - match &*recovered { - LeanConstantInfo::OpaqueInfo(o) => { - assert_eq!(o.cnst.name, name); - assert!(!o.is_unsafe); - assert_eq!(o.all.len(), 1); - }, - _ => panic!("Expected OpaqueInfo"), - } - } - - #[test] - fn test_roundtrip_multiple_constants() { - use crate::decompile::decompile_env; - use ix_common::env::{ - AxiomVal, ConstantVal, DefinitionSafety, DefinitionVal, Env as LeanEnv, - ReducibilityHints, TheoremVal, - }; - - // Create multiple constants of different types - let axiom_name = Name::str(Name::anon(), "A".to_string()); - let def_name = Name::str(Name::anon(), "B".to_string()); - let thm_name = Name::str(Name::anon(), "C".to_string()); - - let type0 = LeanExpr::sort(Level::succ(Level::zero())); - let prop = LeanExpr::sort(Level::zero()); - - let axiom = AxiomVal { - cnst: ConstantVal { - name: axiom_name.clone(), - level_params: vec![], - typ: type0.clone(), - }, - is_unsafe: false, - }; - - let def = DefinitionVal { - cnst: ConstantVal { - name: def_name.clone(), - level_params: vec![], - typ: type0, - }, - value: LeanExpr::cnst(axiom_name.clone(), vec![]), - hints: ReducibilityHints::Regular(10), - safety: DefinitionSafety::Safe, - all: vec![def_name.clone()], - }; - - let thm = TheoremVal { - cnst: ConstantVal { - name: thm_name.clone(), - level_params: vec![], - typ: prop.clone(), - }, - value: prop, - all: vec![thm_name.clone()], - }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(axiom_name.clone(), LeanConstantInfo::AxiomInfo(axiom)); - lean_env.insert(def_name.clone(), LeanConstantInfo::DefnInfo(def)); - lean_env.insert(thm_name.clone(), LeanConstantInfo::ThmInfo(thm)); - let lean_env = Arc::new(lean_env); - - // Compile - let stt = compile_env(&lean_env).expect("compile_env failed"); - assert_eq!(stt.env.const_count(), 3); - - // Decompile - let dstt = decompile_env(&stt).expect("decompile_env failed"); - - // Check all constants roundtrip - assert!(matches!( - &*dstt.env.get(&axiom_name).unwrap(), - LeanConstantInfo::AxiomInfo(_) - )); - assert!(matches!( - &*dstt.env.get(&def_name).unwrap(), - LeanConstantInfo::DefnInfo(_) - )); - assert!(matches!( - &*dstt.env.get(&thm_name).unwrap(), - LeanConstantInfo::ThmInfo(_) - )); - } - - #[test] - fn test_roundtrip_inductive_simple() { - use crate::decompile::decompile_env; - use ix_common::env::{ - ConstantVal, ConstructorVal, Env as LeanEnv, InductiveVal, - }; - - // Create a simple inductive: inductive Unit : Type where | unit : Unit - // No recursor to keep it simple and self-contained - let unit_name = Name::str(Name::anon(), "Unit".to_string()); - let unit_ctor_name = Name::str(unit_name.clone(), "unit".to_string()); - - let type0 = LeanExpr::sort(Level::succ(Level::zero())); // Type - - // Unit : Type - let inductive = InductiveVal { - cnst: ConstantVal { - name: unit_name.clone(), - level_params: vec![], - typ: type0.clone(), - }, - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: vec![unit_name.clone()], - ctors: vec![unit_ctor_name.clone()], - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }; - - // Unit.unit : Unit - let ctor = ConstructorVal { - cnst: ConstantVal { - name: unit_ctor_name.clone(), - level_params: vec![], - typ: LeanExpr::cnst(unit_name.clone(), vec![]), - }, - induct: unit_name.clone(), - cidx: Nat::from(0u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(0u64), - is_unsafe: false, - }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert( - unit_name.clone(), - LeanConstantInfo::InductInfo(inductive.clone()), - ); - lean_env - .insert(unit_ctor_name.clone(), LeanConstantInfo::CtorInfo(ctor.clone())); - let lean_env = Arc::new(lean_env); - - // Compile - let stt = compile_env(&lean_env).expect("compile_env failed"); - - // Decompile - let dstt = decompile_env(&stt).expect("decompile_env failed"); - - // Check roundtrip for inductive - let recovered_ind = dstt.env.get(&unit_name).expect("Unit not found"); - match &*recovered_ind { - LeanConstantInfo::InductInfo(i) => { - assert_eq!(i.cnst.name, unit_name); - assert_eq!(i.ctors.len(), 1); - assert_eq!(i.all.len(), 1); - }, - _ => panic!("Expected InductInfo"), - } - - // Check roundtrip for constructor - let recovered_ctor = - dstt.env.get(&unit_ctor_name).expect("Unit.unit not found"); - match &*recovered_ctor { - LeanConstantInfo::CtorInfo(c) => { - assert_eq!(c.cnst.name, unit_ctor_name); - assert_eq!(c.induct, unit_name); - }, - _ => panic!("Expected CtorInfo"), - } - } - - #[test] - fn test_roundtrip_inductive_with_multiple_ctors() { - use crate::decompile::decompile_env; - use ix_common::env::{ - ConstantVal, ConstructorVal, Env as LeanEnv, InductiveVal, - }; - - // Create Bool with two constructors (no recursor to keep self-contained) - let bool_name = Name::str(Name::anon(), "Bool".to_string()); - let false_name = Name::str(bool_name.clone(), "false".to_string()); - let true_name = Name::str(bool_name.clone(), "true".to_string()); - - let type0 = LeanExpr::sort(Level::succ(Level::zero())); - let bool_type = LeanExpr::cnst(bool_name.clone(), vec![]); - - let inductive = InductiveVal { - cnst: ConstantVal { - name: bool_name.clone(), - level_params: vec![], - typ: type0, - }, - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: vec![bool_name.clone()], - ctors: vec![false_name.clone(), true_name.clone()], - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }; - - let ctor_false = ConstructorVal { - cnst: ConstantVal { - name: false_name.clone(), - level_params: vec![], - typ: bool_type.clone(), - }, - induct: bool_name.clone(), - cidx: Nat::from(0u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(0u64), - is_unsafe: false, - }; - - let ctor_true = ConstructorVal { - cnst: ConstantVal { - name: true_name.clone(), - level_params: vec![], - typ: bool_type.clone(), - }, - induct: bool_name.clone(), - cidx: Nat::from(1u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(0u64), - is_unsafe: false, - }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(bool_name.clone(), LeanConstantInfo::InductInfo(inductive)); - lean_env.insert(false_name.clone(), LeanConstantInfo::CtorInfo(ctor_false)); - lean_env.insert(true_name.clone(), LeanConstantInfo::CtorInfo(ctor_true)); - let lean_env = Arc::new(lean_env); - - // Compile - let stt = compile_env(&lean_env).expect("compile_env failed"); - - // Decompile - let dstt = decompile_env(&stt).expect("decompile_env failed"); - - // Check roundtrip - let recovered = dstt.env.get(&bool_name).expect("Bool not found"); - match &*recovered { - LeanConstantInfo::InductInfo(i) => { - assert_eq!(i.cnst.name, bool_name); - assert_eq!(i.ctors.len(), 2); - }, - _ => panic!("Expected InductInfo"), - } - - // Check both constructors - assert!(dstt.env.contains_key(&false_name)); - assert!(dstt.env.contains_key(&true_name)); - } - - #[test] - fn test_roundtrip_mutual_definitions() { - use crate::decompile::decompile_env; - use ix_common::env::{ - ConstantVal, DefinitionSafety, DefinitionVal, Env as LeanEnv, - ReducibilityHints, - }; - - // Create mutual definitions that only reference each other (self-contained) - // def f : Type → Type and def g : Type → Type - // where f references g and g references f - let f_name = Name::str(Name::anon(), "f".to_string()); - let g_name = Name::str(Name::anon(), "g".to_string()); - - let type0 = LeanExpr::sort(Level::succ(Level::zero())); // Type - let fn_type = LeanExpr::all( - Name::anon(), - type0.clone(), - type0.clone(), - ix_common::env::BinderInfo::Default, - ); - - // f := fun x => g x - let f_value = LeanExpr::lam( - Name::str(Name::anon(), "x".to_string()), - type0.clone(), - LeanExpr::app( - LeanExpr::cnst(g_name.clone(), vec![]), - LeanExpr::bvar(Nat::from(0u64)), - ), - ix_common::env::BinderInfo::Default, - ); - - // g := fun x => f x - let g_value = LeanExpr::lam( - Name::str(Name::anon(), "x".to_string()), - type0.clone(), - LeanExpr::app( - LeanExpr::cnst(f_name.clone(), vec![]), - LeanExpr::bvar(Nat::from(0u64)), - ), - ix_common::env::BinderInfo::Default, - ); - - // Mutual block: both reference each other - let all = vec![f_name.clone(), g_name.clone()]; - - let f_def = DefinitionVal { - cnst: ConstantVal { - name: f_name.clone(), - level_params: vec![], - typ: fn_type.clone(), - }, - value: f_value, - hints: ReducibilityHints::Regular(1), - safety: DefinitionSafety::Safe, - all: all.clone(), - }; - - let g_def = DefinitionVal { - cnst: ConstantVal { - name: g_name.clone(), - level_params: vec![], - typ: fn_type, - }, - value: g_value, - hints: ReducibilityHints::Regular(1), - safety: DefinitionSafety::Safe, - all: all.clone(), - }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(f_name.clone(), LeanConstantInfo::DefnInfo(f_def)); - lean_env.insert(g_name.clone(), LeanConstantInfo::DefnInfo(g_def)); - let lean_env = Arc::new(lean_env); - - // Compile - let stt = compile_env(&lean_env).expect("compile_env failed"); - - // f and g are alpha-equivalent (both `λ x => other x` with the same - // type), but the block is compiled as Lean gives it: one block of two - // members, not merged. - assert_eq!(stt.blocks.len(), 2, "the mutual block has an entry per member"); - - // Decompile - let dstt = decompile_env(&stt).expect("decompile_env failed"); - - // Check both definitions roundtrip - let recovered_f = dstt.env.get(&f_name).expect("f not found"); - match &*recovered_f { - LeanConstantInfo::DefnInfo(d) => { - assert_eq!(d.cnst.name, f_name); - // The all field should contain both names - assert_eq!(d.all.len(), 2); - }, - _ => panic!("Expected DefnInfo for f"), - } - - let recovered_g = dstt.env.get(&g_name).expect("g not found"); - match &*recovered_g { - LeanConstantInfo::DefnInfo(d) => { - assert_eq!(d.cnst.name, g_name); - assert_eq!(d.all.len(), 2); - }, - _ => panic!("Expected DefnInfo for g"), - } - } - - #[test] - fn test_roundtrip_mutual_inductives() { - use crate::decompile::decompile_env; - use ix_common::env::{ - ConstantVal, ConstructorVal, Env as LeanEnv, InductiveVal, - }; - - // Create two mutually recursive inductives (simplified): - // inductive Even : Type where | zero : Even | succ : Odd → Even - // inductive Odd : Type where | succ : Even → Odd - let even_name = Name::str(Name::anon(), "Even".to_string()); - let odd_name = Name::str(Name::anon(), "Odd".to_string()); - let even_zero = Name::str(even_name.clone(), "zero".to_string()); - let even_succ = Name::str(even_name.clone(), "succ".to_string()); - let odd_succ = Name::str(odd_name.clone(), "succ".to_string()); - - let type0 = LeanExpr::sort(Level::succ(Level::zero())); // Type - let even_type = LeanExpr::cnst(even_name.clone(), vec![]); - let odd_type = LeanExpr::cnst(odd_name.clone(), vec![]); - - let all = vec![even_name.clone(), odd_name.clone()]; - - let even_ind = InductiveVal { - cnst: ConstantVal { - name: even_name.clone(), - level_params: vec![], - typ: type0.clone(), - }, - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: all.clone(), - ctors: vec![even_zero.clone(), even_succ.clone()], - num_nested: Nat::from(0u64), - is_rec: true, // mutually recursive - is_unsafe: false, - is_reflexive: false, - }; - - let odd_ind = InductiveVal { - cnst: ConstantVal { - name: odd_name.clone(), - level_params: vec![], - typ: type0.clone(), - }, - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: all.clone(), - ctors: vec![odd_succ.clone()], - num_nested: Nat::from(0u64), - is_rec: true, - is_unsafe: false, - is_reflexive: false, - }; - - // Even.zero : Even - let even_zero_ctor = ConstructorVal { - cnst: ConstantVal { - name: even_zero.clone(), - level_params: vec![], - typ: even_type.clone(), - }, - induct: even_name.clone(), - cidx: Nat::from(0u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(0u64), - is_unsafe: false, - }; - - // Even.succ : Odd → Even - let even_succ_type = LeanExpr::all( - Name::anon(), - odd_type.clone(), - even_type.clone(), - ix_common::env::BinderInfo::Default, - ); - - let even_succ_ctor = ConstructorVal { - cnst: ConstantVal { - name: even_succ.clone(), - level_params: vec![], - typ: even_succ_type, - }, - induct: even_name.clone(), - cidx: Nat::from(1u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(1u64), - is_unsafe: false, - }; - - // Odd.succ : Even → Odd - let odd_succ_type = LeanExpr::all( - Name::anon(), - even_type.clone(), - odd_type.clone(), - ix_common::env::BinderInfo::Default, - ); - - let odd_succ_ctor = ConstructorVal { - cnst: ConstantVal { - name: odd_succ.clone(), - level_params: vec![], - typ: odd_succ_type, - }, - induct: odd_name.clone(), - cidx: Nat::from(0u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(1u64), - is_unsafe: false, - }; - - let mut lean_env = LeanEnv::default(); - lean_env.insert(even_name.clone(), LeanConstantInfo::InductInfo(even_ind)); - lean_env.insert(odd_name.clone(), LeanConstantInfo::InductInfo(odd_ind)); - lean_env - .insert(even_zero.clone(), LeanConstantInfo::CtorInfo(even_zero_ctor)); - lean_env - .insert(even_succ.clone(), LeanConstantInfo::CtorInfo(even_succ_ctor)); - lean_env - .insert(odd_succ.clone(), LeanConstantInfo::CtorInfo(odd_succ_ctor)); - let lean_env = Arc::new(lean_env); - - // Compile - let stt = compile_env(&lean_env).expect("compile_env failed"); - - // Should have at least one mutual block - assert!(!stt.blocks.is_empty(), "Expected mutual block for Even/Odd"); - - // Decompile - let dstt = decompile_env(&stt).expect("decompile_env failed"); - - // Check Even roundtrip - let recovered_even = dstt.env.get(&even_name).expect("Even not found"); - match &*recovered_even { - LeanConstantInfo::InductInfo(i) => { - assert_eq!(i.cnst.name, even_name); - assert_eq!(i.ctors.len(), 2); - assert_eq!(i.all.len(), 2); // Even and Odd in mutual block - }, - _ => panic!("Expected InductInfo for Even"), - } - - // Check Odd roundtrip - let recovered_odd = dstt.env.get(&odd_name).expect("Odd not found"); - match &*recovered_odd { - LeanConstantInfo::InductInfo(i) => { - assert_eq!(i.cnst.name, odd_name); - assert_eq!(i.ctors.len(), 1); - assert_eq!(i.all.len(), 2); - }, - _ => panic!("Expected InductInfo for Odd"), - } - - // Check all constructors exist - assert!(dstt.env.contains_key(&even_zero)); - assert!(dstt.env.contains_key(&even_succ)); - assert!(dstt.env.contains_key(&odd_succ)); - } - - #[test] - fn test_roundtrip_inductive_with_recursor() { - use crate::decompile::decompile_env; - use ix_common::env::{ConstantVal, InductiveVal, RecursorVal}; - - // Create Empty type with recursor (no constructors) - // inductive Empty : Type - // Empty.rec.{u} : (motive : Empty → Sort u) → (e : Empty) → motive e - let empty_name = Name::str(Name::anon(), "Empty".to_string()); - let empty_rec_name = Name::str(empty_name.clone(), "rec".to_string()); - let u = Name::str(Name::anon(), "u".to_string()); - - let type0 = LeanExpr::sort(Level::succ(Level::zero())); // Type - let empty_type = LeanExpr::cnst(empty_name.clone(), vec![]); - - let inductive = InductiveVal { - cnst: ConstantVal { - name: empty_name.clone(), - level_params: vec![], - typ: type0.clone(), - }, - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: vec![empty_name.clone()], - ctors: vec![], // No constructors! - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }; - - // Empty.rec.{u} : (motive : Empty → Sort u) → (e : Empty) → motive e - let motive_type = LeanExpr::all( - Name::anon(), - empty_type.clone(), - LeanExpr::sort(Level::param(u.clone())), - ix_common::env::BinderInfo::Default, - ); - let rec_type = LeanExpr::all( - Name::str(Name::anon(), "motive".to_string()), - motive_type, - LeanExpr::all( - Name::str(Name::anon(), "e".to_string()), - empty_type.clone(), - LeanExpr::app( - LeanExpr::bvar(Nat::from(1u64)), - LeanExpr::bvar(Nat::from(0u64)), - ), - ix_common::env::BinderInfo::Default, - ), - ix_common::env::BinderInfo::Implicit, - ); - - let recursor = RecursorVal { - cnst: ConstantVal { - name: empty_rec_name.clone(), - level_params: vec![u.clone()], - typ: rec_type, - }, - all: vec![empty_name.clone()], - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - num_motives: Nat::from(1u64), - num_minors: Nat::from(0u64), // No minor premises for Empty - rules: vec![], // No rules since no constructors - k: true, - is_unsafe: false, - }; - - let mut lean_env = LeanEnv::default(); - lean_env - .insert(empty_name.clone(), LeanConstantInfo::InductInfo(inductive)); - lean_env - .insert(empty_rec_name.clone(), LeanConstantInfo::RecInfo(recursor)); - let lean_env = Arc::new(lean_env); - - // Compile - let stt = compile_env(&lean_env).expect("compile_env failed"); - - // Decompile - let dstt = decompile_env(&stt).expect("decompile_env failed"); - - // Check inductive roundtrip - let recovered_ind = dstt.env.get(&empty_name).expect("Empty not found"); - match &*recovered_ind { - LeanConstantInfo::InductInfo(i) => { - assert_eq!(i.cnst.name, empty_name); - assert_eq!(i.ctors.len(), 0); - }, - _ => panic!("Expected InductInfo"), - } - - // Check recursor roundtrip - let recovered_rec = - dstt.env.get(&empty_rec_name).expect("Empty.rec not found"); - match &*recovered_rec { - LeanConstantInfo::RecInfo(r) => { - assert_eq!(r.cnst.name, empty_rec_name); - assert_eq!(r.rules.len(), 0); - assert_eq!(r.cnst.level_params.len(), 1); - }, - _ => panic!("Expected RecInfo"), - } - } - - // ========================================================================== - // The canonical sharing route - // ========================================================================== - - /// The `T2 → T2` witness of `docs/sharing-minimum.md` §2 as an axiom - /// payload (with univs `[Zero]` and no refs). - fn t2_arrow_t2() -> Axiom { - let p = Expr::sort(0); - let t1 = Expr::all(p.clone(), p.clone()); - let t2 = Expr::all(p, t1); - Axiom { is_unsafe: false, lvls: 0, typ: Expr::all(t2.clone(), t2) } - } - - fn constant_hex(c: &Constant) -> String { - let mut buf = Vec::new(); - c.put(&mut buf); - buf.iter().map(|b| format!("{b:02x}")).collect() - } - - /// The compiler route is the canonical construction: it reaches the - /// 17-byte minimum of the witness. - #[test] - fn compiler_route_reaches_the_17_byte_minimum() { - let r = apply_sharing_to_axiom_with_limits( - &ExactSharingLimits::default(), - t2_arrow_t2(), - vec![], - vec![Univ::zero()], - ) - .unwrap(); - assert_eq!(constant_hex(&r), "d200009117b0b001921700170000000100"); - } - - /// The compiler route builds exactly what the library normalizer builds - /// from the unshared Constant, for every payload kind it dispatches on. - #[test] - fn compiler_route_matches_the_normalizer() { - use ixon::constant::{DefKind, Definition, MutConst as IxonMutConst}; - use ixon::sharing_exact::normalize_constant_sharing_tiered; - let ax = t2_arrow_t2(); - let def = Definition { - kind: DefKind::Definition, - safety: ix_common::env::DefinitionSafety::Safe, - lvls: 0, - typ: ax.typ.clone(), - value: Expr::app(ax.typ.clone(), ax.typ.clone()), - }; - let limits = ExactSharingLimits::default(); - let univs = vec![Univ::zero()]; - let cases = [ - ( - ConstantInfo::Axio(ax.clone()), - apply_sharing_to_axiom_with_limits( - &limits, - ax.clone(), - vec![], - univs.clone(), - ) - .unwrap(), - ), - ( - ConstantInfo::Defn(def.clone()), - apply_sharing_to_definition_with_limits( - &limits, - def.clone(), - vec![], - univs.clone(), - ) - .unwrap(), - ), - ( - ConstantInfo::Muts(vec![ - IxonMutConst::Defn(def.clone()), - IxonMutConst::Defn(def.clone()), - ]), - apply_sharing_to_mutual_block_with_limits( - &limits, - vec![IxonMutConst::Defn(def.clone()), IxonMutConst::Defn(def)], - vec![], - univs.clone(), - ) - .unwrap(), - ), - ]; - for (info, routed) in cases { - let unshared = Constant::with_tables(info, vec![], vec![], univs.clone()); - let (normalized, _) = normalize_constant_sharing_tiered( - ShareLayout::TagN, - &unshared, - &limits, - ) - .unwrap(); - assert_eq!(routed, normalized); - } - } - - /// A construction that runs out of its limits is a compile error; there is - /// no fallback. - #[test] - fn compiler_route_fails_closed() { - let limits = ExactSharingLimits { - max_distinct_nodes: 2, - ..ExactSharingLimits::default() - }; - let err = apply_sharing_to_axiom_with_limits( - &limits, - t2_arrow_t2(), - vec![], - vec![Univ::zero()], - ) - .expect_err("the route must fail under these limits"); - assert!(matches!(err, CompileError::ResourceLimit { .. }), "{err}"); - // The error names the limit and how to raise it. - let msg = format!("{err}"); - assert!( - msg.contains("resource exhausted: distinct_nodes (limit 2)") - && msg.contains("--sharing-limits distinct_nodes=N") - && msg.contains(SHARING_LIMITS_ENV), - "{msg}" - ); - } - - /// The compiler limits are the library defaults plus the override, and an - /// invalid override is an error that names the variable. - #[test] - fn compiler_sharing_limits_override() { - assert_eq!( - compiler_sharing_limits_with(None).unwrap(), - ExactSharingLimits::default() - ); - let l = compiler_sharing_limits_with(Some("states=2^10, work=7,depth=3")) - .unwrap(); - assert_eq!((l.max_states, l.max_work), (1024, 7)); - assert_eq!(l.max_height, ExactSharingLimits::default().max_height); - let err = compiler_sharing_limits_with(Some("bogus=1")).unwrap_err(); - assert!(err.starts_with("IX_SHARING_LIMITS: unknown sharing limit bogus")); - } - - /// A block refused for a conflicting claim leaves the owner's `Named` - /// entry as it was (the claim comes before the registration, as the Lean - /// drivers merge a block's state only after its claims check), and its - /// failed transaction discards all of its staged writes. - #[test] - fn refused_conflicting_claim_keeps_the_owner_named() { - use ix_common::env::{ - ConstantVal, DefinitionSafety, DefinitionVal, ReducibilityHints, - }; - let f = Name::str(Name::anon(), "f".to_string()); - let g = Name::str(Name::anon(), "g".to_string()); - let def = |n: &Name, other: &Name| { - LeanConstantInfo::DefnInfo(DefinitionVal { - cnst: ConstantVal { - name: n.clone(), - level_params: vec![], - typ: LeanExpr::sort(Level::succ(Level::zero())), - }, - value: LeanExpr::cnst(other.clone(), vec![]), - hints: ReducibilityHints::Opaque, - safety: DefinitionSafety::Safe, - all: vec![f.clone(), g.clone()], - }) - }; - let mut lean_env = LeanEnv::default(); - lean_env.insert(f.clone(), def(&f, &g)); - lean_env.insert(g.clone(), def(&g, &f)); - let lean_env = Arc::new(lean_env); - let stt = CompileState::default(); - // `g` is owned by another block, and its entry carries an original form - let owner = Address::hash(b"owner"); - let owner_original = Address::hash(b"owner-original"); - let mut named = Named::new(owner.clone(), ConstantMeta::default()); - named.set_original(owner_original.clone(), ConstantMeta::default()); - stt.claim_compiled_name(&g, &owner).unwrap(); - stt.register_named(g.clone(), named); - let all: NameSet = [f.clone(), g.clone()].into_iter().collect(); - block_txn::start(); - let err = - compile_const(&f, &all, &lean_env, &mut BlockCache::default(), &stt) - .unwrap_err() - .to_string(); - block_txn::finish(&stt, true).unwrap(); - assert!(err.contains("conflicting claims for name 'g'"), "{err}"); - let kept = stt.env.named.get(&g).map(|r| r.clone()).expect("owner named"); - assert_eq!(kept.addr, owner); - assert_eq!(kept.original().map(|(a, _)| a), Some(owner_original)); - assert_eq!(stt.name_to_addr.get(&g).map(|r| r.clone()), Some(owner)); - assert!(!stt.env.named.contains_key(&f)); - assert!(!stt.name_to_addr.contains_key(&f)); - } - - fn axio_meta(tag: &[u8]) -> ConstantMeta { - ConstantMeta::new(ConstantMetaInfo::Axio { - name: Address::hash(tag), - lvls: vec![], - arena: Default::default(), - type_root: 0, - }) - } - - #[test] - fn identical_reclaim_keeps_the_owner_named() { - // A later block's identical re-claim of a name another block owns - // registers nothing. The owner's entry (its metadata, its original form, - // its hints) is kept, and the re-claiming block's rollback leaves it as - // it was. - use ix_common::env::ReducibilityHints; - let n = Name::str(Name::anon(), "n".to_string()); - let addr = Address::hash(b"shared"); - let owner_original = Address::hash(b"owner-original"); - let stt = CompileState::default(); - // the owner's block, then its original form and hints - block_txn::start(); - stt - .claim_and_register_compiled( - &n, - Named::new(addr.clone(), axio_meta(b"owner")), - ) - .unwrap(); - block_txn::finish(&stt, false).unwrap(); - { - let mut entry = stt.env.named.get_mut(&n).unwrap(); - entry - .value_mut() - .set_original(owner_original.clone(), ConstantMeta::default()); - entry.value_mut().set_hints(Some(ReducibilityHints::Abbrev)); - } - let check = |stage: &str| { - let kept = stt.env.named.get(&n).map(|r| r.clone()).expect("owner named"); - assert_eq!(kept.addr, addr, "{stage}"); - assert_eq!(*kept.meta(), axio_meta(b"owner"), "{stage}"); - assert_eq!( - kept.original().map(|(a, _)| a), - Some(owner_original.clone()), - "{stage}" - ); - assert_eq!(kept.hints(), Some(ReducibilityHints::Abbrev), "{stage}"); - assert_eq!(stt.resolve_addr(&n), Some(addr.clone()), "{stage}"); - }; - // a later block re-claims `n` at the same address with other metadata - block_txn::start(); - stt - .claim_and_register_compiled( - &n, - Named::new(addr.clone(), axio_meta(b"later")), - ) - .unwrap(); - check("after the identical re-claim"); - // ... and fails: its rollback has nothing of the owner's to undo - block_txn::finish(&stt, true).unwrap(); - check("after the re-claiming block's rollback"); - // outside a block transaction (direct callers) the same holds - stt - .claim_and_register_compiled( - &n, - Named::new(addr.clone(), axio_meta(b"direct")), - ) - .unwrap(); - check("after a direct identical re-claim"); - } - - #[test] - fn reclaim_within_one_block_reregisters() { - // A name one block claims twice is still that block's: the later - // registration wins, and the block's failure removes the entry with its - // claim. - let n = Name::str(Name::anon(), "n".to_string()); - let addr = Address::hash(b"own"); - let stt = CompileState::default(); - block_txn::start(); - stt - .claim_and_register_compiled( - &n, - Named::new(addr.clone(), axio_meta(b"first")), - ) - .unwrap(); - stt - .claim_and_register_compiled( - &n, - Named::new(addr.clone(), axio_meta(b"second")), - ) - .unwrap(); - let named = block_txn::with(|txn| { - txn.unwrap().named.get(&n).cloned().expect("own staged named") - }); - assert_eq!(*named.meta(), axio_meta(b"second")); - assert!(!stt.env.named.contains_key(&n)); - block_txn::finish(&stt, true).unwrap(); - assert!(!stt.env.named.contains_key(&n)); - assert!(stt.resolve_addr(&n).is_none()); - } -} diff --git a/crates/compile/src/compile/admission.rs b/crates/compile/src/compile/admission.rs deleted file mode 100644 index 8caa940a9..000000000 --- a/crates/compile/src/compile/admission.rs +++ /dev/null @@ -1,645 +0,0 @@ -//! Non-preemptive memory-aware admission for the main dependency scheduler. -//! -//! A main-compiler block publishes shared names and auxiliary metadata before -//! returning, unlike a pure inductive validation. It cannot safely be retried -//! after arbitrary cancellation. Permits are therefore acquired BEFORE -//! dequeuing work and released AFTER block-local scratch and publication. -//! Backoff stops admissions; already-active blocks keep running, never park -//! holding their scratch. This is not an allocator-enforced memory limit. - -use std::sync::{Arc, Condvar, Mutex}; -use std::thread::{self, JoinHandle}; -use std::time::{Duration, Instant}; - -use super::memory::{ - GIB, Memory, MemoryReader, Pressure, budget_from_env, pressure, ramp_up, - resource_error, -}; -use ixon::CompileError; - -/// The largest ramp step when the growth forecast is close to the headroom. -const RAMP_NEAR_STEP: usize = 8; - -#[derive(Clone, Copy)] -struct Options { - max_workers: usize, - process_budget: Option, - tick: Duration, - ramp_interval: Duration, - recovery: Duration, - idle_timeout: Duration, - lookahead: Duration, - verbose: bool, -} - -impl Options { - fn from_env(max_workers: usize) -> Result { - Ok(Self { - max_workers: max_workers.max(1), - process_budget: budget_from_env("IX_COMPILE_MEMORY_GIB")?, - tick: Duration::from_millis(250), - // Two ticks: the growth forecast samples a raised limit before the - // next doubling (`ramp_up`). - ramp_interval: Duration::from_millis(500), - recovery: Duration::from_secs(5), - idle_timeout: Duration::from_secs(30), - lookahead: Duration::from_secs(10), - verbose: *super::env::IX_VERBOSE, - }) - } -} - -struct Policy { - limit: usize, - open: bool, - pressure: Pressure, - previous: Memory, - sampled: Instant, - ramped: Instant, - pressured: Option, - growth_per_second: u64, - projected_growth: u64, - completed: u64, -} - -impl Policy { - fn new(memory: Memory, now: Instant, options: Options) -> Self { - let pressure = - pressure(memory, memory, options.tick, options.process_budget); - Self { - limit: options.max_workers.min(2), - open: pressure == Pressure::Healthy, - pressure, - previous: memory, - sampled: now, - ramped: now, - pressured: (pressure != Pressure::Healthy).then_some(now), - growth_per_second: 0, - projected_growth: 0, - completed: 0, - } - } - - fn sample( - &mut self, - memory: Memory, - now: Instant, - active: usize, - completed: u64, - options: Options, - ) { - let elapsed = now.duration_since(self.sampled); - let delta = memory - .process - .saturating_sub(self.previous.process) - .max(self.previous.available.saturating_sub(memory.available)); - // Fast rise, gradual decay: reserve space for another ten seconds of - // recent growth, including external pressure, before starting more work. - // This is process-level prediction, NOT an attributed per-block footprint. - let growth = (u128::from(delta) * 1_000_000 / elapsed.as_micros().max(1)) - .min(u128::from(u64::MAX)) as u64; - self.growth_per_second = - growth.max(self.growth_per_second.saturating_mul(3) / 4); - self.projected_growth = - self.growth_per_second.saturating_mul(options.lookahead.as_secs()); - let reserve = memory.reserve(options.process_budget); - let headroom = memory.headroom(options.process_budget); - self.pressure = - pressure(memory, self.previous, elapsed, options.process_budget); - let forecast_tight = - headroom < reserve.saturating_add(self.projected_growth); - let reclaiming = super::memory::reclaiming(memory, self.previous, elapsed); - let blocked = - self.pressure == Pressure::Critical || reclaiming || forecast_tight; - let wide_headroom = self.pressure == Pressure::Healthy - && headroom - >= reserve.saturating_mul(3).saturating_add(self.projected_growth); - if blocked { - self.limit = self.limit.min((active / 2).max(1)); - self.pressured = Some(now); - } else if !wide_headroom { - // Retained, successfully compiled data cannot be reclaimed by reducing - // workers. With stable memory and a reserve, allow one job at a time - // instead of waiting forever for the accumulator to shrink. - self.limit = 1; - } - let recovered = - self.pressured.is_none_or(|t| now.duration_since(t) >= options.recovery); - self.open = !blocked && recovered; - // A long-running block with no completions is not evidence that it is - // safe to ramp up. Require actual completed work before raising the cap. - if self.open - && wide_headroom - && completed > self.completed - && now.duration_since(self.ramped) >= options.ramp_interval - { - // Double while the headroom above the Hold threshold covers four times - // the growth forecast. Closer to it, add at most `RAMP_NEAR_STEP`: - // doubling a large limit there starts a burst whose forecast closes - // admission for the whole recovery period. - let spare = headroom.saturating_sub(reserve.saturating_mul(3)); - self.limit = if spare >= self.projected_growth.saturating_mul(4) { - ramp_up(self.limit, options.max_workers) - } else { - let step = self.limit.min(RAMP_NEAR_STEP); - self.limit.saturating_add(step).min(options.max_workers) - }; - self.ramped = now; - } - self.previous = memory; - self.sampled = now; - self.completed = completed; - } -} - -struct State { - policy: Option, - active: usize, - completed: u64, - stopped: bool, - error: Option, - idle_since: Option, -} -struct Shared { - options: Options, - state: Mutex, - changed: Condvar, - shutdown: Condvar, -} - -pub(super) struct Admission { - shared: Arc, - monitor: Option>, -} - -/// Borrowed permit: no per-block thread or Arc allocation. -pub(super) struct Permit<'a> { - shared: &'a Shared, - completed: bool, -} -impl Permit<'_> { - pub(super) fn complete(mut self) { - self.completed = true; - } -} -impl Drop for Permit<'_> { - fn drop(&mut self) { - let mut state = self.shared.state.lock().unwrap(); - state.active -= 1; - state.completed += u64::from(self.completed); - if state.active == 0 { - state.idle_since = Some(Instant::now()); - } - self.shared.changed.notify_one(); - } -} - -impl Admission { - pub(super) fn new(max_workers: usize) -> Result { - let options = Options::from_env(max_workers)?; - let disabled = std::env::var("IX_COMPILE_ADAPTIVE").as_deref() == Ok("0"); - if disabled && options.process_budget.is_some() { - return Err(resource_error( - "IX_COMPILE_MEMORY_GIB cannot be combined with IX_COMPILE_ADAPTIVE=0", - )); - } - let mut reader = MemoryReader::new(); - let initial = if disabled { None } else { reader.read() }; - if initial.is_none() && options.process_budget.is_some() { - return Err(resource_error( - "IX_COMPILE_MEMORY_GIB requires Linux memory telemetry", - )); - } - Self::with_reader(options, initial, move || reader.read()) - } - - fn with_reader( - options: Options, - initial: Option, - mut read: impl FnMut() -> Option + Send + 'static, - ) -> Result { - let now = Instant::now(); - let shared = Arc::new(Shared { - options, - state: Mutex::new(State { - policy: initial.map(|memory| Policy::new(memory, now, options)), - active: 0, - completed: 0, - stopped: false, - error: None, - idle_since: Some(now), - }), - changed: Condvar::new(), - shutdown: Condvar::new(), - }); - if options.verbose { - eprintln!( - "[compile_memory] {}: initial={} max={} budget_gib={:?}; active blocks finish before slots are reused", - if initial.is_some() { - "adaptive admission" - } else { - "fixed admission (disabled or telemetry unavailable)" - }, - if initial.is_some() { - options.max_workers.min(2) - } else { - options.max_workers - }, - options.max_workers, - options.process_budget.map(|v| v as f64 / GIB as f64) - ); - } - let monitor = if initial.is_some() { - let shared = Arc::clone(&shared); - Some(thread::Builder::new().name("ix-compile-memory".into()).spawn(move || { - let mut logged = now; - loop { - let mut state = shared.state.lock().unwrap(); - let deadline = state.policy.as_ref().unwrap().sampled + options.tick; - // Permit releases wake waiters often; they must not cause /proc - // sampling on every block (millions of blocks in a large env). - while !state.stopped && Instant::now() < deadline { - let wait = deadline.saturating_duration_since(Instant::now()); - state = shared.shutdown.wait_timeout(state, wait).unwrap().0; - } - if state.stopped { return; } - drop(state); - let sample = read(); - let mut state = shared.state.lock().unwrap(); - if state.stopped { return; } - let Some(memory) = sample else { - state.error = Some(resource_error("memory telemetry became unavailable during main compilation")); - state.stopped = true; - shared.changed.notify_all(); - return; - }; - let now = Instant::now(); - let active = state.active; - let completed = state.completed; - let policy = state.policy.as_mut().unwrap(); - let before = (policy.limit, policy.open, policy.pressure); - policy.sample(memory, now, active, completed, options); - if options.verbose && (before != (policy.limit, policy.open, policy.pressure) - || now.duration_since(logged) >= Duration::from_secs(5)) { - eprintln!("[compile_memory] {:?}: limit={}/{} active={active} admitting={} completed={completed} available={:.1} GiB resident+swap={:.1} GiB growth_forecast={:.1} GiB", - policy.pressure, policy.limit, options.max_workers, policy.open, - memory.available as f64 / GIB as f64, memory.process as f64 / GIB as f64, - policy.projected_growth as f64 / GIB as f64); - logged = now; - } - shared.changed.notify_all(); - } - }).map_err(|e| resource_error(format!("could not start memory admission monitor: {e}")))?) - } else { - None - }; - Ok(Self { shared, monitor }) - } - - pub(super) fn acquire(&self) -> Result>, CompileError> { - let mut state = self.shared.state.lock().unwrap(); - loop { - if let Some(error) = &state.error { - return Err(error.clone()); - } - if state.stopped { - return Ok(None); - } - let (open, limit) = state - .policy - .as_ref() - .map_or((true, self.shared.options.max_workers), |p| (p.open, p.limit)); - if open && state.active < limit { - state.active += 1; - state.idle_since = None; - return Ok(Some(Permit { shared: &self.shared, completed: false })); - } - if state.active == 0 { - let start = *state.idle_since.get_or_insert_with(Instant::now); - if start.elapsed() >= self.shared.options.idle_timeout { - let error = resource_error(format!( - "not enough memory headroom to admit a compiler block; no active work for {:.0}s. Increase IX_COMPILE_MEMORY_GIB if set, or make more RAM available", - self.shared.options.idle_timeout.as_secs_f64() - )); - state.error = Some(error.clone()); - state.stopped = true; - self.shared.changed.notify_all(); - self.shared.shutdown.notify_all(); - return Err(error); - } - } - state = self - .shared - .changed - .wait_timeout(state, self.shared.options.tick) - .unwrap() - .0; - } - } - - pub(super) fn stop(&self) { - self.shared.state.lock().unwrap().stopped = true; - self.shared.changed.notify_all(); - self.shared.shutdown.notify_all(); - } - - pub(super) fn check(&self) -> Result<(), CompileError> { - self.shared.state.lock().unwrap().error.clone().map_or(Ok(()), Err) - } -} -impl Drop for Admission { - fn drop(&mut self) { - self.stop(); - if let Some(monitor) = self.monitor.take() { - monitor.join().unwrap(); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::compile::memory::MIB; - use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - - fn memory() -> Memory { - Memory { - capacity: 100 * GIB, - available: 90 * GIB, - process: 10 * GIB, - swap_used: 0, - stall_us: 0, - } - } - fn options(max_workers: usize) -> Options { - Options { - max_workers, - process_budget: None, - tick: Duration::from_millis(2), - ramp_interval: Duration::from_millis(5), - recovery: Duration::from_millis(5), - idle_timeout: Duration::from_millis(100), - lookahead: Duration::from_secs(10), - verbose: false, - } - } - fn until(mut predicate: impl FnMut() -> bool) { - let start = Instant::now(); - while !predicate() { - assert!( - start.elapsed() < Duration::from_secs(5), - "test made no progress" - ); - thread::sleep(Duration::from_millis(1)); - } - } - - #[test] - fn healthy_memory_requires_completions_to_ramp_and_respects_the_ceiling() { - let opt = options(4); - let now = Instant::now(); - let mut policy = Policy::new(memory(), now, opt); - for second in 1..10 { - policy.sample(memory(), now + Duration::from_secs(second), 2, 0, opt); - assert_eq!(policy.limit, 2, "a long job should not trigger ramp-up"); - } - for second in 10..20 { - policy.sample( - memory(), - now + Duration::from_secs(second), - 2, - second, - opt, - ); - assert!(policy.limit <= 4); - assert!(policy.open); - } - assert_eq!(policy.limit, 4); - } - - #[test] - fn growth_forecast_stops_admissions_before_low_memory_threshold() { - let opt = options(64); - let now = Instant::now(); - let mut policy = Policy::new(memory(), now, opt); - let rising = Memory { process: 16 * GIB, available: 84 * GIB, ..memory() }; - policy.sample(rising, now + Duration::from_millis(250), 2, 0, opt); - assert_eq!(policy.pressure, Pressure::Healthy); - assert_eq!(policy.projected_growth, 240 * GIB); - assert!( - !policy.open, - "high current availability must not hide a growth burst" - ); - assert_eq!(policy.limit, 1); - } - - #[test] - fn swap_pressure_reduces_limit_and_recovers_with_hysteresis() { - let opt = options(8); - let now = Instant::now(); - let mut policy = Policy::new(memory(), now, opt); - policy.limit = 8; - let swapped = Memory { swap_used: 9 * MIB, ..memory() }; - policy.sample(swapped, now + Duration::from_millis(2), 8, 0, opt); - assert_eq!(policy.pressure, Pressure::Backoff); - assert_eq!(policy.limit, 4); - assert!(!policy.open); - policy.sample(swapped, now + Duration::from_millis(4), 4, 1, opt); - assert!(!policy.open, "one healthy tick is not enough to recover"); - policy.sample(swapped, now + Duration::from_millis(20), 2, 2, opt); - assert!(policy.open); - // The recovered limit doubles (`ramp_up`), within the ceiling of 8. - assert_eq!(policy.limit, 8); - } - - #[test] - fn healthy_ramp_doubles_each_interval_up_to_the_ceiling() { - let opt = options(64); - let now = Instant::now(); - let mut policy = Policy::new(memory(), now, opt); - assert_eq!(policy.limit, 2); - let mut limits = Vec::new(); - for step in 1..=7u32 { - // One sample per ramp interval, each with new completed work. - let at = now + opt.ramp_interval * step; - policy.sample(memory(), at, policy.limit, u64::from(step), opt); - limits.push(policy.limit); - } - assert_eq!(limits, [4, 8, 16, 32, 64, 64, 64]); - // A sample before the interval has elapsed does not ramp. - let mut early = Policy::new(memory(), now, opt); - early.sample(memory(), now + opt.ramp_interval / 2, 2, 1, opt); - assert_eq!(early.limit, 2); - } - - #[test] - fn near_the_growth_forecast_the_ramp_adds_at_most_eight() { - let opt = options(64); - let now = Instant::now(); - let base = Memory { available: 50 * GIB, ..memory() }; - // Half a GiB per second of growth: a 5 GiB forecast, more than a quarter - // of the 12.5 GiB of headroom above the Hold threshold. - let mut near = Policy::new(base, now, opt); - near.limit = 16; - let growing = Memory { process: base.process + GIB / 2, ..base }; - near.sample(growing, now + Duration::from_secs(1), 16, 1, opt); - assert_eq!(near.projected_growth, 5 * GIB); - assert!(near.open); - assert_eq!(near.limit, 24); - // Without growth, the same headroom doubles the limit. - let mut far = Policy::new(base, now, opt); - far.limit = 16; - far.sample(base, now + Duration::from_secs(1), 16, 1, opt); - assert_eq!(far.limit, 32); - } - - #[test] - fn growth_after_a_doubling_halves_the_limit() { - let opt = options(64); - let now = Instant::now(); - let mut policy = Policy::new(memory(), now, opt); - policy.limit = 32; - policy.sample(memory(), now + opt.ramp_interval, 32, 1, opt); - assert_eq!(policy.limit, 64); - // Ten seconds of the observed growth would not fit above the reserve. - let rising = Memory { process: 16 * GIB, available: 84 * GIB, ..memory() }; - policy.sample(rising, now + opt.ramp_interval * 2, 64, 2, opt); - assert!(!policy.open); - assert_eq!(policy.limit, 32); - } - - #[test] - fn process_budget_uses_resident_plus_swap_headroom() { - let opt = Options { process_budget: Some(20 * GIB), ..options(8) }; - let now = Instant::now(); - let memory = Memory { process: 19 * GIB, ..memory() }; - let policy = Policy::new(memory, now, opt); - assert!(!policy.open); - assert_eq!(policy.pressure, Pressure::Critical); - } - - #[test] - fn stable_retained_data_allows_serial_forward_progress() { - let opt = options(8); - let now = Instant::now(); - let memory = Memory { process: 70 * GIB, available: 30 * GIB, ..memory() }; - let mut policy = Policy::new(memory, now, opt); - policy.sample(memory, now + Duration::from_secs(1), 0, 0, opt); - assert_eq!(policy.pressure, Pressure::Hold); - assert!(policy.open); - assert_eq!(policy.limit, 1); - } - - #[test] - fn pressure_drains_existing_work_before_reusing_its_slot() { - let pressured = Arc::new(AtomicBool::new(false)); - let reader_pressure = Arc::clone(&pressured); - let gate = Admission::with_reader(options(2), Some(memory()), move || { - Some(if reader_pressure.load(Ordering::SeqCst) { - Memory { available: 20 * GIB, ..memory() } - } else { - memory() - }) - }) - .unwrap(); - let first = gate.acquire().unwrap().unwrap(); - let second = gate.acquire().unwrap().unwrap(); - pressured.store(true, Ordering::SeqCst); - until(|| !gate.shared.state.lock().unwrap().policy.as_ref().unwrap().open); - let started = AtomicBool::new(false); - let published = AtomicBool::new(false); - thread::scope(|scope| { - let waiting = scope.spawn(|| { - let permit = gate.acquire().unwrap().unwrap(); - started.store(true, Ordering::SeqCst); - assert!( - published.load(Ordering::SeqCst), - "slot reused before prior block published" - ); - permit.complete(); - }); - // Neither active task is cancelled or parked. A completed block's - // slot remains unavailable while pressure persists. - drop(first); - assert_eq!(gate.shared.state.lock().unwrap().active, 1); - assert!(!started.load(Ordering::SeqCst)); - pressured.store(false, Ordering::SeqCst); - until(|| gate.shared.state.lock().unwrap().policy.as_ref().unwrap().open); - assert!( - !started.load(Ordering::SeqCst), - "backoff must respect the reduced active cap" - ); - published.store(true, Ordering::SeqCst); - second.complete(); - waiting.join().unwrap(); - }); - let state = gate.shared.state.lock().unwrap(); - assert_eq!(state.active, 0); - assert_eq!(state.completed, 2); - } - - #[test] - fn idle_permits_do_not_count_as_completed_blocks() { - let gate = Admission::with_reader(options(2), None, || { - panic!("fixed mode sampled memory") - }) - .unwrap(); - drop(gate.acquire().unwrap().unwrap()); - assert_eq!(gate.shared.state.lock().unwrap().completed, 0); - gate.acquire().unwrap().unwrap().complete(); - assert_eq!(gate.shared.state.lock().unwrap().completed, 1); - } - - #[test] - fn stop_wakes_waiting_workers_even_while_other_permits_are_live() { - let gate = Admission::with_reader(options(1), None, || None).unwrap(); - let first = gate.acquire().unwrap().unwrap(); - thread::scope(|scope| { - let waiting = scope.spawn(|| assert!(gate.acquire().unwrap().is_none())); - gate.stop(); - waiting.join().unwrap(); - }); - drop(first); - assert_eq!(gate.shared.state.lock().unwrap().active, 0); - } - - #[test] - fn no_headroom_returns_resource_error_without_starting_a_block() { - let low = Memory { available: GIB, ..memory() }; - let gate = - Admission::with_reader(options(4), Some(low), move || Some(low)).unwrap(); - assert!(matches!(gate.acquire(), Err(CompileError::ResourceLimit { .. }))); - assert_eq!(gate.shared.state.lock().unwrap().active, 0); - assert!(matches!(gate.check(), Err(CompileError::ResourceLimit { .. }))); - } - - #[test] - fn telemetry_failure_is_visible_even_if_no_worker_acquires_again() { - let gate = - Admission::with_reader(options(1), Some(memory()), || None).unwrap(); - let last = gate.acquire().unwrap().unwrap(); - until(|| gate.check().is_err()); - last.complete(); - assert!(matches!(gate.check(), Err(CompileError::ResourceLimit { .. }))); - assert!(matches!(gate.acquire(), Err(CompileError::ResourceLimit { .. }))); - } - - #[test] - fn monitor_does_not_resample_for_each_block_and_shutdown_is_prompt() { - let reads = Arc::new(AtomicUsize::new(0)); - let reader_count = Arc::clone(&reads); - let opt = Options { tick: Duration::from_secs(10), ..options(2) }; - let gate = Admission::with_reader(opt, Some(memory()), move || { - reader_count.fetch_add(1, Ordering::SeqCst); - Some(memory()) - }) - .unwrap(); - for _ in 0..1000 { - gate.acquire().unwrap().unwrap().complete(); - } - assert_eq!(reads.load(Ordering::SeqCst), 0); - let start = Instant::now(); - drop(gate); - assert!(start.elapsed() < Duration::from_secs(1)); - } -} diff --git a/crates/compile/src/compile/block_txn.rs b/crates/compile/src/compile/block_txn.rs deleted file mode 100644 index 472c35fdf..000000000 --- a/crates/compile/src/compile/block_txn.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! Publish a scheduled block only after compilation and all publication -//! checks succeed. The active thread stages every table write; its lookups -//! can read those writes, while other workers see only committed blocks. -//! -//! Commit validates the entire batch under the state's publication lock -//! before inserting anything. A late conflict is a block failure, including -//! when another worker claimed the same name or address after staging. -//! Failed batches are dropped, never deleted from shared tables: a successful -//! concurrent block may own identical content. This matches the Lean drivers' -//! check-then-merge behaviour, including partial serialized output. - -use std::cell::RefCell; - -use ix_common::{ - address::Address, - env::{Name, ReducibilityHints}, -}; -use ixon::{CompileError, LazyConstant, env::Named}; -use rustc_hash::{FxHashMap, FxHashSet}; - -use super::{CompileState, name_claim_conflict, publication::conflict}; - -#[derive(Default)] -pub(super) struct BlockTxn { - pub constants: FxHashMap, - pub blobs: FxHashMap>, - pub names: FxHashMap, - pub named: FxHashMap, - pub claims: FxHashMap, - pub blocks: FxHashMap>>, - pub hints: FxHashMap>, -} - -thread_local! { - static TXN: RefCell> = const { RefCell::new(None) }; -} - -/// Begin this thread's scheduled block. Nested transactions are a caller error. -pub fn start() { - TXN.with(|t| { - let mut current = t.borrow_mut(); - assert!(current.is_none(), "a block transaction is already active"); - *current = Some(BlockTxn::default()); - }); -} - -/// Access this thread's staged writes. Callers must not recursively access -/// the transaction while this closure holds its borrow. -pub(super) fn with(f: impl FnOnce(Option<&mut BlockTxn>) -> T) -> T { - TXN.with(|t| f(t.borrow_mut().as_mut())) -} - -fn publish(stt: &CompileState, txn: BlockTxn) -> Result<(), CompileError> { - // All compiler writers, including direct callers without a transaction, - // hold this lock. Checks and inserts form one indivisible writer operation. - let _publication = stt.publication_lock.lock().unwrap(); - for (addr, pending) in &txn.constants { - if let Some(existing) = stt.env.consts.get(addr) - && existing.raw_bytes() != pending.raw_bytes() - { - return Err(conflict("constant")); - } - } - for (addr, pending) in &txn.blobs { - if let Some(existing) = stt.env.blobs.get(addr) - && existing.value() != pending - { - return Err(conflict("blob")); - } - } - let mut reclaimed = FxHashSet::default(); - for (name, addr) in &txn.claims { - if let Some(existing) = stt.name_to_addr.get(name) { - if existing.value() != addr { - return Err(name_claim_conflict(name, existing.value(), addr)); - } - reclaimed.insert(name.clone()); - } - } - - // No fallible check remains. Existing identical records and names keep - // their owner; a repeated write within this batch keeps its last value. - for (addr, constant) in txn.constants { - stt.env.consts.entry(addr).or_insert(constant); - } - for (addr, bytes) in txn.blobs { - stt.env.blobs.entry(addr).or_insert(bytes); - } - for (addr, name) in txn.names { - stt.env.names.entry(addr).or_insert(name); - } - for (name, named) in txn.named { - if !reclaimed.contains(&name) { - stt.env.named.insert(name, named); - } - } - for (name, classes) in txn.blocks { - if !reclaimed.contains(&name) { - stt.blocks.insert(name, classes); - } - } - for (name, hint) in txn.hints { - if reclaimed.contains(&name) { - continue; - } - match hint { - Some(hint) => { - stt.def_hints.insert(name, hint); - }, - None => { - stt.def_hints.remove(&name); - }, - } - } - // Publish resolution last, after the records and metadata it exposes. - // The scheduler releases dependents only after this function returns. - for (name, addr) in txn.claims { - stt.name_to_addr.entry(name).or_insert(addr); - } - Ok(()) -} - -/// End this thread's block. Drop failed output, or validate and publish the -/// complete successful batch. A commit conflict must be reported as the -/// block's failure before the scheduler releases its dependents. -pub fn finish(stt: &CompileState, failed: bool) -> Result<(), CompileError> { - let txn = TXN.with(|t| t.borrow_mut().take()); - if let Some(txn) = txn - && !failed - { - publish(stt, txn)?; - } - Ok(()) -} diff --git a/crates/compile/src/compile/c6_flags_tests.rs b/crates/compile/src/compile/c6_flags_tests.rs deleted file mode 100644 index b37da7476..000000000 --- a/crates/compile/src/compile/c6_flags_tests.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! Inductive flag controls. Raw flag mutations are not claims that Lean admits -//! inconsistent mutual blocks: the flags check refuses a wrong recursion flag, -//! the safety flag survives compilation, and the kernel refuses a block whose -//! members disagree on it. - -use super::*; -use ix_common::env::ConstantVal; - -fn name(s: &str) -> Name { - Name::str(Name::anon(), s.to_string()) -} - -fn empty_ind(s: &str) -> Ind { - let n = name(s); - Ind { - ind: InductiveVal { - cnst: ConstantVal { - name: n.clone(), - level_params: vec![], - typ: LeanExpr::sort(Level::succ(Level::zero())), - }, - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: vec![n], - ctors: vec![], - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }, - ctors: vec![], - } -} - -fn env_of(ind: &Ind) -> LeanEnv { - let mut env = LeanEnv::default(); - env.insert( - ind.ind.cnst.name.clone(), - LeanConstantInfo::InductInfo(ind.ind.clone()), - ); - for ctor in &ind.ctors { - env - .insert(ctor.cnst.name.clone(), LeanConstantInfo::CtorInfo(ctor.clone())); - } - env -} - -#[test] -fn recursion_flag_validation_still_rejects_before_compilation() { - use super::ind_flags::validate_lean_ind_flags; - let good = empty_ind("C6Empty"); - assert!(validate_lean_ind_flags(&env_of(&good)).is_ok()); - let mut bad = good.clone(); - bad.ind.is_rec = true; - let env = env_of(&bad); - assert!(matches!( - validate_lean_ind_flags(&env), - Err(CompileError::InvalidMutualBlock { reason }) - if reason.contains("non-canonical inductive flags") - )); - assert!(matches!( - compile_env(&Arc::new(env)), - Err(CompileError::InvalidMutualBlock { reason }) - if reason.contains("non-canonical inductive flags") - )); -} - -#[test] -fn serialization_retains_safety() { - let safe = empty_ind("C6Empty"); - let mut unsafe_ind = safe.clone(); - unsafe_ind.ind.is_unsafe = true; - for ind in [safe, unsafe_ind] { - let (compiled, _, _) = compile_inductive( - &ind, - &MutCtx::default(), - &[], - &mut BlockCache::default(), - &CompileState::default(), - ) - .unwrap(); - assert_eq!(compiled.is_unsafe, ind.ind.is_unsafe); - } -} - -#[test] -fn kernel_safety_check_rejects_disagreement_and_accepts_neighbours() { - use ix_kernel::{ - constant::KConst, env::KEnv, error::TcError, expr::KExpr, id::KId, - level::KUniv, mode::Anon, tc::TypeChecker, - }; - // Two empty members of one block: no constructor or recursion issue can - // mask the safety check. This exercises the unchanged kernel validation. - for (left, right) in - [(false, false), (true, true), (false, true), (true, false)] - { - let mut env = KEnv::::new(); - let block = KId::new(Address::hash(b"C6Block"), ()); - let ids = [ - KId::new(Address::hash(b"C6Left"), ()), - KId::new(Address::hash(b"C6Right"), ()), - ]; - for (i, is_unsafe) in [left, right].into_iter().enumerate() { - env.insert( - ids[i].clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe, - block: block.clone(), - member_idx: i as u64, - ty: KExpr::sort(KUniv::succ(KUniv::zero())), - ctors: vec![], - lean_all: (), - }, - ); - } - env.blocks.insert(block, ids.to_vec()); - let result = TypeChecker::new(&mut env).check_const(&ids[0]); - if left == right { - assert!(result.is_ok(), "valid safety neighbour: {result:?}"); - } else { - assert!(matches!( - result, - Err(TcError::Other(reason)) if reason.contains("same safety flag") - )); - } - } -} diff --git a/crates/compile/src/compile/env.rs b/crates/compile/src/compile/env.rs deleted file mode 100644 index 20ce84ad3..000000000 --- a/crates/compile/src/compile/env.rs +++ /dev/null @@ -1,1108 +0,0 @@ -//! Top-level environment compilation with work-stealing parallelism. -//! -//! Extracted from `compile.rs` to keep the scheduler independently readable. -//! -//! # Memory -//! -//! Peak RSS is kept a small multiple of the environment's serialized -//! size (Mathlib: ~19 GB for a 3 GB `.ixe`) by holding compact -//! representations everywhere the structured forms aren't actually -//! read: the Lean environment decodes on demand behind a bounded cache -//! (`decode_env_lazy` in the ffi crate) rather than as a whole-env -//! owned copy, accumulator constants and named metadata live as their -//! serialized bytes rather than ~20×-larger structured forms, the setup -//! passes share one whole-env decode, and the `.ixe` streams to disk -//! from Rust rather than crossing the FFI as an env-sized ByteArray. -//! All of it is always on, and every mode produces a bit-identical -//! `.ixe`. -//! -//! Three knobs: -//! - `IX_COMPILE_WORKERS=N` — scheduler worker ceiling (default: all -//! cores). Adaptive admission controls the active count under memory -//! pressure; set `IX_COMPILE_ADAPTIVE=0` for fixed parallelism. -//! - `IX_COMPILE_DEMOTE=0` — keep materialized caches next to the -//! accumulator's bytes instead of demoting to bytes-only. Spends -//! RAM to make post-compile structural reads free, which only pays -//! in flows that re-read the compiled env in-process (`ix check` / -//! `ix validate`); `ix compile` itself never reads them back. -//! - `IX_COMPILE_EAGER=1` — decode the whole Lean environment up -//! front instead of on demand. Spends the single largest memory -//! term (FLT: +13 GiB for −8 % wall; Mathlib needs a large-memory -//! machine) to shave the last few percent on hardware with RAM to -//! spare. - -use std::panic::{AssertUnwindSafe, catch_unwind}; -use std::sync::{ - Arc, LazyLock, Mutex, - atomic::{AtomicBool, AtomicUsize, Ordering as AtomicOrdering}, -}; -use std::thread; -use std::time::{Duration, Instant}; - -use dashmap::DashMap; -use rayon::prelude::*; -use rustc_hash::{FxHashMap, FxHashSet}; - -use super::admission::Admission; -use crate::compile::{ - BlockCache, CompileOptions, CompileState, compile_const, - ind_flags::validate_ind_groups, -}; -use crate::condense::compute_sccs; -use crate::graph::{NameSet, RefMap, setup_scan}; -use crate::ground::proliferate_ungrounded; -use ix_common::address::Address; -use ix_common::env::{ - ConstantInfo as LeanConstantInfo, DefinitionSafety, Env as LeanEnv, Name, -}; -use ixon::CompileError; - -// =========================================================================== -// Mutual-block safety (a domain restriction, beside the inductive flags) -// =========================================================================== - -const MIXED_SAFETY: &str = "mixed safety in a mutual block"; - -/// A constant's safety as the kernels compare it within a mutual block: -/// `unsafe`, `partial` (a definition's) or `safe`. Mirrors Lean -/// `Ix.CompileM.constSafety`. -fn const_safety(ci: &LeanConstantInfo) -> &'static str { - let flag = |is_unsafe: bool| if is_unsafe { "unsafe" } else { "safe" }; - match ci { - LeanConstantInfo::AxiomInfo(v) => flag(v.is_unsafe), - LeanConstantInfo::OpaqueInfo(v) => flag(v.is_unsafe), - LeanConstantInfo::InductInfo(v) => flag(v.is_unsafe), - LeanConstantInfo::CtorInfo(v) => flag(v.is_unsafe), - LeanConstantInfo::RecInfo(v) => flag(v.is_unsafe), - LeanConstantInfo::DefnInfo(v) => match v.safety { - DefinitionSafety::Unsafe => "unsafe", - DefinitionSafety::Partial => "partial", - DefinitionSafety::Safe => "safe", - }, - LeanConstantInfo::ThmInfo(_) | LeanConstantInfo::QuotInfo(_) => "safe", - } -} - -/// Whether the members present in `env` share one safety. A block of at most -/// one member is uniform without a lookup, as in Lean's `uniformSafety`: on -/// a lazy environment each lookup decodes the whole constant, and almost -/// every block is a singleton. -fn uniform_safety(members: &[Name], env: &LeanEnv) -> bool { - if members.len() <= 1 { - return true; - } - let mut first: Option<&'static str> = None; - for m in members { - if let Some(c) = env.get(m) { - let s = const_safety(&c); - match first { - None => first = Some(s), - Some(s0) if s != s0 => return false, - Some(_) => {}, - } - } - } - true -} - -/// The refusal of members that disagree on safety: the least member present -/// in `env` (by name hash) and the least member whose safety differs from -/// its (Lean `Ix.CompileM.mixedSafetyMessage`). -fn mixed_safety_error(members: &[Name], env: &LeanEnv) -> CompileError { - let safety = |n: &Name| env.get(n).map_or("safe", |c| const_safety(&c)); - let mut present: Vec<&Name> = - members.iter().filter(|m| env.get(m).is_some()).collect(); - present.sort_unstable_by(|a, b| { - a.get_hash().as_bytes().cmp(b.get_hash().as_bytes()) - }); - let reason = present - .first() - .and_then(|first| { - let s0 = safety(first); - present.iter().find(|m| safety(m) != s0).map(|m| { - format!( - "{MIXED_SAFETY}: '{}' is {s0}, '{}' is {}", - first.pretty(), - m.pretty(), - safety(m) - ) - }) - }) - .unwrap_or_else(|| MIXED_SAFETY.to_string()); - CompileError::InvalidMutualBlock { reason } -} - -/// The second domain restriction (Lean `Ix.CompileM.validateMutualSafety`): -/// the members of a mutual block share one safety, or the compile fails -/// before any block is scheduled. The kernels reject a mixed-safety block, -/// and the compilers' orders do not key on safety (C6), so such members -/// could be alpha-collapsed into one representative's safety. Checked first -/// on each inductive group (`all`, with each member's constructors) in the -/// order of their first members' hashes, then on each condensation block in -/// the order of its least member's hash; the first disagreement is the error. -pub(crate) fn validate_mutual_safety( - ind_groups: &FxHashMap>, - blocks: &RefMap, - env: &LeanEnv, -) -> Result<(), CompileError> { - let mut groups: Vec<(&Name, &Vec)> = ind_groups.iter().collect(); - groups.sort_unstable_by(|(a, _), (b, _)| { - a.get_hash().as_bytes().cmp(b.get_hash().as_bytes()) - }); - for (_, all) in groups { - let mut members: Vec = Vec::with_capacity(all.len()); - for n in all { - members.push(n.clone()); - if let Some(c) = env.get(n) - && let LeanConstantInfo::InductInfo(v) = &*c - { - members.extend(v.ctors.iter().cloned()); - } - } - if !uniform_safety(&members, env) { - return Err(mixed_safety_error(&members, env)); - } - } - let mut offenders: Vec<(Name, Vec)> = blocks - .values() - .filter_map(|set| { - let members: Vec = set.iter().cloned().collect(); - if uniform_safety(&members, env) { - return None; - } - let least = members - .iter() - .min_by(|a, b| a.get_hash().as_bytes().cmp(b.get_hash().as_bytes()))? - .clone(); - Some((least, members)) - }) - .collect(); - offenders.sort_unstable_by(|(a, _), (b, _)| { - a.get_hash().as_bytes().cmp(b.get_hash().as_bytes()) - }); - match offenders.first() { - Some((_, members)) => Err(mixed_safety_error(members, env)), - None => Ok(()), - } -} - -// =========================================================================== -// Progress + diagnostic logging -// =========================================================================== - -/// Enable progress output. Quiet by default; set `IX_VERBOSE=1` to see -/// per-phase timings and scheduler progress. `IX_COMPILE_DBG=1` (the -/// Lean driver's phase-attribution knob) is accepted as an alias so one -/// flag lights up both sides of the pipeline. -pub(super) static IX_VERBOSE: LazyLock = LazyLock::new(|| { - std::env::var("IX_VERBOSE").is_ok() || std::env::var("IX_COMPILE_DBG").is_ok() -}); - -/// Log every block start + finish. Set `IX_LOG_BLOCKS=1` for deep debugging. -/// Very verbose — only useful when you need to pin a panic to a specific block. -static IX_LOG_BLOCKS: LazyLock = - LazyLock::new(|| std::env::var("IX_LOG_BLOCKS").is_ok()); - -/// Periodic progress update interval in milliseconds (default 2000ms). -/// Set `IX_PROGRESS_MS=0` to disable periodic updates. -static IX_PROGRESS_MS: LazyLock = LazyLock::new(|| { - std::env::var("IX_PROGRESS_MS") - .ok() - .and_then(|s| s.parse().ok()) - .unwrap_or(2000) -}); - -/// Recover a short string description from a panic payload. -fn panic_message(panic: &(dyn std::any::Any + Send)) -> String { - panic - .downcast_ref::() - .cloned() - .or_else(|| panic.downcast_ref::<&'static str>().map(|s| (*s).to_string())) - .unwrap_or_else(|| "".to_string()) -} - -/// Run `f` catching any panic and converting it to a `CompileError` tagged -/// with `block_name` (and `caller` to distinguish which compile function -/// panicked). This keeps a single bad block from aborting the whole -/// compilation and preserves enough context to find the culprit — a raw -/// panic from deep inside compilation has no indication of which SCC it was -/// working on. -/// -/// When `IX_LOG_BLOCKS` is set, panics also emit an immediate eprintln so -/// they appear in log order alongside block BEGIN/END markers. -fn run_compile_catching_panic( - block_name: &Name, - caller: &'static str, - f: F, -) -> Result -where - F: FnOnce() -> Result, -{ - match catch_unwind(AssertUnwindSafe(f)) { - Ok(res) => res, - Err(panic) => { - let msg = panic_message(&*panic); - if *IX_LOG_BLOCKS { - eprintln!( - "[compile_env] PANIC in {caller} for {}: {msg}", - block_name.pretty() - ); - } - Err(CompileError::UnsupportedExpr { - desc: format!( - "{caller} panicked while compiling block {}: {msg}", - block_name.pretty() - ), - }) - }, - } -} - -/// Compile an entire Lean environment to Ixon format. -/// Work-stealing compilation using crossbeam channels. -/// -/// Instead of processing blocks in waves (which underutilizes cores when wave sizes vary), -/// we use a work queue. When a block completes, it immediately unlocks dependent blocks. -pub fn compile_env( - lean_env: &Arc, -) -> Result { - compile_env_with_options(lean_env, CompileOptions::default()) -} - -/// Compile an entire Lean environment with explicit resource/correctness -/// options. See [`CompileOptions`] for the intended call-site split between -/// trusted Lean environments and adversarial raw-constant tests. -pub fn compile_env_with_options( - lean_env: &Arc, - options: CompileOptions, -) -> Result { - compile_env_with_profile(lean_env, options, None) -} - -/// Compile semantic input with an explicit profile, or the built-in profile -/// when omitted. No annotated result can bypass combined validation. -pub fn compile_env_with_profile( - lean_env: &Arc, - options: CompileOptions, - profile: Option<&ixon::resource::addressed::Profile>, -) -> Result { - let _memory_sampler = crate::diag::memory_sampler("compile_env"); - // The sharing limits of the whole run, read once: an invalid - // `IX_SHARING_LIMITS` fails the compile here, before any block is - // scheduled (Lean's `Ix.Cli.driverOptionsFromEnv` does the same). - let sharing_limits = crate::compile::compiler_sharing_limits() - .map_err(|reason| CompileError::SharingConstruction { reason })?; - let setup_start = Instant::now(); - // Whole-env scan: ref graph + immediate groundedness + inductive - // groups in one decode per constant — the env decodes lazily, so - // each additional full sweep would decode every constant again. - let phase_start = Instant::now(); - let scan = setup_scan(lean_env.as_ref()); - let semantic_sources = scan.semantic_sources?; - if let Some(name) = - scan.source_contracts.iter().min_by_key(|name| name.pretty()) - { - return Err(CompileError::UnsupportedExpr { - desc: format!("unresolved source binder contracts: {}", name.pretty()), - }); - } - let graph = scan.graph; - if *IX_VERBOSE { - eprintln!( - "[compile_env] setup 1/6 setup_scan (graph+ground+groups): {:.2}s{}", - phase_start.elapsed().as_secs_f32(), - crate::diag::rss_log_suffix(), - ); - } - - // Grounding: constants whose transitive Const-refs can't all be - // resolved are collected into `stt.ungrounded` and filtered from the - // SCC input so they don't clog the scheduler. Callers (e.g. the - // kernel check FFI) inspect `stt.ungrounded` per-constant to report - // them as compile-side rejections without aborting the whole batch. - let phase_start = Instant::now(); - let ungrounded = - proliferate_ungrounded(scan.immediate_ungrounded, &graph.in_refs); - if *IX_VERBOSE { - eprintln!( - "[compile_env] setup 2/6 proliferate_ungrounded: {:.2}s{}", - phase_start.elapsed().as_secs_f32(), - crate::diag::rss_log_suffix(), - ); - } - // Pin the highest-in-degree constants in the lazy env: the ref - // graph gives exact reference counts, and the distribution is - // heavily skewed, so a small never-evicted set absorbs repeat - // decodes of foundational constants that otherwise churn through - // the bounded cache. At this size the overlay costs ≤0.3 GiB on - // every measured env and wall time is Lean −15 %, FLT −4 %, - // InitStd/Mathlib neutral (24-core box); larger sets bought RAM, - // not speed, on the same sweep. - const PIN_HOT_CONSTANTS: usize = 16384; - if !graph.in_refs.is_empty() { - let mut by_deg: Vec<(&Name, usize)> = - graph.in_refs.iter().map(|(n, refs)| (n, refs.len())).collect(); - let k = PIN_HOT_CONSTANTS.min(by_deg.len()); - by_deg.select_nth_unstable_by(k - 1, |a, b| b.1.cmp(&a.1)); - lean_env.pin(by_deg[..k].iter().map(|(n, _)| (*n).clone())); - } - - // each with its named refusal (Lean's `GroundError.refusal`, the same text) - let ungrounded_map: DashMap = ungrounded - .iter() - .map(|(n, e)| (n.clone(), crate::ground::refusal(e, &ungrounded))) - .collect(); - if !ungrounded.is_empty() && *IX_VERBOSE { - eprintln!( - "[compile_env] {} ungrounded constants filtered from graph", - ungrounded.len() - ); - for (n, e) in ungrounded.iter().take(5) { - eprintln!(" ungrounded: {} ({:?})", n.pretty(), e); - } - if ungrounded.len() > 5 { - eprintln!(" ... and {} more", ungrounded.len() - 5); - } - } - - // Filter ungrounded names from the ref graph before SCC computation so - // condensed blocks only contain constants we can actually compile. - let grounded_out_refs: RefMap = if ungrounded_map.is_empty() { - graph.out_refs - } else { - graph - .out_refs - .into_iter() - .filter(|(name, _)| !ungrounded_map.contains_key(name)) - .map(|(k, refs)| { - let filtered: FxHashSet = refs - .into_iter() - .filter(|r| !ungrounded_map.contains_key(r)) - .collect(); - (k, filtered) - }) - .collect() - }; - - let phase_start = Instant::now(); - let condensed = compute_sccs(&grounded_out_refs); - if *IX_VERBOSE { - eprintln!( - "[compile_env] setup 3/6 compute_sccs ({} blocks): {:.2}s{}", - condensed.blocks.len(), - phase_start.elapsed().as_secs_f32(), - crate::diag::rss_log_suffix(), - ); - } - - // Domain restriction: reject environments with non-canonical inductive - // flags. Groups come from the fused scan; only inductive families are - // re-read here. - let phase_start = Instant::now(); - if *IX_VERBOSE { - eprintln!( - "[compile_env] setup 4/6 validate_ind_flags BEGIN ({} groups, {} Rayon threads){}", - scan.ind_groups.len(), - rayon::current_num_threads(), - crate::diag::rss_log_suffix(), - ); - } - validate_ind_groups(&scan.ind_groups, lean_env.as_ref())?; - validate_mutual_safety( - &scan.ind_groups, - &condensed.blocks, - lean_env.as_ref(), - )?; - if *IX_VERBOSE { - eprintln!( - "[compile_env] setup 4/6 validate_ind_flags: {:.2}s{}", - phase_start.elapsed().as_secs_f32(), - crate::diag::rss_log_suffix(), - ); - } - - // Lean's declarations compile as given: the schedule is the - // condensation's (Lean `compileEnvFoldWithSource`). - let stt = CompileState { - ungrounded: ungrounded_map, - sharing_limits, - ..Default::default() - }; - - // Build work-stealing data structures - let total_blocks = condensed.blocks.len(); - let phase_start = Instant::now(); - - // For each block: (all names in block, original deps, remaining deps). - // Using an explicit HashSet instead of an atomic counter prevents silent - // corruption from double-decrements — removing an already-removed name - // is a no-op. - let block_info: DashMap< - Name, - (NameSet, FxHashSet, Mutex>), - > = DashMap::default(); - - // Reverse deps: name -> set of block leaders that depend on this name - let reverse_deps: DashMap> = DashMap::default(); - - // Initialize block info and reverse deps in parallel. - // - // `condensed.blocks` is an `FxHashMap` so we collect a `Vec` of references - // first; `par_iter` on `FxHashMap` would require enabling the `rayon` - // feature on `hashbrown`, which is not a current dep. The collection is - // sub-millisecond on 193k entries. - // - // Both `block_info` and `reverse_deps` are `DashMap`s; `DashMap::insert` - // and `DashMap::entry` are atomic against the per-shard lock, so parallel - // writes are safe. `reverse_deps.entry(dep).or_default().push(lo)` holds - // the shard write-lock for the duration of the `push`, which briefly - // serializes threads that hit the same shard for the same `dep`. The - // shard count (DashMap default 64) is large enough relative to thread - // count (32) that contention stays low. Vec insertion order within a - // reverse-dep entry becomes non-deterministic — that is fine because the - // consumer (the scheduler's unblock loop) only iterates the Vec to - // notify workers, never compares it for equality. - let block_entries: Vec<(&Name, &NameSet)> = condensed.blocks.iter().collect(); - block_entries.par_iter().try_for_each( - |(lo, all)| -> Result<(), CompileError> { - let deps = condensed.block_refs.get(*lo).ok_or( - CompileError::InvalidMutualBlock { - reason: "missing block refs".into(), - }, - )?; - - block_info.insert( - (*lo).clone(), - ((*all).clone(), deps.clone(), Mutex::new(deps.clone())), - ); - - for dep_name in deps { - reverse_deps.entry(dep_name.clone()).or_default().push((*lo).clone()); - } - Ok(()) - }, - )?; - - // Shared ready queue: blocks that are ready to compile - let ready_queue: Mutex> = Mutex::new(Vec::new()); - - if *IX_VERBOSE { - eprintln!( - "[compile_env] setup 5/6 block_info init: {:.2}s", - phase_start.elapsed().as_secs_f32() - ); - } - let phase_start = Instant::now(); - - // Initialize with blocks that have zero remaining dependencies - { - let mut queue = ready_queue.lock().unwrap(); - for entry in block_info.iter() { - let lo = entry.key(); - let (all, _, remaining) = entry.value(); - if remaining.lock().unwrap().is_empty() { - queue.push((lo.clone(), all.clone())); - } - } - } - if *IX_VERBOSE { - eprintln!( - "[compile_env] setup 6/6 ready_queue init: {:.2}s (total pre-scheduler: {:.2}s){}", - phase_start.elapsed().as_secs_f32(), - setup_start.elapsed().as_secs_f32(), - crate::diag::rss_log_suffix(), - ); - } - - // Track completed count for termination - let completed = Arc::new(AtomicUsize::new(0)); - - // Guard against duplicate processing: a block leader that's already been - // handled is skipped. This prevents infinite loops from double-enqueuing. - let processed: dashmap::DashSet = dashmap::DashSet::new(); - - // Error storage for propagating errors from workers - let error: Mutex> = Mutex::new(None); - - // Condvar for signaling workers when new work is available or completion - let work_available = std::sync::Condvar::new(); - - // Use scoped threads to borrow from parent scope. `IX_COMPILE_WORKERS` - // gives large-env callers a simple peak-memory/speed tradeoff knob. - let available_threads = - thread::available_parallelism().map_or(4, |n| n.get()); - let requested_threads = options.max_workers.or_else(|| { - std::env::var("IX_COMPILE_WORKERS") - .ok() - .and_then(|s| s.parse::().ok()) - .filter(|&n| n > 0) - }); - let num_threads = requested_threads - .unwrap_or(available_threads) - .min(available_threads) - .max(1); - let admission = Admission::new(num_threads)?; - - // Progress tracking. `active` holds currently-compiling blocks per worker - // so the reporter thread can show blocks that are still in-flight (useful - // when a slow block is stuck or about to crash — those are the ones you - // can't see otherwise). `stop_progress` signals the reporter to terminate. - let compile_start = Instant::now(); - let active: Arc>> = - Arc::new(Mutex::new(Vec::new())); - let stop_progress = Arc::new(AtomicBool::new(false)); - - if *IX_VERBOSE { - eprintln!( - "[compile_env] starting: {total_blocks} blocks, {num_threads} workers" - ); - } - - // Take references to shared data outside the loop - let error_ref = &error; - let stt_ref = &stt; - let reverse_deps_ref = &reverse_deps; - let block_info_ref = &block_info; - let completed_ref = &completed; - let processed_ref = &processed; - let ready_queue_ref = &ready_queue; - let condvar_ref = &work_available; - let active_ref = &active; - let stop_progress_ref = &stop_progress; - let admission_ref = &admission; - - thread::scope(|s| { - // Periodic progress reporter. Wakes every IX_PROGRESS_MS to print - // completed/total and the oldest in-flight blocks. Exits when - // stop_progress is set (after all workers have finished). - // - // Skipped entirely unless IX_VERBOSE is set, and also when - // IX_PROGRESS_MS=0 — both imply "don't print periodic updates" - // (one-shot errors still print). - if *IX_VERBOSE && *IX_PROGRESS_MS > 0 { - let interval = Duration::from_millis(*IX_PROGRESS_MS); - // Shorter internal check so shutdown latency is bounded (otherwise the - // scheduler waits up to `interval` for the reporter to wake and see - // stop_progress). Cap at 250ms — shorter is wasted cycles, longer is - // noticeable lag on fast compilations. - let check_interval = interval.min(Duration::from_millis(250)); - let total = total_blocks; - let completed_p = Arc::clone(completed_ref); - let active_p = Arc::clone(active_ref); - let stop_p = Arc::clone(stop_progress_ref); - let start = compile_start; - s.spawn(move || { - let mut last_completed = 0usize; - let mut last_print = Instant::now(); - let mut last_progress = Instant::now(); - while !stop_p.load(AtomicOrdering::Relaxed) { - thread::sleep(check_interval); - if stop_p.load(AtomicOrdering::Relaxed) { - break; - } - // Only emit a progress line every `interval` — the sub-interval - // poll exists purely for fast shutdown. - if last_print.elapsed() < interval { - continue; - } - let print_interval = last_print.elapsed().as_secs_f64(); - last_print = Instant::now(); - let done = completed_p.load(AtomicOrdering::SeqCst); - // No new completion is not a deadlock signal: report how long - // current blocks have been running, with their active count. - let changed = done != last_completed; - let delta = done.saturating_sub(last_completed); - if changed { last_progress = Instant::now(); } - last_completed = done; - let pct = if total == 0 { - 100.0 - } else { - (done as f64 / total as f64) * 100.0 - }; - let elapsed = start.elapsed().as_secs_f64(); - - // Oldest in-flight blocks (up to 3) for visibility into - // slow/stuck compilations. Sort by start time ascending. - let (active_count, in_flight): (usize, Vec) = { - let mut entries: Vec<(Name, Instant)> = - active_p.lock().unwrap().clone(); - entries.sort_by_key(|(_, t)| *t); - let names = entries - .iter() - .take(3) - .map(|(n, t)| { - format!("{} ({:.0}s)", n.pretty(), t.elapsed().as_secs_f64()) - }) - .collect(); - (entries.len(), names) - }; - let suffix = if in_flight.is_empty() { - String::new() - } else { - format!(" · in-flight: {}", in_flight.join(", ")) - }; - - // A lifetime-average ETA is misleading when a fast prefix is - // followed by a handful of expensive blocks. Show recent work. - if changed || done == 0 { - eprintln!( - "[compile_env] {done}/{total} ({pct:.1}%) · {elapsed:.0}s · +{delta} blocks/{print_interval:.1}s · active={active_count}{suffix}{}", - crate::diag::rss_log_suffix(), - ); - } else { - eprintln!( - "[compile_env] {done}/{total} ({pct:.1}%) · waiting: no completed blocks for {:.0}s · active={active_count}{suffix}", - last_progress.elapsed().as_secs_f64(), - ); - } - } - }); - } - - // Spawn worker threads. - for _ in 0..num_threads { - s.spawn(move || { - loop { - // Wait without holding a ready-queue lock or any block scratch. - // A permit covers compilation, publication, and dependency release. - let permit = match admission_ref.acquire() { - Ok(Some(permit)) => permit, - Ok(None) => return, - Err(failure) => { - error_ref.lock().unwrap().get_or_insert(failure); - condvar_ref.notify_all(); - return; - }, - }; - // Try to get work from the ready queue - let work = { - let mut queue = ready_queue_ref.lock().unwrap(); - queue.pop() - }; - - match work { - Some((lo, all)) => { - // Check if we should stop due to error - if error_ref.lock().unwrap().is_some() { - return; - } - - // Skip if already processed (prevents double-counting from - // duplicate enqueuing) - if !processed_ref.insert(lo.clone()) { - continue; - } - - // Track time for slow block detection - let block_start = Instant::now(); - - // Register as in-flight for the progress reporter. Remove on - // every exit path (panic converted to error, graceful error, - // success). - active_ref.lock().unwrap().push((lo.clone(), block_start)); - - if *IX_LOG_BLOCKS { - eprintln!( - "[compile_env] BEGIN {} ({} members)", - lo.pretty(), - all.len() - ); - } - - // Compile this block - let cc_start = Instant::now(); - let mut cache = BlockCache::default(); - // Stage the complete block; compilation or commit failure - // drops its output before any dependent is released. - crate::compile::block_txn::start(); - let res = - run_compile_catching_panic(&lo, "compile_const", || { - compile_const(&lo, &all, lean_env, &mut cache, stt_ref) - }); - let publication = - crate::compile::block_txn::finish(stt_ref, res.is_err()); - let res = res.and_then(|addr| publication.map(|()| addr)); - if let Err(e) = res { - // Record the failure per-member and fall through. The - // scheduler keeps running so other constants can still - // compile; dependents of this block will hit - // MissingConstant and be recorded here too. Callers - // inspect `stt.ungrounded` to report per-constant - // compile-side rejections. - let msg = format!("{e}"); - for member in &all { - stt_ref.ungrounded.insert(member.clone(), msg.clone()); - } - // The first time we fail on a given block, log a brief - // line. Full dep-status diagnostics are gated on - // IX_LOG_BLOCKS to avoid log spam on cascading failures. - eprintln!( - "[compile_env] block FAILED {} ({} members): {}", - lo.pretty(), - all.len(), - msg, - ); - if *IX_LOG_BLOCKS { - for member in &all { - eprintln!(" member: {}", member.pretty()); - } - if let CompileError::MissingConstant { - ref name, - ref caller, - } = e - { - eprintln!( - "[compile_env] MissingConstant: {name} (from {caller})" - ); - for member in &all { - let in_main = stt_ref.name_to_addr.contains_key(member); - let in_ungr = stt_ref.ungrounded.contains_key(member); - let status = if in_main { - "name_to_addr" - } else if in_ungr { - "ungrounded" - } else { - "pending" - }; - eprintln!(" {} [{}]", member.pretty(), status); - } - if let Some(entry) = block_info_ref.get(&lo) { - let (_, orig_deps, remaining) = entry.value(); - eprintln!(" deps ({}):", orig_deps.len()); - for d in orig_deps.iter() { - let in_main = stt_ref.name_to_addr.contains_key(d); - let in_ungr = stt_ref.ungrounded.contains_key(d); - let status = if in_main { - "name_to_addr" - } else if in_ungr { - "ungrounded" - } else { - "UNRESOLVED" - }; - eprintln!(" {} [{}]", d.pretty(), status); - } - let rem = remaining.lock().unwrap(); - if !rem.is_empty() { - eprintln!(" unsatisfied ({}):", rem.len()); - for d in rem.iter() { - eprintln!(" {}", d.pretty()); - } - } - } - } - } - } - - // Block completed successfully: drop in-flight entry and - // log to BEGIN/END if requested. Don't touch active_ref - // after completed counter bump — if the reporter happens - // to wake right after bump and before this cleanup, it - // might show a completed block as in-flight, but the - // numbers still reconcile on the next tick. - active_ref.lock().unwrap().retain(|(n, _)| n != &lo); - - // Check for slow blocks - let elapsed = block_start.elapsed(); - if *crate::compile::IX_TIMING && elapsed.as_secs_f32() > 1.0 { - let cc_time = cc_start.elapsed().as_secs_f32(); - eprintln!( - "Slow block {:?} ({} consts): {:.2}s cc={:.2}s", - lo.pretty(), - all.len(), - elapsed.as_secs_f32(), - cc_time, - ); - } - if *IX_LOG_BLOCKS { - eprintln!( - "[compile_env] END {} ({:.2}s)", - lo.pretty(), - elapsed.as_secs_f32(), - ); - } - - // Collect newly-ready blocks by removing satisfied deps. - // HashSet::remove is idempotent — no double-decrement risk. - let mut newly_ready = Vec::new(); - - let resolve_name = - |name: &Name, newly_ready: &mut Vec<(Name, NameSet)>| { - if let Some(dependents) = reverse_deps_ref.get(name) { - for dependent_lo in dependents.value() { - if let Some(entry) = block_info_ref.get(dependent_lo) { - let (dep_all, _, remaining) = entry.value(); - let mut deps = remaining.lock().unwrap(); - let was_present = deps.remove(name); - if was_present && deps.is_empty() { - newly_ready - .push((dependent_lo.clone(), dep_all.clone())); - } - } - } - } - }; - - // For each name in this block, resolve deps - for name in &all { - resolve_name(name, &mut newly_ready); - } - - // Add newly-ready blocks to the queue and notify waiting workers - if !newly_ready.is_empty() { - let mut queue = ready_queue_ref.lock().unwrap(); - queue.extend(newly_ready); - condvar_ref.notify_all(); - } - - let done = completed_ref.fetch_add(1, AtomicOrdering::SeqCst) + 1; - // Wake all workers only when all blocks are done (so they - // can exit), otherwise just wake one to avoid thundering herd. - if done == total_blocks { - condvar_ref.notify_all(); - } else { - condvar_ref.notify_one(); - } - permit.complete(); - }, - None => { - drop(permit); - // No work available - check if we're done - if completed_ref.load(AtomicOrdering::SeqCst) == total_blocks { - return; - } - // Check for errors - if error_ref.lock().unwrap().is_some() { - return; - } - // Wait for new work to become available - let queue = ready_queue_ref.lock().unwrap(); - let _ = condvar_ref - .wait_timeout(queue, Duration::from_millis(10)) - .unwrap(); - }, - } - } - }); - } - - // Wait for workers to drain, then stop the progress reporter. Scoped - // threads join implicitly at the end of the scope, so we signal stop - // before exiting — the reporter's sleep may keep it alive past worker - // exit otherwise. - // - // Workers only exit via `None => ...` which requires either - // all-completed or an error flag set, so by the time we reach here - // (after the explicit join below), the scheduler is truly done. - // - // We can't `join()` scoped worker handles from outside their creation, - // so instead we poll completion/error and only then stop progress. - // The poll is cheap (one atomic + one mutex lock per iteration) and - // bounded by the slowest worker. - while completed_ref.load(AtomicOrdering::SeqCst) < total_blocks - && error_ref.lock().unwrap().is_none() - { - if let Err(failure) = admission_ref.check() { - error_ref.lock().unwrap().get_or_insert(failure); - break; - } - thread::sleep(Duration::from_millis(25)); - } - admission_ref.stop(); - stop_progress_ref.store(true, AtomicOrdering::Relaxed); - }); - admission.check()?; - - if *IX_VERBOSE { - let scheduler_elapsed = compile_start.elapsed().as_secs_f64(); - eprintln!( - "[compile_env] scheduler drained: {}/{} blocks in {scheduler_elapsed:.1}s{}", - completed.load(AtomicOrdering::SeqCst), - total_blocks, - crate::diag::rss_log_suffix(), - ); - } - - // Check for errors - if let Some(e) = error.into_inner().unwrap() { - return Err(e); - } - - // Verify completion - let final_completed = completed.load(AtomicOrdering::SeqCst); - if final_completed != total_blocks { - // Find what's still blocked - let mut blocked_count = 0; - for entry in block_info.iter() { - let (_, _, remaining) = entry.value(); - let deps = remaining.lock().unwrap(); - if !deps.is_empty() { - blocked_count += 1; - if blocked_count <= 5 { - eprintln!( - "Still blocked: {:?} with {} deps remaining: {:?}", - entry.key().pretty(), - deps.len(), - deps.iter().map(|n| n.pretty()).collect::>() - ); - } - } - } - return Err(CompileError::InvalidMutualBlock { - reason: "circular dependency or missing constant".into(), - }); - } - - stt.finalize_hints(); - - if !semantic_sources.is_empty() || profile.is_some() { - if !stt.ungrounded.is_empty() { - return Err(CompileError::UnsupportedExpr { - desc: "resource compilation requires the complete requested closure" - .into(), - }); - } - let default_profile; - let profile = match profile { - Some(profile) => profile, - None => { - default_profile = ix_kernel::resource::standard_profile(&stt.env) - .map_err(|desc| CompileError::UnsupportedExpr { desc })?; - &default_profile - }, - }; - ix_kernel::resource::validate(&stt.env, profile) - .map_err(|desc| CompileError::UnsupportedExpr { desc })?; - } - - if *IX_VERBOSE { - let total_elapsed = compile_start.elapsed().as_secs_f64(); - eprintln!( - "[compile_env] complete in {total_elapsed:.1}s · \ - env: {} consts, {} named, {} names, {} blobs, {} comms{}", - stt.env.const_count(), - stt.env.named_count(), - stt.env.name_count(), - stt.env.blob_count(), - stt.env.comm_count(), - crate::diag::rss_log_suffix(), - ); - } - - Ok(stt) -} -#[cfg(test)] -mod hint_tests { - use super::*; - use crate::compile::block_txn; - use ix_common::env::ReducibilityHints; - - fn name(s: &str) -> Name { - Name::str(Name::anon(), s.to_owned()) - } - - #[test] - fn deferred_hints_keep_read_your_writes_and_last_write_order() { - let stt = CompileState::default(); - let n = name("imageHint"); - stt.record_hint(&n, Some(ReducibilityHints::Opaque)); - block_txn::start(); - stt.record_hint(&n, Some(ReducibilityHints::Abbrev)); - assert_eq!(stt.recorded_hint(&n), Some(ReducibilityHints::Abbrev)); - assert_eq!(*stt.def_hints.get(&n).unwrap(), ReducibilityHints::Opaque); - stt.record_hint(&n, None); - assert_eq!(stt.recorded_hint(&n), None); - stt.record_hint(&n, Some(ReducibilityHints::Regular(7))); - block_txn::finish(&stt, false).unwrap(); - assert_eq!(stt.recorded_hint(&n), Some(ReducibilityHints::Regular(7))); - } - - /// A failed block's hint writes, removals included, are never published. - #[test] - fn failed_block_publishes_none_of_its_hints() { - let stt = CompileState::default(); - let (kept, fresh) = (name("keptHint"), name("freshHint")); - stt.record_hint(&kept, Some(ReducibilityHints::Opaque)); - block_txn::start(); - stt.record_hint(&kept, None); - stt.record_hint(&fresh, Some(ReducibilityHints::Abbrev)); - block_txn::finish(&stt, true).unwrap(); - assert_eq!(stt.recorded_hint(&kept), Some(ReducibilityHints::Opaque)); - assert_eq!(stt.recorded_hint(&fresh), None); - } -} - -#[cfg(test)] -mod safety_tests { - use super::*; - use ix_common::env::{ - AxiomVal, ConstantVal, DefinitionVal, Expr as LeanExpr, Level, - ReducibilityHints, - }; - - fn name(s: &str) -> Name { - Name::str(Name::anon(), s.to_string()) - } - - fn cval(n: &Name) -> ConstantVal { - ConstantVal { - name: n.clone(), - level_params: vec![], - typ: LeanExpr::sort(Level::succ(Level::zero())), - } - } - - fn defn(n: &Name, safety: DefinitionSafety) -> LeanConstantInfo { - LeanConstantInfo::DefnInfo(DefinitionVal { - cnst: cval(n), - value: LeanExpr::sort(Level::zero()), - hints: ReducibilityHints::Opaque, - safety, - all: vec![n.clone()], - }) - } - - fn axiom(n: &Name, is_unsafe: bool) -> LeanConstantInfo { - LeanConstantInfo::AxiomInfo(AxiomVal { cnst: cval(n), is_unsafe }) - } - - fn block(names: &[&Name]) -> RefMap { - let set: NameSet = names.iter().map(|n| (*n).clone()).collect(); - [(names[0].clone(), set)].into_iter().collect() - } - - /// A condensation block whose members disagree on safety is refused, - /// naming the least member and the least one that differs from it; a - /// uniform block, and singleton blocks of any safety, pass. - #[test] - fn mixed_safety_blocks_are_refused_uniform_ones_pass() { - let (f, g, h) = (name("f"), name("g"), name("h")); - for (fs, gs) in [ - (DefinitionSafety::Unsafe, DefinitionSafety::Safe), - (DefinitionSafety::Safe, DefinitionSafety::Unsafe), - (DefinitionSafety::Unsafe, DefinitionSafety::Partial), - ] { - let mut env = LeanEnv::default(); - env.insert(f.clone(), defn(&f, fs)); - env.insert(g.clone(), defn(&g, gs)); - env.insert(h.clone(), axiom(&h, true)); - let groups = FxHashMap::default(); - let err = validate_mutual_safety(&groups, &block(&[&f, &g]), &env) - .expect_err("mixed block"); - let CompileError::InvalidMutualBlock { reason } = err else { - panic!("wrong error kind") - }; - assert!(reason.starts_with(MIXED_SAFETY), "{reason}"); - assert!(reason.contains("'f'") && reason.contains("'g'"), "{reason}"); - // singletons of different safeties are not a mixed block - let mut singles = block(&[&f]); - singles.extend(block(&[&g])); - singles.extend(block(&[&h])); - assert!(validate_mutual_safety(&groups, &singles, &env).is_ok()); - } - let mut env = LeanEnv::default(); - env.insert(f.clone(), defn(&f, DefinitionSafety::Unsafe)); - env.insert(h.clone(), axiom(&h, true)); - let groups = FxHashMap::default(); - assert!(validate_mutual_safety(&groups, &block(&[&f, &h]), &env).is_ok()); - } -} diff --git a/crates/compile/src/compile/expr_key.rs b/crates/compile/src/compile/expr_key.rs deleted file mode 100644 index 19ddec377..000000000 --- a/crates/compile/src/compile/expr_key.rs +++ /dev/null @@ -1,415 +0,0 @@ -//! Exact keys for compiled expressions and universes. Stored digests index the -//! table; complete source equality confirms a hit, through the raw comparisons -//! of `ix_common::env` (`Expr::same_raw`, `Level::same_raw`, `Name::same_raw`: -//! the derived `==`, iterative and linear in the DAG, R03-21). This mirrors -//! `Ix.Compile.ExactKey`. - -use ix_common::{ - address::Address, - env::{Expr, Level, Name}, -}; -use std::{ - hash::{Hash, Hasher}, - sync::Arc, -}; - -#[derive(Clone, Debug)] -pub struct ExprKey(pub Expr); - -impl Hash for ExprKey { - fn hash(&self, state: &mut H) { - self.0.hash(state); - } -} - -impl PartialEq for ExprKey { - fn eq(&self, other: &Self) -> bool { - self.0.same_raw(&other.0) - } -} - -impl Eq for ExprKey {} - -/// A visited collector node in its exact positional universe context. -/// The precomputed context hash is a bucket hint, never an equality witness. -#[derive(Clone, Debug)] -pub(super) struct PreseedKey { - pub(super) expr: Expr, - pub(super) params: Arc<[Name]>, - pub(super) context_hash: Address, -} - -impl Hash for PreseedKey { - fn hash(&self, state: &mut H) { - self.expr.hash(state); - self.context_hash.hash(state); - } -} - -impl PartialEq for PreseedKey { - fn eq(&self, other: &Self) -> bool { - self.context_hash == other.context_hash - && self.expr.same_raw(&other.expr) - && (Arc::ptr_eq(&self.params, &other.params) - || (self.params.len() == other.params.len() - && self - .params - .iter() - .zip(other.params.iter()) - .all(|(a, b)| a.same_raw(b)))) - } -} - -impl Eq for PreseedKey {} - -/// A positional universe-parameter context with its digest, built once per -/// universe and shared by the keys of all its sub-levels. -#[derive(Clone, Debug)] -pub struct UnivContext { - params: Arc<[Name]>, - hash: Address, -} - -impl UnivContext { - pub fn new(params: &[Name]) -> Self { - Self { params: params.into(), hash: super::univ_params_key(params) } - } - - pub fn params(&self) -> &[Name] { - &self.params - } -} - -/// The source level and its complete positional parameter context. Context -/// digests select candidates but never identify different name sequences. -#[derive(Clone, Debug)] -pub struct UnivKey { - level: Level, - params: Arc<[Name]>, - context_hash: Address, -} - -impl UnivKey { - pub fn new(level: &Level, ctx: &UnivContext) -> Self { - Self { - level: level.clone(), - params: ctx.params.clone(), - context_hash: ctx.hash.clone(), - } - } -} - -impl Hash for UnivKey { - fn hash(&self, state: &mut H) { - self.level.hash(state); - self.context_hash.hash(state); - } -} - -impl PartialEq for UnivKey { - fn eq(&self, other: &Self) -> bool { - self.context_hash == other.context_hash - && self.params.len() == other.params.len() - && self.params.iter().zip(other.params.iter()).all(|(a, b)| a.same_raw(b)) - && self.level.same_raw(&other.level) - } -} - -impl Eq for UnivKey {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::compile::{ - BlockCache, CompileState, collect_expr_tables, compile_expr, compile_univ, - compile_univ_idx, preseed_expr_tables, - }; - use crate::mutual::MutCtx; - use bignat::Nat; - use ix_common::env::{ - BinderInfo, DataValue, ExprData, LevelData, Literal, NameData, - }; - use rustc_hash::FxHashSet; - - fn raw(value: ExprData) -> Expr { - Expr(Arc::new(value)) - } - - #[test] - fn constructor_matrix_agrees_with_raw_equality() { - let h = blake3::hash(b"constructed shared digest"); - let n = Name::anon(); - let x = Name::str(n.clone(), "x".into()); - let y = Name(Arc::new(NameData::Str(n.clone(), "y".into(), *x.get_hash()))); - let a = raw(ExprData::Bvar(Nat::from(0u64), h)); - let b = raw(ExprData::Bvar(Nat::from(1u64), h)); - let l = Level::zero(); - let samples = vec![ - a.clone(), - b.clone(), - raw(ExprData::Bvar(Nat::from(0u64), h)), - raw(ExprData::Fvar(x.clone(), h)), - raw(ExprData::Fvar(y, h)), - raw(ExprData::Mvar(x.clone(), h)), - raw(ExprData::Sort(l.clone(), h)), - raw(ExprData::Const(x.clone(), vec![l], h)), - raw(ExprData::App(a.clone(), b.clone(), h)), - raw(ExprData::Lam( - n.clone(), - a.clone(), - b.clone(), - BinderInfo::Default, - h, - )), - raw(ExprData::Lam( - n.clone(), - a.clone(), - b.clone(), - BinderInfo::Implicit, - h, - )), - raw(ExprData::ForallE( - n.clone(), - a.clone(), - b.clone(), - BinderInfo::Default, - h, - )), - raw(ExprData::LetE(n.clone(), a.clone(), b.clone(), a.clone(), false, h)), - raw(ExprData::LetE(n, a.clone(), b.clone(), a.clone(), true, h)), - raw(ExprData::Lit(Literal::NatVal(Nat::from(7u64)), h)), - raw(ExprData::Lit(Literal::StrVal("7".into()), h)), - raw(ExprData::Mdata( - vec![(x.clone(), DataValue::OfBool(false))], - a.clone(), - h, - )), - raw(ExprData::Mdata( - vec![(x.clone(), DataValue::OfBool(true))], - a.clone(), - h, - )), - raw(ExprData::Proj(x, Nat::from(0u64), a, h)), - ]; - for a in &samples { - for b in &samples { - assert_eq!(a.same_raw(b), a == b); - } - } - } - - #[test] - fn distinct_shared_dags_use_completed_pairs() { - let n = Name::anon(); - let mut a = Expr::bvar(Nat::from(0u64)); - let mut b = Expr::bvar(Nat::from(0u64)); - for _ in 0..64 { - a = Expr::lam(n.clone(), a.clone(), a, BinderInfo::Default); - b = Expr::lam(n.clone(), b.clone(), b, BinderInfo::Default); - } - assert!(a.same_raw(&b)); - let mut u = Level::zero(); - let mut v = Level::zero(); - for _ in 0..64 { - u = Level::max(u.clone(), u); - v = Level::max(v.clone(), v); - } - assert!(Expr::sort(u).same_raw(&Expr::sort(v))); - } - - #[test] - fn colliding_source_keys_compile_and_cache_independently() { - let stt = CompileState::default(); - let mut cache = BlockCache::default(); - let ctx = MutCtx::default(); - let a = Expr::bvar(Nat::from(0u64)); - let b = raw(ExprData::Bvar(Nat::from(1u64), *a.get_hash())); - let bad = raw(ExprData::Mvar(Name::anon(), *a.get_hash())); - let first = compile_expr(&a, &[], &ctx, &mut cache, &stt).unwrap(); - assert_eq!(first, ixon::expr::Expr::var(0)); - let second = compile_expr(&b, &[], &ctx, &mut cache, &stt).unwrap(); - assert_eq!(second, ixon::expr::Expr::var(1)); - assert_eq!(cache.arena_roots.last(), Some(&1)); - assert_eq!(cache.exprs.len(), 2); - let again = compile_expr(&a, &[], &ctx, &mut cache, &stt).unwrap(); - assert_eq!(again, first); - assert_eq!(cache.arena_roots.last(), Some(&0)); - assert!(compile_expr(&bad, &[], &ctx, &mut cache, &stt).is_err()); - assert_eq!(cache.exprs.len(), 2); - } - - #[test] - fn preseed_distinguishes_colliding_expressions_with_valid_neighbour() { - for collide in [false, true] { - let u = Name::str(Name::anon(), "u".into()); - let v = Name::str(Name::anon(), "v".into()); - let first = Expr::sort(Level::param(u.clone())); - let second = if collide { - raw(ExprData::Sort(Level::param(v.clone()), *first.get_hash())) - } else { - Expr::sort(Level::param(v.clone())) - }; - let params = [u, v]; - let stt = CompileState::default(); - let ctx = MutCtx::default(); - let mut cache = BlockCache::default(); - preseed_expr_tables( - &[(&first, ¶ms), (&second, ¶ms), (&first, ¶ms)], - &ctx, - &mut cache, - &stt, - "preseed expression collision", - ) - .unwrap(); - assert_eq!(cache.univs.len(), 2); - assert_eq!( - compile_expr(&second, ¶ms, &ctx, &mut cache, &stt).unwrap(), - ixon::expr::Expr::sort(1) - ); - assert_eq!(cache.univs.len(), 2); - assert!(cache.univs_final); - } - } - - #[test] - fn preseed_distinguishes_colliding_contexts_with_valid_neighbour() { - for collide in [false, true] { - let u = Name::str(Name::anon(), "u".into()); - let v = if collide { - Name(Arc::new(NameData::Str(Name::anon(), "v".into(), *u.get_hash()))) - } else { - Name::str(Name::anon(), "v".into()) - }; - let first = [u.clone(), v.clone()]; - let second = [v, u.clone()]; - assert_eq!( - super::super::univ_params_key(&first) - == super::super::univ_params_key(&second), - collide - ); - let level = Level::param(u); - let expr = Expr::sort(level.clone()); - let stt = CompileState::default(); - let ctx = MutCtx::default(); - let mut cache = BlockCache::default(); - preseed_expr_tables( - &[(&expr, &first), (&expr, &second)], - &ctx, - &mut cache, - &stt, - "preseed context collision", - ) - .unwrap(); - assert_eq!(cache.univs.len(), 2); - assert_eq!(compile_univ_idx(&level, &first, &mut cache).unwrap().0, 0); - assert_eq!(compile_univ_idx(&level, &second, &mut cache).unwrap().0, 1); - assert_eq!( - compile_expr(&expr, &second, &ctx, &mut cache, &stt).unwrap(), - ixon::expr::Expr::sort(1) - ); - assert_eq!(cache.univs.len(), 2); - } - } - - #[test] - fn preseed_collision_does_not_hide_an_unsupported_expression() { - let good = Expr::sort(Level::zero()); - let bad = raw(ExprData::Mvar(Name::anon(), *good.get_hash())); - let stt = CompileState::default(); - let ctx = MutCtx::default(); - let mut cache = BlockCache::default(); - let result = preseed_expr_tables( - &[(&good, &[]), (&bad, &[])], - &ctx, - &mut cache, - &stt, - "preseed unsupported collision", - ); - assert!(matches!(result, Err(ixon::CompileError::UnsupportedExpr { .. }))); - assert!(cache.univs.is_empty()); - assert!(!cache.univs_final); - preseed_expr_tables( - &[(&good, &[]), (&good, &[])], - &ctx, - &mut cache, - &stt, - "preseed valid duplicate", - ) - .unwrap(); - assert_eq!(cache.univs.len(), 1); - } - - #[test] - fn preseed_visits_shared_dags_once_under_exact_keys() { - let n = Name::anon(); - let mut a = Expr::bvar(Nat::from(0u64)); - let mut b = Expr::bvar(Nat::from(0u64)); - for _ in 0..64 { - a = Expr::lam(n.clone(), a.clone(), a, BinderInfo::Default); - b = Expr::lam(n.clone(), b.clone(), b, BinderInfo::Default); - } - let mut refs = Vec::new(); - let mut univs = Vec::new(); - let mut seen = FxHashSet::default(); - let mut cache = BlockCache::default(); - let stt = CompileState::default(); - let ctx = MutCtx::default(); - for expr in [&a, &b] { - collect_expr_tables( - expr, - &[], - &ctx, - &mut cache, - &stt, - &mut refs, - &mut univs, - &mut seen, - "preseed shared DAG", - ) - .unwrap(); - } - assert_eq!(seen.len(), 65); - assert!(refs.is_empty()); - assert!(univs.is_empty()); - } - - #[test] - fn universe_cache_distinguishes_equal_digest_contexts() { - let u = Name::str(Name::anon(), "u".into()); - let v = - Name(Arc::new(NameData::Str(Name::anon(), "v".into(), *u.get_hash()))); - let level = Level::param(u.clone()); - let first_context = [u.clone(), v.clone()]; - let second_context = [v, u]; - let mut cache = BlockCache::default(); - assert_eq!( - compile_univ(&level, &first_context, &mut cache).unwrap(), - ixon::univ::Univ::var(0) - ); - let cold = - compile_univ(&level, &second_context, &mut BlockCache::default()) - .unwrap(); - assert_eq!(cold, ixon::univ::Univ::var(1)); - let warm = compile_univ(&level, &second_context, &mut cache).unwrap(); - assert_eq!(warm, cold); - assert_eq!(cache.univ_cache.len(), 2); - } - - #[test] - fn universe_cache_keeps_equal_digest_levels_distinct() { - let zero = Level::zero(); - let succ = Level(Arc::new(LevelData::Succ(zero.clone(), *zero.get_hash()))); - let bad = Level(Arc::new(LevelData::Mvar(Name::anon(), *zero.get_hash()))); - let mut cache = BlockCache::default(); - assert_eq!( - compile_univ(&zero, &[], &mut cache).unwrap(), - ixon::univ::Univ::zero() - ); - assert_eq!( - compile_univ(&succ, &[], &mut cache).unwrap(), - ixon::univ::Univ::succ(ixon::univ::Univ::zero()) - ); - assert!(compile_univ(&bad, &[], &mut cache).is_err()); - assert_eq!(cache.univ_cache.len(), 2); - } -} diff --git a/crates/compile/src/compile/ind_flags.rs b/crates/compile/src/compile/ind_flags.rs deleted file mode 100644 index cb9a1a146..000000000 --- a/crates/compile/src/compile/ind_flags.rs +++ /dev/null @@ -1,1152 +0,0 @@ -//! Lean's block-wide inductive flags (`isRec`, `isReflexive`, `numNested`), -//! recomputed from the constructors. -//! -//! Ixon does not store these flags, so kernel egress and decompilation -//! reconstruct them, and compilation rejects an environment whose stored -//! flags differ from the recomputed ones (a domain restriction). -//! -//! `numNested` counts the auxiliary members of the block's nested expansion: -//! every nested occurrence in constructor syntax (e.g. the `List Tree` inside -//! `Id (List Tree)`) is replaced by an auxiliary member sharing the block's -//! parameters and levels, as the C++ kernel's `elim_nested_inductive` does. -//! The traversal walks syntax without delta-reducing definition heads. -//! Block parameters are opened as FVars; later binders retain BVars. Free -//! BVars and non-parameter FVars cannot enter a specialization. - -use bignat::Nat; -use rustc_hash::{FxHashMap, FxHashSet}; -use std::sync::LazyLock; -use std::time::Instant; - -use crate::compile::ExprKey; -use crate::compile::nat_conv::{nat_to_u64, nat_to_usize}; -use crate::compile::validation::{self, AttemptError, Cancelled, Checkpoint}; -use ix_common::env::{ - BinderInfo, ConstantInfo, Env as LeanEnv, Expr as LeanExpr, ExprData, Level, - Name, -}; -use ixon::CompileError; - -mod checked_expr; -#[cfg(test)] -mod expr_reference; - -// ========================================================================= -// FVar infrastructure -// ========================================================================= - -/// A local declaration: FVar name, binder metadata, and domain type. -/// -/// Accumulates binder information while building expressions in FVar space. -/// The `fvar_name` is a unique identifier; `binder_name` is the cosmetic name -/// that appears in the final forall chain. -#[derive(Clone)] -struct LocalDecl { - fvar_name: Name, - binder_name: Name, - domain: LeanExpr, - info: BinderInfo, -} - -/// Create a fresh FVar with a unique name derived from `prefix` and `idx`. -fn fresh_fvar(prefix: &str, idx: usize) -> (Name, LeanExpr) { - let name = Name::str(Name::anon(), format!("_{}_{}", prefix, idx)); - let fvar = LeanExpr::fvar(name.clone()); - (name, fvar) -} - -/// Decompose an application spine: `f a1 a2 ... an` -> `(f, [a1, ..., an])`. -fn decompose_apps(expr: &LeanExpr) -> (LeanExpr, Vec) { - let mut args = Vec::new(); - let mut cur = expr.clone(); - while let ExprData::App(f, a, _) = cur.as_data() { - args.push(a.clone()); - cur = f.clone(); - } - args.reverse(); - (cur, args) -} - -// ========================================================================= -// Nested expansion -// ========================================================================= - -/// A mutual block whose nested inductive occurrences have been replaced with -/// auxiliary members sharing the block's parameters and levels. -struct ExpandedBlock { - /// All members of the expanded block: originals first, then auxiliaries. - types: Vec, - /// Number of original (non-auxiliary) members. - n_originals: usize, -} - -/// A member of the expanded block (original or auxiliary). -struct ExpandedMember { - /// Inductive name: the original name for originals, `_nested.ExtInd_N` - /// (scoped under `all[0]`) for auxiliaries. - name: Name, - /// Constructor types with nested occurrences replaced by applications of - /// auxiliary members: `∀ (block_params...) (fields...) → Member ...`. - ctors: Vec, -} - -/// Mutable state for the nested expansion. -struct ExpandCtx<'a> { - types: Vec, - /// Mirror of `types.iter().map(|m| m.name)` maintained incrementally, for - /// O(1) "is this name in the block?" checks in the hot `replace_if_nested` - /// path. Every member is pushed through `push_type`, which keeps both in - /// sync. - type_name_set: FxHashSet, - /// Dedup: each detected occurrence, compared exactly, to its auxiliary. - aux_seen: FxHashMap, - next_aux_idx: usize, - all0: Name, - block_levels: Vec, - block_param_fvars: Vec, - block_param_decls: Vec, - block_param_fvar_names: Vec, - lean_env: &'a LeanEnv, - control: &'a Checkpoint, -} - -impl ExpandCtx<'_> { - /// Push a new member and keep `type_name_set` in sync. - fn push_type(&mut self, member: ExpandedMember) { - self.type_name_set.insert(member.name.clone()); - self.types.push(member); - } - - /// Recursively replace all nested inductive occurrences in an expression. - /// - /// Matches C++ `replace_all_nested` (`inductive.cpp:1031`): walks the - /// expression top-down, calling `replace_if_nested` at each sub-expression. - /// - /// `cache` memoizes input-expression hashes to output rewrites for the - /// current constructor walk only: the result depends on `as_fvars`, so the - /// caller provides a fresh cache per constructor. Within a single - /// constructor walk the function is deterministic (auxiliaries created - /// while processing one subterm cannot change the rewrite of an - /// already-processed one), so memoization is safe even though `self` - /// mutates during the walk. - fn replace_all_nested( - &mut self, - e: &LeanExpr, - as_fvars: &[LeanExpr], - cache: &mut FxHashMap, - ) -> Result { - self.control.visit()?; - let key = ExprKey(e.clone()); - if let Some(cached) = cache.get(&key) { - return Ok(cached.clone()); - } - - // Try top-level replacement first. - if let Some(replaced) = self.replace_if_nested(e, as_fvars)? { - cache.insert(key, replaced.clone()); - return Ok(replaced); - } - // No match — recurse into sub-expressions. - let result = match e.as_data() { - ExprData::App(f, a, _) => LeanExpr::app( - self.replace_all_nested(f, as_fvars, cache)?, - self.replace_all_nested(a, as_fvars, cache)?, - ), - ExprData::Lam(n, t, b, bi, _) => LeanExpr::lam( - n.clone(), - self.replace_all_nested(t, as_fvars, cache)?, - self.replace_all_nested(b, as_fvars, cache)?, - bi.clone(), - ), - ExprData::ForallE(n, t, b, bi, _) => LeanExpr::all( - n.clone(), - self.replace_all_nested(t, as_fvars, cache)?, - self.replace_all_nested(b, as_fvars, cache)?, - bi.clone(), - ), - ExprData::LetE(n, t, v, b, nd, _) => LeanExpr::letE( - n.clone(), - self.replace_all_nested(t, as_fvars, cache)?, - self.replace_all_nested(v, as_fvars, cache)?, - self.replace_all_nested(b, as_fvars, cache)?, - *nd, - ), - ExprData::Proj(n, i, val, _) => LeanExpr::proj( - n.clone(), - i.clone(), - self.replace_all_nested(val, as_fvars, cache)?, - ), - ExprData::Mdata(md, inner, _) => LeanExpr::mdata( - md.clone(), - self.replace_all_nested(inner, as_fvars, cache)?, - ), - _ => e.clone(), - }; - let result = if result.same_raw(e) { e.clone() } else { result }; - cache.insert(key, result.clone()); - self.control.scratch( - cache.capacity() - * (size_of::<(ExprKey, LeanExpr)>() + size_of::()), - ); - Ok(result) - } - - /// Check if `e` is a nested inductive application and, if so, create - /// auxiliary members and return the replacement expression. - /// - /// Matches C++ `replace_if_nested` (`inductive.cpp:963-1027`). - fn replace_if_nested( - &mut self, - e: &LeanExpr, - as_fvars: &[LeanExpr], - ) -> Result, Cancelled> { - self.control.visit()?; - let (head, args) = decompose_apps(e); - let (head_name, head_levels) = match head.as_data() { - ExprData::Const(name, levels, _) => (name.clone(), levels.clone()), - _ => return Ok(None), - }; - - // Skip if head is in the block (direct recursive, not nested). - if self.type_name_set.contains(&head_name) { - return Ok(None); - } - - // Verify head is an external inductive. - let ext_ind_ref = self.lean_env.get(&head_name); - let ext_ind = match ext_ind_ref.as_deref() { - Some(ConstantInfo::InductInfo(v)) => v, - _ => return Ok(None), - }; - let ext_n_params = nat_to_usize(&ext_ind.num_params); - - if args.len() < ext_n_params { - return Ok(None); - } - - // Check if any parameter arg mentions a block member. - let mut mentions = false; - for arg in args.iter().take(ext_n_params) { - if checked_expr::any(arg, self.control, |e, _| match e.as_data() { - ExprData::Const(n, _, _) | ExprData::Proj(n, _, _, _) => { - self.type_name_set.contains(n) - }, - _ => false, - })? { - mentions = true; - break; - } - } - if !mentions { - return Ok(None); - } - - // Extract spec_params, normalizing constructor-local parameter FVars to - // the block parameter FVars before validation. Parameterized nested - // occurrences such as `List (Rose α)` are seen while scanning a - // constructor telescope, so their raw spec params mention `as_fvars`; the - // auxiliary identity must be expressed in the shared block-param space. - let spec_params: Vec = args[..ext_n_params] - .iter() - .map(|sp| { - checked_expr::replace_params( - sp, - as_fvars, - &self.block_param_fvars, - self.control, - ) - }) - .collect::>()?; - for sp in &spec_params { - if checked_expr::any(sp, self.control, |e, depth| match e.as_data() { - ExprData::Bvar(i, _) => nat_to_u64(i) >= depth, - ExprData::Fvar(n, _) => !self.block_param_fvar_names.contains(n), - _ => false, - })? { - return Ok(None); - } - } - - // `I.{I_lvls} spec_params`, normalized to block param FVars. - let apply_spec = |name: &Name| { - let mut app = LeanExpr::cnst(name.clone(), head_levels.clone()); - for sp in &spec_params { - app = LeanExpr::app(app, sp.clone()); - } - app - }; - - // Dedup: check if we've already created an auxiliary for this occurrence. - if let Some(aux_name) = self.aux_seen.get(&ExprKey(apply_spec(&head_name))) - { - let mut result = - LeanExpr::cnst(aux_name.clone(), self.block_levels.clone()); - for af in as_fvars { - result = LeanExpr::app(result, af.clone()); - } - for idx_arg in args.iter().skip(ext_n_params) { - result = LeanExpr::app(result, idx_arg.clone()); - } - return Ok(Some(result)); - } - - // New nested occurrence — create an auxiliary member for every member of - // the external inductive's mutual group (Lean's `I.all`). - let group: Vec = ext_ind.all.clone(); - let mut result: Option = None; - - for j_name in &group { - self.control.visit()?; - let j_info_ref = self.lean_env.get(j_name); - let j_info = match j_info_ref.as_deref() { - Some(ConstantInfo::InductInfo(v)) => v, - _ => continue, - }; - - // Auxiliary name: _nested.ExtInd_N (scoped under all[0]). - let aux_name = Name::str( - Name::str(self.all0.clone(), "_nested".to_string()), - format!("{}_{}", j_name.pretty().replace('.', "_"), self.next_aux_idx), - ); - self.next_aux_idx += 1; - - // Every member `J As` of the group registers under its own occurrence - // (Lean's `elim_nested_inductive_fn`): a later occurrence of a sibling, - // e.g. `Forest T` inside the `Tree T` auxiliary's constructors, dedups - // to that sibling's auxiliary instead of opening a second copy of the - // group. - self - .aux_seen - .entry(ExprKey(apply_spec(j_name))) - .or_insert_with(|| aux_name.clone()); - - // Build the auxiliary constructors. - let mut aux_ctors: Vec = Vec::new(); - for j_ctor_name in &j_info.ctors { - self.control.visit()?; - let j_ctor_ref = self.lean_env.get(j_ctor_name); - let j_ctor = match j_ctor_ref.as_deref() { - Some(ConstantInfo::CtorInfo(c)) => c, - _ => continue, - }; - let ctor_type_inst = checked_expr::subst_levels( - &j_ctor.cnst.typ, - &j_info.cnst.level_params, - &head_levels, - self.control, - )?; - let ctor_type_peeled = checked_expr::instantiate_pi_params( - &ctor_type_inst, - ext_n_params, - &spec_params, - self.control, - )?; - let ctor_type_block = checked_expr::replace_params( - &ctor_type_peeled, - as_fvars, - &self.block_param_fvars, - self.control, - )?; - let ctor_type_block = replace_ctor_result_head_with_aux( - &ctor_type_block, - j_name, - &aux_name, - ext_n_params, - &self.block_levels, - &self.block_param_fvars, - self.control, - )?; - aux_ctors.push(checked_expr::mk_forall( - ctor_type_block, - &self.block_param_decls, - self.control, - )?); - } - - // If this is the head inductive, build the replacement expression. - if *j_name == head_name { - let mut r = LeanExpr::cnst(aux_name.clone(), self.block_levels.clone()); - for af in as_fvars { - r = LeanExpr::app(r, af.clone()); - } - for idx_arg in args.iter().skip(ext_n_params) { - r = LeanExpr::app(r, idx_arg.clone()); - } - result = Some(r); - } - - self.push_type(ExpandedMember { name: aux_name, ctors: aux_ctors }); - } - - Ok(result) - } -} - -/// Expand the block `originals` (Lean's `all`, in source order): replace -/// nested inductive occurrences with auxiliary members that share the -/// block's parameters and universe levels. -/// -/// Matches the C++ kernel's `elim_nested_inductive_fn::operator()()` at -/// `refs/lean4/src/kernel/inductive.cpp:1045-1077`. -fn expand_nested_block( - originals: &[Name], - lean_env: &LeanEnv, - control: &Checkpoint, -) -> Result { - control.visit()?; - let first_name = - originals.first().ok_or_else(|| CompileError::InvalidMutualBlock { - reason: "expand_nested_block: empty originals".into(), - })?; - let first_ind_ref = lean_env.get(first_name); - let first_ind = match first_ind_ref.as_deref() { - Some(ConstantInfo::InductInfo(v)) => v, - _ => { - return Err( - CompileError::MissingConstant { - name: first_name.pretty(), - caller: "expand_nested_block: first original not an inductive".into(), - } - .into(), - ); - }, - }; - - let n_params = nat_to_usize(&first_ind.num_params); - let block_levels: Vec = first_ind - .cnst - .level_params - .iter() - .map(|lp| Level::param(lp.clone())) - .collect(); - - let (block_param_fvars, block_param_decls, _) = - checked_expr::forall_telescope( - &first_ind.cnst.typ, - n_params, - "bp", - 0, - control, - )?; - let block_param_fvar_names: Vec = - block_param_decls.iter().map(|d| d.fvar_name.clone()).collect(); - - let all0 = - first_ind.all.first().cloned().unwrap_or_else(|| originals[0].clone()); - - let mut ctx = ExpandCtx { - types: Vec::new(), - type_name_set: FxHashSet::default(), - aux_seen: FxHashMap::default(), - next_aux_idx: 1, - all0, - block_levels, - block_param_fvars, - block_param_decls, - block_param_fvar_names, - lean_env, - control, - }; - - // Seed with the original inductives. - for name in originals { - control.visit()?; - let ind_ref = lean_env.get(name); - let ind = match ind_ref.as_deref() { - Some(ConstantInfo::InductInfo(v)) => v, - _ => { - return Err( - CompileError::MissingConstant { - name: name.pretty(), - caller: "expand_nested_block: original not an inductive".into(), - } - .into(), - ); - }, - }; - let ctors: Vec = ind - .ctors - .iter() - .filter_map(|cn| match lean_env.get(cn).as_deref() { - Some(ConstantInfo::CtorInfo(c)) => Some(c.cnst.typ.clone()), - _ => None, - }) - .collect(); - ctx.push_type(ExpandedMember { name: name.clone(), ctors }); - } - - let n_originals = ctx.types.len(); - - // Queue-based scan: process each member's constructors, auxiliaries - // included. A fresh memoization cache is allocated per constructor because - // `replace_all_nested` closes over `as_fvars`, which differs between - // constructors. Within a single constructor the walk is deterministic, so - // the cache turns DAG traversal from O(shared × nodes) into O(nodes). - let mut qi = 0; - while qi < ctx.types.len() { - control.visit()?; - let n_ctors = ctx.types[qi].ctors.len(); - for ci in 0..n_ctors { - let ctor_type = ctx.types[qi].ctors[ci].clone(); - - // Peel params, re-creating FVars per constructor for binding info. - let (as_fvars, as_decls, peeled) = checked_expr::forall_telescope( - &ctor_type, - n_params, - "cp", - qi * 100 + ci, - control, - )?; - - // Replace all nested occurrences in the peeled body. - let mut walk_cache: FxHashMap = FxHashMap::default(); - let replaced = - ctx.replace_all_nested(&peeled, &as_fvars, &mut walk_cache)?; - - // Re-wrap with constructor-local params. - ctx.types[qi].ctors[ci] = - checked_expr::mk_forall(replaced, &as_decls, control)?; - } - qi += 1; - } - - Ok(ExpandedBlock { types: ctx.types, n_originals }) -} - -/// Rewrite the final result of an auxiliary constructor from the external -/// inductive `J spec_params indices` to the synthetic aux -/// `aux_name block_params indices`. -/// -/// Lean's nested-inductive pass eventually rewrites these constructor results -/// when the queue processes the freshly-created auxiliary type. Doing it at -/// creation time avoids rediscovering the aux's own result as a second nested -/// occurrence while leaving constructor field domains available for the normal -/// queue walk. -fn replace_ctor_result_head_with_aux( - e: &LeanExpr, - original_ind: &Name, - aux_name: &Name, - original_n_params: usize, - block_levels: &[Level], - block_param_fvars: &[LeanExpr], - control: &Checkpoint, -) -> Result { - control.visit()?; - Ok(match e.as_data() { - ExprData::ForallE(n, t, b, bi, _) => LeanExpr::all( - n.clone(), - t.clone(), - replace_ctor_result_head_with_aux( - b, - original_ind, - aux_name, - original_n_params, - block_levels, - block_param_fvars, - control, - )?, - bi.clone(), - ), - ExprData::Mdata(md, inner, _) => LeanExpr::mdata( - md.clone(), - replace_ctor_result_head_with_aux( - inner, - original_ind, - aux_name, - original_n_params, - block_levels, - block_param_fvars, - control, - )?, - ), - _ => { - let (head, args) = decompose_apps(e); - let ExprData::Const(head_name, _, _) = head.as_data() else { - return Ok(e.clone()); - }; - if head_name != original_ind || args.len() < original_n_params { - return Ok(e.clone()); - } - - let mut result = LeanExpr::cnst(aux_name.clone(), block_levels.to_vec()); - for param in block_param_fvars { - result = LeanExpr::app(result, param.clone()); - } - for idx_arg in args.iter().skip(original_n_params) { - result = LeanExpr::app(result, idx_arg.clone()); - } - result - }, - }) -} - -// ========================================================================= -// Flags -// ========================================================================= - -/// Lean-faithful declaration flags for an original mutual block -pub struct LeanIndFlags { - pub is_rec: bool, - pub is_reflexive: bool, - pub num_nested: u64, -} - -/// Recompute Lean's block-wide `isRec` / `isReflexive` / `numNested` for the -/// original mutual group `all` -/// used whereever an `InductiveVal` must be reconstructed without a Lean env -/// to copy from, such as kernel egress and decompile, since Ixon doesn't store -/// these flags -pub fn compute_lean_ind_flags( - all: &[Name], - lean_env: &LeanEnv, -) -> Result { - compute_lean_ind_flags_checked(all, lean_env, &Checkpoint::default()) - .map_err(AttemptError::into_compile) -} - -fn compute_lean_ind_flags_checked( - all: &[Name], - lean_env: &LeanEnv, - control: &Checkpoint, -) -> Result { - let expanded = expand_nested_block(all, lean_env, control)?; - let num_nested = (expanded.types.len() - expanded.n_originals) as u64; - let block_names: FxHashSet<&Name> = - expanded.types.iter().map(|m| &m.name).collect(); - - let mut occ_cache: FxHashMap = FxHashMap::default(); - let mut is_rec = false; - let mut is_reflexive = false; - for member in &expanded.types { - for ctor in &member.ctors { - let mut ty = ctor; - while let ExprData::ForallE(_, dom, body, _, _) = ty.as_data() { - control.visit()?; - if has_ind_occ(dom, &block_names, &mut occ_cache, control)? { - is_rec = true; - if matches!(dom.as_data(), ExprData::ForallE(..)) { - is_reflexive = true; - } - } - ty = body; - } - } - } - Ok(LeanIndFlags { is_rec, is_reflexive, num_nested }) -} - -/// Validate that every inductive group in `lean_env` carries exactly the flags -/// `compute_lean_ind_flags` recomputes. -pub fn validate_lean_ind_flags(lean_env: &LeanEnv) -> Result<(), CompileError> { - let mut groups: FxHashMap> = FxHashMap::default(); - for (_, ci) in lean_env.iter() { - if let ConstantInfo::InductInfo(v) = &*ci - && let Some(first) = v.all.first() - { - groups.entry(first.clone()).or_insert_with(|| v.all.clone()); - } - } - validate_ind_groups(&groups, lean_env) -} - -static LOG_IND_GROUPS: LazyLock = - LazyLock::new(|| std::env::var_os("IX_LOG_IND_GROUPS").is_some()); - -/// Per-group half of [`validate_lean_ind_flags`], for callers that -/// already hold the inductive groups from a wider env pass. -/// `IX_LOG_IND_GROUPS=1` logs group entry/exit for allocation-failure diagnosis. -pub fn validate_ind_groups( - groups: &FxHashMap>, - lean_env: &LeanEnv, -) -> Result<(), CompileError> { - // Stable IDs make cancellations/retries identifiable in progress logs. - let mut entries: Vec<_> = groups.iter().collect(); - entries.sort_unstable_by(|(a, _), (b, _)| { - a.get_hash().as_bytes().cmp(b.get_hash().as_bytes()) - }); - let entries: Vec<_> = entries.into_iter().enumerate().collect(); - validation::run(&entries, |(id, (leader, all)), control| { - let start = Instant::now(); - if *LOG_IND_GROUPS { - eprintln!("[validate_ind_flags] BEGIN {} (group #{id})", leader.pretty()); - } - let result = validate_one_group(all, lean_env, control); - if *LOG_IND_GROUPS { - let status = match &result { - Ok(()) => "END", - Err(AttemptError::Cancelled) => "CANCEL", - Err(AttemptError::Compile(_)) => "ERROR", - }; - eprintln!( - "[validate_ind_flags] {status} {} (group #{id}, {:.2}s)", - leader.pretty(), - start.elapsed().as_secs_f32() - ); - } - result - }) -} - -fn validate_one_group( - all: &[Name], - lean_env: &LeanEnv, - control: &Checkpoint, -) -> Result<(), AttemptError> { - for member in all.iter() { - control.visit()?; - let entry = lean_env.get(member); - let Some(ConstantInfo::InductInfo(v)) = entry.as_deref() else { - return Ok(()); - }; - for cn in &v.ctors { - control.visit()?; - if !matches!(lean_env.get(cn).as_deref(), Some(ConstantInfo::CtorInfo(_))) - { - return Ok(()); - } - } - } - let flags = compute_lean_ind_flags_checked(all, lean_env, control)?; - for member in all.iter() { - let entry = lean_env.get(member); - let Some(ConstantInfo::InductInfo(v)) = entry.as_deref() else { - continue; // unreachable - }; - if v.is_rec != flags.is_rec - || v.is_reflexive != flags.is_reflexive - || v.num_nested != Nat::from(flags.num_nested) - { - return Err( - CompileError::InvalidMutualBlock { - reason: format!( - "non-canonical inductive flags for '{}': \ - stored isRec={} isReflexive={} numNested={}, \ - computed isRec={} isReflexive={} numNested={}", - member.pretty(), - v.is_rec, - v.is_reflexive, - v.num_nested, - flags.is_rec, - flags.is_reflexive, - flags.num_nested, - ), - } - .into(), - ); - } - } - Ok(()) -} - -// does `expr` mention any block type by name anywhere? -// DAG-memoized: ctor types share subterm DAGs -fn has_ind_occ( - expr: &LeanExpr, - names: &FxHashSet<&Name>, - cache: &mut FxHashMap, - control: &Checkpoint, -) -> Result { - control.visit()?; - let key = ExprKey(expr.clone()); - if let Some(&cached) = cache.get(&key) { - return Ok(cached); - } - let result = match expr.as_data() { - ExprData::Const(name, _, _) => names.contains(name), - ExprData::App(f, a, _) => { - has_ind_occ(f, names, cache, control)? - || has_ind_occ(a, names, cache, control)? - }, - ExprData::Lam(_, t, b, _, _) | ExprData::ForallE(_, t, b, _, _) => { - has_ind_occ(t, names, cache, control)? - || has_ind_occ(b, names, cache, control)? - }, - ExprData::LetE(_, t, v, b, _, _) => { - has_ind_occ(t, names, cache, control)? - || has_ind_occ(v, names, cache, control)? - || has_ind_occ(b, names, cache, control)? - }, - ExprData::Proj(_, _, e, _) | ExprData::Mdata(_, e, _) => { - has_ind_occ(e, names, cache, control)? - }, - _ => false, - }; - cache.insert(key, result); - control.scratch(cache.capacity() * size_of::<(ExprKey, bool)>()); - Ok(result) -} - -#[cfg(test)] -mod tests { - use super::*; - use ix_common::env::{ - ConstantVal, ConstructorVal, InductiveVal, Level as LL, - }; - - fn mk_name_for(s: &str) -> Name { - let mut n = Name::anon(); - for part in s.split('.') { - n = Name::str(n, part.to_string()); - } - n - } - - /// Recursive single-ctor inductive `N` with a real `CtorInfo` entry. - /// Canonical flags: isRec=true, isReflexive=false, numNested=0. - fn flags_env(is_rec: bool, is_reflexive: bool, num_nested: u64) -> LeanEnv { - let mut env = LeanEnv::default(); - let n = mk_name_for("N"); - env.insert( - n.clone(), - ConstantInfo::InductInfo(InductiveVal { - cnst: ConstantVal { - name: n.clone(), - level_params: vec![], - typ: LeanExpr::sort(LL::succ(LL::zero())), - }, - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: vec![n.clone()], - ctors: vec![mk_name_for("N.mk")], - num_nested: Nat::from(num_nested), - is_rec, - is_unsafe: false, - is_reflexive, - }), - ); - // N.mk : N → N - env.insert( - mk_name_for("N.mk"), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: ConstantVal { - name: mk_name_for("N.mk"), - level_params: vec![], - typ: LeanExpr::all( - mk_name_for("_"), - LeanExpr::cnst(n.clone(), vec![]), - LeanExpr::cnst(n.clone(), vec![]), - BinderInfo::Default, - ), - }, - induct: n, - cidx: Nat::from(0u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(1u64), - is_unsafe: false, - }), - ); - env - } - - #[test] - fn validate_lean_ind_flags_accepts_canonical() { - assert!(validate_lean_ind_flags(&flags_env(true, false, 0)).is_ok()); - } - - #[test] - fn validate_lean_ind_flags_rejects_bad_is_rec() { - let r = validate_lean_ind_flags(&flags_env(false, false, 0)); - assert!(matches!(r, Err(CompileError::InvalidMutualBlock { .. }))); - } - - #[test] - fn validate_lean_ind_flags_rejects_bad_num_nested() { - let r = validate_lean_ind_flags(&flags_env(true, false, 1)); - assert!(matches!(r, Err(CompileError::InvalidMutualBlock { .. }))); - } - - #[test] - fn validate_lean_ind_flags_skips_unresolvable_group() { - // Wrong flags, but the ctor entry is removed → group unresolvable → - // skipped (grounding owns partial envs), not rejected. - let full = flags_env(false, false, 0); - let env: LeanEnv = full - .iter() - .filter(|(n, _)| **n != mk_name_for("N.mk")) - .map(|(n, ci)| (n.clone(), ci.cloned())) - .collect(); - assert!(validate_lean_ind_flags(&env).is_ok()); - } - - /// `UniverseNested.{u,v}` with constructors `left : Phantom.{0,u} - /// UniverseNested → UniverseNested` and `right : Phantom.{0,v} - /// UniverseNested → UniverseNested`: the two occurrences differ only in - /// the external family's second universe argument, so each opens its own - /// auxiliary. - fn universe_specialized_nested_env() -> LeanEnv { - let mut env = LeanEnv::default(); - let phantom = mk_name_for("Phantom"); - let phantom_mk = mk_name_for("Phantom.mk"); - let root = mk_name_for("UniverseNested"); - let left = mk_name_for("UniverseNested.left"); - let right = mk_name_for("UniverseNested.right"); - let p = mk_name_for("p"); - let q = mk_name_for("q"); - let u = mk_name_for("u"); - let v = mk_name_for("v"); - let param = |name: &Name| LL::param(name.clone()); - let root_app = || LeanExpr::cnst(root.clone(), vec![param(&u), param(&v)]); - let sort0 = || LeanExpr::sort(LL::zero()); - - env.insert( - phantom.clone(), - ConstantInfo::InductInfo(InductiveVal { - cnst: ConstantVal { - name: phantom.clone(), - level_params: vec![p.clone(), q.clone()], - typ: LeanExpr::all( - mk_name_for("alpha"), - LeanExpr::sort(param(&p)), - sort0(), - BinderInfo::Default, - ), - }, - num_params: Nat::from(1u64), - num_indices: Nat::from(0u64), - all: vec![phantom.clone()], - ctors: vec![phantom_mk.clone()], - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }), - ); - env.insert( - phantom_mk.clone(), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: ConstantVal { - name: phantom_mk, - level_params: vec![p.clone(), q.clone()], - typ: LeanExpr::all( - mk_name_for("alpha"), - LeanExpr::sort(param(&p)), - LeanExpr::all( - mk_name_for("beta"), - LeanExpr::sort(param(&q)), - LeanExpr::app( - LeanExpr::cnst(phantom.clone(), vec![param(&p), param(&q)]), - LeanExpr::bvar(Nat::from(1u64)), - ), - BinderInfo::Default, - ), - BinderInfo::Default, - ), - }, - induct: phantom.clone(), - cidx: Nat::from(0u64), - num_params: Nat::from(1u64), - num_fields: Nat::from(1u64), - is_unsafe: false, - }), - ); - - env.insert( - root.clone(), - ConstantInfo::InductInfo(InductiveVal { - cnst: ConstantVal { - name: root.clone(), - level_params: vec![u.clone(), v.clone()], - typ: sort0(), - }, - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: vec![root.clone()], - ctors: vec![left.clone(), right.clone()], - num_nested: Nat::from(2u64), - is_rec: true, - is_unsafe: false, - is_reflexive: false, - }), - ); - for (name, level, cidx) in - [(left, param(&u), 0u64), (right, param(&v), 1u64)] - { - let nested = LeanExpr::app( - LeanExpr::cnst(phantom.clone(), vec![LL::zero(), level]), - root_app(), - ); - env.insert( - name.clone(), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: ConstantVal { - name, - level_params: vec![u.clone(), v.clone()], - typ: LeanExpr::all( - mk_name_for("nested"), - nested, - root_app(), - BinderInfo::Default, - ), - }, - induct: root.clone(), - cidx: Nat::from(cidx), - num_params: Nat::from(0u64), - num_fields: Nat::from(1u64), - is_unsafe: false, - }), - ); - } - env - } - - /// Each nested occurrence opens an auxiliary (`numNested`), and a field - /// whose type is an auxiliary is recursive (`isRec`). - #[test] - fn nested_occurrences_count_as_auxiliaries_and_recursion() { - let env = universe_specialized_nested_env(); - let flags = - compute_lean_ind_flags(&[mk_name_for("UniverseNested")], &env).unwrap(); - assert_eq!(flags.num_nested, 2); - assert!(flags.is_rec); - assert!(!flags.is_reflexive); - assert!(validate_lean_ind_flags(&env).is_ok()); - } - - /// Replace the field domain of both `UniverseNested` constructors by - /// `domain(original domain)`. - fn rewrite_field_domains( - env: &mut LeanEnv, - domain: impl Fn(&LeanExpr) -> LeanExpr, - ) { - for name in ["UniverseNested.left", "UniverseNested.right"] { - let Some(ConstantInfo::CtorInfo(ctor)) = env.get_mut(&mk_name_for(name)) - else { - panic!("missing fixture constructor"); - }; - let ExprData::ForallE(field, dom, result, info, _) = - ctor.cnst.typ.as_data() - else { - panic!("missing fixture field"); - }; - ctor.cnst.typ = - LeanExpr::all(field.clone(), domain(dom), result.clone(), info.clone()); - } - } - - fn flags_of(env: &LeanEnv) -> (bool, bool, u64) { - let flags = - compute_lean_ind_flags(&[mk_name_for("UniverseNested")], env).unwrap(); - (flags.is_rec, flags.is_reflexive, flags.num_nested) - } - - /// Nested occurrences are found beneath non-inductive heads and binders; - /// a field whose type is a function into one is reflexive. - #[test] - fn nested_occurrences_under_wrappers_and_binders_are_found() { - let mut env = universe_specialized_nested_env(); - rewrite_field_domains(&mut env, |dom| { - let wrapped = LeanExpr::app( - LeanExpr::cnst(mk_name_for("NonInductiveWrapper"), vec![]), - dom.clone(), - ); - LeanExpr::all( - mk_name_for("local"), - LeanExpr::sort(LL::zero()), - wrapped, - BinderInfo::Default, - ) - }); - assert_eq!(flags_of(&env), (true, true, 2)); - } - - /// A specialization that mentions a field-local binder is not a nested - /// occurrence: no auxiliary opens, though the field still mentions the - /// block. - #[test] - fn field_local_specializations_open_no_auxiliary() { - let mut env = universe_specialized_nested_env(); - rewrite_field_domains(&mut env, |dom| { - let ExprData::App(head, root, _) = dom.as_data() else { - panic!("missing fixture occurrence"); - }; - let dependent = LeanExpr::app( - head.clone(), - LeanExpr::app(LeanExpr::bvar(Nat::from(0u64)), root.clone()), - ); - let wrapped = LeanExpr::app( - LeanExpr::cnst(mk_name_for("NonInductiveWrapper"), vec![]), - dependent, - ); - let function_type = LeanExpr::all( - mk_name_for("p"), - LeanExpr::sort(LL::zero()), - LeanExpr::sort(LL::zero()), - BinderInfo::Default, - ); - LeanExpr::all( - mk_name_for("local"), - function_type, - wrapped, - BinderInfo::Default, - ) - }); - assert_eq!(flags_of(&env), (true, true, 0)); - } - - /// A recognized occurrence is replaced whole: an occurrence nested in its - /// specialization is not expanded separately, and both constructors' - /// identical occurrences share one auxiliary. - #[test] - fn recognized_occurrences_are_replaced_whole_and_deduplicated() { - let mut env = universe_specialized_nested_env(); - let root = LeanExpr::cnst( - mk_name_for("UniverseNested"), - vec![LL::param(mk_name_for("u")), LL::param(mk_name_for("v"))], - ); - let phantom = |level: &str, arg: LeanExpr| { - LeanExpr::app( - LeanExpr::cnst( - mk_name_for("Phantom"), - vec![LL::zero(), LL::param(mk_name_for(level))], - ), - arg, - ) - }; - let outer = phantom("u", phantom("v", root)); - rewrite_field_domains(&mut env, |_| outer.clone()); - assert_eq!(flags_of(&env), (true, false, 1)); - } - - #[test] - fn fresh_fvar_produces_unique_names() { - let (n1, f1) = fresh_fvar("p", 0); - let (n2, f2) = fresh_fvar("p", 1); - assert_ne!(n1, n2); - assert!(matches!(f1.as_data(), ExprData::Fvar(n, _) if *n == n1)); - assert!(matches!(f2.as_data(), ExprData::Fvar(n, _) if *n == n2)); - } - - #[test] - fn fresh_fvar_prefix_changes_name() { - let (na, _) = fresh_fvar("a", 0); - let (nb, _) = fresh_fvar("b", 0); - assert_ne!(na, nb); - } - - #[test] - fn decompose_apps_non_app() { - let e = LeanExpr::sort(LL::zero()); - let (head, args) = decompose_apps(&e); - assert_eq!(args.len(), 0); - assert_eq!(head.get_hash(), e.get_hash()); - } - - #[test] - fn decompose_apps_left_deep_order() { - // ((f a) b) c → head=f, args=[a, b, c] - let f = LeanExpr::cnst(mk_name_for("f"), vec![]); - let a = LeanExpr::sort(LL::zero()); - let b = LeanExpr::sort(LL::succ(LL::zero())); - let c = LeanExpr::bvar(Nat::from(0u64)); - let e = LeanExpr::app( - LeanExpr::app(LeanExpr::app(f.clone(), a.clone()), b.clone()), - c.clone(), - ); - let (head, args) = decompose_apps(&e); - assert_eq!(head.get_hash(), f.get_hash()); - assert_eq!(args.len(), 3); - assert_eq!(args[0].get_hash(), a.get_hash()); - assert_eq!(args[1].get_hash(), b.get_hash()); - assert_eq!(args[2].get_hash(), c.get_hash()); - } -} diff --git a/crates/compile/src/compile/ind_flags/checked_expr.rs b/crates/compile/src/compile/ind_flags/checked_expr.rs deleted file mode 100644 index c8b579c97..000000000 --- a/crates/compile/src/compile/ind_flags/checked_expr.rs +++ /dev/null @@ -1,983 +0,0 @@ -//! Cancellable, DAG-preserving versions of the transforms used by validation. -//! Caches are local to one substitution context, and include binder depth: -//! the same shared BVar can mean different things beneath different binders. - -use bignat::Nat; -use blake3::Hash; -use ix_common::env::{Expr as LeanExpr, ExprData, Level, LevelData, Name}; -use ix_common::name_table::NameTable; -use rustc_hash::{FxHashMap, FxHashSet}; - -use super::{LocalDecl, fresh_fvar}; -use crate::compile::ExprKey; -use crate::compile::nat_conv::nat_to_u64; -use crate::compile::validation::{Cancelled, Checkpoint}; - -type Key = (Hash, u64); -type Result = std::result::Result; - -fn rewrite( - e: &LeanExpr, - depth: u64, - control: &Checkpoint, - cache: &mut FxHashMap, - leaf: &mut impl FnMut(&LeanExpr, u64) -> Result>, -) -> Result { - control.visit()?; - let key = (*e.get_hash(), depth); - if let Some((stored, value)) = cache.get(&key) { - // Exact input equality includes every cached field: a hit must retain - // the raw result of this invocation's pure substitution step. Raw - // equality is linear in the DAG (the derived `==` is not). - if stored.same_raw(e) { - return Ok(value.clone()); - } - } - let result = if let Some(value) = leaf(e, depth)? { - value - } else { - match e.as_data() { - ExprData::App(f, a, _) => LeanExpr::app( - rewrite(f, depth, control, cache, leaf)?, - rewrite(a, depth, control, cache, leaf)?, - ), - ExprData::Lam(n, t, b, bi, _) => LeanExpr::lam( - n.clone(), - rewrite(t, depth, control, cache, leaf)?, - rewrite(b, depth + 1, control, cache, leaf)?, - bi.clone(), - ), - ExprData::ForallE(n, t, b, bi, _) => LeanExpr::all( - n.clone(), - rewrite(t, depth, control, cache, leaf)?, - rewrite(b, depth + 1, control, cache, leaf)?, - bi.clone(), - ), - ExprData::LetE(n, t, v, b, nd, _) => LeanExpr::letE( - n.clone(), - rewrite(t, depth, control, cache, leaf)?, - rewrite(v, depth, control, cache, leaf)?, - rewrite(b, depth + 1, control, cache, leaf)?, - *nd, - ), - ExprData::Proj(n, i, v, _) => LeanExpr::proj( - n.clone(), - i.clone(), - rewrite(v, depth, control, cache, leaf)?, - ), - ExprData::Mdata(md, v, _) => { - LeanExpr::mdata(md.clone(), rewrite(v, depth, control, cache, leaf)?) - }, - _ => e.clone(), - } - }; - // Avoid retaining a second copy of unchanged nodes and their children. - let result = if result.same_raw(e) { e.clone() } else { result }; - cache.insert(key, (e.clone(), result.clone())); - control.scratch( - cache.capacity() - * (size_of::<(Key, (LeanExpr, LeanExpr))>() + size_of::()), - ); - Ok(result) -} - -fn transform( - e: &LeanExpr, - control: &Checkpoint, - mut leaf: impl FnMut(&LeanExpr, u64) -> Result>, -) -> Result { - rewrite(e, 0, control, &mut FxHashMap::default(), &mut leaf) -} - -fn instantiate1( - e: &LeanExpr, - replacement: &LeanExpr, - c: &Checkpoint, -) -> Result { - instantiate1_at(e, replacement, 0, c) -} - -fn instantiate1_at( - e: &LeanExpr, - replacement: &LeanExpr, - depth: u64, - c: &Checkpoint, -) -> Result { - rewrite(e, depth, c, &mut FxHashMap::default(), &mut |e, depth| { - Ok(match e.as_data() { - ExprData::Bvar(i, _) if nat_to_u64(i) == depth => { - Some(replacement.clone()) - }, - ExprData::Bvar(i, _) if nat_to_u64(i) > depth => { - Some(LeanExpr::bvar(Nat::from(nat_to_u64(i) - 1))) - }, - _ => None, - }) - }) -} - -fn instantiate_rev( - e: &LeanExpr, - args: &[LeanExpr], - c: &Checkpoint, -) -> Result { - if args.is_empty() { - c.visit()?; - return Ok(e.clone()); - } - let mut shifted = FxHashMap::default(); - transform(e, c, |e, depth| { - let ExprData::Bvar(i, _) = e.as_data() else { - return Ok(None); - }; - let i = nat_to_u64(i); - if i < depth { - return Ok(None); - } - let index = i - depth; - if index >= args.len() as u64 { - return Ok(Some(LeanExpr::bvar(Nat::from(i - args.len() as u64)))); - } - if let Some(value) = shifted.get(&(index, depth)) { - return Ok(Some(LeanExpr::clone(value))); - } - let value = if depth == 0 { - args[index as usize].clone() - } else { - transform(&args[index as usize], c, |e, inner| { - Ok(match e.as_data() { - ExprData::Bvar(i, _) if nat_to_u64(i) >= inner => { - Some(LeanExpr::bvar(Nat::from(nat_to_u64(i) + depth))) - }, - _ => None, - }) - })? - }; - shifted.insert((index, depth), value.clone()); - Ok(Some(value)) - }) -} - -pub(super) fn instantiate_pi_params( - e: &LeanExpr, - n: usize, - args: &[LeanExpr], - c: &Checkpoint, -) -> Result { - debug_assert!(args.len() >= n); - let mut cur = e.clone(); - for arg in args.iter().take(n) { - c.visit()?; - if let ExprData::ForallE(_, _, body, _, _) = cur.as_data() { - cur = instantiate_rev(body, std::slice::from_ref(arg), c)?; - } else { - break; - } - } - Ok(cur) -} - -pub(super) fn forall_telescope( - e: &LeanExpr, - n: usize, - prefix: &str, - start: usize, - c: &Checkpoint, -) -> Result<(Vec, Vec, LeanExpr)> { - let mut fvars = Vec::new(); - let mut decls = Vec::new(); - let mut cur = e.clone(); - for i in 0..n { - c.visit()?; - while let ExprData::Mdata(_, inner, _) = cur.as_data() { - c.visit()?; - cur = inner.clone(); - } - let ExprData::ForallE(name, dom, body, bi, _) = cur.as_data() else { - break; - }; - let (fvar_name, fv) = fresh_fvar(prefix, start + i); - decls.push(LocalDecl { - fvar_name, - binder_name: name.clone(), - domain: dom.clone(), - info: bi.clone(), - }); - fvars.push(fv.clone()); - cur = instantiate1(body, &fv, c)?; - } - Ok((fvars, decls, cur)) -} - -#[cfg(test)] -fn batch_abstract( - e: &LeanExpr, - vars: &FxHashMap, - scope: usize, - c: &Checkpoint, -) -> Result { - batch_abstract_with(e, &|name| vars.get(name).copied(), scope, 0, c) -} - -fn batch_abstract_names_at( - e: &LeanExpr, - vars: &NameTable, - scope: usize, - depth: u64, - c: &Checkpoint, -) -> Result { - batch_abstract_with(e, &|name| vars.get(name).copied(), scope, depth, c) -} - -fn batch_abstract_with( - e: &LeanExpr, - lookup: &impl Fn(&Name) -> Option, - scope: usize, - depth: u64, - c: &Checkpoint, -) -> Result { - if scope == 0 { - c.visit()?; - return Ok(e.clone()); - } - rewrite(e, depth, c, &mut FxHashMap::default(), &mut |e, depth| { - Ok(match e.as_data() { - ExprData::Fvar(n, _) => lookup(n) - .filter(|&pos| pos < scope) - .map(|pos| LeanExpr::bvar(Nat::from((scope - 1 - pos) as u64 + depth))), - ExprData::Bvar(i, _) if nat_to_u64(i) >= depth => { - Some(LeanExpr::bvar(Nat::from(nat_to_u64(i) + scope as u64))) - }, - _ => None, - }) - }) -} - -pub(super) fn mk_forall( - body: LeanExpr, - binders: &[LocalDecl], - c: &Checkpoint, -) -> Result { - c.visit()?; - if binders.is_empty() { - return Ok(body); - } - let vars: NameTable = - binders.iter().enumerate().map(|(i, d)| (d.fvar_name.clone(), i)).collect(); - let mut result = batch_abstract_names_at(&body, &vars, binders.len(), 0, c)?; - for (j, decl) in binders.iter().enumerate().rev() { - c.visit()?; - let domain = batch_abstract_names_at(&decl.domain, &vars, j, 0, c)?; - result = LeanExpr::all( - decl.binder_name.clone(), - domain, - result, - decl.info.clone(), - ); - } - Ok(result) -} - -pub(super) fn replace_params( - e: &LeanExpr, - from: &[LeanExpr], - to: &[LeanExpr], - c: &Checkpoint, -) -> Result { - if from.is_empty() { - c.visit()?; - return Ok(e.clone()); - } - let vars: FxHashMap<_, _> = from - .iter() - .zip(to) - .filter_map(|(a, b)| match a.as_data() { - ExprData::Fvar(n, _) => Some((n.clone(), b.clone())), - _ => None, - }) - .collect(); - transform(e, c, |e, _| { - Ok(match e.as_data() { - ExprData::Fvar(n, _) => vars.get(n).cloned(), - _ => None, - }) - }) -} - -pub(super) fn subst_levels( - e: &LeanExpr, - params: &[Name], - univs: &[Level], - c: &Checkpoint, -) -> Result { - if params.is_empty() || univs.is_empty() { - c.visit()?; - return Ok(e.clone()); - } - fn level( - l: &Level, - params: &[Name], - univs: &[Level], - c: &Checkpoint, - cache: &mut FxHashMap, - ) -> Result { - c.visit()?; - let key = *l.get_hash(); - if let Some((stored, value)) = cache.get(&key) { - // Confirm the entire input, including cached fields: returning an old - // result must preserve exactly this pure step's raw output as well. - if stored.same_raw(l) { - return Ok(value.clone()); - } - } - let value = match l.as_data() { - LevelData::Param(n, _) => params - .iter() - .position(|p| p.same_structure(n)) - .and_then(|i| univs.get(i)) - .unwrap_or(l) - .clone(), - LevelData::Succ(v, _) => Level::succ(level(v, params, univs, c, cache)?), - LevelData::Max(a, b, _) => Level::max_smart( - level(a, params, univs, c, cache)?, - level(b, params, univs, c, cache)?, - ), - LevelData::Imax(a, b, _) => Level::imax_smart( - level(a, params, univs, c, cache)?, - level(b, params, univs, c, cache)?, - ), - _ => l.clone(), - }; - cache.insert(key, (l.clone(), value.clone())); - Ok(value) - } - let mut levels = FxHashMap::default(); - transform(e, c, |e, _| { - Ok(match e.as_data() { - ExprData::Sort(l, _) => { - Some(LeanExpr::sort(level(l, params, univs, c, &mut levels)?)) - }, - ExprData::Const(n, ls, _) => Some(LeanExpr::cnst( - n.clone(), - ls.iter() - .map(|l| level(l, params, univs, c, &mut levels)) - .collect::>()?, - )), - _ => None, - }) - }) -} - -/// DAG-memoized predicate traversal, including binder depth for free-BVar tests. -pub(super) fn any( - e: &LeanExpr, - c: &Checkpoint, - mut predicate: impl FnMut(&LeanExpr, u64) -> bool, -) -> Result { - let mut seen: FxHashSet<(ExprKey, u64)> = FxHashSet::default(); - let mut stack = vec![(e, 0)]; - while let Some((e, depth)) = stack.pop() { - c.visit()?; - if !seen.insert((ExprKey(e.clone()), depth)) { - continue; - } - if predicate(e, depth) { - return Ok(true); - } - match e.as_data() { - ExprData::App(f, a, _) => { - stack.push((f, depth)); - stack.push((a, depth)); - }, - ExprData::Lam(_, t, b, _, _) | ExprData::ForallE(_, t, b, _, _) => { - stack.push((t, depth)); - stack.push((b, depth + 1)); - }, - ExprData::LetE(_, t, v, b, _, _) => { - stack.push((t, depth)); - stack.push((v, depth)); - stack.push((b, depth + 1)); - }, - ExprData::Proj(_, _, v, _) | ExprData::Mdata(_, v, _) => { - stack.push((v, depth)) - }, - _ => {}, - } - c.scratch( - seen.capacity() * size_of::<(ExprKey, u64)>() - + stack.capacity() * size_of::<(&LeanExpr, u64)>(), - ); - } - Ok(false) -} - -#[cfg(test)] -mod tests { - use super::super::expr_reference as old; - use super::*; - use ix_common::env::BinderInfo; - - fn name(s: &str) -> Name { - Name::str(Name::anon(), s.into()) - } - fn b(i: u64) -> LeanExpr { - LeanExpr::bvar(Nat::from(i)) - } - fn pi(t: LeanExpr, body: LeanExpr) -> LeanExpr { - LeanExpr::all(name("x"), t, body, BinderInfo::Default) - } - - #[test] - fn expression_cache_collision_recomputes_with_valid_neighbour() { - use std::sync::Arc; - let first = b(0); - let collision = - LeanExpr(Arc::new(ExprData::Bvar(Nat::from(1u64), *first.get_hash()))); - let value = LeanExpr::cnst(name("replacement"), vec![]); - for second in [collision, b(1)] { - let source = LeanExpr::app(first.clone(), second); - let expected = LeanExpr::app(value.clone(), b(0)); - let result = - instantiate1(&source, &value, &Checkpoint::default()).unwrap(); - assert_eq!(result, expected); - assert_eq!(result, old::instantiate1(&source, &value)); - } - } - - #[test] - fn expression_cache_preserves_raw_fields_after_semantic_match() { - use ix_common::env::NameData; - use std::sync::Arc; - let first = LeanExpr::fvar(name("x")); - let altered = Name(Arc::new(NameData::Str( - Name(Arc::new(NameData::Anonymous(blake3::hash(b"root cache")))), - "x".into(), - blake3::hash(b"name cache"), - ))); - assert!(name("x").same_structure(&altered)); - let second = LeanExpr(Arc::new(ExprData::Fvar(altered, *first.get_hash()))); - assert_ne!(first, second); - let mut cache = FxHashMap::default(); - let mut visits = 0; - let mut leaf = |_: &LeanExpr, _: u64| { - visits += 1; - Ok(None) - }; - let control = Checkpoint::default(); - assert_eq!( - rewrite(&first, 0, &control, &mut cache, &mut leaf).unwrap(), - first - ); - assert_eq!( - rewrite(&second, 0, &control, &mut cache, &mut leaf).unwrap(), - second - ); - assert_eq!( - rewrite(&second, 0, &control, &mut cache, &mut leaf).unwrap(), - second - ); - assert_eq!(visits, 2); // The exact repeated input still uses the completed hit. - } - - #[test] - fn result_reuse_requires_exact_result_with_valid_neighbour() { - use std::sync::Arc; - let value = LeanExpr::cnst(name("replacement"), vec![]); - let collision = - LeanExpr(Arc::new(ExprData::Bvar(Nat::from(0u64), *value.get_hash()))); - for source in [collision, b(0)] { - let result = - instantiate1(&source, &value, &Checkpoint::default()).unwrap(); - assert_eq!(result, value); - assert_eq!(result, old::instantiate1(&source, &value)); - } - } - - #[test] - fn binder_producers_use_last_structural_position_with_valid_neighbour() { - use ix_common::env::NameData; - use std::sync::Arc; - let a = name("a"); - let forged_b = - Name(Arc::new(NameData::Str(Name::anon(), "b".into(), *a.get_hash()))); - let altered_a = Name(Arc::new(NameData::Str( - Name(Arc::new(NameData::Anonymous(blake3::hash(b"binder root")))), - "a".into(), - blake3::hash(b"binder name"), - ))); - let sort = LeanExpr::sort(Level::zero()); - for (b_name, last_a) in [(forged_b, altered_a), (name("b"), a.clone())] { - let decls: Vec<_> = [a.clone(), b_name.clone(), last_a] - .into_iter() - .enumerate() - .map(|(i, fvar_name)| LocalDecl { - fvar_name, - binder_name: name(&format!("binder{i}")), - domain: sort.clone(), - info: BinderInfo::Default, - }) - .collect(); - let source = - LeanExpr::app(LeanExpr::fvar(a.clone()), LeanExpr::fvar(b_name)); - let expected_body = LeanExpr::app(b(0), b(1)); - let mut expected_forall = expected_body.clone(); - let mut expected_lambda = expected_body; - for decl in decls.iter().rev() { - expected_forall = LeanExpr::all( - decl.binder_name.clone(), - sort.clone(), - expected_forall, - BinderInfo::Default, - ); - expected_lambda = LeanExpr::lam( - decl.binder_name.clone(), - sort.clone(), - expected_lambda, - BinderInfo::Default, - ); - } - assert_eq!( - mk_forall(source.clone(), &decls, &Checkpoint::default()).unwrap(), - expected_forall - ); - assert_eq!(old::mk_forall(source.clone(), &decls), expected_forall); - assert_eq!(old::mk_lambda(source, &decls), expected_lambda); - } - } - - #[test] - fn level_cache_collision_recomputes_and_preserves_valid_neighbour() { - use std::sync::Arc; - let u = name("u"); - let v = name("v"); - let first = Level::param(u.clone()); - let collision = - Level(Arc::new(LevelData::Param(v.clone(), *first.get_hash()))); - let arguments = [Level::zero(), Level::succ(Level::zero())]; - let expected = LeanExpr::cnst(name("C"), arguments.to_vec()); - for last in [collision, Level::param(v.clone())] { - let source = LeanExpr::cnst(name("C"), vec![first.clone(), last]); - let result = subst_levels( - &source, - &[u.clone(), v.clone()], - &arguments, - &Checkpoint::default(), - ) - .unwrap(); - assert_eq!(result, expected); - assert_eq!( - result, - old::subst_levels(&source, &[u.clone(), v.clone()], &arguments) - ); - } - } - - #[test] - fn same_semantic_input_with_changed_caches_keeps_each_raw_result() { - use ix_common::env::NameData; - use std::sync::Arc; - let v = name("v"); - let first = Level::param(v); - let digest = blake3::hash(b"changed descendant cache"); - let rehashed = Name(Arc::new(NameData::Str( - Name(Arc::new(NameData::Anonymous(digest))), - "v".into(), - digest, - ))); - let other = Level(Arc::new(LevelData::Param(rehashed, *first.get_hash()))); - assert!(first.same_structure(&other)); - assert_ne!(first, other); - let source = LeanExpr::cnst(name("C"), vec![first, other]); - let result = subst_levels( - &source, - &[name("u")], - &[Level::zero()], - &Checkpoint::default(), - ) - .unwrap(); - // Neither parameter matches: each original leaf, including its caches, - // is the exact result of the pure scalar step. - assert_eq!(result, source); - } - - #[test] - fn structural_parameter_match_keeps_first_match_and_short_array_fallback() { - use ix_common::env::NameData; - use std::sync::Arc; - let u = name("u"); - let rehashed = Name(Arc::new(NameData::Str( - Name::anon(), - "u".into(), - blake3::hash(b"parameter cache"), - ))); - let source = - LeanExpr::cnst(name("C"), vec![Level::param(rehashed.clone())]); - let arguments = [Level::zero(), Level::succ(Level::zero())]; - for parameters in [vec![u.clone()], vec![u.clone(), rehashed]] { - let result = - subst_levels(&source, ¶meters, &arguments, &Checkpoint::default()) - .unwrap(); - assert_eq!(result, LeanExpr::cnst(name("C"), vec![arguments[0].clone()])); - } - let unmatched = LeanExpr::cnst(name("C"), vec![Level::param(name("v"))]); - let parameters = [u, name("v")]; - assert_eq!( - subst_levels( - &unmatched, - ¶meters, - &arguments[..1], - &Checkpoint::default() - ) - .unwrap(), - unmatched - ); - } - - #[test] - fn matches_reference_transforms_with_shared_terms_at_different_depths() { - let c = Checkpoint::default(); - let u = name("u"); - let f = LeanExpr::fvar(name("f")); - let leaf = LeanExpr::app(b(1), f.clone()); - let terms = vec![ - b(0), - b(3), - f.clone(), - LeanExpr::sort(Level::param(u.clone())), - LeanExpr::cnst( - name("C"), - vec![Level::imax(Level::param(u.clone()), Level::zero())], - ), - pi(leaf.clone(), pi(leaf.clone(), LeanExpr::app(leaf.clone(), b(2)))), - LeanExpr::lam( - name("x"), - leaf.clone(), - leaf.clone(), - BinderInfo::Implicit, - ), - LeanExpr::letE(name("v"), leaf.clone(), b(0), leaf.clone(), false), - LeanExpr::proj(name("P"), Nat::from(0u64), leaf), - ]; - let vars = [(name("f"), 0), (name("g"), 1)].into_iter().collect(); - for e in &terms { - assert_eq!(instantiate1(e, &f, &c).unwrap(), old::instantiate1(e, &f)); - assert_eq!( - instantiate_rev(e, &[b(2), f.clone()], &c).unwrap(), - old::instantiate_rev(e, &[b(2), f.clone()]) - ); - for scope in 0..=2 { - assert_eq!( - batch_abstract(e, &vars, scope, &c).unwrap(), - old::batch_abstract(e, &vars, scope, 0) - ); - } - assert_eq!( - subst_levels(e, std::slice::from_ref(&u), &[Level::zero()], &c) - .unwrap(), - old::subst_levels(e, std::slice::from_ref(&u), &[Level::zero()]) - ); - let ty = pi(e.clone(), pi(e.clone(), b(1))); - assert_eq!( - instantiate_pi_params(&ty, 1, &[b(2)], &c).unwrap(), - old::instantiate_pi_params(&ty, 1, &[b(2)]) - ); - let (fvars, decls, body) = - forall_telescope(&ty, 2, "test", 3, &c).unwrap(); - let (ref_fvars, ref_decls, ref_body) = - old::forall_telescope(&ty, 2, "test", 3); - assert_eq!(fvars, ref_fvars); - assert_eq!(body, ref_body); - assert_eq!( - mk_forall(body, &decls, &c).unwrap(), - old::mk_forall(ref_body, &ref_decls) - ); - } - } - - #[test] - fn diamond_dag_is_visited_linearly() { - let mut e = b(0); - for _ in 0..40 { - e = LeanExpr::app(e.clone(), e); - } - let mut visits = 0; - let result = transform(&e, &Checkpoint::default(), |_, _| { - visits += 1; - Ok(None) - }) - .unwrap(); - assert_eq!(result, e); - assert_eq!(visits, 41); // Not 2^40 visits/copies. - } - - #[test] - fn cancellation_unwinds_an_in_progress_walk_without_panicking() { - let mut e = b(0); - for _ in 0..40 { - e = LeanExpr::app(e.clone(), e); - } - let mut visits = 0; - let result = transform(&e, &Checkpoint::default(), |_, _| { - visits += 1; - if visits == 20 { Err(Cancelled) } else { Ok(None) } - }); - assert!(matches!(result, Err(Cancelled))); - assert_eq!(visits, 20); - } - - #[test] - fn predicate_cache_distinguishes_binder_depth() { - // BVar 0 is bound in the body (visited first), but free in the domain. - let e = pi(b(0), b(0)); - assert!(any(&e, &Checkpoint::default(), |e, depth| - matches!(e.as_data(), ExprData::Bvar(i, _) if nat_to_u64(i) >= depth)).unwrap()); - } - - #[test] - fn helpers_match_frozen_reference_on_mixed_shared_terms() { - let c = Checkpoint::default(); - let f = name("f"); - let g = name("g"); - let u = name("u"); - let fv = LeanExpr::fvar(f.clone()); - let mut terms = vec![ - b(0), - b(1), - b(3), - fv.clone(), - LeanExpr::fvar(g.clone()), - LeanExpr::sort(Level::param(u.clone())), - LeanExpr::cnst( - name("C"), - vec![Level::max(Level::param(u.clone()), Level::zero())], - ), - ]; - for round in 0..5 { - let previous = terms.clone(); - for (i, e) in previous.iter().take(12).enumerate() { - let other = &previous[(i * 7 + round) % previous.len()]; - terms.push(match (i + round) % 6 { - 0 => LeanExpr::app(e.clone(), e.clone()), - 1 => pi(e.clone(), other.clone()), - 2 => LeanExpr::lam( - name("x"), - e.clone(), - other.clone(), - BinderInfo::Implicit, - ), - 3 => { - LeanExpr::letE(name("x"), e.clone(), other.clone(), e.clone(), true) - }, - 4 => LeanExpr::proj(name("P"), Nat::from(0u64), e.clone()), - _ => LeanExpr::mdata(vec![], e.clone()), - }); - } - } - let vars: FxHashMap = - [(f.clone(), 0), (g.clone(), 1)].into_iter().collect(); - for e in &terms { - for depth in 0..3 { - assert_eq!( - instantiate1_at(e, &fv, depth, &c).unwrap(), - old::instantiate1_at(e, &fv, depth) - ); - assert_eq!( - batch_abstract_with(e, &|n| vars.get(n).copied(), 2, depth, &c) - .unwrap(), - old::batch_abstract(e, &vars, 2, depth) - ); - } - assert_eq!( - instantiate_rev(e, &[b(1), fv.clone()], &c).unwrap(), - old::instantiate_rev(e, &[b(1), fv.clone()]) - ); - assert_eq!( - subst_levels(e, std::slice::from_ref(&u), &[Level::zero()], &c) - .unwrap(), - old::subst_levels(e, std::slice::from_ref(&u), &[Level::zero()]) - ); - } - } - - #[test] - fn transforms_preserve_large_diamond_sharing() { - fn diamond(mut e: LeanExpr) -> LeanExpr { - for _ in 0..40 { - e = LeanExpr::app(e.clone(), e); - } - e - } - fn unique_nodes(e: &LeanExpr) -> usize { - let mut count = 0; - any(e, &Checkpoint::default(), |_, _| { - count += 1; - false - }) - .unwrap(); - count - } - let c = Checkpoint::default(); - let fv = LeanExpr::fvar(name("f")); - let e = diamond(b(0)); - let instantiated = instantiate1(&e, &fv, &c).unwrap(); - assert_eq!(instantiated.get_hash(), diamond(fv.clone()).get_hash()); - assert_eq!(unique_nodes(&instantiated), 41); - let vars = [(name("f"), 0)].into_iter().collect(); - let abstracted = batch_abstract(&instantiated, &vars, 1, &c).unwrap(); - assert_eq!(abstracted.get_hash(), e.get_hash()); - assert_eq!(unique_nodes(&abstracted), 41); - let replaced = - replace_params(&instantiated, std::slice::from_ref(&fv), &[b(1)], &c) - .unwrap(); - assert_eq!(replaced.get_hash(), diamond(b(1)).get_hash()); - assert_eq!(unique_nodes(&replaced), 41); - let universe = diamond(LeanExpr::sort(Level::param(name("u")))); - let substituted = - subst_levels(&universe, &[name("u")], &[Level::zero()], &c).unwrap(); - assert_eq!( - substituted.get_hash(), - diamond(LeanExpr::sort(Level::zero())).get_hash() - ); - assert_eq!(unique_nodes(&substituted), 41); - } - - /// `∀ (a : α) (b : β) (c : γ), body` - fn mk_triple_forall( - a: LeanExpr, - b: LeanExpr, - c: LeanExpr, - body: LeanExpr, - ) -> LeanExpr { - pi(a, pi(b, pi(c, body))) - } - - fn sort0() -> LeanExpr { - LeanExpr::sort(Level::zero()) - } - - fn telescope( - e: &LeanExpr, - n: usize, - start: usize, - ) -> (Vec, Vec, LeanExpr) { - forall_telescope(e, n, "p", start, &Checkpoint::default()).unwrap() - } - - #[test] - fn forall_telescope_opens_exactly_n_binders() { - let e = mk_triple_forall(sort0(), sort0(), sort0(), b(0)); - let (fvars, decls, body) = telescope(&e, 3, 0); - assert_eq!(fvars.len(), 3); - assert_eq!(decls.len(), 3); - // After instantiating all three foralls, body BVar(0) became the - // innermost FVar. - match body.as_data() { - ExprData::Fvar(n, _) => assert_eq!(n, &decls[2].fvar_name), - other => panic!("expected innermost FVar in body, got {other:?}"), - } - } - - #[test] - fn forall_telescope_partial_with_too_small_n() { - let e = mk_triple_forall(sort0(), sort0(), sort0(), b(0)); - let (fvars, decls, body) = telescope(&e, 2, 0); - assert_eq!(fvars.len(), 2); - assert_eq!(decls.len(), 2); - // Body is still a forall because we didn't peel the innermost. - assert!(matches!(body.as_data(), ExprData::ForallE(..))); - } - - #[test] - fn forall_telescope_requests_more_than_available_stops_early() { - // Body is not a forall; telescope caps at 1. - let e = pi(sort0(), b(0)); - let (fvars, decls, _body) = telescope(&e, 5, 0); - assert_eq!(fvars.len(), 1); - assert_eq!(decls.len(), 1); - } - - #[test] - fn forall_telescope_peels_mdata() { - // ∀ (x : α), Mdata(_, ∀ (y : β), body) - let outer = pi(sort0(), LeanExpr::mdata(vec![], pi(sort0(), b(0)))); - let (_, decls, _) = telescope(&outer, 2, 0); - assert_eq!(decls.len(), 2, "mdata should be transparent"); - } - - #[test] - fn forall_telescope_uses_start_idx_offset() { - let e = mk_triple_forall(sort0(), sort0(), sort0(), b(0)); - let (_, decls1, _) = telescope(&e, 1, 0); - let (_, decls2, _) = telescope(&e, 1, 10); - assert_ne!(decls1[0].fvar_name, decls2[0].fvar_name); - } - - #[test] - fn instantiate1_substitutes_bvar_0() { - let e = instantiate1(&b(0), &sort0(), &Checkpoint::default()).unwrap(); - assert_eq!(e.get_hash(), sort0().get_hash()); - } - - #[test] - fn instantiate1_shifts_bvar_above_depth_down() { - // BVar(3) -> BVar(2) (shifted down). - let e = instantiate1(&b(3), &sort0(), &Checkpoint::default()).unwrap(); - match e.as_data() { - ExprData::Bvar(n, _) => assert_eq!(nat_to_u64(n), 2), - other => panic!("expected Bvar, got {other:?}"), - } - } - - #[test] - fn instantiate1_no_bvar_unchanged() { - let e = sort0(); - let r = instantiate1(&e, &b(5), &Checkpoint::default()).unwrap(); - assert_eq!(r.get_hash(), e.get_hash()); - } - - #[test] - fn instantiate1_at_non_zero_depth() { - let r = - instantiate1_at(&b(2), &sort0(), 2, &Checkpoint::default()).unwrap(); - assert_eq!(r.get_hash(), sort0().get_hash()); - } - - #[test] - fn instantiate_rev_empty_args_is_identity() { - let e = b(5); - let r = instantiate_rev(&e, &[], &Checkpoint::default()).unwrap(); - assert_eq!(r.get_hash(), e.get_hash()); - } - - #[test] - fn instantiate_rev_substitutes_multiple() { - // body = App(BVar(0), BVar(1)); args = [a, b]: BVar(0) → a, BVar(1) → b - let a = LeanExpr::cnst(name("a"), vec![]); - let c = LeanExpr::cnst(name("b"), vec![]); - let body = LeanExpr::app(b(0), b(1)); - let r = - instantiate_rev(&body, &[a.clone(), c.clone()], &Checkpoint::default()) - .unwrap(); - let (f, args) = super::super::decompose_apps(&r); - assert_eq!(f.get_hash(), a.get_hash()); - assert_eq!(args.len(), 1); - assert_eq!(args[0].get_hash(), c.get_hash()); - } - - #[test] - fn mk_forall_roundtrips_with_forall_telescope() { - // Open a forall telescope, then reclose with mk_forall: the original, - // binder names included (preserved via LocalDecl). - let orig = mk_triple_forall(sort0(), sort0(), sort0(), b(0)); - let (_, decls, body) = telescope(&orig, 3, 0); - let rebuilt = mk_forall(body, &decls, &Checkpoint::default()).unwrap(); - assert_eq!(rebuilt.get_hash(), orig.get_hash()); - } - - #[test] - fn mk_forall_empty_binders_returns_body_unchanged() { - let body = sort0(); - let r = mk_forall(body.clone(), &[], &Checkpoint::default()).unwrap(); - assert_eq!(r.get_hash(), body.get_hash()); - } -} diff --git a/crates/compile/src/compile/ind_flags/expr_reference.rs b/crates/compile/src/compile/ind_flags/expr_reference.rs deleted file mode 100644 index 0322a9bc0..000000000 --- a/crates/compile/src/compile/ind_flags/expr_reference.rs +++ /dev/null @@ -1,466 +0,0 @@ -//! Independent tree-walk implementations for differential tests only. -//! Raw-map compatibility is retained; binder producers use the shared -//! structural table and preserve forward insertion / reverse binder order. -#![allow(dead_code)] - -use super::{LocalDecl, fresh_fvar}; -use crate::compile::nat_conv::{nat_to_u64, nat_to_usize}; -use bignat::Nat; -use ix_common::env::{Expr as LeanExpr, ExprData, Level, LevelData, Name}; -use ix_common::name_table::NameTable; -use rustc_hash::FxHashMap; - -#[derive(Clone, Copy)] -enum BinderKind { - Forall, - Lambda, -} - -pub(super) fn forall_telescope( - expr: &LeanExpr, - n: usize, - prefix: &str, - start_idx: usize, -) -> (Vec, Vec, LeanExpr) { - let mut fvars = Vec::with_capacity(n); - let mut decls = Vec::with_capacity(n); - let mut cur = expr.clone(); - for i in 0..n { - // Peel any Mdata wrappers before matching — they're structural no-ops. - while let ExprData::Mdata(_, inner, _) = cur.as_data() { - cur = inner.clone(); - } - match cur.as_data() { - ExprData::ForallE(name, dom, body, bi, _) => { - let (fv_name, fv) = fresh_fvar(prefix, start_idx + i); - decls.push(LocalDecl { - fvar_name: fv_name, - binder_name: name.clone(), - domain: dom.clone(), - info: bi.clone(), - }); - fvars.push(fv.clone()); - cur = instantiate1(body, &fv); - }, - _ => break, - } - } - (fvars, decls, cur) -} - -pub(super) fn mk_forall(body: LeanExpr, binders: &[LocalDecl]) -> LeanExpr { - mk_binder_chain(body, binders, BinderKind::Forall) -} - -pub(super) fn mk_lambda(body: LeanExpr, binders: &[LocalDecl]) -> LeanExpr { - mk_binder_chain(body, binders, BinderKind::Lambda) -} - -fn mk_binder_chain( - body: LeanExpr, - binders: &[LocalDecl], - kind: BinderKind, -) -> LeanExpr { - let k = binders.len(); - if k == 0 { - return body; - } - - // Build FVar name → binder position map (0 = outermost). - let fvar_map: NameTable = - binders.iter().enumerate().map(|(i, d)| (d.fvar_name.clone(), i)).collect(); - - // Abstract body: all k binders in scope. - let mut result = batch_abstract_names(&body, &fvar_map, k, 0); - - // Build binder chain from innermost to outermost. - for j in (0..k).rev() { - let decl = &binders[j]; - // Domain D_j: only binders 0..j-1 are in scope (scope_depth = j). - // Binder j's domain is NOT under binder j itself — only the body is. - let domain = batch_abstract_names(&decl.domain, &fvar_map, j, 0); - result = match kind { - BinderKind::Forall => LeanExpr::all( - decl.binder_name.clone(), - domain, - result, - decl.info.clone(), - ), - BinderKind::Lambda => LeanExpr::lam( - decl.binder_name.clone(), - domain, - result, - decl.info.clone(), - ), - }; - } - result -} - -pub(super) fn batch_abstract( - expr: &LeanExpr, - fvar_map: &FxHashMap, - scope_depth: usize, - internal_depth: u64, -) -> LeanExpr { - batch_abstract_with( - expr, - &|name| fvar_map.get(name).copied(), - scope_depth, - internal_depth, - ) -} - -fn batch_abstract_names( - expr: &LeanExpr, - fvar_map: &NameTable, - scope_depth: usize, - internal_depth: u64, -) -> LeanExpr { - batch_abstract_with( - expr, - &|name| fvar_map.get(name).copied(), - scope_depth, - internal_depth, - ) -} - -fn batch_abstract_with( - expr: &LeanExpr, - lookup: &impl Fn(&Name) -> Option, - scope_depth: usize, - internal_depth: u64, -) -> LeanExpr { - // Fast path: no binders to abstract. - if scope_depth == 0 { - return expr.clone(); - } - match expr.as_data() { - ExprData::Fvar(name, _) => { - if let Some(pos) = lookup(name) { - if pos < scope_depth { - let idx = (scope_depth - 1 - pos) as u64 + internal_depth; - LeanExpr::bvar(Nat::from(idx)) - } else { - // FVar not yet in scope (e.g., a forward reference in a domain - // to a binder declared later). Leave as-is. - expr.clone() - } - } else { - // FVar not in our telescope — leave as-is. - expr.clone() - } - }, - ExprData::Bvar(idx, _) => { - let i = nat_to_u64(idx); - if i >= internal_depth { - // Free BVar: shift up by scope_depth to make room for our binders. - LeanExpr::bvar(Nat::from(i + scope_depth as u64)) - } else { - // Bound by an expression-internal binder — unchanged. - expr.clone() - } - }, - ExprData::App(f, a, _) => LeanExpr::app( - batch_abstract_with(f, lookup, scope_depth, internal_depth), - batch_abstract_with(a, lookup, scope_depth, internal_depth), - ), - ExprData::Lam(n, t, b, bi, _) => LeanExpr::lam( - n.clone(), - batch_abstract_with(t, lookup, scope_depth, internal_depth), - batch_abstract_with(b, lookup, scope_depth, internal_depth + 1), - bi.clone(), - ), - ExprData::ForallE(n, t, b, bi, _) => LeanExpr::all( - n.clone(), - batch_abstract_with(t, lookup, scope_depth, internal_depth), - batch_abstract_with(b, lookup, scope_depth, internal_depth + 1), - bi.clone(), - ), - ExprData::LetE(n, t, v, b, nd, _) => LeanExpr::letE( - n.clone(), - batch_abstract_with(t, lookup, scope_depth, internal_depth), - batch_abstract_with(v, lookup, scope_depth, internal_depth), - batch_abstract_with(b, lookup, scope_depth, internal_depth + 1), - *nd, - ), - ExprData::Proj(n, i, e, _) => LeanExpr::proj( - n.clone(), - i.clone(), - batch_abstract_with(e, lookup, scope_depth, internal_depth), - ), - ExprData::Mdata(kvs, e, _) => LeanExpr::mdata( - kvs.clone(), - batch_abstract_with(e, lookup, scope_depth, internal_depth), - ), - // Sort, Const, MVar, Lit — no FVars or BVars to process. - _ => expr.clone(), - } -} - -pub(super) fn instantiate1( - body: &LeanExpr, - replacement: &LeanExpr, -) -> LeanExpr { - instantiate1_at(body, replacement, 0) -} - -pub(super) fn instantiate1_at( - body: &LeanExpr, - replacement: &LeanExpr, - depth: u64, -) -> LeanExpr { - match body.as_data() { - ExprData::Bvar(idx, _) => { - let i = nat_to_u64(idx); - if i == depth { - replacement.clone() - } else if i > depth { - LeanExpr::bvar(Nat::from(i - 1)) - } else { - body.clone() - } - }, - ExprData::App(f, a, _) => LeanExpr::app( - instantiate1_at(f, replacement, depth), - instantiate1_at(a, replacement, depth), - ), - ExprData::Lam(n, t, b, bi, _) => LeanExpr::lam( - n.clone(), - instantiate1_at(t, replacement, depth), - instantiate1_at(b, replacement, depth + 1), - bi.clone(), - ), - ExprData::ForallE(n, t, b, bi, _) => LeanExpr::all( - n.clone(), - instantiate1_at(t, replacement, depth), - instantiate1_at(b, replacement, depth + 1), - bi.clone(), - ), - ExprData::LetE(n, t, v, b, nd, _) => LeanExpr::letE( - n.clone(), - instantiate1_at(t, replacement, depth), - instantiate1_at(v, replacement, depth), - instantiate1_at(b, replacement, depth + 1), - *nd, - ), - ExprData::Proj(n, i, e, _) => LeanExpr::proj( - n.clone(), - i.clone(), - instantiate1_at(e, replacement, depth), - ), - ExprData::Mdata(kvs, e, _) => { - LeanExpr::mdata(kvs.clone(), instantiate1_at(e, replacement, depth)) - }, - _ => body.clone(), - } -} - -pub(super) fn instantiate_rev(body: &LeanExpr, args: &[LeanExpr]) -> LeanExpr { - if args.is_empty() { - return body.clone(); - } - instantiate_rev_at(body, args, 0) -} - -fn instantiate_rev_at( - body: &LeanExpr, - args: &[LeanExpr], - depth: u64, -) -> LeanExpr { - let n = args.len() as u64; - match body.as_data() { - ExprData::Bvar(idx, _) => { - let i = nat_to_u64(idx); - if i >= depth { - let ridx = i - depth; - if ridx < n { - // Replace with args[ridx], shifted up by depth for the binders we're under. - shift_vars(&args[ridx as usize], depth as usize, 0) - } else { - // Free BVar past our substitution range: decrement by n. - LeanExpr::bvar(Nat::from(i - n)) - } - } else { - // Bound by an expression-internal binder — unchanged. - body.clone() - } - }, - ExprData::App(f, a, _) => LeanExpr::app( - instantiate_rev_at(f, args, depth), - instantiate_rev_at(a, args, depth), - ), - ExprData::Lam(name, t, b, bi, _) => LeanExpr::lam( - name.clone(), - instantiate_rev_at(t, args, depth), - instantiate_rev_at(b, args, depth + 1), - bi.clone(), - ), - ExprData::ForallE(name, t, b, bi, _) => LeanExpr::all( - name.clone(), - instantiate_rev_at(t, args, depth), - instantiate_rev_at(b, args, depth + 1), - bi.clone(), - ), - ExprData::LetE(name, t, v, b, nd, _) => LeanExpr::letE( - name.clone(), - instantiate_rev_at(t, args, depth), - instantiate_rev_at(v, args, depth), - instantiate_rev_at(b, args, depth + 1), - *nd, - ), - ExprData::Proj(name, i, e, _) => LeanExpr::proj( - name.clone(), - i.clone(), - instantiate_rev_at(e, args, depth), - ), - ExprData::Mdata(kvs, e, _) => { - LeanExpr::mdata(kvs.clone(), instantiate_rev_at(e, args, depth)) - }, - // Sort, Const, Lit, FVar, MVar — no BVars to substitute. - _ => body.clone(), - } -} - -pub(crate) fn instantiate_pi_params( - typ: &LeanExpr, - n: usize, - args: &[LeanExpr], -) -> LeanExpr { - debug_assert!( - args.len() >= n, - "instantiate_pi_params: args.len()={} < n={}", - args.len(), - n - ); - let mut cur = typ.clone(); - for arg in args.iter().take(n) { - match cur.as_data() { - ExprData::ForallE(_, _, body, _, _) => { - cur = instantiate_rev(body, std::slice::from_ref(arg)); - }, - _ => break, - } - } - cur -} - -fn shift_vars(expr: &LeanExpr, amount: usize, cutoff: usize) -> LeanExpr { - if amount == 0 { - return expr.clone(); - } - match expr.as_data() { - ExprData::Bvar(idx, _) => { - let i = nat_to_usize(idx); - if i >= cutoff { - LeanExpr::bvar(Nat::from((i + amount) as u64)) - } else { - expr.clone() - } - }, - ExprData::App(f, a, _) => LeanExpr::app( - shift_vars(f, amount, cutoff), - shift_vars(a, amount, cutoff), - ), - ExprData::Lam(n, t, b, bi, _) => LeanExpr::lam( - n.clone(), - shift_vars(t, amount, cutoff), - shift_vars(b, amount, cutoff + 1), - bi.clone(), - ), - ExprData::ForallE(n, t, b, bi, _) => LeanExpr::all( - n.clone(), - shift_vars(t, amount, cutoff), - shift_vars(b, amount, cutoff + 1), - bi.clone(), - ), - ExprData::LetE(n, t, v, b, nd, _) => LeanExpr::letE( - n.clone(), - shift_vars(t, amount, cutoff), - shift_vars(v, amount, cutoff), - shift_vars(b, amount, cutoff + 1), - *nd, - ), - ExprData::Proj(n, i, e, _) => { - LeanExpr::proj(n.clone(), i.clone(), shift_vars(e, amount, cutoff)) - }, - ExprData::Mdata(kvs, e, _) => { - LeanExpr::mdata(kvs.clone(), shift_vars(e, amount, cutoff)) - }, - _ => expr.clone(), - } -} - -pub(super) fn subst_levels( - expr: &LeanExpr, - params: &[Name], - univs: &[Level], -) -> LeanExpr { - if params.is_empty() || univs.is_empty() { - return expr.clone(); - } - match expr.as_data() { - ExprData::Sort(lvl, _) => LeanExpr::sort(subst_level(lvl, params, univs)), - ExprData::Const(name, us, _) => LeanExpr::cnst( - name.clone(), - us.iter().map(|u| subst_level(u, params, univs)).collect(), - ), - ExprData::App(f, a, _) => LeanExpr::app( - subst_levels(f, params, univs), - subst_levels(a, params, univs), - ), - ExprData::Lam(n, t, b, bi, _) => LeanExpr::lam( - n.clone(), - subst_levels(t, params, univs), - subst_levels(b, params, univs), - bi.clone(), - ), - ExprData::ForallE(n, t, b, bi, _) => LeanExpr::all( - n.clone(), - subst_levels(t, params, univs), - subst_levels(b, params, univs), - bi.clone(), - ), - ExprData::LetE(n, t, v, b, nd, _) => LeanExpr::letE( - n.clone(), - subst_levels(t, params, univs), - subst_levels(v, params, univs), - subst_levels(b, params, univs), - *nd, - ), - ExprData::Proj(n, i, e, _) => { - LeanExpr::proj(n.clone(), i.clone(), subst_levels(e, params, univs)) - }, - ExprData::Mdata(md, e, _) => { - LeanExpr::mdata(md.clone(), subst_levels(e, params, univs)) - }, - _ => expr.clone(), - } -} - -pub(super) fn subst_level( - lvl: &Level, - params: &[Name], - univs: &[Level], -) -> Level { - match lvl.as_data() { - LevelData::Zero(_) | LevelData::Mvar(_, _) => lvl.clone(), - LevelData::Succ(l, _) => Level::succ(subst_level(l, params, univs)), - LevelData::Max(a, b, _) => Level::max_smart( - subst_level(a, params, univs), - subst_level(b, params, univs), - ), - LevelData::Imax(a, b, _) => Level::imax_smart( - subst_level(a, params, univs), - subst_level(b, params, univs), - ), - LevelData::Param(name, _) => { - for (i, p) in params.iter().enumerate() { - if p.same_structure(name) && i < univs.len() { - return univs[i].clone(); - } - } - lvl.clone() - }, - } -} diff --git a/crates/compile/src/compile/memory.rs b/crates/compile/src/compile/memory.rs deleted file mode 100644 index 560962989..000000000 --- a/crates/compile/src/compile/memory.rs +++ /dev/null @@ -1,246 +0,0 @@ -//! Linux memory telemetry and pressure policy shared by both compilation stages. - -use ixon::CompileError; -use std::path::{Path, PathBuf}; -use std::time::Duration; - -pub(super) const MIB: u64 = 1024 * 1024; -pub(super) const GIB: u64 = 1024 * MIB; - -pub(super) fn budget_from_env(key: &str) -> Result, CompileError> { - std::env::var(key).ok().map(|value| parse_budget(key, &value)).transpose() -} - -fn parse_budget(key: &str, value: &str) -> Result { - value - .parse::() - .ok() - .filter(|v| v.is_finite() && *v > 0.0 && *v < (u64::MAX / GIB) as f64) - .map(|v| { - // The filter proves this is finite and positive. - #[allow(clippy::cast_sign_loss)] - let bytes = (v * GIB as f64) as u64; - bytes - }) - .filter(|&bytes| bytes > 0) - .ok_or_else(|| { - resource_error(format!("{key} must be a positive finite number")) - }) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn memory_budget_rejects_invalid_and_unrepresentable_values() { - for value in ["", "0", "-1", "nan", "inf", "1e50", "1e-100", "no"] { - assert!(matches!( - parse_budget("TEST", value), - Err(CompileError::ResourceLimit { .. }) - )); - } - assert_eq!(parse_budget("TEST", "0.5").unwrap(), GIB / 2); - assert_eq!(parse_budget("TEST", "100").unwrap(), 100 * GIB); - } - - #[test] - fn ramp_up_doubles_to_the_ceiling() { - assert_eq!(ramp_up(1, 64), 2); - assert_eq!(ramp_up(2, 64), 4); - assert_eq!(ramp_up(32, 64), 64); - assert_eq!(ramp_up(40, 64), 64); - assert_eq!(ramp_up(64, 64), 64); - assert_eq!(ramp_up(3, 4), 4); - assert_eq!(ramp_up(1, 1), 1); - assert_eq!(ramp_up(usize::MAX, usize::MAX), usize::MAX); - } -} - -/// The admission limit after one healthy ramp interval: twice the current -/// limit, at most `max`. Doubling reaches a large worker pool in a few -/// intervals; each step still waits for a healthy sample at the previous -/// limit, and pressure still halves the limit or drops it to one. -pub(super) fn ramp_up(limit: usize, max: usize) -> usize { - limit.max(1).saturating_mul(2).min(max.max(1)) -} - -#[derive(Clone, Copy, Debug)] -pub(super) struct Memory { - pub(super) capacity: u64, - pub(super) available: u64, - pub(super) process: u64, - pub(super) swap_used: u64, - pub(super) stall_us: u64, -} - -impl Memory { - pub(super) fn headroom(self, budget: Option) -> u64 { - budget.map_or(self.available, |b| { - self.available.min(b.saturating_sub(self.process)) - }) - } - - pub(super) fn reserve(self, budget: Option) -> u64 { - let capacity = budget.map_or(self.capacity, |b| b.min(self.capacity)); - (capacity / 8).max(64 * MIB).min(capacity / 2) - } -} - -pub(super) fn field(text: &str, key: &str) -> Option { - text.lines().find_map(|line| { - line.strip_prefix(key)?.split_whitespace().next()?.parse().ok() - }) -} -pub(super) fn psi_total(text: &str) -> u64 { - text - .lines() - .find(|line| line.starts_with("full ")) - .and_then(|line| { - line - .split_whitespace() - .find_map(|v| v.strip_prefix("total=")?.parse().ok()) - }) - .unwrap_or(0) -} -fn proc_path(text: &str) -> PathBuf { - PathBuf::from( - text - .replace("\\040", " ") - .replace("\\011", "\t") - .replace("\\012", "\n") - .replace("\\134", "\\"), - ) -} - -/// Resolve the unified cgroup relative to its visible mount, including -/// namespace roots and every visible ancestor's memory.max/memory.high. -pub(super) fn cgroup_dirs(cgroups: &str, mounts: &str) -> Vec { - let Some(group) = cgroups.lines().find_map(|line| line.strip_prefix("0::")) - else { - return Vec::new(); - }; - let group = proc_path(group); - if group.components().any(|c| matches!(c, std::path::Component::ParentDir)) { - return Vec::new(); - } - for line in mounts.lines() { - let Some((left, right)) = line.split_once(" - ") else { - continue; - }; - if right.split_whitespace().next() != Some("cgroup2") { - continue; - } - let fields: Vec<_> = left.split_whitespace().collect(); - if fields.len() < 5 { - continue; - } - let root = proc_path(fields[3]); - let mount = proc_path(fields[4]); - let relative = if group == Path::new("/") { - Path::new("") - } else if let Ok(relative) = group.strip_prefix(&root) { - relative - } else { - continue; - }; - let leaf = mount.join(relative); - return leaf - .ancestors() - .take_while(|path| path.starts_with(&mount)) - .map(Path::to_path_buf) - .collect(); - } - Vec::new() -} - -pub(super) struct MemoryReader { - cgroups: Vec, -} -impl MemoryReader { - pub(super) fn new() -> Self { - Self { - cgroups: cgroup_dirs( - &std::fs::read_to_string("/proc/self/cgroup").unwrap_or_default(), - &std::fs::read_to_string("/proc/self/mountinfo").unwrap_or_default(), - ), - } - } - pub(super) fn read(&mut self) -> Option { - let mem = std::fs::read_to_string("/proc/meminfo").ok()?; - let status = std::fs::read_to_string("/proc/self/status").ok()?; - let mut capacity = field(&mem, "MemTotal:")?.saturating_mul(1024); - let mut available = field(&mem, "MemAvailable:")?.saturating_mul(1024); - for dir in &self.cgroups { - let current = std::fs::read_to_string(dir.join("memory.current")) - .ok() - .and_then(|v| v.trim().parse::().ok()); - for file in ["memory.max", "memory.high"] { - if let (Some(current), Some(limit)) = ( - current, - std::fs::read_to_string(dir.join(file)) - .ok() - .and_then(|v| v.trim().parse::().ok()), - ) { - capacity = capacity.min(limit); - available = available.min(limit.saturating_sub(current)); - } - } - } - Some(Memory { - capacity, - available, - process: field(&status, "VmRSS:")? - .saturating_add(field(&status, "VmSwap:").unwrap_or(0)) - .saturating_mul(1024), - swap_used: field(&mem, "SwapTotal:")? - .saturating_sub(field(&mem, "SwapFree:")?) - .saturating_mul(1024), - stall_us: psi_total( - &std::fs::read_to_string("/proc/pressure/memory").unwrap_or_default(), - ), - }) - } -} - -pub(super) fn resource_error(reason: impl Into) -> CompileError { - CompileError::ResourceLimit { reason: reason.into() } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(super) enum Pressure { - Healthy, - Hold, - Backoff, - Critical, -} - -pub(super) fn reclaiming( - now: Memory, - before: Memory, - elapsed: Duration, -) -> bool { - let stalls = now.stall_us.saturating_sub(before.stall_us); - stalls > (elapsed.as_micros() as u64 / 10).max(1) - || now.swap_used.saturating_sub(before.swap_used) > 8 * MIB -} - -pub(super) fn pressure( - now: Memory, - before: Memory, - elapsed: Duration, - budget: Option, -) -> Pressure { - let reserve = now.reserve(budget); - let available = now.headroom(budget); - if available < reserve / 2 { - return Pressure::Critical; - } - if available < reserve * 2 || reclaiming(now, before, elapsed) { - Pressure::Backoff - } else if available < reserve * 3 { - Pressure::Hold - } else { - Pressure::Healthy - } -} diff --git a/crates/compile/src/compile/nat_conv.rs b/crates/compile/src/compile/nat_conv.rs deleted file mode 100644 index a96ec090b..000000000 --- a/crates/compile/src/compile/nat_conv.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Utilities for converting Lean `Nat` fields to Rust integer types. -//! -//! Lean's `Nat` is arbitrary-precision, but structural metadata fields -//! (`num_params`, `num_indices`, `num_motives`, `num_minors`, `num_fields`, -//! `num_nested`) are always small values. These utilities make the conversion -//! explicit rather than silently producing 0 on overflow. - -use bignat::Nat; - -use ixon::CompileError; - -/// Convert a Lean `Nat` to `usize`, returning `CompileError` on overflow. -/// -/// Use in functions that return `Result<_, CompileError>`. -pub fn try_nat_to_usize(n: &Nat) -> Result { - n.to_u64().map(|v| v as usize).ok_or_else(|| CompileError::UnsupportedExpr { - desc: "Nat field exceeds u64".into(), - }) -} - -/// Convert a Lean `Nat` to `usize`, panicking on overflow. -/// -/// Use in pure functions where returning `Result` would cascade through -/// callers. Overflow is impossible for valid Lean metadata — these fields -/// represent type constructor arities which are always < 2^64. -pub fn nat_to_usize(n: &Nat) -> usize { - n.to_u64().expect("Nat field exceeds u64") as usize -} - -/// Convert a Lean `Nat` to `u64`, panicking on overflow. -pub fn nat_to_u64(n: &Nat) -> u64 { - n.to_u64().expect("Nat field exceeds u64") -} diff --git a/crates/compile/src/compile/publication.rs b/crates/compile/src/compile/publication.rs deleted file mode 100644 index b964d99d5..000000000 --- a/crates/compile/src/compile/publication.rs +++ /dev/null @@ -1,624 +0,0 @@ -//! Anonymous publication preserves every previously stored payload. Address -//! equality alone is not evidence of byte equality. Each comparison and insert -//! holds the same map entry guard, including when compiler workers race. The -//! Lean drivers check the same before merging a block -//! (`Ix.CompileM.checkCompiledBlock`, `Ix.CompileM.storeBlob`). - -use dashmap::mapref::entry::Entry; -use ix_common::address::Address; -use ixon::{CompileError, LazyConstant, constant::Constant, env::DEMOTE}; - -use super::{CompileState, block_txn}; - -pub(super) fn conflict(kind: &str) -> CompileError { - CompileError::InvalidMutualBlock { - reason: format!("anonymous publication: conflicting {kind} payload"), - } -} - -impl CompileState { - /// Store a compiled constant under `addr`, already serialized to `bytes` by - /// the caller (who hashed them to `addr`), so it is not serialized again: an - /// existing entry must hold the same bytes, or the store is the conflict - /// error and the entry is kept. - pub(crate) fn store_const_bytes( - &self, - addr: Address, - bytes: Vec, - constant: Constant, - ) -> Result<(), CompileError> { - self.store_const_bytes_demoted(addr, bytes, constant, *DEMOTE) - } - - fn store_const_bytes_demoted( - &self, - addr: Address, - bytes: Vec, - constant: Constant, - demote: bool, - ) -> Result<(), CompileError> { - // Built outside the map lock. Both storage policies compare the actual - // bytes; neither overwrites nor silently accepts a conflicting key. - let pending = LazyConstant::from_serialized(constant, bytes, !demote); - block_txn::with(|txn| { - if let Some(txn) = txn { - if let Some(existing) = txn.constants.get(&addr) { - if existing.raw_bytes() != pending.raw_bytes() { - return Err(conflict("constant")); - } - } else if let Some(existing) = self.env.consts.get(&addr) { - if existing.raw_bytes() != pending.raw_bytes() { - return Err(conflict("constant")); - } - } else { - txn.constants.insert(addr, pending); - } - return Ok(()); - } - let _publication = self.publication_lock.lock().unwrap(); - match self.env.consts.entry(addr) { - Entry::Occupied(entry) => { - if entry.get().raw_bytes() != pending.raw_bytes() { - return Err(conflict("constant")); - } - }, - Entry::Vacant(entry) => { - entry.insert(pending); - }, - } - Ok(()) - }) - } - - /// Store a blob under its content address: an existing entry must hold the - /// same bytes, or the store is the conflict error and the entry is kept. - pub(crate) fn store_blob( - &self, - bytes: Vec, - ) -> Result { - let addr = Address::hash(&bytes); - block_txn::with(|txn| { - if let Some(txn) = txn { - if let Some(existing) = txn.blobs.get(&addr) { - if existing != &bytes { - return Err(conflict("blob")); - } - } else if let Some(existing) = self.env.blobs.get(&addr) { - if existing.value() != &bytes { - return Err(conflict("blob")); - } - } else { - txn.blobs.insert(addr.clone(), bytes); - } - return Ok(addr); - } - let _publication = self.publication_lock.lock().unwrap(); - match self.env.blobs.entry(addr.clone()) { - Entry::Occupied(entry) => { - if *entry.get() != bytes { - return Err(conflict("blob")); - } - }, - Entry::Vacant(entry) => { - entry.insert(bytes); - }, - } - Ok(addr) - }) - } -} - -#[cfg(test)] -mod tests { - use std::sync::{Arc, Barrier}; - - use ix_common::env::{ - AxiomVal, ConstantInfo as LeanConstantInfo, ConstantVal, DataValue, - DefinitionSafety, DefinitionVal, Env as LeanEnv, Expr as LeanExpr, Level, - Literal, Name, ReducibilityHints, SourceInfo, Syntax, - }; - use ixon::{ - constant::{Axiom, ConstantInfo}, - expr::Expr, - }; - - use super::*; - use crate::compile::{ - BlockCache, CompileOptions, block_txn, compile_const, compile_data_value, - compile_env_with_options, compile_name, store_string, - }; - use crate::graph::NameSet; - - fn constant(lvls: u64) -> Constant { - Constant::new(ConstantInfo::Axio(Axiom { - is_unsafe: false, - lvls, - typ: Expr::sort(0), - })) - } - - fn bytes(c: &Constant) -> Vec { - let mut out = Vec::new(); - c.put(&mut out); - out - } - - fn name(s: &str) -> Name { - Name::str(Name::anon(), s.to_owned()) - } - - fn axiom(n: &Name) -> LeanConstantInfo { - LeanConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: n.clone(), - level_params: vec![], - typ: LeanExpr::sort(Level::succ(Level::zero())), - }, - is_unsafe: false, - }) - } - - fn definition(n: &Name, value: LeanExpr, all: Vec) -> LeanConstantInfo { - LeanConstantInfo::DefnInfo(DefinitionVal { - cnst: ConstantVal { - name: n.clone(), - level_params: vec![], - typ: LeanExpr::sort(Level::succ(Level::zero())), - }, - value, - hints: ReducibilityHints::Opaque, - safety: DefinitionSafety::Safe, - all, - }) - } - - fn artifact(stt: &CompileState) -> Vec { - let mut bytes = Vec::new(); - stt.env.put(&mut bytes).unwrap(); - bytes - } - - #[test] - fn refused_mutual_block_preserves_the_complete_owner_artifact() { - let (f, g) = (name("refusedMember"), name("existingOwner")); - let mut owner_env = LeanEnv::default(); - owner_env.insert(g.clone(), axiom(&g)); - let stt = CompileState::default(); - let owner = compile_const( - &g, - &[g.clone()].into_iter().collect(), - &Arc::new(owner_env), - &mut BlockCache::default(), - &stt, - ) - .unwrap(); - let before = artifact(&stt); - let mut members = LeanEnv::default(); - let all = vec![f.clone(), g.clone()]; - members.insert( - f.clone(), - definition(&f, LeanExpr::cnst(g.clone(), vec![]), all.clone()), - ); - members.insert( - g.clone(), - definition(&g, LeanExpr::cnst(f.clone(), vec![]), all.clone()), - ); - let all: NameSet = all.into_iter().collect(); - block_txn::start(); - let result = compile_const( - &f, - &all, - &Arc::new(members), - &mut BlockCache::default(), - &stt, - ); - block_txn::finish(&stt, result.is_err()).unwrap(); - let error = result.unwrap_err().to_string(); - assert!(error.contains("conflicting claims for name 'existingOwner'")); - assert_eq!(stt.resolve_addr(&g), Some(owner.clone())); - assert_eq!(stt.env.named.get(&g).unwrap().addr, owner.clone()); - let keys: Vec<_> = stt.env.consts.iter().map(|e| e.key().clone()).collect(); - assert_eq!(keys, vec![owner], "failed block leaked anonymous records"); - assert!(stt.blocks.is_empty(), "failed block leaked class ordering"); - assert_eq!(artifact(&stt), before, "failed block changed partial output"); - } - - #[test] - fn scheduled_failure_preserves_the_successful_subset_artifact() { - let (owner, bad, dependent) = - (name("owner"), name("badProjection"), name("dependent")); - let mut success = LeanEnv::default(); - success.insert(owner.clone(), axiom(&owner)); - let options = CompileOptions { max_workers: Some(2) }; - let expected = - compile_env_with_options(&Arc::new(success.clone()), options).unwrap(); - let mut input = success; - input.insert( - bad.clone(), - definition( - &bad, - LeanExpr::proj( - owner.clone(), - bignat::Nat(num_bigint::BigUint::from(1u64) << 64u32), - LeanExpr::lit(Literal::StrVal("failed-block-literal".to_owned())), - ), - vec![bad.clone()], - ), - ); - input.insert( - dependent.clone(), - definition( - &dependent, - LeanExpr::cnst(bad.clone(), vec![]), - vec![dependent.clone()], - ), - ); - let actual = compile_env_with_options(&Arc::new(input), options).unwrap(); - assert_eq!(actual.ungrounded.len(), 2); - assert!( - actual.ungrounded.get(&bad).unwrap().contains("proj index too large") - ); - assert!(actual.ungrounded.contains_key(&dependent)); - assert_eq!(actual.env.consts.len(), expected.env.consts.len()); - assert_eq!(actual.env.named.len(), expected.env.named.len()); - assert_eq!(artifact(&actual), artifact(&expected)); - } - - #[test] - fn successful_mutual_block_publishes_all_tables_with_identical_bytes() { - let (f, g) = (name("firstMember"), name("secondMember")); - let mut env = LeanEnv::default(); - let all = vec![f.clone(), g.clone()]; - env.insert( - f.clone(), - definition(&f, LeanExpr::cnst(g.clone(), vec![]), all.clone()), - ); - env.insert( - g.clone(), - definition(&g, LeanExpr::cnst(f.clone(), vec![]), all.clone()), - ); - let env = Arc::new(env); - let all: NameSet = all.into_iter().collect(); - let run = |stt: &CompileState| { - compile_const(&f, &all, &env, &mut BlockCache::default(), stt).unwrap() - }; - let direct = CompileState::default(); - let expected = run(&direct); - direct.finalize_hints(); - let staged = CompileState::default(); - block_txn::start(); - assert_eq!(run(&staged), expected); - assert_eq!(staged.resolve_addr(&f), Some(expected)); - assert_eq!(artifact(&staged), artifact(&CompileState::default())); - assert!(staged.blocks.is_empty()); - assert!(staged.def_hints.is_empty()); - block_txn::finish(&staged, false).unwrap(); - staged.finalize_hints(); - assert_eq!(staged.blocks.len(), 2); - assert_eq!(staged.def_hints.len(), 2); - assert_eq!(artifact(&staged), artifact(&direct)); - } - - fn store_named_record(stt: &CompileState, n: &Name, lvls: u64) -> Address { - let c = constant(lvls); - let key = Address::hash(&bytes(&c)); - stt.store_const(key.clone(), c).unwrap(); - compile_name(n, stt).unwrap(); - stt - .claim_and_register_compiled(n, ixon::env::Named::with_addr(key.clone())) - .unwrap(); - stt.record_hint(n, Some(ReducibilityHints::Abbrev)); - stt.register_block(n.clone(), vec![vec![n.clone()]]); - key - } - - #[test] - fn concurrent_identical_success_survives_either_failure_order() { - for failure_first in [false, true] { - let stt = CompileState::default(); - let n = name("sharedOwner"); - let ready = Barrier::new(2); - let ordered = Barrier::new(2); - std::thread::scope(|scope| { - let successful = scope.spawn(|| { - block_txn::start(); - store_named_record(&stt, &n, 0); - ready.wait(); - if failure_first { - ordered.wait(); - } - block_txn::finish(&stt, false).unwrap(); - if !failure_first { - ordered.wait(); - } - }); - let failed = scope.spawn(|| { - block_txn::start(); - store_named_record(&stt, &n, 0); - store_named_record(&stt, &name("failedOnly"), 1); - store_string("failed-only-literal", &stt).unwrap(); - ready.wait(); - if !failure_first { - ordered.wait(); - } - block_txn::finish(&stt, true).unwrap(); - if failure_first { - ordered.wait(); - } - }); - successful.join().unwrap(); - failed.join().unwrap(); - }); - stt.finalize_hints(); - let expected = CompileState::default(); - store_named_record(&expected, &n, 0); - expected.finalize_hints(); - assert_eq!(artifact(&stt), artifact(&expected)); - assert_eq!(stt.blocks.len(), 1); - assert_eq!(stt.def_hints.len(), 1); - assert_eq!(stt.name_to_addr.len(), 1); - } - } - - #[test] - fn concurrent_conflicting_batches_publish_only_the_winner() { - let stt = CompileState::default(); - let key = Address::hash(b"forced shared batch key"); - let ready = Barrier::new(2); - let stage = |stt: &CompileState, side: u64| { - stt.store_const(key.clone(), constant(side)).unwrap(); - let n = name(if side == 0 { "leftBatch" } else { "rightBatch" }); - compile_name(&n, stt).unwrap(); - stt - .claim_and_register_compiled( - &n, - ixon::env::Named::with_addr(key.clone()), - ) - .unwrap(); - }; - let winner = std::thread::scope(|scope| { - let run = |side| { - block_txn::start(); - stage(&stt, side); - ready.wait(); - block_txn::finish(&stt, false) - }; - let left = scope.spawn(move || run(0)); - let right = scope.spawn(move || run(1)); - let left = left.join().unwrap(); - let right = right.join().unwrap(); - assert_ne!(left.is_ok(), right.is_ok()); - let winner = if left.is_ok() { 0 } else { 1 }; - assert_eq!( - left.err().or_else(|| right.err()), - Some(conflict("constant")) - ); - winner - }); - let expected = CompileState::default(); - stage(&expected, winner); - assert_eq!(artifact(&stt), artifact(&expected)); - } - - #[test] - fn late_conflicts_refuse_the_entire_batch_without_partial_publication() { - for kind in ["constant", "blob", "claim"] { - let stt = CompileState::default(); - let claimed = name("lateClaim"); - block_txn::start(); - let key = store_named_record(&stt, &claimed, 0); - store_named_record(&stt, &name("unrelatedStagedName"), 2); - let blob = store_string("late-blob", &stt).unwrap(); - assert!(stt.env.consts.is_empty()); - std::thread::scope(|scope| { - scope - .spawn(|| match kind { - "constant" => stt.store_const(key, constant(1)).unwrap(), - "blob" => { - // Deliberately poison the address after the block staged it. - let _publication = stt.publication_lock.lock().unwrap(); - stt.env.blobs.insert(blob, b"existing bytes".to_vec()); - }, - "claim" => { - store_named_record(&stt, &claimed, 1); - }, - _ => unreachable!(), - }) - .join() - .unwrap(); - }); - let before = artifact(&stt); - let error = block_txn::finish(&stt, false).unwrap_err(); - if kind == "claim" { - assert!(error.to_string().contains("conflicting claims for name")); - } else { - assert_eq!(error, conflict(kind)); - } - assert_eq!(artifact(&stt), before, "late {kind} conflict leaked output"); - let expected_names = usize::from(kind == "claim"); - assert_eq!(stt.name_to_addr.len(), expected_names); - assert_eq!(stt.blocks.len(), expected_names); - assert_eq!(stt.def_hints.len(), expected_names); - assert!(block_txn::with(|txn| txn.is_none())); - } - } - - impl CompileState { - fn store_const_demoted( - &self, - addr: Address, - constant: Constant, - demote: bool, - ) -> Result<(), CompileError> { - self.store_const_bytes_demoted(addr, bytes(&constant), constant, demote) - } - - fn store_const( - &self, - addr: Address, - constant: Constant, - ) -> Result<(), CompileError> { - self.store_const_bytes(addr, bytes(&constant), constant) - } - } - - #[test] - fn aliases_agree_and_conflicts_preserve_the_old_payload_in_both_modes() { - for demote in [false, true] { - let stt = CompileState::default(); - let old = constant(0); - let payload = bytes(&old); - let key = Address::hash(&payload); - stt.store_const_demoted(key.clone(), old.clone(), demote).unwrap(); - stt.store_const_demoted(key.clone(), old, demote).unwrap(); - // Deliberately inconsistent key, not a cryptographic collision. - let err = stt - .store_const_demoted(key.clone(), constant(1), demote) - .expect_err("same key with different bytes must fail"); - assert_eq!(err, conflict("constant")); - assert_eq!(stt.env.consts.get(&key).unwrap().raw_bytes(), payload); - assert_eq!(stt.env.consts.len(), 1); - } - } - - #[test] - fn lazy_prior_entries_are_checked_without_materializing_them() { - let stt = CompileState::default(); - let incoming = constant(0); - let key = Address::hash(&bytes(&incoming)); - // A malformed lazy record is still a prior payload to preserve. Comparing - // raw bytes must neither parse it nor silently treat the key as a match. - stt.env.store_const_lazy(key.clone(), vec![0xff].into()); - assert_eq!( - stt.store_const(key.clone(), incoming), - Err(conflict("constant")) - ); - assert_eq!(stt.env.consts.get(&key).unwrap().raw_bytes(), &[0xff]); - } - - #[test] - fn constant_and_blob_address_spaces_remain_separate() { - let stt = CompileState::default(); - let key = store_string("literal", &stt).unwrap(); - stt.store_const(key.clone(), constant(0)).unwrap(); - assert_eq!(stt.env.get_blob(&key).unwrap(), b"literal"); - assert_eq!( - stt.env.consts.get(&key).unwrap().raw_bytes(), - bytes(&constant(0)) - ); - } - - #[test] - fn metadata_writers_propagate_blob_conflicts() { - let stt = CompileState::default(); - let key = Address::hash(b"component"); - stt.env.blobs.insert(key.clone(), b"old payload".to_vec()); - assert_eq!(store_string("component", &stt), Err(conflict("blob"))); - let name = Name::str(Name::anon(), "component".to_owned()); - assert_eq!(compile_name(&name, &stt), Err(conflict("blob"))); - assert!( - !stt.env.names.contains_key(&Address::from_blake3_hash(*name.get_hash())) - ); - let syntax = Syntax::Atom(SourceInfo::None, "component".to_owned()); - assert_eq!( - compile_data_value(&DataValue::OfSyntax(Box::new(syntax)), &stt), - Err(conflict("blob")) - ); - assert_eq!(stt.env.get_blob(&key).unwrap(), b"old payload"); - } - - #[test] - fn compilation_refuses_a_poisoned_prior_record_before_registering_the_name() { - use crate::compile::{BlockCache, block_txn, compile_const}; - use crate::graph::NameSet; - use ix_common::env::{ - AxiomVal, ConstantInfo as LeanConstantInfo, ConstantVal, Env, - Expr as LeanExpr, Level, - }; - use std::sync::Arc; - - let name = Name::str(Name::anon(), "publicationAxiom".to_owned()); - let mut env = Env::default(); - env.insert( - name.clone(), - LeanConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: name.clone(), - level_params: vec![], - typ: LeanExpr::sort(Level::zero()), - }, - is_unsafe: false, - }), - ); - let env = Arc::new(env); - let all: NameSet = [name.clone()].into_iter().collect(); - let run = |stt: &CompileState| { - compile_const(&name, &all, &env, &mut BlockCache::default(), stt) - }; - let key = run(&CompileState::default()).unwrap(); - let stt = CompileState::default(); - stt.env.store_const_lazy(key.clone(), vec![0xff].into()); - block_txn::start(); - let result = run(&stt); - block_txn::finish(&stt, true).unwrap(); - assert_eq!(result, Err(conflict("constant"))); - assert!(!stt.env.named.contains_key(&name)); - assert!(!stt.name_to_addr.contains_key(&name)); - assert_eq!(stt.env.consts.get(&key).unwrap().raw_bytes(), &[0xff]); - } - - #[test] - fn concurrent_conflicting_writers_cannot_overwrite_the_winner() { - for demote in [false, true] { - let stt = CompileState::default(); - let key = Address::hash(b"forced shared key"); - let barrier = Barrier::new(2); - std::thread::scope(|scope| { - let left = scope.spawn(|| { - barrier.wait(); - stt.store_const_demoted(key.clone(), constant(0), demote) - }); - let right = scope.spawn(|| { - barrier.wait(); - stt.store_const_demoted(key.clone(), constant(1), demote) - }); - let left = left.join().unwrap(); - let right = right.join().unwrap(); - assert_ne!(left.is_ok(), right.is_ok()); - let winner = if left.is_ok() { 0 } else { 1 }; - assert_eq!( - stt.env.consts.get(&key).unwrap().raw_bytes(), - bytes(&constant(winner)) - ); - assert_eq!( - left.err().or_else(|| right.err()), - Some(conflict("constant")) - ); - }); - } - } - - #[test] - fn concurrent_identical_writers_both_succeed() { - let stt = CompileState::default(); - let key = Address::hash(&bytes(&constant(0))); - let barrier = Barrier::new(2); - std::thread::scope(|scope| { - let left = scope.spawn(|| { - barrier.wait(); - stt.store_const_demoted(key.clone(), constant(0), false) - }); - let right = scope.spawn(|| { - barrier.wait(); - stt.store_const_demoted(key.clone(), constant(0), true) - }); - left.join().unwrap().unwrap(); - right.join().unwrap().unwrap(); - }); - assert_eq!(stt.env.consts.len(), 1); - assert_eq!( - stt.env.consts.get(&key).unwrap().raw_bytes(), - bytes(&constant(0)) - ); - } -} diff --git a/crates/compile/src/compile/source_recursor_tests.rs b/crates/compile/src/compile/source_recursor_tests.rs deleted file mode 100644 index 21d240fbb..000000000 --- a/crates/compile/src/compile/source_recursor_tests.rs +++ /dev/null @@ -1,945 +0,0 @@ -//! Lean's own recursor families through the kernel's direct ingress and -//! through compilation: hand-transcribed Lean fixtures, checked as given. - -use bignat::Nat; -use ix_common::env::{ - BinderInfo, ConstantInfo, ConstantVal, ConstructorVal, Env as LeanEnv, - Expr as LeanExpr, InductiveVal, Level, Name, RecursorRule, RecursorVal, -}; - -fn n(s: &str) -> Name { - Name::str(Name::anon(), s.to_string()) -} - -/// Helper: `∀ (name : domain), body` with default binder info. -fn epi(name: Name, domain: LeanExpr, body: LeanExpr) -> LeanExpr { - LeanExpr::all(name, domain, body, BinderInfo::Default) -} - -/// Build a minimal Prop mutual block: A | a : B → A, B | b : A → B. -/// -/// Both A and B are in Prop (Sort 0), with single constructors that -/// cross-reference the sibling. `all = [A, B]` on both inductives. -/// No recursors. -fn build_alpha_collapse_env_named(a: Name, b: Name) -> (LeanEnv, Name, Name) { - let hyg = Name::num( - Name::str(Name::anon(), "a._@._internal._hyg".into()), - Nat::from(0u64), - ); - let a_ctor = Name::str(a.clone(), "a".into()); - let b_ctor = Name::str(b.clone(), "b".into()); - let all = vec![a.clone(), b.clone()]; - let a_c = LeanExpr::cnst(a.clone(), vec![]); - let b_c = LeanExpr::cnst(b.clone(), vec![]); - let prop = LeanExpr::sort(Level::zero()); - - let mut env = LeanEnv::default(); - env.insert( - a.clone(), - ConstantInfo::InductInfo(InductiveVal { - cnst: ConstantVal { - name: a.clone(), - level_params: vec![], - typ: prop.clone(), - }, - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: all.clone(), - ctors: vec![a_ctor.clone()], - num_nested: Nat::from(0u64), - is_rec: true, - is_unsafe: false, - is_reflexive: false, - }), - ); - env.insert( - b.clone(), - ConstantInfo::InductInfo(InductiveVal { - cnst: ConstantVal { - name: b.clone(), - level_params: vec![], - typ: prop.clone(), - }, - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: all.clone(), - ctors: vec![b_ctor.clone()], - num_nested: Nat::from(0u64), - is_rec: true, - is_unsafe: false, - is_reflexive: false, - }), - ); - // A.a : B → A - env.insert( - a_ctor.clone(), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: ConstantVal { - name: a_ctor, - level_params: vec![], - typ: epi(hyg.clone(), b_c, a_c.clone()), - }, - induct: a.clone(), - cidx: Nat::from(0u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(1u64), - is_unsafe: false, - }), - ); - // B.b : A → B - env.insert( - b_ctor.clone(), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: ConstantVal { - name: b_ctor, - level_params: vec![], - typ: epi(hyg, a_c, LeanExpr::cnst(b.clone(), vec![])), - }, - induct: b.clone(), - cidx: Nat::from(0u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(1u64), - is_unsafe: false, - }), - ); - (env, a, b) -} - -/// The uncollapsed recursors Lean generates for the Prop A/B fixture. -/// Transcribed from Lean 4.33.1's `#print A.rec` / `#print B.rec`, not -/// generated by the compiler under test. Both have two dependent Prop -/// motives, two minors, and a rule recursively calling the sibling recursor. -fn build_alpha_collapse_env_with_recursors() -> (LeanEnv, Name, Name) { - build_alpha_collapse_env_with_recursors_named(n("A"), n("B")) -} - -fn build_alpha_collapse_env_with_recursors_named( - a: Name, - b: Name, -) -> (LeanEnv, Name, Name) { - let (mut env, a, b) = build_alpha_collapse_env_named(a, b); - let a_ctor = Name::str(a.clone(), "a".into()); - let b_ctor = Name::str(b.clone(), "b".into()); - let a_rec = Name::str(a.clone(), "rec".into()); - let b_rec = Name::str(b.clone(), "rec".into()); - let a_c = LeanExpr::cnst(a.clone(), vec![]); - let b_c = LeanExpr::cnst(b.clone(), vec![]); - insert_two_motive_recursors( - &mut env, - &[a.clone(), b.clone()], - [a_rec, b_rec], - [a_c, b_c], - [ - (a_ctor.clone(), LeanExpr::cnst(a_ctor, vec![])), - (b_ctor.clone(), LeanExpr::cnst(b_ctor, vec![])), - ], - &[], - ); - (env, a, b) -} - -/// Shared binder/rule shape of the Lean-transcribed A/B and Tree/Wrap Tree -/// fixtures. `ctor_apps` includes specialized parameters (Wrap.mk Tree). -fn insert_two_motive_recursors( - env: &mut LeanEnv, - all: &[Name], - rec_names: [Name; 2], - major_types: [LeanExpr; 2], - ctor_apps: [(Name, LeanExpr); 2], - level_params: &[Name], -) { - let [a_rec, b_rec] = rec_names; - let [a_c, b_c] = major_types; - let [(a_ctor, a_ctor_app), (b_ctor, b_ctor_app)] = ctor_apps; - let elim_level = - level_params.first().map_or_else(Level::zero, |u| Level::param(u.clone())); - let motive_sort = LeanExpr::sort(elim_level); - let rec_us: Vec<_> = level_params.iter().cloned().map(Level::param).collect(); - let bv = |i: u64| LeanExpr::bvar(Nat::from(i)); - - // Domains are scoped under the preceding prefix binders: - // {mA : A → Sort u} {mB : B → Sort u} (u = 0 for the Prop fixture) - // (a : ∀ x : B, mB x → mA (A.a x)) - // (b : ∀ x : A, mA x → mB (B.b x)). - let prefix = [ - ( - n("motive_1"), - epi(n("t"), a_c.clone(), motive_sort.clone()), - BinderInfo::Implicit, - ), - ( - n("motive_2"), - epi(n("t"), b_c.clone(), motive_sort), - BinderInfo::Implicit, - ), - ( - n("a"), - epi( - n("x"), - b_c.clone(), - epi( - n("ih"), - LeanExpr::app(bv(1), bv(0)), - LeanExpr::app(bv(3), LeanExpr::app(a_ctor_app, bv(1))), - ), - ), - BinderInfo::Default, - ), - ( - n("b"), - epi( - n("x"), - a_c.clone(), - epi( - n("ih"), - LeanExpr::app(bv(3), bv(0)), - LeanExpr::app(bv(3), LeanExpr::app(b_ctor_app, bv(1))), - ), - ), - BinderInfo::Default, - ), - ]; - - for (rec_name, ctor, major_ty, field_ty, motive_idx, minor_idx, peer) in [ - (&a_rec, &a_ctor, &a_c, &b_c, 4, 2, &b_rec), - (&b_rec, &b_ctor, &b_c, &a_c, 3, 1, &a_rec), - ] { - let typ = prefix.iter().rev().fold( - epi(n("t"), major_ty.clone(), LeanExpr::app(bv(motive_idx), bv(0))), - |body, (name, domain, info)| { - LeanExpr::all(name.clone(), domain.clone(), body, info.clone()) - }, - ); - // λ mA mB a b x, minor x (peer.rec mA mB a b x). - let recursive_call = (0..=4) - .rev() - .fold(LeanExpr::cnst(peer.clone(), rec_us.clone()), |f, i| { - LeanExpr::app(f, bv(i)) - }); - let rhs = LeanExpr::lam( - n("x"), - field_ty.clone(), - LeanExpr::app(LeanExpr::app(bv(minor_idx), bv(0)), recursive_call), - BinderInfo::Default, - ); - let rhs = prefix.iter().rev().fold(rhs, |body, (name, domain, _)| { - LeanExpr::lam(name.clone(), domain.clone(), body, BinderInfo::Default) - }); - env.insert( - rec_name.clone(), - ConstantInfo::RecInfo(RecursorVal { - cnst: ConstantVal { - name: rec_name.clone(), - level_params: level_params.to_vec(), - typ, - }, - all: all.to_vec(), - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - num_motives: Nat::from(2u64), - num_minors: Nat::from(2u64), - rules: vec![RecursorRule { - ctor: ctor.clone(), - n_fields: Nat::from(1u64), - rhs, - }], - k: false, - is_unsafe: false, - }), - ); - } -} - -/// Lean 4.33.1: Wrap (α : Type) | mk : α → Wrap α; -/// Tree | node : Wrap Tree → Tree. The nested source peers are -/// Tree.rec, Tree.rec_1, both universe-polymorphic with two motives/minors. -fn build_nested_source_recursors() -> (LeanEnv, Name) { - let mut env = LeanEnv::default(); - let wrap = n("Wrap"); - let tree = n("Tree"); - let wrap_mk = Name::str(wrap.clone(), "mk".into()); - let node = Name::str(tree.clone(), "node".into()); - let tree_c = LeanExpr::cnst(tree.clone(), vec![]); - let wrap_c = LeanExpr::cnst(wrap.clone(), vec![]); - let wrap_tree = LeanExpr::app(wrap_c.clone(), tree_c.clone()); - let type0 = LeanExpr::sort(Level::succ(Level::zero())); - for (name, typ, ctor, params, nested) in [ - (&wrap, epi(n("α"), type0.clone(), type0.clone()), &wrap_mk, 1u64, 0u64), - (&tree, type0.clone(), &node, 0, 1), - ] { - env.insert( - name.clone(), - ConstantInfo::InductInfo(InductiveVal { - cnst: ConstantVal { name: name.clone(), level_params: vec![], typ }, - num_params: Nat::from(params), - num_indices: Nat::from(0u64), - all: vec![name.clone()], - ctors: vec![ctor.clone()], - num_nested: Nat::from(nested), - is_rec: nested != 0, - is_unsafe: false, - is_reflexive: false, - }), - ); - } - let wrap_mk_ty = LeanExpr::all( - n("α"), - type0, - epi( - n("x"), - LeanExpr::bvar(Nat::from(0u64)), - LeanExpr::app(wrap_c, LeanExpr::bvar(Nat::from(1u64))), - ), - BinderInfo::Implicit, - ); - for (name, typ, induct, params) in [ - (&wrap_mk, wrap_mk_ty, &wrap, 1u64), - (&node, epi(n("x"), wrap_tree.clone(), tree_c.clone()), &tree, 0), - ] { - env.insert( - name.clone(), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: ConstantVal { name: name.clone(), level_params: vec![], typ }, - induct: induct.clone(), - cidx: Nat::from(0u64), - num_params: Nat::from(params), - num_fields: Nat::from(1u64), - is_unsafe: false, - }), - ); - } - insert_two_motive_recursors( - &mut env, - std::slice::from_ref(&tree), - [ - Name::str(tree.clone(), "rec".into()), - Name::str(tree.clone(), "rec_1".into()), - ], - [tree_c.clone(), wrap_tree], - [ - (node.clone(), LeanExpr::cnst(node, vec![])), - (wrap_mk.clone(), LeanExpr::app(LeanExpr::cnst(wrap_mk, vec![]), tree_c)), - ], - &[n("u")], - ); - (env, tree) -} - -/// Build a simple Type-level inductive (Nat-like): T | Z : T | S : T → T -fn build_type_nat_env() -> (LeanEnv, Name) { - let _u = Name::str(Name::anon(), "u".to_string()); - let t = n("T"); - let z_ctor = Name::str(t.clone(), "Z".into()); - let s_ctor = Name::str(t.clone(), "S".into()); - let t_c = LeanExpr::cnst(t.clone(), vec![]); - let type0 = LeanExpr::sort(Level::succ(Level::zero())); - let hyg = Name::num( - Name::str(Name::anon(), "a._@._internal._hyg".into()), - Nat::from(0u64), - ); - - let mut env = LeanEnv::default(); - env.insert( - t.clone(), - ConstantInfo::InductInfo(InductiveVal { - cnst: ConstantVal { name: t.clone(), level_params: vec![], typ: type0 }, - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: vec![t.clone()], - ctors: vec![z_ctor.clone(), s_ctor.clone()], - num_nested: Nat::from(0u64), - is_rec: true, - is_unsafe: false, - is_reflexive: false, - }), - ); - // T.Z : T - env.insert( - z_ctor.clone(), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: ConstantVal { - name: z_ctor, - level_params: vec![], - typ: t_c.clone(), - }, - induct: t.clone(), - cidx: Nat::from(0u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(0u64), - is_unsafe: false, - }), - ); - // T.S : T → T - env.insert( - s_ctor.clone(), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: ConstantVal { - name: s_ctor, - level_params: vec![], - typ: epi(hyg, t_c.clone(), t_c), - }, - induct: t.clone(), - cidx: Nat::from(1u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(1u64), - is_unsafe: false, - }), - ); - // PUnit and PProd, the support declarations of Lean's `.below`. - add_punit_pprod(&mut env); - (env, t) -} - -/// Add minimal PUnit.{u} and PProd.{u,v} definitions to a test environment. -fn add_punit_pprod(env: &mut LeanEnv) { - let u_name = n("u"); - let v_name = n("v"); - let sort_u = LeanExpr::sort(Level::param(u_name.clone())); - let sort_v = LeanExpr::sort(Level::param(v_name.clone())); - - // PUnit.{u} : Sort u, with one constructor PUnit.unit.{u} : PUnit.{u} - let punit = n("PUnit"); - let punit_unit = Name::str(punit.clone(), "unit".into()); - let punit_ty = sort_u.clone(); // PUnit : Sort u - let punit_c = - LeanExpr::cnst(punit.clone(), vec![Level::param(u_name.clone())]); - env.insert( - punit.clone(), - ConstantInfo::InductInfo(InductiveVal { - cnst: ConstantVal { - name: punit.clone(), - level_params: vec![u_name.clone()], - typ: punit_ty, - }, - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: vec![punit.clone()], - ctors: vec![punit_unit.clone()], - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }), - ); - env.insert( - punit_unit.clone(), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: ConstantVal { - name: punit_unit, - level_params: vec![u_name.clone()], - typ: punit_c, - }, - induct: punit.clone(), - cidx: Nat::from(0u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(0u64), - is_unsafe: false, - }), - ); - - // PProd.{u, v} : Sort u → Sort v → Sort (max 1 u v) - let pprod = n("PProd"); - let pprod_mk = Name::str(pprod.clone(), "mk".into()); - let max_1_u_v = Level::max( - Level::succ(Level::zero()), - Level::max(Level::param(u_name.clone()), Level::param(v_name.clone())), - ); - // Type: ∀ (α : Sort u) (β : Sort v), Sort (max 1 u v) - let pprod_ty = LeanExpr::all( - Name::str(Name::anon(), "α".into()), - sort_u.clone(), - LeanExpr::all( - Name::str(Name::anon(), "β".into()), - sort_v.clone(), - LeanExpr::sort(max_1_u_v), - BinderInfo::Default, - ), - BinderInfo::Default, - ); - // mk : ∀ {α : Sort u} {β : Sort v}, α → β → PProd α β - let pprod_c = LeanExpr::cnst( - pprod.clone(), - vec![Level::param(u_name.clone()), Level::param(v_name.clone())], - ); - let mk_ty = LeanExpr::all( - Name::str(Name::anon(), "α".into()), - sort_u, - LeanExpr::all( - Name::str(Name::anon(), "β".into()), - sort_v, - LeanExpr::all( - Name::str(Name::anon(), "fst".into()), - LeanExpr::bvar(Nat::from(1u64)), - LeanExpr::all( - Name::str(Name::anon(), "snd".into()), - LeanExpr::bvar(Nat::from(1u64)), - LeanExpr::app( - LeanExpr::app(pprod_c, LeanExpr::bvar(Nat::from(3u64))), - LeanExpr::bvar(Nat::from(2u64)), - ), - BinderInfo::Default, - ), - BinderInfo::Default, - ), - BinderInfo::Implicit, - ), - BinderInfo::Implicit, - ); - env.insert( - pprod.clone(), - ConstantInfo::InductInfo(InductiveVal { - cnst: ConstantVal { - name: pprod.clone(), - level_params: vec![u_name.clone(), v_name.clone()], - typ: pprod_ty, - }, - num_params: Nat::from(2u64), - num_indices: Nat::from(0u64), - all: vec![pprod.clone()], - ctors: vec![pprod_mk.clone()], - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }), - ); - env.insert( - pprod_mk.clone(), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: ConstantVal { - name: pprod_mk, - level_params: vec![u_name, v_name], - typ: mk_ty, - }, - induct: pprod, - cidx: Nat::from(0u64), - num_params: Nat::from(2u64), - num_fields: Nat::from(2u64), - is_unsafe: false, - }), - ); -} - -/// Support declarations for compile_env fixtures (PUnit, PProd, And, True): -/// valid source declarations, not generated placeholders or trusted kernel -/// entries. -fn add_support_declarations(env: &mut LeanEnv) { - add_punit_pprod(env); - let prop = LeanExpr::sort(Level::zero()); - let and = n("And"); - let intro = Name::str(and.clone(), "intro".into()); - let bv = |i: u64| LeanExpr::bvar(Nat::from(i)); - let and_ty = - epi(n("a"), prop.clone(), epi(n("b"), prop.clone(), prop.clone())); - let intro_ty = LeanExpr::all( - n("a"), - prop.clone(), - LeanExpr::all( - n("b"), - prop.clone(), - epi( - n("left"), - bv(1), - epi( - n("right"), - bv(1), - LeanExpr::app( - LeanExpr::app(LeanExpr::cnst(and.clone(), vec![]), bv(3)), - bv(2), - ), - ), - ), - BinderInfo::Implicit, - ), - BinderInfo::Implicit, - ); - for (name, ctor, typ, ctor_typ, params, fields) in [ - (and, intro, and_ty, intro_ty, 2u64, 2u64), - ( - n("True"), - Name::str(n("True"), "intro".into()), - prop, - LeanExpr::cnst(n("True"), vec![]), - 0, - 0, - ), - ] { - env.insert( - name.clone(), - ConstantInfo::InductInfo(InductiveVal { - cnst: ConstantVal { name: name.clone(), level_params: vec![], typ }, - num_params: Nat::from(params), - num_indices: Nat::from(0u64), - all: vec![name.clone()], - ctors: vec![ctor.clone()], - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }), - ); - env.insert( - ctor.clone(), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: ConstantVal { name: ctor, level_params: vec![], typ: ctor_typ }, - induct: name, - cidx: Nat::from(0u64), - num_params: Nat::from(params), - num_fields: Nat::from(fields), - is_unsafe: false, - }), - ); - } -} - -fn assert_source_recursors_check( - source: &LeanEnv, - head: &Name, - expected_peers: &[Name], -) { - use ix_kernel::{constant::KConst, ingress::lean_ingress, tc::TypeChecker}; - - let mut entries: Vec<_> = - source.iter().map(|(name, ci)| (name.clone(), (*ci).clone())).collect(); - entries.sort_by_key(|(name, _)| name.pretty()); - for reverse in [false, true] { - if reverse { - entries.reverse(); - } - let mut source = LeanEnv::default(); - for (name, ci) in &entries { - source.insert(name.clone(), ci.clone()); - } - let mut kenv = lean_ingress(&source).unwrap(); - let (block, _) = - kenv.consts.iter().find(|(id, _)| &id.name == head).unwrap(); - let peers: Vec<_> = kenv.blocks[block] - .iter() - .filter(|id| matches!(kenv.consts.get(id), Some(KConst::Recr { .. }))) - .map(|id| id.name.clone()) - .collect(); - assert_eq!( - peers, expected_peers, - "recursors must follow source .all, not name or insertion order", - ); - let mut ids: Vec<_> = kenv.consts.keys().cloned().collect(); - ids.sort_by_key(|id| id.name.pretty()); - for id in ids { - TypeChecker::new(&mut kenv) - .check_const(&id) - .unwrap_or_else(|e| panic!("source fixture {}: {e}", id.name.pretty())); - } - } -} - -#[test] -fn test_lean_ingress_checks_source_recursors_in_declaration_order() { - for (a, b) in [(n("A"), n("B")), (n("Z"), n("A"))] { - let (source, _, _) = - build_alpha_collapse_env_with_recursors_named(a.clone(), b.clone()); - assert_source_recursors_check( - &source, - &a, - &[Name::str(a.clone(), "rec".into()), Name::str(b, "rec".into())], - ); - } -} - -#[test] -fn test_lean_ingress_checks_nested_source_recursors() { - let (source, tree) = build_nested_source_recursors(); - assert_source_recursors_check( - &source, - &tree, - &[ - Name::str(tree.clone(), "rec".into()), - Name::str(tree.clone(), "rec_1".into()), - ], - ); -} - -#[test] -fn test_compiled_recursors_keep_meta_and_anon_layout() { - use crate::compile::env::compile_env; - use ix_kernel::{ - env::KEnv, - id::KId, - ingress::{ixon_ingress, lean_ingress}, - mode::{Anon, Meta}, - tc::TypeChecker, - }; - use std::sync::Arc; - - let (flat, _, _) = build_alpha_collapse_env_with_recursors(); - let (nested, _) = build_nested_source_recursors(); - for mut source in [flat, nested] { - add_support_declarations(&mut source); - // Check the complete input, including support declarations, before - // compiling it. An invalid prelude cannot make this test pass. - let mut source_kernel = lean_ingress(&source).unwrap(); - let source_ids: Vec<_> = source_kernel.consts.keys().cloned().collect(); - for id in source_ids { - TypeChecker::new(&mut source_kernel) - .check_const(&id) - .unwrap_or_else(|e| panic!("source fixture {}: {e}", id.name.pretty())); - } - let source = Arc::new(source); - let stt = compile_env(&source).unwrap(); - assert!(stt.ungrounded.is_empty(), "{:?}", stt.ungrounded); - let (mut meta_env, _intern) = ixon_ingress::(&stt.env).unwrap(); - let mut anon_env = KEnv::::new(); - // Check every source target. - let mut names: Vec<_> = source.iter().map(|(n, _)| n.clone()).collect(); - names.sort_by_key(Name::pretty); - for name in &names { - let addr = stt.resolve_addr(name).unwrap(); - // Meta ingress stores canonical representatives, not every source - // alias. Use the actual ingressed KId for this canonical address. - let meta_id = meta_env - .consts - .keys() - .find(|id| id.addr == addr) - .unwrap_or_else(|| { - panic!("missing canonical Meta target for {}", name.pretty()) - }) - .clone(); - TypeChecker::new(&mut meta_env) - .check_const(&meta_id) - .unwrap_or_else(|e| panic!("compiled Meta {}: {e}", name.pretty())); - TypeChecker::new_with_lazy_anon(&mut anon_env, &stt.env) - .check_const(&KId::new(addr, ())) - .unwrap_or_else(|e| panic!("compiled Anon {}: {e}", name.pretty())); - } - } -} - -#[test] -fn test_lean_ingress_rejects_recursors_with_swapped_names() { - use ix_kernel::{ingress::lean_ingress, tc::TypeChecker}; - - let (mut source, a, b) = build_alpha_collapse_env_with_recursors(); - let a_rec = Name::str(a, "rec".into()); - let b_rec = Name::str(b, "rec".into()); - let mut left = source.get(&a_rec).unwrap().cloned(); - let mut right = source.get(&b_rec).unwrap().cloned(); - for (ci, name) in [(&mut left, &b_rec), (&mut right, &a_rec)] { - let ConstantInfo::RecInfo(rec) = ci else { unreachable!() }; - rec.cnst.name = name.clone(); - } - source.insert(a_rec.clone(), right); - source.insert(b_rec, left); - let mut kenv = lean_ingress(&source).unwrap(); - let id = kenv.consts.keys().find(|id| id.name == a_rec).unwrap().clone(); - let error = TypeChecker::new(&mut kenv).check_const(&id).unwrap_err(); - assert!(error.to_string().contains("canonical-order mismatch"), "{error}"); -} - -#[test] -fn test_lean_ingress_retains_extra_recursors_for_rejection() { - use ix_kernel::{constant::KConst, ingress::lean_ingress, tc::TypeChecker}; - - let (mut source, a, _) = build_alpha_collapse_env_with_recursors(); - let a_rec = Name::str(a, "rec".into()); - let extra_name = n("UnexpectedRecursor"); - let mut extra = source.get(&a_rec).unwrap().cloned(); - let ConstantInfo::RecInfo(rec) = &mut extra else { unreachable!() }; - rec.cnst.name = extra_name.clone(); - source.insert(extra_name.clone(), extra); - let mut kenv = lean_ingress(&source).unwrap(); - let id = kenv.consts.keys().find(|id| id.name == a_rec).unwrap().clone(); - let KConst::Recr { block, .. } = &kenv.consts[&id] else { unreachable!() }; - assert!(kenv.blocks[block].iter().any(|id| id.name == extra_name)); - let error = TypeChecker::new(&mut kenv).check_const(&id).unwrap_err(); - assert!(error.to_string().contains("rec_ids/flat count mismatch"), "{error}"); -} - -#[test] -fn test_alpha_collapse_source_recursors_have_structural_dependencies() { - let (env, a, b) = build_alpha_collapse_env_with_recursors(); - for (ind, peer) in [(&a, &b), (&b, &a)] { - let rec_name = Name::str(ind.clone(), "rec".into()); - let rec = env.get(&rec_name).unwrap(); - let ConstantInfo::RecInfo(val) = &*rec else { - panic!("expected a source recursor"); - }; - assert_eq!(val.num_motives, Nat::from(2u64)); - assert_eq!(val.num_minors, Nat::from(2u64)); - assert_eq!(val.rules.len(), 1); - let refs = crate::graph::get_constant_info_references(&rec); - // These must be structural edges from the type/rules, not `.all` - // metadata: the inductive block must finish before either recursor. - assert!(refs.contains(&a)); - assert!(refs.contains(&b)); - assert!(refs.contains(&val.rules[0].ctor)); - assert!(refs.contains(&Name::str(peer.clone(), "rec".into()))); - } -} - -/// Full compile pipeline for a Type-level, Nat-like inductive with its -/// `PUnit`/`PProd` support declarations: compile_env succeeds and the -/// inductive is compiled. -#[test] -fn test_type_level_inductive_compiles_with_its_support() { - use crate::compile::env::compile_env; - use std::sync::Arc; - - let (mut env, t) = build_type_nat_env(); - - // PProd/PUnit support declarations. - let u_name = Name::str(Name::anon(), "u".to_string()); - let v_name = Name::str(Name::anon(), "v".to_string()); - let punit_name = Name::str(Name::anon(), "PUnit".to_string()); - let punit_unit = Name::str(punit_name.clone(), "unit".to_string()); - env.insert( - punit_name.clone(), - ConstantInfo::InductInfo(InductiveVal { - cnst: ConstantVal { - name: punit_name.clone(), - level_params: vec![u_name.clone()], - typ: LeanExpr::sort(Level::succ(Level::param(u_name.clone()))), - }, - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: vec![punit_name.clone()], - ctors: vec![punit_unit.clone()], - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }), - ); - env.insert( - punit_unit.clone(), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: ConstantVal { - name: punit_unit, - level_params: vec![u_name.clone()], - typ: LeanExpr::cnst( - punit_name.clone(), - vec![Level::param(u_name.clone())], - ), - }, - induct: punit_name, - cidx: Nat::from(0u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(0u64), - is_unsafe: false, - }), - ); - - let pprod_name = Name::str(Name::anon(), "PProd".to_string()); - let pprod_mk = Name::str(pprod_name.clone(), "mk".to_string()); - let sort_u = LeanExpr::sort(Level::param(u_name.clone())); - let sort_v = LeanExpr::sort(Level::param(v_name.clone())); - let pprod_typ = LeanExpr::all( - Name::str(Name::anon(), "α".to_string()), - sort_u.clone(), - LeanExpr::all( - Name::str(Name::anon(), "β".to_string()), - sort_v.clone(), - LeanExpr::sort(Level::max( - Level::param(u_name.clone()), - Level::param(v_name.clone()), - )), - BinderInfo::Default, - ), - BinderInfo::Default, - ); - env.insert( - pprod_name.clone(), - ConstantInfo::InductInfo(InductiveVal { - cnst: ConstantVal { - name: pprod_name.clone(), - level_params: vec![u_name.clone(), v_name.clone()], - typ: pprod_typ, - }, - num_params: Nat::from(2u64), - num_indices: Nat::from(0u64), - all: vec![pprod_name.clone()], - ctors: vec![pprod_mk.clone()], - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }), - ); - let pprod_mk_typ = LeanExpr::all( - Name::str(Name::anon(), "α".to_string()), - sort_u, - LeanExpr::all( - Name::str(Name::anon(), "β".to_string()), - sort_v, - LeanExpr::all( - Name::str(Name::anon(), "fst".to_string()), - LeanExpr::bvar(Nat::from(1u64)), - LeanExpr::all( - Name::str(Name::anon(), "snd".to_string()), - LeanExpr::bvar(Nat::from(1u64)), - LeanExpr::app( - LeanExpr::app( - LeanExpr::cnst( - pprod_name.clone(), - vec![ - Level::param(u_name.clone()), - Level::param(v_name.clone()), - ], - ), - LeanExpr::bvar(Nat::from(3u64)), - ), - LeanExpr::bvar(Nat::from(2u64)), - ), - BinderInfo::Default, - ), - BinderInfo::Default, - ), - BinderInfo::Implicit, - ), - BinderInfo::Implicit, - ); - env.insert( - pprod_mk.clone(), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: ConstantVal { - name: pprod_mk, - level_params: vec![u_name, v_name], - typ: pprod_mk_typ, - }, - induct: pprod_name, - cidx: Nat::from(0u64), - num_params: Nat::from(2u64), - num_fields: Nat::from(2u64), - is_unsafe: false, - }), - ); - - let lean_env = Arc::new(env); - let stt = compile_env(&lean_env) - .expect("compile_env should succeed with a Type-level inductive"); - - // Verify T was compiled. - let has_name = |n: &Name| stt.resolve_addr(n).is_some(); - assert!(has_name(&t), "T should be compiled"); -} - -/// Lean's own recursor families round-trip: compiled as given, the -/// decompiled environment is the source, recursors and support -/// declarations included. -#[test] -fn source_recursors_decompile_to_the_source() { - use crate::compile::env::compile_env; - use crate::decompile::{check_decompile, decompile_env}; - use std::sync::Arc; - - let (flat, _, _) = build_alpha_collapse_env_with_recursors(); - let (nested, _) = build_nested_source_recursors(); - for mut source in [flat, nested] { - add_support_declarations(&mut source); - let source = Arc::new(source); - let stt = compile_env(&source).unwrap(); - assert!(stt.ungrounded.is_empty(), "{:?}", stt.ungrounded); - let dstt = decompile_env(&stt).unwrap(); - let check = check_decompile(&source, &stt, &dstt).unwrap(); - assert_eq!(check.failures(|_| false), 0, "{check:?}"); - assert_eq!(check.matches, source.len()); - } -} diff --git a/crates/compile/src/compile/validation.rs b/crates/compile/src/compile/validation.rs deleted file mode 100644 index 445e2422a..000000000 --- a/crates/compile/src/compile/validation.rs +++ /dev/null @@ -1,946 +0,0 @@ -//! Memory-aware admission and cooperative retry for inductive validation. -//! -//! The coordinator never occupies a Rayon worker. Cancelled attempts return -//! normally (the host uses panic=abort), dropping scratch data before retry. -//! This is a soft budget, not an allocator limit: checkpoints cannot interrupt -//! an allocation, a lazy-environment fetch, or destruction already in progress. - -use std::cell::Cell; -use std::collections::VecDeque; -use std::sync::{ - Arc, - atomic::{AtomicBool, AtomicU64, Ordering}, - mpsc, -}; -use std::time::{Duration, Instant}; - -use ixon::CompileError; - -use super::memory::{ - GIB, MIB, Memory, MemoryReader, Pressure, pressure, ramp_up, reclaiming, - resource_error, -}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct Cancelled; - -#[derive(Debug)] -pub(crate) enum AttemptError { - Cancelled, - Compile(CompileError), -} - -impl From for AttemptError { - fn from(_: Cancelled) -> Self { - Self::Cancelled - } -} -impl From for AttemptError { - fn from(error: CompileError) -> Self { - Self::Compile(error) - } -} -impl AttemptError { - pub(crate) fn into_compile(self) -> CompileError { - match self { - Self::Compile(error) => error, - Self::Cancelled => resource_error("inductive validation interrupted"), - } - } -} - -#[derive(Default)] -struct Signals { - cancel: AtomicBool, - visits: AtomicU64, - scratch_peak: AtomicU64, -} - -/// Worker-local checkpoint state. Scratch telemetry estimates the largest -/// observed walk cache plus node payload, not total retained scratch or -/// per-job RSS (which cannot be attributed by subtracting process RSS -/// samples while multiple jobs execute). Admission uses OS telemetry. -#[derive(Default)] -pub(crate) struct Checkpoint { - signals: Option>, - visits: Cell, -} - -impl Checkpoint { - pub(crate) fn visit(&self) -> Result<(), Cancelled> { - if let Some(signals) = &self.signals { - if signals.cancel.load(Ordering::Relaxed) { - return Err(Cancelled); - } - let n = self.visits.get().saturating_add(1); - self.visits.set(n); - if n.is_multiple_of(1024) { - signals.visits.store(n, Ordering::Relaxed); - } - } - Ok(()) - } - - pub(crate) fn scratch(&self, estimate: usize) { - if let Some(signals) = &self.signals { - signals.scratch_peak.fetch_max(estimate as u64, Ordering::Relaxed); - } - } -} -impl Drop for Checkpoint { - fn drop(&mut self) { - if let Some(signals) = &self.signals { - signals.visits.store(self.visits.get(), Ordering::Relaxed); - } - } -} - -#[derive(Clone, Copy, Debug)] -struct Options { - max_workers: usize, - initial_workers: usize, - process_budget: Option, - tick: Duration, - ramp_interval: Duration, - recovery: Duration, - idle_timeout: Duration, - verbose: bool, -} -impl Options { - fn from_env() -> Result { - let budget = super::memory::budget_from_env("IX_VALIDATE_MEMORY_GIB")?; - let max_workers = rayon::current_num_threads().max(1); - Ok(Self { - max_workers, - initial_workers: max_workers.min(2), - process_budget: budget, - tick: Duration::from_millis(250), - // Two ticks: a raised limit is sampled before the next doubling - // (`ramp_up`). - ramp_interval: Duration::from_millis(500), - recovery: Duration::from_secs(5), - idle_timeout: Duration::from_secs(30), - verbose: *super::env::IX_VERBOSE, - }) - } -} - -struct Active { - id: usize, - solo: bool, - signals: Arc, -} - -// Production builds abort on panic. In unwind-enabled test/library builds, -// still notify the coordinator if a task exits abnormally, so the enclosing -// Rayon scope can propagate the panic instead of waiting for a lost result. -struct Completion { - id: usize, - sender: Option)>>, -} -impl Completion { - fn finish(mut self, result: Result<(), AttemptError>) { - let _ = self.sender.take().unwrap().send((self.id, result)); - } -} -impl Drop for Completion { - fn drop(&mut self) { - if let Some(sender) = self.sender.take() { - let _ = sender.send(( - self.id, - Err(resource_error("validation task exited without a result").into()), - )); - } - } -} - -/// Cancel the newest attempts, preserving the oldest work. Cancelled work -/// stays active until its result arrives *after* scratch has been dropped. -fn cancel_to(active: &[Active], keep: usize) { - for job in active - .iter() - .filter(|j| !j.signals.cancel.load(Ordering::Relaxed)) - .skip(keep) - { - job.signals.cancel.store(true, Ordering::Relaxed); - } -} - -fn coordinate<'scope, T: Sync, F>( - scope: &rayon::Scope<'scope>, - jobs: &'scope [T], - work: &'scope F, - options: &Options, - mut read: impl FnMut() -> Option, -) -> Result<(), CompileError> -where - F: Fn(&T, &Checkpoint) -> Result<(), AttemptError> + Sync, -{ - let (sender, receiver) = mpsc::channel(); - let mut ready: VecDeque = (0..jobs.len()).collect(); - let mut deferred = VecDeque::new(); - let mut active: Vec = Vec::new(); - let mut error = None; - let mut limit = options.initial_workers.max(1).min(options.max_workers); - let mut previous = - read().ok_or_else(|| resource_error("memory telemetry unavailable"))?; - let mut sampled = Instant::now(); - let mut ramped = sampled; - let mut idle_since = None; - let mut logged = sampled; - let mut completed = 0; - let mut retries = 0; - // Apply limits before the first allocation-heavy job, not only on tick 1. - let mut state = - pressure(previous, previous, options.tick, options.process_budget); - let mut blocked = previous.headroom(options.process_budget) - < previous.reserve(options.process_budget); - let mut pressured_at = blocked.then_some(sampled); - if state != Pressure::Healthy { - limit = 1; - } - - loop { - let now = Instant::now(); - if now.duration_since(sampled) >= options.tick { - let previous_state = state; - if let Some(memory) = read() { - blocked = memory.headroom(options.process_budget) - < memory.reserve(options.process_budget) - || reclaiming(memory, previous, now.duration_since(sampled)); - state = pressure( - memory, - previous, - now.duration_since(sampled), - options.process_budget, - ); - if blocked { - pressured_at = Some(now); - } - if matches!(state, Pressure::Backoff | Pressure::Critical) { - let running = active - .iter() - .filter(|j| !j.signals.cancel.load(Ordering::Relaxed)) - .count(); - let next = - if state == Pressure::Critical { 1 } else { (running / 2).max(1) }; - limit = limit.min(next); - cancel_to(&active, limit); - // Don't mistake memory still being released by other attempts for - // the survivor's footprint. Only fail a genuinely isolated attempt. - if state == Pressure::Critical && active.len() == 1 && error.is_none() - { - active[0].signals.cancel.store(true, Ordering::Relaxed); - error = Some(resource_error(format!( - "inductive validation group #{} reached the memory safety reserve while running alone (resident+swap {:.1} GiB); increase the budget or reduce this group's working set", - active[0].id, - memory.process as f64 / GIB as f64 - ))); - } - } else if state == Pressure::Hold { - limit = 1; - } else if state == Pressure::Healthy - && pressured_at - .is_none_or(|t| now.duration_since(t) >= options.recovery) - && now.duration_since(ramped) >= options.ramp_interval - { - limit = ramp_up(limit, options.max_workers); - ramped = now; - } - if options.verbose - && (state != previous_state - || now.duration_since(logged) >= Duration::from_secs(5)) - { - eprintln!( - "[validate_memory] {state:?}: limit={limit}/{} active={} ready={} deferred={} completed={completed}/{} retries={retries} available={:.1} GiB resident+swap={:.1} GiB", - options.max_workers, - active.len(), - ready.len(), - deferred.len(), - jobs.len(), - memory.available as f64 / GIB as f64, - memory.process as f64 / GIB as f64 - ); - for job in &active { - let visits = job.signals.visits.load(Ordering::Relaxed); - if visits >= 1_000_000 { - eprintln!( - "[validate_memory] active group #{}: visits={visits} scratch_estimate_peak={:.1} MiB cancelling={}", - job.id, - job.signals.scratch_peak.load(Ordering::Relaxed) as f64 - / MIB as f64, - job.signals.cancel.load(Ordering::Relaxed) - ); - } - } - logged = now; - } - previous = memory; - } else if error.is_none() { - error = Some(resource_error( - "memory telemetry became unavailable during validation", - )); - } - sampled = now; - } - - if error.is_some() { - cancel_to(&active, 0); - } - let recovering = - pressured_at.is_some_and(|t| now.duration_since(t) < options.recovery); - let draining = - active.iter().any(|j| j.signals.cancel.load(Ordering::Relaxed)); - // Retained environments can keep headroom below Healthy after every - // attempt has drained. With a reserve and no active reclaim pressure, - // continue serially rather than waiting for retained data to disappear. - let can_admit = error.is_none() && !recovering && !draining && !blocked; - let admission_limit = if state == Pressure::Healthy { limit } else { 1 }; - if can_admit && !active.iter().any(|j| j.solo) { - while active.len() < admission_limit { - let next = if !ready.is_empty() { - ready.pop_front().map(|id| (id, false)) - } else if active.is_empty() { - deferred.pop_front().map(|id| (id, true)) - } else { - None - }; - let Some((id, solo)) = next else { - break; - }; - let signals = Arc::new(Signals::default()); - let token = Arc::clone(&signals); - let result_sender = sender.clone(); - scope.spawn(move |_| { - let completion = Completion { id, sender: Some(result_sender) }; - let checkpoint = - Checkpoint { signals: Some(token), visits: Cell::new(0) }; - let result = work(&jobs[id], &checkpoint); - drop(checkpoint); - completion.finish(result); - }); - active.push(Active { id, solo, signals }); - if solo { - break; - } - } - } - - if active.is_empty() { - if let Some(error) = error { - return Err(error); - } - if ready.is_empty() && deferred.is_empty() { - return Ok(()); - } - let start = *idle_since.get_or_insert(now); - if now.duration_since(start) >= options.idle_timeout { - return Err(resource_error(format!( - "not enough available memory to admit an inductive validation job; no progress for {:.1} seconds (headroom {:.1} GiB, reserve {:.1} GiB, pressure {state:?})", - options.idle_timeout.as_secs_f64(), - previous.headroom(options.process_budget) as f64 / GIB as f64, - previous.reserve(options.process_budget) as f64 / GIB as f64, - ))); - } - } else { - idle_since = None; - } - - if let Ok((id, result)) = - receiver.recv_timeout(options.tick.min(Duration::from_millis(50))) - { - let position = active - .iter() - .position(|j| j.id == id) - .expect("result for active validation job"); - let job = active.remove(position); - if options.verbose - && (job.solo || job.signals.visits.load(Ordering::Relaxed) >= 1_000_000) - { - eprintln!( - "[validate_memory] group #{id}: visits={} scratch_estimate_peak={:.1} MiB solo={}", - job.signals.visits.load(Ordering::Relaxed), - job.signals.scratch_peak.load(Ordering::Relaxed) as f64 / MIB as f64, - job.solo - ); - } - match result { - Ok(()) => completed += 1, - Err(AttemptError::Compile(failure)) => { - if error.is_none() { - error = Some(failure); - } - }, - Err(AttemptError::Cancelled) => { - if error.is_none() { - if job.solo { - error = Some(resource_error(format!( - "inductive validation group #{id} was cancelled while already running alone" - ))); - } else { - retries += 1; - deferred.push_back(id); - } - } - }, - } - } - } -} - -fn run_with( - jobs: &[T], - work: &F, - options: Options, - read: R, -) -> Result<(), CompileError> -where - F: Fn(&T, &Checkpoint) -> Result<(), AttemptError> + Sync, - R: FnMut() -> Option + Send, -{ - rayon::in_place_scope(|scope| { - std::thread::scope(|threads| { - let controller = - threads.spawn(|| coordinate(scope, jobs, work, &options, read)); - // A caller may itself occupy the only thread of a custom Rayon pool. - // Let it execute scoped work while the independent controller samples - // memory and sends cancellations; never block that last Rayon worker. - while !controller.is_finished() { - rayon::yield_now(); - std::thread::sleep(Duration::from_millis(1)); - } - controller.join().unwrap() - }) - }) -} - -pub(crate) fn run(jobs: &[T], work: F) -> Result<(), CompileError> -where - F: Fn(&T, &Checkpoint) -> Result<(), AttemptError> + Sync, -{ - if jobs.is_empty() { - return Ok(()); - } - let options = Options::from_env()?; - let mut reader = MemoryReader::new(); - let disabled = std::env::var("IX_VALIDATE_ADAPTIVE").as_deref() == Ok("0"); - if disabled && options.process_budget.is_some() { - return Err(resource_error( - "IX_VALIDATE_MEMORY_GIB cannot be combined with IX_VALIDATE_ADAPTIVE=0", - )); - } - if disabled || reader.read().is_none() { - if !disabled && options.process_budget.is_some() { - return Err(resource_error( - "IX_VALIDATE_MEMORY_GIB requires Linux memory telemetry", - )); - } - if options.verbose { - eprintln!( - "[validate_memory] adaptive admission disabled or Linux memory telemetry unavailable" - ); - } - use rayon::prelude::*; - return jobs.par_iter().try_for_each(|job| { - work(job, &Checkpoint::default()).map_err(AttemptError::into_compile) - }); - } - if options.verbose { - eprintln!( - "[validate_memory] adaptive admission: initial={} max={} budget_gib={:?}", - options.initial_workers, - options.max_workers, - options.process_budget.map(|b| b as f64 / GIB as f64) - ); - } - run_with(jobs, &work, options, move || reader.read()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::compile::memory::{cgroup_dirs, field, psi_total}; - use std::path::PathBuf; - use std::sync::atomic::AtomicUsize; - - fn healthy() -> Memory { - Memory { - capacity: 100 * GIB, - available: 90 * GIB, - process: 10 * GIB, - swap_used: 0, - stall_us: 0, - } - } - fn options(max_workers: usize) -> Options { - Options { - max_workers, - initial_workers: 1, - process_budget: None, - tick: Duration::from_millis(2), - ramp_interval: Duration::from_millis(5), - recovery: Duration::from_millis(5), - idle_timeout: Duration::from_millis(100), - verbose: false, - } - } - fn pool(n: usize) -> rayon::ThreadPool { - rayon::ThreadPoolBuilder::new().num_threads(n).build().unwrap() - } - fn until(mut ready: impl FnMut() -> bool) { - let start = Instant::now(); - while !ready() { - assert!( - start.elapsed() < Duration::from_secs(5), - "test made no progress" - ); - std::thread::sleep(Duration::from_millis(1)); - } - } - struct Scratch<'a> { - live: &'a AtomicUsize, - dropping: Option<&'a AtomicUsize>, - } - impl Drop for Scratch<'_> { - fn drop(&mut self) { - if let Some(dropping) = self.dropping { - dropping.fetch_add(1, Ordering::SeqCst); - std::thread::sleep(Duration::from_millis(20)); - dropping.fetch_sub(1, Ordering::SeqCst); - } - self.live.fetch_sub(1, Ordering::SeqCst); - } - } - - #[test] - fn pressure_accounts_for_resident_plus_swap_budget_and_stalls() { - let mem = healthy(); - assert_eq!( - pressure(mem, mem, Duration::from_secs(1), None), - Pressure::Healthy - ); - assert_eq!( - pressure( - Memory { available: 30 * GIB, ..mem }, - mem, - Duration::from_secs(1), - None - ), - Pressure::Hold - ); - assert_eq!( - pressure( - Memory { available: 20 * GIB, ..mem }, - mem, - Duration::from_secs(1), - None - ), - Pressure::Backoff - ); - assert_eq!( - pressure( - Memory { available: 4 * GIB, ..mem }, - mem, - Duration::from_secs(1), - None - ), - Pressure::Critical - ); - assert_eq!( - pressure( - Memory { stall_us: 110_000, ..mem }, - mem, - Duration::from_secs(1), - None - ), - Pressure::Backoff - ); - assert_eq!( - pressure( - Memory { swap_used: 9 * MIB, ..mem }, - mem, - Duration::from_secs(1), - None - ), - Pressure::Backoff - ); - assert_eq!( - pressure( - Memory { process: 96 * GIB, ..mem }, - mem, - Duration::from_secs(1), - Some(100 * GIB) - ), - Pressure::Critical - ); - // Old swap occupancy is not new pressure. - let swapped = Memory { swap_used: 90 * GIB, ..mem }; - assert_eq!( - pressure(swapped, swapped, Duration::from_secs(1), None), - Pressure::Healthy - ); - } - - #[test] - fn parses_proc_and_cgroup_namespace_ancestors() { - assert_eq!( - field("MemTotal: 123 kB\nMemAvailable: 45 kB\n", "MemAvailable:"), - Some(45) - ); - assert_eq!( - psi_total("some avg10=0 total=999\nfull avg10=0 total=123\n"), - 123 - ); - let mount = "30 20 0:25 / /sys/fs/cgroup rw - cgroup2 cgroup rw"; - assert_eq!( - cgroup_dirs("0::/a/b\n", mount), - vec![ - PathBuf::from("/sys/fs/cgroup/a/b"), - PathBuf::from("/sys/fs/cgroup/a"), - PathBuf::from("/sys/fs/cgroup"), - ] - ); - let namespaced = - "30 20 0:25 /host/container /cg\\040mount rw - cgroup2 cgroup rw"; - assert_eq!( - cgroup_dirs("0::/\n", namespaced), - vec![PathBuf::from("/cg mount")] - ); - assert_eq!( - cgroup_dirs("0::/host/container/child\n", namespaced), - vec![PathBuf::from("/cg mount/child"), PathBuf::from("/cg mount"),] - ); - assert!(cgroup_dirs("0::/../../escape\n", mount).is_empty()); - assert!(cgroup_dirs("1:memory:/old\n", mount).is_empty()); - } - - #[test] - fn healthy_admission_ramps_up_without_exceeding_pool_limit() { - let live = AtomicUsize::new(0); - let peak = AtomicUsize::new(0); - let calls: Vec<_> = (0..12).map(|_| AtomicUsize::new(0)).collect(); - pool(4) - .install(|| { - run_with( - &calls, - &|calls: &AtomicUsize, c: &Checkpoint| { - calls.fetch_add(1, Ordering::SeqCst); - let n = live.fetch_add(1, Ordering::SeqCst) + 1; - let _scratch = Scratch { live: &live, dropping: None }; - peak.fetch_max(n, Ordering::SeqCst); - std::thread::sleep(Duration::from_millis(40)); - c.visit()?; - Ok(()) - }, - options(4), - || Some(healthy()), - ) - }) - .unwrap(); - assert_eq!(peak.load(Ordering::SeqCst), 4); - assert!(calls.iter().all(|n| n.load(Ordering::SeqCst) == 1)); - assert_eq!(live.load(Ordering::SeqCst), 0); - } - - #[test] - fn retained_memory_allows_serial_progress_below_healthy_headroom() { - for headroom in [30 * GIB, 20 * GIB] { - for budget in [None, Some(100 * GIB)] { - let memory = Memory { - capacity: if budget.is_some() { 200 * GIB } else { 100 * GIB }, - available: if budget.is_some() { 100 * GIB } else { headroom }, - process: 100 * GIB - headroom, - ..healthy() - }; - let live = AtomicUsize::new(0); - let peak = AtomicUsize::new(0); - let calls: Vec<_> = (0..8).map(|_| AtomicUsize::new(0)).collect(); - let opt = - Options { initial_workers: 4, process_budget: budget, ..options(4) }; - pool(4) - .install(|| { - run_with( - &calls, - &|calls: &AtomicUsize, c: &Checkpoint| { - calls.fetch_add(1, Ordering::SeqCst); - let n = live.fetch_add(1, Ordering::SeqCst) + 1; - let _scratch = Scratch { live: &live, dropping: None }; - peak.fetch_max(n, Ordering::SeqCst); - std::thread::sleep(Duration::from_millis(10)); - c.visit()?; - Ok(()) - }, - opt, - || Some(memory), - ) - }) - .unwrap(); - assert_eq!(peak.load(Ordering::SeqCst), 1); - assert!(calls.iter().all(|n| n.load(Ordering::SeqCst) == 1)); - assert_eq!(live.load(Ordering::SeqCst), 0); - } - } - } - - #[test] - fn cancellation_drops_scratch_before_admitting_and_retries_once_alone() { - let live = AtomicUsize::new(0); - let dropping = AtomicUsize::new(0); - let cancelled = AtomicBool::new(false); - let calls: Vec<_> = (0..5).map(|_| AtomicUsize::new(0)).collect(); - let jobs: Vec<_> = (0..5).collect(); - let mut opt = options(2); - opt.initial_workers = 2; - pool(2) - .install(|| { - run_with( - &jobs, - &|id: &usize, c: &Checkpoint| { - let attempt = calls[*id].fetch_add(1, Ordering::SeqCst); - assert_eq!( - dropping.load(Ordering::SeqCst), - 0, - "admitted while cancelled scratch was dropping" - ); - let n = live.fetch_add(1, Ordering::SeqCst); - let _scratch = Scratch { - live: &live, - dropping: (*id == 1 && attempt == 0).then_some(&dropping), - }; - if *id == 1 && attempt == 0 { - until(|| { - c.signals.as_ref().unwrap().cancel.load(Ordering::Relaxed) - }); - cancelled.store(true, Ordering::SeqCst); - c.visit()?; - unreachable!(); - } - if *id == 0 { - until(|| cancelled.load(Ordering::SeqCst)); - } - if attempt > 0 { - assert_eq!(n, 0, "retry was not alone"); - } - Ok(()) - }, - opt, - || { - // Retained data keeps headroom below Healthy even after the - // cancelled attempt releases its scratch. Retries must still run. - Some( - if live.load(Ordering::SeqCst) >= 2 - || cancelled.load(Ordering::SeqCst) - { - Memory { available: 20 * GIB, ..healthy() } - } else { - healthy() - }, - ) - }, - ) - }) - .unwrap(); - assert_eq!(calls[1].load(Ordering::SeqCst), 2); - for id in [0, 2, 3, 4] { - assert_eq!(calls[id].load(Ordering::SeqCst), 1); - } - assert_eq!(live.load(Ordering::SeqCst), 0); - } - - #[test] - fn completed_work_is_not_retried_even_if_cancellation_was_requested() { - let live = AtomicUsize::new(0); - let finished = AtomicBool::new(false); - let calls = [AtomicUsize::new(0), AtomicUsize::new(0)]; - let mut opt = options(2); - opt.initial_workers = 2; - pool(2) - .install(|| { - run_with( - &[0, 1], - &|id: &usize, c: &Checkpoint| { - calls[*id].fetch_add(1, Ordering::SeqCst); - live.fetch_add(1, Ordering::SeqCst); - let _scratch = Scratch { live: &live, dropping: None }; - if *id == 1 { - until(|| { - c.signals.as_ref().unwrap().cancel.load(Ordering::Relaxed) - }); - finished.store(true, Ordering::SeqCst); - } else { - until(|| finished.load(Ordering::SeqCst)); - } - Ok(()) - }, - opt, - || { - Some(if live.load(Ordering::SeqCst) == 2 { - Memory { available: 20 * GIB, ..healthy() } - } else { - healthy() - }) - }, - ) - }) - .unwrap(); - assert!(calls.iter().all(|n| n.load(Ordering::SeqCst) == 1)); - } - - #[test] - fn oversized_solo_job_returns_resource_error_and_releases_memory() { - let live = AtomicUsize::new(0); - let calls = AtomicUsize::new(0); - let result = pool(1).install(|| { - run_with( - &[()], - &|_: &(), c: &Checkpoint| { - calls.fetch_add(1, Ordering::SeqCst); - live.fetch_add(1, Ordering::SeqCst); - let _scratch = Scratch { live: &live, dropping: Some(&calls) }; - until(|| c.signals.as_ref().unwrap().cancel.load(Ordering::Relaxed)); - c.visit()?; - unreachable!() - }, - options(1), - || { - Some(if live.load(Ordering::SeqCst) == 1 { - Memory { available: GIB, process: 99 * GIB, ..healthy() } - } else { - healthy() - }) - }, - ) - }); - assert!(matches!(result, Err(CompileError::ResourceLimit { .. }))); - assert_eq!(live.load(Ordering::SeqCst), 0); - assert_eq!(calls.load(Ordering::SeqCst), 1); - } - - #[test] - fn refuses_to_launch_without_headroom_and_does_not_wait_forever() { - let calls = AtomicUsize::new(0); - let result = pool(1).install(|| { - run_with( - &[()], - &|_: &(), _: &Checkpoint| { - calls.fetch_add(1, Ordering::SeqCst); - Ok(()) - }, - options(1), - || Some(Memory { available: GIB, ..healthy() }), - ) - }); - assert!(matches!(result, Err(CompileError::ResourceLimit { .. }))); - assert_eq!(calls.load(Ordering::SeqCst), 0); - } - - #[test] - fn serial_admission_preserves_the_reserve_and_waits_out_reclaim_pressure() { - for reclaiming in [false, true] { - let calls = AtomicUsize::new(0); - let mut reads = 0; - let result = pool(1).install(|| { - run_with( - &[()], - &|_: &(), _: &Checkpoint| { - calls.fetch_add(1, Ordering::SeqCst); - Ok(()) - }, - options(1), - || { - reads += 1; - Some(Memory { - // Start below the admission reserve but above Critical. - // Later samples can have room for serial work but must not - // admit it while the system continues swapping. - available: if reclaiming && reads > 1 { - 20 * GIB - } else { - 8 * GIB - }, - swap_used: if reclaiming { reads * 9 * MIB } else { 0 }, - ..healthy() - }) - }, - ) - }); - assert!(matches!(result, Err(CompileError::ResourceLimit { .. }))); - assert_eq!(calls.load(Ordering::SeqCst), 0); - } - } - - #[test] - fn unavailable_telemetry_cancels_and_drains_active_work() { - let live = AtomicUsize::new(0); - let result = pool(1).install(|| { - run_with( - &[()], - &|_: &(), c: &Checkpoint| { - live.fetch_add(1, Ordering::SeqCst); - let _scratch = Scratch { live: &live, dropping: None }; - until(|| c.signals.as_ref().unwrap().cancel.load(Ordering::Relaxed)); - c.visit()?; - unreachable!() - }, - options(1), - || { - if live.load(Ordering::SeqCst) == 0 { Some(healthy()) } else { None } - }, - ) - }); - assert!(matches!(result, Err(CompileError::ResourceLimit { .. }))); - assert_eq!(live.load(Ordering::SeqCst), 0); - } - - #[test] - fn compile_errors_are_preserved_and_other_jobs_are_drained() { - let live = AtomicUsize::new(0); - let failure = - CompileError::InvalidMutualBlock { reason: "test mismatch".into() }; - let mut opt = options(2); - opt.initial_workers = 2; - let result = pool(2).install(|| { - run_with( - &[0, 1], - &|id: &usize, c: &Checkpoint| { - live.fetch_add(1, Ordering::SeqCst); - let _scratch = Scratch { live: &live, dropping: None }; - if *id == 0 { - until(|| live.load(Ordering::SeqCst) == 2); - Err(failure.clone().into()) - } else { - until(|| { - c.signals.as_ref().unwrap().cancel.load(Ordering::Relaxed) - }); - c.visit()?; - unreachable!() - } - }, - opt, - || Some(healthy()), - ) - }); - assert_eq!(result, Err(failure)); - assert_eq!(live.load(Ordering::SeqCst), 0); - } - - #[test] - fn custom_single_worker_pool_can_make_forward_progress() { - let calls = AtomicUsize::new(0); - pool(1) - .install(|| { - run_with( - &[0, 1, 2], - &|_: &usize, c: &Checkpoint| { - c.visit()?; - calls.fetch_add(1, Ordering::SeqCst); - Ok(()) - }, - options(1), - || Some(healthy()), - ) - }) - .unwrap(); - assert_eq!(calls.load(Ordering::SeqCst), 3); - } -} diff --git a/crates/compile/src/condense.rs b/crates/compile/src/condense.rs deleted file mode 100644 index f293aaa04..000000000 --- a/crates/compile/src/condense.rs +++ /dev/null @@ -1,246 +0,0 @@ -//! Computes strongly connected components (SCCs) using iterative Tarjan's algorithm. -//! -//! Produces a condensation of the reference graph: each SCC becomes a single block. -//! Used to identify mutual definition groups for the compilation pipeline. - -use rustc_hash::FxHashMap; - -use ix_common::FxIndexSet; -use ix_common::env::Name; - -use crate::graph::{NameSet, RefMap}; - -/// The condensation of a reference graph into strongly connected components. -pub struct CondensedBlocks { - /// Maps each name to the representative (low-link root) of its SCC. - pub low_links: FxHashMap, - /// Maps each SCC representative to the set of names in that component. - pub blocks: RefMap, - /// Maps each SCC representative to the set of names referenced outside its component. - pub block_refs: RefMap, -} - -/// Compute strongly connected components using an iterative Tarjan’s algorithm. -/// Returns a map from each node to the set of nodes in its SCC. -pub fn compute_sccs(refs: &RefMap) -> CondensedBlocks { - fn neighbors(refs: &RefMap, n: &Name) -> Vec { - refs.get(n).unwrap().iter().cloned().collect() - } - - struct Frame { - node: Name, - neighbors: Vec, - /// Index of the next neighbor to visit. - idx: usize, - } - - let mut index = FxHashMap::default(); - let mut lowlink = FxHashMap::default(); - let mut stack = FxIndexSet::default(); - let mut next_index = 0usize; - - let mut blocks = RefMap::default(); - let mut block_low_links = FxHashMap::default(); - let mut block_refs = RefMap::default(); - - let mut work = Vec::::new(); - - for start in refs.keys() { - if index.contains_key(start) { - continue; - } - - work.push(Frame { - node: start.clone(), - neighbors: neighbors(refs, start), - idx: 0, - }); - - while let Some(frame) = work.last_mut() { - let v = &frame.node; - - if !index.contains_key(v) { - index.insert(v.clone(), next_index); - lowlink.insert(v.clone(), next_index); - next_index += 1; - stack.insert(v.clone()); - } - - if frame.idx < frame.neighbors.len() { - let w = frame.neighbors[frame.idx].clone(); - frame.idx += 1; - - if !index.contains_key(&w) { - // Explore new neighbor. - work.push(Frame { neighbors: neighbors(refs, &w), node: w, idx: 0 }); - } else if stack.contains(&w) { - // w is on stack. - let low_v = *lowlink.get(v).unwrap(); - let idx_w = *index.get(&w).unwrap(); - lowlink.insert(v.clone(), low_v.min(idx_w)); - } - } else { - // All neighbors processed. Pop and process. - let frame = work.pop().unwrap(); - let v = frame.node; - - // Update parent's lowlink. - if let Some(parent) = work.last_mut() { - let low_parent = *lowlink.get(&parent.node).unwrap(); - let low_v = *lowlink.get(&v).unwrap(); - lowlink.insert(parent.node.clone(), low_parent.min(low_v)); - } - - // Root of SCC? - if lowlink[&v] == index[&v] { - let mut component = NameSet::default(); - - // Pop nodes from stack until we reach v. - while let Some(node) = stack.pop() { - component.insert(node.clone()); - if node == v { - break; - } - } - blocks.insert(v.clone(), component.clone()); - - // Insert all nodes in the component directly into the result. - let mut all_refs = NameSet::default(); - for node in &component { - block_low_links.insert(node.clone(), v.clone()); - for r in refs.get(node).unwrap() { - if !component.contains(r) && refs.contains_key(r) { - all_refs.insert(r.clone()); - } - } - } - block_refs.insert(v.clone(), all_refs); - } - } - } - } - CondensedBlocks { blocks, low_links: block_low_links, block_refs } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::slice; - - fn n(s: &str) -> Name { - Name::str(Name::anon(), s.to_string()) - } - - fn map_of(pairs: &[(&Name, &[Name])]) -> RefMap { - let mut map = RefMap::default(); - for (k, vs) in pairs { - map.insert((*k).clone(), vs.iter().cloned().collect()); - } - map - } - - fn scc_to_vec(map: &CondensedBlocks) -> Vec> { - let mut seen = FxIndexSet::default(); - let mut sccs = Vec::new(); - for (k, lo) in map.low_links.iter() { - let set = map.blocks.get(lo).unwrap(); - if !seen.contains(k) { - let mut names: Vec<_> = set.iter().cloned().collect(); - names.sort(); - sccs.push(names); - seen.extend(set.iter().cloned()); - } - } - sccs.sort(); - sccs - } - - #[test] - fn single_node() { - let a = n("A"); - let g = map_of(&[(&a, &[])]); - let sccs = compute_sccs(&g); - assert_eq!(scc_to_vec(&sccs), vec![vec![n("A")]]); - } - - #[test] - fn simple_cycle() { - let a = n("A"); - let b = n("B"); - let g = map_of(&[(&a, slice::from_ref(&b)), (&b, slice::from_ref(&a))]); - let sccs = compute_sccs(&g); - assert_eq!(scc_to_vec(&sccs), vec![vec![n("A"), n("B")]]); - } - - #[test] - fn chain_no_cycle() { - let a = n("A"); - let b = n("B"); - let c = n("C"); - let g = map_of(&[ - (&a, slice::from_ref(&b)), - (&b, slice::from_ref(&c)), - (&c, &[]), - ]); - let sccs = compute_sccs(&g); - let mut res = vec![vec![n("A")], vec![n("B")], vec![n("C")]]; - res.sort(); - assert_eq!(scc_to_vec(&sccs), res); - } - - #[test] - fn two_cycles_connected() { - let a = n("A"); - let b = n("B"); - let c = n("C"); - let d = n("D"); - let g = map_of(&[ - (&a, slice::from_ref(&b)), - (&b, &[a.clone(), c.clone()]), - (&c, slice::from_ref(&d)), - (&d, slice::from_ref(&c)), - ]); - let sccs = compute_sccs(&g); - let mut res = vec![vec![n("A"), n("B")], vec![n("C"), n("D")]]; - for scc in &mut res { - scc.sort(); - } - res.sort(); - assert_eq!(scc_to_vec(&sccs), res); - } - - #[test] - fn complex_graph() { - let a = n("A"); - let b = n("B"); - let c = n("C"); - let d = n("D"); - let e = n("E"); - let f = n("F"); - let g = n("G"); - let h = n("H"); - - let graph = map_of(&[ - (&a, slice::from_ref(&b)), - (&b, &[c.clone(), e.clone(), f.clone()]), - (&c, &[d.clone(), g.clone()]), - (&d, &[c.clone(), h.clone()]), - (&e, &[a.clone(), f.clone()]), - (&f, slice::from_ref(&g)), - (&g, slice::from_ref(&f)), - (&h, &[d.clone(), g.clone()]), - ]); - - let sccs = compute_sccs(&graph); - let mut res = vec![ - vec![n("A"), n("B"), n("E")], - vec![n("C"), n("D"), n("H")], - vec![n("F"), n("G")], - ]; - for scc in &mut res { - scc.sort(); - } - res.sort(); - assert_eq!(scc_to_vec(&sccs), res,); - } -} diff --git a/crates/compile/src/decompile.rs b/crates/compile/src/decompile.rs deleted file mode 100644 index b3c0d38b6..000000000 --- a/crates/compile/src/decompile.rs +++ /dev/null @@ -1,2723 +0,0 @@ -//! Decompilation from Ixon format back to Lean environment. -//! -//! This module decompiles alpha-invariant Ixon representations back to -//! Lean constants, expanding Share references and reattaching metadata. - -#![allow(clippy::cast_possible_truncation)] -#![allow(clippy::cast_precision_loss)] -#![allow(clippy::cast_possible_wrap)] -#![allow(clippy::map_err_ignore)] -#![allow(clippy::match_same_arms)] - -use bignat::Nat; - -use ix_common::address::Address; -use ix_common::env::{ - AxiomVal, BinderInfo, ConstantInfo as LeanConstantInfo, ConstantVal, - ConstructorVal, DataValue as LeanDataValue, DefinitionSafety, DefinitionVal, - Env as LeanEnv, Expr as LeanExpr, InductiveVal, Int, Level, Literal, Name, - OpaqueVal, QuotVal, RecursorRule as LeanRecursorRule, RecursorVal, - ReducibilityHints, SourceInfo, Substring, Syntax, SyntaxPreresolved, - TheoremVal, -}; - -use ixon::{ - DecompileError, TagN, - constant::{ - Axiom, Constant, ConstantInfo, Constructor, DefKind, Definition, Inductive, - MutConst, Quotient, Recursor, - }, - env::Named, - expr::Expr, - metadata::{ - ConstantMeta, ConstantMetaInfo, DataValue, ExprMeta, ExprMetaData, KVMap, - }, - univ::Univ, -}; - -use crate::{ - compile::CompileState, - compile::ind_flags::compute_lean_ind_flags, - diag::rss_log_suffix, - mutual::{MutCtx, all_to_ctx}, -}; -use dashmap::DashMap; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use rustc_hash::{FxHashMap, FxHashSet}; -use std::sync::Arc; - -#[derive(Default, Debug)] -pub struct DecompileState { - /// Decompiled environment - pub env: DashMap, - /// Canonical expression nodes keyed by content hash. Decompilation - /// rebuilds every constant's expressions with sharing local to that - /// constant, so structurally equal subterms (common types, applied - /// prefixes) would otherwise each hold a private copy per referencing - /// constant — a multiple of the whole env's footprint. Constants - /// entering `env` are canonicalized through this table - /// ([`Self::insert_interned`]); `decompile_env` drops the table - /// before returning, since `env` keeps the canonical `Arc`s alive. - expr_intern: DashMap<[u8; 32], LeanExpr>, -} - -#[derive(Debug)] -pub struct DecompileStateStats { - pub env: usize, -} - -impl DecompileState { - pub fn stats(&self) -> DecompileStateStats { - DecompileStateStats { env: self.env.len() } - } - - /// Insert a decompiled constant, canonicalizing its expressions - /// through [`Self::expr_intern`]. - fn insert_interned(&self, name: Name, ci: LeanConstantInfo) { - self.env.insert(name, self.intern_ci(ci)); - } - - /// Canonicalize every expression field of `ci` — see - /// [`Self::expr_intern`]. - fn intern_ci(&self, mut ci: LeanConstantInfo) -> LeanConstantInfo { - match &mut ci { - LeanConstantInfo::AxiomInfo(v) => { - v.cnst.typ = self.intern_expr(&v.cnst.typ); - }, - LeanConstantInfo::DefnInfo(v) => { - v.cnst.typ = self.intern_expr(&v.cnst.typ); - v.value = self.intern_expr(&v.value); - }, - LeanConstantInfo::ThmInfo(v) => { - v.cnst.typ = self.intern_expr(&v.cnst.typ); - v.value = self.intern_expr(&v.value); - }, - LeanConstantInfo::OpaqueInfo(v) => { - v.cnst.typ = self.intern_expr(&v.cnst.typ); - v.value = self.intern_expr(&v.value); - }, - LeanConstantInfo::QuotInfo(v) => { - v.cnst.typ = self.intern_expr(&v.cnst.typ); - }, - LeanConstantInfo::InductInfo(v) => { - v.cnst.typ = self.intern_expr(&v.cnst.typ); - }, - LeanConstantInfo::CtorInfo(v) => { - v.cnst.typ = self.intern_expr(&v.cnst.typ); - }, - LeanConstantInfo::RecInfo(v) => { - v.cnst.typ = self.intern_expr(&v.cnst.typ); - for rule in &mut v.rules { - rule.rhs = self.intern_expr(&rule.rhs); - } - }, - } - ci - } - - /// Canonicalize one expression DAG bottom-up through - /// [`Self::expr_intern`]. Iterative — proof terms nest far deeper - /// than the stack allows for recursion. Nodes are rebuilt only when - /// a child changed; hashes are content-derived from child hashes, so - /// a rebuild with content-equal children reuses the stored hash. - fn intern_expr(&self, root: &LeanExpr) -> LeanExpr { - use ix_common::env::ExprData as ED; - - if let Some(hit) = self.expr_intern.get(root.get_hash().as_bytes()) { - return hit.clone(); - } - - // Per-walk memo keyed by node pointer: within-constant sharing - // makes the same `Arc` reachable from several parents. - let mut memo: FxHashMap<*const ED, LeanExpr> = FxHashMap::default(); - // (node, children_done) - let mut stack: Vec<(LeanExpr, bool)> = vec![(root.clone(), false)]; - - while let Some((e, children_done)) = stack.pop() { - let key: *const ED = Arc::as_ptr(&e.0); - if memo.contains_key(&key) { - continue; - } - if !children_done { - if let Some(hit) = self.expr_intern.get(e.get_hash().as_bytes()) { - memo.insert(key, hit.clone()); - continue; - } - stack.push((e.clone(), true)); - match e.as_data() { - ED::App(f, a, _) => { - stack.push((f.clone(), false)); - stack.push((a.clone(), false)); - }, - ED::Lam(_, t, b, _, _) | ED::ForallE(_, t, b, _, _) => { - stack.push((t.clone(), false)); - stack.push((b.clone(), false)); - }, - ED::LetE(_, t, v, b, _, _) => { - stack.push((t.clone(), false)); - stack.push((v.clone(), false)); - stack.push((b.clone(), false)); - }, - ED::Mdata(_, inner, _) => stack.push((inner.clone(), false)), - ED::Proj(_, _, s, _) => stack.push((s.clone(), false)), - ED::Bvar(..) - | ED::Fvar(..) - | ED::Mvar(..) - | ED::Sort(..) - | ED::Const(..) - | ED::Lit(..) => {}, - } - continue; - } - - // Children are canonicalized (they were pushed after this node's - // revisit frame, so they popped first). - let child = |c: &LeanExpr| -> LeanExpr { - memo - .get(&Arc::as_ptr(&c.0)) - .expect("intern walk: child not canonicalized before parent") - .clone() - }; - let same = - |c: &LeanExpr, i: &LeanExpr| -> bool { Arc::ptr_eq(&c.0, &i.0) }; - let rebuilt = match e.as_data() { - ED::App(f, a, h) => { - let (fi, ai) = (child(f), child(a)); - if same(f, &fi) && same(a, &ai) { - e.clone() - } else { - LeanExpr(Arc::new(ED::App(fi, ai, *h))) - } - }, - ED::Lam(n, t, b, bi, h) => { - let (ti, bdi) = (child(t), child(b)); - if same(t, &ti) && same(b, &bdi) { - e.clone() - } else { - LeanExpr(Arc::new(ED::Lam(n.clone(), ti, bdi, bi.clone(), *h))) - } - }, - ED::ForallE(n, t, b, bi, h) => { - let (ti, bdi) = (child(t), child(b)); - if same(t, &ti) && same(b, &bdi) { - e.clone() - } else { - LeanExpr(Arc::new(ED::ForallE(n.clone(), ti, bdi, bi.clone(), *h))) - } - }, - ED::LetE(n, t, v, b, nd, h) => { - let (ti, vi, bdi) = (child(t), child(v), child(b)); - if same(t, &ti) && same(v, &vi) && same(b, &bdi) { - e.clone() - } else { - LeanExpr(Arc::new(ED::LetE(n.clone(), ti, vi, bdi, *nd, *h))) - } - }, - ED::Mdata(kv, inner, h) => { - let ii = child(inner); - if same(inner, &ii) { - e.clone() - } else { - LeanExpr(Arc::new(ED::Mdata(kv.clone(), ii, *h))) - } - }, - ED::Proj(n, i, s, h) => { - let si = child(s); - if same(s, &si) { - e.clone() - } else { - LeanExpr(Arc::new(ED::Proj(n.clone(), i.clone(), si, *h))) - } - }, - ED::Bvar(..) - | ED::Fvar(..) - | ED::Mvar(..) - | ED::Sort(..) - | ED::Const(..) - | ED::Lit(..) => e.clone(), - }; - let canonical = self - .expr_intern - .entry(*e.get_hash().as_bytes()) - .or_insert(rebuilt) - .clone(); - memo.insert(key, canonical); - } - - memo - .remove(&Arc::as_ptr(&root.0)) - .expect("intern walk: root not canonicalized") - } -} - -/// Per-block decompilation cache. -/// -/// Index-space invariants (see `load_meta_extensions` for details): -/// - `sharing` holds the block `Constant.sharing` table and is the target -/// of `Expr::Share(idx)` lookups produced by whole-block sharing -/// analysis (`apply_sharing_to_*`). These indices start at 0 and are -/// block-wide. -/// - `meta_sharing` holds the per-constant `ConstantMeta.meta_sharing` -/// table. Its indices also start at 0 but live in a SEPARATE namespace -/// from the block sharing, reset per constant (see [`ShareScope`]). -#[derive(Default, Debug)] -pub struct BlockCache { - /// Mutual context for resolving Rec references - pub ctx: MutCtx, - /// Block-level sharing table: target of `Expr::Share(idx)` in - /// the block body exprs. Initialized from - /// `Constant.sharing`. - pub sharing: Vec>, - /// Per-constant metadata sharing table. Populated by - /// `load_meta_extensions` from `ConstantMeta.meta_sharing`. `Share` nodes - /// inside these expressions are read in the extended index space - /// (`ShareScope::Meta`). - pub meta_sharing: Vec>, - /// Reference table for resolving Ref indices to addresses - pub refs: Vec
, - /// Universe table for resolving universe indices - pub univ_table: Vec>, - /// Level-spelling patches of the current constant, keyed by metadata - /// arena index (canonicity §10.6). A patched `sort`/`const`/`rec` - /// occurrence resolves the patch's - /// virtual univ indices — through `univ_table`, which - /// `load_meta_extensions` extends with `meta_univs` — instead of the - /// node's own canonical indices. Absent patch → the canonical - /// spelling from the primary table (the D4 foreign-artifact - /// semantics). Populated by `load_meta_extensions` from - /// `ConstantMeta.univ_patches`. - pub univ_patches: FxHashMap>, - /// Length of the PRIMARY univ table before `load_meta_extensions` - /// appended the constant's `meta_univs`. Virtual indices are minted - /// against the primary length, so nested per-constant extensions (the - /// constructor window inside an inductive) must reinstall at this - /// offset — not at the currently-extended length. `None` until - /// extensions are loaded (then the full table IS primary). - pub primary_univ_len: Option, - /// Cache for decompiled universes - pub univ_cache: FxHashMap<*const Univ, Level>, - /// Cache for decompiled expressions keyed by (Ixon pointer, arena index, - /// share scope). - /// Same Ixon expression at same arena index → same metadata → same result. - /// Same Ixon expression at different arena index → different metadata → different cache key. - /// The scope is part of the key because an expression valid in a - /// metadata scope may be invalid in the primary scope. - pub expr_cache: FxHashMap<(*const Expr, u64, ShareScope), LeanExpr>, - /// Current constant being decompiled (for error messages) - pub current_const: String, -} - -impl BlockCache { - /// Install per-constant metadata extension tables. - /// - /// - `meta_sharing` → dedicated `self.meta_sharing` (separate from the - /// block sharing, see struct docs). Overwrites any previous - /// per-constant table so the cache can be reused across constants - /// within a projection-bearing block. - /// - `meta_refs` extends the primary refs table when present (the - /// documented virtual-address contract). - /// - `meta_univs` extends the primary univ table: the stage-2 compiler - /// stores original (non-canonical) level spellings here, referenced - /// by `univ_patches` in the virtual index space `univs ++ - /// meta_univs` (canonicity §10.6). - /// - `univ_patches` → per-constant arena-index → original-spelling- - /// indices map (overwrite semantics, like `meta_sharing`). - /// - /// Call at most once per cache, immediately after the primary tables - /// are installed — `primary_univ_len` is captured here, and - /// `meta_sharing` is checked against the primary `sharing` length - /// (`validate_meta_sharing`): a malformed table is rejected here even - /// where decompilation never reads it. - pub fn load_meta_extensions( - &mut self, - meta: &ConstantMeta, - ) -> Result<(), DecompileError> { - validate_meta_sharing( - self.sharing.len(), - &meta.meta_sharing, - &self.current_const, - )?; - self.meta_sharing = meta.meta_sharing.clone(); - self.refs.extend(meta.meta_refs.iter().cloned()); - self.primary_univ_len = Some(self.univ_table.len()); - self.univ_table.extend(meta.meta_univs.iter().cloned()); - self.univ_patches = meta - .univ_patches - .iter() - .map(|p| (p.arena_idx, p.univ_idxs.clone())) - .collect(); - Ok(()) - } - - /// Resolve `Share(idx)` read in `scope` (see [`ShareScope`]): the target - /// expression and the scope its own `Share` nodes are read in. Mirrors - /// Lean `Ix.DecompileM.resolveShareIn`. - pub fn resolve_share( - &self, - scope: ShareScope, - idx: u64, - ) -> Result<(Arc, ShareScope), DecompileError> { - let p = self.sharing.len(); - let i = usize::try_from(idx).ok(); - if let Some(e) = i.and_then(|i| self.sharing.get(i)) { - // `i < p`: a primary entry in either scope; its own shares are - // primary again. - return Ok((e.clone(), ShareScope::Primary)); - } - match scope { - ShareScope::Primary => Err(DecompileError::InvalidShareIndex { - idx, - max: p, - constant: self.current_const.clone(), - }), - ShareScope::Meta { entry } => { - // `i >= p` here; only `p <= i < p + entry` (an EARLIER metadata - // entry) is valid. - let target = i - .map(|i| i - p) - .filter(|m| *m < entry) - .and_then(|m| self.meta_sharing.get(m).map(|e| (m, e))); - match target { - Some((m, e)) => Ok((e.clone(), ShareScope::Meta { entry: m })), - None => Err(DecompileError::InvalidMetaShareIndex { - idx, - entry: entry as u64, - primary_len: p, - meta_len: self.meta_sharing.len(), - constant: self.current_const.clone(), - }), - } - }, - } - } -} - -/// The index space a `Share(i)` node is read in: the extended index space -/// of `ConstantMeta::meta_sharing`. With `p = sharing.len()` primary and -/// `q = meta_sharing.len()` metadata entries: -/// -/// - `Primary`: a primary expression (a root or a primary table entry). -/// `Share(i)` is `sharing[i]`; `i >= p` is `InvalidShareIndex`. Metadata -/// never changes how a primary expression decodes. -/// - `Meta { entry: j }`: an expression of `meta_sharing[j]`. `Share(i)` is -/// primary entry `i` when `i < p` (read in `Primary`: shares nested in a -/// primary entry stay primary) and `meta_sharing[i - p]` when -/// `p <= i < p + j` (read in `Meta { entry: i - p }`). Any other index is -/// `InvalidMetaShareIndex`: `i >= p + q` is out of range, -/// `p + j <= i < p + q` a forward or self reference. The entry strictly -/// decreases along metadata-to-metadata resolution, so expansion is well -/// founded. -/// -/// Mirrors Lean `Ix.DecompileM.ShareScope`. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum ShareScope { - Primary, - Meta { entry: usize }, -} - -/// Well-foundedness of a `meta_sharing` table against `primary_len` -/// primary entries: every `Share(i)` occurring in `meta_sharing[j]` must -/// have `i < primary_len + j` (a primary entry, or a metadata entry BEFORE -/// `j`). Reports the first violation in entry order, then in left-to-right -/// pre-order, as `InvalidMetaShareIndex` (Lean `validateMetaSharing` walks -/// in the same order). Iterative; `Share` nodes are not expanded. -/// -/// Each shared node (`Arc`) is visited once across the whole table, so the -/// walk is linear in the DAG size: in-memory tables share subterms through -/// the compiler's expression memo, and a tree walk is exponential in the -/// depth of such sharing. Skipping a node seen before keeps the first -/// violation: its whole subterm passed then, under a bound no larger than -/// the current one (the bound grows with the entry). -pub fn validate_meta_sharing( - primary_len: usize, - meta_sharing: &[Arc], - constant: &str, -) -> Result<(), DecompileError> { - let mut stack: Vec<&Arc> = Vec::new(); - let mut seen: FxHashSet<*const Expr> = FxHashSet::default(); - for (j, root) in meta_sharing.iter().enumerate() { - let bound = primary_len.saturating_add(j); - stack.push(root); - while let Some(e) = stack.pop() { - if !seen.insert(Arc::as_ptr(e)) { - continue; - } - if let Expr::Share(i) = e.as_ref() { - if !matches!(usize::try_from(*i), Ok(i) if i < bound) { - return Err(DecompileError::InvalidMetaShareIndex { - idx: *i, - entry: j as u64, - primary_len, - meta_len: meta_sharing.len(), - constant: constant.to_string(), - }); - } - } else { - stack.extend(e.children().into_iter().rev()); - } - } - } - Ok(()) -} - -// =========================================================================== -// Blob reading utilities -// =========================================================================== - -/// Read raw bytes from the blob store. -fn read_blob( - addr: &Address, - stt: &CompileState, -) -> Result, DecompileError> { - stt.env.get_blob(addr).ok_or(DecompileError::BlobNotFound(addr.clone())) -} - -/// Read a Nat from the blob store. -fn read_nat(addr: &Address, stt: &CompileState) -> Result { - let bytes = read_blob(addr, stt)?; - Ok(Nat::from_le_bytes(&bytes)) -} - -/// Read a string from the blob store. -fn read_string( - addr: &Address, - stt: &CompileState, -) -> Result { - let bytes = read_blob(addr, stt)?; - String::from_utf8(bytes).map_err(|_| DecompileError::BadBlobFormat { - addr: addr.clone(), - expected: "UTF-8 string".into(), - }) -} - -/// Read a Constant from the const store, materializing the lazy entry. -fn read_const( - addr: &Address, - stt: &CompileState, -) -> Result, DecompileError> { - stt.env.get_const(addr).ok_or(DecompileError::MissingAddress(addr.clone())) -} - -// =========================================================================== -// DataValue and KVMap decompilation -// =========================================================================== - -/// Decompile an Ixon DataValue (Address-based) to a Lean DataValue. -fn decompile_data_value( - dv: &DataValue, - stt: &CompileState, -) -> Result { - match dv { - DataValue::OfString(addr) => { - let s = read_string(addr, stt)?; - Ok(LeanDataValue::OfString(s)) - }, - DataValue::OfBool(b) => Ok(LeanDataValue::OfBool(*b)), - DataValue::OfName(addr) => { - let name = decompile_name(addr, stt)?; - Ok(LeanDataValue::OfName(name)) - }, - DataValue::OfNat(addr) => { - let n = read_nat(addr, stt)?; - Ok(LeanDataValue::OfNat(n)) - }, - DataValue::OfInt(addr) => { - let bytes = read_blob(addr, stt)?; - let int = deserialize_int(&bytes)?; - Ok(LeanDataValue::OfInt(int)) - }, - DataValue::OfSyntax(addr) => { - let bytes = read_blob(addr, stt)?; - let syntax = deserialize_syntax(&bytes, stt)?; - Ok(LeanDataValue::OfSyntax(Box::new(syntax))) - }, - } -} - -/// Deserialize an Int from bytes (mirrors compile-side serialization). -fn deserialize_int(bytes: &[u8]) -> Result { - if bytes.is_empty() { - return Err(DecompileError::BadConstantFormat { - msg: "deserialize_int: empty".into(), - }); - } - match bytes[0] { - 0 => Ok(Int::OfNat(Nat::from_le_bytes(&bytes[1..]))), - 1 => Ok(Int::NegSucc(Nat::from_le_bytes(&bytes[1..]))), - _ => Err(DecompileError::BadConstantFormat { - msg: "deserialize_int: invalid tag".into(), - }), - } -} - -/// Read a TagN (f = 0) u64 from a byte slice, advancing the cursor. -fn read_tagn0(buf: &mut &[u8]) -> Result { - TagN::get(0, buf).map(|t| t.value).map_err(|e| { - DecompileError::BadConstantFormat { msg: format!("read_tagn0: {e}") } - }) -} - -/// Read exactly 32 bytes (Address) from a byte slice, advancing the cursor. -fn read_addr_bytes(buf: &mut &[u8]) -> Result { - if buf.len() < 32 { - return Err(DecompileError::BadConstantFormat { - msg: "read_addr: need 32 bytes".into(), - }); - } - let (bytes, rest) = buf.split_at(32); - *buf = rest; - Address::from_slice(bytes).map_err(|_| DecompileError::BadConstantFormat { - msg: "read_addr: invalid".into(), - }) -} - -/// Deserialize a Substring from bytes. -fn deserialize_substring( - buf: &mut &[u8], - stt: &CompileState, -) -> Result { - let str_addr = read_addr_bytes(buf)?; - let s = read_string(&str_addr, stt)?; - let start_pos = Nat::from(read_tagn0(buf)?); - let stop_pos = Nat::from(read_tagn0(buf)?); - Ok(Substring { str: s, start_pos, stop_pos }) -} - -/// Deserialize SourceInfo from bytes. -fn deserialize_source_info( - buf: &mut &[u8], - stt: &CompileState, -) -> Result { - if buf.is_empty() { - return Err(DecompileError::BadConstantFormat { - msg: "source_info: empty".into(), - }); - } - let tag = buf[0]; - *buf = &buf[1..]; - match tag { - 0 => { - let leading = deserialize_substring(buf, stt)?; - let leading_pos = Nat::from(read_tagn0(buf)?); - let trailing = deserialize_substring(buf, stt)?; - let trailing_pos = Nat::from(read_tagn0(buf)?); - Ok(SourceInfo::Original(leading, leading_pos, trailing, trailing_pos)) - }, - 1 => { - let start = Nat::from(read_tagn0(buf)?); - let end = Nat::from(read_tagn0(buf)?); - if buf.is_empty() { - return Err(DecompileError::BadConstantFormat { - msg: "source_info: missing canonical".into(), - }); - } - let canonical = buf[0] != 0; - *buf = &buf[1..]; - Ok(SourceInfo::Synthetic(start, end, canonical)) - }, - 2 => Ok(SourceInfo::None), - _ => Err(DecompileError::BadConstantFormat { - msg: "source_info: invalid tag".into(), - }), - } -} - -/// Deserialize a SyntaxPreresolved from bytes. -fn deserialize_preresolved( - buf: &mut &[u8], - stt: &CompileState, -) -> Result { - if buf.is_empty() { - return Err(DecompileError::BadConstantFormat { - msg: "preresolved: empty".into(), - }); - } - let tag = buf[0]; - *buf = &buf[1..]; - match tag { - 0 => { - let name_addr = read_addr_bytes(buf)?; - let name = decompile_name(&name_addr, stt)?; - Ok(SyntaxPreresolved::Namespace(name)) - }, - 1 => { - let name_addr = read_addr_bytes(buf)?; - let name = decompile_name(&name_addr, stt)?; - let count = read_tagn0(buf)? as usize; - let mut fields = Vec::with_capacity(count.min(buf.len())); - for _ in 0..count { - let field_addr = read_addr_bytes(buf)?; - let field = read_string(&field_addr, stt)?; - fields.push(field); - } - Ok(SyntaxPreresolved::Decl(name, fields)) - }, - _ => Err(DecompileError::BadConstantFormat { - msg: "preresolved: invalid tag".into(), - }), - } -} - -/// Deserialize a Syntax from bytes (mirrors compile-side serialize_syntax). -fn deserialize_syntax( - bytes: &[u8], - stt: &CompileState, -) -> Result { - let mut buf = bytes; - deserialize_syntax_inner(&mut buf, stt) -} - -/// Recursive inner deserializer for Syntax. -fn deserialize_syntax_inner( - buf: &mut &[u8], - stt: &CompileState, -) -> Result { - if buf.is_empty() { - return Err(DecompileError::BadConstantFormat { - msg: "syntax: empty".into(), - }); - } - let tag = buf[0]; - *buf = &buf[1..]; - match tag { - 0 => Ok(Syntax::Missing), - 1 => { - let info = deserialize_source_info(buf, stt)?; - let kind_addr = read_addr_bytes(buf)?; - let kind = decompile_name(&kind_addr, stt)?; - let arg_count = read_tagn0(buf)? as usize; - let mut args = Vec::with_capacity(arg_count.min(buf.len())); - for _ in 0..arg_count { - args.push(deserialize_syntax_inner(buf, stt)?); - } - Ok(Syntax::Node(info, kind, args)) - }, - 2 => { - let info = deserialize_source_info(buf, stt)?; - let val_addr = read_addr_bytes(buf)?; - let val = read_string(&val_addr, stt)?; - Ok(Syntax::Atom(info, val)) - }, - 3 => { - let info = deserialize_source_info(buf, stt)?; - let raw_val = deserialize_substring(buf, stt)?; - let val_addr = read_addr_bytes(buf)?; - let val = decompile_name(&val_addr, stt)?; - let pr_count = read_tagn0(buf)? as usize; - let mut preresolved = Vec::with_capacity(pr_count.min(buf.len())); - for _ in 0..pr_count { - preresolved.push(deserialize_preresolved(buf, stt)?); - } - Ok(Syntax::Ident(info, raw_val, val, preresolved)) - }, - _ => Err(DecompileError::BadConstantFormat { - msg: "syntax: invalid tag".into(), - }), - } -} - -/// Decompile an Ixon KVMap (Address-based) to a Lean KVMap (Name/DataValue). -fn decompile_kvmap( - kvmap: &KVMap, - stt: &CompileState, -) -> Result, DecompileError> { - kvmap - .iter() - .map(|(k_addr, v)| { - let name = decompile_name(k_addr, stt)?; - let val = decompile_data_value(v, stt)?; - Ok((name, val)) - }) - .collect() -} - -/// Wrap a LeanExpr in pre-decompiled mdata layers. -/// -/// The `lean_mdata` vec stores layers outermost-first. -/// We iterate in reverse to wrap innermost-first: -/// given [kv_outer, kv_inner], result is mdata(kv_outer, mdata(kv_inner, expr)). -fn apply_mdata( - mut expr: LeanExpr, - lean_mdata: Vec>, -) -> LeanExpr { - for kvmap in lean_mdata.into_iter().rev() { - expr = LeanExpr::mdata(kvmap, expr); - } - expr -} - -// =========================================================================== -// Name decompilation -// =========================================================================== - -/// Look up a Name by its address. -pub fn decompile_name( - addr: &Address, - stt: &CompileState, -) -> Result { - stt - .env - .names - .get(addr) - .map(|r| r.clone()) - .ok_or(DecompileError::MissingAddress(addr.clone())) -} - -// =========================================================================== -// Universe decompilation -// =========================================================================== - -/// Decompile an Ixon Univ to a Lean Level. -pub fn decompile_univ( - univ: &Arc, - lvl_names: &[Name], - cache: &mut BlockCache, -) -> Result { - let ptr = Arc::as_ptr(univ); - if let Some(cached) = cache.univ_cache.get(&ptr) { - return Ok(cached.clone()); - } - - let level = match univ.as_ref() { - Univ::Zero => Level::zero(), - Univ::Succ(inner) => { - let inner_level = decompile_univ(inner, lvl_names, cache)?; - Level::succ(inner_level) - }, - Univ::Max(a, b) => { - let la = decompile_univ(a, lvl_names, cache)?; - let lb = decompile_univ(b, lvl_names, cache)?; - Level::max(la, lb) - }, - Univ::IMax(a, b) => { - let la = decompile_univ(a, lvl_names, cache)?; - let lb = decompile_univ(b, lvl_names, cache)?; - Level::imax(la, lb) - }, - Univ::Var(idx) => { - let idx_usize = *idx as usize; - let name = lvl_names - .get(idx_usize) - .ok_or_else(|| DecompileError::InvalidUnivVarIndex { - idx: *idx, - max: lvl_names.len(), - constant: cache.current_const.clone(), - })? - .clone(); - Level::param(name) - }, - }; - - cache.univ_cache.insert(ptr, level.clone()); - Ok(level) -} - -// =========================================================================== -// Expression decompilation -// =========================================================================== - -/// Pop a result from the decompilation stack, returning a structured error -/// instead of panicking if the stack is empty (malformed Ixon data). -fn pop_result( - results: &mut Vec, - msg: &str, - constant: &str, -) -> Result { - results.pop().ok_or_else(|| DecompileError::BadConstantFormat { - msg: format!("{msg} in '{constant}'"), - }) -} - -/// Decompile an Ixon Expr to a Lean Expr with arena-based metadata restoration. -/// -/// Traverses the arena tree following child pointers. Share references are -/// expanded with the same arena_idx (parent's child pointer already captures -/// the correct metadata subtree). Mdata arena nodes are collected and applied -/// as wrappers. -pub fn decompile_expr( - expr: &Arc, - arena: &ExprMeta, - arena_idx: u64, - lvl_names: &[Name], - cache: &mut BlockCache, - stt: &CompileState, -) -> Result { - // Lean mdata layers: Vec of KVMaps (outermost-first) - type LeanMdata = Vec>; - - /// Default node for "no metadata" sentinel. Semantically equivalent - /// to a Leaf — no names, no binder info, no metadata to reattach. - const DEFAULT_NODE: ExprMetaData = ExprMetaData::Leaf; - - /// Look up an arena node by index. - /// - /// `u64::MAX` is the legitimate "no metadata" sentinel used by - /// fallback paths when the caller has no metadata to attach (see - /// e.g. the `(_, Expr::App(..))` arm below that has no matching - /// `ExprMetaData::App`). In that case we return a `Leaf`. - /// - /// Any other out-of-bounds index indicates arena corruption — either - /// a malformed `ExprMeta` produced during compile, or an - /// `ExprMetaData` child pointer that overshoots the arena. We reject - /// these loudly rather than silently degrading to `Leaf`, which would - /// strip metadata from the subtree. - fn arena_lookup<'a>( - arena: &'a ExprMeta, - idx: u64, - constant: &str, - ) -> Result<&'a ExprMetaData, DecompileError> { - if idx == u64::MAX { - return Ok(&DEFAULT_NODE); - } - arena.nodes.get(idx as usize).ok_or_else(|| { - DecompileError::BadConstantFormat { - msg: format!( - "arena index {idx} out of bounds (arena has {} nodes) in '{constant}'", - arena.nodes.len(), - ), - } - }) - } - - enum Frame { - /// Decompile an Ixon expression read in a share scope at an arena index. - Decompile(Arc, u64, ShareScope), - BuildApp(LeanMdata), - BuildLam(Name, BinderInfo, LeanMdata), - BuildAll(Name, BinderInfo, LeanMdata), - BuildLet(Name, bool, LeanMdata), - BuildProj(Name, Nat, LeanMdata), - CacheResult(*const Expr, u64, ShareScope), - } - - // The root is a primary expression. - let mut stack: Vec = - vec![Frame::Decompile(expr.clone(), arena_idx, ShareScope::Primary)]; - let mut results: Vec = Vec::new(); - - while let Some(frame) = stack.pop() { - match frame { - Frame::Decompile(e, idx, scope) => { - // Expand Share transparently with the SAME arena_idx, in the - // target's scope. - if let Expr::Share(share_idx) = e.as_ref() { - let (shared_expr, shared_scope) = - cache.resolve_share(scope, *share_idx)?; - stack.push(Frame::Decompile(shared_expr, idx, shared_scope)); - continue; - } - - // Cache check: (Ixon pointer, arena index, share scope) - let cache_key = (Arc::as_ptr(&e), idx, scope); - if let Some(cached) = cache.expr_cache.get(&cache_key) { - results.push(cached.clone()); - continue; - } - - // Follow Mdata chain in arena, collecting mdata layers. - let mut current_idx = idx; - let mut mdata_layers: LeanMdata = Vec::new(); - while let ExprMetaData::Mdata { mdata, child } = - arena_lookup(arena, current_idx, &cache.current_const)? - { - for kvm in mdata { - let data = decompile_kvmap(kvm, stt)?; - if crate::semantic_contract::has_metadata(&data) { - return Err(DecompileError::BadConstantFormat { - msg: "semantic contracts cannot come from optional metadata" - .into(), - }); - } - mdata_layers.push(data); - } - current_idx = *child; - } - - let node = arena_lookup(arena, current_idx, &cache.current_const)?; - - // Push CacheResult frame - stack.push(Frame::CacheResult(Arc::as_ptr(&e), idx, scope)); - - if let Some(contract) = crate::semantic_contract::of_ixon(&e) { - mdata_layers.push(contract.metadata()); - } - match (node, e.as_ref()) { - // Leaf nodes: Var, Sort, Nat, Str - (_, Expr::Var(v)) => { - let expr = apply_mdata(LeanExpr::bvar(Nat::from(*v)), mdata_layers); - results.push(expr); - }, - - (_, Expr::Sort(univ_idx)) => { - // Level-spelling patch replay (canonicity §10.6): a patch - // keyed by this occurrence's arena index restores the - // original spelling via its virtual univ index. - let eff_idx = cache - .univ_patches - .get(¤t_idx) - .and_then(|idxs| idxs.first()) - .copied() - .unwrap_or(*univ_idx); - let univ = cache - .univ_table - .get(eff_idx as usize) - .ok_or_else(|| DecompileError::InvalidUnivIndex { - idx: eff_idx, - univs_len: cache.univ_table.len(), - constant: cache.current_const.clone(), - })? - .clone(); - let level = decompile_univ(&univ, lvl_names, cache)?; - let expr = apply_mdata(LeanExpr::sort(level), mdata_layers); - results.push(expr); - }, - - (_, Expr::Nat(ref_idx)) => { - let addr = cache.refs.get(*ref_idx as usize).ok_or_else(|| { - DecompileError::InvalidRefIndex { - idx: *ref_idx, - refs_len: cache.refs.len(), - constant: cache.current_const.clone(), - } - })?; - let n = read_nat(addr, stt)?; - let expr = - apply_mdata(LeanExpr::lit(Literal::NatVal(n)), mdata_layers); - results.push(expr); - }, - - (_, Expr::Str(ref_idx)) => { - let addr = cache.refs.get(*ref_idx as usize).ok_or_else(|| { - DecompileError::InvalidRefIndex { - idx: *ref_idx, - refs_len: cache.refs.len(), - constant: cache.current_const.clone(), - } - })?; - let s = read_string(addr, stt)?; - let expr = - apply_mdata(LeanExpr::lit(Literal::StrVal(s)), mdata_layers); - results.push(expr); - }, - - // Ref: resolve name from arena Ref node - ( - ExprMetaData::Ref { name: name_addr }, - Expr::Ref(ref_idx, univ_indices), - ) => { - let name = decompile_name(name_addr, stt).map_err(|_| { - DecompileError::BadConstantFormat { - msg: format!( - "Ref metadata name resolution failed in '{}' (ref_idx={}, arena has Ref but name addr {:.12} not found)", - cache.current_const, ref_idx, name_addr.hex(), - ), - } - })?; - // Level-spelling patch replay (canonicity §10.6): the patch - // carries the FULL level-arg list in the virtual index space. - let patch = cache.univ_patches.get(¤t_idx).cloned(); - let levels = match &patch { - Some(idxs) => decompile_univ_indices(idxs, lvl_names, cache)?, - None => decompile_univ_indices(univ_indices, lvl_names, cache)?, - }; - let expr = apply_mdata(LeanExpr::cnst(name, levels), mdata_layers); - results.push(expr); - }, - - (_, Expr::Ref(ref_idx, _univ_indices)) => { - // No Ref metadata — this is a metadata mismatch (the arena - // should always have a Ref node for Ref expressions). - return Err(DecompileError::BadConstantFormat { - msg: format!( - "missing Ref metadata for Expr::Ref in '{}' (ref_idx={}, arena node={:?})", - cache.current_const, - ref_idx, - arena.nodes.get(current_idx as usize).unwrap_or(&DEFAULT_NODE), - ), - }); - }, - - // Rec: resolve name from arena Ref node or fallback - ( - ExprMetaData::Ref { name: name_addr }, - Expr::Rec(rec_idx, univ_indices), - ) => { - // Fallback to cache.ctx is a legitimate recovery path when - // the global name index does not yet know this address — - // typically mid-block compilation where the rec's own name - // isn't registered globally but IS in the local mutual - // context. If neither source yields a name, we return an - // explicit `InvalidRecIndex` error rather than falling back - // to `Name::anon()` (which would round-trip to an unknown - // constant reference and fail much later in kernel - // type-check with a hard-to-attribute error). - let name = match decompile_name(name_addr, stt) { - Ok(n) => n, - Err(_) => { - #[cfg(debug_assertions)] - eprintln!( - "[decompile] Rec name address {:?} not in global index; \ - falling back to cache.ctx (rec_idx={}, constant={})", - name_addr, rec_idx, cache.current_const - ); - cache - .ctx - .iter() - .find(|(_, i)| i.to_u64() == Some(*rec_idx)) - .map(|(n, _)| n.clone()) - .ok_or_else(|| DecompileError::InvalidRecIndex { - idx: *rec_idx, - ctx_size: cache.ctx.len(), - constant: cache.current_const.clone(), - })? - }, - }; - // Level-spelling patch replay (canonicity §10.6). - let patch = cache.univ_patches.get(¤t_idx).cloned(); - let levels = match &patch { - Some(idxs) => decompile_univ_indices(idxs, lvl_names, cache)?, - None => decompile_univ_indices(univ_indices, lvl_names, cache)?, - }; - let expr = apply_mdata(LeanExpr::cnst(name, levels), mdata_layers); - results.push(expr); - }, - - (_, Expr::Rec(rec_idx, univ_indices)) => { - let name = cache - .ctx - .iter() - .find(|(_, i)| i.to_u64() == Some(*rec_idx)) - .map(|(n, _)| n.clone()) - .ok_or_else(|| DecompileError::InvalidRecIndex { - idx: *rec_idx, - ctx_size: cache.ctx.len(), - constant: cache.current_const.clone(), - })?; - // Level-spelling patch replay (canonicity §10.6). - let patch = cache.univ_patches.get(¤t_idx).cloned(); - let levels = match &patch { - Some(idxs) => decompile_univ_indices(idxs, lvl_names, cache)?, - None => decompile_univ_indices(univ_indices, lvl_names, cache)?, - }; - let expr = apply_mdata(LeanExpr::cnst(name, levels), mdata_layers); - results.push(expr); - }, - - // App: follow arena children - (ExprMetaData::App { children }, Expr::App(f, a)) => { - stack.push(Frame::BuildApp(mdata_layers)); - stack.push(Frame::Decompile(a.clone(), children[1], scope)); - stack.push(Frame::Decompile(f.clone(), children[0], scope)); - }, - - (_, Expr::App(f, a)) => { - // No App metadata — use dummy indices (Leaf fallback) - stack.push(Frame::BuildApp(mdata_layers)); - stack.push(Frame::Decompile(a.clone(), u64::MAX, scope)); - stack.push(Frame::Decompile(f.clone(), u64::MAX, scope)); - }, - - // Lam: extract binder name/info from arena - ( - ExprMetaData::Binder { name: name_addr, info, children }, - Expr::Lam(_, ty, body), - ) => { - // Binder name address must resolve. The compiler registers - // every binder name it emits; a missing entry here means - // the name index was built inconsistently with the arena. - // Silently defaulting to anon would lose user-level names - // cosmetically and mask the real corruption. - let binder_name = decompile_name(name_addr, stt)?; - stack.push(Frame::BuildLam( - binder_name, - info.clone(), - mdata_layers, - )); - stack.push(Frame::Decompile(body.clone(), children[1], scope)); - stack.push(Frame::Decompile(ty.clone(), children[0], scope)); - }, - - (_, Expr::Lam(_, ty, body)) => { - stack.push(Frame::BuildLam( - Name::anon(), - BinderInfo::Default, - mdata_layers, - )); - stack.push(Frame::Decompile(body.clone(), u64::MAX, scope)); - stack.push(Frame::Decompile(ty.clone(), u64::MAX, scope)); - }, - - // All: extract binder name/info from arena - ( - ExprMetaData::Binder { name: name_addr, info, children }, - Expr::All(_, _, ty, body), - ) => { - // See Lam arm above: binder address must resolve. - let binder_name = decompile_name(name_addr, stt)?; - stack.push(Frame::BuildAll( - binder_name, - info.clone(), - mdata_layers, - )); - stack.push(Frame::Decompile(body.clone(), children[1], scope)); - stack.push(Frame::Decompile(ty.clone(), children[0], scope)); - }, - - (_, Expr::All(_, _, ty, body)) => { - stack.push(Frame::BuildAll( - Name::anon(), - BinderInfo::Default, - mdata_layers, - )); - stack.push(Frame::Decompile(body.clone(), u64::MAX, scope)); - stack.push(Frame::Decompile(ty.clone(), u64::MAX, scope)); - }, - - // Let: extract name from arena - ( - ExprMetaData::LetBinder { name: name_addr, children }, - Expr::Let(non_dep, ty, val, body), - ) => { - // See Lam arm above: binder address must resolve. - let let_name = decompile_name(name_addr, stt)?; - stack.push(Frame::BuildLet( - let_name, - non_dep.non_dep, - mdata_layers, - )); - stack.push(Frame::Decompile(body.clone(), children[2], scope)); - stack.push(Frame::Decompile(val.clone(), children[1], scope)); - stack.push(Frame::Decompile(ty.clone(), children[0], scope)); - }, - - (_, Expr::Let(non_dep, ty, val, body)) => { - stack.push(Frame::BuildLet( - Name::anon(), - non_dep.non_dep, - mdata_layers, - )); - stack.push(Frame::Decompile(body.clone(), u64::MAX, scope)); - stack.push(Frame::Decompile(val.clone(), u64::MAX, scope)); - stack.push(Frame::Decompile(ty.clone(), u64::MAX, scope)); - }, - - // Prj: extract struct name from arena - ( - ExprMetaData::Prj { struct_name, child }, - Expr::Prj(_type_ref_idx, field_idx, struct_val), - ) => { - let type_name = decompile_name(struct_name, stt)?; - stack.push(Frame::BuildProj( - type_name, - Nat::from(*field_idx), - mdata_layers, - )); - stack.push(Frame::Decompile(struct_val.clone(), *child, scope)); - }, - - (_, Expr::Prj(type_ref_idx, _field_idx, _struct_val)) => { - // Fallback: look up from refs table - let addr = - cache.refs.get(*type_ref_idx as usize).ok_or_else(|| { - DecompileError::InvalidRefIndex { - idx: *type_ref_idx, - refs_len: cache.refs.len(), - constant: cache.current_const.clone(), - } - })?; - // No Prj metadata — this is a metadata mismatch. - return Err(DecompileError::BadConstantFormat { - msg: format!( - "missing Prj metadata for Expr::Prj in '{}' (type_ref_idx={}, addr={:.12})", - cache.current_const, - type_ref_idx, - addr.hex(), - ), - }); - }, - - (_, Expr::Share(_)) => unreachable!("Share handled above"), - } - }, - - Frame::BuildApp(mdata) => { - let a = pop_result( - &mut results, - "BuildApp missing arg", - &cache.current_const, - )?; - let f = pop_result( - &mut results, - "BuildApp missing fun", - &cache.current_const, - )?; - results.push(apply_mdata(LeanExpr::app(f, a), mdata)); - }, - - Frame::BuildLam(name, info, mdata) => { - let body = pop_result( - &mut results, - "BuildLam missing body", - &cache.current_const, - )?; - let ty = pop_result( - &mut results, - "BuildLam missing ty", - &cache.current_const, - )?; - results.push(apply_mdata(LeanExpr::lam(name, ty, body, info), mdata)); - }, - - Frame::BuildAll(name, info, mdata) => { - let body = pop_result( - &mut results, - "BuildAll missing body", - &cache.current_const, - )?; - let ty = pop_result( - &mut results, - "BuildAll missing ty", - &cache.current_const, - )?; - results.push(apply_mdata(LeanExpr::all(name, ty, body, info), mdata)); - }, - - Frame::BuildLet(name, non_dep, mdata) => { - let body = pop_result( - &mut results, - "BuildLet missing body", - &cache.current_const, - )?; - let val = pop_result( - &mut results, - "BuildLet missing val", - &cache.current_const, - )?; - let ty = pop_result( - &mut results, - "BuildLet missing ty", - &cache.current_const, - )?; - results.push(apply_mdata( - LeanExpr::letE(name, ty, val, body, non_dep), - mdata, - )); - }, - - Frame::BuildProj(name, idx, mdata) => { - let s = pop_result( - &mut results, - "BuildProj missing struct", - &cache.current_const, - )?; - results.push(apply_mdata(LeanExpr::proj(name, idx, s), mdata)); - }, - - Frame::CacheResult(e_ptr, arena_idx, scope) => { - if let Some(result) = results.last() { - cache.expr_cache.insert((e_ptr, arena_idx, scope), result.clone()); - } - }, - } - } - - results - .pop() - .ok_or(DecompileError::BadConstantFormat { msg: "empty result".into() }) -} - -/// Helper: decompile universe indices to Lean levels. -fn decompile_univ_indices( - univ_indices: &[u64], - lvl_names: &[Name], - cache: &mut BlockCache, -) -> Result, DecompileError> { - univ_indices - .iter() - .map(|ui| { - let univ = cache - .univ_table - .get(*ui as usize) - .ok_or_else(|| DecompileError::InvalidUnivIndex { - idx: *ui, - univs_len: cache.univ_table.len(), - constant: cache.current_const.clone(), - })? - .clone(); - decompile_univ(&univ, lvl_names, cache) - }) - .collect() -} - -/// Extract the name address from ConstantMeta. -fn get_name_addr_from_meta(meta: &ConstantMeta) -> Option<&Address> { - match &meta.info { - ConstantMetaInfo::Empty => None, - ConstantMetaInfo::Def { name, .. } => Some(name), - ConstantMetaInfo::Axio { name, .. } => Some(name), - ConstantMetaInfo::Quot { name, .. } => Some(name), - ConstantMetaInfo::Indc { name, .. } => Some(name), - ConstantMetaInfo::Ctor { name, .. } => Some(name), - ConstantMetaInfo::Rec { name, .. } => Some(name), - ConstantMetaInfo::Muts { .. } => None, - } -} - -/// Extract level param name addresses from ConstantMeta. -fn get_lvls_from_meta(meta: &ConstantMeta) -> &[Address] { - match &meta.info { - ConstantMetaInfo::Empty => &[], - ConstantMetaInfo::Def { lvls, .. } => lvls, - ConstantMetaInfo::Axio { lvls, .. } => lvls, - ConstantMetaInfo::Quot { lvls, .. } => lvls, - ConstantMetaInfo::Indc { lvls, .. } => lvls, - ConstantMetaInfo::Ctor { lvls, .. } => lvls, - ConstantMetaInfo::Rec { lvls, .. } => lvls, - ConstantMetaInfo::Muts { .. } => &[], - } -} - -/// Extract arena and type_root from ConstantMeta. -fn get_arena_and_type_root(meta: &ConstantMeta) -> (&ExprMeta, u64) { - static EMPTY_ARENA: ExprMeta = ExprMeta { nodes: Vec::new() }; - match &meta.info { - ConstantMetaInfo::Def { arena, type_root, .. } => (arena, *type_root), - ConstantMetaInfo::Axio { arena, type_root, .. } => (arena, *type_root), - ConstantMetaInfo::Quot { arena, type_root, .. } => (arena, *type_root), - ConstantMetaInfo::Indc { arena, type_root, .. } => (arena, *type_root), - ConstantMetaInfo::Ctor { arena, type_root, .. } => (arena, *type_root), - ConstantMetaInfo::Rec { arena, type_root, .. } => (arena, *type_root), - ConstantMetaInfo::Empty => (&EMPTY_ARENA, 0), - ConstantMetaInfo::Muts { .. } => (&EMPTY_ARENA, 0), - } -} - -/// Extract the all field from ConstantMeta (original Lean all field for roundtrip). -fn get_all_from_meta(meta: &ConstantMeta) -> &[Address] { - match &meta.info { - ConstantMetaInfo::Def { all, .. } => all, - ConstantMetaInfo::Indc { all, .. } => all, - ConstantMetaInfo::Rec { all, .. } => all, - _ => &[], - } -} - -/// Extract the ctx field from ConstantMeta (MutCtx used during compilation for Rec expr decompilation). -fn get_ctx_from_meta(meta: &ConstantMeta) -> &[Address] { - match &meta.info { - ConstantMetaInfo::Def { ctx, .. } => ctx, - ConstantMetaInfo::Indc { ctx, .. } => ctx, - ConstantMetaInfo::Rec { ctx, .. } => ctx, - _ => &[], - } -} - -/// Decompile a name from ConstantMeta. -fn decompile_name_from_meta( - meta: &ConstantMeta, - stt: &CompileState, -) -> Result { - match get_name_addr_from_meta(meta) { - Some(addr) => decompile_name(addr, stt), - None => { - Err(DecompileError::BadConstantFormat { msg: "empty metadata".into() }) - }, - } -} - -/// Extract level param names from ConstantMeta. -fn decompile_level_names_from_meta( - meta: &ConstantMeta, - stt: &CompileState, -) -> Result, DecompileError> { - get_lvls_from_meta(meta).iter().map(|a| decompile_name(a, stt)).collect() -} - -// =========================================================================== -// Constant decompilation -// =========================================================================== - -/// Decompile a ConstantVal (name, level_params, type). -fn decompile_const_val( - typ: &Arc, - meta: &ConstantMeta, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result { - let name = decompile_name_from_meta(meta, stt)?; - let level_params = decompile_level_names_from_meta(meta, stt)?; - let (arena, type_root) = get_arena_and_type_root(meta); - let typ = decompile_expr(typ, arena, type_root, &level_params, cache, stt)?; - Ok(ConstantVal { name, level_params, typ }) -} - -/// Decompile a Definition. -fn decompile_definition( - def: &Definition, - meta: &ConstantMeta, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result { - let name = decompile_name_from_meta(meta, stt)?; - let level_params = decompile_level_names_from_meta(meta, stt)?; - - let (arena, type_root, value_root) = match &meta.info { - ConstantMetaInfo::Def { arena, type_root, value_root, .. } => { - (arena, *type_root, *value_root) - }, - _ => { - static EMPTY: ExprMeta = ExprMeta { nodes: Vec::new() }; - (&EMPTY, 0, 0) - }, - }; - - let typ = - decompile_expr(&def.typ, arena, type_root, &level_params, cache, stt)?; - let value = - decompile_expr(&def.value, arena, value_root, &level_params, cache, stt)?; - - let all = match &meta.info { - ConstantMetaInfo::Def { all, .. } => { - let all_names: Result, _> = - all.iter().map(|a| decompile_name(a, stt)).collect(); - all_names? - }, - _ => vec![], - }; - // Hints come from the per-name `Named.hints` channel — the exact - // value `finalize_hints` recorded for this name. The per-address - // `env.anon_hints` channel is unusable here: alpha-identical - // definitions under different names share one constant address, so - // it only holds a merged winner. Absent → `Opaque`, matching the - // compiler's treatment of theorems and opaques. - let hints = stt - .env - .named - .get(&name) - .and_then(|n| n.value().hints()) - .unwrap_or(ReducibilityHints::Opaque); - - let cnst = ConstantVal { name, level_params, typ }; - - match def.kind { - DefKind::Definition => Ok(LeanConstantInfo::DefnInfo(DefinitionVal { - cnst, - value, - hints, - safety: def.safety, - all, - })), - DefKind::Theorem => { - Ok(LeanConstantInfo::ThmInfo(TheoremVal { cnst, value, all })) - }, - DefKind::Opaque => Ok(LeanConstantInfo::OpaqueInfo(OpaqueVal { - cnst, - value, - is_unsafe: def.safety == DefinitionSafety::Unsafe, - all, - })), - } -} - -/// Decompile a Recursor. -/// Arena covers type + all rule RHS expressions with rule_roots. -fn decompile_recursor( - rec: &Recursor, - meta: &ConstantMeta, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result { - let name = decompile_name_from_meta(meta, stt)?; - let level_params = decompile_level_names_from_meta(meta, stt)?; - - let (arena, type_root, rule_roots, rule_addrs, all_addrs) = match &meta.info { - ConstantMetaInfo::Rec { - arena, type_root, rule_roots, rules, all, .. - } => { - // Rec metadata must have one rule_root per recursor rule. - // A mismatch means the arena was produced inconsistently with - // the recursor value; subsequent rule RHS decompilation would - // silently use a Leaf default (losing rule-level metadata) if - // we didn't validate here. - if rule_roots.len() != rec.rules.len() { - return Err(DecompileError::BadConstantFormat { - msg: format!( - "recursor metadata for '{}': rule_roots has {} entries but \ - recursor has {} rules", - name.pretty(), - rule_roots.len(), - rec.rules.len(), - ), - }); - } - ( - arena, - *type_root, - rule_roots.as_slice(), - rules.as_slice(), - all.as_slice(), - ) - }, - _ => { - // No Rec metadata: graceful degradation. Arena is empty and - // rule_roots is empty, so rule RHS decompilation proceeds with - // the u64::MAX sentinel via `rule_roots.get(i).unwrap_or(&...)` - // below falling through to Leaf. Only allowed when the recursor - // has no rules; otherwise data loss would be silent. - if !rec.rules.is_empty() { - return Err(DecompileError::BadConstantFormat { - msg: format!( - "recursor has {} rules but no Rec metadata was supplied", - rec.rules.len() - ), - }); - } - static EMPTY: ExprMeta = ExprMeta { nodes: Vec::new() }; - (&EMPTY, 0u64, &[] as &[u64], &[] as &[Address], &[] as &[Address]) - }, - }; - - let typ = - decompile_expr(&rec.typ, arena, type_root, &level_params, cache, stt)?; - - let rule_names = rule_addrs - .iter() - .map(|a| decompile_name(a, stt)) - .collect::, _>>()?; - // Propagate resolution failures rather than silently degrading to - // `vec![name.clone()]`. If a name in `.all` can't be resolved, the - // recursor's mutual-block structure is incorrect — masking that with - // a singleton fallback produces a plausible-looking but wrong - // recursor that may pass later checks by coincidence. - let all = all_addrs - .iter() - .map(|a| decompile_name(a, stt)) - .collect::, _>>()?; - - let mut rules = Vec::with_capacity(rec.rules.len()); - for (i, (rule, ctor_name)) in - rec.rules.iter().zip(rule_names.iter()).enumerate() - { - // Safe: lengths validated against rec.rules above. If rule_roots - // is empty, rec.rules is also empty and this loop doesn't run. - let rhs_root = rule_roots[i]; - let rhs = - decompile_expr(&rule.rhs, arena, rhs_root, &level_params, cache, stt)?; - rules.push(LeanRecursorRule { - ctor: ctor_name.clone(), - n_fields: Nat::from(rule.fields), - rhs, - }); - } - - let cnst = ConstantVal { name, level_params, typ }; - - Ok(LeanConstantInfo::RecInfo(RecursorVal { - cnst, - all, - num_params: Nat::from(rec.params), - num_indices: Nat::from(rec.indices), - num_motives: Nat::from(rec.motives), - num_minors: Nat::from(rec.minors), - rules, - k: rec.k, - is_unsafe: rec.is_unsafe, - })) -} - -/// Decompile a Constructor. -/// Constructor metadata is in its own ConstantMetaInfo::Ctor (resolved from Named entries). -fn decompile_constructor( - ctor: &Constructor, - meta: &ConstantMeta, - induct_name: Name, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result { - let name = decompile_name_from_meta(meta, stt)?; - let level_params = decompile_level_names_from_meta(meta, stt)?; - - let (arena, type_root) = get_arena_and_type_root(meta); - let typ = - decompile_expr(&ctor.typ, arena, type_root, &level_params, cache, stt)?; - - let cnst = ConstantVal { name, level_params, typ }; - - Ok(ConstructorVal { - cnst, - induct: induct_name, - cidx: Nat::from(ctor.cidx), - num_params: Nat::from(ctor.params), - num_fields: Nat::from(ctor.fields), - is_unsafe: ctor.is_unsafe, - }) -} - -/// Decompile an Inductive. -/// Constructor metadata is resolved from Named entries, not from CtorMeta. -fn decompile_inductive( - ind: &Inductive, - meta: &ConstantMeta, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result<(InductiveVal, Vec), DecompileError> { - let name = decompile_name_from_meta(meta, stt)?; - let level_params = decompile_level_names_from_meta(meta, stt)?; - - let (arena, type_root) = get_arena_and_type_root(meta); - let typ = - decompile_expr(&ind.typ, arena, type_root, &level_params, cache, stt)?; - - // Extract constructor name addresses and all from metadata. The - // non-Indc arm should be unreachable — `decompile_inductive` is only - // called when the meta is an Indc variant. If we ever get here with - // a different variant shape, that's structural corruption, not a - // silently recoverable condition. - let (ctor_name_addrs, all) = match &meta.info { - ConstantMetaInfo::Indc { ctors, all: all_addrs, .. } => { - let all = all_addrs - .iter() - .map(|a| decompile_name(a, stt)) - .collect::, _>>()?; - (ctors.as_slice(), all) - }, - other => { - return Err(DecompileError::BadConstantFormat { - msg: format!( - "decompile_inductive for '{}': expected ConstantMetaInfo::Indc, \ - got variant with discriminant {:?}", - name.pretty(), - std::mem::discriminant(other), - ), - }); - }, - }; - - let mut ctors = Vec::with_capacity(ind.ctors.len()); - let mut ctor_names = Vec::new(); - - for (i, ctor) in ind.ctors.iter().enumerate() { - // Clear expr_cache: each constructor has its own arena, so cached entries - // from the inductive's arena (or a previous constructor's arena) would - // produce stale hits when arena indices coincide. - cache.expr_cache.clear(); - // Clear the pointer-keyed univ memo too (canonicity §10.6): the - // constructor window below swaps per-ctor `meta_univs` extensions in - // and out of `univ_table`, so `*const Univ` keys minted under one - // ctor's extension can be REUSED by a fresh allocation in a later - // iteration — a stale hit then substitutes an arbitrary earlier - // level for the patched spelling. (Pre-stage-2 `meta_univs` was - // always empty, so the memo never saw ephemeral pointers.) - cache.univ_cache.clear(); - - // Look up constructor's Named entry for its ConstantMetaInfo::Ctor - let ctor_meta = if let Some(addr) = ctor_name_addrs.get(i) { - if let Ok(ctor_name) = decompile_name(addr, stt) { - stt - .env - .named - .get(&ctor_name) - .map(|n| (*n.meta()).clone()) - .unwrap_or_default() - } else { - ConstantMeta::default() - } - } else { - ConstantMeta::default() - }; - - // Constructor metadata is per-constructor, not inherited from the parent - // inductive. In particular, a constructor carries its own - // `meta_sharing` table and its own `univ_patches` (canonicity §10.6). - // Install those extensions only while - // walking this constructor so they do not leak across sibling - // constructor arenas. The constructor's `meta_sharing` is checked - // against the block's primary table, as `load_meta_extensions` does. - validate_meta_sharing( - cache.sharing.len(), - &ctor_meta.meta_sharing, - &cache.current_const, - )?; - let saved_meta_sharing = std::mem::replace( - &mut cache.meta_sharing, - ctor_meta.meta_sharing.clone(), - ); - let saved_univ_patches = std::mem::replace( - &mut cache.univ_patches, - ctor_meta - .univ_patches - .iter() - .map(|p| (p.arena_idx, p.univ_idxs.clone())) - .collect(), - ); - let refs_len = cache.refs.len(); - // Virtual univ indices are minted against the PRIMARY table length - // (canonicity §10.6), so the constructor's `meta_univs` must be - // installed at that offset — the parent inductive's own extension - // (appended by `load_meta_extensions`) is displaced for the - // duration, and the whole table is restored afterwards. - let primary_univ_len = - cache.primary_univ_len.unwrap_or(cache.univ_table.len()); - let saved_univ_table = cache.univ_table.clone(); - cache.refs.extend(ctor_meta.meta_refs.iter().cloned()); - cache.univ_table.truncate(primary_univ_len); - cache.univ_table.extend(ctor_meta.meta_univs.iter().cloned()); - - let ctor_result = - decompile_constructor(ctor, &ctor_meta, name.clone(), cache, stt); - - cache.meta_sharing = saved_meta_sharing; - cache.univ_patches = saved_univ_patches; - cache.refs.truncate(refs_len); - cache.univ_table = saved_univ_table; - - let ctor_val = ctor_result?; - ctor_names.push(ctor_val.cnst.name.clone()); - ctors.push(ctor_val); - } - - let cnst = ConstantVal { name, level_params, typ }; - - let ind_val = InductiveVal { - cnst, - num_params: Nat::from(ind.params), - num_indices: Nat::from(ind.indices), - all, - ctors: ctor_names, - num_nested: Nat::from(0u64), - is_rec: false, - is_reflexive: false, - is_unsafe: ind.is_unsafe, - }; - - Ok((ind_val, ctors)) -} - -/// Decompile an Axiom. -fn decompile_axiom( - ax: &Axiom, - meta: &ConstantMeta, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result { - let cnst = decompile_const_val(&ax.typ, meta, cache, stt)?; - Ok(LeanConstantInfo::AxiomInfo(AxiomVal { cnst, is_unsafe: ax.is_unsafe })) -} - -/// Decompile a Quotient. -fn decompile_quotient( - quot: &Quotient, - meta: &ConstantMeta, - cache: &mut BlockCache, - stt: &CompileState, -) -> Result { - let cnst = decompile_const_val(".typ, meta, cache, stt)?; - Ok(LeanConstantInfo::QuotInfo(QuotVal { cnst, kind: quot.kind })) -} - -// =========================================================================== -// Mutual block decompilation -// =========================================================================== - -/// Decompile a mutual block (Vec). -/// Decompile a single projection, given the block data and sharing. -#[allow(clippy::too_many_arguments)] -fn decompile_projection( - name: &Name, - named: &Named, - cnst: &Constant, - mutuals: &[MutConst], - block_sharing: &[Arc], - block_refs: &[Address], - block_univs: &[Arc], - stt: &CompileState, - dstt: &DecompileState, -) -> Result<(), DecompileError> { - // Build ctx from metadata's ctx field - let named_meta = named.meta(); - let ctx_addrs = get_ctx_from_meta(&named_meta); - let ctx_names: Vec = ctx_addrs - .iter() - .map(|a| decompile_name(a, stt)) - .collect::, _>>()?; - - // Set up cache with sharing, refs, univs, and ctx - let mut cache = BlockCache { - sharing: block_sharing.to_vec(), - refs: block_refs.to_vec(), - univ_table: block_univs.to_vec(), - ctx: all_to_ctx(&ctx_names), - current_const: name.pretty(), - ..Default::default() - }; - // Projection metadata can carry the metadata extensions; load them for - // the projection's own arena. - cache.load_meta_extensions(&named_meta)?; - - // Each projection variant must land on the matching `MutConst` kind - // at its block index. A silent fall-through would leave `name` - // unregistered in `dstt.env`, and downstream references would fail - // far from the real point of corruption. - match &cnst.info { - ConstantInfo::DPrj(proj) => match mutuals.get(proj.idx as usize) { - Some(MutConst::Defn(def)) => { - let info = decompile_definition(def, &named_meta, &mut cache, stt)?; - dstt.insert_interned(name.clone(), info); - }, - other => { - return Err(projection_mismatch_error( - "DPrj", - name, - proj.idx, - other, - mutuals.len(), - stt, - )); - }, - }, - - ConstantInfo::IPrj(proj) => match mutuals.get(proj.idx as usize) { - Some(MutConst::Indc(ind)) => { - let (ind_val, ctors) = - decompile_inductive(ind, &named_meta, &mut cache, stt)?; - dstt - .insert_interned(name.clone(), LeanConstantInfo::InductInfo(ind_val)); - for ctor in ctors { - dstt - .env - .insert(ctor.cnst.name.clone(), LeanConstantInfo::CtorInfo(ctor)); - } - }, - other => { - return Err(projection_mismatch_error( - "IPrj", - name, - proj.idx, - other, - mutuals.len(), - stt, - )); - }, - }, - - ConstantInfo::RPrj(proj) => match mutuals.get(proj.idx as usize) { - Some(MutConst::Recr(rec)) => { - let info = decompile_recursor(rec, &named_meta, &mut cache, stt)?; - dstt.insert_interned(name.clone(), info); - }, - other => { - return Err(projection_mismatch_error( - "RPrj", - name, - proj.idx, - other, - mutuals.len(), - stt, - )); - }, - }, - - // Non-projection constants are ignored here; they're handled by - // the generic decompile paths. - _ => {}, - } - - Ok(()) -} - -/// Format a projection kind/index mismatch as a `BadConstantFormat` -/// error. Extracted to avoid triplicate bodies in `decompile_projection`. -fn projection_mismatch_error( - kind: &str, - name: &Name, - idx: u64, - other: Option<&MutConst>, - mutuals_len: usize, - stt: &CompileState, -) -> DecompileError { - let has_addr = stt.name_to_addr.contains_key(name); - let has_original = stt.env.named.get(name).is_some_and(|n| n.has_original()); - DecompileError::BadConstantFormat { - msg: format!( - "{kind} '{}' idx={idx} landed on {:?} (mutuals.len={mutuals_len}, \ - addr={has_addr}, has_original={has_original})", - name.pretty(), - other.map(std::mem::discriminant), - ), - } -} - -/// Decompile a single constant (non-mutual). -fn decompile_const( - name: &Name, - named: &Named, - stt: &CompileState, - dstt: &DecompileState, -) -> Result<(), DecompileError> { - let cnst = read_const(&named.addr, stt)?; - - // Build ctx from metadata's all field - let named_meta = named.meta(); - let all_addrs = get_all_from_meta(&named_meta); - let all_names: Vec = all_addrs - .iter() - .map(|a| decompile_name(a, stt)) - .collect::, _>>()?; - let ctx = all_to_ctx(&all_names); - let current_const = name.pretty(); - - match cnst.as_ref() { - Constant { info: ConstantInfo::Defn(def), sharing, refs, univs } => { - let mut cache = BlockCache { - sharing: sharing.clone(), - refs: refs.clone(), - univ_table: univs.clone(), - ctx: ctx.clone(), - current_const: current_const.clone(), - ..Default::default() - }; - cache.load_meta_extensions(&named_meta)?; - let info = decompile_definition(def, &named_meta, &mut cache, stt)?; - dstt.insert_interned(name.clone(), info); - }, - - Constant { info: ConstantInfo::Recr(rec), sharing, refs, univs } => { - let mut cache = BlockCache { - sharing: sharing.clone(), - refs: refs.clone(), - univ_table: univs.clone(), - ctx: ctx.clone(), - current_const: current_const.clone(), - ..Default::default() - }; - // Recursor rule RHSs can carry metadata extensions, as in the Defn - // branch above. - cache.load_meta_extensions(&named_meta)?; - let info = decompile_recursor(rec, &named_meta, &mut cache, stt)?; - dstt.insert_interned(name.clone(), info); - }, - - Constant { info: ConstantInfo::Axio(ax), sharing, refs, univs } => { - let mut cache = BlockCache { - sharing: sharing.clone(), - refs: refs.clone(), - univ_table: univs.clone(), - ctx: ctx.clone(), - current_const: current_const.clone(), - ..Default::default() - }; - // Load extensions for consistency with the other branches. - cache.load_meta_extensions(&named_meta)?; - let info = decompile_axiom(ax, &named_meta, &mut cache, stt)?; - dstt.insert_interned(name.clone(), info); - }, - - Constant { info: ConstantInfo::Quot(quot), sharing, refs, univs } => { - let mut cache = BlockCache { - sharing: sharing.clone(), - refs: refs.clone(), - univ_table: univs.clone(), - ctx, - current_const, - ..Default::default() - }; - // Quotient types have only a type signature — same story as - // axioms. Load extensions for consistency. - cache.load_meta_extensions(&named_meta)?; - let info = decompile_quotient(quot, &named_meta, &mut cache, stt)?; - dstt.insert_interned(name.clone(), info); - }, - - Constant { info: ConstantInfo::DPrj(_), .. } - | Constant { info: ConstantInfo::IPrj(_), .. } - | Constant { info: ConstantInfo::RPrj(_), .. } - | Constant { info: ConstantInfo::CPrj(_), .. } => { - // Projections are handled by `decompile_projection` - }, - - Constant { info: ConstantInfo::Muts(_), .. } => { - // Mutual blocks are handled separately - }, - } - - Ok(()) -} - -// =========================================================================== -// Per-constant helpers -// =========================================================================== - -/// Decompile a single named constant into the decompile state. -/// -/// Dispatches on the constant kind (definition, recursor, axiom, quotient, -/// projection). -fn decompile_named_const( - name: &Name, - named: &Named, - stt: &CompileState, - dstt: &DecompileState, -) -> Result<(), DecompileError> { - if let Some(cnst) = stt.env.get_const(&named.addr) { - match &cnst.info { - // Direct constants - decompile immediately - ConstantInfo::Defn(_) - | ConstantInfo::Recr(_) - | ConstantInfo::Axio(_) - | ConstantInfo::Quot(_) => decompile_const(name, named, stt, dstt), - - // Projections - get the block and decompile - ConstantInfo::DPrj(proj) => { - match stt.env.get_const(&proj.block).as_deref() { - Some(Constant { - info: ConstantInfo::Muts(mutuals), - sharing, - refs, - univs, - }) => decompile_projection( - name, named, &cnst, mutuals, sharing, refs, univs, stt, dstt, - ), - _ => Err(DecompileError::MissingAddress(proj.block.clone())), - } - }, - - ConstantInfo::IPrj(proj) => { - match stt.env.get_const(&proj.block).as_deref() { - Some(Constant { - info: ConstantInfo::Muts(mutuals), - sharing, - refs, - univs, - }) => decompile_projection( - name, named, &cnst, mutuals, sharing, refs, univs, stt, dstt, - ), - _ => Err(DecompileError::MissingAddress(proj.block.clone())), - } - }, - - ConstantInfo::RPrj(proj) => { - match stt.env.get_const(&proj.block).as_deref() { - Some(Constant { - info: ConstantInfo::Muts(mutuals), - sharing, - refs, - univs, - }) => decompile_projection( - name, named, &cnst, mutuals, sharing, refs, univs, stt, dstt, - ), - _ => Err(DecompileError::MissingAddress(proj.block.clone())), - } - }, - - // Constructor projections are handled when their parent inductive is decompiled - ConstantInfo::CPrj(_) => Ok(()), - - // Mutual blocks themselves don't need separate handling - ConstantInfo::Muts(_) => Ok(()), - } - } else { - Ok(()) - } -} - -// =========================================================================== -// Main entry point -// =========================================================================== - -/// Decompile an Ixon environment back to Lean format: every named constant -/// is decompiled in parallel (`decompile_named_const`), then each inductive -/// block's Lean flags are recomputed (`compute_lean_ind_flags`). -pub fn decompile_env( - stt: &CompileState, -) -> Result { - let dstt = DecompileState::default(); - - let t_constants = std::time::Instant::now(); - eprintln!( - "[decompile] decompiling {} named constants in parallel...", - stt.env.named.len(), - ); - stt.env.named.par_iter().try_for_each(|entry| { - let (name, named) = (entry.key(), entry.value()); - decompile_named_const(name, named, stt, &dstt) - })?; - eprintln!( - "[decompile] constants done in {:.2}s ({} constants in dstt.env){}", - t_constants.elapsed().as_secs_f32(), - dstt.env.len(), - rss_log_suffix(), - ); - - // Lean-faithful inductive flags. Scoped so `lean_env` — a full structured - // copy of the environment used only by `compute_lean_ind_flags` — drops - // here instead of living to the end of the function. - let t_flags = std::time::Instant::now(); - let n_ind_groups = { - let lean_env: ix_common::env::Env = - dstt.env.iter().map(|e| (e.key().clone(), e.value().clone())).collect(); - let mut groups: FxHashMap> = FxHashMap::default(); - for entry in dstt.env.iter() { - if let LeanConstantInfo::InductInfo(v) = entry.value() - && let Some(first) = v.all.first() - { - groups.entry(first.clone()).or_insert_with(|| v.all.clone()); - } - } - for (key, all) in &groups { - let flags = compute_lean_ind_flags(all, &lean_env).map_err(|e| { - DecompileError::BadConstantFormat { - msg: format!("ind-flags fixup for block '{}': {e}", key.pretty()), - } - })?; - for member in all { - if let Some(mut entry) = dstt.env.get_mut(member) - && let LeanConstantInfo::InductInfo(v) = entry.value_mut() - { - v.num_nested = Nat::from(flags.num_nested); - v.is_rec = flags.is_rec; - v.is_reflexive = flags.is_reflexive; - } - } - } - groups.len() - }; - eprintln!( - "[decompile] inductive flags done in {:.2}s ({} inductive groups){}", - t_flags.elapsed().as_secs_f32(), - n_ind_groups, - rss_log_suffix(), - ); - - // The intern table's job is done — `dstt.env` keeps the canonical - // `Arc`s alive, and consumers only read `env`. - let mut dstt = dstt; - dstt.expr_intern = DashMap::new(); - - Ok(dstt) -} - -/// Select the source view of a decompiled environment, the one Lean import -/// restores (`rs_decompile_env_consts`, `import_ixe`). `requested` empty -/// selects every name; otherwise the requested names, which must be present -/// in `env`, and their reference closure. The reference closure is checked -/// for a full selection too, so a broken source reconstruction is refused: -/// every reference must be a present name. Sorted. -pub fn materialized_source_names( - env: &DashMap, - requested: &[Name], -) -> Result, String> { - use crate::graph::{NameSet, get_constant_info_references}; - - let roots: Vec = if requested.is_empty() { - env.iter().map(|entry| entry.key().clone()).collect() - } else { - requested.to_vec() - }; - let mut seen = NameSet::default(); - let mut worklist = Vec::new(); - for root in roots { - if !env.contains_key(&root) { - return Err(format!( - "requested source constant '{}' not found", - root.pretty() - )); - } - if seen.insert(root.clone()) { - worklist.push(root); - } - } - while let Some(name) = worklist.pop() { - let references = { - let declaration = env.get(&name).ok_or_else(|| { - format!( - "source constant '{}' disappeared during selection", - name.pretty() - ) - })?; - get_constant_info_references(declaration.value()) - }; - for reference in references { - if seen.contains(&reference) { - continue; - } - if !env.contains_key(&reference) { - return Err(format!( - "source constant '{}' references missing source constant '{}' — \ - broken closure", - name.pretty(), - reference.pretty() - )); - } - seen.insert(reference.clone()); - worklist.push(reference); - } - } - let mut selected: Vec = seen.into_iter().collect(); - selected.sort_unstable(); - Ok(selected) -} - -/// Result of checking a decompiled environment against the original. -#[derive(Debug)] -pub struct CheckResult { - pub matches: usize, - pub mismatches: usize, - /// Constants in decompiled but not in original. - pub missing: usize, - /// Names of constants in decompiled but not in original. - pub extra_names: Vec, - /// Constants of the original absent from the decompiled environment, - /// sorted by pretty name. Expected only for inputs the compile left - /// ungrounded; any other is a failure (`failures`). - pub absent: Vec, -} - -impl CheckResult { - /// The round trip's failures: mismatches, names not in the original, and - /// original constants absent from the decompiled environment other than - /// those `expected_absent` accepts (the compile's ungrounded inputs). - /// Each absence is printed. - pub fn failures(&self, expected_absent: impl Fn(&Name) -> bool) -> usize { - let unexpected: Vec<&Name> = - self.absent.iter().filter(|n| !expected_absent(n)).collect(); - for n in &unexpected { - eprintln!("check_decompile: FAIL absent from decompiled: {}", n.pretty()); - } - self.mismatches + self.missing + unexpected.len() - } -} - -/// Check that decompiled environment matches the original. -/// Counts and logs hash mismatches (which indicate metadata loss or decompilation errors). -pub fn check_decompile( - original: &LeanEnv, - _stt: &CompileState, - dstt: &DecompileState, -) -> Result { - use std::sync::atomic::{AtomicUsize, Ordering}; - - let mismatches = AtomicUsize::new(0); - let matches = AtomicUsize::new(0); - let missing = AtomicUsize::new(0); - - if original.len() != dstt.env.len() { - eprintln!( - "check_decompile: size mismatch: original={}, decompiled={}", - original.len(), - dstt.env.len() - ); - } - - dstt.env.par_iter().try_for_each(|entry| { - let (name, info) = (entry.key(), entry.value()); - match original.get(name).as_deref() { - Some(orig_info) if orig_info.get_hash() == info.get_hash() => { - matches.fetch_add(1, Ordering::Relaxed); - Ok::<(), DecompileError>(()) - }, - Some(orig_info) => { - // Hash mismatch - log the constant name and hashes - let count = mismatches.fetch_add(1, Ordering::Relaxed); - if count < 20 { - eprintln!( - "check_decompile: hash mismatch for {}: original={:?}, decompiled={:?}", - name.pretty(), - orig_info.get_hash(), - info.get_hash() - ); - } - Ok(()) - }, - None => { - missing.fetch_add(1, Ordering::Relaxed); - Ok(()) - }, - } - })?; - - // Report constants in original but missing from decompiled. - let mut absent: Vec = original - .iter() - .filter(|(name, _)| !dstt.env.contains_key(*name)) - .map(|(name, _)| name.clone()) - .collect(); - absent.sort_by_key(Name::pretty); - if !absent.is_empty() { - eprintln!( - "check_decompile: {} constants missing from decompiled:", - absent.len() - ); - for name in &absent { - eprintln!(" missing: {}", name.pretty()); - } - } - - // Report constants in decompiled but not in original. - let mut extra_names: Vec = dstt - .env - .iter() - .filter(|entry| !original.contains_key(entry.key())) - .map(|entry| entry.key().pretty()) - .collect(); - extra_names.sort(); - if !extra_names.is_empty() { - eprintln!( - "check_decompile: {} constants in decompiled but not in original:", - extra_names.len() - ); - for name in &extra_names { - eprintln!(" extra: {name}"); - } - } - - let result = CheckResult { - matches: matches.load(Ordering::Relaxed), - mismatches: mismatches.load(Ordering::Relaxed), - missing: missing.load(Ordering::Relaxed), - extra_names, - absent, - }; - eprintln!( - "check_decompile: {} matches, {} mismatches, {} not in original, {} \ - absent", - result.matches, - result.mismatches, - result.missing, - result.absent.len(), - ); - - Ok(result) -} - -// =========================================================================== -// Regression tests for the decompiler -// -// These anchor the individual decompile-side invariants so a future change -// that breaks one of them trips immediately in `cargo test`. -// =========================================================================== - -#[cfg(test)] -mod tests { - use super::*; - use ix_common::env::Level; - - fn import_view_axiom(name: Name, typ: LeanExpr) -> LeanConstantInfo { - LeanConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { name, level_params: vec![], typ }, - is_unsafe: false, - }) - } - - /// A full selection keeps every name, including names that start with an - /// underscore; a scoped one is the requested source closure. - #[test] - fn source_import_keeps_public_names_and_scoped_dependencies() { - let env = DashMap::new(); - let root = Name::str(Name::anon(), "ImportView".into()); - let source = Name::str(root.clone(), "source".into()); - let dependency = Name::str(root.clone(), "dependency".into()); - let unrelated = Name::str(root, "unrelated_ix_name".into()); - let source_private = - Name::str(Name::str(Name::anon(), "_private".into()), "original".into()); - for name in [&dependency, &unrelated, &source_private] { - env.insert( - name.clone(), - import_view_axiom(name.clone(), LeanExpr::sort(Level::zero())), - ); - } - env.insert( - source.clone(), - import_view_axiom( - source.clone(), - LeanExpr::cnst(dependency.clone(), vec![]), - ), - ); - let mut all = - vec![source.clone(), dependency.clone(), unrelated, source_private]; - all.sort_unstable(); - assert_eq!(materialized_source_names(&env, &[]).unwrap(), all); - let mut scoped = vec![source.clone(), dependency]; - scoped.sort_unstable(); - assert_eq!(materialized_source_names(&env, &[source]).unwrap(), scoped); - } - - /// A missing requested name and a dangling reference are refused, the - /// latter for a full selection too. - #[test] - fn source_import_rejects_missing_roots_and_dependencies() { - let env = DashMap::new(); - let source = Name::str(Name::anon(), "Source".into()); - let missing = Name::str(Name::anon(), "Missing".into()); - assert!( - materialized_source_names(&env, std::slice::from_ref(&missing)) - .unwrap_err() - .contains("not found") - ); - env.insert( - source.clone(), - import_view_axiom(source.clone(), LeanExpr::cnst(missing, vec![])), - ); - for roots in [vec![], vec![source]] { - assert!( - materialized_source_names(&env, &roots) - .unwrap_err() - .contains("broken closure") - ); - } - } - - /// A decompiled name that is not in the original counts as `missing` and - /// is listed in `extra_names`. - #[test] - fn check_decompile_counts_names_not_in_the_original() { - let source = Name::str(Name::anon(), "Source".into()); - let extra = Name::str(Name::anon(), "Extra".into()); - let axiom = - |n: &Name| import_view_axiom(n.clone(), LeanExpr::sort(Level::zero())); - let mut original = LeanEnv::default(); - original.insert(source.clone(), axiom(&source)); - let dstt = DecompileState::default(); - dstt.env.insert(source.clone(), axiom(&source)); - let stt = CompileState::default(); - let r = check_decompile(&original, &stt, &dstt).unwrap(); - assert_eq!((r.matches, r.missing), (1, 0)); - assert!(r.extra_names.is_empty(), "{:?}", r.extra_names); - dstt.env.insert(extra.clone(), axiom(&extra)); - let r = check_decompile(&original, &stt, &dstt).unwrap(); - assert_eq!((r.matches, r.missing), (1, 1)); - assert_eq!(r.extra_names, vec![extra.pretty()]); - } - - /// `CheckResult::failures` counts every absent original the caller does - /// not expect, besides mismatches and extra names. - #[test] - fn check_failures_count_mismatches_and_unexpected_absences() { - let expected = Name::str(Name::anon(), "Ungrounded".into()); - let unexpected = Name::str(Name::anon(), "Lost".into()); - let result = CheckResult { - matches: 3, - mismatches: 1, - missing: 1, - extra_names: vec!["Extra".into()], - absent: vec![expected.clone(), unexpected], - }; - assert_eq!(result.failures(|n| *n == expected), 3); - let clean = CheckResult { - matches: 3, - mismatches: 0, - missing: 0, - extra_names: vec![], - absent: vec![expected.clone()], - }; - assert_eq!(clean.failures(|n| *n == expected), 0); - } -} - -// =========================================================================== -// Extended index space for `Share` inside `meta_sharing` -// -// `Share(i)` inside `meta_sharing[j]` denotes primary entry `i` (`i < p`) or -// `meta_sharing[i - p]` (`p <= i < p + j`); anything else is -// `InvalidMetaShareIndex`. No compiler emits such a share today, so these -// hand-built fixtures pin the table check (`validate_meta_sharing`). They -// mirror the Lean `decompile-unit` "metadata share" tests in -// `Tests/Ix/Decompile.lean`. -// -// Every fixture has one primary entry (`p = 1`), `prim0 = (#1 #2)`. -// =========================================================================== - -#[cfg(test)] -mod meta_share_tests { - //! The `meta_sharing` table check (`validate_meta_sharing`), directly and - //! through `load_meta_extensions` on the whole `decompile_env` path. - use super::*; - use crate::compile::{compile_name, store_nat}; - - struct Fx { - stt: CompileState, - head_addr: Address, - key_addr: Address, - } - - fn fx() -> Fx { - let stt = CompileState::default(); - let head = Name::str(Name::anon(), "MS.head".to_string()); - let head_addr = compile_name(&head, &stt).unwrap(); - let key = Name::str(Name::anon(), "MS.key".to_string()); - let key_addr = compile_name(&key, &stt).unwrap(); - Fx { stt, head_addr, key_addr } - } - - fn prim0() -> Arc { - Expr::app(Expr::var(1), Expr::var(2)) - } - /// `meta_sharing[0]`, shared: `Share(0)` is primary entry 0. - fn shared_m0() -> Arc { - Expr::app(Expr::share(0), Expr::var(3)) - } - /// `meta_sharing[1]`, shared: `Share(1) = p + 0` is `meta_sharing[0]`. - fn shared_m1() -> Arc { - Expr::app(Expr::share(1), Expr::share(0)) - } - fn plain_m0() -> Arc { - Expr::app(prim0(), Expr::var(3)) - } - fn plain_m1() -> Arc { - Expr::app(plain_m0(), prim0()) - } - - /// The primary root `head #99`. - fn root() -> Arc { - Expr::app(Expr::reference(0, vec![]), Expr::var(99)) - } - - /// Arena `0` a leaf, `1` an ordinary metadata layer `[(MS.key, 1)]` - /// over it. - fn arena(fx: &Fx) -> ExprMeta { - let nat = - |n: u64| DataValue::OfNat(store_nat(&Nat::from(n), &fx.stt).unwrap()); - let mut arena = ExprMeta::default(); - let leaf = arena.alloc(ExprMetaData::Leaf); - arena.alloc(ExprMetaData::Mdata { - mdata: vec![vec![(fx.key_addr.clone(), nat(1))]], - child: leaf, - }); - arena - } - - fn assert_meta_share_err( - r: Result, - want_idx: u64, - want_entry: u64, - want_p: usize, - want_q: usize, - ) { - match r { - Err(DecompileError::InvalidMetaShareIndex { - idx, - entry, - primary_len, - meta_len, - .. - }) => assert_eq!( - (idx, entry, primary_len, meta_len), - (want_idx, want_entry, want_p, want_q) - ), - other => panic!("expected InvalidMetaShareIndex, got {other:?}"), - } - } - - #[test] - fn meta_share_forward_reference_rejected() { - let table = vec![Expr::app(Expr::share(2), Expr::var(3)), Expr::var(4)]; - assert_meta_share_err(validate_meta_sharing(1, &table, "t"), 2, 0, 1, 2); - } - - #[test] - fn meta_share_self_reference_rejected() { - let table = vec![Expr::app(Expr::share(1), Expr::var(3))]; - assert_meta_share_err(validate_meta_sharing(1, &table, "t"), 1, 0, 1, 1); - } - - #[test] - fn meta_share_out_of_range_rejected() { - let table = vec![Expr::var(0), Expr::app(Expr::share(5), Expr::var(3))]; - assert_meta_share_err(validate_meta_sharing(1, &table, "t"), 5, 1, 1, 2); - } - - /// Register an axiom `MS.ax` whose type carries an ordinary metadata - /// layer, with the given `meta_sharing`, and run the whole - /// `decompile_env` path (the table is checked by `load_meta_extensions`). - fn decompile_axiom_env( - meta_sharing: Vec>, - ) -> (Result, Fx, Name) { - let fx = fx(); - let ax_name = Name::str(Name::anon(), "MS.ax".to_string()); - let ax_addr_name = compile_name(&ax_name, &fx.stt).unwrap(); - let ax = Constant { - info: ConstantInfo::Axio(Axiom { - is_unsafe: false, - lvls: 0, - typ: root(), - }), - sharing: vec![prim0()], - refs: vec![fx.head_addr.clone()], - univs: vec![], - }; - let mut bytes = Vec::new(); - ax.put(&mut bytes); - let addr = Address::hash(&bytes); - fx.stt.env.store_const(addr.clone(), ax); - let mut meta = ConstantMeta::new(ConstantMetaInfo::Axio { - name: ax_addr_name, - lvls: vec![], - arena: arena(&fx), - type_root: 1, - }); - meta.meta_sharing = meta_sharing; - fx.stt.env.register_name(ax_name.clone(), Named::new(addr, meta)); - (decompile_env(&fx.stt), fx, ax_name) - } - - /// No entry is read, and entry 2 references itself - /// (`Share(3) = p + 2`): the table check still rejects it. - #[test] - fn meta_share_unread_malformed_entry_rejected() { - let (dstt, _, _) = decompile_axiom_env(vec![ - shared_m0(), - shared_m1(), - Expr::app(Expr::share(3), Expr::var(0)), - ]); - assert_meta_share_err(dstt.map(|_| ()), 3, 2, 1, 3); - } - - /// Regression: metadata without shares behaves as before. - #[test] - fn meta_share_tables_without_shares_unchanged() { - validate_meta_sharing(0, &[plain_m0(), plain_m1()], "t") - .expect("no shares: valid against an empty primary table"); - } - - /// The table check visits each shared node once. In-memory tables share - /// subterms through `Arc`s (the compiler's expression memo); on the chain - /// `e(k+1) = App(e(k), e(k))` a tree walk is 2^depth (depth 26 took 1.6 s, - /// doubling per level), so depth 64 only finishes as a DAG walk. A - /// violation behind shared subterms is still the one the tree walk - /// reports first. - #[test] - fn validate_meta_sharing_visits_shared_subterms_once() { - let mut e = Expr::app(Expr::share(0), Expr::var(0)); - for _ in 0..64 { - e = Expr::app(e.clone(), e); - } - validate_meta_sharing(1, &[e.clone(), e.clone()], "t") - .expect("every share is primary entry 0"); - // Entry 1 reads `e` (seen in entry 0) and then `Share(2)`, a self - // reference of entry 1 (`p + 1 = 2`). - let err = validate_meta_sharing( - 1, - &[e.clone(), Expr::app(e.clone(), Expr::share(2))], - "t", - ) - .unwrap_err(); - assert_eq!( - err, - DecompileError::InvalidMetaShareIndex { - idx: 2, - entry: 1, - primary_len: 1, - meta_len: 2, - constant: "t".into(), - } - ); - // Inside the shared chain itself, the first violation in pre-order is - // the leftmost leaf: `Share(0)` against an empty primary table. - let err = validate_meta_sharing(0, &[e], "t").unwrap_err(); - assert!( - matches!( - err, - DecompileError::InvalidMetaShareIndex { idx: 0, entry: 0, .. } - ), - "{err}" - ); - } -} diff --git a/crates/compile/src/diag.rs b/crates/compile/src/diag.rs deleted file mode 100644 index 9a84d395c..000000000 --- a/crates/compile/src/diag.rs +++ /dev/null @@ -1,170 +0,0 @@ -//! Shared diagnostics helpers for phase logging. - -use std::io::Read; -use std::sync::{LazyLock, mpsc}; -use std::thread::{self, JoinHandle}; -use std::time::Duration; - -static MEMORY_DIAG: LazyLock = - LazyLock::new(|| std::env::var_os("IX_MEMORY_DIAG").is_some()); - -fn field_kb(text: &str, name: &str) -> Option { - text.lines().find_map(|line| { - line.strip_prefix(name)?.split_whitespace().next()?.parse().ok() - }) -} - -/// ` · rss X.X GiB (anon Y.Y, file Z.Z)` sampled from -/// `/proc/self/status`, appended to phase logs. Anon can only leave RAM -/// via swap; file RSS is reclaimable page cache — the split shows which -/// memory-reduction lever applies. Empty when procfs is unavailable. -pub fn rss_log_suffix() -> String { - let Ok(status) = std::fs::read_to_string("/proc/self/status") else { - return String::new(); - }; - let gib_tenths = |kb: u64| -> (u64, u64) { - let tenths = kb * 10 / (1024 * 1024); - (tenths / 10, tenths % 10) - }; - match ( - field_kb(&status, "VmRSS:"), - field_kb(&status, "RssAnon:"), - field_kb(&status, "RssFile:"), - ) { - (Some(rss), Some(anon), Some(file)) => { - let (r, rt) = gib_tenths(rss); - let (a, at) = gib_tenths(anon); - let (f, ft) = gib_tenths(file); - format!(" · rss {r}.{rt} GiB (anon {a}.{at}, file {f}.{ft})") - }, - _ => String::new(), - } -} - -// Stream maps instead of allocating a String proportional to the mapping -// count: this diagnostic must remain small even near vm.max_map_count. -fn count_lines(mut input: impl Read) -> std::io::Result { - let mut buffer = [0u8; 8192]; - let mut count = 0; - loop { - let n = input.read(&mut buffer)?; - if n == 0 { - return Ok(count); - } - count += buffer[..n].iter().filter(|&&b| b == b'\n').count() as u64; - } -} - -// Diagnostic output rounds to tenths of a GiB, not an exact byte count. -#[allow(clippy::cast_precision_loss)] -fn gib_field(text: &str, name: &str) -> String { - field_kb(text, name).map_or_else( - || "?".into(), - |kb| format!("{:.1}", kb as f64 / (1024.0 * 1024.0)), - ) -} - -/// Detailed Linux memory snapshot for diagnosing allocation failures that -/// need not be physical OOMs (e.g. address-space or mapping-count limits). -fn memory_snapshot() -> String { - let Ok(status) = std::fs::read_to_string("/proc/self/status") else { - return "memory diagnostics unavailable (no /proc/self/status)".into(); - }; - let meminfo = std::fs::read_to_string("/proc/meminfo").unwrap_or_default(); - let maps = std::fs::File::open("/proc/self/maps") - .ok() - .and_then(|file| count_lines(file).ok()) - .map_or_else(|| "?".into(), |n| n.to_string()); - let map_limit = std::fs::read_to_string("/proc/sys/vm/max_map_count") - .unwrap_or_else(|_| "?".into()); - let overcommit = std::fs::read_to_string("/proc/sys/vm/overcommit_memory") - .unwrap_or_else(|_| "?".into()); - format!( - "rss={} GiB (anon {}, file {}), vmsize={} GiB, peak_rss={} GiB, \ - process_swap={} GiB, available={} GiB, swap_free={}/{} GiB, \ - committed={}/{} GiB (overcommit={}), maps={}/{}", - gib_field(&status, "VmRSS:"), - gib_field(&status, "RssAnon:"), - gib_field(&status, "RssFile:"), - gib_field(&status, "VmSize:"), - gib_field(&status, "VmHWM:"), - gib_field(&status, "VmSwap:"), - gib_field(&meminfo, "MemAvailable:"), - gib_field(&meminfo, "SwapFree:"), - gib_field(&meminfo, "SwapTotal:"), - gib_field(&meminfo, "Committed_AS:"), - gib_field(&meminfo, "CommitLimit:"), - overcommit.trim(), - maps, - map_limit.trim(), - ) -} - -/// Scope guard for an opt-in periodic memory sampler. Dropping it wakes and -/// joins the thread immediately, including on early compilation errors. -pub struct MemorySampler { - stop: mpsc::Sender<()>, - thread: Option>, -} - -impl Drop for MemorySampler { - fn drop(&mut self) { - let _ = self.stop.send(()); - if let Some(thread) = self.thread.take() { - let _ = thread.join(); - } - } -} - -/// With `IX_MEMORY_DIAG=1`, log process/system memory every five seconds, -/// including inside phases that never reach their completion log on OOM. -pub fn memory_sampler(label: &'static str) -> Option { - if !*MEMORY_DIAG { - return None; - } - let (stop, receiver) = mpsc::channel(); - match thread::Builder::new().name("ix-memory-diag".into()).spawn(move || { - loop { - eprintln!("[{label}] memory: {}", memory_snapshot()); - match receiver.recv_timeout(Duration::from_secs(5)) { - Err(mpsc::RecvTimeoutError::Timeout) => {}, - _ => break, - } - } - }) { - Ok(thread) => Some(MemorySampler { stop, thread: Some(thread) }), - Err(error) => { - eprintln!("[{label}] could not start memory diagnostics: {error}"); - None - }, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parses_proc_fields_without_confusing_similar_names() { - let status = "VmRSS:\t1048576 kB\nRssAnon:\t524288 kB\n"; - assert_eq!(field_kb(status, "VmRSS:"), Some(1048576)); - assert_eq!(gib_field(status, "RssAnon:"), "0.5"); - assert_eq!(field_kb(status, "VmSwap:"), None); - assert_eq!(gib_field(status, "VmSwap:"), "?"); - assert_eq!(field_kb("VmRSS: invalid kB\n", "VmRSS:"), None); - } - - #[test] - fn counts_maps_across_buffer_boundaries() { - let maps = "some mapping\n".repeat(2000); - assert_eq!(count_lines(maps.as_bytes()).unwrap(), 2000); - assert_eq!(count_lines(&b""[..]).unwrap(), 0); - } - - #[test] - fn sampler_guard_wakes_and_joins_its_thread() { - let (stop, receiver) = mpsc::channel(); - let thread = thread::spawn(move || receiver.recv().unwrap()); - drop(MemorySampler { stop, thread: Some(thread) }); - } -} diff --git a/crates/compile/src/graph.rs b/crates/compile/src/graph.rs deleted file mode 100644 index 9ccffe71f..000000000 --- a/crates/compile/src/graph.rs +++ /dev/null @@ -1,1122 +0,0 @@ -//! Builds a reference graph from a Lean environment. -//! -//! The graph tracks which constants reference which other constants, maintaining -//! both forward (`out_refs`) and reverse (`in_refs`) edges. This is used to -//! compute SCCs (strongly connected components) for mutual block detection. -//! Construction is parallelized via rayon. - -use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use rustc_hash::{FxHashMap, FxHashSet}; -use std::collections::hash_map::Entry; - -use ix_common::env::{ConstantInfo, Env, Expr, ExprData, Name, NameData}; - -/// A set of [`Name`]s, used to represent the neighbors of a node in the reference graph. -pub type NameSet = FxHashSet; - -/// Absorbs the elements of the smaller [`NameSet`] into the bigger one and returns -/// the merged set. -pub fn merge_name_sets(mut a: NameSet, mut b: NameSet) -> NameSet { - if a.len() < b.len() { - b.extend(a); - b - } else { - a.extend(b); - a - } -} - -/// Maps each [`Name`] to the set of [`Name`]s it is associated with. -pub type RefMap = FxHashMap; - -/// A bidirectional reference graph over [`Name`]s, storing both forward and reverse edges. -/// ```ignored -/// A ──> B ──> C <── D ──> E -/// out_refs: [(A, [B]), (B, [C]), (C, []), (D, [C, E]), (E, [])] -/// in_refs: [(A, []), (B, [A]), (C, [B, D]), (D, []), (E, [D])] -/// ``` -#[derive(Default)] -pub struct RefGraph { - /// Maps names to the names they reference - pub out_refs: RefMap, - /// Maps names to the names that reference them - pub in_refs: RefMap, -} - -/// Builds a [`RefGraph`] from a Lean [`Env`] by collecting all constant references in parallel. -/// -/// For each constant, extracts the set of names it references (from types, values, constructors, -/// and recursor rules), then assembles both the forward and reverse edge maps. -/// Everything the compile-env setup needs from a whole-env pass: the -/// reference graph, the immediately-ungrounded set (before transitive -/// proliferation), and the inductive mutual-block groups -/// (`all[0] → all`, for flag validation). -pub struct SetupScan { - pub graph: RefGraph, - pub immediate_ungrounded: FxHashMap, - pub ind_groups: FxHashMap>, - /// Source contracts that the current conservative emitter cannot preserve. - pub source_contracts: Vec, - /// Semantic declarations validated during the same lazy decode as the graph. - pub semantic_sources: Result, ixon::CompileError>, -} - -/// Fused whole-env setup pass: one decode per constant feeding the ref -/// graph, the groundedness check, and inductive-group collection. -/// The compile path's lazy environment can decode a constant on each -/// access, so one scan avoids the repeat decodes of separate graph, -/// grounding, and inductive-group scans. Outputs match those passes. -/// -/// Fold directly into chunk-local maps instead of allocating and merging -/// graph fragments for every constant. Reverse edges to a common dependency -/// share one set within the chunk; only completed chunk maps are reduced. -/// Rayon chooses the chunks dynamically, retaining work-stealing balance -/// when some constants take much longer to decode and traverse than others. -pub fn setup_scan(env: &Env) -> SetupScan { - #[derive(Default)] - struct Acc { - out_refs: RefMap, - in_refs: RefMap, - ungrounded: FxHashMap, - ind_groups: FxHashMap>, - source_contracts: Vec, - semantic_sources: Vec, - semantic_error: Option, - } - - let names: Vec<&Name> = env.keys().collect(); - let acc = names - .into_par_iter() - .fold(Acc::default, |mut acc, name| { - let Some(constant) = env.get(name) else { - return acc; - }; - match crate::semantic_contract::inspect_constant(&constant) { - Ok(true) => acc.semantic_sources.push(name.clone()), - Ok(false) => {}, - Err(error) => acc.semantic_error = Some(error), - } - let (deps, keys) = inspect_constant_references(&constant); - if keys.source_contract { - acc.source_contracts.push(name.clone()); - } - // Keep an empty reverse-edge entry even for an isolated constant. - // Missing referenced names also retain their incoming edges, as in - // the reference graph used by ungroundedness propagation. - acc.in_refs.entry(name.clone()).or_default(); - for dep in &deps { - acc.in_refs.entry(dep.clone()).or_default().insert(name.clone()); - } - acc.out_refs.insert(name.clone(), deps); - if let Err(err) = crate::ground::ground_const_check(&constant, env) { - acc.ungrounded.insert(name.clone(), err); - } - // Members of one mutual family share the same `all`, so - // first-wins insertion is value-identical regardless of which - // member lands first. - if let ConstantInfo::InductInfo(v) = &*constant - && let Some(first) = v.all.first() - { - acc.ind_groups.entry(first.clone()).or_insert_with(|| v.all.clone()); - } - acc - }) - .reduce(Acc::default, |mut l, r| { - l.out_refs = merge_ref_maps(l.out_refs, r.out_refs); - l.in_refs = merge_ref_maps(l.in_refs, r.in_refs); - l.ungrounded.extend(r.ungrounded); - l.source_contracts.extend(r.source_contracts); - l.semantic_sources.extend(r.semantic_sources); - l.semantic_error = l.semantic_error.or(r.semantic_error); - for (k, v) in r.ind_groups { - l.ind_groups.entry(k).or_insert(v); - } - l - }); - - SetupScan { - graph: RefGraph { out_refs: acc.out_refs, in_refs: acc.in_refs }, - immediate_ungrounded: acc.ungrounded, - ind_groups: acc.ind_groups, - source_contracts: acc.source_contracts, - semantic_sources: match acc.semantic_error { - Some(error) => Err(error), - None => Ok(acc.semantic_sources), - }, - } -} - -/// Size-aware map union (drain the smaller side into the bigger). -fn merge_ref_maps(l: RefMap, r: RefMap) -> RefMap { - let (smaller, mut bigger) = if l.len() < r.len() { (l, r) } else { (r, l) }; - for (name, set) in smaller { - match bigger.entry(name) { - Entry::Vacant(entry) => { - entry.insert(set); - }, - Entry::Occupied(mut entry) => { - entry.get_mut().extend(set); - }, - } - } - bigger -} - -pub fn build_ref_graph(env: &Env) -> RefGraph { - let mk_in_refs = |name: &Name, deps: &NameSet| -> RefMap { - let mut in_refs = RefMap::from_iter([(name.clone(), NameSet::default())]); - for dep in deps { - match in_refs.entry(dep.clone()) { - Entry::Vacant(entry) => { - entry.insert(NameSet::from_iter([name.clone()])); - }, - Entry::Occupied(mut entry) => { - entry.get_mut().insert(name.clone()); - }, - } - } - in_refs - }; - - let merge = |l: RefMap, r: RefMap| -> RefMap { - let (smaller, mut bigger) = if l.len() < r.len() { (l, r) } else { (r, l) }; - for (name, set) in smaller { - match bigger.entry(name) { - Entry::Vacant(entry) => { - entry.insert(set); - }, - Entry::Occupied(mut entry) => { - entry.get_mut().extend(set); - }, - } - } - bigger - }; - - let names: Vec<&Name> = env.keys().collect(); - let (out_refs, in_refs) = names - .into_par_iter() - .filter_map(|name| { - let constant = env.get(name)?; - let deps = get_constant_info_references(&constant); - let in_refs = mk_in_refs(name, &deps); - let out_refs = RefMap::from_iter([(name.clone(), deps)]); - Some((out_refs, in_refs)) - }) - .reduce( - || (RefMap::default(), RefMap::default()), - |(out_l, in_l), (out_r, in_r)| (merge(out_l, out_r), merge(in_l, in_r)), - ); - - //assert_eq!(env.len(), out_refs.len()); - //assert_eq!(out_refs.len(), in_refs.len()); - RefGraph { out_refs, in_refs } -} - -pub fn get_constant_info_references(constant_info: &ConstantInfo) -> NameSet { - inspect_constant_references(constant_info).0 -} - -fn inspect_constant_references( - constant_info: &ConstantInfo, -) -> (NameSet, MdataKeys) { - let mut acc = NameSet::default(); - let mut visited: FxHashSet<&Expr> = FxHashSet::default(); - let mut keys = MdataKeys::default(); - match constant_info { - ConstantInfo::AxiomInfo(val) => { - keys |= collect_expr_references(&val.cnst.typ, &mut visited, &mut acc); - }, - ConstantInfo::DefnInfo(val) => { - keys |= collect_expr_references(&val.cnst.typ, &mut visited, &mut acc); - keys |= collect_expr_references(&val.value, &mut visited, &mut acc); - }, - ConstantInfo::ThmInfo(val) => { - keys |= collect_expr_references(&val.cnst.typ, &mut visited, &mut acc); - keys |= collect_expr_references(&val.value, &mut visited, &mut acc); - }, - ConstantInfo::OpaqueInfo(val) => { - keys |= collect_expr_references(&val.cnst.typ, &mut visited, &mut acc); - keys |= collect_expr_references(&val.value, &mut visited, &mut acc); - }, - ConstantInfo::QuotInfo(val) => { - keys |= collect_expr_references(&val.cnst.typ, &mut visited, &mut acc); - }, - ConstantInfo::InductInfo(val) => { - keys |= collect_expr_references(&val.cnst.typ, &mut visited, &mut acc); - acc.extend(val.ctors.iter().cloned()); - // The other members of the Lean block: its order is structural. - acc.extend(val.all.iter().filter(|n| **n != val.cnst.name).cloned()); - }, - ConstantInfo::CtorInfo(val) => { - keys |= collect_expr_references(&val.cnst.typ, &mut visited, &mut acc); - acc.insert(val.induct.clone()); - }, - ConstantInfo::RecInfo(val) => { - keys |= collect_expr_references(&val.cnst.typ, &mut visited, &mut acc); - for rule in &val.rules { - acc.insert(rule.ctor.clone()); - keys |= collect_expr_references(&rule.rhs, &mut visited, &mut acc); - } - // The other recursors of the family: its order is structural. - let motives = val.num_motives.to_u64().unwrap_or(0); - for r in recursor_family_names(&val.all, motives) { - if r != val.cnst.name { - acc.insert(r); - } - } - }, - } - (acc, keys) -} - -/// The recursors of an inductive block, in the family's declared order: -/// `X.rec` for each member `X` of `all`, then the nested auxiliary -/// recursors `all[0].rec_N`, `N = 1 ..= num_motives - all.len()`. Mirrors -/// Lean `Ix.recursorFamilyNames`. -pub fn recursor_family_names(all: &[Name], num_motives: u64) -> Vec { - let mut out: Vec = - all.iter().map(|n| Name::str(n.clone(), "rec".to_string())).collect(); - if let Some(head) = all.first() { - let nested = num_motives.saturating_sub(all.len() as u64); - for k in 1..=nested { - out.push(Name::str(head.clone(), format!("rec_{k}"))); - } - } - out -} - -/// The metadata keys a reference walk found. -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct MdataKeys { - /// A source-contract key (`ix.source.binder`). - source_contract: bool, -} - -impl std::ops::BitOrAssign for MdataKeys { - fn bitor_assign(&mut self, other: Self) { - self.source_contract |= other.source_contract; - } -} - -/// Match the reserved Lean metadata namespace by components, without allocating -/// strings or relying on a hash match. This also catches malformed markers. -fn is_source_contract_key(mut key: &Name) -> bool { - loop { - if let NameData::Str(source_parent, binder, _) = key.as_data() - && binder == "binder" - && let NameData::Str(ix_parent, source, _) = source_parent.as_data() - && source == "source" - && let NameData::Str(root, ix, _) = ix_parent.as_data() - && ix == "ix" - && matches!(root.as_data(), NameData::Anonymous(_)) - { - return true; - } - match key.as_data() { - NameData::Str(parent, ..) | NameData::Num(parent, ..) => key = parent, - NameData::Anonymous(_) => return false, - } - } -} - -/// Iterative DAG walk pushing every `Const`/`Proj` head into one shared -/// accumulator. `visited` keys on `Expr`'s digest-backed `Hash`/`Eq` -/// (Arc pointer fast path), so each distinct subterm — structurally -/// shared or not — is expanded once, exactly the dedup the old -/// per-constant memo provided. Θ(n + r) total: the old walk returned a -/// fresh `NameSet` per node and merged/cloned it into every parent -/// (Θ(n·r) allocations on ref-heavy constants). This feeds both -/// compile's `setup_scan` and decompile's Pass-2 ingress BFS. The -/// accumulated set is value-identical to the old result; no downstream -/// consumer reads set iteration order into output bytes (SCC members -/// and serialized sections are canonically re-sorted). -pub(crate) fn collect_expr_references<'a>( - expr: &'a Expr, - visited: &mut FxHashSet<&'a Expr>, - acc: &mut NameSet, -) -> MdataKeys { - let mut keys = MdataKeys::default(); - let mut stack: Vec<&'a Expr> = vec![expr]; - while let Some(e) = stack.pop() { - if !visited.insert(e) { - continue; - } - match e.as_data() { - ExprData::Const(name, ..) => { - acc.insert(name.clone()); - }, - ExprData::App(f, a, _) => { - stack.push(f); - stack.push(a); - }, - ExprData::Lam(_, typ, body, ..) | ExprData::ForallE(_, typ, body, ..) => { - stack.push(typ); - stack.push(body); - }, - ExprData::LetE(_, typ, value, body, ..) => { - stack.push(typ); - stack.push(value); - stack.push(body); - }, - ExprData::Mdata(data, inner, _) => { - for (key, _) in data.iter() { - keys.source_contract |= is_source_contract_key(key); - } - stack.push(inner); - }, - ExprData::Proj(type_name, _, inner, _) => { - acc.insert(type_name.clone()); - stack.push(inner); - }, - _ => {}, - } - } - keys -} - -#[cfg(test)] -mod tests { - use super::*; - use bignat::Nat; - use ix_common::env::*; - - fn n(s: &str) -> Name { - Name::str(Name::anon(), s.to_string()) - } - - fn sort0() -> Expr { - Expr::sort(Level::zero()) - } - - fn mk_cv(name: &str) -> ConstantVal { - ConstantVal { name: n(name), level_params: vec![], typ: sort0() } - } - - fn source_key() -> Name { - ["ix", "source", "binder"] - .into_iter() - .fold(Name::anon(), |parent, part| Name::str(parent, part.to_owned())) - } - - #[test] - fn source_contract_namespace_is_structural() { - let key = source_key(); - assert!(is_source_contract_key(&key)); - assert!(is_source_contract_key(&Name::str(key.clone(), "owned".into()))); - assert!(is_source_contract_key(&Name::num(key, Nat::from(1u64)))); - assert!(!is_source_contract_key(&n("ix.source.binder"))); - assert!(!is_source_contract_key(&n("borrowed"))); - assert!(!is_source_contract_key(&Name::str( - Name::str(n("ix"), "source".into()), - "binders".into() - ))); - } - - #[test] - fn source_contract_environment_rejects_before_emission() { - use crate::compile::{CompileOptions, compile_env_with_options}; - use std::sync::Arc; - - let mut env = Env::default(); - for name in ["ZAnnotated", "AAnnotated"] { - env.insert( - n(name), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: n(name), - level_params: vec![], - // Even malformed markers must not be discarded as presentation data. - typ: Expr::mdata( - vec![(source_key(), DataValue::OfBool(true))], - sort0(), - ), - }, - is_unsafe: false, - }), - ); - } - let env = Arc::new(env); - for workers in [1, 4] { - let pool = - rayon::ThreadPoolBuilder::new().num_threads(workers).build().unwrap(); - let result = pool - .install(|| compile_env_with_options(&env, CompileOptions::default())); - assert!( - matches!(result, Err(ixon::CompileError::UnsupportedExpr { desc }) - if desc == "unresolved source binder contracts: AAnnotated") - ); - } - } - - #[test] - fn source_contract_scan_covers_declaration_bodies() { - let mut env = Env::default(); - let name = n("BodyMarker"); - env.insert( - name.clone(), - ConstantInfo::DefnInfo(DefinitionVal { - cnst: ConstantVal { - name: name.clone(), - level_params: vec![], - typ: Expr::sort(Level::succ(Level::zero())), - }, - value: Expr::mdata( - vec![(source_key(), DataValue::OfBool(true))], - sort0(), - ), - hints: ReducibilityHints::Abbrev, - safety: DefinitionSafety::Safe, - all: vec![name.clone()], - }), - ); - assert_eq!(setup_scan(&env).source_contracts, vec![name]); - } - - #[test] - fn setup_scan_preserves_semantic_validation() { - use crate::semantic_contract::{Contract, Kind}; - use ixon::contract::{BinderContract, LetKind, ValueContract}; - let contract = Contract { - kind: Kind::All, - binder: BinderContract::default(), - result: ValueContract::shared(), - let_kind: LetKind::Value, - }; - let name = n("Semantic"); - let mut env = Env::default(); - let axiom = |typ| { - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { name: name.clone(), level_params: vec![], typ }, - is_unsafe: false, - }) - }; - env.insert( - name.clone(), - axiom(contract.attach(Expr::all( - n("x"), - sort0(), - sort0(), - BinderInfo::Default, - ))), - ); - assert_eq!(setup_scan(&env).semantic_sources.unwrap(), vec![name.clone()]); - // A well-formed frame attached to the wrong expression must still fail - // before compilation, even though validation now shares the graph decode. - env.insert(name.clone(), axiom(contract.attach(sort0()))); - assert!(setup_scan(&env).semantic_sources.is_err()); - } - - #[test] - fn source_contract_guard_preserves_native_borrow_metadata() { - use crate::compile::{CompileOptions, compile_env_with_options}; - use std::sync::Arc; - - let name = n("Ordinary"); - let compile = |borrowed: bool| { - let mut env = Env::default(); - let domain = if borrowed { - Expr::mdata(vec![(n("borrowed"), DataValue::OfBool(true))], sort0()) - } else { - sort0() - }; - env.insert( - name.clone(), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: name.clone(), - level_params: vec![], - typ: Expr::all(n("x"), domain, sort0(), BinderInfo::Default), - }, - is_unsafe: false, - }), - ); - assert!(setup_scan(&env).source_contracts.is_empty()); - let result = - compile_env_with_options(&Arc::new(env), CompileOptions::default()) - .unwrap(); - result.name_to_addr.get(&name).unwrap().value().clone() - }; - assert_eq!(compile(false), compile(true)); - } - - /// Frozen per-constant map/reduce implementation for differential tests - /// and the opt-in performance comparison below. - fn setup_scan_reference(env: &Env) -> SetupScan { - let empty = || SetupScan { - graph: RefGraph::default(), - immediate_ungrounded: FxHashMap::default(), - ind_groups: FxHashMap::default(), - source_contracts: Vec::new(), - semantic_sources: Ok(Vec::new()), - }; - let names: Vec<_> = env.keys().collect(); - names - .into_par_iter() - .filter_map(|name| { - let constant = env.get(name)?; - let deps = get_constant_info_references(&constant); - let mut scan = empty(); - scan.graph.in_refs = - RefMap::from_iter([(name.clone(), NameSet::default())]); - for dep in &deps { - match scan.graph.in_refs.entry(dep.clone()) { - Entry::Vacant(entry) => { - entry.insert(NameSet::from_iter([name.clone()])); - }, - Entry::Occupied(mut entry) => { - entry.get_mut().insert(name.clone()); - }, - } - } - scan.graph.out_refs = RefMap::from_iter([(name.clone(), deps)]); - if let Err(err) = crate::ground::ground_const_check(&constant, env) { - scan.immediate_ungrounded.insert(name.clone(), err); - } - if let ConstantInfo::InductInfo(v) = &*constant - && let Some(first) = v.all.first() - { - scan.ind_groups.insert(first.clone(), v.all.clone()); - } - Some(scan) - }) - .reduce(empty, |mut l, r| { - l.graph.out_refs = merge_ref_maps(l.graph.out_refs, r.graph.out_refs); - l.graph.in_refs = merge_ref_maps(l.graph.in_refs, r.graph.in_refs); - l.immediate_ungrounded.extend(r.immediate_ungrounded); - for (k, v) in r.ind_groups { - l.ind_groups.entry(k).or_insert(v); - } - l - }) - } - - fn assert_same_scan(actual: &SetupScan, expected: &SetupScan) { - assert_eq!(actual.graph.out_refs, expected.graph.out_refs); - assert_eq!(actual.graph.in_refs, expected.graph.in_refs); - assert_eq!(actual.ind_groups, expected.ind_groups); - assert_eq!(actual.source_contracts, expected.source_contracts); - assert_eq!( - actual.immediate_ungrounded.len(), - expected.immediate_ungrounded.len() - ); - for (name, expected) in &expected.immediate_ungrounded { - assert_eq!( - format!("{:?}", actual.immediate_ungrounded.get(name).unwrap()), - format!("{expected:?}"), - "grounding error for {name}", - ); - } - } - - /// Well-typed aliases with both shared and local dependencies. The input - /// graph has no cycles, while many reverse edges meet at the same names. - fn setup_fixture(count: usize) -> Env { - let mut env = Env::default(); - let typ = Expr::sort(Level::succ(Level::zero())); - for name in ["Base", "Isolated"] { - env.insert( - n(name), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: n(name), - level_params: vec![], - typ: typ.clone(), - }, - is_unsafe: false, - }), - ); - } - let names: Vec<_> = (0..count).map(|i| n(&format!("alias_{i}"))).collect(); - let base = Expr::cnst(n("Base"), vec![]); - for (i, name) in names.iter().enumerate() { - let mut value = base.clone(); - for offset in [1, 3, 17, 127] { - let dep = - i.checked_sub(offset).map_or_else(|| n("Base"), |j| names[j].clone()); - value = Expr::all( - Name::anon(), - Expr::cnst(dep, vec![]), - value, - BinderInfo::Default, - ); - } - env.insert( - name.clone(), - ConstantInfo::DefnInfo(DefinitionVal { - cnst: ConstantVal { - name: name.clone(), - level_params: vec![], - typ: typ.clone(), - }, - value, - hints: ReducibilityHints::Abbrev, - safety: DefinitionSafety::Safe, - all: vec![name.clone()], - }), - ); - } - env - } - - #[test] - fn setup_scan_matches_reference_across_worker_counts() { - let mut env = setup_fixture(1024); - // Cover isolated nodes, self/cyclic edges, missing names, each family of - // grounding error, and mutual-group deduplication across fold chunks. - for (name, typ) in [ - ("Self", Expr::cnst(n("Self"), vec![])), - ("CycleA", Expr::cnst(n("CycleB"), vec![])), - ("CycleB", Expr::cnst(n("CycleA"), vec![])), - ("Missing", Expr::cnst(n("Absent"), vec![])), - ("Bvar", Expr::bvar(Nat::from(0u64))), - ("Fvar", Expr::fvar(n("local"))), - ("Mvar", Expr::mvar(n("hole"))), - ("Universe", Expr::sort(Level::param(n("u")))), - ] { - env.insert( - n(name), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { name: n(name), level_params: vec![], typ }, - is_unsafe: false, - }), - ); - } - for (name, all, ctors) in [ - ("MutA", vec![n("MutA"), n("MutB")], vec![]), - ("MutB", vec![n("MutA"), n("MutB")], vec![]), - ("EmptyGroup", vec![], vec![]), - ("BadCtor", vec![n("BadCtor")], vec![n("AbsentCtor")]), - ] { - env.insert( - n(name), - ConstantInfo::InductInfo(InductiveVal { - cnst: mk_cv(name), - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all, - ctors, - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }), - ); - } - let expected = setup_scan_reference(&env); - assert_eq!(expected.immediate_ungrounded.len(), 6); - assert!(expected.graph.in_refs[&n("Isolated")].is_empty()); - assert_eq!(expected.ind_groups.len(), 2); - let graph = build_ref_graph(&env); - assert_eq!(expected.graph.out_refs, graph.out_refs); - assert_eq!(expected.graph.in_refs, graph.in_refs); - for workers in [1, 2, 4, 8] { - let pool = - rayon::ThreadPoolBuilder::new().num_threads(workers).build().unwrap(); - pool.install(|| { - assert_same_scan(&setup_scan(&env), &expected); - assert_same_scan( - &setup_scan(&Env::default()), - &setup_scan_reference(&Env::default()), - ); - }); - } - } - - #[test] - fn setup_scan_fetches_each_lazy_entry_once_and_skips_unavailable_entries() { - use std::sync::{ - Arc, - atomic::{AtomicUsize, Ordering}, - }; - let backing = setup_fixture(256); - let expected = setup_scan_reference(&backing); - let mut names: Vec<_> = backing.keys().cloned().collect(); - names.push(n("Unavailable")); - let calls = Arc::new(AtomicUsize::new(0)); - let counted = Arc::clone(&calls); - let count = names.len(); - let env = Env::new_lazy( - names, - Box::new(move |name| { - counted.fetch_add(1, Ordering::Relaxed); - backing.get(name).map(|entry| entry.cloned()) - }), - 16, - ); - assert_same_scan(&setup_scan(&env), &expected); - assert_eq!(calls.load(Ordering::Relaxed), count); - } - - #[test] - fn setup_scan_preserves_compiled_fixture_bytes() { - use crate::compile::{CompileOptions, compile_env_with_options}; - let source = std::sync::Arc::new(setup_fixture(32)); - for workers in [1, 4] { - let compiled = compile_env_with_options( - &source, - CompileOptions { max_workers: Some(workers) }, - ) - .unwrap(); - assert!(compiled.ungrounded.is_empty()); - let mut bytes = Vec::new(); - compiled.env.put(&mut bytes).unwrap(); - // Ixon v4; both worker counts must preserve these bytes. - assert_eq!( - blake3::hash(&bytes).to_hex().as_str(), - "ea6cf4ca0e86e8db054f872a11fc2115284a9638e7b67d697940e1d19555725e" - ); - } - } - - /// Run explicitly in release mode: cargo test -p ix-compile --release - /// setup_scan_benchmark -- --ignored --nocapture --test-threads=1 - #[test] - #[ignore = "synthetic setup-scan timing comparison; run in release mode"] - fn setup_scan_benchmark() { - use std::time::{Duration, Instant}; - for count in [50_000, 250_000] { - let env = setup_fixture(count); - for workers in [1, 8, 32] { - let pool = - rayon::ThreadPoolBuilder::new().num_threads(workers).build().unwrap(); - pool.install(|| { - assert_same_scan(&setup_scan(&env), &setup_scan_reference(&env)); - let mut elapsed = [Duration::ZERO; 2]; - for trial in 0..6 { - // Alternate order to reduce allocator/cache warmup bias. Exclude - // destruction, which the compiler likewise performs after setup. - for index in [trial % 2, 1 - trial % 2] { - let start = Instant::now(); - let result = if index == 0 { - setup_scan_reference(&env) - } else { - setup_scan(&env) - }; - elapsed[index] += start.elapsed(); - std::hint::black_box(result); - } - } - eprintln!( - "setup_scan: {} constants, {workers} workers: per-constant {:.3}s, chunked {:.3}s ({:.2}x)", - env.len(), - elapsed[0].as_secs_f64() / 6.0, - elapsed[1].as_secs_f64() / 6.0, - elapsed[0].as_secs_f64() / elapsed[1].as_secs_f64() - ); - }); - } - } - } - - #[test] - fn empty_env() { - let env = Env::default(); - let graph = build_ref_graph(&env); - assert!(graph.out_refs.is_empty()); - assert!(graph.in_refs.is_empty()); - } - - #[test] - fn axiom_no_deps() { - // Axiom A : Sort 0 — references nothing - let mut env = Env::default(); - env.insert( - n("A"), - ConstantInfo::AxiomInfo(AxiomVal { cnst: mk_cv("A"), is_unsafe: false }), - ); - let graph = build_ref_graph(&env); - assert!(graph.out_refs[&n("A")].is_empty()); - assert!(graph.in_refs[&n("A")].is_empty()); - } - - #[test] - fn defn_with_const_refs() { - // B : Sort 0, defn A : B := B - // A's type refs B, A's value refs B - let mut env = Env::default(); - env.insert( - n("B"), - ConstantInfo::AxiomInfo(AxiomVal { cnst: mk_cv("B"), is_unsafe: false }), - ); - let b_ref = Expr::cnst(n("B"), vec![]); - env.insert( - n("A"), - ConstantInfo::DefnInfo(DefinitionVal { - cnst: ConstantVal { - name: n("A"), - level_params: vec![], - typ: b_ref.clone(), - }, - value: b_ref, - hints: ReducibilityHints::Opaque, - safety: DefinitionSafety::Safe, - all: vec![n("A")], - }), - ); - let graph = build_ref_graph(&env); - // A references B - assert!(graph.out_refs[&n("A")].contains(&n("B"))); - // B is referenced by A - assert!(graph.in_refs[&n("B")].contains(&n("A"))); - // B references nothing - assert!(graph.out_refs[&n("B")].is_empty()); - } - - #[test] - fn inductive_includes_ctors() { - // Inductive T with constructors T.mk1, T.mk2 - let mut env = Env::default(); - env.insert( - n("T"), - ConstantInfo::InductInfo(InductiveVal { - cnst: mk_cv("T"), - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: vec![n("T")], - ctors: vec![n("T.mk1"), n("T.mk2")], - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }), - ); - // Add constructors to env so they can be referenced - env.insert( - n("T.mk1"), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: mk_cv("T.mk1"), - induct: n("T"), - cidx: Nat::from(0u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(0u64), - is_unsafe: false, - }), - ); - env.insert( - n("T.mk2"), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: mk_cv("T.mk2"), - induct: n("T"), - cidx: Nat::from(1u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(0u64), - is_unsafe: false, - }), - ); - - let graph = build_ref_graph(&env); - // T references T.mk1 and T.mk2 (from ctors list) - assert!(graph.out_refs[&n("T")].contains(&n("T.mk1"))); - assert!(graph.out_refs[&n("T")].contains(&n("T.mk2"))); - } - - #[test] - fn inductive_all_members_are_graph_edges() { - // The members of a Lean block refer to each other: the block's order is - // structural, so the condensation keeps the block whole even when its - // members do not reference each other through their types. - let mut env = Env::default(); - for name in ["A", "B"] { - env.insert( - n(name), - ConstantInfo::InductInfo(InductiveVal { - cnst: mk_cv(name), - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: vec![n("A"), n("B")], - ctors: vec![], - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }), - ); - } - - let graph = build_ref_graph(&env); - assert!(graph.out_refs[&n("A")].contains(&n("B"))); - assert!(graph.out_refs[&n("B")].contains(&n("A"))); - assert!(!graph.out_refs[&n("A")].contains(&n("A"))); - } - - #[test] - fn recursor_family_members_are_graph_edges() { - // A recursor refers to the other recursors of its family: `X.rec` for - // each member of the block, then the nested auxiliaries' `all[0].rec_N`. - let sub = |p: &str, s: &str| Name::str(n(p), s.to_string()); - assert_eq!( - recursor_family_names(&[n("A"), n("B")], 3), - vec![sub("A", "rec"), sub("B", "rec"), sub("A", "rec_1")] - ); - assert_eq!(recursor_family_names(&[n("A")], 1), vec![sub("A", "rec")]); - } - - #[test] - fn ctor_includes_induct() { - // Constructor T.mk references its parent T - let mut env = Env::default(); - env.insert( - n("T"), - ConstantInfo::AxiomInfo(AxiomVal { cnst: mk_cv("T"), is_unsafe: false }), - ); - env.insert( - n("T.mk"), - ConstantInfo::CtorInfo(ConstructorVal { - cnst: mk_cv("T.mk"), - induct: n("T"), - cidx: Nat::from(0u64), - num_params: Nat::from(0u64), - num_fields: Nat::from(0u64), - is_unsafe: false, - }), - ); - let graph = build_ref_graph(&env); - assert!(graph.out_refs[&n("T.mk")].contains(&n("T"))); - } - - #[test] - fn in_refs_bidirectional() { - // A -> B, C -> B - let mut env = Env::default(); - let b_ref = Expr::cnst(n("B"), vec![]); - env.insert( - n("B"), - ConstantInfo::AxiomInfo(AxiomVal { cnst: mk_cv("B"), is_unsafe: false }), - ); - env.insert( - n("A"), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: n("A"), - level_params: vec![], - typ: b_ref.clone(), - }, - is_unsafe: false, - }), - ); - env.insert( - n("C"), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { name: n("C"), level_params: vec![], typ: b_ref }, - is_unsafe: false, - }), - ); - let graph = build_ref_graph(&env); - // B's in_refs should contain both A and C - let b_in = &graph.in_refs[&n("B")]; - assert!(b_in.contains(&n("A"))); - assert!(b_in.contains(&n("C"))); - } - - #[test] - fn recursor_refs_rules() { - // Recursor T.rec with a rule for T.mk whose rhs references Q - let mut env = Env::default(); - env.insert( - n("T.mk"), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: mk_cv("T.mk"), - is_unsafe: false, - }), - ); - env.insert( - n("Q"), - ConstantInfo::AxiomInfo(AxiomVal { cnst: mk_cv("Q"), is_unsafe: false }), - ); - env.insert( - n("T.rec"), - ConstantInfo::RecInfo(RecursorVal { - cnst: mk_cv("T.rec"), - all: vec![n("T")], - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - num_motives: Nat::from(1u64), - num_minors: Nat::from(1u64), - rules: vec![RecursorRule { - ctor: n("T.mk"), - n_fields: Nat::from(0u64), - rhs: Expr::cnst(n("Q"), vec![]), - }], - k: false, - is_unsafe: false, - }), - ); - let graph = build_ref_graph(&env); - let rec_out = &graph.out_refs[&n("T.rec")]; - // References the ctor from the rule - assert!(rec_out.contains(&n("T.mk"))); - // References Q from the rule's rhs - assert!(rec_out.contains(&n("Q"))); - // `RecursorVal.all` is metadata; structural references come from the - // recursor type and rules. - assert!(!rec_out.contains(&n("T"))); - } - - #[test] - fn expr_references_through_app_lam_let() { - // Test that get_expr_references traverses App, Lam, LetE, Proj - let mut env = Env::default(); - env.insert( - n("X"), - ConstantInfo::AxiomInfo(AxiomVal { cnst: mk_cv("X"), is_unsafe: false }), - ); - env.insert( - n("Y"), - ConstantInfo::AxiomInfo(AxiomVal { cnst: mk_cv("Y"), is_unsafe: false }), - ); - env.insert( - n("Z"), - ConstantInfo::AxiomInfo(AxiomVal { cnst: mk_cv("Z"), is_unsafe: false }), - ); - // Build: fun (_ : X) => let _ : Y := #0 in Z - let x_ref = Expr::cnst(n("X"), vec![]); - let y_ref = Expr::cnst(n("Y"), vec![]); - let z_ref = Expr::cnst(n("Z"), vec![]); - let body = Expr::letE( - Name::anon(), - y_ref, - Expr::bvar(Nat::from(0u64)), - z_ref, - false, - ); - let lam = Expr::lam(Name::anon(), x_ref, body, BinderInfo::Default); - env.insert( - n("W"), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { name: n("W"), level_params: vec![], typ: lam }, - is_unsafe: false, - }), - ); - let graph = build_ref_graph(&env); - let w_out = &graph.out_refs[&n("W")]; - assert!(w_out.contains(&n("X"))); - assert!(w_out.contains(&n("Y"))); - assert!(w_out.contains(&n("Z"))); - } - - #[test] - fn proj_references_type_name() { - // Proj references the type name it projects from - let mut env = Env::default(); - env.insert( - n("S"), - ConstantInfo::AxiomInfo(AxiomVal { cnst: mk_cv("S"), is_unsafe: false }), - ); - let proj_expr = - Expr::proj(n("S"), Nat::from(0u64), Expr::bvar(Nat::from(0u64))); - env.insert( - n("P"), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: n("P"), - level_params: vec![], - typ: proj_expr, - }, - is_unsafe: false, - }), - ); - let graph = build_ref_graph(&env); - assert!(graph.out_refs[&n("P")].contains(&n("S"))); - } -} diff --git a/crates/compile/src/ground.rs b/crates/compile/src/ground.rs deleted file mode 100644 index 5581a2b8b..000000000 --- a/crates/compile/src/ground.rs +++ /dev/null @@ -1,636 +0,0 @@ -//! Groundedness checking for Lean environment constants. -//! -//! A constant is "grounded" if all its references resolve to known constants, all -//! bound variables are in scope, and no metavariables remain. Ungroundedness -//! propagates through the reference graph: if A references ungrounded B, then A -//! is also ungrounded. - -use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use rustc_hash::{FxHashMap, FxHashSet}; -use std::collections::hash_map::Entry; - -use bignat::Nat; - -use ix_common::env::{ - ConstantInfo, Env, Expr, ExprData, InductiveVal, Level, LevelData, Name, -}; - -use crate::graph::RefMap; - -/// Reason a constant failed groundedness checking. -/// -/// `Indc` carries `InductiveVal + Option` (~360 bytes) — the -/// payload is boxed so the enum stays small and `Result<(), GroundError>` -/// remains cheap to return up the call stack. -#[derive(Debug)] -pub enum GroundError { - /// A universe level parameter or metavariable is not in scope. - Level(Level, Vec), - /// A referenced constant does not exist in the environment (or is itself ungrounded). - Ref(Name), - /// An expression-level metavariable was encountered. - MVar(Expr), - /// A free or out-of-scope bound variable was encountered. - Var(Expr, usize), - /// An inductive type's constructor is missing or has the wrong kind. - Indc(Box<(InductiveVal, Option)>), - /// An invalid de Bruijn index. - Idx(Nat), -} - -/// Checks every constant in `env` for groundedness and returns a map of all ungrounded names. -/// -/// First collects immediately ungrounded constants in parallel, then propagates -/// ungroundedness transitively through `in_refs` (the reverse reference graph). -pub fn ground_consts( - env: &Env, - in_refs: &RefMap, -) -> FxHashMap { - // Collect immediate ungrounded constants. - let names: Vec<&Name> = env.keys().collect(); - let ungrounded: FxHashMap<_, _> = names - .into_par_iter() - .filter_map(|name| { - let constant = env.get(name)?; - match ground_const_check(&constant, env) { - Err(err) => Some((name.clone(), err)), - Ok(()) => None, - } - }) - .collect(); - proliferate_ungrounded(ungrounded, in_refs) -} - -/// Per-constant groundedness check, for callers that already hold a -/// decoded constant and check as part of a wider pass. -pub fn ground_const_check( - constant: &ConstantInfo, - env: &Env, -) -> Result<(), GroundError> { - let univs = const_univs(constant); - let mut stt = GroundState::default(); - ground_const(constant, env, univs, 0, &mut stt) -} - -/// The named refusal of a constant the groundedness scan rejects, as the -/// compile records it (`stt.ungrounded`; Lean's `GroundError.refusal`, the -/// same text). An immediate error names its cause; a transitive `Ref` (to a -/// constant that is itself ungrounded) does not name the dependency, whose -/// choice depends on the propagation order (`proliferate_ungrounded`). -pub fn refusal( - e: &GroundError, - ungrounded: &FxHashMap, -) -> String { - match e { - GroundError::Ref(n) if ungrounded.contains_key(n) => { - "UNGROUNDED-INPUT: references a constant that is itself ungrounded" - .to_string() - }, - GroundError::Ref(n) => format!( - "UNGROUNDED-INPUT: references {}, which is not in the input", - n.pretty() - ), - GroundError::Level(..) => { - "UNGROUNDED-INPUT: a universe level outside the constant's universe parameters" - .to_string() - }, - GroundError::MVar(..) => { - "UNGROUNDED-INPUT: an expression metavariable".to_string() - }, - GroundError::Var(..) => { - "UNGROUNDED-INPUT: a free or out-of-scope bound variable".to_string() - }, - GroundError::Indc(..) => { - "UNGROUNDED-INPUT: an inductive type whose constructor is missing or not a constructor" - .to_string() - }, - GroundError::Idx(..) => { - "UNGROUNDED-INPUT: an invalid de Bruijn index".to_string() - }, - } -} - -/// Spread ungroundedness from the immediately-ungrounded set through -/// the reverse-reference graph: anything referencing an ungrounded -/// constant is itself ungrounded. -pub fn proliferate_ungrounded( - mut ungrounded: FxHashMap, - in_refs: &RefMap, -) -> FxHashMap { - let mut stack: Vec<_> = ungrounded.keys().cloned().collect(); - while let Some(popped) = stack.pop() { - let Some(in_ref_set) = in_refs.get(&popped) else { - continue; - }; - for in_ref in in_ref_set { - if let Entry::Vacant(entry) = ungrounded.entry(in_ref.clone()) { - entry.insert(GroundError::Ref(popped.clone())); - stack.push(in_ref.clone()); - } - } - } - ungrounded -} - -fn const_univs(constant: &ConstantInfo) -> &[Name] { - match constant { - ConstantInfo::AxiomInfo(val) => &val.cnst.level_params, - ConstantInfo::DefnInfo(val) => &val.cnst.level_params, - ConstantInfo::ThmInfo(val) => &val.cnst.level_params, - ConstantInfo::OpaqueInfo(val) => &val.cnst.level_params, - ConstantInfo::QuotInfo(val) => &val.cnst.level_params, - ConstantInfo::InductInfo(val) => &val.cnst.level_params, - ConstantInfo::CtorInfo(val) => &val.cnst.level_params, - ConstantInfo::RecInfo(val) => &val.cnst.level_params, - } -} - -#[derive(Default)] -struct GroundState { - expr_cache: FxHashSet<(usize, Expr)>, - univ_cache: FxHashSet, -} - -fn ground_const( - constant: &ConstantInfo, - env: &Env, - univs: &[Name], - binds: usize, - stt: &mut GroundState, -) -> Result<(), GroundError> { - match constant { - ConstantInfo::AxiomInfo(val) => { - ground_expr(&val.cnst.typ, env, univs, binds, stt) - }, - ConstantInfo::DefnInfo(val) => { - ground_expr(&val.cnst.typ, env, univs, binds, stt)?; - ground_expr(&val.value, env, univs, binds, stt) - }, - ConstantInfo::ThmInfo(val) => { - ground_expr(&val.cnst.typ, env, univs, binds, stt)?; - ground_expr(&val.value, env, univs, binds, stt) - }, - ConstantInfo::OpaqueInfo(val) => { - ground_expr(&val.cnst.typ, env, univs, binds, stt)?; - ground_expr(&val.value, env, univs, binds, stt) - }, - ConstantInfo::QuotInfo(val) => { - ground_expr(&val.cnst.typ, env, univs, binds, stt) - }, - ConstantInfo::InductInfo(val) => { - for ctor in &val.ctors { - let ci = env.get(ctor).map(|e| e.cloned()); - match ci.as_ref() { - Some(ConstantInfo::CtorInfo(_)) => (), - _ => { - return Err(GroundError::Indc(Box::new((val.clone(), ci)))); - }, - } - } - ground_expr(&val.cnst.typ, env, univs, binds, stt) - }, - ConstantInfo::CtorInfo(val) => { - ground_expr(&val.cnst.typ, env, univs, binds, stt) - }, - ConstantInfo::RecInfo(val) => { - for rule in &val.rules { - ground_expr(&rule.rhs, env, univs, binds, stt)?; - } - ground_expr(&val.cnst.typ, env, univs, binds, stt) - }, - } -} - -fn ground_expr( - expr: &Expr, - env: &Env, - univs: &[Name], - binds: usize, - stt: &mut GroundState, -) -> Result<(), GroundError> { - let key = (binds, expr.clone()); - if stt.expr_cache.contains(&key) { - return Ok(()); - } - stt.expr_cache.insert(key); - match expr.as_data() { - ExprData::Mdata(_, e, _) => ground_expr(e, env, univs, binds, stt), - ExprData::Bvar(idx, _) => { - if *idx >= Nat(binds.into()) { - return Err(GroundError::Var(expr.clone(), binds)); - } - Ok(()) - }, - ExprData::Sort(level, _) => ground_level(level, univs, stt), - ExprData::Const(name, levels, _) => { - for level in levels { - ground_level(level, univs, stt)?; - } - if !env.contains_key(name) { - return Err(GroundError::Ref(name.clone())); - } - Ok(()) - }, - ExprData::App(f, a, _) => { - ground_expr(f, env, univs, binds, stt)?; - ground_expr(a, env, univs, binds, stt) - }, - ExprData::Lam(_, t, b, ..) | ExprData::ForallE(_, t, b, ..) => { - ground_expr(t, env, univs, binds, stt)?; - ground_expr(b, env, univs, binds + 1, stt) - }, - ExprData::LetE(_, t, v, b, ..) => { - ground_expr(t, env, univs, binds, stt)?; - ground_expr(v, env, univs, binds, stt)?; - ground_expr(b, env, univs, binds + 1, stt) - }, - ExprData::Proj(name, _, e, _) => { - if !env.contains_key(name) { - return Err(GroundError::Ref(name.clone())); - } - ground_expr(e, env, univs, binds, stt) - }, - ExprData::Lit(..) => Ok(()), - ExprData::Mvar(..) => Err(GroundError::MVar(expr.clone())), - ExprData::Fvar(..) => Err(GroundError::Var(expr.clone(), binds)), - } -} - -fn ground_level( - level: &Level, - univs: &[Name], - stt: &mut GroundState, -) -> Result<(), GroundError> { - let key = level.clone(); - if stt.univ_cache.contains(&key) { - return Ok(()); - } - stt.univ_cache.insert(key); - match level.as_data() { - LevelData::Zero(_) => Ok(()), - LevelData::Succ(x, _) => ground_level(x, univs, stt), - LevelData::Max(x, y, _) | LevelData::Imax(x, y, _) => { - ground_level(x, univs, stt)?; - ground_level(y, univs, stt) - }, - LevelData::Param(n, _) => { - if !univs.contains(n) { - return Err(GroundError::Level(level.clone(), univs.to_vec())); - } - Ok(()) - }, - LevelData::Mvar(_, _) => { - Err(GroundError::Level(level.clone(), univs.to_vec())) - }, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::graph::build_ref_graph; - use ix_common::env::*; - - fn n(s: &str) -> Name { - Name::str(Name::anon(), s.to_string()) - } - - fn sort0() -> Expr { - Expr::sort(Level::zero()) - } - - fn mk_cv(name: &str) -> ConstantVal { - ConstantVal { name: n(name), level_params: vec![], typ: sort0() } - } - - fn check(env: &Env) -> FxHashMap { - let graph = build_ref_graph(env); - ground_consts(env, &graph.in_refs) - } - - #[test] - fn grounded_axiom() { - let mut env = Env::default(); - env.insert( - n("A"), - ConstantInfo::AxiomInfo(AxiomVal { cnst: mk_cv("A"), is_unsafe: false }), - ); - let errors = check(&env); - assert!(errors.is_empty(), "well-formed axiom should be grounded"); - } - - #[test] - fn grounded_defn_with_bvar_in_lam() { - // fun (_ : Sort 0) => #0 is grounded (bvar(0) under one binder) - let mut env = Env::default(); - let body = Expr::lam( - Name::anon(), - sort0(), - Expr::bvar(Nat::from(0u64)), - BinderInfo::Default, - ); - env.insert( - n("f"), - ConstantInfo::DefnInfo(DefinitionVal { - cnst: ConstantVal { name: n("f"), level_params: vec![], typ: sort0() }, - value: body, - hints: ReducibilityHints::Opaque, - safety: DefinitionSafety::Safe, - all: vec![n("f")], - }), - ); - assert!(check(&env).is_empty()); - } - - #[test] - fn ungrounded_missing_ref() { - // Axiom A : B, but B is not in env - let mut env = Env::default(); - env.insert( - n("A"), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: n("A"), - level_params: vec![], - typ: Expr::cnst(n("B"), vec![]), - }, - is_unsafe: false, - }), - ); - let errors = check(&env); - assert!(errors.contains_key(&n("A"))); - assert!(matches!(errors[&n("A")], GroundError::Ref(_))); - } - - #[test] - fn ungrounded_bvar_out_of_scope() { - // Axiom A : #0 (bvar with no enclosing binder) - let mut env = Env::default(); - env.insert( - n("A"), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: n("A"), - level_params: vec![], - typ: Expr::bvar(Nat::from(0u64)), - }, - is_unsafe: false, - }), - ); - let errors = check(&env); - assert!(errors.contains_key(&n("A"))); - assert!(matches!(errors[&n("A")], GroundError::Var(_, 0))); - } - - #[test] - fn ungrounded_mvar() { - // Axiom A : ?m - let mut env = Env::default(); - env.insert( - n("A"), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: n("A"), - level_params: vec![], - typ: Expr::mvar(n("m")), - }, - is_unsafe: false, - }), - ); - let errors = check(&env); - assert!(errors.contains_key(&n("A"))); - assert!(matches!(errors[&n("A")], GroundError::MVar(_))); - } - - #[test] - fn ungrounded_level_param() { - // Axiom A : Sort (Param "u"), but "u" not in level_params - let mut env = Env::default(); - env.insert( - n("A"), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: n("A"), - level_params: vec![], // u is not declared - typ: Expr::sort(Level::param(n("u"))), - }, - is_unsafe: false, - }), - ); - let errors = check(&env); - assert!(errors.contains_key(&n("A"))); - assert!(matches!(errors[&n("A")], GroundError::Level(_, _))); - } - - #[test] - fn grounded_level_param_when_declared() { - // Axiom A.{u} : Sort u — "u" is declared - let mut env = Env::default(); - env.insert( - n("A"), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: n("A"), - level_params: vec![n("u")], - typ: Expr::sort(Level::param(n("u"))), - }, - is_unsafe: false, - }), - ); - assert!(check(&env).is_empty()); - } - - #[test] - fn propagation_through_in_refs() { - // B is ungrounded (refs missing C), A refs B → A should also be ungrounded - let mut env = Env::default(); - env.insert( - n("B"), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: n("B"), - level_params: vec![], - typ: Expr::cnst(n("C"), vec![]), // C not in env - }, - is_unsafe: false, - }), - ); - env.insert( - n("A"), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: n("A"), - level_params: vec![], - typ: Expr::cnst(n("B"), vec![]), - }, - is_unsafe: false, - }), - ); - let errors = check(&env); - // B is directly ungrounded - assert!(errors.contains_key(&n("B"))); - // A is transitively ungrounded via Ref(B) - assert!(errors.contains_key(&n("A"))); - assert!(matches!(errors[&n("A")], GroundError::Ref(_))); - } - - #[test] - fn refusals_name_the_immediate_cause_only() { - // B references the missing C (immediate: names it); A references B, - // itself ungrounded (transitive: names no dependency). The texts are - // Lean's `GroundError.refusal`'s. - let mut env = Env::default(); - for (name, refd) in [("B", "C"), ("A", "B")] { - env.insert( - n(name), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: n(name), - level_params: vec![], - typ: Expr::cnst(n(refd), vec![]), - }, - is_unsafe: false, - }), - ); - } - let errors = check(&env); - assert_eq!( - refusal(&errors[&n("B")], &errors), - "UNGROUNDED-INPUT: references C, which is not in the input" - ); - assert_eq!( - refusal(&errors[&n("A")], &errors), - "UNGROUNDED-INPUT: references a constant that is itself ungrounded" - ); - assert_eq!( - refusal(&GroundError::Idx(Nat::from(0u64)), &errors), - "UNGROUNDED-INPUT: an invalid de Bruijn index" - ); - } - - #[test] - fn inductive_missing_ctor() { - // Inductive T lists T.mk as a ctor, but T.mk is not in env - let mut env = Env::default(); - env.insert( - n("T"), - ConstantInfo::InductInfo(InductiveVal { - cnst: mk_cv("T"), - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: vec![n("T")], - ctors: vec![n("T.mk")], - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }), - ); - let errors = check(&env); - assert!(errors.contains_key(&n("T"))); - assert!(matches!(errors[&n("T")], GroundError::Indc(_))); - } - - #[test] - fn inductive_ctor_wrong_kind() { - // Inductive T lists T.mk as a ctor, but T.mk is an axiom not a ctor - let mut env = Env::default(); - env.insert( - n("T"), - ConstantInfo::InductInfo(InductiveVal { - cnst: mk_cv("T"), - num_params: Nat::from(0u64), - num_indices: Nat::from(0u64), - all: vec![n("T")], - ctors: vec![n("T.mk")], - num_nested: Nat::from(0u64), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }), - ); - env.insert( - n("T.mk"), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: mk_cv("T.mk"), - is_unsafe: false, - }), - ); - let errors = check(&env); - assert!(errors.contains_key(&n("T"))); - assert!(matches!( - &errors[&n("T")], - GroundError::Indc(b) if b.1.is_some() - )); - } - - #[test] - fn binding_increments_depth() { - // fun (_ : Sort 0) => #0 is grounded (bvar under 1 binder) - // but fun (_ : Sort 0) => #1 is ungrounded (bvar escapes) - let mut env = Env::default(); - - // Grounded case - env.insert( - n("ok"), - ConstantInfo::DefnInfo(DefinitionVal { - cnst: ConstantVal { name: n("ok"), level_params: vec![], typ: sort0() }, - value: Expr::lam( - Name::anon(), - sort0(), - Expr::bvar(Nat::from(0u64)), - BinderInfo::Default, - ), - hints: ReducibilityHints::Opaque, - safety: DefinitionSafety::Safe, - all: vec![n("ok")], - }), - ); - - // Ungrounded case - env.insert( - n("bad"), - ConstantInfo::DefnInfo(DefinitionVal { - cnst: ConstantVal { - name: n("bad"), - level_params: vec![], - typ: sort0(), - }, - value: Expr::lam( - Name::anon(), - sort0(), - Expr::bvar(Nat::from(1u64)), // escapes the single binder - BinderInfo::Default, - ), - hints: ReducibilityHints::Opaque, - safety: DefinitionSafety::Safe, - all: vec![n("bad")], - }), - ); - - let errors = check(&env); - assert!(!errors.contains_key(&n("ok"))); - assert!(errors.contains_key(&n("bad"))); - assert!(matches!(errors[&n("bad")], GroundError::Var(_, 1))); - } - - #[test] - fn fvar_is_ungrounded() { - // Free variables should be ungrounded - let mut env = Env::default(); - env.insert( - n("A"), - ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: n("A"), - level_params: vec![], - typ: Expr::fvar(n("x")), - }, - is_unsafe: false, - }), - ); - let errors = check(&env); - assert!(errors.contains_key(&n("A"))); - assert!(matches!(errors[&n("A")], GroundError::Var(_, 0))); - } -} diff --git a/crates/compile/src/kernel_egress.rs b/crates/compile/src/kernel_egress.rs deleted file mode 100644 index ab0ea5100..000000000 --- a/crates/compile/src/kernel_egress.rs +++ /dev/null @@ -1,2206 +0,0 @@ -//! Egress: convert kernel types (`Meta` mode) to `crates/common/src/env.rs` Lean types. -//! -//! Only works for `Meta` mode since it needs actual names and binder info. - -use rayon::iter::{ - IntoParallelIterator, IntoParallelRefIterator, ParallelIterator, -}; -use rustc_hash::FxHashMap; - -use bignat::Nat; -use ix_common::env::{ - self, AxiomVal, ConstantInfo as LeanCI, ConstantVal, ConstructorVal, - DefinitionVal, InductiveVal, Name, OpaqueVal, QuotVal, - RecursorRule as LeanRecRule, RecursorVal, TheoremVal, -}; -use ixon::constant::DefKind; - -use crate::compile::ind_flags::compute_lean_ind_flags; -use dashmap::DashMap; -use ix_kernel::constant::KConst; -use ix_kernel::env::KEnv; -use ix_kernel::expr::{ExprData, KExpr, MData, UnivDecor}; -use ix_kernel::id::KId; -use ix_kernel::level::{KUniv, UnivData}; -use ix_kernel::mode::Meta; - -/// Convert a kernel universe to a Lean level. -fn egress_level( - u: &KUniv, - level_params: &[Name], -) -> Result { - match u.data() { - UnivData::Zero(_) => Ok(env::Level::zero()), - UnivData::Succ(inner, _) => { - Ok(env::Level::succ(egress_level(inner, level_params)?)) - }, - UnivData::Max(a, b, _) => Ok(env::Level::max( - egress_level(a, level_params)?, - egress_level(b, level_params)?, - )), - UnivData::IMax(a, b, _) => Ok(env::Level::imax( - egress_level(a, level_params)?, - egress_level(b, level_params)?, - )), - UnivData::Param(idx, _, _) => { - let pos = usize::try_from(*idx) - .map_err(|_err| format!("level param index {idx} exceeds usize"))?; - let name = level_params.get(pos).cloned().unwrap_or_else(Name::anon); - Ok(env::Level::param(name)) - }, - } -} - -fn egress_levels( - levels: &[KUniv], - level_params: &[Name], -) -> Result, String> { - levels.iter().map(|l| egress_level(l, level_params)).collect() -} - -/// Convert a stored Ixon universe spelling to a Lean level, structural, -/// resolving `Var` indices positionally through the constant's level -/// params (same resolution as `egress_level`). Used to replay -/// level-spelling decorations (canonicity §10.6). -fn ixon_univ_to_level( - u: &IxonUniv, - level_params: &[Name], -) -> Result { - match u { - IxonUniv::Zero => Ok(env::Level::zero()), - IxonUniv::Succ(inner) => { - Ok(env::Level::succ(ixon_univ_to_level(inner, level_params)?)) - }, - IxonUniv::Max(a, b) => Ok(env::Level::max( - ixon_univ_to_level(a, level_params)?, - ixon_univ_to_level(b, level_params)?, - )), - IxonUniv::IMax(a, b) => Ok(env::Level::imax( - ixon_univ_to_level(a, level_params)?, - ixon_univ_to_level(b, level_params)?, - )), - IxonUniv::Var(idx) => { - let pos = usize::try_from(*idx) - .map_err(|_err| format!("level param index {idx} exceeds usize"))?; - let name = level_params.get(pos).cloned().unwrap_or_else(Name::anon); - Ok(env::Level::param(name)) - }, - } -} - -/// Expression egress cache, keyed by content hash. -type Cache = FxHashMap; - -/// Convert a kernel expression to a Lean expression. -fn egress_expr( - expr: &KExpr, - level_params: &[Name], - cache: &mut Cache, -) -> Result { - let hk = expr.hash_key(); - if let Some(cached) = cache.get(&hk) { - return Ok(cached.clone()); - } - - let mdata: &Vec = expr.mdata(); - - let inner = match expr.data() { - ExprData::Var(idx, _, _) => env::Expr::bvar(Nat::from(*idx)), - // Egress is meant to be invoked only on closed expressions that are - // already abstracted back into de Bruijn binders. A live FVar here - // means a kernel path leaked an open expression past its binder - // open/close pairing — surface it loudly rather than silently emit a - // bogus Lean term. - ExprData::FVar(id, _, _) => panic!( - "egress_expr: unexpected FVar({id}) — abstract back to de Bruijn \ - before exporting" - ), - // Decoration replay (canonicity §10.6): a present decoration is the - // occurrence's original spelling — emit it instead of the kernel-held - // (mk*-normal) level. Decorated twins intern to distinct uids - // (`ExprKey` includes the decoration), so the uid-keyed cache never - // collapses them. - ExprData::Sort(u, i) => match &i.univ_decor { - Some(UnivDecor::Sort(orig)) => { - env::Expr::sort(ixon_univ_to_level(orig, level_params)?) - }, - _ => env::Expr::sort(egress_level(u, level_params)?), - }, - ExprData::Const(id, levels, i) => { - let lvls = match &i.univ_decor { - Some(UnivDecor::Const(origs)) => origs - .iter() - .map(|u| ixon_univ_to_level(u, level_params)) - .collect::, _>>()?, - _ => egress_levels(levels, level_params)?, - }; - env::Expr::cnst(id.name.clone(), lvls) - }, - ExprData::App(f, a, _) => { - let ef = egress_expr(f, level_params, cache)?; - let ea = egress_expr(a, level_params, cache)?; - env::Expr::app(ef, ea) - }, - ExprData::Lam(name, bi, _, ty, body, _) => { - let ety = egress_expr(ty, level_params, cache)?; - let ebody = egress_expr(body, level_params, cache)?; - env::Expr::lam(name.clone(), ety, ebody, bi.clone()) - }, - ExprData::All(name, bi, _, _, ty, body, _) => { - let ety = egress_expr(ty, level_params, cache)?; - let ebody = egress_expr(body, level_params, cache)?; - env::Expr::all(name.clone(), ety, ebody, bi.clone()) - }, - ExprData::Let(name, ty, val, body, nd, _) => { - let ety = egress_expr(ty, level_params, cache)?; - let eval = egress_expr(val, level_params, cache)?; - let ebody = egress_expr(body, level_params, cache)?; - env::Expr::letE(name.clone(), ety, eval, ebody, nd.non_dep) - }, - ExprData::Prj(id, field, val, _) => { - let eval = egress_expr(val, level_params, cache)?; - env::Expr::proj(id.name.clone(), Nat::from(*field), eval) - }, - ExprData::Nat(n, _, _) => env::Expr::lit(env::Literal::NatVal(n.clone())), - ExprData::Str(s, _, _) => env::Expr::lit(env::Literal::StrVal(s.clone())), - }; - - // Re-wrap with mdata layers (innermost first via reverse iteration). - let layers = - ixon::contract::source::egress_layers(expr.source_contract(), mdata) - .map_err(|e| e.to_string())?; - let result = layers - .iter() - .rev() - .fold(inner, |acc, kvs| env::Expr::mdata(kvs.clone(), acc)); - - cache.insert(hk, result.clone()); - Ok(result) -} - -fn zids_to_names(ids: &[KId]) -> Vec { - ids.iter().map(|id| id.name.clone()).collect() -} - -/// Convert a kernel constant to a Lean `ConstantInfo`. -pub fn egress_constant(zc: &KConst) -> Result { - let mut cache = Cache::default(); - - Ok(match zc { - KConst::Defn { - name, - level_params, - kind, - safety, - hints, - ty, - val, - lean_all, - .. - } => { - let lp: &Vec = level_params; - let cnst = ConstantVal { - name: name.clone(), - level_params: lp.clone(), - typ: egress_expr(ty, lp, &mut cache)?, - }; - let value = egress_expr(val, lp, &mut cache)?; - let all = zids_to_names(lean_all); - match kind { - DefKind::Definition => LeanCI::DefnInfo(DefinitionVal { - cnst, - value, - hints: *hints, - safety: *safety, - all, - }), - DefKind::Theorem => LeanCI::ThmInfo(TheoremVal { cnst, value, all }), - DefKind::Opaque => LeanCI::OpaqueInfo(OpaqueVal { - cnst, - value, - is_unsafe: *safety == env::DefinitionSafety::Unsafe, - all, - }), - } - }, - - KConst::Axio { name, level_params, is_unsafe, ty, .. } => { - let lp: &Vec = level_params; - LeanCI::AxiomInfo(AxiomVal { - cnst: ConstantVal { - name: name.clone(), - level_params: lp.clone(), - typ: egress_expr(ty, lp, &mut cache)?, - }, - is_unsafe: *is_unsafe, - }) - }, - - KConst::Quot { name, level_params, kind, ty, .. } => { - let lp: &Vec = level_params; - LeanCI::QuotInfo(QuotVal { - cnst: ConstantVal { - name: name.clone(), - level_params: lp.clone(), - typ: egress_expr(ty, lp, &mut cache)?, - }, - kind: *kind, - }) - }, - - KConst::Indc { - name, - level_params, - params, - indices, - is_unsafe, - ty, - ctors, - lean_all, - .. - } => { - let lp: &Vec = level_params; - LeanCI::InductInfo(InductiveVal { - cnst: ConstantVal { - name: name.clone(), - level_params: lp.clone(), - typ: egress_expr(ty, lp, &mut cache)?, - }, - num_params: Nat::from(*params), - num_indices: Nat::from(*indices), - all: zids_to_names(lean_all), - ctors: zids_to_names(ctors), - is_unsafe: *is_unsafe, - // temporary stubs that get recomputed in lean_egress stage 2 - num_nested: Nat::from(0u64), - is_rec: false, - is_reflexive: false, - }) - }, - - KConst::Ctor { - name, - level_params, - induct, - cidx, - params, - fields, - is_unsafe, - ty, - .. - } => { - let lp: &Vec = level_params; - LeanCI::CtorInfo(ConstructorVal { - cnst: ConstantVal { - name: name.clone(), - level_params: lp.clone(), - typ: egress_expr(ty, lp, &mut cache)?, - }, - induct: induct.name.clone(), - cidx: Nat::from(*cidx), - num_params: Nat::from(*params), - num_fields: Nat::from(*fields), - is_unsafe: *is_unsafe, - }) - }, - - KConst::Recr { - name, - level_params, - params, - indices, - motives, - minors, - ty, - rules, - k, - is_unsafe, - lean_all, - .. - } => { - let lp: &Vec = level_params; - // `RecRule` carries the Lean ctor name as an `MField` - // purely for LEON roundtrip. The kernel doesn't consult it during - // type checking — dispatch is positional via the ctor's `cidx` — - // so we just echo it out verbatim. - let lean_rules: Vec = rules - .iter() - .map(|r| { - Ok::<_, String>(LeanRecRule { - ctor: r.ctor.clone(), - n_fields: Nat::from(r.fields), - rhs: egress_expr(&r.rhs, lp, &mut cache)?, - }) - }) - .collect::>()?; - let typ = egress_expr(ty, lp, &mut cache)?; - // Surgery permutation is deferred — no source_motive_perm / source_minor_groups - LeanCI::RecInfo(RecursorVal { - cnst: ConstantVal { name: name.clone(), level_params: lp.clone(), typ }, - all: zids_to_names(lean_all), - num_params: Nat::from(*params), - num_indices: Nat::from(*indices), - num_motives: Nat::from(*motives), - num_minors: Nat::from(*minors), - rules: lean_rules, - k: *k, - is_unsafe: *is_unsafe, - }) - }, - }) -} - -/// Convert the entire kernel environment to a Lean environment. -pub fn lean_egress(zenv: &KEnv) -> Result { - let entries: Vec<_> = zenv.iter().collect(); - - // Stage 1: egresss everything in parallel, split inductives into a staging - // area - let egressed: DashMap = DashMap::with_capacity(entries.len()); - let groups: DashMap> = DashMap::new(); - entries.into_par_iter().try_for_each(|(id, zc)| -> Result<(), String> { - let ci = egress_constant(&zc)?; - if let LeanCI::InductInfo(v) = &ci - && let Some(first) = v.all.first() - { - groups.entry(first.clone()).or_insert_with(|| v.all.clone()); - } - egressed.insert(id.name.clone(), ci); - Ok(()) - })?; - - // Stage 2: fixup the staged inductive flags which were stubbed - let mut lean_env: env::Env = egressed.into_iter().collect(); - for entry in groups.iter() { - let (key, all) = (entry.key(), entry.value()); - let f = compute_lean_ind_flags(all, &lean_env).map_err(|e| { - format!("lean_egress: ind flags for '{}': {e}", key.pretty()) - })?; - for member in all { - if let Some(LeanCI::InductInfo(v)) = lean_env.get_mut(member) { - v.is_rec = f.is_rec; - v.is_reflexive = f.is_reflexive; - v.num_nested = Nat::from(f.num_nested); - } - } - } - Ok(lean_env) -} - -// =========================================================================== -// Ixon egress: KEnv → IxonEnv -// =========================================================================== -// -// This is the inverse of `ixon_ingress`. We walk each constant in the kernel -// env, produce the corresponding Ixon `Constant` payload, and pair it with -// the original `ConstantMeta` (arena + extension tables) so the output env -// is a well-formed input for `decompile_env`. -// -// Why we reuse the original meta: the kernel does not track per-expression -// metadata like binder names, mdata KV-maps, or call-site surgery — those -// live in `ConstantMeta.arena` + `meta_sharing` / `meta_refs` / `meta_univs`. -// Regenerating them from kenv alone would be equivalent to re-running -// compile's call-site surgery pass (hundreds of LOC, and any divergence -// would reintroduce the "second decompiler" problem we're trying to solve). -// Instead we take the original `Named.meta` as-is. -// -// Consequence: `ixon_egress` is only meaningful after a prior `compile_env` -// produced the original `IxonEnv`. For the diagnostic roundtrip test that's -// fine — the test path is `compile_env → ixon_ingress → kenv → ixon_egress -// → decompile_env`. Callers without a pre-existing compile state would need -// to regenerate metadata themselves (out of scope here). -// -// Meta-only: we only need this for the Meta-mode roundtrip diagnostic. -// Generalizing to `` requires address-keyed lookups (in -// Anon mode `kid.name` is `()`, so we can't look up `original_env.named` -// by name). Left as future work. - -use std::sync::Arc; - -use indexmap::IndexSet; - -use crate::compile::{ - apply_sharing_to_axiom_with_limits, apply_sharing_to_definition_with_limits, - apply_sharing_to_mutual_block_with_limits, - apply_sharing_to_quotient_with_limits, apply_sharing_to_recursor_with_limits, -}; -use ix_common::address::Address; -use ixon::constant::{ - Axiom as IxonAxiom, Constant as IxonConstant, ConstantInfo as IxonCI, - Constructor as IxonConstructor, ConstructorProj, - Definition as IxonDefinition, DefinitionProj, Inductive as IxonInductive, - InductiveProj, MutConst as IxonMutConst, Quotient as IxonQuotient, - Recursor as IxonRecursor, RecursorProj, RecursorRule as IxonRecursorRule, -}; -use ixon::env::{Env as IxonEnv, Named}; -use ixon::expr::Expr as IxonExpr; -use ixon::metadata::ConstantMetaInfo; -use ixon::sharing_exact::ExactSharingLimits; -use ixon::univ::Univ as IxonUniv; - -/// Per-constant (or per-block) working context accumulated while converting -/// kernel expressions back to Ixon. Mirrors `BlockCache.refs` / `univs` on -/// the compile side: every distinct address gets one slot in `refs`, every -/// distinct universe term gets one slot in `univs`, and expressions refer to -/// entries by positional index. -/// -/// Also carries the block's `mut_ctx` as a `FxHashMap` (for O(1) per-Const -/// lookup when discriminating `Rec` from `Ref`) and a memoization cache -/// keyed by `KExpr::addr()` so DAG-shared subexpressions are converted -/// only once. -struct EgressCtx { - /// External constant references, in insertion order. - refs: IndexSet
, - /// Universe terms. When the original constant is available these are - /// PRESEEDED with its univ table verbatim (canonicity §10.6): the - /// original `ConstantMeta.univ_patches` reference the original - /// table's index space (primary indices for unchanged args, virtual - /// `univs.len() + slot` indices into `meta_univs` otherwise), and the - /// roundtrip pairs the rebuilt constant with that original meta — so - /// the rebuilt table must reproduce the original layout exactly, not - /// a first-use reordering. Traversal interns then always HIT (every - /// kernel-held level was ingressed from this very table, and P3 makes - /// stored canonical entries mk*-fixed); `preseeded` arms the - /// debug-assert on miss. - univs: IndexSet>, - /// Whether `univs` was preseeded from the original constant. - preseeded: bool, - /// Mutual block sibling lookup: KId of a sibling → its position in the - /// block. Used to decide `Rec(idx, _)` vs. `Ref(idx, _)` for Const nodes. - /// Empty for non-Muts (standalone) constants. - mut_ctx: FxHashMap, u64>, - /// Memoized expression conversion. Keyed by `KExpr::addr()` (content - /// hash); same hash → same Ixon expression (within a single block's - /// tables). - expr_cache: FxHashMap>, - /// Memoized universe conversion. - univ_cache: FxHashMap>, -} - -impl EgressCtx { - fn new() -> Self { - Self { - refs: IndexSet::new(), - univs: IndexSet::new(), - preseeded: false, - mut_ctx: FxHashMap::default(), - expr_cache: FxHashMap::default(), - univ_cache: FxHashMap::default(), - } - } - - fn with_mut_ctx(mut_ctx: Vec>) -> Self { - let mut out = Self::new(); - for (i, kid) in mut_ctx.into_iter().enumerate() { - out.mut_ctx.insert(kid, i as u64); - } - out - } - - /// Install the ORIGINAL constant's univ table (see `univs` docs). - /// Entries unused by the kernel expressions (e.g. levels appearing - /// only in collapsed call-site args) ride along so the rebuilt table - /// stays layout-identical to the original. - fn preseed_univs(&mut self, univs: &[Arc]) { - for u in univs { - self.univs.insert(u.clone()); - } - self.preseeded = true; - } - - /// Install the ORIGINAL constant's refs table, for the same reason as - /// [`Self::preseed_univs`]: the expressions of `ConstantMeta.meta_sharing` - /// address the original table with their `Ref`/`Str`/`Nat` indices - /// (primary indices, then `refs.len() + j` into `meta_refs`), and the - /// roundtrip pairs the rebuilt constant with that original meta. - /// Traversal interns then hit the original positions instead of a - /// first-use order. - fn preseed_refs(&mut self, refs: &[Address]) { - for r in refs { - self.refs.insert(r.clone()); - } - } - - fn intern_ref(&mut self, addr: Address) -> u64 { - let (idx, _) = self.refs.insert_full(addr); - idx as u64 - } - - /// Intern `u` and return its index. Once the table is preseeded with the - /// original constant's, a universe absent from it is an error in every - /// build: appending it would shift the original meta's `univ_patches` - /// index space (canonicity §10.6 V1). This was a `debug_assert!`, so a - /// release build appended the universe silently. - fn intern_univ(&mut self, u: Arc) -> Result { - if self.preseeded { - return self.univs.get_index_of(&u).map(|idx| idx as u64).ok_or_else( - || { - format!( - "kexpr_to_ixon: universe {u:?} is absent from the original \ - constant's table, so the rebuilt table would no longer match \ - the original meta's univ_patches index space (canonicity \ - §10.6 V1)" - ) - }, - ); - } - let (idx, _) = self.univs.insert_full(u); - Ok(idx as u64) - } - - fn into_vecs(self) -> (Vec
, Vec>) { - (self.refs.into_iter().collect(), self.univs.into_iter().collect()) - } -} - -/// Convert a kernel universe to an Ixon universe (memoized by content hash). -fn kuniv_to_ixon(u: &KUniv, ctx: &mut EgressCtx) -> Arc { - let key = *u.addr(); - if let Some(hit) = ctx.univ_cache.get(&key) { - return hit.clone(); - } - let out = match u.data() { - UnivData::Zero(_) => IxonUniv::zero(), - UnivData::Succ(inner, _) => IxonUniv::succ(kuniv_to_ixon(inner, ctx)), - UnivData::Max(a, b, _) => { - let a = kuniv_to_ixon(a, ctx); - let b = kuniv_to_ixon(b, ctx); - IxonUniv::max(a, b) - }, - UnivData::IMax(a, b, _) => { - let a = kuniv_to_ixon(a, ctx); - let b = kuniv_to_ixon(b, ctx); - IxonUniv::imax(a, b) - }, - UnivData::Param(idx, _, _) => IxonUniv::var(*idx), - }; - ctx.univ_cache.insert(key, out.clone()); - out -} - -/// Intern a universe into the block's `univs` table and return its index. -fn kuniv_idx(u: &KUniv, ctx: &mut EgressCtx) -> Result { - let u = kuniv_to_ixon(u, ctx); - ctx.intern_univ(u) -} - -/// Convert a list of kernel universes to a `Vec` of indices into the -/// block's `univs` table. Used for `IxonExpr::Ref` / `Rec` universe args. -fn kunivs_to_idxs( - us: &[KUniv], - ctx: &mut EgressCtx, -) -> Result, String> { - us.iter().map(|u| kuniv_idx(u, ctx)).collect() -} - -/// Convert a kernel expression to an Ixon expression, accumulating any -/// referenced addresses and universes into `ctx`. Memoized on -/// `expr.addr()` (content hash) so DAG-shared subtrees convert once. -/// -/// `ctx.mut_ctx` is the block's list of sibling `KId`s (for mutual -/// blocks): if an `ExprData::Const` node's `KId` matches one of these, -/// it is emitted as an `IxonExpr::Rec(idx, univs)` rather than a -/// `Ref(idx, univs)`. This is the inverse of `ingress_expr`'s -/// `IxonExpr::Rec → KExpr::Const(mut_ctx[i])` case. -/// -/// Note on `Share`: we never emit `IxonExpr::Share(_)` here; sharing is -/// discovered fresh by the `apply_sharing_*` pass that wraps our output. -fn kexpr_to_ixon( - expr: &KExpr, - ctx: &mut EgressCtx, -) -> Result, String> { - let key = *expr.addr(); - if let Some(hit) = ctx.expr_cache.get(&key) { - return Ok(hit.clone()); - } - let out = match expr.data() { - ExprData::Var(idx, _, _) => IxonExpr::var(*idx), - // See `egress_expr`: FVars must be abstracted back into de Bruijn - // before serialization. They have no Ixon representation. - ExprData::FVar(id, _, _) => { - return Err(format!( - "kexpr_to_ixon: unexpected FVar({id}) — abstract back to de Bruijn \ - before exporting" - )); - }, - // Canonicity §10.6: the ixon half emits the kernel-held (canonical) - // level in ALL cases — decorations do NOT flow into the rebuilt - // tables. The original spellings ride along in the untouched - // original `ConstantMeta` (`univ_patches` + `meta_univs`) that the - // roundtrip pairs with this constant; the decompiler replays them - // from there. (The Lean half, `egress_expr` above, still replays - // decorations by VALUE — no table index space involved.) - ExprData::Sort(u, _) => IxonExpr::sort(kuniv_idx(u, ctx)?), - ExprData::Const(id, univs, _) => { - let u_idxs = kunivs_to_idxs(univs, ctx)?; - // Look up in mut_ctx first — a match means this is a mutual - // self-reference and must be emitted as `Rec`, not `Ref`. - if let Some(&rec_idx) = ctx.mut_ctx.get(id) { - IxonExpr::rec(rec_idx, u_idxs) - } else { - let r_idx = ctx.intern_ref(id.addr.clone()); - IxonExpr::reference(r_idx, u_idxs) - } - }, - ExprData::App(f, a, _) => { - let f = kexpr_to_ixon(f, ctx)?; - let a = kexpr_to_ixon(a, ctx)?; - IxonExpr::app(f, a) - }, - ExprData::Lam(_, _, contract, ty, body, _) => { - let ty = kexpr_to_ixon(ty, ctx)?; - let body = kexpr_to_ixon(body, ctx)?; - IxonExpr::lam_contract(*contract, ty, body) - }, - ExprData::All(_, _, contract, result, ty, body, _) => { - let ty = kexpr_to_ixon(ty, ctx)?; - let body = kexpr_to_ixon(body, ctx)?; - IxonExpr::all_contract(*contract, *result, ty, body) - }, - ExprData::Let(_, ty, val, body, contract, _) => { - let ty = kexpr_to_ixon(ty, ctx)?; - let val = kexpr_to_ixon(val, ctx)?; - let body = kexpr_to_ixon(body, ctx)?; - IxonExpr::let_contract(*contract, ty, val, body) - }, - ExprData::Prj(id, field, val, _) => { - let val = kexpr_to_ixon(val, ctx)?; - let r_idx = ctx.intern_ref(id.addr.clone()); - IxonExpr::prj(r_idx, *field, val) - }, - ExprData::Nat(_, addr, _) => { - let r_idx = ctx.intern_ref(addr.clone()); - IxonExpr::nat(r_idx) - }, - ExprData::Str(_, addr, _) => { - let r_idx = ctx.intern_ref(addr.clone()); - IxonExpr::str(r_idx) - }, - }; - ctx.expr_cache.insert(key, out.clone()); - Ok(out) -} - -/// Build an `IxonDefinition` body (type + value) from a `KConst::Defn`. -fn kdefn_to_ixon( - kc: &KConst, - ctx: &mut EgressCtx, -) -> Result { - match kc { - KConst::Defn { kind, safety, lvls, ty, val, .. } => { - let typ = kexpr_to_ixon(ty, ctx)?; - let value = kexpr_to_ixon(val, ctx)?; - Ok(IxonDefinition { - kind: *kind, - safety: *safety, - lvls: *lvls, - typ, - value, - }) - }, - _ => Err(format!("kdefn_to_ixon: expected Defn, got {}", kc_kind(kc))), - } -} - -/// Build an `IxonRecursor` body from a `KConst::Recr`. -fn krecr_to_ixon( - kc: &KConst, - ctx: &mut EgressCtx, -) -> Result { - match kc { - KConst::Recr { - k, - is_unsafe, - lvls, - params, - indices, - motives, - minors, - ty, - rules, - .. - } => { - let typ = kexpr_to_ixon(ty, ctx)?; - let rules: Vec = rules - .iter() - .map(|r| { - Ok(IxonRecursorRule { - fields: r.fields, - rhs: kexpr_to_ixon(&r.rhs, ctx)?, - }) - }) - .collect::>()?; - Ok(IxonRecursor { - k: *k, - is_unsafe: *is_unsafe, - lvls: *lvls, - params: *params, - indices: *indices, - motives: *motives, - minors: *minors, - typ, - rules, - }) - }, - _ => Err(format!("krecr_to_ixon: expected Recr, got {}", kc_kind(kc))), - } -} - -/// Build an `IxonAxiom` body from a `KConst::Axio`. -fn kaxio_to_ixon( - kc: &KConst, - ctx: &mut EgressCtx, -) -> Result { - match kc { - KConst::Axio { is_unsafe, lvls, ty, .. } => { - let typ = kexpr_to_ixon(ty, ctx)?; - Ok(IxonAxiom { is_unsafe: *is_unsafe, lvls: *lvls, typ }) - }, - _ => Err(format!("kaxio_to_ixon: expected Axio, got {}", kc_kind(kc))), - } -} - -/// Build an `IxonQuotient` body from a `KConst::Quot`. -fn kquot_to_ixon( - kc: &KConst, - ctx: &mut EgressCtx, -) -> Result { - match kc { - KConst::Quot { kind, lvls, ty, .. } => { - let typ = kexpr_to_ixon(ty, ctx)?; - Ok(IxonQuotient { kind: *kind, lvls: *lvls, typ }) - }, - _ => Err(format!("kquot_to_ixon: expected Quot, got {}", kc_kind(kc))), - } -} - -/// Short name for the kernel constant kind — for error messages only. -fn kc_kind(kc: &KConst) -> &'static str { - match kc { - KConst::Defn { .. } => "Defn", - KConst::Recr { .. } => "Recr", - KConst::Axio { .. } => "Axio", - KConst::Quot { .. } => "Quot", - KConst::Indc { .. } => "Indc", - KConst::Ctor { .. } => "Ctor", - } -} - -/// Build an `IxonInductive` body from a `KConst::Indc` plus all of its -/// constructor `KConst::Ctor` entries. -/// -/// `ctor_kconsts` must be in cidx order (0, 1, 2, ...) — we rely on this to -/// mirror the compile-side layout. (`egress_muts_block` sorts by cidx -/// before calling.) -fn kind_to_ixon( - ind_kc: &KConst, - ctor_kconsts: &[&KConst], - ctx: &mut EgressCtx, -) -> Result { - let KConst::Indc { lvls, params, indices, is_unsafe, ty, .. } = ind_kc else { - return Err(format!( - "kind_to_ixon: expected Indc, got {}", - kc_kind(ind_kc) - )); - }; - - let typ = kexpr_to_ixon(ty, ctx)?; - let ctors: Vec = ctor_kconsts - .iter() - .map(|cc| match cc { - KConst::Ctor { is_unsafe, lvls, cidx, params, fields, ty, .. } => { - let typ = kexpr_to_ixon(ty, ctx)?; - Ok(IxonConstructor { - is_unsafe: *is_unsafe, - lvls: *lvls, - cidx: *cidx, - params: *params, - fields: *fields, - typ, - }) - }, - other => Err(format!( - "kind_to_ixon: expected Ctor under Indc, got {}", - kc_kind(other) - )), - }) - .collect::>()?; - - Ok(IxonInductive { - is_unsafe: *is_unsafe, - lvls: *lvls, - params: *params, - indices: *indices, - typ, - ctors, - }) -} - -/// Compute content address of an Ixon `Constant` by serializing and hashing. -fn content_address_of(c: &IxonConstant) -> Address { - let mut bytes = Vec::new(); - c.put(&mut bytes); - Address::hash(&bytes) -} - -/// Build a `FxHashMap` for fast lookup by Lean name. -/// Call once per `ixon_egress` invocation and share. -fn build_name_index( - kenv: &KEnv, -) -> FxHashMap, KConst)> { - let mut out: FxHashMap, KConst)> = - FxHashMap::default(); - for (kid, kc) in kenv.iter() { - out.insert(kid.name.clone(), (kid, kc)); - } - out -} - -/// Build the `mut_ctx` KId slice for a Muts block, taking one canonical name -/// from each equivalence class in `all`. This must mirror the compile-side -/// ctx — the ingress constructed mut_ctx entries via `resolve_all(ctx, names, -/// name_to_addr)` which looks up each class-representative name's stored -/// projection/block address. Here we replicate that lookup against our -/// `name_index` (Meta-mode) rather than against the Ixon `named` table, so -/// the resulting KIds are byte-for-byte identical to those `ingress_expr` -/// emitted for `IxonExpr::Rec` nodes inside this block. -fn build_block_mut_ctx( - all: &[Vec
], - names: &FxHashMap, - name_index: &FxHashMap, KConst)>, -) -> Result>, String> { - let mut ctx: Vec> = Vec::with_capacity(all.len()); - for (i, cls) in all.iter().enumerate() { - let name_addr = cls.first().ok_or_else(|| { - format!("build_block_mut_ctx: class {i} has no canonical name") - })?; - let name = names.get(name_addr).cloned().ok_or_else(|| { - format!( - "build_block_mut_ctx: name_addr {} not in names map", - &name_addr.hex()[..8] - ) - })?; - let (kid, _) = name_index - .get(&name) - .ok_or_else(|| format!("build_block_mut_ctx: '{name}' not in kenv"))?; - ctx.push(kid.clone()); - } - Ok(ctx) -} - -/// Build an `IxonMutConst` for one member of a Muts block. -/// -/// For `Indc` members we also need the constructor `KConst`s; caller passes -/// them pre-sorted by cidx. -fn build_mut_const( - member: &KConst, - ctor_kconsts: &[&KConst], - ctx: &mut EgressCtx, -) -> Result { - match member { - KConst::Defn { .. } => Ok(IxonMutConst::Defn(kdefn_to_ixon(member, ctx)?)), - KConst::Recr { .. } => Ok(IxonMutConst::Recr(krecr_to_ixon(member, ctx)?)), - KConst::Indc { .. } => { - Ok(IxonMutConst::Indc(kind_to_ixon(member, ctor_kconsts, ctx)?)) - }, - other => Err(format!( - "build_mut_const: invalid member kind {} in Muts block", - kc_kind(other) - )), - } -} - -/// Build a fresh `Named` entry for a reconstructed constant, preserving -/// the input entry's `meta`, `hints`, and `original` fields but with an -/// updated `addr`, so the egressed environment carries the same per-name -/// metadata as its input. -/// -/// The per-name `hints` channel matters to decompilation: it reconstructs -/// `DefinitionVal.hints` from `Named.hints` (absent → `Opaque`), and the -/// Lean-level hash of a definition includes its hints. -fn rebuild_named(addr: Address, original: &Named) -> Named { - let mut named = Named::new(addr, (*original.meta()).clone()); - named.set_hints(original.hints()); - if let Some((orig_addr, orig_meta)) = original.original() { - named.set_original(orig_addr, (*orig_meta).clone()); - } - named -} - -/// Register a member `Named` pointing at the appropriate address: -/// - If `is_singleton_class`, the member lives directly at `block_addr` -/// (no projection: `compile_mutual`'s singleton-class branch). -/// - Otherwise emit the appropriate projection (`IPrj` / `CPrj` / `RPrj` -/// / `DPrj`), store it, and register the name with the projection addr. -#[allow(clippy::too_many_arguments)] -fn register_muts_member( - out: &IxonEnv, - member_name: &Name, - original: &Named, - block_addr: &Address, - member_kind: MutConstKind, - member_idx: u64, - ctor_idx: Option, - is_singleton_class: bool, -) -> Result<(), String> { - if is_singleton_class { - // Singleton non-inductive class: Named.addr = block_addr directly. - out.register_name( - member_name.clone(), - rebuild_named(block_addr.clone(), original), - ); - return Ok(()); - } - // Multi-class / inductive block: build the projection wrapper. - let proj_constant = match (member_kind, ctor_idx) { - (MutConstKind::Indc, None) => { - IxonConstant::new(IxonCI::IPrj(InductiveProj { - idx: member_idx, - block: block_addr.clone(), - })) - }, - (MutConstKind::Indc, Some(ci)) => { - IxonConstant::new(IxonCI::CPrj(ConstructorProj { - idx: member_idx, - cidx: ci, - block: block_addr.clone(), - })) - }, - (MutConstKind::Recr, None) => { - IxonConstant::new(IxonCI::RPrj(RecursorProj { - idx: member_idx, - block: block_addr.clone(), - })) - }, - (MutConstKind::Defn, None) => { - IxonConstant::new(IxonCI::DPrj(DefinitionProj { - idx: member_idx, - block: block_addr.clone(), - })) - }, - (k, Some(_)) => { - return Err(format!( - "register_muts_member: ctor_idx is only valid for Indc (got {k:?})" - )); - }, - }; - let proj_addr = content_address_of(&proj_constant); - out.store_const(proj_addr.clone(), proj_constant); - out.register_name(member_name.clone(), rebuild_named(proj_addr, original)); - Ok(()) -} - -#[derive(Clone, Copy, Debug)] -enum MutConstKind { - Defn, - Indc, - Recr, -} - -impl MutConstKind { - fn of(kc: &KConst) -> Option { - match kc { - KConst::Defn { .. } => Some(Self::Defn), - KConst::Indc { .. } => Some(Self::Indc), - KConst::Recr { .. } => Some(Self::Recr), - _ => None, - } - } -} - -/// Reconstruct one Muts block from the kenv. -/// -/// `muts_name` is the synthetic `Ix..` name under which the -/// block was registered by compile. `muts_named` is its `Named` entry (with -/// `meta.info == ConstantMetaInfo::Muts { all }`). `all` is the -/// class-equivalence structure. -#[allow(clippy::too_many_arguments)] -fn egress_muts_block( - muts_name: &Name, - muts_named: &Named, - all: &[Vec
], - original_env: &IxonEnv, - names: &FxHashMap, - name_index: &FxHashMap, KConst)>, - limits: &ExactSharingLimits, - out: &IxonEnv, -) -> Result<(), String> { - if all.is_empty() { - return Err("egress_muts_block: empty Muts block".into()); - } - let mut_ctx_vec = build_block_mut_ctx(all, names, name_index)?; - let mut ctx = EgressCtx::with_mut_ctx(mut_ctx_vec); - // Reproduce the ORIGINAL block's univ-table layout so member metas' - // `univ_patches` stay index-valid (canonicity §10.6, see EgressCtx). - if let Some(orig_const) = original_env.get_const(&muts_named.addr) { - ctx.preseed_univs(&orig_const.univs); - ctx.preseed_refs(&orig_const.refs); - } - - // Determine per-class representative KConst: this is the kernel's - // canonical member for the class. Alpha-equivalent siblings share a - // KConst; the `all[i][0]` choice matches the compile-side canonical pick. - let mut mut_consts: Vec = Vec::with_capacity(all.len()); - // Track whether any class is inductive. An Indc anywhere forces the - // block into the "multi-class-or-inductive" register-as-projection - // branch below (mirroring `compile_mutual`'s logic). - let mut has_indc = false; - for (i, cls) in all.iter().enumerate() { - let name_addr = cls.first().ok_or_else(|| { - format!("egress_muts_block: class {i} has no canonical name") - })?; - let rep_name = names.get(name_addr).cloned().ok_or_else(|| { - format!( - "egress_muts_block: canonical name addr {} not in names map", - &name_addr.hex()[..8] - ) - })?; - let (_, rep_kc) = name_index.get(&rep_name).ok_or_else(|| { - format!( - "egress_muts_block: canonical name '{rep_name}' (class {i}) not in kenv" - ) - })?; - - // For Indc, collect constructor KConsts in cidx order. - let ctor_ks: Vec<&KConst> = match rep_kc { - KConst::Indc { ctors, .. } => { - has_indc = true; - let mut sorted: Vec<(u64, &KConst)> = - Vec::with_capacity(ctors.len()); - for ctor_id in ctors { - let (_, c) = name_index.get(&ctor_id.name).ok_or_else(|| { - format!( - "egress_muts_block: ctor '{}' (of '{rep_name}') not in kenv", - ctor_id.name - ) - })?; - let cidx = match c { - KConst::Ctor { cidx, .. } => *cidx, - other => { - return Err(format!( - "egress_muts_block: expected Ctor for '{}', got {}", - ctor_id.name, - kc_kind(other) - )); - }, - }; - sorted.push((cidx, c)); - } - sorted.sort_by_key(|(cidx, _)| *cidx); - sorted.into_iter().map(|(_, c)| c).collect() - }, - _ => Vec::new(), - }; - - mut_consts.push(build_mut_const(rep_kc, &ctor_ks, &mut ctx)?); - } - - let (refs, univs) = ctx.into_vecs(); - let result = - apply_sharing_to_mutual_block_with_limits(limits, mut_consts, refs, univs) - .map_err(|e| format!("egress '{muts_name}': {e}"))?; - let block_addr = content_address_of(&result); - out.store_const(block_addr.clone(), result); - - // Register the synthetic Muts Named entry at the new block_addr, - // preserving the input entry's fields (`rebuild_named`). - out.register_name( - muts_name.clone(), - rebuild_named(block_addr.clone(), muts_named), - ); - - // Register all member names. Singleton case: no projections. - let is_singleton = all.len() == 1 && !has_indc; - - for (i, cls) in all.iter().enumerate() { - let i_u64 = i as u64; - let rep_name_addr = cls.first().expect("class non-empty"); - let rep_name = names.get(rep_name_addr).cloned().expect("rep present"); - let (_, rep_kc) = - name_index.get(&rep_name).expect("rep in name_index above"); - let rep_kind = MutConstKind::of(rep_kc).ok_or_else(|| { - format!( - "egress_muts_block: class {i} canonical '{rep_name}' is {}, expected Defn/Indc/Recr", - kc_kind(rep_kc) - ) - })?; - - // Every equivalent member gets its own Named, all pointing at the same - // projection/block addr (alpha-collapsed members share their post- - // compile representation). - for member_name_addr in cls { - let member_name = - names.get(member_name_addr).cloned().ok_or_else(|| { - format!( - "egress_muts_block: member name addr {} not in names map", - &member_name_addr.hex()[..8] - ) - })?; - let orig_named = - original_env.lookup_name(&member_name).ok_or_else(|| { - format!( - "egress_muts_block: original Named for '{member_name}' missing \ - — can't preserve meta" - ) - })?; - register_muts_member( - out, - &member_name, - &orig_named, - &block_addr, - rep_kind, - i_u64, - None, - is_singleton, - )?; - } - - // For Indc: also register each constructor name at its own CPrj. - if let KConst::Indc { ctors, .. } = rep_kc { - // Collect (cidx, ctor_name) pairs so we register with the right cidx - // regardless of the `ctors` order. - let mut sorted: Vec<(u64, Name)> = Vec::with_capacity(ctors.len()); - for cid in ctors { - let (_, c) = name_index - .get(&cid.name) - .ok_or_else(|| format!("ctor '{}' not in kenv", cid.name))?; - let cidx = match c { - KConst::Ctor { cidx, .. } => *cidx, - other => { - return Err(format!( - "expected Ctor for '{}' got {}", - cid.name, - kc_kind(other) - )); - }, - }; - sorted.push((cidx, cid.name.clone())); - } - sorted.sort_by_key(|(cidx, _)| *cidx); - for (cidx, ctor_name) in sorted { - let orig_named = - original_env.lookup_name(&ctor_name).ok_or_else(|| { - format!( - "egress_muts_block: original Named for ctor '{ctor_name}' missing" - ) - })?; - register_muts_member( - out, - &ctor_name, - &orig_named, - &block_addr, - MutConstKind::Indc, - i_u64, - Some(cidx), - // Ctors are never singleton-class (an Indc class forces - // projection emission even when there's only one class). - false, - )?; - } - } - } - - Ok(()) -} - -/// Reconstruct a single standalone constant from the kenv. -fn egress_standalone( - name: &Name, - original_named: &Named, - original_env: &IxonEnv, - name_index: &FxHashMap, KConst)>, - limits: &ExactSharingLimits, - out: &IxonEnv, -) -> Result<(), String> { - let (_, kc) = name_index - .get(name) - .ok_or_else(|| format!("egress_standalone: '{name}' not in kenv"))?; - let mut ctx = EgressCtx::new(); - // Reproduce the ORIGINAL constant's univ-table layout so its meta's - // `univ_patches` stay index-valid (canonicity §10.6, see EgressCtx). - if let Some(orig_const) = original_env.get_const(&original_named.addr) { - ctx.preseed_univs(&orig_const.univs); - ctx.preseed_refs(&orig_const.refs); - } - // A sharing failure (resource limit or construction error) names the - // constant it stopped. - let sharing_err = |e: ixon::CompileError| format!("egress '{name}': {e}"); - let (constant, addr) = match kc { - KConst::Defn { .. } => { - let def = kdefn_to_ixon(kc, &mut ctx)?; - let (refs, univs) = ctx.into_vecs(); - let result = - apply_sharing_to_definition_with_limits(limits, def, refs, univs) - .map_err(sharing_err)?; - let addr = content_address_of(&result); - (result, addr) - }, - KConst::Recr { .. } => { - let rec = krecr_to_ixon(kc, &mut ctx)?; - let (refs, univs) = ctx.into_vecs(); - let result = - apply_sharing_to_recursor_with_limits(limits, rec, refs, univs) - .map_err(sharing_err)?; - let addr = content_address_of(&result); - (result, addr) - }, - KConst::Axio { .. } => { - let ax = kaxio_to_ixon(kc, &mut ctx)?; - let (refs, univs) = ctx.into_vecs(); - let result = apply_sharing_to_axiom_with_limits(limits, ax, refs, univs) - .map_err(sharing_err)?; - let addr = content_address_of(&result); - (result, addr) - }, - KConst::Quot { .. } => { - let q = kquot_to_ixon(kc, &mut ctx)?; - let (refs, univs) = ctx.into_vecs(); - let result = - apply_sharing_to_quotient_with_limits(limits, q, refs, univs) - .map_err(sharing_err)?; - let addr = content_address_of(&result); - (result, addr) - }, - other => { - return Err(format!( - "egress_standalone: '{name}' is {} (should have been handled by Muts path)", - kc_kind(other) - )); - }, - }; - out.store_const(addr.clone(), constant); - out.register_name(name.clone(), rebuild_named(addr, original_named)); - Ok(()) -} - -/// Top-level Ixon egress. -/// -/// Traverses `kenv`, emits Ixon `Constant`s paired with the original metadata -/// sourced from `original_env.named`, and returns a new `IxonEnv` whose -/// `named[name]` entries preserve every per-constant meta the decompiler -/// needs. Blobs, names, and commitments are cloned from `original_env` -/// unchanged (they're content-addressed so any address referenced by an -/// expression resolves without needing regeneration). -/// -/// Partitions original Named entries into Muts-block drivers and standalone -/// constants, then processes each partition in parallel via rayon. Storing -/// into the output `IxonEnv` is thread-safe because the env uses DashMaps. -/// Every rebuilt constant is shared under `limits` (the compile's -/// `CompileState::sharing_limits`); a sharing failure names its constant. -/// A bound input retains its exact binding only when every constant is -/// reproduced at the same address with the same bytes. Changed output needs -/// a newly generated binding; this roundtrip API does not remap role evidence. -pub fn ixon_egress( - kenv: &KEnv, - original_env: &IxonEnv, - limits: &ExactSharingLimits, -) -> Result { - let t_start = std::time::Instant::now(); - let mut out = IxonEnv::new(); - if let Some(binding) = &original_env.role_binding { - binding.check_hash()?; - } - - // Copy immutable content tables. - for entry in original_env.blobs.iter() { - out.blobs.insert(entry.key().clone(), entry.value().clone()); - } - for entry in original_env.names.iter() { - out.names.insert(entry.key().clone(), entry.value().clone()); - } - for entry in original_env.comms.iter() { - out.comms.insert(entry.key().clone(), entry.value().clone()); - } - if *crate::compile::IX_TIMING { - eprintln!( - "[ixon_egress] copy content tables: {:.2?} (blobs={}, names={}, comms={})", - t_start.elapsed(), - out.blobs.len(), - out.names.len(), - out.comms.len() - ); - } - - // Build name_index for fast lookups (Meta mode only — KId.name is the Lean name). - let t_idx = std::time::Instant::now(); - let name_index = build_name_index(kenv); - if *crate::compile::IX_TIMING { - eprintln!( - "[ixon_egress] build name_index: {:.2?} ({} entries)", - t_idx.elapsed(), - name_index.len() - ); - } - - // Build address → name map for resolving class canonical names. - let mut names: FxHashMap = FxHashMap::default(); - for entry in original_env.names.iter() { - names.insert(entry.key().clone(), entry.value().clone()); - } - - // Partition original Named entries: - // - Muts-synthetic entries drive block reconstruction. - // - Standalone entries (Def/Axio/Quot/Rec pointing at a non-projection - // body) get their own rebuild. - // - Everything else (members of Muts blocks — meta is Indc/Ctor/Rec/Def - // pointing at IPrj/CPrj/RPrj/DPrj/Muts) is skipped here; the Muts - // block's reconstruction registers them. - let t_partition = std::time::Instant::now(); - let mut muts_entries: Vec<(Name, Named)> = Vec::new(); - let mut standalone_entries: Vec<(Name, Named)> = Vec::new(); - for entry in original_env.named.iter() { - let name = entry.key().clone(); - let named = entry.value().clone(); - match &named.meta().info { - ConstantMetaInfo::Muts { .. } => muts_entries.push((name, named)), - _ => { - let orig_const = original_env.get_const(&named.addr); - let is_muts_member = matches!( - orig_const.as_ref().map(|c| &c.info), - Some( - IxonCI::IPrj(_) - | IxonCI::CPrj(_) - | IxonCI::RPrj(_) - | IxonCI::DPrj(_) - | IxonCI::Muts(_) - ) - ); - if !is_muts_member { - standalone_entries.push((name, named)); - } - }, - } - } - if *crate::compile::IX_TIMING { - eprintln!( - "[ixon_egress] partition: {:.2?} (muts={}, standalone={})", - t_partition.elapsed(), - muts_entries.len(), - standalone_entries.len() - ); - } - - // Process Muts blocks in parallel. - let t_muts = std::time::Instant::now(); - muts_entries.par_iter().try_for_each( - |(muts_name, muts_named)| -> Result<(), String> { - let muts_meta = muts_named.meta(); - let all: &[Vec
] = match &muts_meta.info { - ConstantMetaInfo::Muts { all, .. } => all.as_slice(), - _ => unreachable!("partitioned above"), - }; - egress_muts_block( - muts_name, - muts_named, - all, - original_env, - &names, - &name_index, - limits, - &out, - ) - }, - )?; - if *crate::compile::IX_TIMING { - eprintln!("[ixon_egress] muts blocks: {:.2?}", t_muts.elapsed()); - } - - // Process standalone constants in parallel. - let t_solo = std::time::Instant::now(); - standalone_entries.par_iter().try_for_each( - |(name, named)| -> Result<(), String> { - egress_standalone(name, named, original_env, &name_index, limits, &out) - }, - )?; - if *crate::compile::IX_TIMING { - eprintln!("[ixon_egress] standalone consts: {:.2?}", t_solo.elapsed()); - } - if let Some(binding) = &original_env.role_binding { - let unchanged = original_env.consts.len() == out.consts.len() - && original_env.consts.iter().all(|entry| { - out.consts.get(entry.key()).is_some_and(|rebuilt| { - entry.value().raw_bytes() == rebuilt.value().raw_bytes() - }) - }); - if !unchanged { - return Err( - "ixon_egress: changed bound constants require a new role binding" - .into(), - ); - } - out.role_binding = Some(binding.clone()); - } - if *crate::compile::IX_TIMING { - eprintln!("[ixon_egress] total: {:.2?}", t_start.elapsed()); - } - - Ok(out) -} - -#[cfg(test)] -mod tests { - use super::*; - use ix_common::address::Address; - use ix_common::env::{ - BinderInfo, DefinitionSafety, ExprData as LeanExprData, Literal, QuotKind, - ReducibilityHints, - }; - use ix_kernel::constant::{KConst, RecRule}; - use ix_kernel::env::KEnv; - use ix_kernel::expr::KExpr; - use ix_kernel::id::KId; - use ix_kernel::level::KUniv; - use ix_kernel::mode::Meta; - - fn mk_name(s: &str) -> Name { - let mut n = Name::anon(); - for part in s.split('.') { - n = Name::str(n, part.to_string()); - } - n - } - - fn mk_addr(s: &str) -> Address { - Address::hash(s.as_bytes()) - } - - fn mk_id(s: &str) -> KId { - KId::new(mk_addr(s), mk_name(s)) - } - - fn sort0() -> KExpr { - KExpr::sort(KUniv::zero()) - } - - fn sort_succ() -> KExpr { - KExpr::sort(KUniv::succ(KUniv::zero())) - } - - // ---- egress_level ---- - - #[test] - fn egress_level_zero() { - let l = egress_level(&KUniv::::zero(), &[]).unwrap(); - assert!(matches!(l.as_data(), env::LevelData::Zero(_))); - } - - #[test] - fn egress_level_succ() { - let l = egress_level(&KUniv::::succ(KUniv::zero()), &[]).unwrap(); - assert!(matches!(l.as_data(), env::LevelData::Succ(..))); - } - - #[test] - fn egress_level_param_by_index() { - // Param(0) with level_params=["u"] → Level::param("u") - let u_name = mk_name("u"); - let ku = KUniv::::param(0, u_name.clone()); - let l = egress_level(&ku, std::slice::from_ref(&u_name)).unwrap(); - match l.as_data() { - env::LevelData::Param(n, _) => assert_eq!(n, &u_name), - other => panic!("expected Param, got {other:?}"), - } - } - - #[test] - fn egress_level_param_out_of_range_falls_back_to_anon() { - // Index 5 with only 1 level_param → fallback to Name::anon(). - let ku = KUniv::::param(5, mk_name("x")); - let l = egress_level(&ku, &[mk_name("u")]).unwrap(); - match l.as_data() { - env::LevelData::Param(n, _) => { - assert!(matches!(n.as_data(), env::NameData::Anonymous(_))); - }, - other => panic!("expected Param, got {other:?}"), - } - } - - // ---- egress_expr: each variant ---- - - fn do_egress(e: &KExpr) -> env::Expr { - let mut cache = Cache::default(); - egress_expr(e, &[], &mut cache).unwrap() - } - - #[test] - fn warm_result_only_conversion_preserves_original_egress() { - use ix_kernel::tc::TypeChecker; - use ixon::contract::{BinderContract, ValueContract}; - use ixon::expr::Uses; - - let input = BinderContract::plain(Uses::Linear); - for reverse in [false, true] { - let mut kernel = KEnv::::new(); - let make = |result| { - KExpr::all_contract( - mk_name("x"), - BinderInfo::Default, - input, - result, - sort0(), - sort0(), - ) - }; - let shared = kernel.intern.intern_expr(make(ValueContract::shared())); - let unique = kernel.intern.intern_expr(make(ValueContract::unique())); - assert_ne!(shared.addr(), unique.addr()); - let (first, second) = - if reverse { (&unique, &shared) } else { (&shared, &unique) }; - let mut checker = TypeChecker::new(&mut kernel); - assert!(checker.is_def_eq(first, second).unwrap()); - assert!(checker.is_def_eq(second, first).unwrap()); - assert!(checker.is_def_eq(first, second).unwrap()); - - // Conversion equivalence is not structural identity. Reconstructing - // after positive cache/union-find traffic must retain each result. - let rebuilt = - checker.env.intern.intern_expr(make(ValueContract::unique())); - assert_eq!(rebuilt.addr(), unique.addr()); - assert_ne!(rebuilt.addr(), shared.addr()); - let mut ctx = EgressCtx::new(); - for (expr, result) in if reverse { - [(&unique, ValueContract::unique()), (&shared, ValueContract::shared())] - } else { - [(&shared, ValueContract::shared()), (&unique, ValueContract::unique())] - } { - let encoded = kexpr_to_ixon(expr, &mut ctx).unwrap(); - assert_eq!( - encoded, - IxonExpr::all_contract( - input, - result, - IxonExpr::sort(0), - IxonExpr::sort(0) - ) - ); - } - let encoded = kexpr_to_ixon(&rebuilt, &mut ctx).unwrap(); - assert_eq!( - encoded, - IxonExpr::all_contract( - input, - ValueContract::unique(), - IxonExpr::sort(0), - IxonExpr::sort(0) - ) - ); - // Lean egress and a cleared conversion cache retain the same payload. - checker.env.clear_reduction_caches(); - checker.reset(); - assert!(checker.is_def_eq(first, second).unwrap()); - for expr in [&shared, &unique, &rebuilt] { - let source = do_egress(expr); - let roundtrip = ix_kernel::ingress::lean_expr_to_zexpr_with_kenv( - &source, - &[], - checker.env, - None, - None, - ) - .unwrap(); - assert_eq!(&roundtrip, expr); - assert_eq!(roundtrip.source_contract(), expr.source_contract()); - } - } - } - - #[test] - fn egress_uses_scalar_payload_and_preserves_all_three_binder_kinds() { - use ixon::contract::source::decode; - use ixon::contract::{BinderContract, LetContract, ValueContract}; - use ixon::expr::Uses; - let input = BinderContract::from_bits(9).unwrap(); - let result = ValueContract::local_unique(); - let borrow = LetContract::borrow(true, Uses::Affine); - let term = KExpr::lam_contract( - mk_name("x"), - BinderInfo::Implicit, - input, - sort0(), - KExpr::all_contract( - mk_name("y"), - BinderInfo::Default, - input, - result, - sort0(), - KExpr::let_contract(mk_name("z"), borrow, sort0(), sort0(), sort0()), - ), - ); - let expected = IxonExpr::lam_contract( - input, - IxonExpr::sort(0), - IxonExpr::all_contract( - input, - result, - IxonExpr::sort(0), - IxonExpr::let_contract( - borrow, - IxonExpr::sort(0), - IxonExpr::sort(0), - IxonExpr::sort(0), - ), - ), - ); - assert_eq!(kexpr_to_ixon(&term, &mut EgressCtx::new()).unwrap(), expected); - let source = do_egress(&term); - let mut kernel = KEnv::new(); - let roundtrip = ix_kernel::ingress::lean_expr_to_zexpr_with_kenv( - &source, - &[], - &mut kernel, - None, - None, - ) - .unwrap(); - assert_eq!(roundtrip, term); - assert_eq!(do_egress(&roundtrip), source); - - let conflicting = KExpr::::all_contract_mdata( - mk_name("x"), - BinderInfo::Default, - input, - result, - sort0(), - sort0(), - vec![decode(1, 23).unwrap().metadata()], - ); - assert!( - egress_expr(&conflicting, &[], &mut Cache::default()) - .unwrap_err() - .contains("conflicts with kernel payload") - ); - // The authoritative Ixon output does not consult optional source metadata. - assert_eq!( - kexpr_to_ixon(&conflicting, &mut EgressCtx::new()).unwrap(), - IxonExpr::all_contract( - input, - result, - IxonExpr::sort(0), - IxonExpr::sort(0) - ) - ); - let default = - KExpr::::all(mk_name("x"), BinderInfo::Default, sort0(), sort0()); - assert!(matches!(do_egress(&default).as_data(), LeanExprData::ForallE(..))); - } - - #[test] - fn egress_expr_var() { - let k = KExpr::::var(7, mk_name("_")); - let e = do_egress(&k); - match e.as_data() { - LeanExprData::Bvar(n, _) => { - assert_eq!(n.to_u64(), Some(7)); - }, - other => panic!("expected Bvar, got {other:?}"), - } - } - - #[test] - fn egress_expr_sort() { - let k = sort0(); - let e = do_egress(&k); - assert!(matches!(e.as_data(), LeanExprData::Sort(..))); - } - - #[test] - fn egress_expr_const_without_univs() { - let k = KExpr::::cnst(mk_id("Unit"), Box::new([])); - let e = do_egress(&k); - match e.as_data() { - LeanExprData::Const(name, us, _) => { - assert_eq!(name, &mk_name("Unit")); - assert_eq!(us.len(), 0); - }, - other => panic!("expected Const, got {other:?}"), - } - } - - #[test] - fn egress_expr_app() { - let k = KExpr::::app(sort0(), KExpr::var(0, mk_name("_"))); - let e = do_egress(&k); - assert!(matches!(e.as_data(), LeanExprData::App(..))); - } - - #[test] - fn egress_expr_lambda() { - let k = KExpr::::lam( - mk_name("x"), - BinderInfo::Default, - sort0(), - KExpr::var(0, mk_name("_")), - ); - let e = do_egress(&k); - assert!(matches!(e.as_data(), LeanExprData::Lam(..))); - } - - #[test] - fn egress_expr_forall() { - let k = - KExpr::::all(mk_name("x"), BinderInfo::Default, sort0(), sort0()); - let e = do_egress(&k); - assert!(matches!(e.as_data(), LeanExprData::ForallE(..))); - } - - #[test] - fn egress_expr_let() { - let k = KExpr::::let_( - mk_name("x"), - sort0(), - KExpr::var(0, mk_name("_")), - KExpr::var(0, mk_name("_")), - false, - ); - let e = do_egress(&k); - assert!(matches!(e.as_data(), LeanExprData::LetE(..))); - } - - #[test] - fn egress_expr_proj() { - let k = KExpr::::prj(mk_id("Prod"), 0, KExpr::var(0, mk_name("_"))); - let e = do_egress(&k); - match e.as_data() { - LeanExprData::Proj(name, idx, _, _) => { - assert_eq!(name, &mk_name("Prod")); - assert_eq!(idx.to_u64(), Some(0)); - }, - other => panic!("expected Proj, got {other:?}"), - } - } - - #[test] - fn egress_expr_nat_lit() { - let k = KExpr::::nat(Nat::from(42u64), mk_addr("blob")); - let e = do_egress(&k); - match e.as_data() { - LeanExprData::Lit(Literal::NatVal(n), _) => { - assert_eq!(n.to_u64(), Some(42)); - }, - other => panic!("expected Lit(NatVal), got {other:?}"), - } - } - - #[test] - fn egress_expr_str_lit() { - let k = KExpr::::str("hi".into(), mk_addr("blob")); - let e = do_egress(&k); - match e.as_data() { - LeanExprData::Lit(Literal::StrVal(s), _) => { - assert_eq!(s, "hi"); - }, - other => panic!("expected Lit(StrVal), got {other:?}"), - } - } - - // ---- egress_expr cache behavior ---- - - #[test] - fn egress_expr_cache_returns_same_tree() { - let k = KExpr::::app(sort0(), sort0()); - let mut cache = Cache::default(); - let e1 = egress_expr(&k, &[], &mut cache).unwrap(); - // The inner Sort 0 was hit by the cache after the first subexpr. Run - // twice on the same cache and confirm deterministic output. - let e2 = egress_expr(&k, &[], &mut cache).unwrap(); - assert_eq!(e1.get_hash(), e2.get_hash()); - } - - // ---- egress_constant: each variant roundtrips ---- - - fn defn_meta(name: &str) -> KConst { - KConst::::Defn { - name: mk_name(name), - level_params: vec![mk_name("u")], - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Opaque, - lvls: 1, - ty: sort_succ(), - val: sort0(), - lean_all: vec![mk_id(name)], - block: mk_id(name), - } - } - - #[test] - fn egress_const_axio_roundtrip() { - let kc = KConst::::Axio { - name: mk_name("A"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - ty: sort0(), - }; - let ci = egress_constant(&kc).unwrap(); - match ci { - LeanCI::AxiomInfo(v) => { - assert_eq!(v.cnst.name, mk_name("A")); - assert!(!v.is_unsafe); - assert_eq!(v.cnst.level_params.len(), 0); - }, - other => panic!("expected AxiomInfo, got {other:?}"), - } - } - - #[test] - fn egress_const_defn_kind_definition() { - let kc = defn_meta("f"); - let ci = egress_constant(&kc).unwrap(); - match ci { - LeanCI::DefnInfo(v) => { - assert_eq!(v.cnst.name, mk_name("f")); - assert_eq!(v.cnst.level_params.len(), 1); - assert_eq!(v.all.len(), 1); - assert_eq!(v.all[0], mk_name("f")); - }, - other => panic!("expected DefnInfo, got {other:?}"), - } - } - - #[test] - fn egress_const_defn_kind_theorem() { - let mut kc = defn_meta("thm"); - if let KConst::Defn { kind, .. } = &mut kc { - *kind = DefKind::Theorem; - } - let ci = egress_constant(&kc).unwrap(); - assert!(matches!(ci, LeanCI::ThmInfo(..))); - } - - #[test] - fn egress_const_defn_kind_opaque() { - let mut kc = defn_meta("op"); - if let KConst::Defn { kind, .. } = &mut kc { - *kind = DefKind::Opaque; - } - let ci = egress_constant(&kc).unwrap(); - assert!(matches!(ci, LeanCI::OpaqueInfo(..))); - } - - #[test] - fn egress_const_opaque_preserves_unsafe_bit() { - let mut kc = defn_meta("op"); - if let KConst::Defn { kind, safety, .. } = &mut kc { - *kind = DefKind::Opaque; - *safety = DefinitionSafety::Unsafe; - } - let ci = egress_constant(&kc).unwrap(); - match ci { - LeanCI::OpaqueInfo(v) => assert!(v.is_unsafe), - other => panic!("expected OpaqueInfo, got {other:?}"), - } - } - - #[test] - fn egress_const_quot_roundtrip() { - let kc = KConst::::Quot { - name: mk_name("Quot"), - level_params: vec![mk_name("u")], - kind: QuotKind::Type, - lvls: 1, - ty: sort_succ(), - }; - let ci = egress_constant(&kc).unwrap(); - match ci { - LeanCI::QuotInfo(v) => { - assert_eq!(v.kind, QuotKind::Type); - assert_eq!(v.cnst.name, mk_name("Quot")); - }, - other => panic!("expected QuotInfo, got {other:?}"), - } - } - - #[test] - fn egress_const_indc_preserves_counts() { - let kc = KConst::::Indc { - name: mk_name("A"), - level_params: vec![], - lvls: 0, - params: 2, - indices: 3, - is_unsafe: false, - block: mk_id("A"), - member_idx: 0, - ty: sort0(), - ctors: vec![mk_id("A.mk")], - lean_all: vec![mk_id("A")], - }; - let ci = egress_constant(&kc).unwrap(); - match ci { - LeanCI::InductInfo(v) => { - assert_eq!(v.num_params.to_u64(), Some(2)); - assert_eq!(v.num_indices.to_u64(), Some(3)); - assert_eq!(v.num_nested.to_u64(), Some(0)); - assert!(!v.is_rec); - assert!(!v.is_reflexive); - assert_eq!(v.all.len(), 1); - assert_eq!(v.ctors.len(), 1); - assert_eq!(v.ctors[0], mk_name("A.mk")); - }, - other => panic!("expected InductInfo, got {other:?}"), - } - } - - #[test] - fn egress_const_ctor_roundtrip() { - let kc = KConst::::Ctor { - name: mk_name("A.mk"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: mk_id("A"), - cidx: 2, - params: 3, - fields: 4, - ty: sort0(), - }; - let ci = egress_constant(&kc).unwrap(); - match ci { - LeanCI::CtorInfo(v) => { - assert_eq!(v.cidx.to_u64(), Some(2)); - assert_eq!(v.num_params.to_u64(), Some(3)); - assert_eq!(v.num_fields.to_u64(), Some(4)); - assert_eq!(v.induct, mk_name("A")); - }, - other => panic!("expected CtorInfo, got {other:?}"), - } - } - - #[test] - fn egress_const_recr_with_rules_roundtrip() { - let rules = - vec![RecRule { ctor: mk_name("A.mk"), fields: 5, rhs: sort0() }]; - let kc = KConst::::Recr { - name: mk_name("A.rec"), - level_params: vec![], - k: true, - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: mk_id("A"), - member_idx: 0, - ty: sort0(), - rules, - lean_all: vec![mk_id("A")], - }; - let ci = egress_constant(&kc).unwrap(); - match ci { - LeanCI::RecInfo(v) => { - assert_eq!(v.num_motives.to_u64(), Some(1)); - assert_eq!(v.num_minors.to_u64(), Some(1)); - assert_eq!(v.rules.len(), 1); - assert_eq!(v.rules[0].ctor, mk_name("A.mk")); - assert_eq!(v.rules[0].n_fields.to_u64(), Some(5)); - assert!(v.k); - }, - other => panic!("expected RecInfo, got {other:?}"), - } - } - - // ---- lean_egress: environment-level roundtrip ---- - - #[test] - fn lean_egress_on_empty_env() { - let zenv = KEnv::::new(); - let le = lean_egress(&zenv).unwrap(); - // `Env` is a `FxHashMap`. - assert_eq!(le.len(), 0); - } - - #[test] - fn lean_egress_roundtrips_multiple_axioms() { - let mut zenv = KEnv::::new(); - for name in ["A", "B", "C"] { - let id = mk_id(name); - zenv.insert( - id.clone(), - KConst::::Axio { - name: mk_name(name), - level_params: vec![], - is_unsafe: false, - lvls: 0, - ty: sort0(), - }, - ); - } - let le = lean_egress(&zenv).unwrap(); - assert_eq!(le.len(), 3); - for name in ["A", "B", "C"] { - let ci = le.get(&mk_name(name)).expect("missing name"); - assert!(matches!(&*ci, LeanCI::AxiomInfo(..))); - } - } - - // ---- ixon egress: sharing limits ---- - - #[test] - fn ixon_egress_preserves_binding_only_for_identical_constants() { - use ixon::env::RoleBinding; - use ixon::role_table::RoleTable; - use std::sync::Arc; - - let name = mk_name("Egress.Bound"); - let id = mk_id("Egress.Bound"); - let mut kenv = KEnv::::new(); - let axiom = |ty| KConst::::Axio { - name: name.clone(), - level_params: vec![], - is_unsafe: false, - lvls: 0, - ty, - }; - kenv.insert(id.clone(), axiom(sort0())); - let seed = IxonEnv::new(); - seed.register_name( - name.clone(), - Named::new(id.addr.clone(), ixon::ConstantMeta::default()), - ); - let limits = ExactSharingLimits::default(); - let mut original = ixon_egress(&kenv, &seed, &limits).unwrap(); - assert_eq!(original.consts.len(), 1); - assert!(original.role_binding.is_none()); - - let bytes = RoleTable { version: 2, bound: vec![], evidence: vec![] } - .to_bytes() - .unwrap(); - let binding = - RoleBinding { address: Address::hash(&bytes), bytes: Arc::from(bytes) }; - original.role_binding = Some(binding.clone()); - let rebuilt = ixon_egress(&kenv, &original, &limits).unwrap(); - assert_eq!(rebuilt.role_binding, Some(binding.clone())); - assert_eq!(rebuilt.consts.len(), 1); - assert_eq!(rebuilt.envelope_version(), 5); - - // The same named constant now has different content and a new address. - kenv.insert( - id, - axiom(KExpr::all(mk_name("x"), BinderInfo::Default, sort0(), sort0())), - ); - let error = ixon_egress(&kenv, &original, &limits).unwrap_err(); - assert_eq!( - error, - "ixon_egress: changed bound constants require a new role binding" - ); - original.role_binding = None; - let changed = ixon_egress(&kenv, &original, &limits).unwrap(); - assert_ne!( - changed.lookup_name(&name).unwrap().addr, - original.lookup_name(&name).unwrap().addr - ); - assert!(changed.role_binding.is_none()); - - // Losing an unnamed record also refuses to carry the old binding. - original.role_binding = Some(binding.clone()); - original.named.clear(); - assert_eq!( - ixon_egress(&kenv, &original, &limits).unwrap_err(), - "ixon_egress: changed bound constants require a new role binding" - ); - - // Authentication is checked even when the constant set is empty. - let mut empty = IxonEnv::new(); - empty.role_binding = Some(RoleBinding { - address: Address::hash(b"different binding"), - ..binding - }); - assert!( - ixon_egress(&KEnv::new(), &empty, &limits) - .unwrap_err() - .starts_with("role binding: bytes hash mismatch") - ); - } - - /// A sharing failure while rebuilding a constant names the constant and - /// keeps the compile error's category (here a resource limit). - #[test] - fn egress_standalone_sharing_failure_names_the_constant() { - let name = mk_name("Egress.Limited"); - let kc = KConst::::Axio { - name: name.clone(), - level_params: vec![], - is_unsafe: false, - lvls: 0, - ty: KExpr::all( - mk_name("x"), - BinderInfo::Default, - sort0(), - KExpr::all(mk_name("y"), BinderInfo::Default, sort0(), sort0()), - ), - }; - let mut name_index = FxHashMap::default(); - name_index.insert(name.clone(), (mk_id("Egress.Limited"), kc)); - let original_named = - Named::new(mk_addr("Egress.Limited"), ixon::ConstantMeta::default()); - let limits = - ExactSharingLimits { max_output_bytes: 1, ..Default::default() }; - let out = IxonEnv::new(); - let err = egress_standalone( - &name, - &original_named, - &IxonEnv::new(), - &name_index, - &limits, - &out, - ) - .expect_err("one output byte cannot hold the axiom"); - assert!( - err.starts_with("egress 'Egress.Limited': resource limit:"), - "{err}" - ); - assert!(err.contains("--sharing-limits output_bytes=N"), "{err}"); - // Under the default limits the same constant egresses. - egress_standalone( - &name, - &original_named, - &IxonEnv::new(), - &name_index, - &ExactSharingLimits::default(), - &out, - ) - .unwrap(); - assert!(out.named.contains_key(&name)); - } - - // ---- fallible universe interning ---- - - #[test] - fn preseeded_univ_table_refuses_an_absent_universe() { - // The original constant's table holds `zero` only: `Sort 0` resolves - // to its index, while `Sort 1` would append and shift the original - // meta's patch index space, so it is an error in every build. - let mut ctx = EgressCtx::new(); - ctx.preseed_univs(&[IxonUniv::zero()]); - let hit = kexpr_to_ixon(&sort0(), &mut ctx).unwrap(); - assert_eq!(hit, IxonExpr::sort(0)); - let err = kexpr_to_ixon(&sort_succ(), &mut ctx) - .expect_err("a universe absent from the preseeded table"); - assert!(err.contains("absent from the original constant's table"), "{err}"); - let (_, univs) = ctx.into_vecs(); - assert_eq!(univs, vec![IxonUniv::zero()]); - } - - #[test] - fn unseeded_univ_table_interns_in_first_use_order() { - let mut ctx = EgressCtx::new(); - assert_eq!( - kexpr_to_ixon(&sort_succ(), &mut ctx).unwrap(), - IxonExpr::sort(0) - ); - assert_eq!(kexpr_to_ixon(&sort0(), &mut ctx).unwrap(), IxonExpr::sort(1)); - let (_, univs) = ctx.into_vecs(); - assert_eq!(univs, vec![IxonUniv::succ(IxonUniv::zero()), IxonUniv::zero()]); - } - - #[test] - fn fvar_is_an_egress_error() { - let mut ctx = EgressCtx::new(); - let e = KExpr::::fvar(ix_kernel::expr::FVarId(7), mk_name("x")); - let err = - kexpr_to_ixon(&e, &mut ctx).expect_err("an FVar has no Ixon form"); - assert!(err.contains("unexpected FVar"), "{err}"); - } -} diff --git a/crates/compile/src/lib.rs b/crates/compile/src/lib.rs deleted file mode 100644 index 330493c10..000000000 --- a/crates/compile/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Lean ↔ Ixon compilation pipeline plus kernel ↔ Lean egress. - -pub mod compile; -pub mod condense; -pub mod decompile; -pub mod diag; -pub mod graph; -pub mod ground; -pub mod kernel_egress; -pub mod mutual; -pub mod semantic_contract; -pub mod store; diff --git a/crates/compile/src/mutual.rs b/crates/compile/src/mutual.rs deleted file mode 100644 index 5b7db373d..000000000 --- a/crates/compile/src/mutual.rs +++ /dev/null @@ -1,367 +0,0 @@ -//! Types for representing mutual definition blocks in the compilation pipeline. -//! -//! Mutual blocks are groups of definitions that reference each other cyclically. -//! [`MutCtx`] maps names to their indices within a mutual block, and the -//! [`ctx_to_all`] / [`all_to_ctx`] functions convert between ordered name -//! vectors and index maps. - -use bignat::Nat; - -use ix_common::env::{ - ConstructorVal, DefinitionSafety, DefinitionVal, Expr, InductiveVal, Name, - OpaqueVal, RecursorVal, ReducibilityHints, TheoremVal, -}; - -use ixon::constant::DefKind; - -use rustc_hash::FxHashMap; - -/// A definition-like constant (definition, theorem, or opaque) unified into a -/// single representation for mutual block processing. -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct Def { - /// Fully-qualified name of the definition. - pub name: Name, - /// Universe-polymorphic level parameter names. - pub level_params: Vec, - /// The type of the definition. - pub typ: Expr, - /// The kind of definition (definition, theorem, or opaque). - pub kind: DefKind, - /// The definition body. - pub value: Expr, - /// Reducibility hints for the kernel. - pub hints: ReducibilityHints, - /// Safety classification. - pub safety: DefinitionSafety, - /// Names of all constants in the same mutual block. - pub all: Vec, -} - -impl Def { - /// Constructs a `Def` from a [`DefinitionVal`]. - pub fn mk_defn(val: &DefinitionVal) -> Self { - let DefinitionVal { cnst, value, hints, safety, all } = val; - Self { - name: cnst.name.clone(), - level_params: cnst.level_params.clone(), - typ: cnst.typ.clone(), - kind: DefKind::Definition, - value: value.clone(), - hints: *hints, - safety: *safety, - all: all.clone(), - } - } - /// Constructs a `Def` from a [`TheoremVal`]. - pub fn mk_theo(val: &TheoremVal) -> Self { - let TheoremVal { cnst, value, all } = val; - Self { - name: cnst.name.clone(), - level_params: cnst.level_params.clone(), - typ: cnst.typ.clone(), - kind: DefKind::Theorem, - value: value.clone(), - hints: ReducibilityHints::Opaque, - safety: DefinitionSafety::Safe, - all: all.clone(), - } - } - /// Constructs a `Def` from an [`OpaqueVal`]. - pub fn mk_opaq(val: &OpaqueVal) -> Self { - let OpaqueVal { cnst, value, is_unsafe, all } = val; - Self { - name: cnst.name.clone(), - level_params: cnst.level_params.clone(), - typ: cnst.typ.clone(), - kind: DefKind::Opaque, - value: value.clone(), - hints: ReducibilityHints::Opaque, - safety: if *is_unsafe { - DefinitionSafety::Unsafe - } else { - DefinitionSafety::Safe - }, - all: all.clone(), - } - } -} - -/// An inductive type bundled with its constructors for mutual block processing. -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct Ind { - /// The inductive type declaration. - pub ind: InductiveVal, - /// The constructors belonging to this inductive type. - pub ctors: Vec, -} - -/// Type alias for a recursor value within a mutual block. -pub type Rec = RecursorVal; - -/// A constant within a mutual definition block. -#[derive(Clone, PartialEq, Eq, Debug)] -pub enum MutConst { - /// A definition, theorem, or opaque constant. - Defn(Def), - /// An inductive type with its constructors. - Indc(Ind), - /// A recursor (eliminator). - Recr(Rec), -} - -/// Maps names to their index within a mutual block. -pub type MutCtx = FxHashMap; - -/// Convert a MutCtx to a Vec ordered by index. -/// Position i contains the name with Nat value i. -pub fn ctx_to_all(ctx: &MutCtx) -> Vec { - let mut pairs: Vec<_> = ctx.iter().collect(); - pairs.sort_by(|(n1, i1), (n2, i2)| { - i1.to_u64() - .unwrap_or(0) - .cmp(&i2.to_u64().unwrap_or(0)) - .then_with(|| n1.cmp(n2)) - }); - pairs.into_iter().map(|(name, _)| name.clone()).collect() -} - -/// Convert a Vec to a MutCtx. -/// Each name gets its position as the Nat value. -pub fn all_to_ctx(all: &[Name]) -> MutCtx { - let mut ctx = FxHashMap::default(); - for (i, name) in all.iter().enumerate() { - ctx.insert(name.clone(), Nat(i.into())); - } - ctx -} - -impl MutConst { - /// Returns the name of this mutual constant. - pub fn name(&self) -> Name { - match self { - Self::Defn(x) => x.name.clone(), - Self::Recr(x) => x.cnst.name.clone(), - Self::Indc(x) => x.ind.cnst.name.clone(), - } - } - - /// Returns the constructors if this is an inductive, or an empty vec otherwise. - pub fn ctors(&self) -> Vec { - match self { - Self::Indc(ind) => ind.ctors.clone(), - _ => vec![], - } - } - /// Returns `true` if this mutual constant contains the given name - /// (including constructor names for inductives). - pub fn contains(&self, name: &Name) -> bool { - match self { - Self::Defn(x) => x.name == *name, - Self::Recr(x) => x.cnst.name == *name, - Self::Indc(x) => { - x.ind.cnst.name == *name || x.ctors.iter().any(|c| c.cnst.name == *name) - }, - } - } - /// Creates a [`MutCtx`] with a single name at index 0. - pub fn single_ctx(name: Name) -> MutCtx { - let mut mut_ctx = FxHashMap::default(); - mut_ctx.insert(name, Nat(0u64.into())); - mut_ctx - } - - /// Builds a [`MutCtx`] from ordered mutual members, assigning - /// indices to types first and then to constructors. - pub fn ctx(members: &[&MutConst]) -> MutCtx { - let mut mut_ctx = FxHashMap::default(); - let mut i = members.len(); - for (j, cnst) in members.iter().enumerate() { - mut_ctx.insert(cnst.name(), Nat(j.into())); - let ctors = cnst.ctors(); - for (cidx, c) in ctors.iter().enumerate() { - mut_ctx.insert(c.cnst.name.clone(), Nat((i + cidx).into())); - } - i += ctors.len(); - } - mut_ctx - } -} - -#[cfg(test)] -mod tests { - use super::*; - use ix_common::env::{ConstantVal, Level}; - - fn n(s: &str) -> Name { - Name::str(Name::anon(), s.to_string()) - } - - fn sort0() -> Expr { - Expr::sort(Level::zero()) - } - - fn mk_constant_val(name: &str) -> ConstantVal { - ConstantVal { name: n(name), level_params: vec![], typ: sort0() } - } - - fn mk_def(name: &str) -> MutConst { - MutConst::Defn(Def { - name: n(name), - level_params: vec![], - typ: sort0(), - kind: DefKind::Definition, - value: Expr::bvar(Nat(0u64.into())), - hints: ReducibilityHints::Opaque, - safety: DefinitionSafety::Safe, - all: vec![n(name)], - }) - } - - fn mk_ind(name: &str, ctor_names: &[&str]) -> MutConst { - let ctors = ctor_names - .iter() - .enumerate() - .map(|(i, cn)| ConstructorVal { - cnst: mk_constant_val(cn), - induct: n(name), - cidx: Nat::from(i as u64), - num_params: Nat(0u64.into()), - num_fields: Nat(0u64.into()), - is_unsafe: false, - }) - .collect(); - MutConst::Indc(Ind { - ind: InductiveVal { - cnst: mk_constant_val(name), - num_params: Nat(0u64.into()), - num_indices: Nat(0u64.into()), - all: vec![n(name)], - ctors: ctor_names.iter().map(|c| n(c)).collect(), - num_nested: Nat(0u64.into()), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }, - ctors, - }) - } - - fn mk_rec(name: &str) -> MutConst { - MutConst::Recr(RecursorVal { - cnst: mk_constant_val(name), - all: vec![n(name)], - num_params: Nat(0u64.into()), - num_indices: Nat(0u64.into()), - num_motives: Nat(1u64.into()), - num_minors: Nat(0u64.into()), - rules: vec![], - k: false, - is_unsafe: false, - }) - } - - #[test] - fn all_to_ctx_assigns_indices() { - let names = vec![n("A"), n("B"), n("C")]; - let ctx = all_to_ctx(&names); - assert_eq!(ctx.get(&n("A")), Some(&Nat(0u64.into()))); - assert_eq!(ctx.get(&n("B")), Some(&Nat(1u64.into()))); - assert_eq!(ctx.get(&n("C")), Some(&Nat(2u64.into()))); - assert_eq!(ctx.len(), 3); - } - - #[test] - fn ctx_to_all_roundtrip() { - let names = vec![n("X"), n("Y"), n("Z")]; - let ctx = all_to_ctx(&names); - let recovered = ctx_to_all(&ctx); - assert_eq!(recovered, names); - } - - #[test] - fn single_ctx_has_one_entry() { - let ctx = MutConst::single_ctx(n("Foo")); - assert_eq!(ctx.len(), 1); - assert_eq!(ctx.get(&n("Foo")), Some(&Nat(0u64.into()))); - } - - #[test] - fn ctx_definitions_have_consecutive_indices() { - let d1 = mk_def("f"); - let d2 = mk_def("g"); - let ctx = MutConst::ctx(&[&d1, &d2]); - assert_eq!(ctx.get(&n("f")), Some(&Nat(0u64.into()))); - assert_eq!(ctx.get(&n("g")), Some(&Nat(1u64.into()))); - } - - #[test] - fn ctx_member_with_ctors() { - let ind = mk_ind("Bool", &["Bool.true", "Bool.false"]); - let ctx = MutConst::ctx(&[&ind]); - // The type gets member index 0. - assert_eq!(ctx.get(&n("Bool")), Some(&Nat(0u64.into()))); - // Constructors follow the one member. - assert_eq!(ctx.get(&n("Bool.true")), Some(&Nat(1u64.into()))); - assert_eq!(ctx.get(&n("Bool.false")), Some(&Nat(2u64.into()))); - } - - #[test] - fn ctx_constructors_follow_all_members() { - let ind = mk_ind("A", &["A.mk1", "A.mk2"]); - let def = mk_def("f"); - let other = mk_ind("B", &["B.mk"]); - let ctx = MutConst::ctx(&[&ind, &def, &other]); - assert_eq!(ctx.get(&n("A")), Some(&Nat(0u64.into()))); - assert_eq!(ctx.get(&n("f")), Some(&Nat(1u64.into()))); - assert_eq!(ctx.get(&n("B")), Some(&Nat(2u64.into()))); - assert_eq!(ctx.get(&n("A.mk1")), Some(&Nat(3u64.into()))); - assert_eq!(ctx.get(&n("A.mk2")), Some(&Nat(4u64.into()))); - assert_eq!(ctx.get(&n("B.mk")), Some(&Nat(5u64.into()))); - } - - #[test] - fn contains_defn() { - let d = mk_def("f"); - assert!(d.contains(&n("f"))); - assert!(!d.contains(&n("g"))); - } - - #[test] - fn contains_indc_and_ctors() { - let ind = mk_ind("Nat", &["Nat.zero", "Nat.succ"]); - assert!(ind.contains(&n("Nat"))); - assert!(ind.contains(&n("Nat.zero"))); - assert!(ind.contains(&n("Nat.succ"))); - assert!(!ind.contains(&n("Bool"))); - } - - #[test] - fn contains_recr() { - let r = mk_rec("Nat.rec"); - assert!(r.contains(&n("Nat.rec"))); - assert!(!r.contains(&n("Bool.rec"))); - } - - #[test] - fn name_returns_correct_name() { - assert_eq!(mk_def("f").name(), n("f")); - assert_eq!(mk_ind("T", &["T.mk"]).name(), n("T")); - assert_eq!(mk_rec("T.rec").name(), n("T.rec")); - } - - #[test] - fn ctors_returns_empty_for_non_inductive() { - assert!(mk_def("f").ctors().is_empty()); - assert!(mk_rec("r").ctors().is_empty()); - } - - #[test] - fn ctors_returns_constructor_vals_for_inductive() { - let ind = mk_ind("T", &["T.mk1", "T.mk2"]); - let ctors = ind.ctors(); - assert_eq!(ctors.len(), 2); - assert_eq!(ctors[0].cnst.name, n("T.mk1")); - assert_eq!(ctors[1].cnst.name, n("T.mk2")); - } -} diff --git a/crates/compile/src/semantic_contract.rs b/crates/compile/src/semantic_contract.rs deleted file mode 100644 index f7600344e..000000000 --- a/crates/compile/src/semantic_contract.rs +++ /dev/null @@ -1,393 +0,0 @@ -//! Semantic contract nodes carried through the source compiler. -//! Mirrors `Ix/SemanticContract.lean`; these frames are consumed into Ixon -//! binders and never serve as a resource-validity certificate. - -use ix_common::env::{Expr as Source, ExprData}; -use ixon::CompileError; -use ixon::contract::{BinderContract, LetKind, ValueContract}; -use ixon::expr::Expr; - -pub use ixon::contract::source::{ - Contract, Kind, decode, has_metadata, read, reserved, -}; - -#[cfg(test)] -use bignat::Nat; -#[cfg(test)] -use ix_common::env::{DataValue, Name, NameData}; -#[cfg(test)] -use std::sync::Arc; - -fn error(desc: &str) -> CompileError { - CompileError::UnsupportedExpr { desc: desc.into() } -} - -pub fn inspect(top: &Source) -> Result { - let mut seen = rustc_hash::FxHashSet::default(); - let mut stack = vec![top]; - let mut annotated = false; - while let Some(expr) = stack.pop() { - if !seen.insert(expr) { - continue; - } - match expr.as_data() { - ExprData::App(f, a, _) => { - stack.push(f); - stack.push(a); - }, - ExprData::Lam(_, t, b, ..) | ExprData::ForallE(_, t, b, ..) => { - stack.push(t); - stack.push(b); - }, - ExprData::LetE(_, t, v, b, ..) => { - stack.push(t); - stack.push(v); - stack.push(b); - }, - ExprData::Proj(_, _, v, _) => stack.push(v), - ExprData::Mdata(data, inner, _) => { - if has_metadata(data) { - let c = read(data)?; - if !matches!( - (c.kind, inner.as_data()), - (Kind::Lam, ExprData::Lam(..)) - | (Kind::All, ExprData::ForallE(..)) - | (Kind::Let, ExprData::LetE(..)) - ) { - return Err(error( - "semantic contract is not attached to its original binder", - )); - } - annotated = true; - } - stack.push(inner); - }, - _ => {}, - } - } - Ok(annotated) -} - -pub fn inspect_constant( - c: &ix_common::env::ConstantInfo, -) -> Result { - let mut annotated = inspect(c.get_type())?; - if let Some(body) = c.get_value() { - annotated |= inspect(body)?; - } - if let ix_common::env::ConstantInfo::RecInfo(r) = c { - for rule in &r.rules { - annotated |= inspect(&rule.rhs)?; - } - } - if annotated - && !matches!( - c, - ix_common::env::ConstantInfo::DefnInfo(_) - | ix_common::env::ConstantInfo::ThmInfo(_) - | ix_common::env::ConstantInfo::OpaqueInfo(_) - | ix_common::env::ConstantInfo::AxiomInfo(_) - ) - { - return Err(error( - "annotated inductive/constructor/recursor transformations are unsupported", - )); - } - Ok(annotated) -} - -pub fn of_ixon(expr: &Expr) -> Option { - let c = match expr { - Expr::Lam(binder, ..) => Contract { - kind: Kind::Lam, - binder: *binder, - result: ValueContract::shared(), - let_kind: LetKind::Value, - }, - Expr::All(binder, result, ..) => Contract { - kind: Kind::All, - binder: *binder, - result: *result, - let_kind: LetKind::Value, - }, - Expr::Let(c, ..) => Contract { - kind: Kind::Let, - binder: c.binder, - result: ValueContract::shared(), - let_kind: c.kind, - }, - _ => return None, - }; - (c.binder != BinderContract::default() - || c.result != ValueContract::shared() - || c.let_kind != LetKind::Value) - .then_some(c) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::compile::{BlockCache, CompileState, compile_expr}; - use crate::mutual::MutCtx; - use ix_common::env::{BinderInfo, Level}; - #[test] - fn inspection_rejects_raw_hash_aliases_in_both_orders() { - let domain = Source::sort(Level::zero()); - let lam = Source::lam( - Name::anon(), - domain.clone(), - Source::bvar(0u64.into()), - BinderInfo::Default, - ); - let contract = decode(0, 1).unwrap(); - let data = contract.metadata(); - let frame = |md| { - Source(Arc::new(ExprData::Mdata( - md, - lam.clone(), - blake3::Hash::from([0; 32]), - ))) - }; - let good = frame(data.clone()); - let mut nonnat = data.clone(); - nonnat[2].1 = DataValue::OfBool(true); - let mut bits = data.clone(); - bits[2].1 = DataValue::OfNat(16u64.into()); - let mut unknown = data.clone(); - let NameData::Str(parent, _, hash) = data[2].0.as_data() else { - panic!("code field has no name") - }; - unknown[2].0 = - Name(Arc::new(NameData::Str(parent.clone(), "unknown".into(), *hash))); - for (md, message) in [ - (data[..2].to_vec(), "malformed semantic contract frame"), - ( - decode(1, 1).unwrap().metadata(), - "semantic contract is not attached to its original binder", - ), - (nonnat, "semantic contract fields must be natural numbers"), - (bits, "reserved semantic contract bits"), - (unknown, "unknown or duplicate semantic contract field"), - ] { - let bad = frame(md); - assert_eq!(bad.get_hash(), good.get_hash()); - assert_ne!(bad, good); // Rust's existing derived Eq is already structural. - for input in [ - bad.clone(), - Source::app(bad.clone(), good.clone()), - Source::app(good.clone(), bad), - ] { - assert!( - matches!(inspect(&input), Err(CompileError::UnsupportedExpr { desc }) if desc == message) - ); - } - } - let twin = frame(data); - assert!(!Arc::ptr_eq(&good.0, &twin.0)); - assert!(good.same_raw(&twin)); - assert!(inspect(&Source::app(good.clone(), good.clone())).unwrap()); - assert!(inspect(&Source::app(good, twin)).unwrap()); - assert!(!inspect(&domain).unwrap()); - } - - #[test] - fn strict_metadata_and_all_contract_codes() { - for (kind, count) in [(0, 16), (1, 64), (2, 32)] { - for code in 0..count { - match decode(kind, code) { - Ok(contract) => { - assert_eq!(read(&contract.metadata()).unwrap(), contract); - assert_eq!(u64::from(contract.code()), code); - }, - Err(_) => assert!(kind == 2 && code >= 16 && (code % 16) / 4 != 3), - } - } - assert!(decode(kind, count).is_err()); - } - let data = decode(0, 1).unwrap().metadata(); - assert!(has_metadata(&data)); - assert!(read(&data[..2]).is_err()); - let mut duplicate = data.clone(); - duplicate[0] = duplicate[1].clone(); - assert!(read(&duplicate).is_err()); - let mut wrong = data; - wrong[0].1 = DataValue::OfNat(Nat::from(2u64)); - assert!(read(&wrong).is_err()); - } - - #[test] - fn lowering_cache_ordering_and_borrow_contracts() { - let stt = CompileState::default(); - let mut cache = BlockCache::default(); - let domain = Source::sort(Level::zero()); - let body = Source::bvar(Nat::from(0u64)); - let lambda = Source::lam( - Name::anon(), - domain.clone(), - body.clone(), - BinderInfo::Default, - ); - let arrow = Source::all( - Name::anon(), - domain.clone(), - domain.clone(), - BinderInfo::Default, - ); - for code in 0..16 { - let contract = decode(0, code).unwrap(); - let source = contract.attach(lambda.clone()); - let output = - compile_expr(&source, &[], &MutCtx::default(), &mut cache, &stt) - .unwrap(); - assert_eq!( - output, - Expr::lam_contract(contract.binder, Expr::sort(0), Expr::var(0)) - ); - let key = crate::compile::ExprKey(source.clone()); - assert_eq!(cache.exprs.get(&key).unwrap().expr, output); - } - for code in 0..64 { - let contract = decode(1, code).unwrap(); - let source = contract.attach(arrow.clone()); - let output = - compile_expr(&source, &[], &MutCtx::default(), &mut cache, &stt) - .unwrap(); - assert_eq!( - output, - Expr::all_contract( - contract.binder, - contract.result, - Expr::sort(0), - Expr::sort(0) - ) - ); - let key = crate::compile::ExprKey(source.clone()); - assert_eq!(cache.exprs.get(&key).unwrap().expr, output); - } - let plain = Source::letE( - Name::anon(), - domain.clone(), - body.clone(), - body.clone(), - false, - ); - let contract = decode(2, 30).unwrap(); - let borrowed = contract.attach(plain.clone()); - let expected = Expr::let_contract( - ixon::contract::LetContract { - non_dep: false, - kind: LetKind::BorrowShared, - binder: contract.binder, - }, - Expr::sort(0), - Expr::var(0), - Expr::var(0), - ); - for (source, expected) in [ - (&plain, Expr::let_(false, Expr::sort(0), Expr::var(0), Expr::var(0))), - (&borrowed, expected), - (&plain, Expr::let_(false, Expr::sort(0), Expr::var(0), Expr::var(0))), - ] { - assert_eq!( - compile_expr(source, &[], &MutCtx::default(), &mut cache, &stt) - .unwrap(), - expected - ); - } - assert!( - compile_expr( - &contract.attach(domain.clone()), - &[], - &MutCtx::default(), - &mut cache, - &stt - ) - .is_err() - ); - let bad = - Source::mdata(decode(0, 1).unwrap().metadata()[..1].to_vec(), domain); - assert!( - compile_expr(&bad, &[], &MutCtx::default(), &mut cache, &stt).is_err() - ); - } - #[test] - fn committed_contracts_roundtrip_without_optional_metadata() { - use crate::decompile::{BlockCache as DecompileCache, decompile_expr}; - use ixon::metadata::{DataValue, ExprMeta, ExprMetaData}; - let stt = CompileState::new_empty(); - let mut terms = vec![]; - for bits in 0..16 { - let c = BinderContract::from_bits(bits).unwrap(); - terms.push(Expr::lam_contract(c, Expr::sort(0), Expr::var(0))); - for result in 0..4 { - terms.push(Expr::all_contract( - c, - ValueContract::from_bits(result).unwrap(), - Expr::sort(0), - Expr::sort(0), - )); - } - for non_dep in [false, true] { - for kind in [LetKind::Value, LetKind::BorrowShared] { - if kind == LetKind::BorrowShared - && c.value != ValueContract::local_shared() - { - continue; - } - terms.push(Expr::let_contract( - ixon::contract::LetContract { non_dep, kind, binder: c }, - Expr::sort(0), - Expr::var(0), - Expr::var(0), - )); - } - } - } - for term in terms { - let mut cache = DecompileCache { - univ_table: vec![ixon::univ::Univ::zero()], - ..Default::default() - }; - let source = decompile_expr( - &term, - &ExprMeta::default(), - u64::MAX, - &[], - &mut cache, - &stt, - ) - .unwrap(); - let result = compile_expr( - &source, - &[], - &MutCtx::default(), - &mut BlockCache::default(), - &stt, - ) - .unwrap(); - assert_eq!(result, term); - } - - let mut arena = ExprMeta::default(); - let key = - Name::str(Name::str(Name::anon(), "ix".into()), "contract".into()); - let addr = ix_common::address::Address::from_blake3_hash(*key.get_hash()); - stt.env.store_name(addr.clone(), key); - let leaf = arena.alloc(ExprMetaData::Leaf); - let root = arena.alloc(ExprMetaData::Mdata { - mdata: vec![vec![(addr, DataValue::OfBool(true))]], - child: leaf, - }); - assert!( - decompile_expr( - &Expr::var(0), - &arena, - root, - &[], - &mut DecompileCache::default(), - &stt, - ) - .is_err() - ); - } -} diff --git a/crates/compile/src/store.rs b/crates/compile/src/store.rs deleted file mode 100644 index 33dcaf797..000000000 --- a/crates/compile/src/store.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! Content-addressed filesystem store for Ix data. -//! -//! Objects are stored at `~/.ix/store/XX/YY/ZZ/` where `XX/YY/ZZ` -//! are derived from the first 6 hex characters of the Blake3 hash. This provides -//! deterministic addressing: identical content always maps to the same path. - -use ix_common::address::Address; -use std::env; -use std::fs; -use std::io; -use std::path::PathBuf; - -/// Errors that can occur during store operations. -#[derive(Debug)] -pub enum StoreError { - /// The requested address does not exist in the store. - UnknownAddress(Address), - /// An underlying filesystem I/O error. - IoError(io::Error), - /// An error during ixon serialization or deserialization. - IxonError(String), - /// The `HOME` environment variable is not set. - NoHome(env::VarError), -} - -impl std::fmt::Display for StoreError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - StoreError::UnknownAddress(a) => write!(f, "unknown address {:?}", a), - StoreError::IoError(e) => write!(f, "IO error: {}", e), - StoreError::IxonError(e) => write!(f, "ixon error: {}", e), - StoreError::NoHome(e) => write!(f, "no HOME environment variable: {}", e), - } - } -} - -impl std::error::Error for StoreError {} - -impl From for StoreError { - fn from(error: io::Error) -> Self { - StoreError::IoError(error) - } -} - -/// Alias for `Result`. -pub type StoreResult = Result; - -/// Handle for reading and writing content-addressed objects under `~/.ix/store`. -pub struct Store; - -impl Store { - /// Get the home directory from the HOME environment variable - fn get_home_dir() -> StoreResult { - env::var("HOME").map(PathBuf::from).map_err(StoreError::NoHome) - } - - /// Get the store directory path (~/.ix/store), creating it if needed - fn store_dir() -> StoreResult { - let home = Self::get_home_dir()?; - let path = home.join(".ix").join("store"); - if !path.exists() { - fs::create_dir_all(&path)?; - } - Ok(path) - } - - /// Get the full path for a given address, creating parent directories if needed - /// Path structure: store/XX/YY/ZZ/remaining_hex - /// where XX, YY, ZZ are the first 6 hex characters of the hash - fn store_path(addr: &Address) -> StoreResult { - let store = Self::store_dir()?; - let hex = addr.hex(); - let dir1 = &hex[0..2]; - let dir2 = &hex[2..4]; - let dir3 = &hex[4..6]; - let file = &hex[6..]; - let path = store.join(dir1).join(dir2).join(dir3); - if !path.exists() { - fs::create_dir_all(&path)?; - } - - Ok(path.join(file)) - } - - /// Write bytes to the store and return their address (blake3 hash) - pub fn write(bytes: &[u8]) -> StoreResult
{ - let addr = Address::hash(bytes); - let path = Self::store_path(&addr)?; - fs::write(path, bytes)?; - Ok(addr) - } - - /// Read bytes from the store given an address - pub fn read(addr: &Address) -> StoreResult> { - let path = Self::store_path(addr)?; - let bytes = fs::read(path)?; - Ok(bytes) - } -} diff --git a/crates/ffi/Cargo.toml b/crates/ffi/Cargo.toml index b79730d73..1f08db277 100644 --- a/crates/ffi/Cargo.toml +++ b/crates/ffi/Cargo.toml @@ -9,24 +9,15 @@ crate-type = ["lib", "staticlib"] [dependencies] aiur = { workspace = true } -anyhow = { workspace = true } -bignat = { workspace = true } blake3 = { workspace = true } -dashmap = { workspace = true, features = ["rayon"] } -indexmap = { workspace = true, features = ["rayon"] } -itertools = { workspace = true } ixvm-codegen = { workspace = true } -ix-bench = { workspace = true } ix-common = { workspace = true } -ix-compile = { workspace = true } ixon = { workspace = true } -ix-kernel = { workspace = true } ix-proof-compression = { path = "../proof-compression", default-features = false } lean-ffi = { workspace = true } memmap2 = { workspace = true } multi-stark = { workspace = true } mimalloc = { workspace = true } -num-bigint = { workspace = true } rayon = { workspace = true } rustc-hash = { workspace = true } serde_json = { workspace = true } @@ -37,10 +28,8 @@ tracing-subscriber = { workspace = true } tracing-texray = { workspace = true } # Iroh dependencies -bytes = { version = "1.10.1", optional = true } tokio = { version = "1.44.1", optional = true } iroh = { version = "0.97", optional = true } -iroh-base = { version = "0.97", optional = true } n0-error = { version = "0.1", optional = true } getrandom = { version = "0.3", optional = true } bincode = { version = "2.0.1", optional = true } @@ -54,8 +43,8 @@ cuda-trace-codegen = ["cuda", "aiur/cuda-trace-codegen"] # Final proof systems, which link the gnark Go archive. plonk = ["ix-proof-compression/plonk"] groth16 = ["ix-proof-compression/groth16"] -test-ffi = ["ixon/sharing-reference"] -net = ["bytes", "tokio", "iroh", "iroh-base", "n0-error", "getrandom", "bincode", "serde"] +test-ffi = [] +net = ["tokio", "iroh", "n0-error", "getrandom", "bincode", "serde"] [lints] workspace = true diff --git a/crates/ffi/examples/check_anon_subject.rs b/crates/ffi/examples/check_anon_subject.rs deleted file mode 100644 index 7e9f41e75..000000000 --- a/crates/ffi/examples/check_anon_subject.rs +++ /dev/null @@ -1,297 +0,0 @@ -//! Bounded-run building block for profiling ONE anonymous work item by primary -//! address. Dependencies are lazily ingressed but trusted, exactly as in one -//! work item of `ix check-rs --anon`. This is NOT corpus/closure verification. -//! -//! cargo run --release -p ix-ffi --example check_anon_subject -- FILE.ixe HEX -//! cargo run --release -p ix-ffi --example check_anon_subject -- --resolve FILE.ixe HEX... -//! -//! `--resolve` only enumerates work: map target addresses (including non-primary -//! block members) to their primary, without checking anything or loading names. -//! -//! Run under an external timeout/memory limit. IX_MAX_REC_FUEL and the existing -//! kernel diagnostic variables are honored. A fresh process gives a fresh -//! KEnv and avoids carrying worker-history caches between samples. -//! -//! Diagnostic-only runs: `IX_PERF_COUNTERS=1` prints cache hit rates; -//! `IX_REDUCE_HISTO=1` prints the top 20 delta/iota addresses and totals. -//! `IX_SAME_HEAD_PROFILE=1` reports actual same-head attempts and their fuel. -//! `IX_HOT_MISSES=1` prints miss shapes once at completion; optional -//! `IX_HOT_MISS_CTX=1` includes their context identities. -//! Collection is bounded to 4,096 keys; reported counts are intervals after -//! low-frequency entries are replaced, not exact per-key totals. -//! Reports go to stderr after checking; stdout's subject JSON is unchanged. -//! Leave these flags unset for paired benchmark timings. - -use std::{ - collections::{HashMap, HashSet}, - path::Path, - process::ExitCode, - sync::atomic::Ordering, - time::Instant, -}; - -use ix_common::address::Address; -use ix_kernel::{ - anon_work::{AnonWorkItem, build_anon_work}, - ixon_checker::IxonChecker, -}; -use ixon::env::Env; - -// Match the native ix executable, without calling its Lean FFI entrypoints. -#[global_allocator] -static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; - -#[derive(Debug, PartialEq, Eq)] -struct ResolvedSubject { - requested: Address, - primary: Address, - targets: usize, -} - -fn resolve_subjects( - work: &[AnonWorkItem], - requested: &[Address], -) -> Result, String> { - let wanted: HashSet<_> = requested.iter().cloned().collect(); - let mut found = HashMap::new(); - for item in work { - for target in item.targets() { - if wanted.contains(target) { - found.insert(target.clone(), (item.primary(), item.targets().len())); - } - } - } - requested - .iter() - .map(|addr| { - let (primary, targets) = found.get(addr).ok_or_else(|| { - format!("{} is not a kernel-checkable target address", addr.hex()) - })?; - Ok(ResolvedSubject { - requested: addr.clone(), - primary: (*primary).clone(), - targets: *targets, - }) - }) - .collect() -} - -fn run(args: &[String]) -> Result { - if args.first().is_some_and(|arg| arg == "--resolve") && args.len() >= 3 { - let requested: Vec<_> = args[2..] - .iter() - .map(|arg| { - Address::from_hex(arg).ok_or_else(|| format!("invalid address: {arg}")) - }) - .collect::>()?; - let env = Env::get_anon_mmap(Path::new(&args[1]))?; - let work = build_anon_work(&env)?; - let resolved = resolve_subjects(&work, &requested)?; - let rows: Vec<_> = resolved - .iter() - .map(|row| { - serde_json::json!({ - "requested": row.requested.hex(), "primary": row.primary.hex(), - "targets": row.targets, - }) - }) - .collect(); - println!( - "{}", - serde_json::json!({ - "scope": "index-only", "resolutions": rows, - }) - ); - return Ok(true); - } - if args.len() != 2 || args[0] == "--resolve" { - return Err("usage: check_anon_subject FILE.ixe PRIMARY_HEX\n check_anon_subject --resolve FILE.ixe TARGET_HEX...\nSubject-only profiling: dependencies are trusted, not checked.".to_owned()); - } - let primary = Address::from_hex(&args[1]) - .ok_or_else(|| format!("invalid primary address: {}", args[1]))?; - check(&args[0], &primary) -} - -fn check(path: &str, primary: &Address) -> Result { - let start = Instant::now(); - let env = Env::get_anon_mmap(Path::new(path))?; - let load_secs = start.elapsed().as_secs_f64(); - let start = Instant::now(); - let work = build_anon_work(&env)?; - let item = - work.iter().find(|item| item.primary() == primary).ok_or_else(|| { - format!("{} is not a work-item primary address", primary.hex()) - })?; - let targets = item.targets().len(); - eprintln!( - "[subject] primary={} targets={targets} scope=subject-only workers=1 fuel_cap_per_member={} load={load_secs:.3}s enumerate={:.3}s", - primary.hex(), - ix_kernel::tc::max_rec_fuel(), - start.elapsed().as_secs_f64() - ); - let mut checker = IxonChecker::new(&env); - checker.set_debug_label(format!("#{}", primary.hex())); - let _ = ix_kernel::profile::take_op_counts(); - ix_kernel::perf::same_head::reset(); - let start = Instant::now(); - let result = checker.check_const(primary); - let stats = checker.last_check(); - let check_secs = start.elapsed().as_secs_f64(); - let ops = ix_kernel::profile::take_op_counts(); - let aggregate_fuel = ix_kernel::perf::enabled() - .then(|| checker.perf().total_rec_fuel_used.load(Ordering::Relaxed)); - let report = serde_json::json!({ - "primary": primary.hex(), "scope": "subject-only", "targets": targets, - "passed": result.is_ok(), "error": result.as_ref().err().map(ToString::to_string), - "load_secs": load_secs, "check_secs": check_secs, - "fuel_cap_per_member": ix_kernel::tc::max_rec_fuel(), - "last_member_fuel": stats.fuel_used, - // Null unless IX_PERF_COUNTERS is enabled; do not label the final member's - // budget as the total fuel of a multi-member work item. - "aggregate_fuel": aggregate_fuel, "last_member_def_eq_peak": stats.def_eq_peak, - "subst": ops.subst_nodes, "whnf": ops.whnf_calls, - "def_eq": ops.def_eq_calls, "intern": ops.intern_nodes, - "nat_arith": ops.nat_arith, - }); - println!("{report}"); - eprint!("{}", stats.hot_misses); - eprint!("{}", ix_kernel::perf::same_head::summary()); - if ix_kernel::perf::enabled() { - // The example does not install a log backend, so KEnv's log::info! - // drop summary would otherwise be invisible. No checker work is rerun. - eprint!("{}", checker.perf().summary()); - } - if ix_kernel::perf::reduce_histo_enabled() { - print_reductions( - "delta", - ix_kernel::perf::DELTA_HISTO - .iter() - .map(|entry| (entry.key().clone(), *entry.value())) - .collect(), - ); - print_reductions( - "iota", - ix_kernel::perf::IOTA_HISTO - .iter() - .map(|entry| (entry.key().clone(), *entry.value())) - .collect(), - ); - eprintln!( - "[reduce-histo] nat_succ_peels={}", - ix_kernel::perf::NAT_SUCC_PEELS.load(Ordering::Relaxed) - ); - } - Ok(result.is_ok()) -} - -const REDUCTION_REPORT_LIMIT: usize = 20; - -fn top_reductions( - mut entries: Vec<(Address, u64)>, -) -> (u128, usize, Vec<(Address, u64)>) { - let total = entries.iter().map(|(_, n)| u128::from(*n)).sum(); - let distinct = entries.len(); - entries.sort_unstable_by(|(a, x), (b, y)| y.cmp(x).then_with(|| a.cmp(b))); - entries.truncate(REDUCTION_REPORT_LIMIT); - (total, distinct, entries) -} - -fn print_reductions(label: &str, entries: Vec<(Address, u64)>) { - let (total, distinct, top) = top_reductions(entries); - eprintln!( - "[reduce-histo] {label}: {total} reductions across {distinct} addresses; top {}", - top.len() - ); - for (addr, count) in top { - eprintln!("[reduce-histo] {label} {count} #{}", addr.hex()); - } -} - -fn main() -> ExitCode { - let args: Vec<_> = std::env::args().skip(1).collect(); - // Match the CLI's dedicated worker stack, not the process main stack. - let worker = std::thread::Builder::new() - .name("ix-kernel-subject".to_owned()) - .stack_size(256 * 1024 * 1024) - .spawn(move || run(&args)); - match worker { - Ok(worker) => match worker.join() { - Ok(Ok(true)) => ExitCode::SUCCESS, - Ok(Ok(false)) => ExitCode::FAILURE, - Ok(Err(error)) => { - eprintln!("{error}"); - ExitCode::from(2) - }, - Err(_) => { - eprintln!("subject worker panicked"); - ExitCode::from(2) - }, - }, - Err(error) => { - eprintln!("cannot start subject worker: {error}"); - ExitCode::from(2) - }, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn reduction_report_bounds_rows_but_preserves_complete_totals() { - let entries: Vec<_> = - (0..25u64).map(|n| (Address::hash(&n.to_le_bytes()), n + 1)).collect(); - let (total, distinct, top) = top_reductions(entries); - assert_eq!(total, 325); - assert_eq!(distinct, 25); - assert_eq!(top.len(), REDUCTION_REPORT_LIMIT); - assert_eq!(top.first().unwrap().1, 25); - assert_eq!(top.last().unwrap().1, 6); - } - - #[test] - fn reduction_report_handles_ties_empty_input_and_wide_totals() { - assert_eq!(top_reductions(Vec::new()), (0, 0, Vec::new())); - let a = Address::hash(b"a"); - let b = Address::hash(b"b"); - let (total, distinct, top) = - top_reductions(vec![(b.clone(), u64::MAX), (a.clone(), u64::MAX)]); - assert_eq!(total, 2 * u128::from(u64::MAX)); - assert_eq!(distinct, 2); - assert!(top[0].0 < top[1].0); - assert_eq!(top_reductions(vec![(a, u64::MAX), (b, u64::MAX)]).2, top); - } - - #[test] - fn resolves_members_to_primary_preserving_request_order() { - let a = Address::hash(b"standalone"); - let b = Address::hash(b"primary"); - let c = Address::hash(b"member"); - let work = vec![ - AnonWorkItem::Standalone { addr: a.clone() }, - AnonWorkItem::Block { - block_addr: Address::hash(b"block"), - primary: b.clone(), - targets: vec![b.clone(), c.clone()], - }, - ]; - let rows = - resolve_subjects(&work, &[c.clone(), a.clone(), b.clone()]).unwrap(); - assert_eq!( - rows, - vec![ - ResolvedSubject { requested: c, primary: b.clone(), targets: 2 }, - ResolvedSubject { requested: a.clone(), primary: a, targets: 1 }, - ResolvedSubject { requested: b.clone(), primary: b, targets: 2 }, - ] - ); - } - - #[test] - fn missing_address_is_an_error_not_a_partial_success() { - let a = Address::hash(b"present"); - let work = vec![AnonWorkItem::Standalone { addr: a.clone() }]; - assert!(resolve_subjects(&work, &[a, Address::hash(b"absent")]).is_err()); - } -} diff --git a/crates/ffi/src/aiur/aggregate.rs b/crates/ffi/src/aiur/aggregate.rs index 6f9d53222..00d2fe183 100644 --- a/crates/ffi/src/aiur/aggregate.rs +++ b/crates/ffi/src/aiur/aggregate.rs @@ -13,7 +13,7 @@ //! `contains` traversals that made the former Lean startup super-linear. use aiur::synthesis::{AiurProof, AiurSystem}; -use ix_kernel::shard::ShardManifest; +use ixon::shard::ShardManifest; use ixon::{ClaimContext, Proof as IxonProof}; use ixvm_codegen::env_handle::EnvHandle; use plan::{ diff --git a/crates/ffi/src/aiur/aggregate/ffi.rs b/crates/ffi/src/aiur/aggregate/ffi.rs index 04fe2988b..63e7a4f46 100644 --- a/crates/ffi/src/aiur/aggregate/ffi.rs +++ b/crates/ffi/src/aiur/aggregate/ffi.rs @@ -19,7 +19,7 @@ use super::{ }; use crate::lean::LeanAiurAggregateExpected; use aiur::execute::IOBuffer; -use ix_kernel::shard::ShardManifest; +use ixon::shard::ShardManifest; use ixvm_codegen::env_handle::EnvHandle; use lean_ffi::object::LeanString; use std::{fs, path::Path}; diff --git a/crates/ffi/src/aiur/aggregate/multi_gpu.rs b/crates/ffi/src/aiur/aggregate/multi_gpu.rs index b8bf80ede..b1000cca3 100644 --- a/crates/ffi/src/aiur/aggregate/multi_gpu.rs +++ b/crates/ffi/src/aiur/aggregate/multi_gpu.rs @@ -525,8 +525,8 @@ pub fn run( return Ok(root); }, PassOutcome::Refine(ids) => { - let profile = profile - .get_or_insert_with(|| crate::kernel::static_block_profile(env)); + let profile = + profile.get_or_insert_with(|| ixon::shard::static_block_profile(env)); let (refined, parts) = manifest.bisect_leaves(profile, &ids)?; checkpoint::write_atomic(&checkpoint_path, &refined.to_bytes())?; for (&id, parts) in ids.iter().zip(parts) { diff --git a/crates/ffi/src/aiur/aggregate/plan.rs b/crates/ffi/src/aiur/aggregate/plan.rs index 109c06232..803616b7f 100644 --- a/crates/ffi/src/aiur/aggregate/plan.rs +++ b/crates/ffi/src/aiur/aggregate/plan.rs @@ -11,7 +11,7 @@ use super::{ }; use aiur::G; use ix_common::address::Address; -use ix_kernel::shard::AggNode; +use ixon::shard::AggNode; use rustc_hash::FxHashMap; use std::sync::Arc; diff --git a/crates/ffi/src/aiur/aggregate/prepare.rs b/crates/ffi/src/aiur/aggregate/prepare.rs index 8e4ecdc6b..0ad6827f0 100644 --- a/crates/ffi/src/aiur/aggregate/prepare.rs +++ b/crates/ffi/src/aiur/aggregate/prepare.rs @@ -2,7 +2,7 @@ use super::statement::{CanonicalTree, ShardSet, Statement, SubjectTree}; use ix_common::address::Address; -use ix_kernel::shard::{AggNode, ShardManifest}; +use ixon::shard::{AggNode, ShardManifest}; use ixon::{ Constant, ConstantInfo, merkle::merkle_root_canonical_sorted, shard_claim::walk_edges, diff --git a/crates/ffi/src/aiur/aggregate/statement.rs b/crates/ffi/src/aiur/aggregate/statement.rs index c43cb70f0..60ed1869c 100644 --- a/crates/ffi/src/aiur/aggregate/statement.rs +++ b/crates/ffi/src/aiur/aggregate/statement.rs @@ -36,6 +36,11 @@ impl ShardSet { .get(index / 64) .is_some_and(|word| word & (1u64 << (index % 64)) != 0) } + + pub(super) fn is_disjoint(&self, other: &Self) -> bool { + debug_assert_eq!(self.0.len(), other.0.len()); + self.0.iter().zip(&other.0).all(|(a, b)| a & b == 0) + } } /// Canonical sorted tree whose expensive derivative representations are built @@ -243,6 +248,11 @@ impl Statement { if left.ctx != right.ctx { return Err("aggregate children differ in claim context".into()); } + if !left.subjects.shards.is_disjoint(&right.subjects.shards) + || left.subjects.root == right.subjects.root + { + return Err("aggregate subject sets overlap".into()); + } let subjects = if structural { SubjectTree::structural(left.subjects.clone(), right.subjects.clone()) } else { @@ -257,6 +267,22 @@ impl Statement { let owner = owner_by_address.get(&candidate).ok_or_else(|| { format!("aggregate assumption {} has no owning shard", candidate.hex()) })?; + let in_left_asm = left + .assumptions + .as_ref() + .is_some_and(|tree| tree.leaves.binary_search(&candidate).is_ok()); + let in_right_asm = right + .assumptions + .as_ref() + .is_some_and(|tree| tree.leaves.binary_search(&candidate).is_ok()); + if (in_left_asm && left.subjects.shards.contains(*owner)) + || (in_right_asm && right.subjects.shards.contains(*owner)) + { + return Err(format!( + "aggregate child assumes its own subject {}", + candidate.hex() + )); + } if !subjects.shards.contains(*owner) { remaining.push(candidate); } diff --git a/crates/ffi/src/aiur/aggregate/tests.rs b/crates/ffi/src/aiur/aggregate/tests.rs index 67cf7365b..88c869d20 100644 --- a/crates/ffi/src/aiur/aggregate/tests.rs +++ b/crates/ffi/src/aiur/aggregate/tests.rs @@ -8,7 +8,7 @@ use super::{ }; use aiur::G; use ix_common::address::Address; -use ix_kernel::shard::{AggNode, ShardInfo, ShardManifest}; +use ixon::shard::{AggNode, ShardInfo, ShardManifest}; use ixon::{Constant, ConstantInfo}; use multi_stark::p3_field::PrimeCharacteristicRing; use std::sync::Arc; diff --git a/crates/ffi/src/aiur/aggregate/tests/prepare.rs b/crates/ffi/src/aiur/aggregate/tests/prepare.rs index 5d260364d..2fb7fd0b4 100644 --- a/crates/ffi/src/aiur/aggregate/tests/prepare.rs +++ b/crates/ffi/src/aiur/aggregate/tests/prepare.rs @@ -1,5 +1,5 @@ -use ix_kernel::shard::{AggNode, ShardManifest}; use ixon::Expr; +use ixon::shard::{AggNode, ShardManifest}; use super::super::{ expected_from_manifest, diff --git a/crates/ffi/src/aiur/protocol.rs b/crates/ffi/src/aiur/protocol.rs index d3e7b9732..dae6226da 100644 --- a/crates/ffi/src/aiur/protocol.rs +++ b/crates/ffi/src/aiur/protocol.rs @@ -762,7 +762,7 @@ fn exec_rss_estimate(owned_bytes: usize) -> usize { } /// FFI: the detected prover/execution RAM budget in bytes -/// ([`detected_ram_budget`]: [`ix_kernel::shard::RAM_USABLE_FRAC`] of +/// ([`detected_ram_budget`]: [`ixon::shard::RAM_USABLE_FRAC`] of /// `MemAvailable`), or `0` when `/proc/meminfo` is unreadable — the caller /// decides whether to fail closed. #[unsafe(no_mangle)] @@ -773,7 +773,7 @@ extern "C" fn rs_aiur_detected_ram_budget() } /// Detected prover/execution RAM budget: -/// [`ix_kernel::shard::RAM_USABLE_FRAC`] of `MemAvailable`, reserving +/// [`ixon::shard::RAM_USABLE_FRAC`] of `MemAvailable`, reserving /// the rest for the OS. `None` (no gate) when meminfo is unreadable — /// disabling the check beats guessing at it. /// The trace-shard cell budget the prover plans to, from @@ -808,7 +808,7 @@ pub(crate) fn record_share( #[allow(clippy::cast_sign_loss)] // MemAvailable and the fraction are positive pub(crate) fn detected_ram_budget() -> Option { available_ram_bytes() - .map(|b| (b as f64 * ix_kernel::shard::RAM_USABLE_FRAC) as usize) + .map(|b| (b as f64 * ixon::shard::RAM_USABLE_FRAC) as usize) } /// This process's resident set from `/proc/self/status`, in bytes; `None` diff --git a/crates/ffi/src/builder.rs b/crates/ffi/src/builder.rs deleted file mode 100644 index 8876395be..000000000 --- a/crates/ffi/src/builder.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! LeanBuildCache struct for constructing Lean Ix types with caching. - -use blake3::Hash; -use rustc_hash::FxHashMap; - -use lean_ffi::object::LeanOwned; - -use crate::lean::{LeanIxExpr, LeanIxLevel, LeanIxName}; - -/// Cache for constructing Lean Ix types with deduplication. -/// -/// This struct maintains caches for names, levels, and expressions to avoid -/// rebuilding the same Lean objects multiple times during environment construction. -pub struct LeanBuildCache { - pub(crate) names: FxHashMap>, - pub(crate) levels: FxHashMap>, - pub(crate) exprs: FxHashMap>, -} - -impl LeanBuildCache { - pub fn new() -> Self { - Self { - names: FxHashMap::default(), - levels: FxHashMap::default(), - exprs: FxHashMap::default(), - } - } - - pub fn with_capacity(cap: usize) -> Self { - Self { - names: FxHashMap::with_capacity_and_hasher(cap, Default::default()), - levels: FxHashMap::with_capacity_and_hasher(cap, Default::default()), - exprs: FxHashMap::with_capacity_and_hasher(cap * 10, Default::default()), - } - } -} - -impl Default for LeanBuildCache { - fn default() -> Self { - Self::new() - } -} diff --git a/crates/ffi/src/compile.rs b/crates/ffi/src/compile.rs deleted file mode 100644 index 698c0884b..000000000 --- a/crates/ffi/src/compile.rs +++ /dev/null @@ -1,1886 +0,0 @@ -//! FFI bridge between Lean and Rust for the Ixon compilation/decompilation pipeline. -//! -//! Provides `extern "C"` functions callable from Lean via `@[extern]`: -//! - `rs_compile_env` / `rs_compile_env_full`: compile a Lean environment to Ixon -//! - `rs_compile_phases`: run individual pipeline phases (canon, condense, graph, compile) -//! - `rs_decompile_env`: decompile a serialized `.ixe` env back to Lean constants -//! - `rs_roundtrip_*`: roundtrip FFI tests for Lean↔Rust type conversions -//! - `build_*` / `decode_*`: convert between Lean constructor layouts and Rust types - -use std::sync::Arc; - -use crate::lean::{ - LeanIxBlock, LeanIxCompileEnvStatus, LeanIxCompileError, LeanIxCompilePhases, - LeanIxCondensedBlocks, LeanIxDecompileError, LeanIxName, - LeanIxRawEnvironment, LeanIxSerializeError, LeanIxonRawBlob, LeanIxonRawComm, - LeanIxonRawConst, LeanIxonRawEnv, LeanIxonRawNameEntry, LeanIxonRawNamed, -}; -use ix_common::address::Address; -use ix_common::env::{Name, ReducibilityHints}; -use ix_compile::compile::{ - CompileOptions, CompileState, compile_env_with_options, -}; -use ix_compile::condense::compute_sccs; -use ix_compile::decompile::decompile_env; -use ix_compile::graph::build_ref_graph; -use ixon::constant::Constant as IxonConstant; -#[cfg(feature = "test-ffi")] -use ixon::constant::ConstantInfo; -#[cfg(feature = "test-ffi")] -use ixon::expr::Expr as IxonExpr; -use ixon::{Comm, ConstantMeta}; -use lean_ffi::object::LeanIOResult; -use lean_ffi::object::LeanNat; -use lean_ffi::object::{ - LeanArray, LeanBorrowed, LeanByteArray, LeanList, LeanOwned, LeanProd, - LeanRef, LeanString, -}; - -use crate::builder::LeanBuildCache; -use crate::lean::LeanIxAddress; - -#[cfg(feature = "test-ffi")] -use crate::lean::{LeanIxBlockCompareDetail, LeanIxBlockCompareResult}; -#[cfg(feature = "test-ffi")] -use crate::lean_env::{GlobalCache, decode_name}; -#[cfg(feature = "test-ffi")] -use ixon::serialize::put_expr; -#[cfg(feature = "test-ffi")] -use std::collections::HashMap; - -// ============================================================================= -// Helper builders -// ============================================================================= - -/// Build a Lean String from a Rust &str. -fn build_lean_string(s: &str) -> LeanString { - LeanString::new(s) -} - -/// Build a Lean Nat from a usize. -fn build_lean_nat_usize(n: usize) -> LeanOwned { - LeanOwned::from_nat_u64(n as u64) -} - -// ============================================================================= -// Raw* Builder Functions for Compile FFI -// ============================================================================= - -/// Build RawConst using type method. -fn build_raw_const( - addr: &Address, - constant: &IxonConstant, -) -> LeanIxonRawConst { - LeanIxonRawConst::build_from_parts(addr, constant) -} - -/// Build RawNamed using type method. -fn build_raw_named( - cache: &mut LeanBuildCache, - name: &Name, - addr: &Address, - meta: &ConstantMeta, - original: &Option<(Address, ConstantMeta)>, - hints: Option, -) -> LeanIxonRawNamed { - LeanIxonRawNamed::build_from_parts(cache, name, addr, meta, original, hints) -} - -/// Build RawBlob using type method. -fn build_raw_blob(addr: &Address, bytes: &[u8]) -> LeanIxonRawBlob { - LeanIxonRawBlob::build_from_parts(addr, bytes) -} - -/// Build RawComm using type method. -fn build_raw_comm(addr: &Address, comm: &Comm) -> LeanIxonRawComm { - LeanIxonRawComm::build_from_parts(addr, comm) -} - -// ============================================================================= -// RustCondensedBlocks roundtrip FFI -// ============================================================================= - -/// Round-trip a RustCondensedBlocks structure. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_rust_condensed_blocks( - obj: LeanIxCondensedBlocks>, -) -> LeanIxCondensedBlocks { - let low_links = obj.get_obj(0).to_owned_ref(); - let blocks = obj.get_obj(1).to_owned_ref(); - let block_refs = obj.get_obj(2).to_owned_ref(); - - let result = LeanIxCondensedBlocks::alloc(0); - result.set_obj(0, low_links); - result.set_obj(1, blocks); - result.set_obj(2, block_refs); - result -} - -/// Round-trip a RustCompilePhases structure. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_rust_compile_phases( - obj: LeanIxCompilePhases>, -) -> LeanIxCompilePhases { - let raw_env = obj.get_obj(0).to_owned_ref(); - let condensed = obj.get_obj(1).to_owned_ref(); - let compile_env = obj.get_obj(2).to_owned_ref(); - - let result = LeanIxCompilePhases::alloc(0); - result.set_obj(0, raw_env); - result.set_obj(1, condensed); - result.set_obj(2, compile_env); - result -} - -// ============================================================================= -// BlockCompareResult and BlockCompareDetail roundtrip FFI -// ============================================================================= - -/// Round-trip a BlockCompareResult. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_block_compare_result( - obj: LeanIxBlockCompareResult>, -) -> LeanIxBlockCompareResult { - // Tags 0 (match) and 2 (notFound) have 0 fields → Lean represents as scalars - if obj.inner().is_scalar() { - return LeanIxBlockCompareResult::new(obj.inner().to_owned_ref()); - } - match obj.as_ctor().tag() { - 1 => { - // mismatch: 0 obj, 24 scalar bytes (3 × u64) - let lean_size = obj.get_num_64(0); - let rust_size = obj.get_num_64(1); - let first_diff = obj.get_num_64(2); - - let out = LeanIxBlockCompareResult::alloc(1); - out.set_num_64(0, lean_size); - out.set_num_64(1, rust_size); - out.set_num_64(2, first_diff); - out - }, - tag => unreachable!("Invalid BlockCompareResult tag: {tag}"), - } -} - -/// Round-trip a BlockCompareDetail. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_block_compare_detail( - obj: LeanIxBlockCompareDetail>, -) -> LeanIxBlockCompareDetail { - let lean_sharing_len = obj.get_num_64(0); - let rust_sharing_len = obj.get_num_64(1); - - let result_obj = - rs_roundtrip_block_compare_result(LeanIxBlockCompareResult(obj.get_obj(0))); - - let out = LeanIxBlockCompareDetail::alloc(0); - out.set_obj(0, result_obj); - out.set_num_64(0, lean_sharing_len); - out.set_num_64(1, rust_sharing_len); - out -} - -// ============================================================================= -// Full Compilation FFI -// ============================================================================= - -/// FFI function to run the complete compilation pipeline and return all data. -/// -/// Note on ungrounded constants: `compile_env_with_options` records any -/// pre-compile or per-block grounding rejections in `compile_stt.ungrounded` -/// and omits them from `compile_stt.name_to_addr` and `compile_stt.env`. -/// This legacy phase-inspection entrypoint returns the grounded subset -/// without a status wrapper; production callers that require fail-closed -/// semantics or the `ungrounded` diagnostic list must use [`rs_compile_env`] -/// or [`rs_compile_env_anon`]. -#[unsafe(no_mangle)] -pub extern "C" fn rs_compile_env_full( - env_consts_ptr: LeanList>, -) -> LeanIOResult { - { - // Phase 1: Decode Lean environment - let rust_env = crate::lean_env::decode_env(env_consts_ptr); - let env_len = rust_env.len(); - let rust_env = Arc::new(rust_env); - - // Phase 2: Build ref graph and compute SCCs - let ref_graph = build_ref_graph(&rust_env); - let condensed = compute_sccs(&ref_graph.out_refs); - - // Phase 3: Compile - let compile_stt = - match compile_env_with_options(&rust_env, CompileOptions::default()) { - Ok(stt) => stt, - Err(e) => { - let msg = - format!("rs_compile_env_full: Rust compilation failed: {:?}", e); - return LeanIOResult::error_string(&msg); - }, - }; - - // Phase 4: Build Lean structures - let mut cache = LeanBuildCache::with_capacity(env_len); - - let raw_env = LeanIxRawEnvironment::build(&mut cache, &rust_env); - let condensed_obj = LeanIxCondensedBlocks::build(&mut cache, &condensed); - - // Collect blocks - let mut blocks_data: Vec<(Name, Vec, usize)> = Vec::new(); - let mut seen_addrs: std::collections::HashSet
= - std::collections::HashSet::new(); - - for entry in compile_stt.name_to_addr.iter() { - let name = entry.key().clone(); - let addr = entry.value().clone(); - - if seen_addrs.contains(&addr) { - continue; - } - seen_addrs.insert(addr.clone()); - - if let Some(constant) = compile_stt.env.get_const(&addr) { - let mut bytes = Vec::new(); - constant.put(&mut bytes); - let sharing_len = constant.sharing.len(); - blocks_data.push((name, bytes, sharing_len)); - } - } - - // Build blocks array - let blocks_arr = LeanArray::alloc(blocks_data.len()); - for (i, (name, bytes, sharing_len)) in blocks_data.iter().enumerate() { - let name_obj = LeanIxName::build(&mut cache, name); - let ba = LeanByteArray::from_bytes(bytes); - - // Block: { name: Ix.Name, bytes: ByteArray, sharingLen: UInt64 } - let block = LeanIxBlock::alloc(0); - block.set_obj(0, name_obj); - block.set_obj(1, ba); - block.set_num_64(0, *sharing_len as u64); - - blocks_arr.set(i, block); - } - - // Build nameToAddr array - let name_to_addr_len = compile_stt.name_to_addr.len(); - let name_to_addr_arr = LeanArray::alloc(name_to_addr_len); - for (i, entry) in compile_stt.name_to_addr.iter().enumerate() { - let name = entry.key(); - let addr = entry.value(); - - let name_obj = LeanIxName::build(&mut cache, name); - let addr_ba = LeanByteArray::from_bytes(addr.as_bytes()); - - let entry_obj = LeanProd::new(name_obj, addr_ba); - name_to_addr_arr.set(i, entry_obj); - } - - // Build RawCompiledEnv - let compiled_obj = LeanProd::new(blocks_arr, name_to_addr_arr); - - // Build RustCompilationResult - let result = LeanIxCompilePhases::alloc(0); - result.set_obj(0, raw_env); - result.set_obj(1, condensed_obj); - result.set_obj(2, compiled_obj); - - LeanIOResult::ok(result) - } -} - -/// FFI: compile a Lean environment and stream the serialized Ixon.Env -/// straight to `out_path` (see `Env::put_file`) — no env-sized `Vec` or -/// Lean `ByteArray` is built. Writes `.tmp`, then renames, so -/// a crash cannot leave a truncated file. The file is the canonical -/// `Env::put` encoding (see `put_file`'s equivalence test). -/// -/// Fail-closed semantics live here, behind the FFI: when -/// `allow_partial == 0` and any requested constant landed in -/// `CompileState.ungrounded`, nothing is written — the final path is -/// never created — and the returned status carries the full ungrounded -/// list for the caller to report. With `allow_partial != 0`, the -/// grounded subset is serialized and the same status discloses what -/// was omitted (see plans/aux-recursor-alias-collision.md §8). -/// -/// Returns `Ix.CompileM.CompileEnvStatus` (layout -/// `LeanIxCompileEnvStatus`): root (64-hex canonical consts merkle -/// root — matches the serialized header, computed even when nothing is -/// written), ungrounded (`Array (String × String)` of pretty-name / -/// reason, sorted by name), nonCanonical (`Array (String × String)`, -/// always empty: the compiler rewrites no block), -/// bytes (0 when not written), named count, unique anon count. -#[unsafe(no_mangle)] -pub extern "C" fn rs_compile_env( - env_consts_ptr: LeanList>, - out_path: LeanString>, - allow_partial: u8, -) -> LeanIOResult { - compile_env_inner(env_consts_ptr, out_path, allow_partial, false) -} - -/// `rs_compile_env` with strict-anon output (`ix compile --anon`): -/// §4/§5/§6 cleared after `finalize_hints`, so the piece file carries -/// the anon layer only (§1–§3). Same status shape, `named = 0`. -/// -/// Lean signature: -/// ```lean -/// @[extern "rs_compile_env_anon"] -/// opaque rsCompileEnvBytesAnonFFI -/// : @& List (Lean.Name × Lean.ConstantInfo) → @& String → Bool -/// → IO CompileEnvStatus -/// ``` -#[unsafe(no_mangle)] -pub extern "C" fn rs_compile_env_anon( - env_consts_ptr: LeanList>, - out_path: LeanString>, - allow_partial: u8, -) -> LeanIOResult { - compile_env_inner(env_consts_ptr, out_path, allow_partial, true) -} - -fn compile_env_inner( - env_consts_ptr: LeanList>, - out_path: LeanString>, - allow_partial: u8, - strict_anon: bool, -) -> LeanIOResult { - let rust_env = crate::lean_env::decode_env_for_compile(env_consts_ptr); - let rust_env = Arc::new(rust_env); - - let compile_stt = - match compile_env_with_options(&rust_env, CompileOptions::default()) { - Ok(stt) => stt, - Err(e) => { - let msg = format!("rs_compile_env: Rust compilation failed: {:?}", e); - return LeanIOResult::error_string(&msg); - }, - }; - - // Deterministically ordered (pretty name, reason) pairs — DashMap - // iteration order is shard-dependent. - let mut ungrounded: Vec<(String, String)> = compile_stt - .ungrounded - .iter() - .map(|e| (e.key().pretty(), e.value().clone())) - .collect(); - ungrounded.sort_by(|a, b| a.0.cmp(&b.0)); - // The non-canonical set: empty, since nothing is rewritten. The field - // stays in the status until the Lean side drops it. - let non_canonical: Vec<(String, String)> = Vec::new(); - - // Canonical consts merkle root — equals the serialized header root - // when a file is written; still reported on a fail-closed abort so - // callers can record what the complete-subset content was. Computed - // once here (parallel sort + tree) and handed to the writer — the - // sort and the ~2·N-node tree used to run twice per compile. - let mut const_addrs: Vec
= - compile_stt.env.consts.iter().map(|e| e.key().clone()).collect(); - { - use rayon::prelude::*; - const_addrs.par_sort_unstable(); - } - let root = ixon::merkle::merkle_root_canonical_sorted(&const_addrs) - .unwrap_or_else(ixon::merkle::zero_address); - if strict_anon { - // Strict-anon piece (`ix compile --anon`): drop §4 names, §5 - // metadata, and §6 commitments. This runs strictly AFTER - // `finalize_hints` (inside `compile_env_with_options`), which - // derives §3 anon hints FROM `env.named` — the ordering is - // load-bearing, and the fixture suite pins §3 survival. §1 blobs - // stay: value blobs are load-bearing for constant bodies, and the - // conservative superset is never unsound (content-addressed). - // The root is unaffected either way — it covers §2 keys only. - compile_stt.env.names.clear(); - compile_stt.env.named.clear(); - compile_stt.env.comms.clear(); - } - let named_count = compile_stt.env.named.len() as u64; - let unique_anon = const_addrs.len() as u64; - - let fail_closed = allow_partial == 0 && !ungrounded.is_empty(); - let written: u64 = if fail_closed { - 0 - } else { - let path = std::path::PathBuf::from(out_path.as_str()); - let tmp = { - let mut s = path.clone().into_os_string(); - s.push(".tmp"); - std::path::PathBuf::from(s) - }; - let written = - match compile_stt.env.put_file_with_header(&tmp, &const_addrs, &root) { - Ok(n) => n, - Err(e) => { - std::fs::remove_file(&tmp).ok(); - let msg = format!("rs_compile_env: serialization failed: {e}"); - return LeanIOResult::error_string(&msg); - }, - }; - if let Err(e) = std::fs::rename(&tmp, &path) { - std::fs::remove_file(&tmp).ok(); - let msg = format!( - "rs_compile_env: rename {} -> {}: {e}", - tmp.display(), - path.display() - ); - return LeanIOResult::error_string(&msg); - } - written - }; - - let ungrounded_arr = LeanArray::alloc(ungrounded.len()); - for (i, (name, reason)) in ungrounded.iter().enumerate() { - ungrounded_arr - .set(i, LeanProd::new(LeanString::new(name), LeanString::new(reason))); - } - let nc_arr = LeanArray::alloc(non_canonical.len()); - for (i, (name, cause)) in non_canonical.iter().enumerate() { - nc_arr.set(i, LeanProd::new(LeanString::new(name), LeanString::new(cause))); - } - let status = LeanIxCompileEnvStatus::alloc(0); - status.set_obj(0, LeanString::new(&root.hex())); - status.set_obj(1, ungrounded_arr); - status.set_obj(2, nc_arr); - status.set_num_64(0, written); - status.set_num_64(1, named_count); - status.set_num_64(2, unique_anon); - - // Skip destructors: the compile CLI is a one-shot process, the OS - // reclaims at exit, and dropping the maps costs tens of seconds at - // Mathlib scale. - std::mem::forget(compile_stt); - std::mem::forget(rust_env); - LeanIOResult::ok(status) -} - -/// Round-trip a RawEnv: decode from Lean, re-encode via builder. -/// This performs a full decode/build cycle to verify FFI correctness. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_raw_env( - raw_env_obj: LeanIxonRawEnv>, -) -> LeanIxonRawEnv { - let env = raw_env_obj.decode(); - LeanIxonRawEnv::build(&env) -} - -/// FFI function to run all compilation phases and return combined results. -#[unsafe(no_mangle)] -pub extern "C" fn rs_compile_phases( - env_consts_ptr: LeanList>, -) -> LeanIOResult { - { - let rust_env = crate::lean_env::decode_env(env_consts_ptr); - let env_len = rust_env.len(); - let rust_env = Arc::new(rust_env); - - let mut cache = LeanBuildCache::with_capacity(env_len); - let raw_env = LeanIxRawEnvironment::build(&mut cache, &rust_env); - - let ref_graph = build_ref_graph(&rust_env); - - let condensed = compute_sccs(&ref_graph.out_refs); - - let condensed_obj = LeanIxCondensedBlocks::build(&mut cache, &condensed); - - let compile_stt = - match compile_env_with_options(&rust_env, CompileOptions::default()) { - Ok(stt) => stt, - Err(e) => { - let msg = format!("rs_compile_phases: compilation failed: {:?}", e); - return LeanIOResult::error_string(&msg); - }, - }; - - // Build Lean objects from compile results - let consts: Vec<_> = compile_stt - .env - .consts - .iter() - .filter_map(|e| { - let c = e.value().get().ok()?; - Some((e.key().clone(), c)) - }) - .collect(); - let consts_arr = LeanArray::alloc(consts.len()); - for (i, (addr, constant)) in consts.iter().enumerate() { - consts_arr.set(i, build_raw_const(addr, constant.as_ref())); - } - - let named: Vec<_> = compile_stt - .env - .named - .iter() - .map(|e| (e.key().clone(), e.value().clone())) - .collect(); - let named_arr = LeanArray::alloc(named.len()); - for (i, (name, n)) in named.iter().enumerate() { - named_arr.set( - i, - build_raw_named( - &mut cache, - name, - &n.addr, - &n.meta(), - &n.original().map(|(a, m)| (a, (*m).clone())), - n.hints(), - ), - ); - } - - let blobs: Vec<_> = compile_stt - .env - .blobs - .iter() - .map(|e| (e.key().clone(), e.value().clone())) - .collect(); - let blobs_arr = LeanArray::alloc(blobs.len()); - for (i, (addr, bytes)) in blobs.iter().enumerate() { - blobs_arr.set(i, build_raw_blob(addr, bytes)); - } - - let comms: Vec<_> = compile_stt - .env - .comms - .iter() - .map(|e| (e.key().clone(), e.value().clone())) - .collect(); - let comms_arr = LeanArray::alloc(comms.len()); - for (i, (addr, comm)) in comms.iter().enumerate() { - comms_arr.set(i, build_raw_comm(addr, comm)); - } - - // Build names array (Address → Ix.Name) - let names: Vec<_> = compile_stt - .env - .names - .iter() - .map(|e| (e.key().clone(), e.value().clone())) - .collect(); - let names_arr = LeanArray::alloc(names.len()); - for (i, (addr, name)) in names.iter().enumerate() { - names_arr.set(i, LeanIxonRawNameEntry::build(&mut cache, addr, name)); - } - - let raw_ixon_env = LeanIxonRawEnv::alloc(0); - raw_ixon_env.set_obj(0, consts_arr); - raw_ixon_env.set_obj(1, named_arr); - raw_ixon_env.set_obj(2, blobs_arr); - raw_ixon_env.set_obj(3, comms_arr); - raw_ixon_env.set_obj(4, names_arr); - crate::lean_ixon::env::set_raw_env_bundle_fields( - &raw_ixon_env, - &compile_stt.env, - ); - - let result = LeanIxCompilePhases::alloc(0); - result.set_obj(0, raw_env); - result.set_obj(1, condensed_obj); - result.set_obj(2, raw_ixon_env); - - LeanIOResult::ok(result) - } -} - -/// FFI function to compile a Lean environment to a RawEnv. -#[unsafe(no_mangle)] -pub extern "C" fn rs_compile_env_to_ixon( - env_consts_ptr: LeanList>, -) -> LeanIOResult { - compile_env_to_ixon_profile(env_consts_ptr, None) -} - -/// Explicit canonical policy; the native compiler validates it before emission. -#[unsafe(no_mangle)] -pub extern "C" fn rs_compile_env_to_ixon_profile( - env_consts_ptr: LeanList>, - bytes: LeanByteArray>, -) -> LeanIOResult { - let profile = - match ixon::resource::addressed::Profile::from_bytes(bytes.as_bytes()) { - Ok(profile) => profile, - Err(error) => return LeanIOResult::error_string(&error), - }; - compile_env_to_ixon_profile(env_consts_ptr, Some(&profile)) -} - -fn compile_env_to_ixon_profile( - env_consts_ptr: LeanList>, - profile: Option<&ixon::resource::addressed::Profile>, -) -> LeanIOResult { - { - let rust_env = crate::lean_env::decode_env(env_consts_ptr); - let rust_env = Arc::new(rust_env); - - let compile_stt = match ix_compile::compile::compile_env_with_profile( - &rust_env, - CompileOptions::default(), - profile, - ) { - Ok(stt) => stt, - Err(e) => { - let msg = - format!("rs_compile_env_to_ixon: compilation failed: {:?}", e); - return LeanIOResult::error_string(&msg); - }, - }; - - let mut cache = LeanBuildCache::with_capacity(rust_env.len()); - - let consts: Vec<_> = compile_stt - .env - .consts - .iter() - .filter_map(|e| { - let c = e.value().get().ok()?; - Some((e.key().clone(), c)) - }) - .collect(); - let consts_arr = LeanArray::alloc(consts.len()); - for (i, (addr, constant)) in consts.iter().enumerate() { - consts_arr.set(i, build_raw_const(addr, constant.as_ref())); - } - - let named: Vec<_> = compile_stt - .env - .named - .iter() - .map(|e| (e.key().clone(), e.value().clone())) - .collect(); - let named_arr = LeanArray::alloc(named.len()); - for (i, (name, n)) in named.iter().enumerate() { - named_arr.set( - i, - build_raw_named( - &mut cache, - name, - &n.addr, - &n.meta(), - &n.original().map(|(a, m)| (a, (*m).clone())), - n.hints(), - ), - ); - } - - let blobs: Vec<_> = compile_stt - .env - .blobs - .iter() - .map(|e| (e.key().clone(), e.value().clone())) - .collect(); - let blobs_arr = LeanArray::alloc(blobs.len()); - for (i, (addr, bytes)) in blobs.iter().enumerate() { - blobs_arr.set(i, build_raw_blob(addr, bytes)); - } - - let comms: Vec<_> = compile_stt - .env - .comms - .iter() - .map(|e| (e.key().clone(), e.value().clone())) - .collect(); - let comms_arr = LeanArray::alloc(comms.len()); - for (i, (addr, comm)) in comms.iter().enumerate() { - comms_arr.set(i, build_raw_comm(addr, comm)); - } - - // Build names array (Address → Ix.Name) - let names: Vec<_> = compile_stt - .env - .names - .iter() - .map(|e| (e.key().clone(), e.value().clone())) - .collect(); - let names_arr = LeanArray::alloc(names.len()); - for (i, (addr, name)) in names.iter().enumerate() { - names_arr.set(i, LeanIxonRawNameEntry::build(&mut cache, addr, name)); - } - - let result = LeanIxonRawEnv::alloc(0); - result.set_obj(0, consts_arr); - result.set_obj(1, named_arr); - result.set_obj(2, blobs_arr); - result.set_obj(3, comms_arr); - result.set_obj(4, names_arr); - crate::lean_ixon::env::set_raw_env_bundle_fields(&result, &compile_stt.env); - LeanIOResult::ok(result) - } -} - -/// FFI function to canonicalize environment to Ix.RawEnvironment. -#[unsafe(no_mangle)] -pub extern "C" fn rs_canonicalize_env_to_ix( - env_consts_ptr: LeanList>, -) -> LeanIOResult { - { - let rust_env = crate::lean_env::decode_env(env_consts_ptr); - let mut cache = LeanBuildCache::with_capacity(rust_env.len()); - let raw_env = LeanIxRawEnvironment::build(&mut cache, &rust_env); - LeanIOResult::ok(raw_env) - } -} - -/// FFI function to compute the LEON content hash of every constant in a -/// Lean environment. Returns an `Array (Ix.Name × Ix.Address)` where each -/// `Address` is the 32-byte Blake3 digest produced by -/// `ConstantInfo::get_hash()` in `crates/common/src/env.rs`. -/// -/// The LEON hash is the Rust kernel's "original" addressing scheme: it's -/// derived from the serialized `ConstantInfo` (name + level params + type -/// expression + variant-specific fields: ctors, rules, `all`, value, hints, -/// etc.) so two constants with the same name but different content get -/// distinct addresses. This is the address scheme `lean_ingress` uses (or -/// will use) when populating `orig_kenv`, and the table Lean callers need -/// to dump when regenerating `PrimOrigAddrs` in the Rust kernel. -/// -/// No compilation happens here — we only decode the Lean env and hash each -/// `ConstantInfo` in place. That makes this cheap relative to -/// `rs_compile_env_to_ixon` and safe to run on the full environment. -#[unsafe(no_mangle)] -pub extern "C" fn rs_leon_hashes( - env_consts_ptr: LeanList>, -) -> LeanIOResult { - let rust_env = crate::lean_env::decode_env(env_consts_ptr); - let mut cache = LeanBuildCache::with_capacity(rust_env.len()); - - let arr = LeanArray::alloc(rust_env.len()); - for (i, (name, ci)) in rust_env.iter().enumerate() { - let name_obj = LeanIxName::build(&mut cache, name); - let addr_obj = LeanIxAddress::build_from_hash(&ci.get_hash()); - arr.set(i, LeanProd::new(name_obj, addr_obj)); - } - LeanIOResult::ok(arr) -} - -// ============================================================================= -// RustCompiledEnv - Holds Rust compilation results for comparison -// ============================================================================= - -#[cfg(feature = "test-ffi")] -/// Rust-compiled environment holding blocks indexed by low-link name. -/// Each block is stored as serialized bytes for comparison with Lean output. -pub struct RustCompiledEnv { - /// Map from low-link name to (serialized constant bytes, sharing vector length) - blocks: HashMap, usize)>, - /// The full compile state for accessing pre-sharing expressions - compile_state: CompileState, -} - -// ============================================================================= -// Block-by-block comparison FFI -// ============================================================================= - -/// FFI: Simple test to verify FFI round-trip works. -/// Takes a Lean.Name and returns a magic number to verify the call succeeded. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -extern "C" fn rs_test_ffi_roundtrip(name_ptr: LeanBorrowed<'_>) -> u64 { - let global_cache = GlobalCache::default(); - let name = decode_name(name_ptr, &global_cache); - - // Return a magic number plus the hash of the name to verify it worked - let hash = name.get_hash(); - let hash_bytes = hash.as_bytes(); - let hash_prefix = - u64::from_le_bytes(hash_bytes[0..8].try_into().unwrap_or([0u8; 8])); - - // Magic number 0xDEADBEEF plus hash prefix - 0xDEAD_BEEF_0000_0000 | (hash_prefix & 0x0000_0000_FFFF_FFFF) -} - -/// FFI: Compile entire environment with Rust, returning a handle to RustCompiledEnv. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -extern "C" fn rs_compile_env_rust_first( - env_consts_ptr: LeanList>, -) -> *mut RustCompiledEnv { - // Decode Lean environment - let lean_env = crate::lean_env::decode_env(env_consts_ptr); - let lean_env = Arc::new(lean_env); - - // Compile with Rust - let rust_stt = - match compile_env_with_options(&lean_env, CompileOptions::default()) { - Ok(stt) => stt, - Err(_e) => { - return std::ptr::null_mut(); - }, - }; - - // Build block map: lowlink name -> (serialized bytes, sharing len) - let mut blocks: HashMap, usize)> = HashMap::new(); - - // Iterate over all names and their addresses - for entry in rust_stt.name_to_addr.iter() { - let name = entry.key().clone(); - let addr = entry.value().clone(); - - // Skip if we already have this block (multiple names map to same block) - if blocks.contains_key(&name) { - continue; - } - - // Get the compiled constant - if let Some(constant) = rust_stt.env.get_const(&addr) { - let mut bytes = Vec::new(); - constant.put(&mut bytes); - let sharing_len = constant.sharing.len(); - blocks.insert(name, (bytes, sharing_len)); - } - } - - // Return boxed RustCompiledEnv with full compile state for pre-sharing access - Box::into_raw(Box::new(RustCompiledEnv { blocks, compile_state: rust_stt })) -} - -/// FFI: Compare a single block and return packed result. -/// Returns a packed u64: high 32 bits = matches (1) or error code (0 = mismatch, 2 = not found) -/// low 32 bits = first diff offset (if mismatch) -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -extern "C" fn rs_compare_block( - rust_env: *const RustCompiledEnv, - lowlink_name: LeanOwned, - lean_bytes: LeanByteArray, -) -> u64 { - if rust_env.is_null() { - return 2u64 << 32; // not found - } - let global_cache = GlobalCache::default(); - let name = decode_name(lowlink_name.borrow(), &global_cache); - - let rust_env = unsafe { &*rust_env }; - let lean_data = lean_bytes.as_bytes(); - - // Look up Rust's compiled block - let rust_bytes = match rust_env.blocks.get(&name) { - Some((bytes, _)) => bytes, - None => { - // Block not found in Rust compilation: code 2 - return 2u64 << 32; - }, - }; - - // Compare bytes - if rust_bytes == lean_data { - // Match: code 1 - return 1u64 << 32; - } - - // Mismatch: find first differing byte - rust_bytes.iter().zip(lean_data.iter()).position(|(a, b)| a != b).map_or_else( - || { - // One is a prefix of the other - rust_bytes.len().min(lean_data.len()) as u64 - }, - |i| i as u64, - ) -} - -/// FFI: Free a RustCompiledEnv. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -extern "C" fn rs_free_rust_env(rust_env: *mut RustCompiledEnv) { - if !rust_env.is_null() { - unsafe { - drop(Box::from_raw(rust_env)); - } - } -} - -/// FFI: Get the number of blocks in a RustCompiledEnv. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -extern "C" fn rs_get_rust_env_block_count( - rust_env: *const RustCompiledEnv, -) -> u64 { - if rust_env.is_null() { - return 0; - } - let rust_env = unsafe { &*rust_env }; - rust_env.blocks.len() as u64 -} - -/// FFI: Get Rust's compiled bytes length for a block. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -extern "C" fn rs_get_block_bytes_len( - rust_env: *const RustCompiledEnv, - lowlink_name: LeanOwned, -) -> u64 { - if rust_env.is_null() { - return 0; - } - let global_cache = GlobalCache::default(); - let name = decode_name(lowlink_name.borrow(), &global_cache); - - let rust_env = unsafe { &*rust_env }; - - match rust_env.blocks.get(&name) { - Some((bytes, _)) => bytes.len() as u64, - None => 0, - } -} - -/// FFI: Copy Rust's compiled bytes into a pre-allocated Lean ByteArray. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -extern "C" fn rs_copy_block_bytes( - rust_env: *const RustCompiledEnv, - lowlink_name: LeanOwned, - dest: LeanByteArray, -) { - if rust_env.is_null() { - return; - } - let global_cache = GlobalCache::default(); - let name = decode_name(lowlink_name.borrow(), &global_cache); - - let rust_env = unsafe { &*rust_env }; - - let bytes = match rust_env.blocks.get(&name) { - Some((bytes, _)) => bytes, - None => return, - }; - - // Copy into the Lean ByteArray - unsafe { dest.set_data(bytes) }; -} - -/// FFI: Get Rust's sharing vector length for a block. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -extern "C" fn rs_get_block_sharing_len( - rust_env: *const RustCompiledEnv, - lowlink_name: LeanOwned, -) -> u64 { - if rust_env.is_null() { - return 0; - } - let global_cache = GlobalCache::default(); - let name = decode_name(lowlink_name.borrow(), &global_cache); - - let rust_env = unsafe { &*rust_env }; - - match rust_env.blocks.get(&name) { - Some((_, sharing_len)) => *sharing_len as u64, - None => 0, - } -} - -// ============================================================================= -// Pre-sharing expression extraction FFI -// ============================================================================= - -#[cfg(feature = "test-ffi")] -/// Expand shares while preserving all Ixon contracts and static groups. -fn unshare_expr( - expr: &Arc, - sharing: &[Arc], -) -> Arc { - enum Frame<'a> { - Visit(&'a Arc), - Build(&'a IxonExpr), - } - let mut stack = vec![Frame::Visit(expr)]; - let mut results: Vec> = vec![]; - while let Some(frame) = stack.pop() { - match frame { - Frame::Visit(e) => { - if let IxonExpr::Share(idx) = e.as_ref() - && let Ok(idx) = usize::try_from(*idx) - && let Some(shared) = sharing.get(idx) - { - stack.push(Frame::Visit(shared)); - continue; - } - let children = e.children(); - if children.is_empty() { - results.push(e.clone()); - } else { - stack.push(Frame::Build(e)); - stack.extend(children.into_iter().rev().map(Frame::Visit)); - } - }, - Frame::Build(e) => { - let children = results.split_off(results.len() - e.children().len()); - results.push(Arc::new( - e.with_children(&children).expect("unshare child count"), - )); - }, - } - } - results.pop().expect("unshare result") -} - -/// FFI: Get the pre-sharing root expressions for a constant. -/// Returns the number of root expressions, and writes serialized expressions to the output buffer. -/// Each expression is serialized without sharing (Share nodes are expanded). -/// -/// Output format: [n_exprs:u64, len1:u64, expr1_bytes..., len2:u64, expr2_bytes..., ...] -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -extern "C" fn rs_get_pre_sharing_exprs( - rust_env: *const RustCompiledEnv, - lowlink_name: LeanOwned, - out_buf: LeanByteArray, -) -> u64 { - if rust_env.is_null() { - return 0; - } - let global_cache = GlobalCache::default(); - let name = decode_name(lowlink_name.borrow(), &global_cache); - - let rust_env = unsafe { &*rust_env }; - - // Look up the address for this name - let addr = match rust_env.compile_state.name_to_addr.get(&name) { - Some(a) => a.clone(), - None => { - return 0; - }, - }; - - // Get the constant (note: contains post-sharing expressions) - let constant = match rust_env.compile_state.env.get_const(&addr) { - Some(c) => c, - None => { - return 0; - }, - }; - - // Extract root expressions from the constant info - let root_exprs: Vec> = match &constant.info { - ConstantInfo::Defn(def) => vec![def.typ.clone(), def.value.clone()], - ConstantInfo::Axio(ax) => vec![ax.typ.clone()], - ConstantInfo::Quot(q) => vec![q.typ.clone()], - ConstantInfo::Recr(rec) => { - let mut exprs = vec![rec.typ.clone()]; - for rule in &rec.rules { - exprs.push(rule.rhs.clone()); - } - exprs - }, - // Projections don't contain expressions directly - ConstantInfo::CPrj(_) - | ConstantInfo::RPrj(_) - | ConstantInfo::IPrj(_) - | ConstantInfo::DPrj(_) => { - vec![] - }, - ConstantInfo::Muts(muts) => { - let mut exprs = Vec::new(); - for mc in muts { - match mc { - ixon::constant::MutConst::Defn(def) => { - exprs.push(def.typ.clone()); - exprs.push(def.value.clone()); - }, - ixon::constant::MutConst::Indc(ind) => { - exprs.push(ind.typ.clone()); - for ctor in &ind.ctors { - exprs.push(ctor.typ.clone()); - } - }, - ixon::constant::MutConst::Recr(rec) => { - exprs.push(rec.typ.clone()); - for rule in &rec.rules { - exprs.push(rule.rhs.clone()); - } - }, - } - } - exprs - }, - }; - - // Unshare and serialize each root expression - let mut output_bytes: Vec = Vec::new(); - let n_exprs = root_exprs.len() as u64; - - // Write number of expressions - output_bytes.extend_from_slice(&n_exprs.to_le_bytes()); - - for expr in &root_exprs { - // Unshare the expression - let unshared = unshare_expr(expr, &constant.sharing); - - // Serialize to bytes - let mut expr_bytes: Vec = Vec::new(); - put_expr(&unshared, &mut expr_bytes); - - // Write length and bytes - output_bytes.extend_from_slice(&(expr_bytes.len() as u64).to_le_bytes()); - output_bytes.extend(expr_bytes); - } - - // Write to output buffer - unsafe { out_buf.set_data(&output_bytes) }; - - n_exprs -} - -/// FFI: Get the buffer length needed for pre-sharing expressions. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -extern "C" fn rs_get_pre_sharing_exprs_len( - rust_env: *const RustCompiledEnv, - lowlink_name: LeanOwned, -) -> u64 { - if rust_env.is_null() { - return 0; - } - let global_cache = GlobalCache::default(); - let name = decode_name(lowlink_name.borrow(), &global_cache); - - let rust_env = unsafe { &*rust_env }; - - // Look up the address for this name - let addr = match rust_env.compile_state.name_to_addr.get(&name) { - Some(a) => a.clone(), - None => return 0, - }; - - // Get the constant - let constant = match rust_env.compile_state.env.get_const(&addr) { - Some(c) => c, - None => return 0, - }; - - // Count root expressions - let n_exprs = match &constant.info { - ConstantInfo::Defn(_) => 2, - ConstantInfo::Axio(_) | ConstantInfo::Quot(_) => 1, - ConstantInfo::Recr(rec) => 1 + rec.rules.len(), - // Projections don't contain expressions directly - ConstantInfo::CPrj(_) - | ConstantInfo::RPrj(_) - | ConstantInfo::IPrj(_) - | ConstantInfo::DPrj(_) => 0, - ConstantInfo::Muts(muts) => { - let mut count = 0; - for mc in muts { - match mc { - ixon::constant::MutConst::Defn(_) => count += 2, - ixon::constant::MutConst::Indc(ind) => count += 1 + ind.ctors.len(), - ixon::constant::MutConst::Recr(rec) => count += 1 + rec.rules.len(), - } - } - count - }, - }; - - // Estimate: 8 bytes per header + some for expression data - // This is an upper bound estimate - (8 + n_exprs * 1024) as u64 -} - -/// FFI: Look up a constant's compiled address from RustCompiledEnv. -/// Copies the 32-byte blake3 hash into the provided ByteArray. -/// Returns 1 on success, 0 if name not found. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -extern "C" fn rs_lookup_const_addr( - rust_env: *const RustCompiledEnv, - name_ptr: LeanOwned, - out_addr: LeanByteArray, -) -> u64 { - if rust_env.is_null() { - return 0; - } - let global_cache = GlobalCache::default(); - let name = decode_name(name_ptr.borrow(), &global_cache); - - let rust_env = unsafe { &*rust_env }; - - // Look up the address for this name - match rust_env.compile_state.name_to_addr.get(&name) { - Some(addr_ref) => { - // Copy the 32-byte address into the output ByteArray - unsafe { out_addr.set_data(addr_ref.as_bytes()) }; - 1 - }, - None => 0, - } -} - -/// FFI: Get the total number of compiled constants in RustCompiledEnv. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -extern "C" fn rs_get_compiled_const_count( - rust_env: *const RustCompiledEnv, -) -> u64 { - if rust_env.is_null() { - return 0; - } - let rust_env = unsafe { &*rust_env }; - rust_env.compile_state.name_to_addr.len() as u64 -} - -// ============================================================================= -// Error type FFI builders -// ============================================================================= - -use ixon::error::{CompileError, DecompileError, SerializeError}; - -impl LeanIxSerializeError { - /// Build a Lean Ixon.SerializeError from a Rust SerializeError. - /// - /// Tags 0–6: - /// 0: unexpectedEof (expected : String) → 1 obj - /// 1: invalidTag (tag : UInt8) (context : String) → 1 obj + 1 scalar (UInt8) - /// 2: invalidFlag (flag : UInt8) (context : String) → 1 obj + 1 scalar (UInt8) - /// 3: invalidVariant (variant : UInt64) (context : String) → 1 obj + 8 scalar (UInt64) - /// 4: invalidBool (value : UInt8) → 0 obj + 1 scalar (UInt8) - /// 5: addressError → 0 obj + 0 scalar - /// 6: invalidShareIndex (idx : UInt64) (max : Nat) → 1 obj (Nat) + 8 scalar (UInt64) - pub fn build(se: &SerializeError) -> Self { - match se { - SerializeError::UnexpectedEof { expected } => { - let ctor = LeanIxSerializeError::alloc(0); - ctor.set_obj(0, build_lean_string(expected)); - ctor - }, - SerializeError::InvalidTag { tag, context } => { - let ctor = LeanIxSerializeError::alloc(1); - ctor.set_obj(0, build_lean_string(context)); - ctor.set_num_8(0, *tag); - ctor - }, - SerializeError::InvalidFlag { flag, context } => { - let ctor = LeanIxSerializeError::alloc(2); - ctor.set_obj(0, build_lean_string(context)); - ctor.set_num_8(0, *flag); - ctor - }, - SerializeError::InvalidVariant { variant, context } => { - let ctor = LeanIxSerializeError::alloc(3); - ctor.set_obj(0, build_lean_string(context)); - ctor.set_num_64(0, *variant); - ctor - }, - SerializeError::InvalidBool { value } => { - let ctor = LeanIxSerializeError::alloc(4); - ctor.set_num_8(0, *value); - ctor - }, - SerializeError::AddressError => Self::new(LeanOwned::box_usize(5)), - SerializeError::InvalidShareIndex { idx, max } => { - let ctor = LeanIxSerializeError::alloc(6); - ctor.set_obj(0, build_lean_nat_usize(*max)); - ctor.set_num_64(0, *idx); - ctor - }, - } - } -} - -impl LeanIxSerializeError { - /// Decode a Lean Ixon.SerializeError to a Rust SerializeError. - pub fn decode(&self) -> SerializeError { - // Tag 5 (addressError) has 0 fields → Lean represents as scalar - if self.inner().is_scalar() { - let tag = self.inner().unbox_usize(); - assert_eq!(tag, 5, "Invalid scalar SerializeError tag: {}", tag); - return SerializeError::AddressError; - } - match self.as_ctor().tag() { - 0 => { - let expected = self.get_obj(0).as_string().to_string(); - SerializeError::UnexpectedEof { expected } - }, - 1 => { - let context = self.get_obj(0).as_string().to_string(); - let tag_val = self.get_num_8(0); - SerializeError::InvalidTag { tag: tag_val, context } - }, - 2 => { - let context = self.get_obj(0).as_string().to_string(); - let flag = self.get_num_8(0); - SerializeError::InvalidFlag { flag, context } - }, - 3 => { - let context = self.get_obj(0).as_string().to_string(); - let variant = self.get_num_64(0); - SerializeError::InvalidVariant { variant, context } - }, - 4 => { - let value = self.get_num_8(0); - SerializeError::InvalidBool { value } - }, - 5 => SerializeError::AddressError, - 6 => { - let max = LeanNat::to_nat(&self.get_obj(0)) - .to_u64() - .and_then(|x| usize::try_from(x).ok()) - .unwrap_or(0); - let idx = self.get_num_64(0); - SerializeError::InvalidShareIndex { idx, max } - }, - tag => unreachable!("Invalid SerializeError tag: {tag}"), - } - } -} - -impl LeanIxDecompileError { - /// Build a Lean DecompileError from a Rust DecompileError. - pub fn build(err: &DecompileError) -> Self { - match err { - DecompileError::InvalidRefIndex { idx, refs_len, constant } => { - let ctor = LeanIxDecompileError::alloc(0); - ctor.set_obj(0, build_lean_nat_usize(*refs_len)); - ctor.set_obj(1, build_lean_string(constant)); - ctor.set_num_64(0, *idx); - ctor - }, - DecompileError::InvalidUnivIndex { idx, univs_len, constant } => { - let ctor = LeanIxDecompileError::alloc(1); - ctor.set_obj(0, build_lean_nat_usize(*univs_len)); - ctor.set_obj(1, build_lean_string(constant)); - ctor.set_num_64(0, *idx); - ctor - }, - DecompileError::InvalidShareIndex { idx, max, constant } => { - let ctor = LeanIxDecompileError::alloc(2); - ctor.set_obj(0, build_lean_nat_usize(*max)); - ctor.set_obj(1, build_lean_string(constant)); - ctor.set_num_64(0, *idx); - ctor - }, - DecompileError::InvalidRecIndex { idx, ctx_size, constant } => { - let ctor = LeanIxDecompileError::alloc(3); - ctor.set_obj(0, build_lean_nat_usize(*ctx_size)); - ctor.set_obj(1, build_lean_string(constant)); - ctor.set_num_64(0, *idx); - ctor - }, - DecompileError::InvalidUnivVarIndex { idx, max, constant } => { - let ctor = LeanIxDecompileError::alloc(4); - ctor.set_obj(0, build_lean_nat_usize(*max)); - ctor.set_obj(1, build_lean_string(constant)); - ctor.set_num_64(0, *idx); - ctor - }, - DecompileError::MissingAddress(addr) => { - let ctor = LeanIxDecompileError::alloc(5); - ctor.set_obj(0, LeanIxAddress::build(addr)); - ctor - }, - DecompileError::MissingMetadata(addr) => { - let ctor = LeanIxDecompileError::alloc(6); - ctor.set_obj(0, LeanIxAddress::build(addr)); - ctor - }, - DecompileError::BlobNotFound(addr) => { - let ctor = LeanIxDecompileError::alloc(7); - ctor.set_obj(0, LeanIxAddress::build(addr)); - ctor - }, - DecompileError::BadBlobFormat { addr, expected } => { - let ctor = LeanIxDecompileError::alloc(8); - ctor.set_obj(0, LeanIxAddress::build(addr)); - ctor.set_obj(1, build_lean_string(expected)); - ctor - }, - DecompileError::BadConstantFormat { msg } => { - let ctor = LeanIxDecompileError::alloc(9); - ctor.set_obj(0, build_lean_string(msg)); - ctor - }, - DecompileError::Serialize(se) => { - let ctor = LeanIxDecompileError::alloc(10); - ctor.set_obj(0, LeanIxSerializeError::build(se)); - ctor - }, - DecompileError::InvalidMetaShareIndex { - idx, - entry, - primary_len, - meta_len, - constant, - } => { - let ctor = LeanIxDecompileError::alloc(11); - ctor.set_obj(0, build_lean_nat_usize(*primary_len)); - ctor.set_obj(1, build_lean_nat_usize(*meta_len)); - ctor.set_obj(2, build_lean_string(constant)); - ctor.set_num_64(0, *idx); - ctor.set_num_64(1, *entry); - ctor - }, - } - } -} - -impl LeanIxDecompileError { - /// Decode a Lean DecompileError to a Rust DecompileError. - pub fn decode(&self) -> DecompileError { - match self.as_ctor().tag() { - 0 => { - let refs_len = LeanNat::to_nat(&self.get_obj(0)) - .to_u64() - .and_then(|x| usize::try_from(x).ok()) - .unwrap_or(0); - let constant = self.get_obj(1).as_string().to_string(); - let idx = self.get_num_64(0); - DecompileError::InvalidRefIndex { idx, refs_len, constant } - }, - 1 => { - let univs_len = LeanNat::to_nat(&self.get_obj(0)) - .to_u64() - .and_then(|x| usize::try_from(x).ok()) - .unwrap_or(0); - let constant = self.get_obj(1).as_string().to_string(); - let idx = self.get_num_64(0); - DecompileError::InvalidUnivIndex { idx, univs_len, constant } - }, - 2 => { - let max = LeanNat::to_nat(&self.get_obj(0)) - .to_u64() - .and_then(|x| usize::try_from(x).ok()) - .unwrap_or(0); - let constant = self.get_obj(1).as_string().to_string(); - let idx = self.get_num_64(0); - DecompileError::InvalidShareIndex { idx, max, constant } - }, - 3 => { - let ctx_size = LeanNat::to_nat(&self.get_obj(0)) - .to_u64() - .and_then(|x| usize::try_from(x).ok()) - .unwrap_or(0); - let constant = self.get_obj(1).as_string().to_string(); - let idx = self.get_num_64(0); - DecompileError::InvalidRecIndex { idx, ctx_size, constant } - }, - 4 => { - let max = LeanNat::to_nat(&self.get_obj(0)) - .to_u64() - .and_then(|x| usize::try_from(x).ok()) - .unwrap_or(0); - let constant = self.get_obj(1).as_string().to_string(); - let idx = self.get_num_64(0); - DecompileError::InvalidUnivVarIndex { idx, max, constant } - }, - 5 => DecompileError::MissingAddress( - LeanIxAddress::from_borrowed(self.get_obj(0).as_byte_array()).decode(), - ), - 6 => DecompileError::MissingMetadata( - LeanIxAddress::from_borrowed(self.get_obj(0).as_byte_array()).decode(), - ), - 7 => DecompileError::BlobNotFound( - LeanIxAddress::from_borrowed(self.get_obj(0).as_byte_array()).decode(), - ), - 8 => { - let addr = - LeanIxAddress::from_borrowed(self.get_obj(0).as_byte_array()) - .decode(); - let expected = self.get_obj(1).as_string().to_string(); - DecompileError::BadBlobFormat { addr, expected } - }, - 9 => { - let msg = self.get_obj(0).as_string().to_string(); - DecompileError::BadConstantFormat { msg } - }, - 10 => DecompileError::Serialize( - LeanIxSerializeError(self.get_obj(0)).decode(), - ), - 11 => { - let primary_len = LeanNat::to_nat(&self.get_obj(0)) - .to_u64() - .and_then(|x| usize::try_from(x).ok()) - .unwrap_or(0); - let meta_len = LeanNat::to_nat(&self.get_obj(1)) - .to_u64() - .and_then(|x| usize::try_from(x).ok()) - .unwrap_or(0); - let constant = self.get_obj(2).as_string().to_string(); - let idx = self.get_num_64(0); - let entry = self.get_num_64(1); - DecompileError::InvalidMetaShareIndex { - idx, - entry, - primary_len, - meta_len, - constant, - } - }, - tag => unreachable!("Invalid DecompileError tag: {tag}"), - } - } -} - -impl LeanIxCompileError { - /// Build a Lean CompileError from a Rust CompileError. - /// - /// Tags 0–7: - /// 0: missingConstant (name : String) → 1 obj - /// 1: missingAddress (addr : Address) → 1 obj - /// 2: invalidMutualBlock (reason : String) → 1 obj - /// 3: unsupportedExpr (desc : String) → 1 obj - /// 4: unknownUnivParam (curr param : String) → 2 obj - /// 5: serializeError (err : SerializeError) → 1 obj - /// 6: resourceLimit (reason : String) → 1 obj - /// 7: sharingConstruction (reason : String) → 1 obj - pub fn build(err: &CompileError) -> Self { - match err { - CompileError::MissingConstant { name, .. } => { - let ctor = LeanIxCompileError::alloc(0); - ctor.set_obj(0, build_lean_string(name)); - ctor - }, - CompileError::MissingAddress(addr) => { - let ctor = LeanIxCompileError::alloc(1); - ctor.set_obj(0, LeanIxAddress::build(addr)); - ctor - }, - CompileError::InvalidMutualBlock { reason } => { - let ctor = LeanIxCompileError::alloc(2); - ctor.set_obj(0, build_lean_string(reason)); - ctor - }, - CompileError::UnsupportedExpr { desc } => { - let ctor = LeanIxCompileError::alloc(3); - ctor.set_obj(0, build_lean_string(desc)); - ctor - }, - CompileError::UnknownUnivParam { curr, param } => { - let ctor = LeanIxCompileError::alloc(4); - ctor.set_obj(0, build_lean_string(curr)); - ctor.set_obj(1, build_lean_string(param)); - ctor - }, - CompileError::Serialize(se) => { - let ctor = LeanIxCompileError::alloc(5); - ctor.set_obj(0, LeanIxSerializeError::build(se)); - ctor - }, - CompileError::ResourceLimit { reason } => { - let ctor = LeanIxCompileError::alloc(6); - ctor.set_obj(0, build_lean_string(reason)); - ctor - }, - CompileError::SharingConstruction { reason } => { - let ctor = LeanIxCompileError::alloc(7); - ctor.set_obj(0, build_lean_string(reason)); - ctor - }, - } - } -} - -impl LeanIxCompileError { - /// Decode a Lean CompileError to a Rust CompileError. - pub fn decode(&self) -> CompileError { - match self.as_ctor().tag() { - 0 => { - let name = self.get_obj(0).as_string().to_string(); - CompileError::MissingConstant { - name, - caller: "ffi:decode_compile_error".into(), - } - }, - 1 => CompileError::MissingAddress( - LeanIxAddress::from_borrowed(self.get_obj(0).as_byte_array()).decode(), - ), - 2 => { - let reason = self.get_obj(0).as_string().to_string(); - CompileError::InvalidMutualBlock { reason } - }, - 3 => { - let desc = self.get_obj(0).as_string().to_string(); - CompileError::UnsupportedExpr { desc } - }, - 4 => { - let curr = self.get_obj(0).as_string().to_string(); - let param = self.get_obj(1).as_string().to_string(); - CompileError::UnknownUnivParam { curr, param } - }, - 5 => { - CompileError::Serialize(LeanIxSerializeError(self.get_obj(0)).decode()) - }, - 6 => CompileError::ResourceLimit { - reason: self.get_obj(0).as_string().to_string(), - }, - 7 => CompileError::SharingConstruction { - reason: self.get_obj(0).as_string().to_string(), - }, - tag => unreachable!("Invalid CompileError tag: {tag}"), - } - } -} - -/// FFI: Round-trip a DecompileError: Lean → Rust → Lean. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_decompile_error( - obj: LeanIxDecompileError>, -) -> LeanIxDecompileError { - let err = obj.decode(); - LeanIxDecompileError::build(&err) -} - -/// FFI: Round-trip a CompileError: Lean → Rust → Lean. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_compile_error( - obj: LeanIxCompileError>, -) -> LeanIxCompileError { - let err = obj.decode(); - LeanIxCompileError::build(&err) -} - -/// FFI: Round-trip a SerializeError: Lean → Rust → Lean. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_serialize_error( - obj: LeanIxSerializeError>, -) -> LeanIxSerializeError { - let err = obj.decode(); - LeanIxSerializeError::build(&err) -} - -// ============================================================================= -// Decompilation FFI -// ============================================================================= - -/// FFI: decompile a serialized `.ixe` env from disk — the inverse of -/// `rs_compile_env` — returning the decompiled constant count. -/// -/// A `decompile_env` failure (malformed output) is a hard error, -/// returned as an IO error so the caller reddens the cell. Timing and -/// peak-RSS are measured by the Lean caller around this call, with the -/// same texray infrastructure `ix compile --json` uses. Deeper -/// compile→decompile roundtrip fidelity is gated by the canonical -/// roundtrip tests (`ix validate` / `rs_decompile_roundtrip`), which -/// need the original Lean env a `.ixe` can't supply — the bench does -/// not reproduce them here. -/// -/// Lean signature: -/// ```lean -/// @[extern "rs_decompile_env"] -/// opaque rsDecompileEnvFFI : @& String → IO Nat -/// ``` -#[unsafe(no_mangle)] -pub extern "C" fn rs_decompile_env( - path: LeanString>, -) -> LeanIOResult { - let path = path.as_str().to_string(); - - let t_read = std::time::Instant::now(); - let bytes = match std::fs::read(&path) { - Ok(b) => b, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_decompile_env: failed to read {path}: {e}" - )); - }, - }; - // Demoted-at-parse load: decompile reads each entry's metadata a - // bounded number of times, and the whole named section's structured - // form costs a large multiple of its encoding — enough to dominate - // the decompile's peak RSS on the biggest envs. One load path at - // every scale also keeps the bench rows comparable across envs. - let t_parse = std::time::Instant::now(); - let read_secs = (t_parse - t_read).as_secs_f32(); - let mut slice: &[u8] = &bytes; - let env = match ixon::env::Env::get_demoted_named(&mut slice) { - Ok(env) => env, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_decompile_env: failed to deserialize {path}: {e}" - )); - }, - }; - eprintln!( - "[decompile] env read in {read_secs:.2}s, parsed in {:.2}s{}", - t_parse.elapsed().as_secs_f32(), - ix_compile::diag::rss_log_suffix(), - ); - - // The env owns its bytes after parsing; release the file buffer - // before the decompile allocates next to it. - drop(bytes); - - // Every decompile output is keyed by a §5 name, so an anonymous env - // (§5 empty) would "succeed" with 0 constants. Fail loudly instead. - if env.named.is_empty() { - return LeanIOResult::error_string(&format!( - "rs_decompile_env: {path} is an anonymous env (empty named \ - section); decompile needs names" - )); - } - - // Decompile needs every reachable constant carried: a thin bundle - // deliberately omits its assumed subtrees, and a bundle with a broken - // closure would otherwise surface deep inside the decompile as a - // confusing per-constant error. Whole envs (`main = None`) carry - // everything by construction and skip the closure walk. - if !env.assumptions.is_empty() { - return LeanIOResult::error_string(&format!( - "rs_decompile_env: {path} is a thin bundle ({} assumptions); \ - decompile needs a self-contained env", - env.assumptions.len() - )); - } - if env.main.is_some() - && let Err(e) = env.validate_closed() - { - return LeanIOResult::error_string(&format!( - "rs_decompile_env: {path}: {e}" - )); - } - - // decompile_env reads `stt.env`; `name_to_addr` mirrors the compiled - // state's name table for its diagnostics. - let stt = CompileState { env, ..CompileState::default() }; - for entry in stt.env.named.iter() { - stt.name_to_addr.insert(entry.key().clone(), entry.value().addr.clone()); - } - - let dstt = match decompile_env(&stt) { - Ok(d) => d, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_decompile_env: decompile of {path} failed: {e:?}" - )); - }, - }; - let n = dstt.env.len() as u64; - // One-shot CLI entry (`ix decompile` only — the import path uses - // `rs_decompile_env_consts`): skip the destructors like - // `rs_compile_env` does; dropping ~30 GiB of DashMaps costs seconds - // and the OS reclaims at exit. - std::mem::forget(dstt); - std::mem::forget(stt); - LeanIOResult::ok(LeanOwned::from_nat_u64(n)) -} - -/// Decompile a serialized `.ixe` env and materialize constants as real -/// `Lean.ConstantInfo` objects — the production import path behind -/// `import_ixe` (`Ix/ImportIxe.lean`). -/// -/// `names` selects source roots: empty ⇒ every decompiled constant; -/// nonempty ⇒ the requested source constants plus their full name-level -/// reference closure over the decompiled env -/// (`ix_compile::decompile::materialized_source_names`). An absent -/// requested name or a reference that resolves to nothing (which would -/// fail kernel replay much later with a worse message) is an immediate -/// error (fail closed), for a full selection too. -/// -/// Pairs return sorted by name; mutual-block grouping and topological -/// replay order are the Lean side's concern (`Ix.Catalog` machinery). -/// Construction of `Name`/`Level`/`Expr` goes through the toolchain's -/// exported constructors so computed fields (hashes, bvar ranges) are -/// Lean's own — see `crate::lean_build`. -/// -/// Lean signature: -/// ```lean -/// @[extern "rs_decompile_env_consts"] -/// opaque rsDecompileEnvConstsFFI : @& String → @& Array Lean.Name → -/// IO (Array (Lean.Name × Lean.ConstantInfo)) -/// ``` -#[unsafe(no_mangle)] -pub extern "C" fn rs_decompile_env_consts( - path: LeanString>, - names: LeanArray>, -) -> LeanIOResult { - use crate::lean_build::{LeanEncodeCache, encode_constant_info, encode_name}; - - let path = path.as_str().to_string(); - let requested = crate::lean_env::decode_name_array(&names); - - let bytes = match std::fs::read(&path) { - Ok(b) => b, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_decompile_env_consts: failed to read {path}: {e}" - )); - }, - }; - let mut slice: &[u8] = &bytes; - let env = match ixon::env::Env::get_demoted_named(&mut slice) { - Ok(env) => env, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_decompile_env_consts: failed to deserialize {path}: {e}" - )); - }, - }; - drop(bytes); - - // Same guard as `rs_decompile_env`: materialization is name-keyed, - // and an anonymous env would silently materialize nothing. - if env.named.is_empty() { - return LeanIOResult::error_string(&format!( - "rs_decompile_env_consts: {path} is an anonymous env (empty named \ - section); decompile needs names" - )); - } - - if !env.assumptions.is_empty() { - return LeanIOResult::error_string(&format!( - "rs_decompile_env_consts: {path} is a thin bundle ({} assumptions); \ - materialization needs a self-contained env", - env.assumptions.len() - )); - } - if env.main.is_some() - && let Err(e) = env.validate_closed() - { - return LeanIOResult::error_string(&format!( - "rs_decompile_env_consts: {path}: {e}" - )); - } - - let stt = CompileState { env, ..CompileState::default() }; - for entry in stt.env.named.iter() { - stt.name_to_addr.insert(entry.key().clone(), entry.value().addr.clone()); - } - - let dstt = match decompile_env(&stt) { - Ok(d) => d, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_decompile_env_consts: decompile of {path} failed: {e:?}" - )); - }, - }; - - // Select the output set: every decompiled constant, or the requested - // source closure. - let selected = match ix_compile::decompile::materialized_source_names( - &dstt.env, &requested, - ) { - Ok(selected) => selected, - Err(error) => { - return LeanIOResult::error_string(&format!( - "rs_decompile_env_consts: {path}: {error}" - )); - }, - }; - - // Marshal on the calling thread with one shared encode cache so - // subterms shared across constants become shared Lean objects. - let mut cache = LeanEncodeCache::default(); - let arr = LeanArray::alloc(selected.len()); - for (i, name) in selected.iter().enumerate() { - let ci_obj = { - let ci = dstt.env.get(name).expect("selected name present"); - match encode_constant_info(&mut cache, ci.value()) { - Ok(o) => o, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_decompile_env_consts: {}: {e}", - name.pretty() - )); - }, - } - }; - let name_obj = encode_name(&mut cache, name); - arr.set(i, LeanProd::new(name_obj, ci_obj)); - } - LeanIOResult::ok(arr) -} diff --git a/crates/ffi/src/graph.rs b/crates/ffi/src/graph.rs deleted file mode 100644 index 5dfdd9e8a..000000000 --- a/crates/ffi/src/graph.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! Graph and SCC FFI functions. - -use std::sync::Arc; - -use crate::lean::LeanIxCondensedBlocks; -use ix_compile::condense::compute_sccs; -use ix_compile::graph::build_ref_graph; -use lean_ffi::object::{ - LeanArray, LeanBorrowed, LeanIOResult, LeanList, LeanOwned, LeanProd, -}; - -use crate::builder::LeanBuildCache; -use crate::lean::LeanIxName; -use crate::lean_env::decode_env; - -/// Build an Array (Ix.Name × Array Ix.Name) from a RefMap. -pub fn build_ref_graph_array( - cache: &mut LeanBuildCache, - refs: &ix_compile::graph::RefMap, -) -> LeanArray { - let arr = LeanArray::alloc(refs.len()); - for (i, (name, ref_set)) in refs.iter().enumerate() { - let name_obj = LeanIxName::build(cache, name); - - let refs_arr = LeanArray::alloc(ref_set.len()); - for (j, ref_name) in ref_set.iter().enumerate() { - let ref_name_obj = LeanIxName::build(cache, ref_name); - refs_arr.set(j, ref_name_obj); - } - - let pair = LeanProd::new(name_obj, refs_arr); - arr.set(i, pair); - } - arr -} - -impl LeanIxCondensedBlocks { - /// Build a RustCondensedBlocks structure. - pub fn build( - cache: &mut LeanBuildCache, - condensed: &ix_compile::condense::CondensedBlocks, - ) -> Self { - // Build lowLinks: Array (Ix.Name × Ix.Name) - let low_links_arr = LeanArray::alloc(condensed.low_links.len()); - for (i, (name, low_link)) in condensed.low_links.iter().enumerate() { - let name_obj = LeanIxName::build(cache, name); - let low_link_obj = LeanIxName::build(cache, low_link); - let pair = LeanProd::new(name_obj, low_link_obj); - low_links_arr.set(i, pair); - } - - // Build blocks: Array (Ix.Name × Array Ix.Name) - let blocks_arr = LeanArray::alloc(condensed.blocks.len()); - for (i, (name, block_set)) in condensed.blocks.iter().enumerate() { - let name_obj = LeanIxName::build(cache, name); - let block_names_arr = LeanArray::alloc(block_set.len()); - for (j, block_name) in block_set.iter().enumerate() { - let block_name_obj = LeanIxName::build(cache, block_name); - block_names_arr.set(j, block_name_obj); - } - let pair = LeanProd::new(name_obj, block_names_arr); - blocks_arr.set(i, pair); - } - - // Build blockRefs: Array (Ix.Name × Array Ix.Name) - let block_refs_arr = LeanArray::alloc(condensed.block_refs.len()); - for (i, (name, ref_set)) in condensed.block_refs.iter().enumerate() { - let name_obj = LeanIxName::build(cache, name); - let refs_arr = LeanArray::alloc(ref_set.len()); - for (j, ref_name) in ref_set.iter().enumerate() { - let ref_name_obj = LeanIxName::build(cache, ref_name); - refs_arr.set(j, ref_name_obj); - } - let pair = LeanProd::new(name_obj, refs_arr); - block_refs_arr.set(i, pair); - } - - // Build RustCondensedBlocks structure (3 fields) - let result = LeanIxCondensedBlocks::alloc(0); - result.set_obj(0, low_links_arr); - result.set_obj(1, blocks_arr); - result.set_obj(2, block_refs_arr); - result - } -} - -// ============================================================================= -// FFI Exports -// ============================================================================= - -/// FFI function to build a reference graph from a Lean environment. -#[unsafe(no_mangle)] -pub extern "C" fn rs_build_ref_graph( - env_consts_ptr: LeanList>, -) -> LeanIOResult { - let rust_env = decode_env(env_consts_ptr); - let rust_env = Arc::new(rust_env); - let ref_graph = build_ref_graph(&rust_env); - let mut cache = LeanBuildCache::with_capacity(rust_env.len()); - let result = build_ref_graph_array(&mut cache, &ref_graph.out_refs); - LeanIOResult::ok(result) -} - -/// FFI function to compute SCCs from a Lean environment. -#[unsafe(no_mangle)] -pub extern "C" fn rs_compute_sccs( - env_consts_ptr: LeanList>, -) -> LeanIOResult { - let rust_env = decode_env(env_consts_ptr); - let rust_env = Arc::new(rust_env); - let ref_graph = build_ref_graph(&rust_env); - let condensed = compute_sccs(&ref_graph.out_refs); - let mut cache = LeanBuildCache::with_capacity(rust_env.len()); - let result = LeanIxCondensedBlocks::build(&mut cache, &condensed); - LeanIOResult::ok(result) -} diff --git a/crates/ffi/src/ix.rs b/crates/ffi/src/ix.rs deleted file mode 100644 index 2973c3bae..000000000 --- a/crates/ffi/src/ix.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! FFI for Ix types (canonical types with embedded hashes). -//! -//! This module provides build/decode/roundtrip functions for Ix types: -//! - Ix.Address - Blake3 hash wrapper -//! - Ix.Name - anonymous, str, num -//! - Ix.Level - zero, succ, max, imax, param, mvar -//! - Ix.Expr - 12 constructors -//! - Ix.ConstantInfo - 8 variants -//! - Ix.DataValue, Ix.Syntax, Ix.SourceInfo -//! - Ix.Environment - -pub mod address; -pub mod constant; -pub mod data; -pub mod env; -pub mod expr; -pub mod level; -pub mod name; diff --git a/crates/ffi/src/ix/address.rs b/crates/ffi/src/ix/address.rs deleted file mode 100644 index 34512e458..000000000 --- a/crates/ffi/src/ix/address.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Ix.Address build/decode/roundtrip FFI. -//! -//! Address = { hash : ByteArray } - ByteArray wrapper for blake3 Hash - -use crate::lean::LeanIxAddress; -use ix_common::address::Address; -use lean_ffi::object::{LeanArray, LeanBorrowed, LeanByteArray, LeanOwned}; - -impl LeanIxAddress { - /// Build an Ix.Address from a blake3::Hash. - pub fn build_from_hash(hash: &blake3::Hash) -> Self { - LeanByteArray::from_bytes(hash.as_bytes()).into() - } - - /// Build an Ix.Address from an Ixon Address (which is just a [u8; 32]). - pub fn build(addr: &Address) -> Self { - LeanByteArray::from_bytes(addr.as_bytes()).into() - } - - /// Build an Array of Addresses. - pub fn build_array(addrs: &[Address]) -> LeanArray { - let arr = LeanArray::alloc(addrs.len()); - for (i, addr) in addrs.iter().enumerate() { - arr.set(i, Self::build(addr)); - } - arr - } -} - -impl LeanIxAddress { - /// Decode a ByteArray (Address) to Address. - pub fn decode(&self) -> Address { - Address::from_slice(&self.as_bytes()[..32]) - .expect("Address should be 32 bytes") - } -} - -impl LeanIxAddress> { - /// Decode Array Address. - pub fn decode_array(obj: LeanArray>) -> Vec
{ - obj.map(|x| LeanIxAddress::from_borrowed(x.as_byte_array()).decode()) - } -} - -/// Round-trip an Ix.Address: decode ByteArray, re-encode. -/// Address = { hash : ByteArray } - single field struct, so UNBOXED to ByteArray directly -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_address( - addr: LeanIxAddress>, -) -> LeanIxAddress { - let decoded = addr.decode(); - LeanIxAddress::build(&decoded) -} diff --git a/crates/ffi/src/ix/constant.rs b/crates/ffi/src/ix/constant.rs deleted file mode 100644 index 5c3fb70a2..000000000 --- a/crates/ffi/src/ix/constant.rs +++ /dev/null @@ -1,466 +0,0 @@ -//! Ix.ConstantInfo build/decode/roundtrip FFI. -//! -//! ConstantInfo variants: -//! - Tag 0: axiomInfo (v : AxiomVal) -//! - Tag 1: defnInfo (v : DefinitionVal) -//! - Tag 2: thmInfo (v : TheoremVal) -//! - Tag 3: opaqueInfo (v : OpaqueVal) -//! - Tag 4: quotInfo (v : QuotVal) -//! - Tag 5: inductInfo (v : InductiveVal) -//! - Tag 6: ctorInfo (v : ConstructorVal) -//! - Tag 7: recInfo (v : RecursorVal) - -use crate::lean::{ - LeanIxAxiomVal, LeanIxConstantInfo, LeanIxConstantVal, LeanIxConstructorVal, - LeanIxDefinitionVal, LeanIxExpr, LeanIxInductiveVal, LeanIxName, - LeanIxOpaqueVal, LeanIxQuotVal, LeanIxRecursorRule, LeanIxRecursorVal, - LeanIxReducibilityHints, LeanIxTheoremVal, -}; -use ix_common::env::{ - AxiomVal, ConstantInfo, ConstantVal, ConstructorVal, DefinitionSafety, - DefinitionVal, InductiveVal, Name, OpaqueVal, QuotKind, QuotVal, - RecursorRule, RecursorVal, ReducibilityHints, TheoremVal, -}; -#[cfg(feature = "test-ffi")] -use lean_ffi::object::LeanBorrowed; -use lean_ffi::object::LeanNat; -use lean_ffi::object::{LeanArray, LeanOwned, LeanRef}; - -use crate::builder::LeanBuildCache; - -// ============================================================================= -// ConstantVal -// ============================================================================= - -impl LeanIxConstantVal { - /// Build a Ix.ConstantVal structure. - pub fn build(cache: &mut LeanBuildCache, cv: &ConstantVal) -> Self { - // ConstantVal = { name : Name, levelParams : Array Name, type : Expr } - let name_obj = LeanIxName::build(cache, &cv.name); - let level_params_obj = LeanIxName::build_array(cache, &cv.level_params); - let type_obj = LeanIxExpr::build(cache, &cv.typ); - - let ctor = LeanIxConstantVal::alloc(0); - ctor.set_obj(0, name_obj); - ctor.set_obj(1, level_params_obj); - ctor.set_obj(2, type_obj); - ctor - } -} - -impl LeanIxConstantVal { - /// Decode Ix.ConstantVal from Lean object. - /// ConstantVal = { name : Name, levelParams : Array Name, type : Expr } - pub fn decode(&self) -> ConstantVal { - let ctor = self.as_ctor(); - let name = LeanIxName(ctor.get(0)).decode(); - let level_params: Vec = - ctor.get(1).as_array().map(|x| LeanIxName(x).decode()); - let typ = LeanIxExpr(ctor.get(2)).decode(); - - ConstantVal { name, level_params, typ } - } -} - -// ============================================================================= -// ReducibilityHints -// ============================================================================= - -impl LeanIxReducibilityHints { - /// Build ReducibilityHints. - /// NOTE: In Lean 4, 0-field constructors are boxed scalars when the inductive has - /// other constructors with fields. So opaque and abbrev use box_usize. - pub fn build(hints: &ReducibilityHints) -> Self { - match hints { - // | opaque -- tag 0, boxed as scalar - ReducibilityHints::Opaque => Self::new(LeanOwned::box_usize(0)), - // | abbrev -- tag 1, boxed as scalar - ReducibilityHints::Abbrev => Self::new(LeanOwned::box_usize(1)), - // | regular (h : UInt32) -- tag 2, object constructor - ReducibilityHints::Regular(h) => { - let ctor = LeanIxReducibilityHints::alloc(2); - ctor.set_num_32(0, *h); - ctor - }, - } - } -} - -impl LeanIxReducibilityHints { - /// Decode Lean.ReducibilityHints from Lean object. - pub fn decode(&self) -> ReducibilityHints { - if self.inner().is_scalar() { - let tag = self.inner().unbox_usize(); - match tag { - 0 => return ReducibilityHints::Opaque, - 1 => return ReducibilityHints::Abbrev, - _ => panic!("Invalid ReducibilityHints scalar tag: {}", tag), - } - } - - let ctor = self.as_ctor(); - match ctor.tag() { - 0 => ReducibilityHints::Opaque, - 1 => ReducibilityHints::Abbrev, - 2 => { - // regular: 0 obj fields, 4 scalar bytes (UInt32) - ReducibilityHints::Regular(self.get_num_32(0)) - }, - _ => panic!("Invalid ReducibilityHints tag: {}", ctor.tag()), - } - } -} - -// ============================================================================= -// RecursorRule -// ============================================================================= - -impl LeanIxRecursorRule { - /// Decode Ix.RecursorRule from Lean object. - pub fn decode(&self) -> RecursorRule { - let ctor = self.as_ctor(); - RecursorRule { - ctor: LeanIxName(ctor.get(0)).decode(), - n_fields: LeanNat::to_nat(&ctor.get(1)), - rhs: LeanIxExpr(ctor.get(2)).decode(), - } - } -} - -// ============================================================================= -// ConstantInfo -// ============================================================================= - -impl LeanIxRecursorRule { - /// Build an Array of RecursorRule. - pub fn build_array( - cache: &mut LeanBuildCache, - rules: &[RecursorRule], - ) -> LeanArray { - let arr = LeanArray::alloc(rules.len()); - for (i, rule) in rules.iter().enumerate() { - // RecursorRule = { ctor : Name, nFields : Nat, rhs : Expr } - let ctor_obj = LeanIxName::build(cache, &rule.ctor); - let n_fields_obj = LeanNat::from_nat(&rule.n_fields); - let rhs_obj = LeanIxExpr::build(cache, &rule.rhs); - - let rule_obj = LeanIxRecursorRule::alloc(0); - rule_obj.set_obj(0, ctor_obj); - rule_obj.set_obj(1, n_fields_obj); - rule_obj.set_obj(2, rhs_obj); - - arr.set(i, rule_obj); - } - arr - } -} - -impl LeanIxConstantInfo { - /// Build a Ix.ConstantInfo from a Rust ConstantInfo. - pub fn build(cache: &mut LeanBuildCache, info: &ConstantInfo) -> Self { - match info { - // | axiomInfo (v : AxiomVal) -- tag 0 - ConstantInfo::AxiomInfo(v) => { - // AxiomVal = { cnst : ConstantVal, isUnsafe : Bool } - let cnst_obj = LeanIxConstantVal::build(cache, &v.cnst); - let axiom_val = LeanIxAxiomVal::alloc(0); - axiom_val.set_obj(0, cnst_obj); - axiom_val.set_num_8(0, u8::from(v.is_unsafe)); - - let obj = LeanIxConstantInfo::alloc(0); - obj.set_obj(0, axiom_val); - obj - }, - // | defnInfo (v : DefinitionVal) -- tag 1 - ConstantInfo::DefnInfo(v) => { - // DefinitionVal = { cnst, value, hints, safety, all } - // Memory layout: 4 obj fields (cnst, value, hints, all), 1 scalar byte (safety) - let cnst_obj = LeanIxConstantVal::build(cache, &v.cnst); - let value_obj = LeanIxExpr::build(cache, &v.value); - let hints_obj = LeanIxReducibilityHints::build(&v.hints); - let all_obj = LeanIxName::build_array(cache, &v.all); - let safety_byte = match v.safety { - DefinitionSafety::Unsafe => 0u8, - DefinitionSafety::Safe => 1u8, - DefinitionSafety::Partial => 2u8, - }; - - let defn_val = LeanIxDefinitionVal::alloc(0); - defn_val.set_obj(0, cnst_obj); - defn_val.set_obj(1, value_obj); - defn_val.set_obj(2, hints_obj); - defn_val.set_obj(3, all_obj); - defn_val.set_num_8(0, safety_byte); - - let obj = LeanIxConstantInfo::alloc(1); - obj.set_obj(0, defn_val); - obj - }, - // | thmInfo (v : TheoremVal) -- tag 2 - ConstantInfo::ThmInfo(v) => { - // TheoremVal = { cnst, value, all } - let cnst_obj = LeanIxConstantVal::build(cache, &v.cnst); - let value_obj = LeanIxExpr::build(cache, &v.value); - let all_obj = LeanIxName::build_array(cache, &v.all); - - let thm_val = LeanIxTheoremVal::alloc(0); - thm_val.set_obj(0, cnst_obj); - thm_val.set_obj(1, value_obj); - thm_val.set_obj(2, all_obj); - - let obj = LeanIxConstantInfo::alloc(2); - obj.set_obj(0, thm_val); - obj - }, - // | opaqueInfo (v : OpaqueVal) -- tag 3 - ConstantInfo::OpaqueInfo(v) => { - // OpaqueVal = { cnst, value, isUnsafe, all } - let cnst_obj = LeanIxConstantVal::build(cache, &v.cnst); - let value_obj = LeanIxExpr::build(cache, &v.value); - let all_obj = LeanIxName::build_array(cache, &v.all); - - let opaque_val = LeanIxOpaqueVal::alloc(0); - opaque_val.set_obj(0, cnst_obj); - opaque_val.set_obj(1, value_obj); - opaque_val.set_obj(2, all_obj); - opaque_val.set_num_8(0, u8::from(v.is_unsafe)); - - let obj = LeanIxConstantInfo::alloc(3); - obj.set_obj(0, opaque_val); - obj - }, - // | quotInfo (v : QuotVal) -- tag 4 - ConstantInfo::QuotInfo(v) => { - // QuotVal = { cnst, kind } - // Memory layout: 1 obj field (cnst), 1 scalar byte (kind) - let cnst_obj = LeanIxConstantVal::build(cache, &v.cnst); - let kind_byte = match v.kind { - QuotKind::Type => 0u8, - QuotKind::Ctor => 1u8, - QuotKind::Lift => 2u8, - QuotKind::Ind => 3u8, - }; - - let quot_val = LeanIxQuotVal::alloc(0); - quot_val.set_obj(0, cnst_obj); - quot_val.set_num_8(0, kind_byte); - - let obj = LeanIxConstantInfo::alloc(4); - obj.set_obj(0, quot_val); - obj - }, - // | inductInfo (v : InductiveVal) -- tag 5 - ConstantInfo::InductInfo(v) => { - // InductiveVal = { cnst, numParams, numIndices, all, ctors, numNested, isRec, isUnsafe, isReflexive } - let cnst_obj = LeanIxConstantVal::build(cache, &v.cnst); - let num_params_obj = LeanNat::from_nat(&v.num_params); - let num_indices_obj = LeanNat::from_nat(&v.num_indices); - let all_obj = LeanIxName::build_array(cache, &v.all); - let ctors_obj = LeanIxName::build_array(cache, &v.ctors); - let num_nested_obj = LeanNat::from_nat(&v.num_nested); - - // 6 object fields, 3 scalar bytes for bools - let induct_val = LeanIxInductiveVal::alloc(0); - induct_val.set_obj(0, cnst_obj); - induct_val.set_obj(1, num_params_obj); - induct_val.set_obj(2, num_indices_obj); - induct_val.set_obj(3, all_obj); - induct_val.set_obj(4, ctors_obj); - induct_val.set_obj(5, num_nested_obj); - induct_val.set_num_8(0, u8::from(v.is_rec)); - induct_val.set_num_8(1, u8::from(v.is_unsafe)); - induct_val.set_num_8(2, u8::from(v.is_reflexive)); - - let obj = LeanIxConstantInfo::alloc(5); - obj.set_obj(0, induct_val); - obj - }, - // | ctorInfo (v : ConstructorVal) -- tag 6 - ConstantInfo::CtorInfo(v) => { - // ConstructorVal = { cnst, induct, cidx, numParams, numFields, isUnsafe } - let cnst_obj = LeanIxConstantVal::build(cache, &v.cnst); - let induct_obj = LeanIxName::build(cache, &v.induct); - let cidx_obj = LeanNat::from_nat(&v.cidx); - let num_params_obj = LeanNat::from_nat(&v.num_params); - let num_fields_obj = LeanNat::from_nat(&v.num_fields); - - // 5 object fields, 1 scalar byte for bool - let ctor_val = LeanIxConstructorVal::alloc(0); - ctor_val.set_obj(0, cnst_obj); - ctor_val.set_obj(1, induct_obj); - ctor_val.set_obj(2, cidx_obj); - ctor_val.set_obj(3, num_params_obj); - ctor_val.set_obj(4, num_fields_obj); - ctor_val.set_num_8(0, u8::from(v.is_unsafe)); - - let obj = LeanIxConstantInfo::alloc(6); - obj.set_obj(0, ctor_val); - obj - }, - // | recInfo (v : RecursorVal) -- tag 7 - ConstantInfo::RecInfo(v) => { - // RecursorVal = { cnst, all, numParams, numIndices, numMotives, numMinors, rules, k, isUnsafe } - let cnst_obj = LeanIxConstantVal::build(cache, &v.cnst); - let all_obj = LeanIxName::build_array(cache, &v.all); - let num_params_obj = LeanNat::from_nat(&v.num_params); - let num_indices_obj = LeanNat::from_nat(&v.num_indices); - let num_motives_obj = LeanNat::from_nat(&v.num_motives); - let num_minors_obj = LeanNat::from_nat(&v.num_minors); - let rules_obj = LeanIxRecursorRule::build_array(cache, &v.rules); - - // 7 object fields, 2 scalar bytes for bools - let rec_val = LeanIxRecursorVal::alloc(0); - rec_val.set_obj(0, cnst_obj); - rec_val.set_obj(1, all_obj); - rec_val.set_obj(2, num_params_obj); - rec_val.set_obj(3, num_indices_obj); - rec_val.set_obj(4, num_motives_obj); - rec_val.set_obj(5, num_minors_obj); - rec_val.set_obj(6, rules_obj); - rec_val.set_num_8(0, u8::from(v.k)); - rec_val.set_num_8(1, u8::from(v.is_unsafe)); - - let obj = LeanIxConstantInfo::alloc(7); - obj.set_obj(0, rec_val); - obj - }, - } - } -} - -impl LeanIxConstantInfo { - /// Decode Ix.ConstantInfo from Lean object. - pub fn decode(&self) -> ConstantInfo { - let outer = self.as_ctor(); - let inner_obj = outer.get(0); - let inner = inner_obj.as_ctor(); - - match outer.tag() { - 0 => { - // AxiomVal inner - let inner_val = LeanIxAxiomVal(inner_obj); - let is_unsafe = inner_val.get_num_8(0) != 0; - - ConstantInfo::AxiomInfo(AxiomVal { - cnst: LeanIxConstantVal(inner_val.get_obj(0)).decode(), - is_unsafe, - }) - }, - 1 => { - let inner_val = LeanIxDefinitionVal(inner_obj); - let safety_byte = inner_val.get_num_8(0); - let safety = match safety_byte { - 0 => DefinitionSafety::Unsafe, - 1 => DefinitionSafety::Safe, - 2 => DefinitionSafety::Partial, - _ => panic!("Invalid DefinitionSafety: {}", safety_byte), - }; - - ConstantInfo::DefnInfo(DefinitionVal { - cnst: LeanIxConstantVal(inner_val.get_obj(0)).decode(), - value: LeanIxExpr(inner_val.get_obj(1)).decode(), - hints: LeanIxReducibilityHints(inner_val.get_obj(2)).decode(), - safety, - all: LeanIxName::decode_array(inner_val.get_obj(3).as_array()), - }) - }, - 2 => ConstantInfo::ThmInfo(TheoremVal { - cnst: LeanIxConstantVal(inner.get(0)).decode(), - value: LeanIxExpr(inner.get(1)).decode(), - all: LeanIxName::decode_array(inner.get(2).as_array()), - }), - 3 => { - let inner_val = LeanIxOpaqueVal(inner_obj); - let is_unsafe = inner_val.get_num_8(0) != 0; - - ConstantInfo::OpaqueInfo(OpaqueVal { - cnst: LeanIxConstantVal(inner_val.get_obj(0)).decode(), - value: LeanIxExpr(inner_val.get_obj(1)).decode(), - is_unsafe, - all: LeanIxName::decode_array(inner_val.get_obj(2).as_array()), - }) - }, - 4 => { - let inner_val = LeanIxQuotVal(inner_obj); - let kind_byte = inner_val.get_num_8(0); - let kind = match kind_byte { - 0 => QuotKind::Type, - 1 => QuotKind::Ctor, - 2 => QuotKind::Lift, - 3 => QuotKind::Ind, - _ => panic!("Invalid QuotKind: {}", kind_byte), - }; - - ConstantInfo::QuotInfo(QuotVal { - cnst: LeanIxConstantVal(inner_val.get_obj(0)).decode(), - kind, - }) - }, - 5 => { - let inner_val = LeanIxInductiveVal(inner_obj); - let is_rec = inner_val.get_num_8(0) != 0; - let is_unsafe = inner_val.get_num_8(1) != 0; - let is_reflexive = inner_val.get_num_8(2) != 0; - - ConstantInfo::InductInfo(InductiveVal { - cnst: LeanIxConstantVal(inner_val.get_obj(0)).decode(), - num_params: LeanNat::to_nat(&inner_val.get_obj(1)), - num_indices: LeanNat::to_nat(&inner_val.get_obj(2)), - all: LeanIxName::decode_array(inner_val.get_obj(3).as_array()), - ctors: LeanIxName::decode_array(inner_val.get_obj(4).as_array()), - num_nested: LeanNat::to_nat(&inner_val.get_obj(5)), - is_rec, - is_unsafe, - is_reflexive, - }) - }, - 6 => { - let inner_val = LeanIxConstructorVal(inner_obj); - let is_unsafe = inner_val.get_num_8(0) != 0; - - ConstantInfo::CtorInfo(ConstructorVal { - cnst: LeanIxConstantVal(inner_val.get_obj(0)).decode(), - induct: LeanIxName(inner_val.get_obj(1)).decode(), - cidx: LeanNat::to_nat(&inner_val.get_obj(2)), - num_params: LeanNat::to_nat(&inner_val.get_obj(3)), - num_fields: LeanNat::to_nat(&inner_val.get_obj(4)), - is_unsafe, - }) - }, - 7 => { - let inner_val = LeanIxRecursorVal(inner_obj); - let k = inner_val.get_num_8(0) != 0; - let is_unsafe = inner_val.get_num_8(1) != 0; - - let rules: Vec = inner_val - .get_obj(6) - .as_array() - .map(|x| LeanIxRecursorRule(x).decode()); - - ConstantInfo::RecInfo(RecursorVal { - cnst: LeanIxConstantVal(inner_val.get_obj(0)).decode(), - all: LeanIxName::decode_array(inner_val.get_obj(1).as_array()), - num_params: LeanNat::to_nat(&inner_val.get_obj(2)), - num_indices: LeanNat::to_nat(&inner_val.get_obj(3)), - num_motives: LeanNat::to_nat(&inner_val.get_obj(4)), - num_minors: LeanNat::to_nat(&inner_val.get_obj(5)), - rules, - k, - is_unsafe, - }) - }, - _ => panic!("Invalid ConstantInfo tag: {}", outer.tag()), - } - } -} - -/// Round-trip an Ix.ConstantInfo: decode from Lean, re-encode via LeanBuildCache. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_constant_info( - info_ptr: LeanIxConstantInfo>, -) -> LeanIxConstantInfo { - let info = info_ptr.decode(); - let mut cache = LeanBuildCache::new(); - LeanIxConstantInfo::build(&mut cache, &info) -} diff --git a/crates/ffi/src/ix/data.rs b/crates/ffi/src/ix/data.rs deleted file mode 100644 index 98f99cba4..000000000 --- a/crates/ffi/src/ix/data.rs +++ /dev/null @@ -1,441 +0,0 @@ -//! Ix.DataValue, Ix.Syntax, Ix.SourceInfo build/decode/roundtrip FFI. - -use crate::lean::{ - LeanIxDataValue, LeanIxInt, LeanIxName, LeanIxSourceInfo, LeanIxSubstring, - LeanIxSyntax, LeanIxSyntaxPreresolved, -}; -use ix_common::env::{ - DataValue, Int, Name, SourceInfo, Substring, Syntax, SyntaxPreresolved, -}; -#[cfg(feature = "test-ffi")] -use lean_ffi::object::LeanBorrowed; -use lean_ffi::object::LeanNat; -use lean_ffi::object::{LeanArray, LeanOwned, LeanProd, LeanRef, LeanString}; - -use crate::builder::LeanBuildCache; - -impl LeanIxInt { - /// Build a Ix.Int (ofNat or negSucc). - pub fn build(int: &Int) -> Self { - match int { - Int::OfNat(n) => { - let ctor = LeanIxInt::alloc(0); - ctor.set_obj(0, LeanNat::from_nat(n)); - ctor - }, - Int::NegSucc(n) => { - let ctor = LeanIxInt::alloc(1); - ctor.set_obj(0, LeanNat::from_nat(n)); - ctor - }, - } - } -} - -impl LeanIxInt { - /// Decode Ix.Int from Lean object. - /// Ix.Int: ofNat (tag 0, 1 field) | negSucc (tag 1, 1 field) - pub fn decode(&self) -> Int { - let ctor = self.as_ctor(); - let nat = LeanNat::to_nat(&ctor.get(0)); - match ctor.tag() { - 0 => Int::OfNat(nat), - 1 => Int::NegSucc(nat), - _ => panic!("Invalid Ix.Int tag: {}", ctor.tag()), - } - } -} - -impl LeanIxSubstring { - /// Build a Ix.Substring. - pub fn build(ss: &Substring) -> Self { - let ctor = LeanIxSubstring::alloc(0); - ctor.set_obj(0, LeanString::from_bytes(ss.str.as_bytes())); - ctor.set_obj(1, LeanNat::from_nat(&ss.start_pos)); - ctor.set_obj(2, LeanNat::from_nat(&ss.stop_pos)); - ctor - } -} - -impl LeanIxSubstring { - /// Decode Ix.Substring. - pub fn decode(&self) -> Substring { - let ctor = self.as_ctor(); - Substring { - str: ctor.get(0).as_string().to_string(), - start_pos: LeanNat::to_nat(&ctor.get(1)), - stop_pos: LeanNat::to_nat(&ctor.get(2)), - } - } -} - -impl LeanIxSourceInfo { - /// Build a Ix.SourceInfo. - pub fn build(si: &SourceInfo) -> Self { - match si { - // | original (leading : Substring) (pos : Nat) (trailing : Substring) (endPos : Nat) -- tag 0 - SourceInfo::Original(leading, pos, trailing, end_pos) => { - let ctor = LeanIxSourceInfo::alloc(0); - ctor.set_obj(0, LeanIxSubstring::build(leading)); - ctor.set_obj(1, LeanNat::from_nat(pos)); - ctor.set_obj(2, LeanIxSubstring::build(trailing)); - ctor.set_obj(3, LeanNat::from_nat(end_pos)); - ctor - }, - // | synthetic (pos : Nat) (endPos : Nat) (canonical : Bool) -- tag 1 - SourceInfo::Synthetic(pos, end_pos, canonical) => { - let ctor = LeanIxSourceInfo::alloc(1); - ctor.set_obj(0, LeanNat::from_nat(pos)); - ctor.set_obj(1, LeanNat::from_nat(end_pos)); - ctor.set_num_8(0, u8::from(*canonical)); - ctor - }, - // | none -- tag 2 (no fields — scalar) - SourceInfo::None => Self::new(LeanOwned::box_usize(2)), - } - } -} - -impl LeanIxSourceInfo { - /// Decode Ix.SourceInfo. - pub fn decode(&self) -> SourceInfo { - if self.inner().is_scalar() { - return SourceInfo::None; - } - let ctor = self.as_ctor(); - match ctor.tag() { - 0 => { - // original - SourceInfo::Original( - LeanIxSubstring(ctor.get(0)).decode(), - LeanNat::to_nat(&ctor.get(1)), - LeanIxSubstring(ctor.get(2)).decode(), - LeanNat::to_nat(&ctor.get(3)), - ) - }, - 1 => { - // synthetic: 2 obj fields (pos, end_pos), 1 scalar byte (canonical) - let canonical = self.get_num_8(0) != 0; - - SourceInfo::Synthetic( - LeanNat::to_nat(&self.get_obj(0)), - LeanNat::to_nat(&self.get_obj(1)), - canonical, - ) - }, - 2 => SourceInfo::None, - _ => panic!("Invalid SourceInfo tag: {}", ctor.tag()), - } - } -} - -impl LeanIxSyntaxPreresolved { - /// Build a Ix.SyntaxPreresolved. - pub fn build(cache: &mut LeanBuildCache, sp: &SyntaxPreresolved) -> Self { - match sp { - // | namespace (name : Name) -- tag 0 - SyntaxPreresolved::Namespace(name) => { - let ctor = LeanIxSyntaxPreresolved::alloc(0); - ctor.set_obj(0, LeanIxName::build(cache, name)); - ctor - }, - // | decl (name : Name) (aliases : Array String) -- tag 1 - SyntaxPreresolved::Decl(name, aliases) => { - let ctor = LeanIxSyntaxPreresolved::alloc(1); - ctor.set_obj(0, LeanIxName::build(cache, name)); - ctor.set_obj(1, build_string_array(aliases)); - ctor - }, - } - } -} - -impl LeanIxSyntaxPreresolved { - /// Decode Ix.SyntaxPreresolved. - pub fn decode(&self) -> SyntaxPreresolved { - let ctor = self.as_ctor(); - match ctor.tag() { - 0 => { - // namespace - SyntaxPreresolved::Namespace(LeanIxName(ctor.get(0)).decode()) - }, - 1 => { - // decl - let name = LeanIxName(ctor.get(0)).decode(); - let aliases: Vec = - ctor.get(1).as_array().map(|obj| obj.as_string().to_string()); - - SyntaxPreresolved::Decl(name, aliases) - }, - _ => panic!("Invalid SyntaxPreresolved tag: {}", ctor.tag()), - } - } -} - -/// Build an Array of Strings. -pub fn build_string_array(strings: &[String]) -> LeanArray { - let arr = LeanArray::alloc(strings.len()); - for (i, s) in strings.iter().enumerate() { - arr.set(i, LeanString::from_bytes(s.as_bytes())); - } - arr -} - -impl LeanIxSyntax { - /// Build a Ix.Syntax. - pub fn build(cache: &mut LeanBuildCache, syn: &Syntax) -> Self { - match syn { - // | missing -- tag 0 (no fields — scalar) - Syntax::Missing => Self::new(LeanOwned::box_usize(0)), - // | node (info : SourceInfo) (kind : Name) (args : Array Syntax) -- tag 1 - Syntax::Node(info, kind, args) => { - let ctor = LeanIxSyntax::alloc(1); - ctor.set_obj(0, LeanIxSourceInfo::build(info)); - ctor.set_obj(1, LeanIxName::build(cache, kind)); - ctor.set_obj(2, Self::build_array(cache, args)); - ctor - }, - // | atom (info : SourceInfo) (val : String) -- tag 2 - Syntax::Atom(info, val) => { - let ctor = LeanIxSyntax::alloc(2); - ctor.set_obj(0, LeanIxSourceInfo::build(info)); - ctor.set_obj(1, LeanString::from_bytes(val.as_bytes())); - ctor - }, - // | ident (info : SourceInfo) (rawVal : Substring) (val : Name) (preresolved : Array SyntaxPreresolved) -- tag 3 - Syntax::Ident(info, raw_val, val, preresolved) => { - let ctor = LeanIxSyntax::alloc(3); - ctor.set_obj(0, LeanIxSourceInfo::build(info)); - ctor.set_obj(1, LeanIxSubstring::build(raw_val)); - ctor.set_obj(2, LeanIxName::build(cache, val)); - ctor.set_obj(3, Self::build_preresolved_array(cache, preresolved)); - ctor - }, - } - } - - /// Build an Array of Syntax. - pub fn build_array( - cache: &mut LeanBuildCache, - items: &[Syntax], - ) -> LeanArray { - let arr = LeanArray::alloc(items.len()); - for (i, item) in items.iter().enumerate() { - arr.set(i, Self::build(cache, item)); - } - arr - } - - /// Build an Array of SyntaxPreresolved. - fn build_preresolved_array( - cache: &mut LeanBuildCache, - items: &[SyntaxPreresolved], - ) -> LeanArray { - let arr = LeanArray::alloc(items.len()); - for (i, item) in items.iter().enumerate() { - arr.set(i, LeanIxSyntaxPreresolved::build(cache, item)); - } - arr - } -} - -impl LeanIxSyntax { - /// Decode Ix.Syntax from a Lean object. - pub fn decode(&self) -> Syntax { - if self.inner().is_scalar() { - return Syntax::Missing; - } - let ctor = self.as_ctor(); - match ctor.tag() { - 0 => Syntax::Missing, - 1 => { - // node: info, kind, args - let info = LeanIxSourceInfo(ctor.get(0)).decode(); - let kind = LeanIxName(ctor.get(1)).decode(); - let args: Vec = - ctor.get(2).as_array().map(|x| LeanIxSyntax(x).decode()); - - Syntax::Node(info, kind, args) - }, - 2 => { - // atom: info, val - let info = LeanIxSourceInfo(ctor.get(0)).decode(); - Syntax::Atom(info, ctor.get(1).as_string().to_string()) - }, - 3 => { - // ident: info, rawVal, val, preresolved - let info = LeanIxSourceInfo(ctor.get(0)).decode(); - let raw_val = LeanIxSubstring(ctor.get(1)).decode(); - let val = LeanIxName(ctor.get(2)).decode(); - let preresolved: Vec = - ctor.get(3).as_array().map(|x| LeanIxSyntaxPreresolved(x).decode()); - - Syntax::Ident(info, raw_val, val, preresolved) - }, - _ => panic!("Invalid Syntax tag: {}", ctor.tag()), - } - } -} - -impl LeanIxDataValue { - /// Build Ix.DataValue. - pub fn build(cache: &mut LeanBuildCache, dv: &DataValue) -> Self { - match dv { - DataValue::OfString(s) => { - let ctor = LeanIxDataValue::alloc(0); - ctor.set_obj(0, LeanString::from_bytes(s.as_bytes())); - ctor - }, - DataValue::OfBool(b) => { - let ctor = LeanIxDataValue::alloc(1); - ctor.set_num_8(0, u8::from(*b)); - ctor - }, - DataValue::OfName(n) => { - let ctor = LeanIxDataValue::alloc(2); - ctor.set_obj(0, LeanIxName::build(cache, n)); - ctor - }, - DataValue::OfNat(n) => { - let ctor = LeanIxDataValue::alloc(3); - ctor.set_obj(0, LeanNat::from_nat(n)); - ctor - }, - DataValue::OfInt(i) => { - let ctor = LeanIxDataValue::alloc(4); - ctor.set_obj(0, LeanIxInt::build(i)); - ctor - }, - DataValue::OfSyntax(syn) => { - let ctor = LeanIxDataValue::alloc(5); - ctor.set_obj(0, LeanIxSyntax::build(cache, syn)); - ctor - }, - } - } - - /// Build an Array of (Name x DataValue) for mdata. - pub fn build_kvmap( - cache: &mut LeanBuildCache, - data: &[(Name, DataValue)], - ) -> LeanArray { - let arr = LeanArray::alloc(data.len()); - for (i, (name, dv)) in data.iter().enumerate() { - let name_obj = LeanIxName::build(cache, name); - let dv_obj = Self::build(cache, dv); - // Prod (Name x DataValue) - let pair = LeanProd::new(name_obj, dv_obj); - arr.set(i, pair); - } - arr - } -} - -impl LeanIxDataValue { - /// Decode Ix.DataValue from a Lean object. - pub fn decode(&self) -> DataValue { - let ctor = self.as_ctor(); - match ctor.tag() { - 0 => { - // ofString: 1 object field - DataValue::OfString(ctor.get(0).as_string().to_string()) - }, - 1 => { - // ofBool: 0 object fields, 1 scalar byte - let b = self.get_num_8(0) != 0; - DataValue::OfBool(b) - }, - 2 => { - // ofName: 1 object field - DataValue::OfName(LeanIxName(ctor.get(0)).decode()) - }, - 3 => { - // ofNat: 1 object field - DataValue::OfNat(LeanNat::to_nat(&ctor.get(0))) - }, - 4 => { - // ofInt: 1 object field - let inner = ctor.get(0); - let inner_ctor = inner.as_ctor(); - let nat = LeanNat::to_nat(&inner_ctor.get(0)); - match inner_ctor.tag() { - 0 => DataValue::OfInt(Int::OfNat(nat)), - 1 => DataValue::OfInt(Int::NegSucc(nat)), - _ => panic!("Invalid Int tag: {}", inner_ctor.tag()), - } - }, - 5 => { - // ofSyntax: 1 object field - DataValue::OfSyntax(LeanIxSyntax(ctor.get(0)).decode().into()) - }, - _ => panic!("Invalid DataValue tag: {}", ctor.tag()), - } - } -} - -// ============================================================================= -// FFI Exports -// ============================================================================= - -/// Round-trip an Ix.Int: decode from Lean, re-encode. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_int( - int_ptr: LeanIxInt>, -) -> LeanIxInt { - let int_val = int_ptr.decode(); - LeanIxInt::build(&int_val) -} - -/// Round-trip an Ix.Substring: decode from Lean, re-encode. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_substring( - sub_ptr: LeanIxSubstring>, -) -> LeanIxSubstring { - let sub = sub_ptr.decode(); - LeanIxSubstring::build(&sub) -} - -/// Round-trip an Ix.SourceInfo: decode from Lean, re-encode. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_source_info( - si_ptr: LeanIxSourceInfo>, -) -> LeanIxSourceInfo { - let si = si_ptr.decode(); - LeanIxSourceInfo::build(&si) -} - -/// Round-trip an Ix.SyntaxPreresolved: decode from Lean, re-encode. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_syntax_preresolved( - sp_ptr: LeanIxSyntaxPreresolved>, -) -> LeanIxSyntaxPreresolved { - let sp = sp_ptr.decode(); - let mut cache = LeanBuildCache::new(); - LeanIxSyntaxPreresolved::build(&mut cache, &sp) -} - -/// Round-trip an Ix.Syntax: decode from Lean, re-encode. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_syntax( - syn_ptr: LeanIxSyntax>, -) -> LeanIxSyntax { - let syn = syn_ptr.decode(); - let mut cache = LeanBuildCache::new(); - LeanIxSyntax::build(&mut cache, &syn) -} - -/// Round-trip an Ix.DataValue: decode from Lean, re-encode. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_data_value( - dv_ptr: LeanIxDataValue>, -) -> LeanIxDataValue { - let dv = dv_ptr.decode(); - let mut cache = LeanBuildCache::new(); - LeanIxDataValue::build(&mut cache, &dv) -} diff --git a/crates/ffi/src/ix/env.rs b/crates/ffi/src/ix/env.rs deleted file mode 100644 index ccaef3dc6..000000000 --- a/crates/ffi/src/ix/env.rs +++ /dev/null @@ -1,260 +0,0 @@ -//! Ix.Environment build/decode/roundtrip FFI. - -use rustc_hash::FxHashMap; - -use crate::lean::{ - LeanIxConstantInfo, LeanIxEnvironment, LeanIxName, LeanIxRawEnvironment, -}; -use ix_common::env::{ConstantInfo, Name}; -use lean_ffi::object::{ - LeanArray, LeanBorrowed, LeanCtor, LeanOwned, LeanProd, LeanRef, -}; - -use crate::builder::LeanBuildCache; - -// ============================================================================= -// HashMap Building -// ============================================================================= - -/// Build a Lean HashMap from pre-built key-value pairs. -/// -/// Lean's Std.HashMap structure (with unboxing): -/// - HashMap α β unboxes through DHashMap to Raw -/// - Raw = { size : Nat, buckets : Array (AssocList α β) } -/// - Field 0 = size (Nat), Field 1 = buckets (Array) -/// -/// AssocList α β = nil | cons (key : α) (value : β) (tail : AssocList α β) -pub fn build_hashmap_from_pairs( - pairs: Vec<(LeanOwned, LeanOwned, u64)>, // (key_obj, val_obj, hash) -) -> LeanOwned { - let size = pairs.len(); - let bucket_count = (size * 4 / 3 + 1).next_power_of_two().max(8); - - // Create array of AssocLists (initially all nil = boxed 0) - let buckets = LeanArray::alloc(bucket_count); - let nil = LeanOwned::box_usize(0); - for i in 0..bucket_count { - buckets.set(i, nil.clone()); // nil - } - - // Insert entries - for (key_obj, val_obj, hash) in pairs { - let bucket_idx = - usize::try_from(hash).expect("hash overflows usize") % bucket_count; - - // Get current bucket (AssocList) - let current_tail = buckets.get(bucket_idx).to_owned_ref(); - - // cons (key : α) (value : β) (tail : AssocList α β) -- tag 1 - let cons = LeanCtor::alloc(1, 3, 0); - cons.set(0, key_obj); - cons.set(1, val_obj); - cons.set(2, current_tail); - - buckets.set(bucket_idx, cons); - } - - // Build Raw { size : Nat, buckets : Array } - // Due to unboxing, this IS the HashMap directly - // Field 0 = size, Field 1 = buckets (2 object fields, no scalars) - let size_obj = LeanOwned::box_usize(size); - - let raw = LeanCtor::alloc(0, 2, 0); - raw.set(0, size_obj); - raw.set(1, buckets); - raw.into() -} - -// ============================================================================= -// Environment Building / Decoding -// ============================================================================= - -/// Decode a HashMap's AssocList and collect key-value pairs using a custom decoder. -fn decode_assoc_list( - obj: LeanBorrowed<'_>, - decode_key: FK, - decode_val: FV, -) -> Vec<(K, V)> -where - FK: Fn(LeanBorrowed<'_>) -> K, - FV: Fn(LeanBorrowed<'_>) -> V, -{ - let mut result = Vec::new(); - let mut current = obj; - - while !current.is_scalar() { - let ctor = current.as_ctor(); - if ctor.tag() == 0 { - break; // AssocList.nil - } - // AssocList.cons: 3 fields (key, value, tail) - let key = ctor.get(0); - let val = ctor.get(1); - let next = ctor.get(2).as_raw(); - result.push((decode_key(key), decode_val(val))); - current = unsafe { LeanBorrowed::from_raw(next) }; - } - - result -} - -/// Decode a Lean HashMap into a Vec of key-value pairs. -/// HashMap structure (after unboxing): Raw { size : Nat, buckets : Array (AssocList α β) } -/// -/// Due to single-field struct unboxing: -/// - HashMap { inner : DHashMap } unboxes to DHashMap -/// - DHashMap { inner : Raw, wf : Prop } unboxes to Raw (Prop is erased) -/// - Raw { size : Nat, buckets : Array } - field 0 = size, field 1 = buckets -fn decode_hashmap( - obj: LeanBorrowed<'_>, - decode_key: FK, - decode_val: FV, -) -> Vec<(K, V)> -where - FK: Fn(LeanBorrowed<'_>) -> K + Copy, - FV: Fn(LeanBorrowed<'_>) -> V + Copy, -{ - let ctor = obj.as_ctor(); - // Raw layout: field 0 = size (Nat), field 1 = buckets (Array) - let _size = ctor.get(0); // unused but needed for layout - let buckets = ctor.get(1).as_array(); - - let mut pairs = Vec::new(); - for bucket in buckets.iter() { - let bucket_pairs = decode_assoc_list(bucket, decode_key, decode_val); - pairs.extend(bucket_pairs); - } - - pairs -} - -impl LeanIxRawEnvironment { - /// Build a Ix.RawEnvironment from collected caches. - /// RawEnvironment has arrays that Lean will convert to HashMaps. - /// - /// Ix.RawEnvironment = { - /// consts : Array (Name × ConstantInfo) - /// } - /// - /// NOTE: RawEnvironment with a single field is UNBOXED by Lean, - /// so we return just the array, not a structure containing it. - pub fn build( - cache: &mut LeanBuildCache, - consts: &ix_common::env::Env, - ) -> Self { - // Build consts array: Array (Name × ConstantInfo) - let consts_arr = LeanArray::alloc(consts.len()); - for (i, entry) in consts.iter().enumerate() { - let (name, info) = entry; - let key_obj = LeanIxName::build(cache, name); - let val_obj = LeanIxConstantInfo::build(cache, &info); - // Build pair (Name × ConstantInfo) - let pair = LeanProd::new(key_obj, val_obj); - consts_arr.set(i, pair); - } - - Self::new(consts_arr.into()) - } - - /// Build Ix.RawEnvironment from Vec, preserving order and duplicates. - pub fn build_from_vec( - cache: &mut LeanBuildCache, - consts: &[(Name, ConstantInfo)], - ) -> Self { - let consts_arr = LeanArray::alloc(consts.len()); - for (i, (name, info)) in consts.iter().enumerate() { - let key_obj = LeanIxName::build(cache, name); - let val_obj = LeanIxConstantInfo::build(cache, info); - let pair = LeanProd::new(key_obj, val_obj); - consts_arr.set(i, pair); - } - Self::new(consts_arr.into()) - } -} - -impl LeanIxRawEnvironment { - /// RawEnvironment is a single-field struct, unboxed to just Array by Lean. - fn as_array(&self) -> LeanArray> { - unsafe { LeanBorrowed::from_raw(self.as_raw()) }.as_array() - } - - /// Decode Ix.RawEnvironment from Lean object into HashMap. - /// RawEnvironment = { consts : Array (Name × ConstantInfo) } - /// NOTE: Unboxed to just Array. This version deduplicates by name. - pub fn decode(&self) -> FxHashMap { - let arr = self.as_array(); - let mut consts: FxHashMap = FxHashMap::default(); - for pair_obj in arr.iter() { - let pair = pair_obj.as_ctor(); - let name = LeanIxName(pair.get(0)).decode(); - let info = LeanIxConstantInfo(pair.get(1)).decode(); - consts.insert(name, info); - } - consts - } - - /// Decode Ix.RawEnvironment preserving array structure (including duplicates). - pub fn decode_to_vec(&self) -> Vec<(Name, ConstantInfo)> { - let arr = self.as_array(); - let mut consts = Vec::with_capacity(arr.len()); - for pair_obj in arr.iter() { - let pair = pair_obj.as_ctor(); - let name = LeanIxName(pair.get(0)).decode(); - let info = LeanIxConstantInfo(pair.get(1)).decode(); - consts.push((name, info)); - } - consts - } -} - -impl LeanIxEnvironment { - /// Decode Ix.Environment from Lean object. - /// - /// Ix.Environment = { - /// consts : HashMap Name ConstantInfo - /// } - /// - /// NOTE: Environment with a single field is UNBOXED by Lean, - /// so the pointer IS the HashMap directly, not a structure containing it. - pub fn decode(&self) -> FxHashMap { - let borrowed = unsafe { LeanBorrowed::from_raw(self.as_raw()) }; - let consts_pairs = decode_hashmap( - borrowed, - |x| LeanIxName::new(x.to_owned_ref()).decode(), - |x| LeanIxConstantInfo::new(x.to_owned_ref()).decode(), - ); - let mut consts: FxHashMap = FxHashMap::default(); - for (name, info) in consts_pairs { - consts.insert(name, info); - } - consts - } -} - -// ============================================================================= -// FFI Exports -// ============================================================================= - -/// Round-trip an Ix.Environment: decode from Lean, re-encode. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_environment( - env_ptr: LeanIxEnvironment>, -) -> LeanIxRawEnvironment { - let decoded = env_ptr.decode(); - let env: ix_common::env::Env = decoded.into_iter().collect(); - let mut cache = LeanBuildCache::with_capacity(env.len()); - LeanIxRawEnvironment::build(&mut cache, &env) -} - -/// Round-trip an Ix.RawEnvironment: decode from Lean, re-encode. -/// Uses Vec-preserving functions to maintain array structure and order. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_raw_environment( - env_ptr: LeanIxRawEnvironment>, -) -> LeanIxRawEnvironment { - let env = env_ptr.decode_to_vec(); - let mut cache = LeanBuildCache::with_capacity(env.len()); - LeanIxRawEnvironment::build_from_vec(&mut cache, &env) -} diff --git a/crates/ffi/src/ix/expr.rs b/crates/ffi/src/ix/expr.rs deleted file mode 100644 index 1d80f65a5..000000000 --- a/crates/ffi/src/ix/expr.rs +++ /dev/null @@ -1,336 +0,0 @@ -//! Ix.Expr build/decode/roundtrip FFI. -//! -//! Ix.Expr layout (12 constructors): -//! - Tag 0: bvar (idx : Nat) (hash : Address) -//! - Tag 1: fvar (name : Name) (hash : Address) -//! - Tag 2: mvar (name : Name) (hash : Address) -//! - Tag 3: sort (level : Level) (hash : Address) -//! - Tag 4: const (name : Name) (levels : Array Level) (hash : Address) -//! - Tag 5: app (fn arg : Expr) (hash : Address) -//! - Tag 6: lam (name : Name) (ty body : Expr) (bi : BinderInfo) (hash : Address) -//! - Tag 7: forallE (name : Name) (ty body : Expr) (bi : BinderInfo) (hash : Address) -//! - Tag 8: letE (name : Name) (ty val body : Expr) (nonDep : Bool) (hash : Address) -//! - Tag 9: lit (l : Literal) (hash : Address) -//! - Tag 10: mdata (data : Array (Name × DataValue)) (expr : Expr) (hash : Address) -//! - Tag 11: proj (typeName : Name) (idx : Nat) (struct : Expr) (hash : Address) - -use crate::builder::LeanBuildCache; -use crate::lean::LeanIxAddress; -use crate::lean::{ - LeanIxBinderInfo, LeanIxDataValue, LeanIxExpr, LeanIxLevel, LeanIxLiteral, - LeanIxName, -}; -use ix_common::env::{ - BinderInfo, DataValue, Expr, ExprData, Level, Literal, Name, -}; -#[cfg(feature = "test-ffi")] -use lean_ffi::object::LeanBorrowed; -use lean_ffi::object::LeanNat; -use lean_ffi::object::{LeanOwned, LeanRef, LeanString}; - -impl LeanIxExpr { - /// Build a Lean Ix.Expr with embedded hash. - /// Uses caching to avoid rebuilding the same expression. - pub fn build(cache: &mut LeanBuildCache, expr: &Expr) -> Self { - let hash = *expr.get_hash(); - if let Some(cached) = cache.exprs.get(&hash) { - return cached.clone(); - } - - let result = match expr.as_data() { - ExprData::Bvar(idx, h) => { - let ctor = LeanIxExpr::alloc(0); - ctor.set_obj(0, LeanNat::from_nat(idx)); - ctor.set_obj(1, LeanIxAddress::build_from_hash(h)); - ctor - }, - ExprData::Fvar(name, h) => { - let ctor = LeanIxExpr::alloc(1); - ctor.set_obj(0, LeanIxName::build(cache, name)); - ctor.set_obj(1, LeanIxAddress::build_from_hash(h)); - ctor - }, - ExprData::Mvar(name, h) => { - let ctor = LeanIxExpr::alloc(2); - ctor.set_obj(0, LeanIxName::build(cache, name)); - ctor.set_obj(1, LeanIxAddress::build_from_hash(h)); - ctor - }, - ExprData::Sort(level, h) => { - let ctor = LeanIxExpr::alloc(3); - ctor.set_obj(0, LeanIxLevel::build(cache, level)); - ctor.set_obj(1, LeanIxAddress::build_from_hash(h)); - ctor - }, - ExprData::Const(name, levels, h) => { - let name_obj = LeanIxName::build(cache, name); - let levels_obj = LeanIxLevel::build_array(cache, levels); - let ctor = LeanIxExpr::alloc(4); - ctor.set_obj(0, name_obj); - ctor.set_obj(1, levels_obj); - ctor.set_obj(2, LeanIxAddress::build_from_hash(h)); - ctor - }, - ExprData::App(fn_expr, arg_expr, h) => { - let fn_obj = Self::build(cache, fn_expr); - let arg_obj = Self::build(cache, arg_expr); - let ctor = LeanIxExpr::alloc(5); - ctor.set_obj(0, fn_obj); - ctor.set_obj(1, arg_obj); - ctor.set_obj(2, LeanIxAddress::build_from_hash(h)); - ctor - }, - ExprData::Lam(name, ty, body, bi, h) => { - let name_obj = LeanIxName::build(cache, name); - let ty_obj = Self::build(cache, ty); - let body_obj = Self::build(cache, body); - let hash_obj = LeanIxAddress::build_from_hash(h); - let ctor = LeanIxExpr::alloc(6); - ctor.set_obj(0, name_obj); - ctor.set_obj(1, ty_obj); - ctor.set_obj(2, body_obj); - ctor.set_obj(3, hash_obj); - ctor.set_num_8(0, LeanIxBinderInfo::::to_u8(bi)); - ctor - }, - ExprData::ForallE(name, ty, body, bi, h) => { - let name_obj = LeanIxName::build(cache, name); - let ty_obj = Self::build(cache, ty); - let body_obj = Self::build(cache, body); - let hash_obj = LeanIxAddress::build_from_hash(h); - let ctor = LeanIxExpr::alloc(7); - ctor.set_obj(0, name_obj); - ctor.set_obj(1, ty_obj); - ctor.set_obj(2, body_obj); - ctor.set_obj(3, hash_obj); - ctor.set_num_8(0, LeanIxBinderInfo::::to_u8(bi)); - ctor - }, - ExprData::LetE(name, ty, val, body, non_dep, h) => { - let name_obj = LeanIxName::build(cache, name); - let ty_obj = Self::build(cache, ty); - let val_obj = Self::build(cache, val); - let body_obj = Self::build(cache, body); - let hash_obj = LeanIxAddress::build_from_hash(h); - let ctor = LeanIxExpr::alloc(8); - ctor.set_obj(0, name_obj); - ctor.set_obj(1, ty_obj); - ctor.set_obj(2, val_obj); - ctor.set_obj(3, body_obj); - ctor.set_obj(4, hash_obj); - ctor.set_num_8(0, u8::from(*non_dep)); - ctor - }, - ExprData::Lit(lit, h) => { - let lit_obj = LeanIxLiteral::build(lit); - let ctor = LeanIxExpr::alloc(9); - ctor.set_obj(0, lit_obj); - ctor.set_obj(1, LeanIxAddress::build_from_hash(h)); - ctor - }, - ExprData::Mdata(md, inner, h) => { - let md_obj = LeanIxDataValue::build_kvmap(cache, md); - let inner_obj = Self::build(cache, inner); - let ctor = LeanIxExpr::alloc(10); - ctor.set_obj(0, md_obj); - ctor.set_obj(1, inner_obj); - ctor.set_obj(2, LeanIxAddress::build_from_hash(h)); - ctor - }, - ExprData::Proj(type_name, idx, struct_expr, h) => { - let name_obj = LeanIxName::build(cache, type_name); - let idx_obj = LeanNat::from_nat(idx); - let struct_obj = Self::build(cache, struct_expr); - let ctor = LeanIxExpr::alloc(11); - ctor.set_obj(0, name_obj); - ctor.set_obj(1, idx_obj); - ctor.set_obj(2, struct_obj); - ctor.set_obj(3, LeanIxAddress::build_from_hash(h)); - ctor - }, - }; - - cache.exprs.insert(hash, result.clone()); - result - } -} - -impl LeanIxExpr { - /// Decode a Lean Ix.Expr to Rust Expr. - pub fn decode(&self) -> Expr { - let ctor = self.as_ctor(); - match ctor.tag() { - 0 => { - // bvar - let idx = LeanNat::to_nat(&ctor.get(0)); - Expr::bvar(idx) - }, - 1 => { - // fvar - let name = LeanIxName(ctor.get(0)).decode(); - Expr::fvar(name) - }, - 2 => { - // mvar - let name = LeanIxName(ctor.get(0)).decode(); - Expr::mvar(name) - }, - 3 => { - // sort - let level = LeanIxLevel(ctor.get(0)).decode(); - Expr::sort(level) - }, - 4 => { - // const - let name = LeanIxName(ctor.get(0)).decode(); - let levels: Vec = - ctor.get(1).as_array().map(|x| LeanIxLevel(x).decode()); - - Expr::cnst(name, levels) - }, - 5 => { - // app - let fn_expr = LeanIxExpr(ctor.get(0)).decode(); - let arg_expr = LeanIxExpr(ctor.get(1)).decode(); - Expr::app(fn_expr, arg_expr) - }, - 6 => { - // lam: name, ty, body, hash, bi (scalar) - let name = LeanIxName(self.get_obj(0)).decode(); - let ty = LeanIxExpr(self.get_obj(1)).decode(); - let body = LeanIxExpr(self.get_obj(2)).decode(); - - let bi_byte = self.get_num_8(0); - let bi = LeanIxBinderInfo::::from_u8(bi_byte); - - Expr::lam(name, ty, body, bi) - }, - 7 => { - // forallE: same layout as lam - let name = LeanIxName(self.get_obj(0)).decode(); - let ty = LeanIxExpr(self.get_obj(1)).decode(); - let body = LeanIxExpr(self.get_obj(2)).decode(); - - let bi_byte = self.get_num_8(0); - let bi = LeanIxBinderInfo::::from_u8(bi_byte); - - Expr::all(name, ty, body, bi) - }, - 8 => { - // letE: name, ty, val, body, hash, nonDep (scalar) - let name = LeanIxName(self.get_obj(0)).decode(); - let ty = LeanIxExpr(self.get_obj(1)).decode(); - let val = LeanIxExpr(self.get_obj(2)).decode(); - let body = LeanIxExpr(self.get_obj(3)).decode(); - - let non_dep = self.get_num_8(0) != 0; - - Expr::letE(name, ty, val, body, non_dep) - }, - 9 => { - // lit - let lit = LeanIxLiteral(ctor.get(0)).decode(); - Expr::lit(lit) - }, - 10 => { - // mdata: data, expr, hash - let data: Vec<(Name, DataValue)> = ctor.get(0).as_array().map(|obj| { - let pair = obj.as_ctor(); - let name = LeanIxName(pair.get(0)).decode(); - let dv = LeanIxDataValue(pair.get(1)).decode(); - (name, dv) - }); - - let inner = LeanIxExpr(ctor.get(1)).decode(); - Expr::mdata(data, inner) - }, - 11 => { - // proj: typeName, idx, struct, hash - let type_name = LeanIxName(ctor.get(0)).decode(); - let idx = LeanNat::to_nat(&ctor.get(1)); - let struct_expr = LeanIxExpr(ctor.get(2)).decode(); - - Expr::proj(type_name, idx, struct_expr) - }, - _ => panic!("Invalid Ix.Expr tag: {}", ctor.tag()), - } - } -} - -impl LeanIxLiteral { - /// Build a Literal (natVal or strVal). - pub fn build(lit: &Literal) -> Self { - match lit { - Literal::NatVal(n) => { - let ctor = LeanIxLiteral::alloc(0); - ctor.set_obj(0, LeanNat::from_nat(n)); - ctor - }, - Literal::StrVal(s) => { - // Preserve NUL bytes instead of filtering them through a C string. - let ctor = LeanIxLiteral::alloc(1); - ctor.set_obj(0, LeanString::from_bytes(s.as_bytes())); - ctor - }, - } - } -} - -impl LeanIxLiteral { - /// Decode Lean.Literal from a Lean object. - pub fn decode(&self) -> Literal { - let ctor = self.as_ctor(); - match ctor.tag() { - 0 => { - // natVal - let nat = LeanNat::to_nat(&ctor.get(0)); - Literal::NatVal(nat) - }, - 1 => { - // strVal - Literal::StrVal(ctor.get(0).as_string().to_string()) - }, - _ => panic!("Invalid Literal tag: {}", ctor.tag()), - } - } -} - -impl LeanIxBinderInfo { - /// Build Ix.BinderInfo enum. - /// BinderInfo is a 4-constructor enum with no fields, stored as boxed scalar. - pub fn build(bi: &BinderInfo) -> Self { - Self::new(LeanOwned::box_usize(Self::to_u8(bi) as usize)) - } - - /// Convert BinderInfo to u8 tag. - pub fn to_u8(bi: &BinderInfo) -> u8 { - match bi { - BinderInfo::Default => 0, - BinderInfo::Implicit => 1, - BinderInfo::StrictImplicit => 2, - BinderInfo::InstImplicit => 3, - } - } - - /// Decode BinderInfo from byte. - pub fn from_u8(bi_byte: u8) -> BinderInfo { - match bi_byte { - 0 => BinderInfo::Default, - 1 => BinderInfo::Implicit, - 2 => BinderInfo::StrictImplicit, - 3 => BinderInfo::InstImplicit, - _ => panic!("Invalid BinderInfo: {}", bi_byte), - } - } -} - -/// Round-trip an Ix.Expr: decode from Lean, re-encode via LeanBuildCache. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_expr( - expr_ptr: LeanIxExpr>, -) -> LeanIxExpr { - let expr = expr_ptr.decode(); - let mut cache = LeanBuildCache::new(); - LeanIxExpr::build(&mut cache, &expr) -} diff --git a/crates/ffi/src/ix/level.rs b/crates/ffi/src/ix/level.rs deleted file mode 100644 index fead881f1..000000000 --- a/crates/ffi/src/ix/level.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! Ix.Level build/decode/roundtrip FFI. -//! -//! Ix.Level layout: -//! - Tag 0: zero (hash : Address) -//! - Tag 1: succ (x : Level) (hash : Address) -//! - Tag 2: max (x y : Level) (hash : Address) -//! - Tag 3: imax (x y : Level) (hash : Address) -//! - Tag 4: param (n : Name) (hash : Address) -//! - Tag 5: mvar (n : Name) (hash : Address) - -use crate::lean::{LeanIxLevel, LeanIxName}; -use ix_common::env::{Level, LevelData}; -use lean_ffi::object::{LeanArray, LeanBorrowed, LeanOwned, LeanRef}; - -use crate::builder::LeanBuildCache; -use crate::lean::LeanIxAddress; - -impl LeanIxLevel { - /// Build a Lean Ix.Level with embedded hash. - /// Uses caching to avoid rebuilding the same level. - pub fn build(cache: &mut LeanBuildCache, level: &Level) -> Self { - let hash = *level.get_hash(); - if let Some(cached) = cache.levels.get(&hash) { - return cached.clone(); - } - - let result = match level.as_data() { - LevelData::Zero(h) => { - let ctor = LeanIxLevel::alloc(0); - ctor.set_obj(0, LeanIxAddress::build_from_hash(h)); - ctor - }, - LevelData::Succ(x, h) => { - let x_obj = Self::build(cache, x); - let ctor = LeanIxLevel::alloc(1); - ctor.set_obj(0, x_obj); - ctor.set_obj(1, LeanIxAddress::build_from_hash(h)); - ctor - }, - LevelData::Max(x, y, h) => { - let x_obj = Self::build(cache, x); - let y_obj = Self::build(cache, y); - let ctor = LeanIxLevel::alloc(2); - ctor.set_obj(0, x_obj); - ctor.set_obj(1, y_obj); - ctor.set_obj(2, LeanIxAddress::build_from_hash(h)); - ctor - }, - LevelData::Imax(x, y, h) => { - let x_obj = Self::build(cache, x); - let y_obj = Self::build(cache, y); - let ctor = LeanIxLevel::alloc(3); - ctor.set_obj(0, x_obj); - ctor.set_obj(1, y_obj); - ctor.set_obj(2, LeanIxAddress::build_from_hash(h)); - ctor - }, - LevelData::Param(n, h) => { - let n_obj = LeanIxName::build(cache, n); - let ctor = LeanIxLevel::alloc(4); - ctor.set_obj(0, n_obj); - ctor.set_obj(1, LeanIxAddress::build_from_hash(h)); - ctor - }, - LevelData::Mvar(n, h) => { - let n_obj = LeanIxName::build(cache, n); - let ctor = LeanIxLevel::alloc(5); - ctor.set_obj(0, n_obj); - ctor.set_obj(1, LeanIxAddress::build_from_hash(h)); - ctor - }, - }; - - cache.levels.insert(hash, result.clone()); - result - } - - /// Build an Array of Levels. - pub fn build_array( - cache: &mut LeanBuildCache, - levels: &[Level], - ) -> LeanArray { - let arr = LeanArray::alloc(levels.len()); - for (i, level) in levels.iter().enumerate() { - arr.set(i, Self::build(cache, level)); - } - arr - } -} - -impl LeanIxLevel { - /// Decode a Lean Ix.Level to Rust Level. - pub fn decode(&self) -> Level { - let ctor = self.as_ctor(); - match ctor.tag() { - 0 => Level::zero(), - 1 => { - let x = LeanIxLevel(ctor.get(0)).decode(); - Level::succ(x) - }, - 2 => { - let x = LeanIxLevel(ctor.get(0)).decode(); - let y = LeanIxLevel(ctor.get(1)).decode(); - Level::max(x, y) - }, - 3 => { - let x = LeanIxLevel(ctor.get(0)).decode(); - let y = LeanIxLevel(ctor.get(1)).decode(); - Level::imax(x, y) - }, - 4 => { - let n = LeanIxName(ctor.get(0)).decode(); - Level::param(n) - }, - 5 => { - let n = LeanIxName(ctor.get(0)).decode(); - Level::mvar(n) - }, - _ => panic!("Invalid Ix.Level tag: {}", ctor.tag()), - } - } -} - -impl LeanIxLevel> { - /// Decode Array of Levels from Lean pointer. - pub fn decode_array(obj: LeanArray>) -> Vec { - obj.map(|x| LeanIxLevel(x).decode()) - } -} - -/// Round-trip an Ix.Level: decode from Lean, re-encode via LeanBuildCache. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_level( - level_ptr: LeanIxLevel>, -) -> LeanIxLevel { - let level = level_ptr.decode(); - let mut cache = LeanBuildCache::new(); - LeanIxLevel::build(&mut cache, &level) -} diff --git a/crates/ffi/src/ix/name.rs b/crates/ffi/src/ix/name.rs deleted file mode 100644 index 346698a2c..000000000 --- a/crates/ffi/src/ix/name.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! Ix.Name build/decode/roundtrip FFI. -//! -//! Ix.Name layout: -//! - Tag 0: anonymous (hash : Address) -//! - Tag 1: str (parent : Name) (s : String) (hash : Address) -//! - Tag 2: num (parent : Name) (i : Nat) (hash : Address) - -use crate::lean::LeanIxName; -use ix_common::env::{Name, NameData}; -use lean_ffi::object::LeanNat; -use lean_ffi::object::{ - LeanArray, LeanBorrowed, LeanOwned, LeanRef, LeanString, -}; - -use crate::builder::LeanBuildCache; -use crate::lean::LeanIxAddress; - -impl LeanIxName { - /// Build a Lean Ix.Name with embedded hash. - /// Uses caching to avoid rebuilding the same name. - pub fn build(cache: &mut LeanBuildCache, name: &Name) -> Self { - let hash = name.get_hash(); - if let Some(cached) = cache.names.get(hash) { - return cached.clone(); - } - - let result = match name.as_data() { - NameData::Anonymous(h) => { - let ctor = LeanIxName::alloc(0); - ctor.set_obj(0, LeanIxAddress::build_from_hash(h)); - ctor - }, - NameData::Str(parent, s, h) => { - let parent_obj = Self::build(cache, parent); - let s_obj = LeanString::from_bytes(s.as_bytes()); - let ctor = LeanIxName::alloc(1); - ctor.set_obj(0, parent_obj); - ctor.set_obj(1, s_obj); - ctor.set_obj(2, LeanIxAddress::build_from_hash(h)); - ctor - }, - NameData::Num(parent, n, h) => { - let parent_obj = Self::build(cache, parent); - let n_obj = LeanNat::from_nat(n); - let ctor = LeanIxName::alloc(2); - ctor.set_obj(0, parent_obj); - ctor.set_obj(1, n_obj); - ctor.set_obj(2, LeanIxAddress::build_from_hash(h)); - ctor - }, - }; - - cache.names.insert(*hash, result.clone()); - result - } - - /// Build an Array of Names. - pub fn build_array( - cache: &mut LeanBuildCache, - names: &[Name], - ) -> LeanArray { - let arr = LeanArray::alloc(names.len()); - for (i, name) in names.iter().enumerate() { - arr.set(i, Self::build(cache, name)); - } - arr - } -} - -impl LeanIxName { - /// Decode a Lean Ix.Name to Rust Name. - pub fn decode(&self) -> Name { - let ctor = self.as_ctor(); - match ctor.tag() { - 0 => { - // anonymous: just has hash, construct anon Name - Name::anon() - }, - 1 => { - // str: parent, s, hash - let parent = LeanIxName(ctor.get(0)).decode(); - let s = ctor.get(1).as_string().to_string(); - Name::str(parent, s) - }, - 2 => { - // num: parent, i, hash - let parent = LeanIxName(ctor.get(0)).decode(); - let i = LeanNat::to_nat(&ctor.get(1)); - Name::num(parent, i) - }, - _ => panic!("Invalid Ix.Name tag: {}", ctor.tag()), - } - } -} - -impl LeanIxName> { - /// Decode Array of Names from Lean pointer. - pub fn decode_array(obj: LeanArray>) -> Vec { - obj.map(|x| LeanIxName(x).decode()) - } -} - -/// Round-trip an Ix.Name: decode from Lean, re-encode via LeanBuildCache. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_name( - name_ptr: LeanIxName>, -) -> LeanIxName { - let name = name_ptr.decode(); - let mut cache = LeanBuildCache::new(); - LeanIxName::build(&mut cache, &name) -} diff --git a/crates/ffi/src/kernel.rs b/crates/ffi/src/kernel.rs deleted file mode 100644 index 037015060..000000000 --- a/crates/ffi/src/kernel.rs +++ /dev/null @@ -1,4978 +0,0 @@ -//! Kernel constant checking FFI. -//! -//! Exposes `rs_kernel_check_consts` (production, used by `lake exe ix check` -//! and `Tests/Ix/Kernel/Tutorial.lean`), `rs_kernel_ingress` (production, -//! used by `lake exe ix ingress` for ingress-only performance analysis), -//! plus a pair of test-only roundtrip probes (`rs_kernel_roundtrip` / -//! `rs_kernel_roundtrip_no_compile`). -//! -//! `rs_kernel_check_consts` runs the full pipeline `Lean env → Ixon compile -//! → kernel ingress → typecheck` against a batch of requested constant names. -//! Pipeline: -//! -//! 1. Decode the Lean environment into the Rust `Env` type. -//! 2. Run `compile_env` to obtain the Ixon environment. -//! 3. Run `ixon_ingress::` to ingress into the kernel. -//! 4. For each requested name, construct a `TypeChecker` sharing the -//! `Arc` (so whnf / infer / def_eq caches accumulate across the -//! batch) and call `check_const`. -//! 5. Return a Lean `Array (Option CheckError)` reporting per-name -//! results, where `some (.kernelException msg)` signals a rejection. -//! -//! The `CheckError` ABI (tag 0 = `kernelException`, tag 1 = `compileError`) -//! lives in `Ix/KernelCheck.lean`; see `KERNEL_EXCEPTION_TAG` below. -//! -//! The roundtrip helpers below `rs_kernel_check_consts` are test-only -//! (cfg-gated to `feature = "test-ffi"`) — they import `egress` / -//! `decompile_env` to compare against the original env, which is dead -//! weight in production builds. - -use std::fs::File; -use std::io::Write; -use std::sync::{ - Arc, Mutex, OnceLock, - atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}, -}; -use std::thread; -use std::time::{Duration, Instant}; - -use lean_ffi::include::lean_object; -use lean_ffi::object::LeanNat; -use rustc_hash::FxHashMap; - -use lean_ffi::object::{ - LeanArray, LeanBool, LeanBorrowed, LeanByteArray, LeanExternal, LeanIOResult, - LeanList, LeanOption, LeanOwned, LeanProd, LeanRef, LeanString, -}; - -use crate::lean::LeanIxCheckError; - -#[cfg(feature = "test-ffi")] -use crate::lean_env::{GlobalCache, decode_name}; -use crate::lean_env::{decode_env, decode_name_array}; -use ix_common::address::Address; -use ix_common::env::{Name, NameData, normalize_displayed_name}; -use ix_compile::compile::{ - CompileOptions, CompileState, compile_env_with_options, -}; -#[cfg(feature = "test-ffi")] -use ix_compile::decompile::decompile_env; -#[cfg(feature = "test-ffi")] -use ix_compile::kernel_egress::{ixon_egress, lean_egress}; -use ix_kernel::artifact_checker::ArtifactChecker; -use ix_kernel::error::TcError; -#[cfg(feature = "test-ffi")] -use ix_kernel::id::KId; -use ix_kernel::ingress::{ - IxonIngressLookups, build_ixon_ingress_lookups, ixon_ingress_owned, -}; -#[cfg(feature = "test-ffi")] -use ix_kernel::ingress::{ixon_ingress, lean_ingress}; -use ix_kernel::mode::{CheckDupLevelParams, KernelMode, Meta}; -use ix_kernel::profile::{BlockProfile, OpCounts, ProfileBuilder, ProfileSink}; -use ix_kernel::role_admission::ArtifactSource; -#[cfg(feature = "test-ffi")] -use ix_kernel::tc::TypeChecker; -use ixon::constant::ConstantInfo as IxonCI; -#[cfg(feature = "test-ffi")] -use ixon::constant::MutConst as IxonMutConst; -use ixon::env::Env as IxonEnv; -#[cfg(feature = "test-ffi")] -use ixon::expr::Expr as IxonExpr; -use ixon::metadata::ConstantMetaInfo; - -unsafe extern "C" { - fn lean_name_mk_string( - parent: *mut lean_object, - part: *mut lean_object, - ) -> *mut lean_object; - fn lean_name_mk_numeral( - parent: *mut lean_object, - part: *mut lean_object, - ) -> *mut lean_object; -} - -/// Lean-side `CheckError` constructor tags. -/// -/// Defined in `Ix/KernelCheck.lean`: -/// ```lean -/// inductive CheckError where -/// | kernelException (msg : String) -- tag 0 -/// | compileError (msg : String) -- tag 1 -/// deriving Repr -/// ``` -/// Tags follow Lean's declaration order (top-to-bottom, starting at 0). -/// -/// The second variant exists for two reasons: (1) to disambiguate compile- -/// side rejections from kernel-side rejections at the Lean call site, and -/// (2) to prevent Lean's LCNF "trivial structure" optimization from -/// elididing a single-ctor-single-field inductive into its field type -/// (`hasTrivialStructure?` in `Lean/Compiler/LCNF/MonoTypes.lean`). Without -/// that, the runtime representation of `CheckError` would be identical to -/// `String`, and the heap ctor we allocate here would be read as if it -/// were a string header — `INTERNAL PANIC: out of memory` on decode. -const KERNEL_EXCEPTION_TAG: u8 = 0; -const COMPILE_ERROR_TAG: u8 = 1; - -/// Streaming writer for the `--fail-out` file used by `lake exe ix -/// check`. -/// -/// The previous implementation buffered all failures in Lean and dumped them -/// once at the very end of the run, which meant a long-running full-env -/// check exposed nothing to a `tail -f` observer until the whole batch had -/// completed. Streaming here writes a header up front, appends each failure -/// (one record == one comment-line + one bare-name line + a trailing blank -/// line, matching the format `readNamesFile` understands) as it is detected, -/// and flushes after every record so the file is immediately readable from -/// outside the process. -/// -/// Records are written under a `Mutex` so parallel workers don't -/// interleave bytes — failures are rare enough that the lock contention is -/// negligible, and `File` writes go straight to the kernel page cache so -/// `tail -f` observers see new entries without needing `fsync`. -struct FailureLog { - writer: Mutex, - count: AtomicUsize, -} - -impl FailureLog { - /// Truncate-create the file at `path`, write the comment header (`# env`, - /// `# seeds`), and return a handle ready to record per-failure entries. - fn open(path: &str, env_path: &str, seeds: usize) -> std::io::Result { - let mut file = File::create(path)?; - writeln!(file, "# ix check failures")?; - writeln!(file, "# env: {env_path}")?; - writeln!(file, "# seeds: {seeds}")?; - writeln!(file)?; - file.flush()?; - Ok(Self { writer: Mutex::new(file), count: AtomicUsize::new(0) }) - } - - /// Append a single failure record. `name_pretty` is the dot-separated form - /// of the constant; `msg` is the raw error string (newlines collapsed to - /// ` | ` to keep each comment on one line). - fn record(&self, name_pretty: &str, msg: &str) { - let one_line = msg.replace('\n', " | "); - let mut file = self.writer.lock().unwrap(); - let _ = writeln!(file, "# {one_line}"); - let _ = writeln!(file, "{name_pretty}"); - let _ = writeln!(file); - let _ = file.flush(); - self.count.fetch_add(1, Ordering::Relaxed); - } - - /// Append the trailing `# total failures: N` summary. Called once after - /// all per-constant checks have reported. - fn finalize(&self) { - let mut file = self.writer.lock().unwrap(); - let _ = writeln!( - file, - "# total failures: {}", - self.count.load(Ordering::Relaxed) - ); - let _ = file.flush(); - } - - fn count(&self) -> usize { - self.count.load(Ordering::Relaxed) - } -} - -/// FFI: type-check a batch of constants through the full pipeline. -/// -/// Lean signature: -/// ```lean -/// @[extern "rs_kernel_check_consts"] -/// opaque rsCheckConstsFFI : -/// @& List (Lean.Name × Lean.ConstantInfo) → -/// @& Array Lean.Name → -/// @& Array Bool → -/// @& Bool → -/// IO (Array (Option CheckError)) -/// ``` -/// -/// Results come back in input order — the caller pairs each with its -/// `names[i]`. This was previously `Array (String × Option CheckError)` -/// with the Lean side round-tripping names through `Name.toString` (which -/// adds `«»` escaping for non-identifier components) and Rust reparsing -/// them back into a `Name`. That round-trip was brittle: Lean's escaped -/// `Lean.Order.«term_⊑_»` didn't match the kernel's unescaped -/// `Lean.Order.term_⊑_` key and logged `? not found`. Structural pass- -/// through via `decode_name_array` is the canonical form. -/// -/// `expect_pass[i]` is a hint: `true` means "good" (checker expected to -/// accept), `false` means "bad" (checker expected to reject). It only -/// influences per-constant progress logging; the actual pass/fail logic -/// lives on the Lean side. -/// -/// `quiet` toggles the progress-output style: -/// - `false` (verbose): every constant is printed with its elapsed time, -/// matching the original line-per-constant behaviour. -/// - `true` (ephemeral): the current `[i/N] name ...` label is written -/// over itself each iteration, and *only* slow constants (>=7s by default), -/// unexpected passes/failures, not-found names, and ungrounded compile -/// failures are promoted to persistent lines. Suitable for full-env -/// runs where the vast majority of constants are expected to pass -/// quickly. -/// -/// Parallel quiet-mode progress is persistent and compiler-like: periodic -/// `done/total`, rate, ETA, and oldest in-flight constants. Useful knobs: -/// `IX_KERNEL_CHECK_PROGRESS_MS`, `IX_KERNEL_CHECK_SLOW_MS`, -/// `IX_KERNEL_CHECK_ACTIVE_SLOW_MS`, `IX_KERNEL_CHECK_INFLIGHT`, and -/// `IX_KERNEL_CHECK_NAME_CHARS`. -#[unsafe(no_mangle)] -pub extern "C" fn rs_kernel_check_consts( - env_consts: LeanList>, - names: LeanArray>, - expect_pass: LeanArray>, - quiet: LeanBool>, -) -> LeanIOResult { - let total_start = Instant::now(); - let quiet = quiet.to_bool(); - - // --------------------------------------------------------------------- - // Decode inputs - // --------------------------------------------------------------------- - let t0 = Instant::now(); - let rust_env = decode_env(env_consts); - // Decode names structurally — no `Name.toString` / `parse_name` dance. - // The resulting `Name`s are byte-for-byte the same as the kernel's - // stored names (same component strings, same content hash). - let names_vec: Vec = decode_name_array(&names); - // `Array Bool` elements are boxed tagged scalars: - // `lean_box(n) = (n << 1) | 1`, so `Bool.false` has raw value 1 and - // `Bool.true` has raw value 3. `unbox_usize()` (= `as_raw() >> 1`) - // recovers the ctor tag (0 = false, 1 = true). - let expect_pass_vec: Vec = - expect_pass.map(|b| b.unbox_usize() == 1).into_iter().collect(); - eprintln!("[rs_kernel_check] read env: {:>8.1?}", t0.elapsed()); - - // --------------------------------------------------------------------- - // Compile Lean → Ixon - // --------------------------------------------------------------------- - let t1 = Instant::now(); - let rust_env_arc = Arc::new(rust_env); - let compile_state = - match compile_env_with_options(&rust_env_arc, CompileOptions::default()) { - Ok(s) => s, - Err(e) => { - return build_uniform_error( - names_vec.len(), - &format!("[compile] {e:?}"), - ); - }, - }; - eprintln!("[rs_kernel_check] compile: {:>8.1?}", t1.elapsed()); - - let CompileState { env: ixon_env, ungrounded: compile_ungrounded, .. } = - compile_state; - - // Snapshot per-constant compile failures (ill-formed inductives, - // cascading MissingConstant, etc.) keyed by `Name` so the check loop - // can skip the kernel and report them as compile-side rejections. - // `compile_env` no longer aborts on per-block failure; it populates - // `CompileState.ungrounded` and continues, letting good constants still - // compile cleanly. - let ungrounded: FxHashMap = compile_ungrounded - .iter() - .map(|e| (e.key().clone(), e.value().clone())) - .collect(); - drop(compile_ungrounded); - drop(rust_env_arc); - if !ungrounded.is_empty() { - eprintln!( - "[rs_kernel_check] {} constants failed to compile (will report as rejected without kernel check):", - ungrounded.len() - ); - // Sort for deterministic output — `FxHashMap` iteration order is - // platform-defined. Sort by pretty-form once up front rather than in - // the comparator to avoid repeated `format!` allocations. - let mut ordered: Vec<(String, &String)> = - ungrounded.iter().map(|(k, v)| (k.pretty(), v)).collect(); - ordered.sort_by(|a, b| a.0.cmp(&b.0)); - for (name, msg) in &ordered { - // `msg` from `compile_env` can be multi-line; collapse internal - // newlines so each constant occupies one log line. - let flat = msg.replace('\n', " "); - eprintln!(" [ungrounded] {name}: {flat}"); - } - } - - // --------------------------------------------------------------------- - // Prepare read-only Ixon lookups. Kernel ingress happens on demand inside - // each worker's private KEnv, so there is no shared typecheck cache. - // --------------------------------------------------------------------- - let t2 = Instant::now(); - let ixon_env = Arc::new(ixon_env); - let lookups = Arc::new(build_ixon_ingress_lookups(&ixon_env)); - eprintln!( - "[rs_kernel_check] ingress prep:{:>8.1?} ({} named)", - t2.elapsed(), - ixon_env.named_count() - ); - let total = names_vec.len(); - let t3 = Instant::now(); - - // --------------------------------------------------------------------- - // Per-constant checking on a 256 MB stack - // --------------------------------------------------------------------- - // Deep recursor expansions push the Rust stack. A dedicated thread with a - // large stack matches the old ix_old pattern. - let results = match run_checks_on_large_stack::( - Arc::clone(&ixon_env), - lookups, - names_vec.clone(), - expect_pass_vec, - ungrounded, - quiet, - None, - ) { - Ok(r) => r, - Err(msg) => { - return build_uniform_error(names_vec.len(), &format!("[thread] {msg}")); - }, - }; - - let passed = results.iter().filter(|r| r.is_ok()).count(); - let failed = results.iter().filter(|r| r.is_err()).count(); - eprintln!( - "[rs_kernel_check] {passed}/{total} passed, {failed} failed ({:.1?})", - t3.elapsed() - ); - eprintln!("[rs_kernel_check] total: {:>8.1?}", total_start.elapsed()); - - build_result_array(&results) -} - -/// Test-only FFI: compile a Lean fixture to Ixon, deliberately corrupt one -/// recursor rule in the compiled Ixon payload, then check that exact malformed -/// Ixon with the kernel. -/// -/// This is intentionally separate from `rs_kernel_check_consts`: mutating -/// the compiled payload, rather than the Lean source, gives the tutorial -/// suite a precise "reject this stored recursor payload" regression hook. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_kernel_check_malformed_rec_rule_ixon( - env_consts: LeanList>, - rec_name_obj: LeanBorrowed<'_>, -) -> LeanIOResult { - let t0 = Instant::now(); - let rust_env = decode_env(env_consts); - let global = GlobalCache::default(); - let rec_name = decode_name(rec_name_obj, &global); - eprintln!( - "[rs_kernel_check_malformed_rec_rule_ixon] read env: {:>8.1?}", - t0.elapsed() - ); - - let t1 = Instant::now(); - let rust_env_arc = Arc::new(rust_env); - let compile_state = - match compile_env_with_options(&rust_env_arc, CompileOptions::default()) { - Ok(s) => s, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_check_malformed_rec_rule_ixon: compile failed: {e:?}" - )); - }, - }; - eprintln!( - "[rs_kernel_check_malformed_rec_rule_ixon] compile: {:>8.1?}", - t1.elapsed() - ); - - let CompileState { env: ixon_env, ungrounded, .. } = compile_state; - if let Some(msg) = ungrounded.get(&rec_name).map(|m| m.clone()) { - drop(ungrounded); - drop(rust_env_arc); - return LeanIOResult::ok(build_option_result(&Err(( - ErrKind::Compile, - msg, - )))); - } - drop(ungrounded); - drop(rust_env_arc); - - let rec_addr = - match poison_second_rec_rule_returns_first_minor(&ixon_env, &rec_name) { - Ok(addr) => addr, - Err(msg) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_check_malformed_rec_rule_ixon: {msg}" - )); - }, - }; - - let t2 = Instant::now(); - let (mut kenv, intern) = match ixon_ingress_owned::(ixon_env) { - Ok(v) => v, - Err(msg) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_check_malformed_rec_rule_ixon: ingress failed: {msg}" - )); - }, - }; - kenv.intern = intern; - eprintln!( - "[rs_kernel_check_malformed_rec_rule_ixon] ingress: {:>8.1?}", - t2.elapsed() - ); - - let kid = KId::new(rec_addr, rec_name); - let result = { - let mut tc = TypeChecker::new(&mut kenv); - match tc.check_const(&kid) { - Ok(()) => Ok(()), - Err(e) => Err((ErrKind::Kernel, e.to_string())), - } - }; - LeanIOResult::ok(build_option_result(&result)) -} - -#[cfg(feature = "test-ffi")] -fn poison_second_rec_rule_returns_first_minor( - ixon_env: &IxonEnv, - rec_name: &Name, -) -> Result { - let named = ixon_env - .lookup_name(rec_name) - .ok_or_else(|| format!("{}: missing Named entry", rec_name.pretty()))?; - let rec_addr = named.addr.clone(); - // Materialize then clone out of the Arc — we mutate the constant - // in-place to poison a recursor rule before storing it back. - let rec_arc = ixon_env.get_const(&rec_addr).ok_or_else(|| { - format!("{}: missing constant {}", rec_name.pretty(), rec_addr.hex()) - })?; - let mut rec_constant: ixon::constant::Constant = (*rec_arc).clone(); - drop(rec_arc); - - // The stores below must use `store_const_demoted(.., false)`: - // `store_const` under `DEMOTE` treats a re-store of an existing address - // as a no-op (content addressing assumes identical bytes), and this - // helper deliberately stores *different* bytes at the original address. - match &mut rec_constant.info { - IxonCI::Recr(rec) => { - poison_recursor_rule_payload(rec)?; - ixon_env.store_const_demoted(rec_addr.clone(), rec_constant, false); - Ok(rec_addr) - }, - IxonCI::Muts(members) => { - let mut found = false; - for member in members.iter_mut() { - if let IxonMutConst::Recr(rec) = member { - poison_recursor_rule_payload(rec)?; - found = true; - break; - } - } - if !found { - return Err(format!( - "{}: directly named Muts block contains no recursor member", - rec_name.pretty() - )); - } - ixon_env.store_const_demoted(rec_addr.clone(), rec_constant, false); - Ok(rec_addr) - }, - IxonCI::RPrj(proj) => { - let block_addr = proj.block.clone(); - let block_arc = ixon_env.get_const(&block_addr).ok_or_else(|| { - format!( - "{}: recursor projection points at missing block {}", - rec_name.pretty(), - block_addr.hex() - ) - })?; - let mut block_constant: ixon::constant::Constant = (*block_arc).clone(); - drop(block_arc); - match &mut block_constant.info { - IxonCI::Muts(members) => { - let idx = usize::try_from(proj.idx).map_err(|_e| { - format!( - "{}: recursor projection index too large", - rec_name.pretty() - ) - })?; - match members.get_mut(idx) { - Some(IxonMutConst::Recr(rec)) => poison_recursor_rule_payload(rec)?, - Some(_) => { - return Err(format!( - "{}: projection index {} is not a recursor member", - rec_name.pretty(), - proj.idx - )); - }, - None => { - return Err(format!( - "{}: projection index {} out of range for recursor block", - rec_name.pretty(), - proj.idx - )); - }, - } - }, - other => { - return Err(format!( - "{}: recursor projection block is not Muts (got {other:?})", - rec_name.pretty() - )); - }, - } - ixon_env.store_const_demoted(block_addr, block_constant, false); - Ok(rec_addr) - }, - other => Err(format!( - "{}: expected recursor or recursor projection, got {other:?}", - rec_name.pretty() - )), - } -} - -#[cfg(feature = "test-ffi")] -fn poison_recursor_rule_payload( - rec: &mut ixon::constant::Recursor, -) -> Result<(), String> { - if rec.rules.len() < 2 { - return Err(format!( - "expected at least two recursor rules, got {}", - rec.rules.len() - )); - } - rec.rules[1].rhs = - wrong_successor_rule_returning_first_minor(&rec.rules[1].rhs)?; - Ok(()) -} - -#[cfg(feature = "test-ffi")] -fn wrong_successor_rule_returning_first_minor( - succ_rhs: &Arc, -) -> Result, String> { - match succ_rhs.as_ref() { - IxonExpr::Lam(motive_uses, motive_ty, rest) => match rest.as_ref() { - IxonExpr::Lam(h_zero_uses, h_zero_ty, rest) => match rest.as_ref() { - IxonExpr::Lam(h_succ_uses, h_succ_ty, rest) => match rest.as_ref() { - IxonExpr::Lam(n_uses, n_ty, _) => Ok(IxonExpr::lam_contract( - *motive_uses, - motive_ty.clone(), - IxonExpr::lam_contract( - *h_zero_uses, - h_zero_ty.clone(), - IxonExpr::lam_contract( - *h_succ_uses, - h_succ_ty.clone(), - IxonExpr::lam_contract(*n_uses, n_ty.clone(), IxonExpr::var(2)), - ), - ), - )), - other => { - Err(format!("successor rule fourth node is not Lam: {other:?}")) - }, - }, - other => { - Err(format!("successor rule third node is not Lam: {other:?}")) - }, - }, - other => Err(format!("successor rule second node is not Lam: {other:?}")), - }, - other => Err(format!("successor rule first node is not Lam: {other:?}")), - } -} - -/// FFI: type-check constants from a serialized Ixon environment produced by -/// `ix compile --out`. -/// -/// `fail_out` is a streaming-friendly failure file. An empty string means -/// "no file"; any other value is treated as a filesystem path that gets -/// truncate-created at start-of-run, populated incrementally as failures -/// are detected (one record per failure, flushed immediately so `tail -f` -/// observers see entries as they happen), and capped with a `# total -/// failures: N` footer once all checks complete. The format is the same -/// one `Ix.Cli.CheckIxonCmd.readNamesFile` expects (`#`-prefixed comments -/// plus bare-name lines), so the file is round-trippable as a -/// `--consts-file` input on a re-run. -#[unsafe(no_mangle)] -pub extern "C" fn rs_kernel_check_ixon( - env_path: LeanString>, - names: LeanArray>, - expect_pass: LeanArray>, - quiet: LeanBool>, - fail_out: LeanString>, -) -> LeanIOResult { - let total_start = Instant::now(); - let quiet = quiet.to_bool(); - let path = env_path.to_string(); - let fail_out_path = fail_out.to_string(); - let fail_out_path = - if fail_out_path.is_empty() { None } else { Some(fail_out_path) }; - let names_vec: Vec = decode_name_array(&names); - let expect_pass_vec: Vec = - expect_pass.map(|b| b.unbox_usize() == 1).into_iter().collect(); - - let t0 = Instant::now(); - let bytes = match std::fs::read(&path) { - Ok(bytes) => bytes, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_check_ixon: failed to read {path}: {e}" - )); - }, - }; - eprintln!( - "[rs_kernel_check_ixon] read env: {:>8.1?} ({} bytes)", - t0.elapsed(), - bytes.len() - ); - - let t1 = Instant::now(); - 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_check_ixon: failed to deserialize {path}: {e}" - )); - }, - }; - drop(bytes); - eprintln!( - "[rs_kernel_check_ixon] deserialize:{:>8.1?} ({} named)", - t1.elapsed(), - ixon_env.named_count() - ); - - // Open the streaming failure log up front so any seed that fails - // mid-run is persisted before this function returns. We open it before - // the ingress lookups are built so that even a setup-time crash leaves - // the user with a header noting the env path and seed count. - let failure_log: Option> = match fail_out_path.as_deref() { - None => None, - Some(out_path) => { - match FailureLog::open(out_path, &path, names_vec.len()) { - Ok(log) => { - eprintln!("[rs_kernel_check_ixon] streaming failures to {out_path}"); - Some(Arc::new(log)) - }, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_check_ixon: failed to open fail-out file {out_path}: {e}" - )); - }, - } - }, - }; - - let t2 = Instant::now(); - let ixon_env = Arc::new(ixon_env); - let lookups = Arc::new(build_ixon_ingress_lookups(&ixon_env)); - eprintln!("[rs_kernel_check_ixon] ingress prep:{:>8.1?}", t2.elapsed()); - - let total = names_vec.len(); - let t3 = Instant::now(); - let results = match run_checks_on_large_stack::( - ixon_env, - lookups, - names_vec, - expect_pass_vec, - FxHashMap::default(), - quiet, - failure_log.clone(), - ) { - Ok(r) => r, - Err(msg) => { - if let Some(log) = failure_log.as_ref() { - log.finalize(); - } - return build_uniform_error(total, &format!("[thread] {msg}")); - }, - }; - - let passed = results.iter().filter(|r| r.is_ok()).count(); - let failed = results.iter().filter(|r| r.is_err()).count(); - eprintln!( - "[rs_kernel_check_ixon] {passed}/{total} passed, {failed} failed ({:.1?})", - t3.elapsed() - ); - eprintln!( - "[rs_kernel_check_ixon] total: {:>8.1?}", - total_start.elapsed() - ); - if let Some(log) = failure_log.as_ref() { - log.finalize(); - eprintln!( - "[rs_kernel_check_ixon] streamed {} failure(s) to fail-out", - log.count() - ); - } - - build_result_array(&results) -} - -/// FFI: list the checkable names in a serialized Ixon environment. -#[unsafe(no_mangle)] -pub extern "C" fn rs_kernel_ixon_names( - env_path: LeanString>, -) -> LeanIOResult { - let path = env_path.to_string(); - let bytes = match std::fs::read(&path) { - Ok(bytes) => bytes, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_ixon_names: failed to read {path}: {e}" - )); - }, - }; - 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_ixon_names: failed to deserialize {path}: {e}" - )); - }, - }; - let names = all_checkable_ixon_names(&ixon_env); - LeanIOResult::ok(build_lean_name_array(&names)) -} - -/// FFI: expose the canonical primitive `(lean_name, hex_address)` -/// table from `PrimAddrs::new()` to the Lean test suite. -/// -/// Used by `testPrimitivesParity` -/// (`Tests/Ix/Kernel/BuildPrimitives.lean`) to detect drift between -/// the hardcoded `PrimAddrs::new()` addresses and the freshly -/// compiled addresses produced by `rsCompileEnvFFI`. Any future -/// compile/serialize change that touches a primitive's content hash -/// will fail this test with a diff before the breakage propagates -/// to downstream consumers (Aiur, kernel primitive resolution). -#[unsafe(no_mangle)] -pub extern "C" fn rs_prim_addrs_canonical() -> LeanIOResult { - let table = ix_kernel::primitive::PrimAddrs::lean_parity_table(); - build_prim_addrs_table(&table) -} - -/// FFI: expose the original/LEON primitive `(lean_name, hex_address)` -/// table from `PrimAddrs::new_orig()` to the Lean test suite. -#[unsafe(no_mangle)] -pub extern "C" fn rs_prim_addrs_orig() -> LeanIOResult { - let table = ix_kernel::primitive::PrimAddrs::lean_orig_parity_table(); - build_prim_addrs_table(&table) -} - -/// FFI: expose the canonical addresses of Lean's native-evaluation -/// constants (`ix_kernel::primitive::native_evaluation_addrs`), which the -/// kernel declines, as `(lean_name, hex_address)` pairs. -/// -/// Used by `testNativeEvaluationParity` -/// (`Tests/Ix/Kernel/BuildPrimitives.lean`) to compare them with a live -/// compile of the Lean declarations. -#[unsafe(no_mangle)] -pub extern "C" fn rs_native_evaluation_addrs() -> LeanIOResult { - let table: Vec<(&'static str, String)> = - ix_kernel::primitive::native_evaluation_addrs() - .iter() - .map(|(name, addr)| (*name, addr.hex())) - .collect(); - build_prim_addrs_table(&table) -} - -fn build_prim_addrs_table( - table: &[(&'static str, String)], -) -> LeanIOResult { - let arr = LeanArray::alloc(table.len()); - for (i, (name, hex)) in table.iter().enumerate() { - let name_obj: LeanOwned = LeanString::new(name).into(); - let hex_obj: LeanOwned = LeanString::new(hex).into(); - let pair: LeanOwned = LeanProd::new(name_obj, hex_obj).into(); - arr.set(i, pair); - } - LeanIOResult::ok(arr) -} - -fn all_checkable_ixon_names(ixon_env: &IxonEnv) -> Vec { - let mut names = Vec::with_capacity(ixon_env.named_count()); - for entry in ixon_env.named.iter() { - if matches!(entry.value().meta().info, ConstantMetaInfo::Muts { .. }) { - continue; - } - names.push(entry.key().clone()); - } - names.sort_by_key(|name| name.pretty()); - names -} - -fn build_lean_name_array(names: &[Name]) -> LeanArray { - let arr = LeanArray::alloc(names.len()); - for (i, name) in names.iter().enumerate() { - arr.set(i, build_lean_name(name)); - } - arr -} - -fn build_lean_name(name: &Name) -> LeanOwned { - match name.as_data() { - NameData::Anonymous(_) => LeanOwned::box_usize(0), - NameData::Str(parent, s, _) => { - let parent = build_lean_name(parent); - let part = LeanString::new(s); - unsafe { - LeanOwned::from_raw(lean_name_mk_string( - parent.into_raw(), - part.into_raw(), - )) - } - }, - NameData::Num(parent, n, _) => { - let parent = build_lean_name(parent); - let part = LeanNat::from_nat(n); - unsafe { - LeanOwned::from_raw(lean_name_mk_numeral( - parent.into_raw(), - part.into_raw(), - )) - } - }, - } -} - -/// FFI: ingress a Lean environment through compile + `ixon_ingress`, stopping -/// before kernel typechecking. Used by `lake exe ix ingress` for performance -/// analysis of the Lean → Ixon → KEnv pipeline in isolation. -/// -/// Lean signature: -/// ```lean -/// @[extern "rs_kernel_ingress"] -/// opaque rsKernelIngressFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO USize -/// ``` -/// -/// Returns the number of kernel constants ingressed. The Rust side prints a -/// per-phase timing breakdown to stderr, mirroring `rs_kernel_check_consts`'s -/// `[rs_kernel_check] read env / compile / ingress` lines (renamed to -/// `[rs_kernel_ingress] ...`). Errors during compile or ingress are reported -/// via `LeanIOResult::error_string`, matching `rs_compile_env`. -/// -/// **Always runs destructors** by default (opt out with `IX_SKIP_DROPS=1`), -/// because this is a perf-analysis tool — the `Arc` chain-drops -/// across the InternTable shards and the KEnv consts map are part of the -/// real ingress pipeline we want to measure. The reported `total:` line -/// therefore includes teardown cost. Contrast with `rs_compile_env`, which -/// defaults to leaking those allocations to keep a one-shot CLI's wall -/// clock low; here measurement beats wall-clock. -#[unsafe(no_mangle)] -pub extern "C" fn rs_kernel_ingress( - env_consts: LeanList>, -) -> LeanIOResult { - let total_start = Instant::now(); - - // --------------------------------------------------------------------- - // Decode inputs - // --------------------------------------------------------------------- - let t0 = Instant::now(); - let rust_env = decode_env(env_consts); - eprintln!("[rs_kernel_ingress] read env: {:>8.1?}", t0.elapsed()); - - // --------------------------------------------------------------------- - // Compile Lean → Ixon - // --------------------------------------------------------------------- - let t1 = Instant::now(); - let rust_env_arc = Arc::new(rust_env); - let compile_state = - match compile_env_with_options(&rust_env_arc, CompileOptions::default()) { - Ok(s) => s, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_ingress: compile failed: {e:?}" - )); - }, - }; - eprintln!("[rs_kernel_ingress] compile: {:>8.1?}", t1.elapsed()); - - let CompileState { env: ixon_env, ungrounded: compile_ungrounded, .. } = - compile_state; - let ungrounded_count = compile_ungrounded.len(); - drop(compile_ungrounded); - drop(rust_env_arc); - if ungrounded_count > 0 { - eprintln!( - "[rs_kernel_ingress] {ungrounded_count} constants failed to compile (ungrounded; ignored for ingress)" - ); - } - - // --------------------------------------------------------------------- - // Ingress Ixon → kernel - // --------------------------------------------------------------------- - let t2 = Instant::now(); - let (mut kenv, intern) = match ixon_ingress_owned::(ixon_env) { - Ok(v) => v, - Err(msg) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_ingress: ingress failed: {msg}" - )); - }, - }; - // Move `intern` into the KEnv so they form a single owned tree, matching - // `rs_kernel_check_consts`'s post-ingress shape. Dropping kenv (which - // owns intern) gives the same drop-order as the check FFI: KEnv first - // releases its expr/univ refs into the InternTable's DashMaps, then the - // InternTable releases the underlying KExpr/KUniv values. Dropping the - // two as separate locals would invert that order on `intern`'s contents - // and (empirically) destabilises Lean's later runtime shutdown — this - // form is segfault-free. - kenv.intern = intern; - let kenv_len = kenv.len(); - eprintln!( - "[rs_kernel_ingress] ingress: {:>8.1?} ({kenv_len} consts)", - t2.elapsed(), - ); - - // Always run destructors so the reported `total:` includes teardown - // cost — this is a perf-analysis CLI, and `Arc` chain-drops - // across the InternTable shards are part of the real ingress pipeline - // we want to measure. (Contrast with `rs_compile_env`, which intentionally - // forgets state to keep one-shot CLI wall-clock low; here measurement - // beats wall-clock.) Opt out with `IX_SKIP_DROPS=1` if you want to - // compare against the leaked-allocation baseline. - if std::env::var("IX_SKIP_DROPS").ok().as_deref() == Some("1") { - eprintln!("[rs_kernel_ingress] skipping destructors (IX_SKIP_DROPS=1)"); - std::mem::forget(kenv); - } else { - let drop_start = Instant::now(); - drop(kenv); - eprintln!( - "[rs_kernel_ingress] destructors: {:>8.1?}", - drop_start.elapsed() - ); - } - - eprintln!("[rs_kernel_ingress] total: {:>8.1?}", total_start.elapsed()); - - // Return the kenv length to Lean so the CLI can include it in its - // `##ingress##` benchmark line. `USize` values stored inside Lean objects - // must use Lean's heap scalar representation (`lean_box_usize`), not the - // tagged-small-object representation used by `lean_box`. - LeanIOResult::ok(LeanOwned::box_usize_obj(kenv_len)) -} - -// ============================================================================= -// Checking runners (large-stack workers) -// ============================================================================= - -/// Kind of per-constant error — selects which `CheckError` ctor to build on -/// the Lean side. See tag constants at the top of the module. -#[derive(Clone, Copy)] -enum ErrKind { - Kernel, - Compile, -} - -impl ErrKind { - fn tag(self) -> u8 { - match self { - ErrKind::Kernel => KERNEL_EXCEPTION_TAG, - ErrKind::Compile => COMPILE_ERROR_TAG, - } - } -} - -/// Per-constant result: `Ok(())` on pass, `Err((kind, msg))` on rejection. -type CheckRes = Result<(), (ErrKind, String)>; - -const KERNEL_CHECK_STACK_SIZE: usize = 256 * 1024 * 1024; - -unsafe extern "C" { - /// Lean runtime (`src/util/thread.cpp`): stack size, in bytes, for - /// subsequently spawned `lthread`s — dedicated task threads and new - /// task-pool workers. This is the knob behind `lean --tstack`. - fn lean_internal_set_thread_stack_size(size: usize); -} - -/// `Ix.Runtime.setLeanThreadStackSize : USize → BaseIO Unit` -/// -/// ABI adapter: the runtime setter is a bare `void(size_t)`, so Lean can't -/// `@[extern]` it directly. The pure-Lean parallel checker calls this before -/// spawning its `.dedicated` workers so they get `KERNEL_CHECK_STACK_SIZE`- -/// class stacks — deep recursor expansions overflow the default 8 MB -/// `lthread` stack just like they would the Rust workers' (see above). -#[unsafe(no_mangle)] -extern "C" fn rs_lean_set_thread_stack_size( - size: usize, -) -> LeanIOResult { - unsafe { lean_internal_set_thread_stack_size(size) }; - LeanIOResult::ok(LeanOwned::box_usize(0)) -} - -#[derive(Clone, Debug)] -struct CheckWorkItem { - primary: usize, - aliases: Vec, -} - -/// Group the requested names into kernel work items. Names share an item, -/// and so a verdict, only when each one's carried record is authentically a -/// member of the same carried owner (`ArtifactSource::member`, which checks -/// the record's hash, the owner's record and the member's kind and index). A -/// projection's own `block` field is not trusted: a projection that fails -/// authentication gets an item of its own, whose check reports the defect. -fn build_check_work( - ixon_env: &IxonEnv, - source: &ArtifactSource, - names: &[Name], - expect_pass: &[bool], - ungrounded: &FxHashMap, -) -> Vec { - use rayon::prelude::*; - let mut work: Vec = Vec::with_capacity(names.len()); - let mut by_block: FxHashMap<(Address, bool), usize> = FxHashMap::default(); - // Resolving a name's unit authenticates its record: do it in parallel, - // then group in order. - let block_keys: Vec> = names - .par_iter() - .map(|name| check_schedule_block_addr(ixon_env, source, name, ungrounded)) - .collect(); - - for (i, block_key) in block_keys.into_iter().enumerate() { - let should_pass = expect_pass.get(i).copied().unwrap_or(true); - if let Some(block_key) = block_key { - let key = (block_key, should_pass); - if let Some(work_idx) = by_block.get(&key).copied() { - work[work_idx].aliases.push(i); - continue; - } - let work_idx = work.len(); - by_block.insert(key, work_idx); - } - - work.push(CheckWorkItem { primary: i, aliases: vec![i] }); - } - - work -} - -fn check_schedule_block_addr( - ixon_env: &IxonEnv, - source: &ArtifactSource, - name: &Name, - ungrounded: &FxHashMap, -) -> Option
{ - if ungrounded.contains_key(name) { - return None; - } - let named = ixon_env.lookup_name(name)?; - if matches!(named.meta().info, ConstantMetaInfo::Muts { .. }) { - return None; - } - // Only collapse work by actual serialized kernel units: a projection's - // authenticated owner, or the constant itself. Do not use - // declaration-family `all` metadata here: it can include names that are - // not checked by the same kernel block. - let member = source.member(&named.addr).ok()?; - Some(member.owner().map_or(named.addr, |owner| owner.address().clone())) -} - -fn run_checks_on_large_stack( - ixon_env: Arc, - lookups: Arc, - names: Vec, - expect_pass: Vec, - ungrounded: FxHashMap, - quiet: bool, - failure_log: Option>, -) -> Result, String> -where - M::MField>: CheckDupLevelParams, -{ - if names.is_empty() { - eprintln!("[rs_kernel_check] checking 0 constants..."); - return Ok(Vec::new()); - } - - // Capture the input once, before planning: the plan and every worker read - // this same authenticated source. - let snapshot_start = Instant::now(); - let source = - ArtifactSource::snapshot(&ixon_env).map_err(|error| error.to_string())?; - eprintln!("[rs_kernel_check] snapshot: {:.1?}", snapshot_start.elapsed()); - report_admission("rs_kernel_check", &source); - let plan_start = Instant::now(); - let work = - build_check_work(&ixon_env, &source, &names, &expect_pass, &ungrounded); - eprintln!("[rs_kernel_check] plan: {:.1?}", plan_start.elapsed()); - if work.len() == names.len() { - eprintln!("[rs_kernel_check] checking {} constants...", names.len()); - } else { - eprintln!( - "[rs_kernel_check] checking {} block work item(s) for {} constants...", - work.len(), - names.len() - ); - } - - let worker_count = resolve_kernel_check_workers(work.len(), quiet); - let tier_source = source.clone(); - let tier_names: Vec> = names - .iter() - .map(|name| ixon_env.lookup_name(name).map(|named| named.addr)) - .collect(); - let results = if worker_count == 1 { - run_checks_serial_on_large_stack::( - ixon_env, - source, - lookups, - names, - expect_pass, - ungrounded, - work, - quiet, - failure_log, - )? - } else { - run_checks_parallel_on_large_stacks::( - ixon_env, - source, - lookups, - names, - expect_pass, - ungrounded, - work, - quiet, - worker_count, - failure_log, - )? - }; - report_safety_tier( - "rs_kernel_check", - &tier_source, - tier_names.iter().zip(&results).filter_map(|(address, result)| { - address.as_ref().filter(|_| result.is_ok()) - }), - ); - Ok(results) -} - -/// Report, for a bound source, how many of the passing declarations are in -/// the safety tier: partial or unsafe, checked with partial and unsafe -/// constants opaque (the certified checker's `typed` outcome) rather than -/// accepted into the logical theory. -fn report_safety_tier<'a>( - label: &str, - source: &ArtifactSource, - passing: impl Iterator, -) { - if source.binding().is_none() { - return; - } - let (mut passed, mut typed) = (0usize, 0usize); - for address in passing { - passed += 1; - if source.in_safety_tier(address) { - typed += 1; - } - } - eprintln!( - "[{label}] of {passed} passing, {} accepted and {typed} typed (partial or unsafe)", - passed - typed - ); -} - -fn run_checks_serial_on_large_stack( - ixon_env: Arc, - source: ArtifactSource, - lookups: Arc, - names: Vec, - expect_pass: Vec, - ungrounded: FxHashMap, - work: Vec, - quiet: bool, - failure_log: Option>, -) -> Result, String> -where - M::MField>: CheckDupLevelParams, -{ - thread::Builder::new() - .stack_size(KERNEL_CHECK_STACK_SIZE) - .spawn(move || { - check_consts_loop::( - ixon_env, - source, - lookups, - names, - expect_pass, - ungrounded, - work, - quiet, - failure_log, - ) - }) - .map_err(|e| format!("failed to spawn kernel-check thread: {e}"))? - .join() - .map_err(|_panic| "kernel-check thread panicked".to_string()) -} - -// All by-value arguments below are immediately wrapped in `Arc` for sharing -// with worker threads — clippy can't see that, so suppress the lint. -#[allow(clippy::needless_pass_by_value)] -fn run_checks_parallel_on_large_stacks( - ixon_env: Arc, - source: ArtifactSource, - lookups: Arc, - names: Vec, - expect_pass: Vec, - ungrounded: FxHashMap, - work: Vec, - quiet: bool, - worker_count: usize, - failure_log: Option>, -) -> Result, String> -where - M::MField>: CheckDupLevelParams, -{ - // Workers share the planner's single capture and cannot mutate the input - // or inject a public KEnv success cache. - let total = names.len(); - let work_total = work.len(); - eprintln!( - "[rs_kernel_check] checking {work_total} work item(s) for {total} constants with {worker_count} workers..." - ); - - let names = Arc::new(names); - let expect_pass = Arc::new(expect_pass); - let ungrounded = Arc::new(ungrounded); - let work = Arc::new(work); - let next_index = Arc::new(AtomicUsize::new(0)); - let results: Arc>> = - Arc::new((0..total).map(|_| OnceLock::new()).collect()); - let progress = - Arc::new(ParallelProgress::new(work_total, worker_count, quiet)); - let mut reporter = ParallelProgress::spawn_reporter(Arc::clone(&progress)); - - let mut handles: Vec> = - Vec::with_capacity(worker_count); - for worker_idx in 0..worker_count { - let ixon_env = Arc::clone(&ixon_env); - let source = source.clone(); - let lookups = Arc::clone(&lookups); - let names = Arc::clone(&names); - let expect_pass = Arc::clone(&expect_pass); - let ungrounded = Arc::clone(&ungrounded); - let work = Arc::clone(&work); - let next_index = Arc::clone(&next_index); - let results = Arc::clone(&results); - let progress_worker = Arc::clone(&progress); - let failure_log_worker = failure_log.clone(); - - let handle = match thread::Builder::new() - .name(format!("ix-kernel-check-{worker_idx}")) - .stack_size(KERNEL_CHECK_STACK_SIZE) - .spawn(move || { - let mut checker = ArtifactChecker::::from_named_source(Ok(source)); - let clear_every = kernel_check_clear_every(); - let mut checks_since_clear = clear_every; - let diag_threshold = kernel_check_diag_threshold(); - let mut worker_peak_cache: usize = 0; - loop { - let work_idx = next_index.fetch_add(1, Ordering::Relaxed); - if work_idx >= work_total { - break; - } - let item = &work[work_idx]; - if checks_since_clear >= clear_every { - checker.clear_releasing_memory(); - checks_since_clear = 0; - } - - let outcome = check_one_const( - item.primary, - work_idx, - work_total, - &ixon_env, - &lookups, - names.as_slice(), - expect_pass.as_slice(), - ungrounded.as_ref(), - &mut checker, - |prefix| progress_worker.begin(worker_idx, prefix), - ); - progress_worker.finish(worker_idx, &outcome); - if let Some(threshold) = diag_threshold { - log_block_diag_if_big( - checker.cache_sizes(), - worker_idx, - work_idx, - work_total, - &outcome, - threshold, - &mut worker_peak_cache, - &progress_worker, - ); - } - let result = outcome.result.clone(); - for &result_idx in &item.aliases { - // Each result slot should be written exactly once. The - // work-item dedup in `dedup_by_primary` ensures we - // never schedule the same alias twice. If this fires, - // a future build_check_work refactor has broken that - // invariant — surface it instead of silently dropping - // the second result. - if results[result_idx].set(result.clone()).is_err() { - debug_assert!( - false, - "meta work-item dedup invariant: result slot {result_idx} set twice" - ); - } - // Stream this seed's failure to the fail-out file (if any) as - // soon as it's known, so a long full-env run grows the file - // incrementally instead of dropping everything at the end. - if let (Some(log), Err((_, msg))) = - (failure_log_worker.as_ref(), result.as_ref()) - { - log.record(&names[result_idx].pretty(), msg); - } - } - checks_since_clear += 1; - } - }) { - Ok(handle) => handle, - Err(e) => { - progress.stop_reporter(); - if let Some(reporter) = reporter.take() { - reporter.thread().unpark(); - let _ = reporter.join(); - } - for handle in handles { - let _ = handle.join(); - } - return Err(format!("failed to spawn kernel-check worker: {e}")); - }, - }; - handles.push(handle); - } - - let mut panicked = false; - for handle in handles { - if handle.join().is_err() { - panicked = true; - } - } - progress.stop_reporter(); - if let Some(reporter) = reporter { - reporter.thread().unpark(); - let _ = reporter.join(); - } - progress.log_mem_summary(); - if panicked { - return Err("kernel-check worker panicked".to_string()); - } - - let mut ordered = Vec::with_capacity(total); - for i in 0..total { - match results[i].get() { - Some(result) => ordered.push(result.clone()), - None => { - return Err(format!("kernel-check worker missed result index {i}")); - }, - } - } - Ok(ordered) -} - -fn resolve_kernel_check_workers(total: usize, quiet: bool) -> usize { - let env_workers = std::env::var("IX_KERNEL_CHECK_WORKERS").ok(); - let no_par = std::env::var("IX_NO_PAR").ok().as_deref() == Some("1"); - let available = thread::available_parallelism().map_or(1, |n| n.get()); - resolve_kernel_check_workers_from( - total, - quiet, - env_workers.as_deref(), - no_par, - available, - ) -} - -fn resolve_kernel_check_workers_from( - total: usize, - quiet: bool, - env_workers: Option<&str>, - no_par: bool, - available_parallelism: usize, -) -> usize { - if let Some(n) = - env_workers.and_then(|s| s.parse::().ok()).filter(|&n| n > 0) - { - return n; - } - if no_par || !quiet { - return 1; - } - if total == 0 { 1 } else { available_parallelism.max(1).min(total) } -} - -// ============================================================================ -// Anon-mode parallel runner -// ============================================================================ -// -// Companion to `run_checks_parallel_on_large_stacks` for the metadata-free -// anon path. Iterates `env.consts` exactly once to enumerate work items -// (block or standalone), then dispatches to workers each running -// `IxonChecker` owns each worker's kernel state and lazy Ixon ingress. -// The lazy ingress mechanism (in `tc.rs`) handles cross-block faults -// without consulting metadata. - -/// FFI-side anon work item with per-target result-slot indices. -/// -/// Wraps [`ix_kernel::anon_work::AnonWorkItem`] with the index -/// mapping the parallel runner needs to write per-target results -/// into a flat `OnceLock` vector for the FFI return ABI. -#[derive(Clone, Debug)] -enum AnonWorkItem { - /// A standalone (non-mutual, non-projection) constant. - Standalone { result_idx: usize, addr: Address }, - /// A Muts block. `primary_addr` is the first member's projection address; - /// `result_idxs` enumerates every kernel-checkable target produced by - /// the block (each member's projection + each ctor's CPrj of inductive - /// members), all sharing the same check result via the kernel's - /// block coordination. - Block { block_addr: Address, primary_addr: Address, result_idxs: Vec }, -} - -/// One pass over `env.consts`: enumerate work items + the kernel-checkable -/// target addresses (one per result slot). Skips projection constants -/// (covered by their parent block) and Muts addresses themselves -/// (blocks aren't kernel KIds). -/// -/// Delegates the enumeration to -/// [`ix_kernel::anon_work::build_anon_work`] and layers per-target result-slot -/// bookkeeping on top. -/// Assign result slots to a set of kernel work items — the indexing step -/// shared by the whole-env check (every item) and the per-constant closure -/// check (a filtered subset). -/// Report, for a bound source, which role groups were admitted and why any -/// bound group was refused. -fn report_admission(label: &str, source: &ArtifactSource) { - if source.binding().is_none() { - eprintln!( - "[{label}] no role binding: checked under the legacy policy, in which \ - every axiom is usable" - ); - return; - } - let start = Instant::now(); - let admission = source.admission(); - eprintln!( - "[{label}] bound roles: {} admitted, {} group(s) refused ({:.1?})", - admission.admitted_count(), - admission.refused().len(), - start.elapsed() - ); - for (role, reason) in admission.refused() { - eprintln!("[{label}] {} group refused: {reason}", role.lean_name()); - } - if let Some(reason) = source.prelude_refusal() { - eprintln!("[{label}] no unit will be checked: {reason}"); - } -} - -fn index_anon_work( - kernel_work: Vec, -) -> (Vec, Vec
) { - use ix_kernel::anon_work::AnonWorkItem as KItem; - - let mut work: Vec = Vec::with_capacity(kernel_work.len()); - let mut addrs: Vec
= Vec::new(); - for item in kernel_work { - match item { - KItem::Standalone { addr } => { - let result_idx = addrs.len(); - addrs.push(addr.clone()); - work.push(AnonWorkItem::Standalone { result_idx, addr }); - }, - KItem::Block { block_addr, primary, targets } => { - let start = addrs.len(); - let result_idxs: Vec = (start..start + targets.len()).collect(); - addrs.extend(targets); - work.push(AnonWorkItem::Block { - block_addr, - primary_addr: primary, - result_idxs, - }); - }, - } - } - (work, addrs) -} - -#[allow(clippy::needless_pass_by_value)] -fn run_anon_checks_parallel( - source: ArtifactSource, - work: Vec, - addrs: Vec
, - quiet: bool, - failure_log: Option>, -) -> Result, String> { - let total = addrs.len(); - let work_total = work.len(); - let worker_count = resolve_kernel_check_workers(work_total, quiet); - eprintln!( - "[rs_kernel_check_anon] checking {work_total} work item(s) for {total} consts with {worker_count} worker(s)..." - ); - // Opt-in native throughput experiment: reuse bucket capacity, never - // logical cache entries. Zero preserves the existing release policy. - let retain_capacity = env_usize("IX_KERNEL_CHECK_RETAIN_CAPACITY", 0); - if retain_capacity > 0 { - eprintln!( - "[rs_kernel_check_anon] cache reset: retaining at most {retain_capacity} entries of empty capacity per cleared table" - ); - } - // 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); - let next_index = Arc::new(AtomicUsize::new(0)); - let results: Arc>> = - Arc::new((0..total).map(|_| OnceLock::new()).collect()); - let progress = - Arc::new(ParallelProgress::new(work_total, worker_count, quiet)); - let mut reporter = ParallelProgress::spawn_reporter(Arc::clone(&progress)); - - let mut handles: Vec> = - Vec::with_capacity(worker_count); - for worker_idx in 0..worker_count { - let source = source.clone(); - let work = Arc::clone(&work); - let addrs = Arc::clone(&addrs); - let next_index = Arc::clone(&next_index); - 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}")) - .stack_size(KERNEL_CHECK_STACK_SIZE) - .spawn(move || { - let mut checker = ix_kernel::ixon_checker::IxonChecker::from_source(Ok(source)); - let clear_every = kernel_check_clear_every(); - let mut checks_since_clear = clear_every; - loop { - let work_idx = next_index.fetch_add(1, Ordering::Relaxed); - if work_idx >= work_total { - break; - } - let item = &work[work_idx]; - if checks_since_clear >= clear_every { - if retain_capacity == 0 { - checker.clear_releasing_memory(); - } else { - checker.clear_with_capacity_limit(retain_capacity); - } - checks_since_clear = 0; - } - let (primary_addr, result_idxs): (Address, Vec) = match item { - AnonWorkItem::Standalone { result_idx, addr } => { - (addr.clone(), vec![*result_idx]) - }, - AnonWorkItem::Block { primary_addr, result_idxs, .. } => { - (primary_addr.clone(), result_idxs.clone()) - }, - }; - let display = format!("#{}", primary_addr.hex()); - let prefix = format!(" [{}/{work_total}] {display}", work_idx + 1); - progress_worker.begin(worker_idx, &prefix); - - let tc_start = Instant::now(); - 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 = match item { - AnonWorkItem::Block { block_addr, .. } => checker.check_owner(block_addr), - AnonWorkItem::Standalone { addr, .. } => checker.check_const(addr), - }; - let item_fuel = checker.last_check().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}"))), - }; - - let outcome = CheckOutcome { - progress_index: work_idx, - progress_total: work_total, - display: display.clone(), - should_pass: true, - result: result.clone(), - status: CheckStatus::Checked, - elapsed: Some(elapsed), - peak: None, - }; - progress_worker.finish(worker_idx, &outcome); - - for &result_idx in &result_idxs { - // Each result slot is written exactly once across the - // entire run. `build_anon_work` assigns disjoint - // `result_idxs` per work item; if this assertion fires, - // that invariant has been broken (likely by a future - // dedup refactor) and we'd silently drop the second - // write rather than expose the bug. - if results[result_idx].set(result.clone()).is_err() { - debug_assert!( - false, - "anon work-item dedup invariant: result slot {result_idx} set twice" - ); - } - if let (Some(log), Err((_, msg))) = - (failure_log_worker.as_ref(), result.as_ref()) - { - let label = format!("#{}", addrs[result_idx].hex()); - log.record(&label, msg); - } - } - checks_since_clear += 1; - } - }) { - Ok(h) => h, - Err(e) => { - progress.stop_reporter(); - if let Some(r) = reporter.take() { - r.thread().unpark(); - let _ = r.join(); - } - for h in handles { - let _ = h.join(); - } - return Err(format!("spawn anon worker: {e}")); - }, - }; - handles.push(handle); - } - - let mut panicked = false; - for h in handles { - if h.join().is_err() { - panicked = true; - } - } - progress.stop_reporter(); - if let Some(r) = reporter { - r.thread().unpark(); - let _ = r.join(); - } - progress.log_mem_summary(); - if panicked { - 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() { - Some(r) => ordered.push(r.clone()), - None => { - return Err(format!("anon worker missed result idx {i}")); - }, - } - } - Ok(ordered) -} - -/// FFI: typecheck every kernel-checkable constant in a `.ixe` using the -/// metadata-free anon kernel. -/// -/// - Loads the env via `IxonEnv::get_anon` (discards `named` / `names` / -/// `comms` sections during deserialization). -/// - Enumerates work items by walking `env.consts` once and skipping -/// projection constants (`IPrj`/`CPrj`/`RPrj`/`DPrj`); Muts blocks -/// become block work items whose member + ctor projection addresses -/// are reconstructed deterministically via `Constant::commit`. -/// - Workers each get their own `IxonChecker`. Deep refs fault in -/// lazily via the anon-mode shallow ingress (`ingress_anon_addr_shallow`). -/// - Returns `Array (Option CheckError)`, one slot per kernel-checkable -/// address discovered during enumeration. -#[unsafe(no_mangle)] -pub extern "C" fn rs_kernel_check_anon( - env_path: LeanString>, - quiet: LeanBool>, - fail_out: LeanString>, -) -> LeanIOResult { - let total_start = Instant::now(); - let quiet = quiet.to_bool(); - let path = env_path.to_string(); - let fail_out_path = fail_out.to_string(); - let fail_out_path = - if fail_out_path.is_empty() { None } else { Some(fail_out_path) }; - - // mmap the .ixe directly. Section 2 consts become zero-copy windows - // into the mapping (`LazyConstant::from_mmap_slice`), avoiding the - // ~3 GB heap copy that `std::fs::read` would impose on mathlib. - // Sections 3-4 (names + named) are still parse-and-discard, but - // their decoded forms drop before `get_anon_mmap` returns. - let t1 = Instant::now(); - let ixon_env = match IxonEnv::get_anon_mmap(std::path::Path::new(&path)) { - Ok(env) => env, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_check_anon: failed to mmap+deserialize {path}: {e}" - )); - }, - }; - eprintln!( - "[rs_kernel_check_anon] mmap+parse: {:>8.1?} ({} consts; \ - named={} names={} comms={})", - t1.elapsed(), - ixon_env.const_count(), - ixon_env.named_count(), - ixon_env.name_count(), - ixon_env.comm_count(), - ); - - let t2 = Instant::now(); - // Own one snapshot before workers; the mapped input file must remain - // immutable for the lifetime of its shared mmap buffers. - let source = match ArtifactSource::prepare(ixon_env) { - Ok(source) => source, - Err(error) => return LeanIOResult::error_string(&error.to_string()), - }; - report_admission("rs_kernel_check_anon", &source); - let (work, addrs) = match ix_kernel::anon_work::build_artifact_work(&source) - .map(index_anon_work) - { - Ok(pair) => pair, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_check_anon: build_anon_work: {e}" - )); - }, - }; - eprintln!( - "[rs_kernel_check_anon] build work: {:>8.1?} ({} items, {} targets)", - t2.elapsed(), - work.len(), - addrs.len() - ); - - let failure_log: Option> = match fail_out_path.as_deref() { - None => None, - Some(out_path) => match FailureLog::open(out_path, &path, addrs.len()) { - Ok(log) => { - eprintln!("[rs_kernel_check_anon] streaming failures to {out_path}"); - Some(Arc::new(log)) - }, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_check_anon: failed to open fail-out file {out_path}: {e}" - )); - }, - }, - }; - - let total = addrs.len(); - // Keep our own copy for the per-slot `(hex, result)` FFI return; - // the runner clones internally for worker dispatch. - let addrs_for_return = addrs.clone(); - let t3 = Instant::now(); - let tier_source = source.clone(); - let results = match run_anon_checks_parallel( - source, - work, - addrs, - quiet, - failure_log.clone(), - ) { - Ok(r) => r, - Err(msg) => { - if let Some(log) = failure_log.as_ref() { - log.finalize(); - } - return build_uniform_error(total, &format!("[thread] {msg}")); - }, - }; - - let passed = results.iter().filter(|r| r.is_ok()).count(); - let failed = results.iter().filter(|r| r.is_err()).count(); - eprintln!( - "[rs_kernel_check_anon] {passed}/{total} passed, {failed} failed ({:.1?})", - t3.elapsed() - ); - report_safety_tier( - "rs_kernel_check_anon", - &tier_source, - addrs_for_return - .iter() - .zip(&results) - .filter_map(|(address, result)| result.is_ok().then_some(address)), - ); - eprintln!( - "[rs_kernel_check_anon] total: {:>8.1?}", - total_start.elapsed() - ); - if let Some(log) = failure_log.as_ref() { - log.finalize(); - eprintln!( - "[rs_kernel_check_anon] streamed {} failure(s) to fail-out", - log.count() - ); - } - - build_anon_result_array(&addrs_for_return, &results) -} - -/// Shared anon-mode seed context: the anon-view env, its whole-env work -/// items, and each requested name resolved to its covering work item -/// (standalone → itself; a mutual-block member → the whole block, checked -/// atomically). -struct AnonSeedContext { - source: ArtifactSource, - kernel_work: Vec, - /// One `(displayed name, work-item index)` per requested name, in request - /// order. Two names may share a work item (same mutual block). - seeds: Vec<(String, usize)>, -} - -/// Load `path` once and resolve `names` for anon-mode seeded checking: -/// displayed names → addresses via the full view's `named` metadata (dropped -/// right after), then the anon view + whole-env work items. `label` prefixes -/// diagnostics and error messages. -fn load_anon_seed_context( - label: &str, - path: &str, - names: &[String], -) -> Result { - let t0 = Instant::now(); - let bytes = std::fs::read(path) - .map_err(|e| format!("{label}: failed to read {path}: {e}"))?; - - let resolved: Vec
= { - let mut slice: &[u8] = &bytes; - let full = IxonEnv::get(&mut slice) - .map_err(|e| format!("{label}: failed to deserialize {path}: {e}"))?; - // Keyed through `normalize_displayed_name` on both sides so requests - // rendered by either kernel (Lean `«»`-escaped, Rust bare) resolve. - let by_name: FxHashMap = full - .named - .iter() - .map(|e| { - (normalize_displayed_name(&e.key().to_string()), e.value().addr.clone()) - }) - .collect(); - let mut addrs = Vec::with_capacity(names.len()); - let mut missing: Vec<&str> = Vec::new(); - for n in names { - match by_name.get(&normalize_displayed_name(n)) { - Some(a) => addrs.push(a.clone()), - None => missing.push(n), - } - } - if !missing.is_empty() { - return Err(format!( - "{label}: no constant(s) named [{}] in {path}", - missing.join(", ") - )); - } - addrs - }; - eprintln!( - "[{label}] resolve: {:>8.1?} ({} name(s))", - t0.elapsed(), - resolved.len() - ); - - let t1 = Instant::now(); - let mut slice: &[u8] = &bytes; - let ixon_env = IxonEnv::get_anon(&mut slice).map_err(|e| { - format!("{label}: failed to deserialize (anon) {path}: {e}") - })?; - drop(bytes); - eprintln!( - "[{label}] anon parse: {:>8.1?} ({} consts)", - t1.elapsed(), - ixon_env.const_count(), - ); - - let t2 = Instant::now(); - let source = - ArtifactSource::prepare(ixon_env).map_err(|error| error.to_string())?; - report_admission(label, &source); - let kernel_work = ix_kernel::anon_work::build_artifact_work(&source) - .map_err(|e| format!("{label}: build_anon_work: {e}"))?; - let by_block: FxHashMap = kernel_work - .iter() - .enumerate() - .map(|(i, w)| { - ( - match w { - ix_kernel::anon_work::AnonWorkItem::Standalone { addr } => { - addr.clone() - }, - ix_kernel::anon_work::AnonWorkItem::Block { block_addr, .. } => { - block_addr.clone() - }, - }, - i, - ) - }) - .collect(); - let mut seeds: Vec<(String, usize)> = Vec::with_capacity(names.len()); - for (n, addr) in names.iter().zip(&resolved) { - let block = source.block_of(addr).map_err(|error| error.to_string())?; - match by_block.get(&block) { - Some(i) => seeds.push((n.clone(), *i)), - None => { - return Err(format!( - "{label}: no work item covers {} (block {})", - addr.hex(), - block.hex() - )); - }, - } - } - eprintln!( - "[{label}] build work: {:>8.1?} ({} item(s))", - t2.elapsed(), - kernel_work.len(), - ); - Ok(AnonSeedContext { source, kernel_work, seeds }) -} - -/// The work items a verdict on the seeds rests on -/// ([`ix_kernel::anon_work::verdict_work`]): with `dependencies`, those -/// inside the seeds' full dependency closure — the set a zkVM guest -/// enumerates from its closure sub-env (`build_sub_env` + `build_anon_work`) -/// — and otherwise the seeds' own items. Both include the closure of the -/// role binding's records and certificates, whose units role admission -/// defers. -fn verdict_work_items( - ctx: &AnonSeedContext, - seed_items: &[usize], - dependencies: bool, -) -> Result, String> { - let roots: Vec
= seed_items - .iter() - .flat_map(|&i| ctx.kernel_work[i].proven_targets()) - .collect(); - ix_kernel::anon_work::verdict_work( - &ctx.source, - &ctx.kernel_work, - &roots, - dependencies, - ) -} - -/// The work items in `seed_items`' own dependency closure (or `seed_items` -/// themselves when `!dependencies`), assuming the environment's role binding -/// support has already been checked and cached. -fn closure_work_items( - ctx: &AnonSeedContext, - seed_items: &[usize], - dependencies: bool, -) -> Result, String> { - if !dependencies { - return Ok( - seed_items.iter().map(|&i| ctx.kernel_work[i].clone()).collect(), - ); - } - let roots: Vec
= seed_items - .iter() - .flat_map(|&i| ctx.kernel_work[i].proven_targets()) - .collect(); - let selected = ctx.source.closure(&roots).map_err(|e| e.to_string())?; - Ok( - ctx - .kernel_work - .iter() - .filter(|item| match item { - ix_kernel::anon_work::AnonWorkItem::Standalone { addr } => { - selected.contains(addr) - }, - ix_kernel::anon_work::AnonWorkItem::Block { block_addr, .. } => { - selected.contains(block_addr) - }, - }) - .cloned() - .collect(), - ) -} - -/// FFI: anon-mode type-check of named constants with (by default) their full -/// dependency closures. `skip_deps` restricts the check to each name's own -/// work item (subject-only; deps trusted). Every mode also checks the -/// closure of a bound artifact's role records and certificates, which role -/// admission assumes and a verdict on an admitted role rests on. -/// -/// Names resolve through the env's `named` metadata by displayed form, -/// then the metadata is dropped and -/// the check runs on the anon view — the kernel never sees names. A member -/// of a mutual block selects the whole block's work item (blocks check -/// atomically). Multiple names union their closures into one check set. -/// -/// `json_path` (empty = off) additionally emits per-name benchmark results -/// rows. With it set, each name is checked as its own closure run — the -/// per-constant scope the zkVM hosts measure — instead of one union set: -/// the env still loads once, and each row's `check-time`/`peak-rss` window -/// wraps only that name's closure selection + check. Rows are flushed per -/// name, so a killed run keeps completed rows; a rejected name gets -/// `status: rejected` and the loop continues. -/// -/// Returns `(hex_address, Option CheckError)` pairs, one per checked target, -/// exactly like `rs_kernel_check_anon` (concatenated across names when -/// per-name rows are on — shared deps then appear once per name that -/// re-checked them). -#[unsafe(no_mangle)] -pub extern "C" fn rs_kernel_check_anon_consts( - env_path: LeanString>, - names: LeanArray>, - skip_deps: LeanBool>, - quiet: LeanBool>, - fail_out: LeanString>, - json_path: LeanString>, -) -> LeanIOResult { - let total_start = Instant::now(); - let quiet = quiet.to_bool(); - let skip_deps = skip_deps.to_bool(); - let path = env_path.to_string(); - let fail_out_path = fail_out.to_string(); - let fail_out_path = - if fail_out_path.is_empty() { None } else { Some(fail_out_path) }; - let json_path = json_path.to_string(); - let names_vec: Vec = names.map(|obj| obj.as_string().to_string()); - if names_vec.is_empty() { - return LeanIOResult::error_string( - "rs_kernel_check_anon_consts: no constant names given", - ); - } - if !json_path.is_empty() && fail_out_path.is_some() { - return LeanIOResult::error_string( - "rs_kernel_check_anon_consts: --fail-out is not supported with per-name rows (--json)", - ); - } - - let ctx = match load_anon_seed_context( - "rs_kernel_check_anon_consts", - &path, - &names_vec, - ) { - Ok(ctx) => ctx, - Err(e) => return LeanIOResult::error_string(&e), - }; - - // ---- Per-name rows mode: one independent closure run per name (the - // zkVM hosts' per-constant scope), timing only each name's closure - // selection + check. The role binding's support (bound records and - // certificate proof theorems) is checked once up front and cached across - // constants. Rows flush per name; a rejected name records - // `status: rejected` and the loop continues. - if !json_path.is_empty() { - let json_file = std::path::PathBuf::from(&json_path); - let support_selected = match verdict_work_items(&ctx, &[], true) { - Ok(work) => work, - Err(error) => return LeanIOResult::error_string(&error), - }; - let (support_work, support_addrs) = index_anon_work(support_selected); - let support_total = support_addrs.len(); - let support_results = match run_anon_checks_parallel( - ctx.source.clone(), - support_work, - support_addrs, - quiet, - None, - ) { - Ok(r) => r, - Err(msg) => { - return build_uniform_error(support_total, &format!("[thread] {msg}")); - }, - }; - let support_failed = support_results.iter().filter(|r| r.is_err()).count(); - - let mut all_addrs: Vec
= Vec::new(); - let mut all_results = Vec::new(); - for (name, item_idx) in &ctx.seeds { - tracing_texray::rss_sampler::reset_peak_tree_rss(); - let t = Instant::now(); - let selected = match closure_work_items(&ctx, &[*item_idx], !skip_deps) { - Ok(work) => work, - Err(error) => return LeanIOResult::error_string(&error), - }; - let (work, addrs) = index_anon_work(selected); - let total = addrs.len(); - let results = match run_anon_checks_parallel( - ctx.source.clone(), - work, - addrs.clone(), - quiet, - None, - ) { - Ok(r) => r, - Err(msg) => { - return build_uniform_error(total, &format!("[thread] {msg}")); - }, - }; - let failed = - support_failed + results.iter().filter(|r| r.is_err()).count(); - let secs = t.elapsed().as_secs_f64(); - let tput = if secs > 0.0 { - f64::from(u32::try_from(total).unwrap_or(u32::MAX)) / secs - } else { - 0.0 - }; - let status = if failed > 0 { - ix_bench::Status::Rejected - } else { - ix_bench::Status::Ok - }; - eprintln!( - "[rs_kernel_check_anon_consts] {name}: {}/{total} passed ({secs:.3}s)", - total - failed, - ); - if let Err(e) = ix_bench::write_row( - &json_file, - name, - status, - serde_json::json!({ - "constants": total, - "check-time": (secs * 1e6).round() / 1e6, - "throughput": (tput * 100.0).round() / 100.0, - "peak-rss": ix_bench::peak_rss_bytes(), - }), - ) { - return LeanIOResult::error_string(&format!( - "rs_kernel_check_anon_consts: write {}: {e}", - json_file.display() - )); - } - all_addrs.extend(addrs); - all_results.extend(results); - } - eprintln!( - "[rs_kernel_check_anon_consts] rows total: {:>8.1?} ({} name(s))", - total_start.elapsed(), - ctx.seeds.len(), - ); - return build_anon_result_array(&all_addrs, &all_results); - } - - let mut seed_items: Vec = Vec::new(); - for &(_, i) in &ctx.seeds { - if !seed_items.contains(&i) { - seed_items.push(i); - } - } - - // The check set. Subject-only: the seeds' own items. Full-closure: every - // work item inside the seeds' dependency closure (union across seeds). - // Both add the closure of the binding's records and certificates. - let selected = match verdict_work_items(&ctx, &seed_items, !skip_deps) { - Ok(work) => work, - Err(error) => return LeanIOResult::error_string(&error), - }; - let (work, addrs) = index_anon_work(selected); - eprintln!( - "[rs_kernel_check_anon_consts] check set: {} items, {} targets, {}", - work.len(), - addrs.len(), - if skip_deps { "subject-only" } else { "full-closure" }, - ); - - let failure_log: Option> = match fail_out_path.as_deref() { - None => None, - Some(out_path) => match FailureLog::open(out_path, &path, addrs.len()) { - Ok(log) => { - eprintln!( - "[rs_kernel_check_anon_consts] streaming failures to {out_path}" - ); - Some(Arc::new(log)) - }, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_check_anon_consts: failed to open fail-out file {out_path}: {e}" - )); - }, - }, - }; - - let total = addrs.len(); - let addrs_for_return = addrs.clone(); - let t3 = Instant::now(); - let results = match run_anon_checks_parallel( - ctx.source, - work, - addrs, - quiet, - failure_log.clone(), - ) { - Ok(r) => r, - Err(msg) => { - if let Some(log) = failure_log.as_ref() { - log.finalize(); - } - return build_uniform_error(total, &format!("[thread] {msg}")); - }, - }; - - let passed = results.iter().filter(|r| r.is_ok()).count(); - let failed = results.iter().filter(|r| r.is_err()).count(); - eprintln!( - "[rs_kernel_check_anon_consts] {passed}/{total} passed, {failed} failed ({:.1?})", - t3.elapsed() - ); - eprintln!( - "[rs_kernel_check_anon_consts] total: {:>8.1?}", - total_start.elapsed() - ); - if let Some(log) = failure_log.as_ref() { - log.finalize(); - eprintln!( - "[rs_kernel_check_anon_consts] streamed {} failure(s) to fail-out", - log.count() - ); - } - - build_anon_result_array(&addrs_for_return, &results) -} - -/// FFI: extract the named constants' dependency closure from a serialized -/// env into a standalone `.ixe` — genuine constant bytes, blobs, and -/// reducibility hints (via the anon view), plus every closure constant's -/// Named entry (via the full view) so names still resolve downstream — all -/// without recompiling from source. Each name extracts its covering work -/// item (a mutual-block member pulls the whole block). -#[unsafe(no_mangle)] -pub extern "C" fn rs_env_extract( - env_path: LeanString>, - names: LeanArray>, - out_path: LeanString>, - quiet: LeanBool>, -) -> LeanIOResult { - use ix_kernel::anon_work::{ - block_of_addr, build_anon_work, build_sub_env_named, work_block_addr, - }; - - let quiet = quiet.to_bool(); - let path = env_path.to_string(); - let out = out_path.to_string(); - let names_vec: Vec = names.map(|obj| obj.as_string().to_string()); - if names_vec.is_empty() { - return LeanIOResult::error_string( - "rs_env_extract: no constant names given", - ); - } - - let bytes = match std::fs::read(&path) { - Ok(bytes) => bytes, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_env_extract: failed to read {path}: {e}" - )); - }, - }; - let mut slice: &[u8] = &bytes; - let full = match IxonEnv::get(&mut slice) { - Ok(env) => env, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_env_extract: failed to deserialize {path}: {e}" - )); - }, - }; - // Resolve displayed names → addresses through the full env's `named` - // metadata (the anon view discards it). Keys and requests both go - // through `normalize_displayed_name` (Lean `«»`-escaped ↔ Rust bare). - let by_name: FxHashMap = full - .named - .iter() - .map(|e| { - (normalize_displayed_name(&e.key().to_string()), e.value().addr.clone()) - }) - .collect(); - let mut resolved: Vec
= Vec::with_capacity(names_vec.len()); - let mut missing: Vec<&str> = Vec::new(); - for n in &names_vec { - match by_name.get(&normalize_displayed_name(n)) { - Some(a) => resolved.push(a.clone()), - None => missing.push(n), - } - } - if !missing.is_empty() { - return LeanIOResult::error_string(&format!( - "rs_env_extract: no constant(s) named [{}] in {path}", - missing.join(", ") - )); - } - - let mut slice: &[u8] = &bytes; - let anon = match IxonEnv::get_anon(&mut slice) { - Ok(env) => env, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_env_extract: failed to deserialize (anon) {path}: {e}" - )); - }, - }; - - // Roots: each name's covering work item's proven targets (standalone → - // itself; a mutual-block member → every sibling, checked atomically). - let work = match build_anon_work(&anon) { - Ok(work) => work, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_env_extract: build_anon_work: {e}" - )); - }, - }; - let by_block: FxHashMap = work - .iter() - .enumerate() - .map(|(i, w)| (work_block_addr(&anon, w), i)) - .collect(); - let mut roots: Vec
= Vec::new(); - for addr in &resolved { - let block = block_of_addr(&anon, addr); - match by_block.get(&block) { - Some(&i) => roots.extend(work[i].proven_targets()), - None => { - return LeanIOResult::error_string(&format!( - "rs_env_extract: no work item covers block {}…", - &block.hex()[..16] - )); - }, - } - } - - let sub_bytes = match build_sub_env_named(&anon, &full, &roots) { - Ok(b) => b, - Err(e) => { - return LeanIOResult::error_string(&format!("rs_env_extract: {e}")); - }, - }; - if let Err(e) = std::fs::write(&out, &sub_bytes) { - return LeanIOResult::error_string(&format!( - "rs_env_extract: failed to write {out}: {e}" - )); - } - if !quiet { - eprintln!( - "[rs_env_extract] {} name(s) → {} ({} bytes) from {path}", - names_vec.len(), - out, - sub_bytes.len(), - ); - } - LeanIOResult::ok(LeanOwned::box_usize(0)) -} - -// =========================================================================== -// Sharding profiler: run the anon kernel out of circuit over a `.ixe`, -// recording per-block heartbeats + the delta-unfold graph into a `.ixprof`. -// `profile_anon_ixe` below records the profile; `Ix/Cli/ShardCmd.lean` -// selects its consumer with `--profile`. -// =========================================================================== - -/// Summary returned by [`profile_anon_ixe`]. -pub struct ProfileStats { - pub targets: usize, - pub passed: usize, - pub failed: usize, - pub blocks: usize, - pub edges: usize, - pub bytes: usize, -} - -/// Map a constant address to its *block* (ingress unit): the `block` address of -/// a projection constant, otherwise the address itself. -fn profile_block_of(env: &IxonEnv, addr: &Address) -> Address { - match env.get_const(addr) { - Some(c) => match &c.info { - IxonCI::IPrj(p) => p.block.clone(), - IxonCI::CPrj(p) => p.block.clone(), - IxonCI::RPrj(p) => p.block.clone(), - IxonCI::DPrj(p) => p.block.clone(), - _ => addr.clone(), - }, - None => addr.clone(), - } -} - -/// Serialized byte length of a block constant (the ingress-cost / net weight). -#[allow(clippy::cast_possible_truncation)] // clamped to u32::MAX above -fn profile_block_size(env: &IxonEnv, block: &Address) -> u32 { - env - .get_const_bytes(block) - .map_or(0, |b| b.len().min(u32::MAX as usize) as u32) -} - -/// Build the STATIC block profile of an env — the profile-free analog of -/// [`build_block_profile`]: same [`BlockProfile`] shape, but derived from -/// the serialized constants alone, no kernel run. -/// -/// - Vertices: ingress units (a Muts block or a standalone constant), -/// `serialized_size` real, `const_count` = member constants, -/// `heartbeats` := size (report-only). -/// - Balance weight: the partitioner's vertex weight is `block_step_cost` -/// (a linear model over the op counters), so the byte weight is carried -/// in `subst` — `vweight ∝ size` exactly, matching the measured-best -/// bisection weight (`ix_kernel::shard::STATIC_OWNED_PER_BYTE` docs). -/// - Edges: the claim-walk relation (`ixon::shard_claim::walk_edges` — -/// positively-used refs + projection block pointers) at block -/// granularity, deduped, self-edges dropped. These are exactly the -/// edges that generate a shard's thin frontier, i.e. its ingress. -#[allow(clippy::cast_possible_truncation)] // block sizes clamped to u32::MAX -pub(crate) fn static_block_profile(env: &IxonEnv) -> BlockProfile { - use rayon::prelude::*; - let addrs: Vec
= - env.consts.iter().map(|e| e.key().clone()).collect(); - // Phase 1 (parallel): parse each constant exactly ONCE. Lazy mmap - // entries re-parse on every `get()` (see `LazyConstant`'s cache - // policy), so the home block is derived from the constant we already - // hold instead of `profile_block_of` (which would parse again), and - // edge targets are kept as raw addresses to be resolved in phase 2 - // through the addr→home table this pass produces. - struct ConstRow { - addr: Address, - home: Address, - targets: Vec
, - } - let rows: Vec = addrs - .par_iter() - .filter_map(|addr| { - let c = env.get_const(addr)?; - let home = match &c.info { - IxonCI::IPrj(p) => p.block.clone(), - IxonCI::CPrj(p) => p.block.clone(), - IxonCI::RPrj(p) => p.block.clone(), - IxonCI::DPrj(p) => p.block.clone(), - _ => addr.clone(), - }; - let mut targets = Vec::new(); - ixon::shard_claim::walk_edges(&c, &mut targets); - Some(ConstRow { addr: addr.clone(), home, targets }) - }) - .collect(); - // Phase 2 (serial, no parsing): addr→home table, per-block member - // counts, and block-level edges. A target absent from the table is a - // blob payload (or an unparseable constant) — skipped, matching the - // claim walk's blob sentinel. - let home_of: FxHashMap<&Address, &Address> = - rows.iter().map(|r| (&r.addr, &r.home)).collect(); - let mut counts: FxHashMap<&Address, u32> = FxHashMap::default(); - for r in &rows { - *counts.entry(&r.home).or_insert(0) += 1; - } - let mut builder = ProfileBuilder::new(); - for r in &rows { - for t in &r.targets { - if let Some(&pb) = home_of.get(t) - && *pb != r.home - { - builder.delta_edge(r.home.clone(), pb.clone()); - } - } - } - for (home, cnt) in counts { - // Size without materializing: `raw_bytes().len()` on the lazy entry - // (`profile_block_size` would copy the bytes into a fresh Arc). - let size = env - .consts - .get(home) - .map_or(0, |e| e.value().raw_bytes().len().min(u32::MAX as usize)) - as u32; - let ops = OpCounts { subst_nodes: u64::from(size), ..OpCounts::default() }; - builder.block(home.clone(), u64::from(size), size, cnt, ops); - } - builder.finish() -} - -/// The blocks each block of `profile` references, by block id — every -/// constant's `refs` mapped to their home blocks, the edges a byte scope -/// (`Env::bfs_closure`) follows between blocks; projections reference -/// their own block and a block its wrappers, so those edges never leave a -/// block — and the blocks of the primitives the environment carries, which -/// every byte scope also starts from. A superset of the profile's walk -/// edges, which follow only the references a constant's body uses. -fn static_block_dependencies( - env: &IxonEnv, - profile: &BlockProfile, -) -> (Vec>, Vec) { - use ix_common::prim_addrs::PrimAddrs; - use rayon::prelude::*; - let id_of: FxHashMap<&Address, u32> = profile - .blocks() - .iter() - .enumerate() - .map(|(i, b)| (&b.addr, u32::try_from(i).expect("block ids are u32"))) - .collect(); - let addrs: Vec
= - env.consts.iter().map(|e| e.key().clone()).collect(); - let rows: Vec<(Address, Address, Vec
)> = addrs - .par_iter() - .filter_map(|addr| { - let c = env.get_const(addr)?; - let home = match &c.info { - IxonCI::IPrj(p) => p.block.clone(), - IxonCI::CPrj(p) => p.block.clone(), - IxonCI::RPrj(p) => p.block.clone(), - IxonCI::DPrj(p) => p.block.clone(), - _ => addr.clone(), - }; - Some((addr.clone(), home, c.refs.clone())) - }) - .collect(); - // Every constant's block id, projections included; a blob has none. - let block_of: FxHashMap<&Address, u32> = rows - .iter() - .filter_map(|(addr, home, _)| id_of.get(home).map(|&id| (addr, id))) - .collect(); - let mut depends: Vec> = vec![Vec::new(); profile.num_blocks()]; - for (addr, _, refs) in &rows { - let Some(&consumer) = block_of.get(addr) else { continue }; - for r in refs { - if let Some(&producer) = block_of.get(r) - && producer != consumer - { - depends[consumer as usize].push(producer); - } - } - } - for d in &mut depends { - d.sort_unstable(); - d.dedup(); - } - let mut primitives: Vec = PrimAddrs::lean_parity_table() - .iter() - .filter_map(|(_, hex)| Address::from_hex(hex)) - .chain(PrimAddrs::reserved_marker_addrs().iter().map(|(_, a)| a.clone())) - .filter_map(|a| block_of.get(&a).copied()) - .collect(); - primitives.sort_unstable(); - primitives.dedup(); - (depends, primitives) -} - -/// FFI: partition a `.ixe` with the STATIC strategy — no out-of-circuit -/// profiling run. An explicit nonzero `num_shards` fixes the count. Otherwise, -/// `ram_gib` seeds it from the static block-shape score after the profile is -/// loaded ([`ix_kernel::shard::static_seed_shards`]). Builds the static block -/// profile ([`static_block_profile`]), then either the byte-balanced min-cut -/// over the walk-edge nets with the predicted-cost rebalance post-pass -/// (`ix_kernel::shard::shard_static`, `layout` = `mincut`) or contiguous -/// ranges of a dependency order (`ix_kernel::shard::shard_static_ordered`, -/// `layout` = `ordered`). Writes a `.ixes` manifest. -#[allow(clippy::cast_precision_loss)] // balance_pct is a small percentage -#[unsafe(no_mangle)] -pub extern "C" fn rs_shard_env_static( - env_path: LeanString>, - requested_shards: usize, - ram_gib: u64, - balance_pct: u64, - layout: LeanString>, - out_path: LeanString>, - exec_ahead: usize, -) -> LeanIOResult { - let path = env_path.to_string(); - let layout = layout.to_string(); - if layout != "mincut" && layout != "ordered" { - return LeanIOResult::error_string(&format!( - "rs_shard_env_static: unknown layout `{layout}` (mincut or ordered)" - )); - } - if requested_shards == 0 && ram_gib == 0 { - return LeanIOResult::error_string( - "rs_shard_env_static: pass a positive shard count or RAM budget", - ); - } - let balance = (balance_pct as f64) / 100.0; - let out = out_path.to_string(); - let out_opt = if out.is_empty() { None } else { Some(out.as_str()) }; - let t0 = Instant::now(); - let env = match IxonEnv::get_anon_mmap(std::path::Path::new(&path)) { - Ok(e) => e, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_shard_env_static: failed to load {path}: {e}" - )); - }, - }; - let t_load = t0.elapsed(); - let t1 = Instant::now(); - let profile = static_block_profile(&env); - eprintln!( - "[rs_shard_static] env load {:.1?}, static graph {:.1?} ({} blocks, {} edges)", - t_load, - t1.elapsed(), - profile.num_blocks(), - profile.num_edges() - ); - let num_shards = if requested_shards > 0 { - requested_shards - } else if cfg!(feature = "cuda") { - // The trace-shard prover: seed against one execution's record share, - // computed as `ix prove --ixe --ixes` computes it. - let cells = crate::aiur::protocol::trace_shard_max_cells(); - let budget = usize::try_from(ram_gib << 30).unwrap_or(usize::MAX); - let share = - crate::aiur::protocol::record_share(budget, cells, exec_ahead + 2); - let tenths = - u32::try_from(share.saturating_mul(10) >> 30).unwrap_or(u32::MAX); - let share_gib = f64::from(tenths) / 10.0; - let bytes = ix_kernel::shard::static_env_bytes(&profile); - let n = ix_kernel::shard::gpu_seed_shards(&profile, share_gib); - eprintln!( - "[shard] trace-shard seed (cuda build): bytes={bytes:.4e}; round({} x (bytes/{:.4e}) x ({} GiB / {share_gib:.1} GiB record share: {ram_gib} GiB per worker, {cells} cells, {exec_ahead} executions ahead)) -> {n} shard(s) (heuristic; the record cap names any shard over its share)", - ix_kernel::shard::GPU_SEED_REFERENCE_SHARDS, - ix_kernel::shard::GPU_SEED_REFERENCE_BYTES, - ix_kernel::shard::GPU_SEED_REFERENCE_SHARE_GIB, - ); - n - } else { - let score = ix_kernel::shard::static_env_score(&profile); - let n = ix_kernel::shard::static_seed_shards(&profile, ram_gib); - eprintln!( - "[shard] static seed (cpu prover model): score={score:.3e}; round({} x (score/{:.3e})^{:.2} x ({}/{ram_gib})^{:.2}) -> {n} shard(s) (heuristic; gated execution corrects every boundary)", - ix_kernel::shard::STATIC_SEED_REFERENCE_SHARDS, - ix_kernel::shard::STATIC_SEED_REFERENCE_SCORE, - ix_kernel::shard::STATIC_SEED_SCORE_EXPONENT, - ix_kernel::shard::STATIC_SEED_REFERENCE_RAM_GIB, - ix_kernel::shard::STATIC_SEED_BUDGET_EXPONENT, - ); - n - }; - let report = if layout == "ordered" { - let (depends, primitives) = static_block_dependencies(&env, &profile); - ix_kernel::shard::shard_static_ordered( - &profile, - &depends, - &primitives, - num_shards, - out_opt, - ) - } else { - ix_kernel::shard::shard_static(&profile, num_shards, balance, out_opt) - }; - match report { - Ok(report) => { - eprintln!("[rs_shard_static]\n{report}"); - LeanIOResult::ok(LeanOwned::box_usize(0)) - }, - Err(e) => LeanIOResult::error_string(&format!("rs_shard_env_static: {e}")), - } -} - -/// FFI: write a `.ixes` manifest describing an EXPLICIT partition — the -/// block lists a prove run actually produced (splits included), so the -/// emitted manifest is the one `ix verify --ixes` can check the run's -/// proofs against, and the one that seeds the next run with a -/// partition that already fits. -/// -/// `shards_blob` encodes, per shard, a 4-byte LE block count followed -/// by that many 32-byte block addresses. Every profile block must -/// appear in EXACTLY one shard: a prove partition covers the env by -/// construction (splits partition their parent), so a gap or duplicate -/// here is an input error, not a policy choice. -/// -/// `peaks_blob`: one 8-byte LE measured prover peak per shard in shard -/// order — the run's `peak_prove_bytes` measurements, recorded on each -/// manifest shard for schedulers to bin-pack on. -/// -/// Takes the caller's live `EnvHandle`: both call sites hold one for -/// this env already, so the emit costs a profile build, not an env -/// re-parse. -#[allow(clippy::cast_possible_truncation)] // block/shard ids are u32 by construction -#[unsafe(no_mangle)] -pub extern "C" fn rs_shard_manifest_from_partition( - env_handle: LeanExternal< - ixvm_codegen::env_handle::EnvHandle, - LeanBorrowed<'_>, - >, - shards_blob: LeanByteArray>, - peaks_blob: LeanByteArray>, - out_path: LeanString>, -) -> LeanIOResult { - let out = out_path.to_string(); - let profile = static_block_profile(&env_handle.get().env); - let block_id: FxHashMap = profile - .blocks() - .iter() - .enumerate() - .map(|(i, b)| (b.addr.clone(), i as u32)) - .collect(); - let lists = - match crate::aiur::protocol::decode_addr_lists(shards_blob.as_bytes()) { - Ok(v) => v, - Err(e) => return LeanIOResult::error_string(&e), - }; - let num_shards = lists.len(); - let mut shard_of = vec![u32::MAX; profile.num_blocks()]; - for (k, list) in lists.iter().enumerate() { - for addr in list { - let Some(&b) = block_id.get(addr) else { - return LeanIOResult::error_string(&format!( - "shard {k}: block {} not in the env's block profile", - addr.hex() - )); - }; - if shard_of[b as usize] != u32::MAX { - return LeanIOResult::error_string(&format!( - "block {} appears in shards {} and {k}", - addr.hex(), - shard_of[b as usize] - )); - } - shard_of[b as usize] = k as u32; - } - } - let uncovered = shard_of.iter().filter(|&&s| s == u32::MAX).count(); - if uncovered > 0 { - return LeanIOResult::error_string(&format!( - "partition covers {} of {} blocks ({uncovered} missing)", - profile.num_blocks() - uncovered, - profile.num_blocks() - )); - } - let peaks: Vec = peaks_blob - .as_bytes() - .as_chunks::<8>() - .0 - .iter() - .map(|c| u64::from_le_bytes(*c)) - .collect(); - match ix_kernel::shard::shard_manifest_explicit( - &profile, &shard_of, num_shards, &peaks, &out, - ) { - Ok(summary) => { - eprintln!("[shard-manifest] {out}: {summary}"); - LeanIOResult::ok(LeanOwned::box_usize(0)) - }, - Err(e) => LeanIOResult::error_string(&format!( - "rs_shard_manifest_from_partition: {e}" - )), - } -} - -/// Decode `rs_shard_manifest_refine`'s refinements blob: `count(u32)`, then -/// per refined leaf `id(u32) ‖ nparts(u32)` and per part -/// `nblocks(u32) ‖ 32·nblocks ‖ peak(u64)`. -fn decode_refinements( - bytes: &[u8], -) -> Result, String> { - struct Cur<'a> { - b: &'a [u8], - p: usize, - } - impl<'a> Cur<'a> { - fn take(&mut self, n: usize) -> Result<&'a [u8], String> { - let end = self - .p - .checked_add(n) - .filter(|&end| end <= self.b.len()) - .ok_or("refinements blob: truncated")?; - let s = &self.b[self.p..end]; - self.p = end; - Ok(s) - } - fn u32(&mut self) -> Result { - Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()) as usize) - } - fn u64(&mut self) -> Result { - Ok(u64::from_le_bytes(self.take(8)?.try_into().unwrap())) - } - } - let mut c = Cur { b: bytes, p: 0 }; - let count = c.u32()?; - let mut out = Vec::with_capacity(count); - for _ in 0..count { - let id = u32::try_from(c.u32()?) - .map_err(|e| format!("refinements blob: bad id: {e}"))?; - let nparts = c.u32()?; - let mut parts = Vec::with_capacity(nparts); - let mut part_peaks = Vec::with_capacity(nparts); - for _ in 0..nparts { - let nblocks = c.u32()?; - let raw = c.take(nblocks * 32)?; - parts.push( - raw - .as_chunks::<32>() - .0 - .iter() - .map(|chunk| Address::from_slice(chunk).unwrap()) - .collect(), - ); - part_peaks.push(c.u64()?); - } - out.push(ix_kernel::shard::LeafRefinement { id, parts, part_peaks }); - } - if c.p != bytes.len() { - return Err("refinements blob: trailing bytes".into()); - } - Ok(out) -} - -/// FFI: refine an existing `.ixes` manifest by cutting some of its leaves -/// into parts (`ShardManifest::refine`): every other leaf keeps its block -/// list, record, id and place in the aggregation tree; a refined leaf's -/// place becomes a balanced subtree over its parts, part 0 keeping the -/// leaf's id and later parts taking fresh ids after the last existing one. -/// `measured_blob` is empty or one `u64` analytic prover peak per SOURCE -/// shard in id order (nonzero overrides the peak carried forward for an -/// unsplit leaf). The new partition is validated as an exact, disjoint -/// cover of the env's block profile; the manifest summary goes to stderr. -/// Returns the parts' new ids: `count(u32)`, then per refinement -/// `n(u32) ‖ n × id(u32)`. -#[allow(clippy::cast_possible_truncation)] // ids and counts are u32 by construction -#[unsafe(no_mangle)] -pub extern "C" fn rs_shard_manifest_refine( - env_handle: LeanExternal< - ixvm_codegen::env_handle::EnvHandle, - LeanBorrowed<'_>, - >, - source_path: LeanString>, - refinements_blob: LeanByteArray>, - measured_blob: LeanByteArray>, - out_path: LeanString>, -) -> LeanIOResult { - let source_path = source_path.to_string(); - let out = out_path.to_string(); - let fail = |e: String| { - LeanIOResult::error_string(&format!("rs_shard_manifest_refine: {e}")) - }; - let source = match std::fs::read(&source_path) - .map_err(|e| format!("read {source_path}: {e}")) - .and_then(|bytes| ix_kernel::shard::ShardManifest::from_bytes(&bytes)) - { - Ok(m) => m, - Err(e) => return fail(e), - }; - let refinements = match decode_refinements(refinements_blob.as_bytes()) { - Ok(r) => r, - Err(e) => return fail(e), - }; - let measured: Vec = measured_blob - .as_bytes() - .as_chunks::<8>() - .0 - .iter() - .map(|c| u64::from_le_bytes(*c)) - .collect(); - let profile = static_block_profile(&env_handle.get().env); - match ix_kernel::shard::shard_manifest_refine( - &profile, - &source, - &refinements, - &measured, - &out, - ) { - Ok((summary, part_ids)) => { - eprintln!("[shard-refine] {out}: {summary}"); - let mut bytes = Vec::new(); - bytes.extend_from_slice(&(part_ids.len() as u32).to_le_bytes()); - for ids in &part_ids { - bytes.extend_from_slice(&(ids.len() as u32).to_le_bytes()); - for id in ids { - bytes.extend_from_slice(&id.to_le_bytes()); - } - } - let ids: LeanOwned = LeanByteArray::from_bytes(&bytes).into(); - LeanIOResult::ok(ids) - }, - Err(e) => fail(e), - } -} - -/// FFI: dump the static block-level reference graph of a `.ixe` as text — -/// `block ` per ingress unit (a Muts block or a -/// standalone constant) and `edge ` per deduped -/// claim-walk edge (`ixon::shard_claim::walk_edges`, mapped to block -/// granularity, self-edges dropped). The graph is the input for offline -/// partitioner prototypes in the sharding campaign: the walk-edge -/// relation is exactly what generates a shard's thin frontier, so a -/// partition's ingress cost is computable from this file alone. Same -/// data as [`static_block_profile`], serialized for offline use. -#[allow(clippy::cast_possible_truncation)] // block ids are u32 by construction -#[unsafe(no_mangle)] -pub extern "C" fn rs_shard_static_graph( - env_path: LeanString>, - out_path: LeanString>, -) -> LeanIOResult { - let path = env_path.to_string(); - let out = out_path.to_string(); - let env = match IxonEnv::get_anon_mmap(std::path::Path::new(&path)) { - Ok(e) => e, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_shard_static_graph: failed to load {path}: {e}" - )); - }, - }; - let profile = static_block_profile(&env); - let mut buf = String::new(); - for b in profile.blocks() { - buf.push_str(&format!( - "block {} {} {}\n", - b.addr.hex(), - b.serialized_size, - b.const_count - )); - } - for c in 0..profile.num_blocks() as u32 { - let ca = profile.block(c).addr.hex(); - for &p in profile.producers(c) { - buf.push_str(&format!("edge {} {}\n", ca, profile.block(p).addr.hex())); - } - } - if let Err(e) = std::fs::write(&out, buf) { - return LeanIOResult::error_string(&format!( - "rs_shard_static_graph: failed to write {out}: {e}" - )); - } - eprintln!( - "[rs_shard_static_graph] {} blocks, {} edges → {}", - profile.num_blocks(), - profile.num_edges(), - out - ); - LeanIOResult::ok(LeanOwned::box_usize(0)) -} - -/// Print the general-purpose cost breakdown for `ix profile` — the kernel-work -/// metrics plus the legacy guest-cost and RAM estimates. -// `steps as f64` is a display-only cast for `{:.2e}` formatting; precision loss -// past 2⁵³ steps is irrelevant to a two-sig-fig estimate. -#[allow(clippy::cast_precision_loss)] -fn print_profile_summary( - env_path: &str, - sink: &ProfileSink, - profile: &BlockProfile, -) { - use ix_kernel::shard::{ - 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, 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(); - let steps: u64 = profile - .blocks() - .iter() - .map(block_step_cost) - .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)" - } else { - "" - }; - eprintln!( - "\n── ix profile: {env_path} ──\n\ - constants {} blocks {}\n\n\ - kernel work\n\ - \u{20}\u{20}heartbeats {hb:>14}\n\ - \u{20}\u{20}subst nodes {subst:>14}\n\ - \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\ - estimated guest 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, - ); -} - -/// Aggregate per-constant records into a block-level [`BlockProfile`]: map each -/// consumer/producer constant to its home block, attach serialized sizes, and -/// accumulate heartbeats + delta edges. -fn build_block_profile(env: &IxonEnv, merged: &ProfileSink) -> BlockProfile { - let mut builder = ProfileBuilder::new(); - // addr -> (home block, block serialized size), memoized. - let mut cache: FxHashMap = FxHashMap::default(); - let mut resolve = |a: &Address| -> (Address, u32) { - if let Some(v) = cache.get(a) { - return v.clone(); - } - let block = profile_block_of(env, a); - let size = profile_block_size(env, &block); - let v = (block, size); - cache.insert(a.clone(), v.clone()); - v - }; - for (consumer, rec) in &merged.records { - let (cblock, csize) = resolve(consumer); - 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, OpCounts::default()); - builder.delta_edge(cblock.clone(), pblock); - } - } - builder.finish() -} - -/// Run the anon kernel over `work`, with per-worker profile recording, and -/// return `(passed, failed, merged_sink)`. -// `map_err_ignore`: the discarded `Arc`/`PoisonError` carry no useful context. -#[allow(clippy::needless_pass_by_value, clippy::map_err_ignore)] -fn run_anon_profile_parallel( - source: ArtifactSource, - work: Vec, - isolate: bool, - quiet: bool, -) -> Result<(usize, usize, ProfileSink), String> { - let work_total = work.len(); - let worker_count = resolve_kernel_check_workers(work_total, quiet); - eprintln!( - "[rs_kernel_profile] profiling {work_total} work item(s) with {worker_count} worker(s), isolate={isolate}..." - ); - let work = Arc::new(work); - let next_index = Arc::new(AtomicUsize::new(0)); - let passed = Arc::new(AtomicUsize::new(0)); - let failed = Arc::new(AtomicUsize::new(0)); - let sinks: Arc>> = Arc::new(Mutex::new(Vec::new())); - - let mut handles: Vec> = - Vec::with_capacity(worker_count); - for worker_idx in 0..worker_count { - let source = source.clone(); - let work = Arc::clone(&work); - let next_index = Arc::clone(&next_index); - let passed = Arc::clone(&passed); - let failed = Arc::clone(&failed); - let sinks = Arc::clone(&sinks); - let handle = thread::Builder::new() - .name(format!("ix-kernel-profile-{worker_idx}")) - .stack_size(KERNEL_CHECK_STACK_SIZE) - .spawn(move || { - let mut checker = - ix_kernel::ixon_checker::IxonChecker::from_source(Ok(source)); - checker.set_profile_sink(ProfileSink::new(isolate)); - let clear_every = kernel_check_clear_every(); - let mut checks_since_clear = clear_every; - loop { - let work_idx = next_index.fetch_add(1, Ordering::Relaxed); - if work_idx >= work_total { - break; - } - // `clear_releasing_memory` preserves `profile_sink`, so recording - // accumulates across scheduled-block boundaries. - if checks_since_clear >= clear_every { - checker.clear_releasing_memory(); - checks_since_clear = 0; - } - let res = match &work[work_idx] { - AnonWorkItem::Block { block_addr, .. } => { - checker.check_owner(block_addr) - }, - AnonWorkItem::Standalone { addr, .. } => checker.check_const(addr), - }; - if res.is_ok() { - passed.fetch_add(1, Ordering::Relaxed); - } else { - failed.fetch_add(1, Ordering::Relaxed); - } - checks_since_clear += 1; - } - if let Some(sink) = checker.take_profile_sink() { - sinks.lock().unwrap().push(sink); - } - }) - .map_err(|e| format!("spawn profile worker: {e}"))?; - handles.push(handle); - } - - let mut panicked = false; - for h in handles { - if h.join().is_err() { - panicked = true; - } - } - if panicked { - return Err("profile worker panicked".to_string()); - } - - let mut merged = ProfileSink::new(isolate); - let collected = Arc::try_unwrap(sinks) - .map_err(|_| "profile sinks still shared".to_string())? - .into_inner() - .map_err(|_| "profile sinks mutex poisoned".to_string())?; - for s in collected { - merged.merge(s); - } - Ok((passed.load(Ordering::Relaxed), failed.load(Ordering::Relaxed), merged)) -} - -/// Profile a `.ixe` out of circuit and write the `.ixprof` sidecar. Pure-Rust -/// entry point (used by the FFI wrapper and Rust tests). -pub fn profile_anon_ixe( - path: &str, - out: &str, - isolate: bool, - quiet: bool, -) -> Result { - let load_start = Instant::now(); - let ixon_env = IxonEnv::get_anon_mmap(std::path::Path::new(path)) - .map_err(|e| format!("profile: mmap+deserialize {path}: {e}"))?; - eprintln!( - "[rs_kernel_profile] loaded {} consts in {:.1?}", - ixon_env.const_count(), - load_start.elapsed() - ); - // The map capture is quiescent; the original is retained only for reporting. - // Its mapped file must remain immutable until profiling and reporting end. - let source = - ArtifactSource::snapshot(&ixon_env).map_err(|error| error.to_string())?; - report_admission("rs_kernel_profile", &source); - let (work, addrs) = - index_anon_work(ix_kernel::anon_work::build_artifact_work(&source)?); - let targets = addrs.len(); - let env_arc = Arc::new(ixon_env); - let run_start = Instant::now(); - let (passed, failed, merged) = - run_anon_profile_parallel(source, work, isolate, quiet)?; - eprintln!( - "[rs_kernel_profile] checked {passed}/{} ({} failed) in {:.1?}", - passed + failed, - failed, - run_start.elapsed() - ); - let profile = build_block_profile(&env_arc, &merged); - print_profile_summary(path, &merged, &profile); - let bytes = profile.to_bytes(); - std::fs::write(out, &bytes).map_err(|e| format!("write {out}: {e}"))?; - eprintln!( - "[rs_kernel_profile] wrote {out}: {} blocks, {} delta edges, {} bytes", - profile.num_blocks(), - profile.num_edges(), - bytes.len() - ); - Ok(ProfileStats { - targets, - passed, - failed, - blocks: profile.num_blocks(), - edges: profile.num_edges(), - bytes: bytes.len(), - }) -} - -/// FFI: profile a `.ixe` out of circuit and write a `.ixprof` sidecar. -#[unsafe(no_mangle)] -pub extern "C" fn rs_kernel_profile_anon( - env_path: LeanString>, - out_path: LeanString>, - isolate: LeanBool>, - quiet: LeanBool>, -) -> LeanIOResult { - match profile_anon_ixe( - &env_path.to_string(), - &out_path.to_string(), - isolate.to_bool(), - quiet.to_bool(), - ) { - Ok(s) => { - eprintln!( - "[rs_kernel_profile] done: {} targets, {} blocks, {} edges, {} failed", - s.targets, s.blocks, s.edges, s.failed - ); - LeanIOResult::ok(LeanOwned::box_usize(0)) - }, - Err(e) => { - LeanIOResult::error_string(&format!("rs_kernel_profile_anon: {e}")) - }, - } -} - -/// FFI: partition a `.ixprof` into `num_shards` shards and write a `.ixes` -/// manifest. Prints a what-if report to stderr. -#[allow(clippy::cast_precision_loss)] // balance_pct is a small percentage -#[unsafe(no_mangle)] -pub extern "C" fn rs_shard_esp( - esp_path: LeanString>, - num_shards: usize, - balance_pct: u64, - parallelism: usize, - out_path: LeanString>, -) -> LeanIOResult { - let num_shards = num_shards.max(1); - let parallelism = parallelism.max(1); - let out = out_path.to_string(); - let out_opt = if out.is_empty() { None } else { Some(out.as_str()) }; - let balance = (balance_pct as f64) / 100.0; - match ix_kernel::shard::shard_esp( - &esp_path.to_string(), - num_shards, - balance, - parallelism, - out_opt, - ) { - Ok(report) => { - eprintln!("[rs_shard]\n{report}"); - LeanIOResult::ok(LeanOwned::box_usize(0)) - }, - Err(e) => LeanIOResult::error_string(&format!("rs_shard_esp: {e}")), - } -} - -/// Total system RAM in GiB from `/proc/meminfo` (Linux); `None` if unreadable. -fn system_ram_gib() -> Option { - let s = std::fs::read_to_string("/proc/meminfo").ok()?; - let rest = s.lines().find_map(|l| l.strip_prefix("MemTotal:"))?; - let kib: f64 = rest.trim().trim_end_matches("kB").trim().parse().ok()?; - Some(kib / 1024.0 / 1024.0) // KiB → GiB -} - -/// FFI: partition a `.ixprof` to a per-shard cycle/RAM budget and write a -/// `.ixes` manifest. `max_cycles` is a guest-STEP cap; if `ram_gib` > 0 it is -/// converted via the measured prover RAM model and overrides `max_cycles`. Pass -/// 0 for both to default the budget to detected system RAM. -#[allow(clippy::cast_precision_loss)] -#[unsafe(no_mangle)] -pub extern "C" fn rs_shard_esp_cap( - esp_path: LeanString>, - max_cycles: u64, - ram_gib: u64, - balance_pct: u64, - parallelism: usize, - out_path: LeanString>, -) -> LeanIOResult { - let mc = max_cycles; - let mut ram = ram_gib as f64; - let parallelism = parallelism.max(1); - let balance = (balance_pct as f64) / 100.0; - // No explicit cap → default the RAM budget to detected system RAM. - if mc == 0 && ram <= 0.0 { - match system_ram_gib() { - Some(g) => { - eprintln!( - "[rs_shard] no cap given; defaulting to detected system RAM: {g:.0} GiB" - ); - ram = g; - }, - None => { - return LeanIOResult::error_string( - "could not detect system RAM; pass --max-ram G or --max-cycles N", - ); - }, - } - } - let cap = - if ram > 0.0 { ix_kernel::shard::cycle_cap_for_ram(ram) } else { mc }; - if cap == 0 { - return LeanIOResult::error_string( - "shard cap: RAM budget too small (must clear the ~50 GiB prover base)", - ); - } - let out = out_path.to_string(); - let out_opt = if out.is_empty() { None } else { Some(out.as_str()) }; - match ix_kernel::shard::shard_esp_cap( - &esp_path.to_string(), - cap, - balance, - parallelism, - out_opt, - ) { - Ok(report) => { - eprintln!("[rs_shard]\n{report}"); - LeanIOResult::ok(LeanOwned::box_usize(0)) - }, - Err(e) => LeanIOResult::error_string(&format!("rs_shard_esp_cap: {e}")), - } -} - -#[cfg(test)] -mod tests { - use super::{compact_in_flight_label, resolve_kernel_check_workers_from}; - - #[test] - fn explicit_kernel_check_workers_wins_when_positive() { - assert_eq!( - resolve_kernel_check_workers_from(3, false, Some("8"), true, 2), - 8 - ); - } - - #[test] - fn zero_or_invalid_worker_override_falls_through() { - assert_eq!( - resolve_kernel_check_workers_from(10, true, Some("0"), false, 4), - 4 - ); - assert_eq!( - resolve_kernel_check_workers_from(10, true, Some("nope"), false, 4), - 4 - ); - } - - #[test] - fn no_par_and_verbose_force_serial_without_override() { - assert_eq!(resolve_kernel_check_workers_from(10, true, None, true, 4), 1); - assert_eq!(resolve_kernel_check_workers_from(10, false, None, false, 4), 1); - } - - #[test] - fn default_parallelism_is_clamped_to_total() { - assert_eq!(resolve_kernel_check_workers_from(3, true, None, false, 16), 3); - assert_eq!(resolve_kernel_check_workers_from(10, true, None, false, 0), 1); - assert_eq!(resolve_kernel_check_workers_from(0, true, None, false, 16), 1); - } - - #[test] - fn compact_in_flight_label_preserves_index_and_tail() { - let label = - "[123/456] _private.Std.Tactic.BVDecide.LRAT.Internal.Formula.Proof"; - let compact = compact_in_flight_label(label, 40); - assert!(compact.starts_with("[123/456] ...")); - assert!(compact.ends_with("Internal.Formula.Proof")); - assert!(compact.chars().count() <= 40); - } - - #[test] - fn compact_in_flight_label_handles_tiny_limits() { - assert_eq!(compact_in_flight_label("[1/2] Very.Long.Name", 0), ""); - assert_eq!(compact_in_flight_label("[1/2] Very.Long.Name", 2), "[1"); - } -} - -#[cfg(test)] -mod planner_tests { - use super::build_check_work; - use ix_common::address::Address; - use ix_common::env::Name; - use ix_kernel::role_admission::ArtifactSource; - use ixon::constant::{ConstantInfo, defn_proj_address, defn_proj_constant}; - use ixon::env::{Env, Named}; - use ixon::metadata::{ConstantMeta, ConstantMetaInfo, ExprMeta}; - use rustc_hash::FxHashMap; - - const GOOD: &[u8] = include_bytes!( - "../../kernel/tests/fixtures/serialized_units/homogeneous-good.ixe" - ); - - fn register(env: &Env, label: &str, address: Address) -> Name { - let name = Name::str(Name::anon(), label.into()); - let own = Address::from_blake3_hash(*name.get_hash()); - env.store_name(own.clone(), name.clone()); - env.register_name( - name.clone(), - Named::new( - address, - ConstantMeta { - info: ConstantMetaInfo::Def { - name: own.clone(), - lvls: vec![], - all: vec![own.clone()], - ctx: vec![own], - arena: ExprMeta::default(), - type_root: 0, - value_root: 0, - }, - ..ConstantMeta::default() - }, - ), - ); - name - } - - /// Names share a work item, and so a verdict, only through an - /// authenticated owner: a genuinely serialized projection whose `block` - /// field names a real owner but whose index that owner lacks is scheduled - /// on its own, not as an alias of the owner's valid members. - #[test] - fn invalid_projection_does_not_alias_its_claimed_owner() { - let mut input = GOOD; - let env = Env::get(&mut input).unwrap(); - let owner = env - .consts - .iter() - .find_map(|entry| { - matches!(entry.value().get().unwrap().info, ConstantInfo::Muts(_)) - .then(|| entry.key().clone()) - }) - .unwrap(); - let invalid = defn_proj_constant(99, owner.clone()); - let invalid_address = invalid.commit().0; - env.store_const(invalid_address.clone(), invalid); - let names = vec![ - register(&env, "member0", defn_proj_address(0, &owner)), - register(&env, "member1", defn_proj_address(1, &owner)), - register(&env, "invalid", invalid_address), - ]; - let source = ArtifactSource::snapshot(&env).unwrap(); - let work = build_check_work( - &env, - &source, - &names, - &[true, true, true], - &FxHashMap::default(), - ); - let groups: Vec> = - work.iter().map(|item| item.aliases.clone()).collect(); - assert_eq!(groups, vec![vec![0, 1], vec![2]]); - } -} - -/// Default threshold at and above which a completed check is "slow" enough to -/// keep a persistent line in quiet mode. Override with -/// `IX_KERNEL_CHECK_SLOW_MS`. -const DEFAULT_SLOW_THRESHOLD: Duration = Duration::from_secs(7); - -/// Default threshold for a one-shot "still checking ..." line when an active -/// parallel check has been in-flight for a long time. Override with -/// `IX_KERNEL_CHECK_ACTIVE_SLOW_MS`; set it to `0` to disable the notice. -const DEFAULT_ACTIVE_SLOW_THRESHOLD: Duration = Duration::from_secs(30); - -const DEFAULT_IN_FLIGHT_LIMIT: usize = 3; -const DEFAULT_IN_FLIGHT_LABEL_CHARS: usize = 120; -const DEFAULT_CHECK_CLEAR_EVERY: usize = 1; - -fn env_duration_ms(var: &str, default: Duration) -> Duration { - std::env::var(var) - .ok() - .and_then(|s| s.parse::().ok()) - .map_or(default, Duration::from_millis) -} - -fn env_duration_ms_optional(var: &str, default: Duration) -> Option { - let ms = std::env::var(var) - .ok() - .and_then(|s| s.parse::().ok()) - .unwrap_or_else(|| u64::try_from(default.as_millis()).unwrap_or(u64::MAX)); - if ms == 0 { None } else { Some(Duration::from_millis(ms)) } -} - -fn env_usize(var: &str, default: usize) -> usize { - std::env::var(var) - .ok() - .and_then(|s| s.parse::().ok()) - .unwrap_or(default) -} - -fn kernel_check_slow_threshold() -> Duration { - env_duration_ms("IX_KERNEL_CHECK_SLOW_MS", DEFAULT_SLOW_THRESHOLD) -} - -fn kernel_check_clear_every() -> usize { - env_usize("IX_KERNEL_CHECK_CLEAR_EVERY", DEFAULT_CHECK_CLEAR_EVERY).max(1) -} - -/// Threshold (max cache len) above which a per-block diagnostic line is -/// emitted, when `IX_KERNEL_CHECK_DIAG=1`. Default 100k entries — empirically -/// well above the typical mathlib block, so only the heavy outliers print. -/// Override with `IX_KERNEL_CHECK_DIAG_THRESHOLD=N`. -fn kernel_check_diag_threshold() -> Option { - let enabled = matches!( - std::env::var("IX_KERNEL_CHECK_DIAG").as_deref(), - Ok("1" | "true" | "on" | "yes") - ); - if !enabled { - return None; - } - Some(env_usize("IX_KERNEL_CHECK_DIAG_THRESHOLD", 100_000)) -} - -fn kernel_check_mem_stats_enabled() -> bool { - // Default ON: RSS via /proc/self/status + DashMap.len() is one syscall and - // one atomic load per progress tick (~2s). Negligible overhead, and the - // suffix is the primary signal for diagnosing memory growth across a long - // env-check run. Explicit `IX_KERNEL_CHECK_MEM_STATS=0|false|off|no` opts - // out for callers who want a clean line. - !matches!( - std::env::var("IX_KERNEL_CHECK_MEM_STATS").as_deref(), - Ok("0" | "false" | "off" | "no") - ) -} - -/// Emit a per-block cache-size diagnostic when the just-finished block -/// pushed any single cache past `threshold` entries, or when this block -/// set a new per-worker peak. Used only with `IX_KERNEL_CHECK_DIAG=1`. -#[allow(clippy::too_many_arguments)] -fn log_block_diag_if_big( - sizes: ix_kernel::env::KEnvCacheSizes, - worker_idx: usize, - work_idx: usize, - work_total: usize, - outcome: &CheckOutcome, - threshold: usize, - worker_peak_cache: &mut usize, - progress: &ParallelProgress, -) { - let max_cache = sizes.max(); - let is_new_peak = max_cache > *worker_peak_cache; - let exceeds_threshold = max_cache >= threshold; - if !is_new_peak && !exceeds_threshold { - return; - } - if is_new_peak { - *worker_peak_cache = max_cache; - } - let elapsed = outcome - .elapsed - .map_or_else(|| "?".to_string(), |d| format!("{:.1}s", d.as_secs_f64())); - let tag = if is_new_peak { "[diag-peak]" } else { "[diag-big]" }; - progress.log(&format!( - "{tag} w={worker_idx} block={}/{} ({}) elapsed={elapsed} max={max_cache} {sizes}", - work_idx + 1, - work_total, - outcome.display, - )); -} - -fn current_rss_mib() -> Option { - let status = std::fs::read_to_string("/proc/self/status").ok()?; - for line in status.lines() { - let Some(rest) = line.strip_prefix("VmRSS:") else { - continue; - }; - let kb = rest.split_whitespace().next()?.parse::().ok()?; - return Some(kb.div_ceil(1024)); - } - None -} - -fn kernel_check_mem_suffix(peak_rss_mib: Option<&AtomicU64>) -> String { - if !kernel_check_mem_stats_enabled() { - return String::new(); - } - let rss_now = current_rss_mib(); - if let (Some(now), Some(peak)) = (rss_now, peak_rss_mib) { - // Monotonic max: load-then-CAS loop, but a relaxed fetch_max is simpler. - peak.fetch_max(now, Ordering::Relaxed); - } - let rss = - rss_now.map_or_else(|| "unknown".to_string(), |mib| format!("{mib}MiB")); - format!(" · mem: rss={rss}") -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum CheckStatus { - Checked, - CompileFailed, - NotFound, -} - -#[derive(Clone)] -struct CheckOutcome { - progress_index: usize, - progress_total: usize, - display: String, - should_pass: bool, - result: CheckRes, - status: CheckStatus, - elapsed: Option, - peak: Option, -} - -impl CheckOutcome { - fn prefix(&self) -> String { - format!( - " [{}/{}] {}", - self.progress_index + 1, - self.progress_total, - self.display - ) - } - - fn err_msg(&self) -> &str { - match &self.result { - Ok(()) => "", - Err((_kind, msg)) => msg, - } - } - - fn is_expected(&self) -> bool { - self.result.is_ok() == self.should_pass - } - - fn is_slow(&self, slow_threshold: Duration) -> bool { - self.elapsed.is_some_and(|elapsed| elapsed >= slow_threshold) - } - - fn checked_suffix(&self, slow_threshold: Duration) -> String { - let elapsed = self.elapsed.unwrap_or_default(); - let peak = self.peak.unwrap_or_default(); - let suffix = match (&self.result, self.should_pass) { - (Ok(()), true) => format!("ok ({elapsed:.1?}, depth={peak})"), - (Ok(()), false) => { - format!("UNEXPECTED PASS ({elapsed:.1?}, depth={peak})") - }, - (Err((_kind, msg)), false) => { - format!("REJECTED ({elapsed:.1?}): {msg}") - }, - (Err((_kind, msg)), true) => { - format!("FAIL ({elapsed:.1?}, depth={peak}): {msg}") - }, - }; - - if self.is_slow(slow_threshold) && self.is_expected() { - format!("{suffix} [slow]") - } else { - suffix - } - } -} - -fn check_one_const( - i: usize, - progress_index: usize, - progress_total: usize, - ixon_env: &IxonEnv, - lookups: &IxonIngressLookups, - names: &[Name], - expect_pass: &[bool], - ungrounded: &FxHashMap, - checker: &mut ArtifactChecker, - mut before_kernel_check: F, -) -> CheckOutcome -where - F: FnMut(&str), - M::MField>: CheckDupLevelParams, -{ - let name = &names[i]; - let should_pass = expect_pass.get(i).copied().unwrap_or(true); - // In anon mode, surface the content address in the per-constant log - // line — the kernel itself doesn't see names. We still accept names - // as input (and resolve them via `ixon_env.named` at the FFI - // scheduling layer), so the user's identifier is preserved in the - // CLI surface; only the kernel-visible progress label switches to a - // hash. Falls back to the name if the address lookup fails. - let display = if M::HAS_META { - name.pretty() - } else { - match ixon_env.lookup_name(name) { - Some(named) => format!("#{}", named.addr.hex()), - None => name.pretty(), - } - }; - - if let Some(msg) = ungrounded.get(name) { - return CheckOutcome { - progress_index, - progress_total, - display, - should_pass, - result: Err((ErrKind::Compile, msg.clone())), - status: CheckStatus::CompileFailed, - elapsed: None, - peak: None, - }; - } - - let prefix = - format!(" [{}/{}] {display}", progress_index + 1, progress_total); - before_kernel_check(&prefix); - - let tc_start = Instant::now(); - checker.set_debug_label(display.clone()); - let result = checker - .check_name(name) - .map_err(|error| format_tc_error(&error, ixon_env, lookups)); - let peak = checker.last_check().def_eq_peak; - let status = if result - .as_ref() - .err() - .is_some_and(|message| message.contains("missing Named entry")) - { - CheckStatus::NotFound - } else { - CheckStatus::Checked - }; - let elapsed = tc_start.elapsed(); - - CheckOutcome { - progress_index, - progress_total, - display, - should_pass, - result: result.map_err(|msg| (ErrKind::Kernel, msg)), - status, - elapsed: Some(elapsed), - peak: Some(peak), - } -} - -// Owned arguments are consumed via the worker pool but only borrowed in this -// function body — clippy flags the by-value receivers, but transferring -// ownership keeps the call sites simpler. -#[allow(clippy::needless_pass_by_value)] -fn check_consts_loop( - ixon_env: Arc, - source: ArtifactSource, - lookups: Arc, - names: Vec, - expect_pass: Vec, - ungrounded: FxHashMap, - work: Vec, - quiet: bool, - failure_log: Option>, -) -> Vec -where - M::MField>: CheckDupLevelParams, -{ - let total = names.len(); - let work_total = work.len(); - let mut results: Vec> = vec![None; total]; - let slow_threshold = kernel_check_slow_threshold(); - - // Terminal width is only needed for ephemeral clearing in quiet mode. In - // verbose mode we never rewrite, so the value is ignored. - let mut progress = Progress::new(quiet); - let mut checker = ArtifactChecker::::from_named_source(Ok(source)); - let clear_every = kernel_check_clear_every(); - let mut checks_since_clear = clear_every; - - for (work_idx, item) in work.iter().enumerate() { - if checks_since_clear >= clear_every { - checker.clear_releasing_memory(); - checks_since_clear = 0; - } - let outcome = check_one_const( - item.primary, - work_idx, - work_total, - &ixon_env, - &lookups, - &names, - &expect_pass, - &ungrounded, - &mut checker, - |prefix| progress.start(prefix), - ); - let prefix = outcome.prefix(); - - match outcome.status { - CheckStatus::CompileFailed => { - // Unexpected compile failure (should_pass=true) is a real problem and - // must persist. Expected rejections (should_pass=false) only persist in - // verbose mode; quiet mode drops them since they're part of the - // tutorial's bad-constant coverage, not user-visible failures. - if outcome.should_pass { - progress.persist(&format!( - "{prefix} ... FAIL (compile): {}", - outcome.err_msg() - )); - } else if !quiet { - progress.persist(&format!( - "{prefix} ... REJECTED (compile): {}", - outcome.err_msg() - )); - } - }, - CheckStatus::NotFound => { - // Not-found is always unexpected — the Lean side asked for a name - // that compile+ingress didn't produce. Always persist. - progress.persist(&format!("{prefix} ? not found")); - }, - CheckStatus::Checked => { - // Outcomes that must persist in quiet mode: - // - Unexpected pass / unexpected failure: user cares about these. - // - Slow runs with the expected outcome: useful for bisecting perf. - // - // Fast runs with the expected outcome stay ephemeral and are - // overwritten on the next iteration. - let must_persist = - !outcome.is_expected() || outcome.is_slow(slow_threshold); - progress.finish( - &prefix, - &outcome.checked_suffix(slow_threshold), - must_persist, - ); - }, - } - - for &result_idx in &item.aliases { - results[result_idx] = Some(outcome.result.clone()); - // Stream this seed's failure to the fail-out file (if any) as soon as - // it's known, so a long check grows the file incrementally rather - // than dumping everything at the end. - if let (Some(log), Err((_, msg))) = - (failure_log.as_ref(), outcome.result.as_ref()) - { - log.record(&names[result_idx].pretty(), msg); - } - } - checks_since_clear += 1; - } - - // Clear any trailing ephemeral label before the summary lines print. - progress.flush(); - - results - .into_iter() - .enumerate() - .map(|(i, result)| { - result.unwrap_or_else(|| { - Err((ErrKind::Kernel, format!("kernel-check missed result index {i}"))) - }) - }) - .collect() -} - -// ============================================================================= -// Parallel progress output -// ============================================================================= - -struct InFlightCheck { - label: String, - started: Instant, - reported_active_slow: bool, -} - -struct ParallelProgress { - total: usize, - quiet: bool, - started: Instant, - slow_threshold: Duration, - active_slow_threshold: Option, - in_flight_limit: usize, - in_flight_label_chars: usize, - done: AtomicUsize, - active: Mutex>>, - stop: AtomicBool, - print_lock: Mutex<()>, - /// Peak resident-set size (MiB) sampled at progress ticks. Updated by the - /// reporter and printed at end-of-run when memory stats are enabled. - peak_rss_mib: AtomicU64, -} - -impl ParallelProgress { - fn new(total: usize, worker_count: usize, quiet: bool) -> Self { - let active = std::iter::repeat_with(|| None).take(worker_count).collect(); - Self { - total, - quiet, - started: Instant::now(), - slow_threshold: kernel_check_slow_threshold(), - active_slow_threshold: env_duration_ms_optional( - "IX_KERNEL_CHECK_ACTIVE_SLOW_MS", - DEFAULT_ACTIVE_SLOW_THRESHOLD, - ), - in_flight_limit: env_usize( - "IX_KERNEL_CHECK_INFLIGHT", - DEFAULT_IN_FLIGHT_LIMIT, - ), - in_flight_label_chars: env_usize( - "IX_KERNEL_CHECK_NAME_CHARS", - DEFAULT_IN_FLIGHT_LABEL_CHARS, - ), - done: AtomicUsize::new(0), - active: Mutex::new(active), - stop: AtomicBool::new(false), - print_lock: Mutex::new(()), - peak_rss_mib: AtomicU64::new(0), - } - } - - fn spawn_reporter(progress: Arc) -> Option> { - let interval = kernel_check_progress_interval()?; - Some(thread::spawn(move || { - let check_interval = interval.min(Duration::from_millis(250)); - let mut last_print = Instant::now(); - while !progress.stop.load(Ordering::Relaxed) { - thread::park_timeout(check_interval); - if progress.stop.load(Ordering::Relaxed) { - break; - } - if last_print.elapsed() < interval { - continue; - } - last_print = Instant::now(); - progress.report(); - } - })) - } - - fn begin(&self, worker_idx: usize, prefix: &str) { - if let Some(slot) = self.active.lock().unwrap().get_mut(worker_idx) { - *slot = Some(InFlightCheck { - label: prefix.trim().to_string(), - started: Instant::now(), - reported_active_slow: false, - }); - } - } - - fn finish(&self, worker_idx: usize, outcome: &CheckOutcome) { - if let Some(slot) = self.active.lock().unwrap().get_mut(worker_idx) { - *slot = None; - } - self.done.fetch_add(1, Ordering::SeqCst); - if let Some(line) = self.persistent_line(outcome) { - self.log(&line); - } - } - - fn stop_reporter(&self) { - self.stop.store(true, Ordering::Relaxed); - } - - /// Print a one-shot summary of memory-related telemetry collected during - /// the run. No-op when `IX_KERNEL_CHECK_MEM_STATS` is disabled. - fn log_mem_summary(&self) { - if !kernel_check_mem_stats_enabled() { - return; - } - // Sample one more time so the suffix reflects post-completion state and - // peak gets a final fetch_max. - let final_rss = current_rss_mib(); - if let Some(now) = final_rss { - self.peak_rss_mib.fetch_max(now, Ordering::Relaxed); - } - let rss_now = final_rss - .map_or_else(|| "unknown".to_string(), |mib| format!("{mib}MiB")); - let peak = self.peak_rss_mib.load(Ordering::Relaxed); - let peak_str = - if peak == 0 { "unknown".to_string() } else { format!("{peak}MiB") }; - self.log(&format!( - "[rs_kernel_check] mem summary: peak_rss={peak_str} final_rss={rss_now}" - )); - } - - fn persistent_line(&self, outcome: &CheckOutcome) -> Option { - let prefix = outcome.prefix(); - match outcome.status { - CheckStatus::CompileFailed => { - let label = if outcome.should_pass { - "FAIL (compile)" - } else { - "REJECTED (compile)" - }; - Some(format!("{prefix} ... {label}: {}", outcome.err_msg())) - }, - CheckStatus::NotFound => Some(format!("{prefix} ? not found")), - CheckStatus::Checked => { - let must_persist = !self.quiet - || !outcome.is_expected() - || outcome.is_slow(self.slow_threshold); - if must_persist { - Some(format!( - "{prefix} ... {}", - outcome.checked_suffix(self.slow_threshold) - )) - } else { - None - } - }, - } - } - - fn report(&self) { - let done = self.done.load(Ordering::SeqCst); - // Progress reporting is approximate by nature; usize→f64 precision loss - // is acceptable for percentages and ETAs. - #[allow(clippy::cast_precision_loss)] - let pct = if self.total == 0 { - 100.0 - } else { - (done as f64 / self.total as f64) * 100.0 - }; - let elapsed = self.started.elapsed().as_secs_f64(); - #[allow(clippy::cast_precision_loss)] - let rate = if elapsed > 0.0 { done as f64 / elapsed } else { 0.0 }; - #[allow(clippy::cast_precision_loss)] - let eta = if rate > 0.0 && done < self.total { - format!(" · eta {:.0}s", (self.total - done) as f64 / rate) - } else { - String::new() - }; - - let (in_flight, active_slow_lines) = { - let mut active = self.active.lock().unwrap(); - let mut active_slow_lines = Vec::new(); - if let Some(active_slow_threshold) = self.active_slow_threshold { - for slot in active.iter_mut() { - if let Some(check) = slot.as_mut() { - let age = check.started.elapsed(); - if !check.reported_active_slow && age >= active_slow_threshold { - check.reported_active_slow = true; - active_slow_lines.push(format!( - "[rs_kernel_check] still checking {} after {:.0}s", - compact_in_flight_label( - &check.label, - self.in_flight_label_chars - ), - age.as_secs_f64() - )); - } - } - } - } - - let mut entries: Vec<_> = active - .iter() - .filter_map(|slot| { - slot.as_ref().map(|check| (check.started, check.label.clone())) - }) - .collect(); - entries.sort_by_key(|(started, _)| *started); - let in_flight = entries - .into_iter() - .take(self.in_flight_limit) - .map(|(started, label)| { - format!( - "{} ({:.0}s)", - compact_in_flight_label(&label, self.in_flight_label_chars), - started.elapsed().as_secs_f64() - ) - }) - .collect::>(); - (in_flight, active_slow_lines) - }; - let active_suffix = if in_flight.is_empty() { - String::new() - } else { - format!(" · in-flight: {}", in_flight.join(", ")) - }; - let mem_suffix = kernel_check_mem_suffix(Some(&self.peak_rss_mib)); - - self.log(&format!( - "[rs_kernel_check] {done}/{} ({pct:.1}%) · {:.1}/s · elapsed {:.0}s{eta}{mem_suffix}{active_suffix}", - self.total, - rate, - elapsed, - )); - for line in active_slow_lines { - self.log(&line); - } - } - - fn log(&self, line: &str) { - let _guard = self.print_lock.lock().unwrap(); - eprintln!("{line}"); - } -} - -fn kernel_check_progress_interval() -> Option { - let ms = std::env::var("IX_KERNEL_CHECK_PROGRESS_MS") - .ok() - .or_else(|| std::env::var("IX_PROGRESS_MS").ok()) - .and_then(|s| s.parse::().ok()) - .unwrap_or(2000); - if ms == 0 { None } else { Some(Duration::from_millis(ms)) } -} - -fn compact_in_flight_label(label: &str, max_chars: usize) -> String { - if max_chars == 0 { - return String::new(); - } - - let label = label.trim(); - if label.chars().count() <= max_chars { - return label.to_string(); - } - - const ELLIPSIS: &str = "..."; - if max_chars <= ELLIPSIS.len() { - return label.chars().take(max_chars).collect(); - } - - if let Some((head, tail)) = label.split_once("] ") { - let head = format!("{head}] "); - let head_chars = head.chars().count(); - if head_chars + ELLIPSIS.len() < max_chars { - let tail_chars = max_chars - head_chars - ELLIPSIS.len(); - return format!("{head}{ELLIPSIS}{}", last_chars(tail, tail_chars)); - } - } - - format!("{ELLIPSIS}{}", last_chars(label, max_chars - ELLIPSIS.len())) -} - -fn last_chars(s: &str, count: usize) -> String { - let chars: Vec = s.chars().collect(); - if chars.len() <= count { - return s.to_string(); - } - chars[chars.len() - count..].iter().collect() -} - -// ============================================================================= -// Progress output (ephemeral + verbose) -// ============================================================================= -// -// Quiet mode rewrites the "[i/N] name ..." line in place and only promotes a -// constant to a persistent log line when it's slow, unexpected, or otherwise -// interesting. Verbose mode keeps the original behaviour: every constant -// lives on its own line. -// -// The ANSI escape sequences used are a minimal subset supported by every -// terminal the test suite has been exercised on: -// \x1b[2K — clear entire current line -// \x1b[A — move cursor up one line -// \r — move cursor to column 0 -// -// Ported from ix_old's `rs_zero_check_env_impl` (see -// `ix_old/src/lean/ffi/check.rs` around line 1798). - -/// Progress reporter used by `check_consts_loop`. In verbose mode it simply -/// emits one line per constant; in quiet mode it rewrites the current line in -/// place and persists only the ones we explicitly ask it to. -struct Progress { - quiet: bool, - term_cols: usize, - /// Number of terminal lines the current ephemeral label occupies. Zero - /// means there's nothing to clear on the next `start`/`persist`. - ephemeral_lines: usize, -} - -impl Progress { - fn new(quiet: bool) -> Self { - let term_cols = if quiet { term_cols_stderr() } else { 0 }; - Self { quiet, term_cols, ephemeral_lines: 0 } - } - - /// Begin the progress indicator for a new constant. Quiet mode writes - /// `{prefix} ...` as an ephemeral label; verbose mode writes it as the - /// start of a line that will be completed by `finish`. - fn start(&mut self, prefix: &str) { - if self.quiet { - self.clear_ephemeral(); - let label = format!("{prefix} ..."); - eprint!("{label}"); - self.ephemeral_lines = lines_occupied(&label, self.term_cols); - } else { - eprint!("{prefix} ... "); - } - } - - /// Complete the current constant's progress line. `persist=true` always - /// prints a `{prefix} ... {suffix}` line; `persist=false` means quiet mode - /// leaves the ephemeral label to be overwritten on the next `start`. - /// Verbose mode always prints the suffix (continuing the line `start` - /// opened). - fn finish(&mut self, prefix: &str, suffix: &str, persist: bool) { - if self.quiet { - if persist { - self.clear_ephemeral(); - eprintln!("{prefix} ... {suffix}"); - } - // else: ephemeral label stays, overwritten on next `start` - } else { - eprintln!("{suffix}"); - } - } - - /// Print a persistent line that is NOT preceded by a `start`, e.g. the - /// not-found / ungrounded branches where we don't call `check_const`. - fn persist(&mut self, line: &str) { - if self.quiet { - self.clear_ephemeral(); - } - eprintln!("{line}"); - } - - /// Clear any trailing ephemeral output so subsequent prints start on a - /// fresh line. Safe to call when nothing is buffered. - fn flush(&mut self) { - if self.quiet { - self.clear_ephemeral(); - } - } - - /// Rewind over the currently-buffered ephemeral label (if any) so the next - /// write lands in column 0 of the topmost affected row. - fn clear_ephemeral(&mut self) { - let n = self.ephemeral_lines; - if n == 0 { - return; - } - if n == 1 { - eprint!("\x1b[2K\r"); - } else { - // Clear current line, then move up and clear each line above. - eprint!("\x1b[2K"); - for _ in 1..n { - eprint!("\x1b[A\x1b[2K"); - } - eprint!("\r"); - } - self.ephemeral_lines = 0; - } -} - -/// How many terminal rows a single `text` occupies in a `cols`-wide terminal. -/// -/// Uses byte length as a proxy for display width — good enough for ASCII -/// constant names; Unicode-heavy names may under-count, but the resulting -/// clear is at worst missing a trailing byte which the next label overwrites -/// anyway. -#[inline] -fn lines_occupied(text: &str, cols: usize) -> usize { - if cols == 0 { - return 1; - } - let len = text.len(); - if len == 0 { 1 } else { len.div_ceil(cols) } -} - -/// Terminal width of stderr via `ioctl(TIOCGWINSZ)`. Falls back to 80 when -/// stderr isn't a TTY (e.g. piped to `tee` or `less`) or the syscall fails. -fn term_cols_stderr() -> usize { - // `winsize` layout: [ws_row, ws_col, ws_xpixel, ws_ypixel]. - let mut ws = [0u16; 4]; - #[cfg(target_os = "linux")] - const TIOCGWINSZ: std::ffi::c_ulong = 0x5413; - #[cfg(target_os = "macos")] - const TIOCGWINSZ: std::ffi::c_ulong = 0x40087468; - #[cfg(any(target_os = "linux", target_os = "macos"))] - { - unsafe extern "C" { - fn ioctl(fd: i32, request: std::ffi::c_ulong, ...) -> i32; - } - let ret = unsafe { ioctl(2, TIOCGWINSZ, ws.as_mut_ptr()) }; - if ret == 0 && ws[1] > 0 { ws[1] as usize } else { 80 } - } - #[cfg(not(any(target_os = "linux", target_os = "macos")))] - { - 80 - } -} - -/// Format a `TcError` for user-facing Lean-side display. For the two cases we -/// hit most often we emit a human-tuned multi-line message; everything else -/// falls through to `Debug`. -fn format_tc_error( - e: &TcError, - ixon_env: &IxonEnv, - lookups: &IxonIngressLookups, -) -> String { - match e { - TcError::AppTypeMismatch { depth, .. } => { - format!("AppTypeMismatch at depth={depth}") - }, - TcError::FunExpected { .. } => "FunExpected".to_string(), - TcError::UnknownConst(addr) => { - // Address-only label in anon mode: the kernel itself doesn't - // know names, so error messages from the anon path shouldn't - // either. - if !M::HAS_META { - return format!("unknown constant ({:.12})", addr.hex()); - } - let name = lookups.name_for_addr(addr).map_or_else( - || { - if ixon_env.consts.contains_key(addr) { - "".to_string() - } else { - "".to_string() - } - }, - |n| n.pretty(), - ); - format!("unknown constant {name} ({:.12})", addr.hex()) - }, - // Everything else has a hand-written `Display` impl in - // `crates/kernel/src/error.rs` — prefer it over `{:?}` which dumps raw - // KExpr internals. - other => format!("{other}"), - } -} - -// ============================================================================= -// Lean-side result construction -// ============================================================================= - -/// Build one `Option CheckError` object from a Rust check result. -/// -/// - `Ok(())` → `none` -/// - `Err((Kernel, msg))` → `some (CheckError.kernelException msg)` -/// - `Err((Compile, msg))` → `some (CheckError.compileError msg)` -fn build_option_result(result: &CheckRes) -> LeanOwned { - match result { - Ok(()) => LeanOption::none().into(), - Err((kind, msg)) => { - let err_ctor = LeanIxCheckError::alloc(kind.tag()); - err_ctor.set_obj(0, LeanString::new(msg)); - LeanOption::some(err_ctor).into() - }, - } -} - -/// Build an `IO (Array (Option CheckError))` from Rust results. -/// -/// The Lean caller pairs each slot with `names[i]` (the input array) for -/// display, so there's no name in the returned tuple. -fn build_result_array(results: &[CheckRes]) -> LeanIOResult { - let arr = LeanArray::alloc(results.len()); - for (i, result) in results.iter().enumerate() { - arr.set(i, build_option_result(result)); - } - LeanIOResult::ok(arr) -} - -/// Build an `IO (Array (String × Option CheckError))` from Rust -/// `(address, result)` pairs. -/// -/// Used by `rs_kernel_check_anon` to return per-result content -/// addresses alongside the check outcome — the anon CLI has no -/// Lean-side name to associate with each result slot, so the -/// `#` address label has to come back through the FFI to keep -/// `--fail-out` and `[check]` summary lines content-addressed. -fn build_anon_result_array( - addrs: &[Address], - results: &[CheckRes], -) -> LeanIOResult { - debug_assert_eq!( - addrs.len(), - results.len(), - "build_anon_result_array: addrs/results length mismatch" - ); - let arr = LeanArray::alloc(results.len()); - for (i, result) in results.iter().enumerate() { - let hex: LeanOwned = LeanString::new(&addrs[i].hex()).into(); - let res_obj = build_option_result(result); - let pair: LeanOwned = LeanProd::new(hex, res_obj).into(); - arr.set(i, pair); - } - LeanIOResult::ok(arr) -} - -/// Build a result array of length `count` where every slot is the same -/// compile-kind error. Used when compile/ingress/thread setup fails -/// before per-constant checking can begin — the error arose before the -/// kernel was consulted, so `Compile` is the honest tag. -fn build_uniform_error(count: usize, msg: &str) -> LeanIOResult { - let results: Vec = - (0..count).map(|_| Err((ErrKind::Compile, msg.to_string()))).collect(); - build_result_array(&results) -} - -// ============================================================================= -// Kernel ingress + egress roundtrip (via Ixon + decompile) -// ============================================================================= -// -// End-to-end check of the compile + kernel pipeline WITHOUT typechecking: -// Lean env → compile_env (stt) -// → ixon_ingress (stt.env) → KEnv -// → ixon_egress (kenv, stt.env) → IxonEnv' -// → patch stt.env = IxonEnv' -// → decompile_env (stt) → DecompileState.env (Lean) -// and compare each constant's type/value against the original by content -// hash. -// -// The Lean side comes from the same `decompile_env` that `rust-compile` -// covers, run over the kernel's Ixon form, so the comparison checks the -// kernel round trip rather than a second decompiler. -// -// If `ixon_egress` is structurally faithful (kenv → ixon inversion preserves -// the original addressing), this test should report zero mismatches. -// -// Test-only: this and the no-compile variant below import `egress` and -// `decompile_env`, which the production CLI path (`rs_kernel_check_consts`) -// doesn't need. Cfg-gating keeps `lake build ix` (no `test-ffi`) lean. - -/// FFI: exercise the serialized decompile pipeline -/// Lean → compile → serialize → deserialize → decompile → Lean, and -/// compare each constant against the original. This is `ix decompile`'s -/// path (an in-memory `.ixe`): the demoted-at-parse metadata load, the -/// `Named.original` recovery for shape-divergent aux blocks, and -/// expression interning — without the kernel ingress/egress leg -/// [`rs_kernel_roundtrip`] adds. -/// -/// Lean signature: -/// ```lean -/// @[extern "rs_decompile_roundtrip"] -/// opaque rsDecompileRoundtripFFI : -/// @& List (Lean.Name × Lean.ConstantInfo) → IO (Array String) -/// ``` -/// Returns an `Array String` of per-constant diff messages. Empty = pass. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_decompile_roundtrip( - env_consts: LeanList>, -) -> LeanIOResult { - let total_start = Instant::now(); - - let t0 = Instant::now(); - let rust_env = decode_env(env_consts); - eprintln!("[rs_decompile_roundtrip] read env: {:>8.1?}", t0.elapsed()); - - let t1 = Instant::now(); - let rust_env_arc = Arc::new(rust_env); - let compile_state = - match compile_env_with_options(&rust_env_arc, CompileOptions::default()) { - Ok(s) => s, - Err(e) => { - return build_string_array(&[format!("compile error: {e:?}")]); - }, - }; - eprintln!("[rs_decompile_roundtrip] compile: {:>8.1?}", t1.elapsed()); - - let t2 = Instant::now(); - let mut bytes = Vec::new(); - if let Err(e) = compile_state.env.put(&mut bytes) { - return build_string_array(&[format!("serialize error: {e}")]); - } - let sharing_limits = compile_state.sharing_limits.clone(); - let ungrounded: rustc_hash::FxHashSet = - compile_state.ungrounded.iter().map(|e| e.key().clone()).collect(); - drop(compile_state); - let mut slice: &[u8] = &bytes; - let env = match ixon::env::Env::get_demoted_named(&mut slice) { - Ok(env) => env, - Err(e) => { - return build_string_array(&[format!("deserialize error: {e}")]); - }, - }; - eprintln!( - "[rs_decompile_roundtrip] serialize: {:>8.1?} ({} bytes)", - t2.elapsed(), - bytes.len() - ); - drop(bytes); - - let stt = CompileState { env, sharing_limits, ..CompileState::default() }; - for entry in stt.env.named.iter() { - stt.name_to_addr.insert(entry.key().clone(), entry.value().addr.clone()); - } - - let t3 = Instant::now(); - let dstt = match decompile_env(&stt) { - Ok(d) => d, - Err(e) => { - return build_string_array(&[format!("decompile error: {e:?}")]); - }, - }; - eprintln!( - "[rs_decompile_roundtrip] decompile: {:>8.1?} ({} consts)", - t3.elapsed(), - dstt.env.len() - ); - - let t4 = Instant::now(); - let (mut errors, checked, not_found) = - compare_envs(&rust_env_arc, |n| dstt.env.get(n)); - errors.extend(decompile_completeness_errors( - &rust_env_arc, - &dstt, - &ungrounded, - )); - eprintln!( - "[rs_decompile_roundtrip] verify: {:>8.1?} (checked {checked}, not_found {not_found}, errors {})", - t4.elapsed(), - errors.len() - ); - eprintln!( - "[rs_decompile_roundtrip] total: {:>8.1?}", - total_start.elapsed() - ); - - build_string_array(&errors) -} - -/// FFI: exercise the full pipeline -/// Lean → Ixon → kernel → Ixon' → decompile → Lean, and compare each -/// constant against the original. -/// -/// Lean signature: -/// ```lean -/// @[extern "rs_kernel_roundtrip"] -/// opaque rsKernelRoundtripFFI : -/// @& List (Lean.Name × Lean.ConstantInfo) → IO (Array String) -/// ``` -/// Returns an `Array String` of per-constant diff messages. Empty = pass. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_kernel_roundtrip( - env_consts: LeanList>, -) -> LeanIOResult { - let total_start = Instant::now(); - - let t0 = Instant::now(); - let rust_env = decode_env(env_consts); - eprintln!("[rs_kernel_roundtrip] read env: {:>8.1?}", t0.elapsed()); - - let t1 = Instant::now(); - let rust_env_arc = Arc::new(rust_env); - let mut compile_state = - match compile_env_with_options(&rust_env_arc, CompileOptions::default()) { - Ok(s) => s, - Err(e) => { - return build_string_array(&[format!("compile error: {e:?}")]); - }, - }; - eprintln!("[rs_kernel_roundtrip] compile: {:>8.1?}", t1.elapsed()); - - let t2 = Instant::now(); - let (mut kenv, intern) = match ixon_ingress::(&compile_state.env) { - Ok(v) => v, - Err(msg) => { - return build_string_array(&[format!("ingress error: {msg}")]); - }, - }; - kenv.intern = intern; - eprintln!( - "[rs_kernel_roundtrip] ingress: {:>8.1?} ({} consts)", - t2.elapsed(), - kenv.len() - ); - - // Egress KEnv → IxonEnv (reusing the original env's `ConstantMeta` + - // blobs + names). - let t3 = Instant::now(); - let egressed_ixon = - match ixon_egress(&kenv, &compile_state.env, &compile_state.sharing_limits) - { - Ok(e) => e, - Err(msg) => { - return build_string_array(&[format!("ixon_egress error: {msg}")]); - }, - }; - eprintln!( - "[rs_kernel_roundtrip] ixon egress: {:>8.1?} ({} consts, {} named)", - t3.elapsed(), - egressed_ixon.const_count(), - egressed_ixon.named_count() - ); - - // Free the kenv now that we've extracted everything we need; decompile - // works off CompileState only and the kenv is the large structure we - // built during ingress. - drop(kenv); - - // Patch the compile state to point at the egressed Ixon env. Decompile - // reads from `stt.env.named` / `stt.env.get_const` / `stt.env.get_blob` — - // the egressed env preserves all of those (meta is copied from the - // original; constants are re-synthesized from kenv; blobs/names are - // cloned). - compile_state.env = egressed_ixon; - - let t4 = Instant::now(); - let dstt = match decompile_env(&compile_state) { - Ok(d) => d, - Err(e) => { - return build_string_array(&[format!("decompile error: {e:?}")]); - }, - }; - eprintln!( - "[rs_kernel_roundtrip] decompile: {:>8.1?} ({} consts)", - t4.elapsed(), - dstt.env.len() - ); - - // Compare decompiled env against original, content-hash by - // content-hash, reading decompile's DashMap directly — a plain-`Env` - // copy of it would double the resident decompiled env at peak. - let t6 = Instant::now(); - let (mut errors, checked, not_found) = - compare_envs(&rust_env_arc, |n| dstt.env.get(n)); - let ungrounded: rustc_hash::FxHashSet = - compile_state.ungrounded.iter().map(|e| e.key().clone()).collect(); - errors.extend(decompile_completeness_errors( - &rust_env_arc, - &dstt, - &ungrounded, - )); - eprintln!( - "[rs_kernel_roundtrip] verify: {:>8.1?} (checked {checked}, not_found {not_found}, errors {})", - t6.elapsed(), - errors.len() - ); - - drop(compile_state); - drop(rust_env_arc); - - eprintln!( - "[rs_kernel_roundtrip] total: {:>8.1?}", - total_start.elapsed() - ); - - build_string_array(&errors) -} - -/// The round trip's completeness errors besides `compare_envs`' content -/// diffs: every original constant absent from the decompiled env other than -/// the compile's ungrounded inputs. -#[cfg(feature = "test-ffi")] -fn decompile_completeness_errors( - original: &ix_common::env::Env, - dstt: &ix_compile::decompile::DecompileState, - ungrounded: &rustc_hash::FxHashSet, -) -> Vec { - let mut absent: Vec = original - .iter() - .filter(|(n, _)| !dstt.env.contains_key(*n) && !ungrounded.contains(*n)) - .map(|(n, _)| format!("absent from decompiled: {}", n.pretty())) - .collect(); - absent.sort(); - absent -} - -/// Compare the original env against a decompiled/egressed side for -/// structural equality under content-hashing. Returns -/// `(errors, checked, not_found)`. `errors` is capped at 50 to keep outputs -/// manageable. -/// -/// The compared side is a lookup rather than an `Env` so callers can pass -/// whatever map already holds their constants (e.g. decompile's DashMap) -/// instead of copying into a fresh `Env`. -#[cfg(feature = "test-ffi")] -fn compare_envs>( - original: &ix_common::env::Env, - lookup: impl Fn(&Name) -> Option, -) -> (Vec, usize, usize) { - use ix_common::env::ConstantInfo as LCI; - - let total = original.len(); - let mut errors: Vec = Vec::new(); - let mut checked = 0usize; - let mut not_found = 0usize; - - for (name, orig_ci) in original.iter() { - match lookup(name) { - None => { - not_found += 1; - }, - Some(egressed_ci) => { - checked += 1; - if orig_ci.get_type().get_hash() != egressed_ci.get_type().get_hash() { - let diff = - find_diff(orig_ci.get_type(), egressed_ci.get_type(), "type"); - errors.push(format!("{name}: {diff}")); - } - match (&*orig_ci, &*egressed_ci) { - (LCI::DefnInfo(a), LCI::DefnInfo(b)) - if a.value.get_hash() != b.value.get_hash() => - { - let diff = find_diff(&a.value, &b.value, "value"); - errors.push(format!("{name}: {diff}")); - }, - (LCI::ThmInfo(a), LCI::ThmInfo(b)) - if a.value.get_hash() != b.value.get_hash() => - { - let diff = find_diff(&a.value, &b.value, "value"); - errors.push(format!("{name}: {diff}")); - }, - (LCI::OpaqueInfo(a), LCI::OpaqueInfo(b)) - if a.value.get_hash() != b.value.get_hash() => - { - let diff = find_diff(&a.value, &b.value, "value"); - errors.push(format!("{name}: {diff}")); - }, - (LCI::RecInfo(a), LCI::RecInfo(b)) => { - for (i, (r1, r2)) in a.rules.iter().zip(b.rules.iter()).enumerate() - { - if r1.rhs.get_hash() != r2.rhs.get_hash() { - let diff = - find_diff(&r1.rhs, &r2.rhs, &format!("rule[{i}].rhs")); - errors.push(format!("{name}: {diff}")); - } - } - }, - _ => {}, - } - if errors.len() >= 50 { - break; - } - }, - } - if checked.is_multiple_of(10000) && checked > 0 { - eprintln!( - "[compare_envs] verify: {checked}/{total} ({} errors so far)", - errors.len() - ); - } - } - - (errors, checked, not_found) -} - -/// Walk two `Expr` trees in parallel and return the first structural diff. -/// Returns a path-annotated description of where the mismatch is. -#[cfg(feature = "test-ffi")] -fn find_diff( - a: &ix_common::env::Expr, - b: &ix_common::env::Expr, - path: &str, -) -> String { - use ix_common::env::ExprData; - - if a.get_hash() == b.get_hash() { - return format!("{path}: hashes match (ok)"); - } - match (a.as_data(), b.as_data()) { - (ExprData::Bvar(i, _), ExprData::Bvar(j, _)) if i != j => { - format!("{path}: bvar {i} vs {j}") - }, - (ExprData::Sort(l1, _), ExprData::Sort(l2, _)) => { - format!("{path}: sort hash {} vs {}", l1.get_hash(), l2.get_hash()) - }, - (ExprData::Const(n1, ls1, _), ExprData::Const(n2, ls2, _)) => { - if n1 != n2 { - format!("{path}: const name {n1} vs {n2}") - } else { - format!("{path}: const {n1} levels {}-vs-{}", ls1.len(), ls2.len()) - } - }, - (ExprData::App(f1, a1, _), ExprData::App(f2, a2, _)) => { - if f1.get_hash() != f2.get_hash() { - find_diff(f1, f2, &format!("{path}.app.fn")) - } else { - find_diff(a1, a2, &format!("{path}.app.arg")) - } - }, - (ExprData::Lam(n1, t1, b1, bi1, _), ExprData::Lam(n2, t2, b2, bi2, _)) => { - if n1 != n2 { - return format!("{path}: lam name {n1} vs {n2}"); - } - if bi1 != bi2 { - return format!("{path}: lam bi {bi1:?} vs {bi2:?}"); - } - if t1.get_hash() != t2.get_hash() { - find_diff(t1, t2, &format!("{path}.lam.ty")) - } else { - find_diff(b1, b2, &format!("{path}.lam.body")) - } - }, - ( - ExprData::ForallE(n1, t1, b1, bi1, _), - ExprData::ForallE(n2, t2, b2, bi2, _), - ) => { - if n1 != n2 { - return format!("{path}: pi name {n1} vs {n2}"); - } - if bi1 != bi2 { - return format!("{path}: pi bi {bi1:?} vs {bi2:?}"); - } - if t1.get_hash() != t2.get_hash() { - find_diff(t1, t2, &format!("{path}.pi.ty")) - } else { - find_diff(b1, b2, &format!("{path}.pi.body")) - } - }, - ( - ExprData::LetE(n1, t1, v1, b1, nd1, _), - ExprData::LetE(n2, t2, v2, b2, nd2, _), - ) => { - if n1 != n2 { - return format!("{path}: let name {n1} vs {n2}"); - } - if nd1 != nd2 { - return format!("{path}: let nonDep {nd1} vs {nd2}"); - } - if t1.get_hash() != t2.get_hash() { - find_diff(t1, t2, &format!("{path}.let.ty")) - } else if v1.get_hash() != v2.get_hash() { - find_diff(v1, v2, &format!("{path}.let.val")) - } else { - find_diff(b1, b2, &format!("{path}.let.body")) - } - }, - (ExprData::Lit(l1, _), ExprData::Lit(l2, _)) => { - format!("{path}: lit {l1:?} vs {l2:?}") - }, - (ExprData::Proj(n1, i1, s1, _), ExprData::Proj(n2, i2, s2, _)) => { - if n1 != n2 || i1 != i2 { - format!("{path}: proj {n1}.{i1} vs {n2}.{i2}") - } else { - find_diff(s1, s2, &format!("{path}.proj.struct")) - } - }, - (ExprData::Mdata(kvs1, e1, _), ExprData::Mdata(kvs2, e2, _)) => { - // Both sides have mdata — compare content. - let h1 = - kvs1.iter().map(|(n, _)| format!("{n}")).collect::>().join(","); - let h2 = - kvs2.iter().map(|(n, _)| format!("{n}")).collect::>().join(","); - if kvs1.len() != kvs2.len() || h1 != h2 { - format!("{path}: mdata keys differ [{h1}] vs [{h2}]") - } else { - // Keys match — compare hashes of each value. - let mut val_diffs = Vec::new(); - for (i, ((n1, v1), (_, v2))) in kvs1.iter().zip(kvs2.iter()).enumerate() - { - use ix_common::env::hash_data_value; - let mut h1 = blake3::Hasher::new(); - let mut h2 = blake3::Hasher::new(); - hash_data_value(v1, &mut h1); - hash_data_value(v2, &mut h2); - if h1.finalize() != h2.finalize() { - val_diffs.push(format!("mdata[{i}] key={n1}: value hash differs")); - } - } - if !val_diffs.is_empty() { - format!("{path}: {}", val_diffs.join("; ")) - } else { - // Mdata content matches — diff must be in the inner expr. - find_diff(e1, e2, &format!("{path}.mdata=")) - } - } - }, - (ExprData::Mdata(kvs, e1, _), _) => { - let keys: Vec<_> = kvs.iter().map(|(n, _)| format!("{n}")).collect(); - find_diff(e1, b, &format!("{path}.ORIG_HAS_mdata[{}]>", keys.join(","))) - }, - (_, ExprData::Mdata(kvs, e2, _)) => { - let keys: Vec<_> = kvs.iter().map(|(n, _)| format!("{n}")).collect(); - find_diff(a, e2, &format!("{path}. { - let kind_a = std::mem::discriminant(a.as_data()); - let kind_b = std::mem::discriminant(b.as_data()); - format!("{path}: node kind mismatch {kind_a:?} vs {kind_b:?}") - }, - } -} - -/// Build an `IO (Array String)` from a slice of error messages. -#[cfg(feature = "test-ffi")] -fn build_string_array(errors: &[String]) -> LeanIOResult { - let arr = LeanArray::alloc(errors.len()); - for (i, msg) in errors.iter().enumerate() { - arr.set(i, LeanString::new(msg)); - } - LeanIOResult::ok(arr) -} - -// ============================================================================= -// Direct Lean env → kernel env roundtrip (no compile) -// ============================================================================= -// -// End-to-end check that skips `compile_env` / `ixon_ingress` entirely. -// Pipeline: decoded Lean `Env` → `lean_ingress` → `KEnv` → -// `lean_egress` → `Lean env` → compare against original. -// -// Reuses the same `compare_envs` / `find_diff` / `build_string_array` -// infrastructure as `rs_kernel_roundtrip`, so error messages have identical -// shape and we can diff counts 1:1 between the two paths. -// -// Useful for bisecting brecOn-like regressions: if this path is clean and -// `rs_kernel_roundtrip` has ~50 errors, the compile pipeline is dropping -// information; if both show the same errors, ingress/egress is. - -/// FFI: exercise the full pipeline Lean env → kernel → Lean (egress) WITHOUT -/// going through Ixon compilation, and compare each constant against the -/// original. -/// -/// Lean signature: -/// ```lean -/// @[extern "rs_kernel_roundtrip_no_compile"] -/// opaque rsKernelRoundtripNoCompileFFI : -/// @& List (Lean.Name × Lean.ConstantInfo) → IO (Array String) -/// ``` -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_kernel_roundtrip_no_compile( - env_consts: LeanList>, -) -> LeanIOResult { - let total_start = Instant::now(); - - let t0 = Instant::now(); - let rust_env = decode_env(env_consts); - eprintln!( - "[rs_kernel_roundtrip_no_compile] read env: {:>8.1?}", - t0.elapsed() - ); - - // Direct Lean → kernel ingress. No compile, no Ixon. - let t1 = Instant::now(); - let rust_env_arc = Arc::new(rust_env); - let kenv = match lean_ingress(&rust_env_arc) { - Ok(kenv) => kenv, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_roundtrip_no_compile: ingress failed {e}" - )); - }, - }; - eprintln!( - "[rs_kernel_roundtrip_no_compile] ingress: {:>8.1?} ({} consts)", - t1.elapsed(), - kenv.len() - ); - - // Egress kernel → Lean. - let t2 = Instant::now(); - let egressed_env = match lean_egress(&kenv) { - Ok(env) => env, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_roundtrip_no_compile: egress failed {e}" - )); - }, - }; - eprintln!( - "[rs_kernel_roundtrip_no_compile] egress: {:>8.1?} ({} consts)", - t2.elapsed(), - egressed_env.len() - ); - - // Compare. - let t3 = Instant::now(); - let (errors, checked, not_found) = - compare_envs(&rust_env_arc, |n| egressed_env.get(n)); - eprintln!( - "[rs_kernel_roundtrip_no_compile] verify: {:>8.1?} (checked {checked}, not_found {not_found}, errors {})", - t3.elapsed(), - errors.len() - ); - - drop(rust_env_arc); - - eprintln!( - "[rs_kernel_roundtrip_no_compile] total: {:>8.1?}", - total_start.elapsed() - ); - - build_string_array(&errors) -} diff --git a/crates/ffi/src/lean.rs b/crates/ffi/src/lean.rs index c5a330e76..1bc8dfe86 100644 --- a/crates/ffi/src/lean.rs +++ b/crates/ffi/src/lean.rs @@ -19,8 +19,6 @@ lean_ffi::lean_domain_type! { LeanIxonQuotKind; // Opaque types (passed through without field access) - LeanIxRawEnvironment; - LeanIxEnvironment; LeanIxonExprMetaArena; LeanAiurCommitmentParameters; LeanAiurFriParameters; @@ -162,19 +160,6 @@ lean_ffi::lean_inductive! { { num_obj: 1 }, // tag 8: muts ]; - // --- Ix structures (single-constructor) --- - - LeanIxConstantVal [ { num_obj: 3 } ]; - LeanIxRecursorRule [ { num_obj: 3 } ]; - LeanIxTheoremVal [ { num_obj: 3 } ]; - LeanIxCondensedBlocks [ { num_obj: 3 } ]; - LeanIxCompilePhases [ { num_obj: 3 } ]; - // Ix.CompileM.CompileEnvStatus: root (String), ungrounded - // (Array (String × String)), nonCanonical (Array (String × String)), - // then UInt64 scalars bytes/named/uniqueAnon - LeanIxCompileEnvStatus [ { num_obj: 3, num_64: 3 } ]; - LeanIxSubstring [ { num_obj: 3 } ]; - // --- Ix multi-variant inductives --- LeanIxName [ @@ -183,94 +168,12 @@ lean_ffi::lean_inductive! { { num_obj: 3 }, // tag 2: num ]; - LeanIxLevel [ - { num_obj: 1 }, // tag 0: zero - { num_obj: 2 }, // tag 1: succ - { num_obj: 3 }, // tag 2: max - { num_obj: 3 }, // tag 3: imax - { num_obj: 2 }, // tag 4: param - { num_obj: 2 }, // tag 5: mvar - ]; - - LeanIxExpr [ - { num_obj: 2 }, // tag 0: bvar - { num_obj: 2 }, // tag 1: fvar - { num_obj: 2 }, // tag 2: mvar - { num_obj: 2 }, // tag 3: sort - { num_obj: 3 }, // tag 4: const - { num_obj: 3 }, // tag 5: app - { num_obj: 4, num_8: 1 }, // tag 6: lam - { num_obj: 4, num_8: 1 }, // tag 7: forallE - { num_obj: 5, num_8: 1 }, // tag 8: letE - { num_obj: 2 }, // tag 9: lit - { num_obj: 3 }, // tag 10: mdata - { num_obj: 4 }, // tag 11: proj - ]; - - LeanIxConstantInfo [ - { num_obj: 1 }, // tag 0: axiom - { num_obj: 1 }, // tag 1: defn - { num_obj: 1 }, // tag 2: thm - { num_obj: 1 }, // tag 3: opaque - { num_obj: 1 }, // tag 4: quot - { num_obj: 1 }, // tag 5: induct - { num_obj: 1 }, // tag 6: ctor - { num_obj: 1 }, // tag 7: rec - ]; - LeanIxReducibilityHints [ { }, // tag 0: opaque (scalar) { }, // tag 1: abbrev (scalar) { num_32: 1 }, // tag 2: regular ]; - LeanIxLiteral [ - { num_obj: 1 }, // tag 0: nat - { num_obj: 1 }, // tag 1: str - ]; - - LeanIxInt [ - { num_obj: 1 }, // tag 0: ofNat - { num_obj: 1 }, // tag 1: negSucc - ]; - - LeanIxSourceInfo [ - { num_obj: 4 }, // tag 0: original - { num_obj: 2, num_8: 1 }, // tag 1: synthetic - { }, // tag 2: none - ]; - - LeanIxSyntax [ - { }, // tag 0: missing - { num_obj: 3 }, // tag 1: node - { num_obj: 2 }, // tag 2: atom - { num_obj: 4 }, // tag 3: ident - ]; - - LeanIxSyntaxPreresolved [ - { num_obj: 1 }, // tag 0: namespace - { num_obj: 2 }, // tag 1: decl - ]; - - LeanIxDataValue [ - { num_obj: 1 }, // tag 0: ofString - { num_8: 1 }, // tag 1: ofBool - { num_obj: 1 }, // tag 2: ofName - { num_obj: 1 }, // tag 3: ofNat - { num_obj: 1 }, // tag 4: ofInt - { num_obj: 1 }, // tag 5: ofSyntax - ]; - - // --- Lean kernel inner val types (used in Ix.ConstantInfo decode) --- - - LeanIxAxiomVal [ { num_obj: 1, num_8: 1 } ]; - LeanIxDefinitionVal [ { num_obj: 4, num_8: 1 } ]; - LeanIxOpaqueVal [ { num_obj: 3, num_8: 1 } ]; - LeanIxQuotVal [ { num_obj: 1, num_8: 1 } ]; - LeanIxInductiveVal [ { num_obj: 6, num_8: 3 } ]; - LeanIxConstructorVal [ { num_obj: 5, num_8: 1 } ]; - LeanIxRecursorVal [ { num_obj: 7, num_8: 2 } ]; - // --- Aiur types --- LeanAiurToplevel [ { num_obj: 3 } ]; @@ -302,61 +205,6 @@ lean_ffi::lean_inductive! { // claimBytes, constantCount LeanAiurAggregateExpected [ { num_obj: 2 } ]; - // --- Block / comparison types --- - - LeanIxBlockCompareResult [ - { }, // tag 0: match - { num_64: 3 }, // tag 1: mismatch - { }, // tag 2: notFound - ]; - - LeanIxBlockCompareDetail [ { num_obj: 1, num_64: 2 } ]; - LeanIxBlock [ { num_obj: 2, num_64: 1 } ]; - - // --- Error types --- - - LeanIxSerializeError [ - { num_obj: 1 }, // tag 0: unexpectedEof - { num_obj: 1, num_8: 1 }, // tag 1: invalidTag - { num_obj: 1, num_8: 1 }, // tag 2: invalidFlag - { num_obj: 1, num_64: 1 }, // tag 3: invalidVariant - { num_8: 1 }, // tag 4: invalidBool - { }, // tag 5: addressError - { num_obj: 1, num_64: 1 }, // tag 6: invalidShareIndex - ]; - - LeanIxDecompileError [ - { num_obj: 2, num_64: 1 }, // tag 0: invalidRefIndex - { num_obj: 2, num_64: 1 }, // tag 1: invalidUnivIndex - { num_obj: 2, num_64: 1 }, // tag 2: invalidShareIndex - { num_obj: 2, num_64: 1 }, // tag 3: invalidRecIndex - { num_obj: 2, num_64: 1 }, // tag 4: invalidUnivVarIndex - { num_obj: 1 }, // tag 5: missingAddress - { num_obj: 1 }, // tag 6: missingMetadata - { num_obj: 1 }, // tag 7: blobNotFound - { num_obj: 2 }, // tag 8: badBlobFormat - { num_obj: 1 }, // tag 9: badConstantFormat - { num_obj: 1 }, // tag 10: serialize - { num_obj: 3, num_64: 2 }, // tag 11: invalidMetaShareIndex - ]; - - LeanIxCompileError [ - { num_obj: 1 }, // tag 0: missingConstant - { num_obj: 1 }, // tag 1: missingAddress - { num_obj: 1 }, // tag 2: invalidMutualBlock - { num_obj: 1 }, // tag 3: unsupportedExpr - { num_obj: 2 }, // tag 4: unknownUnivParam - { num_obj: 1 }, // tag 5: serialize - { num_obj: 1 }, // tag 6: resourceLimit - { num_obj: 1 }, // tag 7: sharingConstruction - ]; - - // Defined in `Ix/KernelCheck.lean`. - LeanIxCheckError [ - { num_obj: 1 }, // tag 0: kernelException - { num_obj: 1 }, // tag 1: compileError - ]; - // --- Iroh types --- LeanPutResponse [ { num_obj: 2 } ]; @@ -427,3 +275,173 @@ impl<'a> LeanIxAddress> { Self(ba) } } + +// ============================================================================= +// LeanBuildCache & Scalar Domain Builders/Decoders +// ============================================================================= + +use ix_common::address::Address; +use ix_common::env::{BinderInfo, Name, NameData, ReducibilityHints}; +use lean_ffi::object::{LeanArray, LeanNat, LeanString}; +use rustc_hash::FxHashMap; + +/// Deduplication cache for constructing Lean objects during FFI calls. +#[derive(Default)] +pub struct LeanBuildCache { + /// Cached `Ix.Name` objects keyed by content hash. + pub names: FxHashMap>, +} + +impl LeanBuildCache { + /// Create an empty build cache. + pub fn new() -> Self { + Self::default() + } +} + +impl LeanIxAddress { + /// Construct a Lean `Address` (`ByteArray` of length 32). + pub fn build(addr: &Address) -> Self { + LeanIxAddress::new(LeanByteArray::from_bytes(addr.as_bytes())) + } + + /// Construct a Lean `Address` from a [`blake3::Hash`]. + pub fn build_hash(hash: &blake3::Hash) -> Self { + LeanIxAddress::new(LeanByteArray::from_bytes(hash.as_bytes())) + } + + /// Construct a `LeanArray` of `Address` values. + pub fn build_array(addrs: &[Address]) -> LeanArray { + let arr = LeanArray::alloc(addrs.len()); + for (i, a) in addrs.iter().enumerate() { + arr.set(i, Self::build(a)); + } + arr + } +} + +impl LeanIxAddress { + /// Decode a 32-byte Lean `Address` into a Rust [`Address`]. + pub fn decode(&self) -> Address { + Address::from_slice(self.as_bytes()).expect("Lean Address must be 32 bytes") + } +} + +impl LeanIxAddress> { + /// Decode a `LeanArray` of `Address` values into a `Vec
`. + pub fn decode_array(arr: LeanArray>) -> Vec
{ + arr + .iter() + .map(|elem| LeanIxAddress::from_borrowed(elem.as_byte_array()).decode()) + .collect() + } +} + +impl LeanIxName { + /// Construct a Lean `Ix.Name` (deduplicated via `cache`). + pub fn build(cache: &mut LeanBuildCache, name: &Name) -> Self { + let key = *name.get_hash(); + if let Some(existing) = cache.names.get(&key) { + return existing.clone(); + } + let built = match name.as_data() { + NameData::Anonymous(h) => { + let ctor = LeanIxName::alloc(0); + ctor.set_obj(0, LeanIxAddress::build_hash(h)); + ctor + }, + NameData::Str(parent, s, h) => { + let p_obj = Self::build(cache, parent); + let ctor = LeanIxName::alloc(1); + ctor.set_obj(0, p_obj); + ctor.set_obj(1, LeanString::new(s)); + ctor.set_obj(2, LeanIxAddress::build_hash(h)); + ctor + }, + NameData::Num(parent, n, h) => { + let p_obj = Self::build(cache, parent); + let ctor = LeanIxName::alloc(2); + ctor.set_obj(0, p_obj); + ctor.set_obj(1, LeanNat::from_nat(n)); + ctor.set_obj(2, LeanIxAddress::build_hash(h)); + ctor + }, + }; + cache.names.insert(key, built.clone()); + built + } +} + +impl LeanIxName { + /// Decode a Lean `Ix.Name` into a Rust [`Name`]. + pub fn decode(&self) -> Name { + match self.as_ctor().tag() { + 0 => Name::anon(), + 1 => { + let parent = LeanIxName::new(self.get_obj(0).to_owned_ref()).decode(); + let s = self.get_obj(1).as_string().to_string(); + Name::str(parent, s) + }, + 2 => { + let parent = LeanIxName::new(self.get_obj(0).to_owned_ref()).decode(); + let n = LeanNat::to_nat(&self.get_obj(1)); + Name::num(parent, n) + }, + t => panic!("LeanIxName::decode: unexpected tag {t}"), + } + } +} + +impl LeanIxBinderInfo { + /// Encode [`BinderInfo`] as a Lean enum tag. + pub fn to_u8(bi: &BinderInfo) -> u8 { + match bi { + BinderInfo::Default => 0, + BinderInfo::Implicit => 1, + BinderInfo::StrictImplicit => 2, + BinderInfo::InstImplicit => 3, + } + } + + /// Decode [`BinderInfo`] from a Lean enum tag. + pub fn from_u8(v: u8) -> BinderInfo { + match v { + 0 => BinderInfo::Default, + 1 => BinderInfo::Implicit, + 2 => BinderInfo::StrictImplicit, + 3 => BinderInfo::InstImplicit, + _ => panic!("Invalid BinderInfo tag: {v}"), + } + } +} + +impl LeanIxReducibilityHints { + /// Construct a Lean `Ix.ReducibilityHints`. + pub fn build(hints: &ReducibilityHints) -> Self { + match hints { + ReducibilityHints::Opaque => Self::new(LeanOwned::box_usize(0)), + ReducibilityHints::Abbrev => Self::new(LeanOwned::box_usize(1)), + ReducibilityHints::Regular(h) => { + let ctor = LeanIxReducibilityHints::alloc(2); + ctor.set_num_32(0, *h); + ctor + }, + } + } +} + +impl LeanIxReducibilityHints { + /// Decode a Lean `Ix.ReducibilityHints`. + pub fn decode(&self) -> ReducibilityHints { + if self.inner().is_scalar() { + match (self.inner().as_raw() as usize) >> 1 { + 0 => ReducibilityHints::Opaque, + 1 => ReducibilityHints::Abbrev, + t => panic!("Invalid scalar ReducibilityHints tag: {t}"), + } + } else { + assert_eq!(self.as_ctor().tag(), 2); + ReducibilityHints::Regular(self.get_num_32(0)) + } + } +} diff --git a/crates/ffi/src/lean_build.rs b/crates/ffi/src/lean_build.rs deleted file mode 100644 index a76c4aa39..000000000 --- a/crates/ffi/src/lean_build.rs +++ /dev/null @@ -1,734 +0,0 @@ -//! Encode Rust `ix_common::env` values as real Lean kernel objects. -//! -//! The reverse of `lean_env.rs`: where that module decodes Lean's in-memory -//! C representation into the Rust `Name` / `Level` / `Expr` / `ConstantInfo` -//! types, this one constructs genuine `Lean.Name` / `Lean.Level` / -//! `Lean.Expr` / `Lean.ConstantInfo` objects from those Rust values, for -//! FFI entries that hand decompiled constants back to Lean (the -//! materialization path behind `import_ixe`). -//! -//! `Name`, `Level`, and `Expr` carry `@[computed_field]` data words (hash, -//! loose-bvar depth, has-fvar flags, …) that MUST be computed by Lean's own -//! algorithms, so those three types are built exclusively through the -//! toolchain's exported constructors (`lean_name_mk_*`, `lean_level_mk_*`, -//! `lean_expr_mk_*` — the `mk*Ex` exports in `Lean/Expr.lean` and -//! `Lean/Level.lean`). Plain structures (`Literal`, the `*Val` payloads, -//! `ConstantInfo` itself, `Syntax`, …) have no computed fields and are -//! allocated directly through the `LeanIx*` layout types, whose field -//! orders are pinned by `lean_env.rs::decode_constant_info`. -//! -//! Sharing: `ExprData` / `LevelData` / `NameData` live behind `Arc`s that -//! the decompiler dedups aggressively; the encode cache keys on those Arc -//! data pointers so shared Rust subterms become shared Lean objects. One -//! cache should span a whole marshaling batch (sharing crosses constant -//! boundaries). -//! -//! Expression encoding is iterative (explicit frame stack): Init-scale -//! terms overflow default runtime stacks (same reason -//! `Ix/Tc/EgressLean.lean` uses a stack machine). -//! -//! Known hole, deliberate: `DataValue::OfInt` inside mdata is rejected -//! with an error. Lean's runtime `Int` is scalar-or-mpz (no exported -//! constructor symbol), and the decode direction's ctor-read of an `Int` -//! would have crashed on any real env carrying one — empirically the case -//! never occurs in kernel constants. - -use ix_common::env::{ - BinderInfo, ConstantInfo, ConstantVal, DataValue, DefinitionSafety, Expr, - ExprData, Level, LevelData, Literal, Name, NameData, QuotKind, RecursorRule, - ReducibilityHints, SourceInfo, Substring, Syntax, SyntaxPreresolved, -}; -use lean_ffi::include::lean_object; -use lean_ffi::object::{LeanArray, LeanList, LeanNat, LeanOwned, LeanString}; -use rustc_hash::FxHashMap; - -use crate::lean::{ - LeanIxAxiomVal, LeanIxConstantInfo, LeanIxConstantVal, LeanIxConstructorVal, - LeanIxDataValue, LeanIxDefinitionVal, LeanIxInductiveVal, LeanIxLiteral, - LeanIxOpaqueVal, LeanIxQuotVal, LeanIxRecursorRule, LeanIxRecursorVal, - LeanIxReducibilityHints, LeanIxSourceInfo, LeanIxSubstring, LeanIxSyntax, - LeanIxSyntaxPreresolved, LeanIxTheoremVal, -}; - -unsafe extern "C" { - // Lean/Level.lean `mkLevel*Ex` exports (computed-field data word done by - // Lean). `lean_level_mk_zero : Unit → Level` takes the unit box. - fn lean_name_mk_string( - parent: *mut lean_object, - part: *mut lean_object, - ) -> *mut lean_object; - fn lean_name_mk_numeral( - parent: *mut lean_object, - part: *mut lean_object, - ) -> *mut lean_object; - fn lean_level_mk_zero(unit: *mut lean_object) -> *mut lean_object; - fn lean_level_mk_succ(l: *mut lean_object) -> *mut lean_object; - fn lean_level_mk_max( - a: *mut lean_object, - b: *mut lean_object, - ) -> *mut lean_object; - fn lean_level_mk_imax( - a: *mut lean_object, - b: *mut lean_object, - ) -> *mut lean_object; - fn lean_level_mk_param(name: *mut lean_object) -> *mut lean_object; - fn lean_level_mk_mvar(id: *mut lean_object) -> *mut lean_object; - // Lean/Expr.lean `mk*Ex` exports. `BinderInfo` and `Bool` pass unboxed - // as `uint8_t` per Lean's C ABI for scalar enums. - fn lean_expr_mk_bvar(idx: *mut lean_object) -> *mut lean_object; - fn lean_expr_mk_fvar(fvar_id: *mut lean_object) -> *mut lean_object; - fn lean_expr_mk_mvar(mvar_id: *mut lean_object) -> *mut lean_object; - fn lean_expr_mk_sort(level: *mut lean_object) -> *mut lean_object; - fn lean_expr_mk_const( - name: *mut lean_object, - levels: *mut lean_object, - ) -> *mut lean_object; - fn lean_expr_mk_app( - f: *mut lean_object, - a: *mut lean_object, - ) -> *mut lean_object; - fn lean_expr_mk_lambda( - name: *mut lean_object, - ty: *mut lean_object, - body: *mut lean_object, - bi: u8, - ) -> *mut lean_object; - fn lean_expr_mk_forall( - name: *mut lean_object, - ty: *mut lean_object, - body: *mut lean_object, - bi: u8, - ) -> *mut lean_object; - fn lean_expr_mk_let( - name: *mut lean_object, - ty: *mut lean_object, - value: *mut lean_object, - body: *mut lean_object, - nondep: u8, - ) -> *mut lean_object; - fn lean_expr_mk_lit(lit: *mut lean_object) -> *mut lean_object; - fn lean_expr_mk_mdata( - kvmap: *mut lean_object, - e: *mut lean_object, - ) -> *mut lean_object; - fn lean_expr_mk_proj( - type_name: *mut lean_object, - idx: *mut lean_object, - e: *mut lean_object, - ) -> *mut lean_object; -} - -/// Cache of already-encoded nodes, keyed by the Rust `Arc` data pointer. -/// Each entry holds one owned reference; hits hand out a fresh -/// `to_owned_ref` (refcount-incremented) copy. -#[derive(Default)] -pub struct LeanEncodeCache { - names: FxHashMap<*const NameData, LeanOwned>, - levels: FxHashMap<*const LevelData, LeanOwned>, - exprs: FxHashMap<*const ExprData, LeanOwned>, -} - -/// Encode a `Name` as a real `Lean.Name` (hash computed field done by the -/// exported constructors). Names are shallow; plain recursion is fine. -pub fn encode_name(cache: &mut LeanEncodeCache, name: &Name) -> LeanOwned { - if matches!(name.as_data(), NameData::Anonymous(_)) { - return LeanOwned::box_usize(0); - } - let key = std::sync::Arc::as_ptr(&name.0); - if let Some(cached) = cache.names.get(&key) { - return cached.borrow().to_owned_ref(); - } - let obj = match name.as_data() { - NameData::Anonymous(_) => unreachable!("handled above"), - NameData::Str(parent, s, _) => { - let parent = encode_name(cache, parent); - let part = LeanString::from_bytes(s.as_bytes()); - unsafe { - LeanOwned::from_raw(lean_name_mk_string( - parent.into_raw(), - part.into_raw(), - )) - } - }, - NameData::Num(parent, n, _) => { - let parent = encode_name(cache, parent); - let part = LeanNat::from_nat(n); - unsafe { - LeanOwned::from_raw(lean_name_mk_numeral( - parent.into_raw(), - part.into_raw(), - )) - } - }, - }; - let out = obj.borrow().to_owned_ref(); - cache.names.insert(key, obj); - out -} - -/// Encode a `Level` as a real `Lean.Level`. Levels are shallow (depth = -/// syntactic level size); plain recursion mirrors `decode_level`. -pub fn encode_level(cache: &mut LeanEncodeCache, level: &Level) -> LeanOwned { - let key = std::sync::Arc::as_ptr(&level.0); - if let Some(cached) = cache.levels.get(&key) { - return cached.borrow().to_owned_ref(); - } - let obj = match level.as_data() { - LevelData::Zero(_) => unsafe { - LeanOwned::from_raw(lean_level_mk_zero( - LeanOwned::box_usize(0).into_raw(), - )) - }, - LevelData::Succ(inner, _) => { - let inner = encode_level(cache, inner); - unsafe { LeanOwned::from_raw(lean_level_mk_succ(inner.into_raw())) } - }, - LevelData::Max(a, b, _) => { - let a = encode_level(cache, a); - let b = encode_level(cache, b); - unsafe { - LeanOwned::from_raw(lean_level_mk_max(a.into_raw(), b.into_raw())) - } - }, - LevelData::Imax(a, b, _) => { - let a = encode_level(cache, a); - let b = encode_level(cache, b); - unsafe { - LeanOwned::from_raw(lean_level_mk_imax(a.into_raw(), b.into_raw())) - } - }, - LevelData::Param(name, _) => { - let name = encode_name(cache, name); - unsafe { LeanOwned::from_raw(lean_level_mk_param(name.into_raw())) } - }, - // LMVarId is a trivial structure over Name: runtime repr is the Name. - LevelData::Mvar(name, _) => { - let name = encode_name(cache, name); - unsafe { LeanOwned::from_raw(lean_level_mk_mvar(name.into_raw())) } - }, - }; - let out = obj.borrow().to_owned_ref(); - cache.levels.insert(key, obj); - out -} - -fn encode_substring(s: &Substring) -> LeanOwned { - let obj = LeanIxSubstring::alloc(0); - obj.set_obj(0, LeanString::from_bytes(s.str.as_bytes())); - obj.set_obj(1, LeanNat::from_nat(&s.start_pos)); - obj.set_obj(2, LeanNat::from_nat(&s.stop_pos)); - obj.into() -} - -fn encode_source_info(si: &SourceInfo) -> LeanOwned { - match si { - SourceInfo::Original(leading, pos, trailing, end_pos) => { - let obj = LeanIxSourceInfo::alloc(0); - obj.set_obj(0, encode_substring(leading)); - obj.set_obj(1, LeanNat::from_nat(pos)); - obj.set_obj(2, encode_substring(trailing)); - obj.set_obj(3, LeanNat::from_nat(end_pos)); - obj.into() - }, - SourceInfo::Synthetic(pos, end_pos, canonical) => { - let obj = LeanIxSourceInfo::alloc(1); - obj.set_obj(0, LeanNat::from_nat(pos)); - obj.set_obj(1, LeanNat::from_nat(end_pos)); - obj.set_num_8(0, u8::from(*canonical)); - obj.into() - }, - SourceInfo::None => LeanOwned::box_usize(2), - } -} - -fn encode_syntax_preresolved( - cache: &mut LeanEncodeCache, - p: &SyntaxPreresolved, -) -> LeanOwned { - match p { - SyntaxPreresolved::Namespace(name) => { - let obj = LeanIxSyntaxPreresolved::alloc(0); - obj.set_obj(0, encode_name(cache, name)); - obj.into() - }, - SyntaxPreresolved::Decl(name, fields) => { - let obj = LeanIxSyntaxPreresolved::alloc(1); - obj.set_obj(0, encode_name(cache, name)); - obj.set_obj( - 1, - fields - .iter() - .map(|f| LeanString::from_bytes(f.as_bytes())) - .collect::>(), - ); - obj.into() - }, - } -} - -fn encode_syntax(cache: &mut LeanEncodeCache, syn: &Syntax) -> LeanOwned { - match syn { - Syntax::Missing => LeanOwned::box_usize(0), - Syntax::Node(info, kind, args) => { - let obj = LeanIxSyntax::alloc(1); - obj.set_obj(0, encode_source_info(info)); - obj.set_obj(1, encode_name(cache, kind)); - let arr = LeanArray::alloc(args.len()); - for (i, a) in args.iter().enumerate() { - arr.set(i, encode_syntax(cache, a)); - } - obj.set_obj(2, arr); - obj.into() - }, - Syntax::Atom(info, val) => { - let obj = LeanIxSyntax::alloc(2); - obj.set_obj(0, encode_source_info(info)); - obj.set_obj(1, LeanString::from_bytes(val.as_bytes())); - obj.into() - }, - Syntax::Ident(info, raw_val, val, preresolved) => { - let obj = LeanIxSyntax::alloc(3); - obj.set_obj(0, encode_source_info(info)); - obj.set_obj(1, encode_substring(raw_val)); - obj.set_obj(2, encode_name(cache, val)); - obj.set_obj( - 3, - preresolved - .iter() - .map(|p| encode_syntax_preresolved(cache, p)) - .collect::>(), - ); - obj.into() - }, - } -} - -fn encode_data_value( - cache: &mut LeanEncodeCache, - dv: &DataValue, -) -> Result { - Ok(match dv { - DataValue::OfString(s) => { - let obj = LeanIxDataValue::alloc(0); - obj.set_obj(0, LeanString::from_bytes(s.as_bytes())); - obj.into() - }, - DataValue::OfBool(b) => { - let obj = LeanIxDataValue::alloc(1); - obj.set_num_8(0, u8::from(*b)); - obj.into() - }, - DataValue::OfName(n) => { - let obj = LeanIxDataValue::alloc(2); - obj.set_obj(0, encode_name(cache, n)); - obj.into() - }, - DataValue::OfNat(n) => { - let obj = LeanIxDataValue::alloc(3); - obj.set_obj(0, LeanNat::from_nat(n)); - obj.into() - }, - DataValue::OfInt(_) => { - return Err( - "encode_data_value: DataValue.ofInt in kernel-constant mdata is \ - not supported (Lean's runtime Int has no exported constructor)" - .to_string(), - ); - }, - DataValue::OfSyntax(syn) => { - let obj = LeanIxDataValue::alloc(5); - obj.set_obj(0, encode_syntax(cache, syn)); - obj.into() - }, - }) -} - -/// Encode an mdata payload as a runtime `MData` (= `KVMap`, whose -/// single-field-structure runtime repr is the bare -/// `List (Name × DataValue)` — see `decode_expr` tag 10). -fn encode_kvmap( - cache: &mut LeanEncodeCache, - kv: &[(Name, DataValue)], -) -> Result { - let mut pairs: Vec = Vec::with_capacity(kv.len()); - for (name, dv) in kv { - let name_obj = encode_name(cache, name); - let dv_obj = encode_data_value(cache, dv)?; - pairs.push(lean_ffi::object::LeanProd::new(name_obj, dv_obj).into()); - } - Ok(pairs.into_iter().collect::>().into()) -} - -fn encode_literal(lit: &Literal) -> LeanOwned { - match lit { - Literal::NatVal(n) => { - let obj = LeanIxLiteral::alloc(0); - obj.set_obj(0, LeanNat::from_nat(n)); - obj.into() - }, - Literal::StrVal(s) => { - // Source strings may contain NULs; the C-string constructor drops them. - let obj = LeanIxLiteral::alloc(1); - obj.set_obj(0, LeanString::from_bytes(s.as_bytes())); - obj.into() - }, - } -} - -const fn binder_info_byte(bi: &BinderInfo) -> u8 { - match bi { - BinderInfo::Default => 0, - BinderInfo::Implicit => 1, - BinderInfo::StrictImplicit => 2, - BinderInfo::InstImplicit => 3, - } -} - -/// Frames for the iterative expression encoder. `Build` re-matches the -/// node once its children sit on the value stack. -enum EFrame<'a> { - Process(&'a Expr), - Build(&'a Expr), -} - -/// Encode an `Expr` as a real `Lean.Expr`, iterative with an explicit -/// frame stack and value stack. Shared subterms (Arc identity) encode -/// once via the cache. -pub fn encode_expr( - cache: &mut LeanEncodeCache, - root: &Expr, -) -> Result { - let mut stack: Vec> = vec![EFrame::Process(root)]; - let mut values: Vec = Vec::new(); - - // Insert into the cache and push an incremented handle onto `values`. - fn finish( - cache: &mut LeanEncodeCache, - values: &mut Vec, - key: *const ExprData, - obj: LeanOwned, - ) { - let out = obj.borrow().to_owned_ref(); - cache.exprs.insert(key, obj); - values.push(out); - } - - while let Some(frame) = stack.pop() { - match frame { - EFrame::Process(e) => { - let key = std::sync::Arc::as_ptr(&e.0); - if let Some(cached) = cache.exprs.get(&key) { - values.push(cached.borrow().to_owned_ref()); - continue; - } - match e.as_data() { - ExprData::Bvar(idx, _) => { - let obj = unsafe { - LeanOwned::from_raw(lean_expr_mk_bvar( - LeanNat::from_nat(idx).into_raw(), - )) - }; - finish(cache, &mut values, key, obj); - }, - // FVarId / MVarId are trivial structures over Name: runtime - // repr is the Name itself. - ExprData::Fvar(name, _) => { - let name = encode_name(cache, name); - let obj = unsafe { - LeanOwned::from_raw(lean_expr_mk_fvar(name.into_raw())) - }; - finish(cache, &mut values, key, obj); - }, - ExprData::Mvar(name, _) => { - let name = encode_name(cache, name); - let obj = unsafe { - LeanOwned::from_raw(lean_expr_mk_mvar(name.into_raw())) - }; - finish(cache, &mut values, key, obj); - }, - ExprData::Sort(level, _) => { - let level = encode_level(cache, level); - let obj = unsafe { - LeanOwned::from_raw(lean_expr_mk_sort(level.into_raw())) - }; - finish(cache, &mut values, key, obj); - }, - ExprData::Const(name, levels, _) => { - let name = encode_name(cache, name); - let levels = levels - .iter() - .map(|l| encode_level(cache, l)) - .collect::>(); - let obj = unsafe { - LeanOwned::from_raw(lean_expr_mk_const( - name.into_raw(), - LeanOwned::from(levels).into_raw(), - )) - }; - finish(cache, &mut values, key, obj); - }, - ExprData::Lit(lit, _) => { - let obj = unsafe { - LeanOwned::from_raw(lean_expr_mk_lit( - encode_literal(lit).into_raw(), - )) - }; - finish(cache, &mut values, key, obj); - }, - ExprData::App(f, a, _) => { - stack.push(EFrame::Build(e)); - stack.push(EFrame::Process(a)); - stack.push(EFrame::Process(f)); - }, - ExprData::Lam(_, ty, body, _, _) - | ExprData::ForallE(_, ty, body, _, _) => { - stack.push(EFrame::Build(e)); - stack.push(EFrame::Process(body)); - stack.push(EFrame::Process(ty)); - }, - ExprData::LetE(_, ty, value, body, _, _) => { - stack.push(EFrame::Build(e)); - stack.push(EFrame::Process(body)); - stack.push(EFrame::Process(value)); - stack.push(EFrame::Process(ty)); - }, - ExprData::Mdata(_, inner, _) | ExprData::Proj(_, _, inner, _) => { - stack.push(EFrame::Build(e)); - stack.push(EFrame::Process(inner)); - }, - } - }, - EFrame::Build(e) => { - let key = std::sync::Arc::as_ptr(&e.0); - match e.as_data() { - ExprData::App(..) => { - let a = values.pop().expect("app arg on value stack"); - let f = values.pop().expect("app fn on value stack"); - let obj = unsafe { - LeanOwned::from_raw(lean_expr_mk_app(f.into_raw(), a.into_raw())) - }; - finish(cache, &mut values, key, obj); - }, - ExprData::Lam(name, _, _, bi, _) => { - let body = values.pop().expect("lam body on value stack"); - let ty = values.pop().expect("lam ty on value stack"); - let name = encode_name(cache, name); - let obj = unsafe { - LeanOwned::from_raw(lean_expr_mk_lambda( - name.into_raw(), - ty.into_raw(), - body.into_raw(), - binder_info_byte(bi), - )) - }; - finish(cache, &mut values, key, obj); - }, - ExprData::ForallE(name, _, _, bi, _) => { - let body = values.pop().expect("forall body on value stack"); - let ty = values.pop().expect("forall ty on value stack"); - let name = encode_name(cache, name); - let obj = unsafe { - LeanOwned::from_raw(lean_expr_mk_forall( - name.into_raw(), - ty.into_raw(), - body.into_raw(), - binder_info_byte(bi), - )) - }; - finish(cache, &mut values, key, obj); - }, - ExprData::LetE(name, _, _, _, nondep, _) => { - let body = values.pop().expect("let body on value stack"); - let value = values.pop().expect("let value on value stack"); - let ty = values.pop().expect("let ty on value stack"); - let name = encode_name(cache, name); - let obj = unsafe { - LeanOwned::from_raw(lean_expr_mk_let( - name.into_raw(), - ty.into_raw(), - value.into_raw(), - body.into_raw(), - u8::from(*nondep), - )) - }; - finish(cache, &mut values, key, obj); - }, - ExprData::Mdata(kv, _, _) => { - let inner = values.pop().expect("mdata inner on value stack"); - let kvmap = encode_kvmap(cache, kv)?; - let obj = unsafe { - LeanOwned::from_raw(lean_expr_mk_mdata( - kvmap.into_raw(), - inner.into_raw(), - )) - }; - finish(cache, &mut values, key, obj); - }, - ExprData::Proj(type_name, idx, _, _) => { - let inner = values.pop().expect("proj struct on value stack"); - let type_name = encode_name(cache, type_name); - let obj = unsafe { - LeanOwned::from_raw(lean_expr_mk_proj( - type_name.into_raw(), - LeanNat::from_nat(idx).into_raw(), - inner.into_raw(), - )) - }; - finish(cache, &mut values, key, obj); - }, - _ => unreachable!("leaf variants never get Build frames"), - } - }, - } - } - - match (values.pop(), values.is_empty()) { - (Some(v), true) => Ok(v), - (Some(_), false) => Err("encode_expr: unbalanced value stack".to_string()), - (None, _) => Err("encode_expr: empty result stack".to_string()), - } -} - -fn encode_name_list(cache: &mut LeanEncodeCache, names: &[Name]) -> LeanOwned { - names.iter().map(|n| encode_name(cache, n)).collect::>().into() -} - -fn encode_constant_val( - cache: &mut LeanEncodeCache, - cv: &ConstantVal, -) -> Result { - let obj = LeanIxConstantVal::alloc(0); - obj.set_obj(0, encode_name(cache, &cv.name)); - obj.set_obj(1, encode_name_list(cache, &cv.level_params)); - obj.set_obj(2, encode_expr(cache, &cv.typ)?); - Ok(obj.into()) -} - -fn encode_reducibility_hints(hints: &ReducibilityHints) -> LeanOwned { - match hints { - ReducibilityHints::Opaque => LeanOwned::box_usize(0), - ReducibilityHints::Abbrev => LeanOwned::box_usize(1), - ReducibilityHints::Regular(n) => { - let obj = LeanIxReducibilityHints::alloc(2); - obj.set_num_32(0, *n); - obj.into() - }, - } -} - -const fn definition_safety_byte(safety: &DefinitionSafety) -> u8 { - match safety { - DefinitionSafety::Unsafe => 0, - DefinitionSafety::Safe => 1, - DefinitionSafety::Partial => 2, - } -} - -const fn quot_kind_byte(kind: &QuotKind) -> u8 { - match kind { - QuotKind::Type => 0, - QuotKind::Ctor => 1, - QuotKind::Lift => 2, - QuotKind::Ind => 3, - } -} - -fn encode_recursor_rule( - cache: &mut LeanEncodeCache, - rule: &RecursorRule, -) -> Result { - let obj = LeanIxRecursorRule::alloc(0); - obj.set_obj(0, encode_name(cache, &rule.ctor)); - obj.set_obj(1, LeanNat::from_nat(&rule.n_fields)); - obj.set_obj(2, encode_expr(cache, &rule.rhs)?); - Ok(obj.into()) -} - -/// Encode a `ConstantInfo` as a real `Lean.ConstantInfo`. Field orders and -/// scalar placements mirror `lean_env.rs::decode_constant_info` exactly. -pub fn encode_constant_info( - cache: &mut LeanEncodeCache, - ci: &ConstantInfo, -) -> Result { - let (tag, inner): (u8, LeanOwned) = match ci { - ConstantInfo::AxiomInfo(v) => { - let obj = LeanIxAxiomVal::alloc(0); - obj.set_obj(0, encode_constant_val(cache, &v.cnst)?); - obj.set_num_8(0, u8::from(v.is_unsafe)); - (0, obj.into()) - }, - ConstantInfo::DefnInfo(v) => { - let obj = LeanIxDefinitionVal::alloc(0); - obj.set_obj(0, encode_constant_val(cache, &v.cnst)?); - obj.set_obj(1, encode_expr(cache, &v.value)?); - obj.set_obj(2, encode_reducibility_hints(&v.hints)); - obj.set_obj(3, encode_name_list(cache, &v.all)); - obj.set_num_8(0, definition_safety_byte(&v.safety)); - (1, obj.into()) - }, - ConstantInfo::ThmInfo(v) => { - let obj = LeanIxTheoremVal::alloc(0); - obj.set_obj(0, encode_constant_val(cache, &v.cnst)?); - obj.set_obj(1, encode_expr(cache, &v.value)?); - obj.set_obj(2, encode_name_list(cache, &v.all)); - (2, obj.into()) - }, - ConstantInfo::OpaqueInfo(v) => { - let obj = LeanIxOpaqueVal::alloc(0); - obj.set_obj(0, encode_constant_val(cache, &v.cnst)?); - obj.set_obj(1, encode_expr(cache, &v.value)?); - obj.set_obj(2, encode_name_list(cache, &v.all)); - obj.set_num_8(0, u8::from(v.is_unsafe)); - (3, obj.into()) - }, - ConstantInfo::QuotInfo(v) => { - let obj = LeanIxQuotVal::alloc(0); - obj.set_obj(0, encode_constant_val(cache, &v.cnst)?); - obj.set_num_8(0, quot_kind_byte(&v.kind)); - (4, obj.into()) - }, - ConstantInfo::InductInfo(v) => { - let obj = LeanIxInductiveVal::alloc(0); - obj.set_obj(0, encode_constant_val(cache, &v.cnst)?); - obj.set_obj(1, LeanNat::from_nat(&v.num_params)); - obj.set_obj(2, LeanNat::from_nat(&v.num_indices)); - obj.set_obj(3, encode_name_list(cache, &v.all)); - obj.set_obj(4, encode_name_list(cache, &v.ctors)); - obj.set_obj(5, LeanNat::from_nat(&v.num_nested)); - obj.set_num_8(0, u8::from(v.is_rec)); - obj.set_num_8(1, u8::from(v.is_unsafe)); - obj.set_num_8(2, u8::from(v.is_reflexive)); - (5, obj.into()) - }, - ConstantInfo::CtorInfo(v) => { - let obj = LeanIxConstructorVal::alloc(0); - obj.set_obj(0, encode_constant_val(cache, &v.cnst)?); - obj.set_obj(1, encode_name(cache, &v.induct)); - obj.set_obj(2, LeanNat::from_nat(&v.cidx)); - obj.set_obj(3, LeanNat::from_nat(&v.num_params)); - obj.set_obj(4, LeanNat::from_nat(&v.num_fields)); - obj.set_num_8(0, u8::from(v.is_unsafe)); - (6, obj.into()) - }, - ConstantInfo::RecInfo(v) => { - let obj = LeanIxRecursorVal::alloc(0); - obj.set_obj(0, encode_constant_val(cache, &v.cnst)?); - obj.set_obj(1, encode_name_list(cache, &v.all)); - obj.set_obj(2, LeanNat::from_nat(&v.num_params)); - obj.set_obj(3, LeanNat::from_nat(&v.num_indices)); - obj.set_obj(4, LeanNat::from_nat(&v.num_motives)); - obj.set_obj(5, LeanNat::from_nat(&v.num_minors)); - let mut rules: Vec = Vec::with_capacity(v.rules.len()); - for rule in &v.rules { - rules.push(encode_recursor_rule(cache, rule)?); - } - obj.set_obj(6, rules.into_iter().collect::>()); - obj.set_num_8(0, u8::from(v.k)); - obj.set_num_8(1, u8::from(v.is_unsafe)); - (7, obj.into()) - }, - }; - let outer = LeanIxConstantInfo::alloc(tag); - outer.set_obj(0, inner); - Ok(outer.into()) -} diff --git a/crates/ffi/src/lean_env.rs b/crates/ffi/src/lean_env.rs deleted file mode 100644 index 1c86f0889..000000000 --- a/crates/ffi/src/lean_env.rs +++ /dev/null @@ -1,1104 +0,0 @@ -//! Decode Lean kernel objects from their in-memory C representation. -//! -//! Provides functions to walk Lean object pointers and decode them into -//! the Rust `Name`, `Level`, `Expr`, and `ConstantInfo` types defined in -//! `ix_common::env`. Used by the compilation pipeline to read the Lean -//! environment before transforming it to Ixon format. -//! -//! Uses a two-level cache (`GlobalCache` + `LocalCache`) to avoid redundant -//! decoding of shared subterms when processing environments in parallel. - -#![allow(clippy::cast_possible_truncation)] -#![allow(clippy::cast_precision_loss)] -#![allow(clippy::cast_possible_wrap)] - -use dashmap::DashMap; -use rayon::prelude::*; - -use rustc_hash::FxHashMap; - -#[cfg(feature = "test-ffi")] -use ix_compile::compile::{CompileOptions, compile_env_with_options}; -#[cfg(feature = "test-ffi")] -use ix_compile::decompile::{check_decompile, decompile_env}; -use std::sync::Arc; - -use lean_ffi::nat::Nat; -use lean_ffi::object::LeanNat; -use lean_ffi::object::{ - LeanArray, LeanBorrowed, LeanList, LeanOwned, LeanRef, LeanShared, -}; - -use crate::lean::{ - LeanIxAxiomVal, LeanIxConstantInfo, LeanIxConstantVal, LeanIxConstructorVal, - LeanIxDataValue, LeanIxDefinitionVal, LeanIxExpr, LeanIxInductiveVal, - LeanIxLevel, LeanIxLiteral, LeanIxName, LeanIxOpaqueVal, LeanIxQuotVal, - LeanIxRecursorRule, LeanIxRecursorVal, LeanIxReducibilityHints, - LeanIxSourceInfo, LeanIxSubstring, LeanIxSyntax, LeanIxSyntaxPreresolved, - LeanIxTheoremVal, -}; - -use ix_common::env::{ - AxiomVal, BinderInfo, ConstantInfo, ConstantVal, ConstructorVal, DataValue, - DefinitionSafety, DefinitionVal, Env, Expr, InductiveVal, Int, Level, - Literal, Name, OpaqueVal, QuotKind, QuotVal, RecursorRule, RecursorVal, - ReducibilityHints, SourceInfo, Substring, Syntax, SyntaxPreresolved, - TheoremVal, -}; - -const PARALLEL_THRESHOLD: usize = 100; - -/// Global cache for Names, shared across all threads. -#[derive(Default)] -pub struct GlobalCache { - names: DashMap<*mut lean_ffi::include::lean_object, Name>, -} - -impl GlobalCache { - fn new() -> Self { - Self { names: DashMap::new() } - } - - fn with_capacity(capacity: usize) -> Self { - Self { names: DashMap::with_capacity(capacity) } - } -} - -// SAFETY: The raw pointers are only used as keys for identity comparison. -// The underlying Lean memory remains valid for the duration of decoding. -unsafe impl Send for GlobalCache {} -unsafe impl Sync for GlobalCache {} - -/// Thread-local cache for Levels and Exprs. -#[derive(Default)] -struct LocalCache { - univs: FxHashMap<*mut lean_ffi::include::lean_object, Level>, - exprs: FxHashMap<*mut lean_ffi::include::lean_object, Expr>, -} - -// SAFETY: LocalCache is only accessed by a single thread. -unsafe impl Send for LocalCache {} - -/// Combined cache reference passed to decoding functions. -pub struct Cache<'g> { - global: &'g GlobalCache, - local: LocalCache, -} - -impl<'g> Cache<'g> { - pub fn new(global: &'g GlobalCache) -> Self { - Self { global, local: LocalCache::default() } - } -} - -/// Collect list elements as borrowed pointers (no refcount changes). -/// Uses `LeanList::to_vec` which preserves the `'a` lifetime from the -/// underlying Lean objects rather than tying it to a local borrow. -fn collect_list_borrowed<'a>( - list: LeanList>, -) -> Vec> { - list.to_vec() -} - -/// Collect list elements as LeanShared handles for cross-thread use. -/// The caller should have already MT-marked the parent list via `LeanShared::new`, -/// so `lean_mark_mt` on each element is a single `lean_is_st` check (fast no-op). -fn collect_list_shared(list: LeanList>) -> Vec { - list.iter().map(|b| LeanShared::new(b.to_owned_ref())).collect() -} - -// Name decoding with global cache -pub fn decode_name(obj: LeanBorrowed<'_>, global: &GlobalCache) -> Name { - let ptr = obj.as_raw(); - // Fast path: check if already cached - if let Some(name) = global.names.get(&ptr) { - return name.clone(); - } - - // Compute the name - let name = if obj.is_scalar() { - Name::anon() - } else { - let n = LeanIxName::from_ctor(obj.as_ctor()); - let pre = decode_name(n.get_obj(0), global); - let pos = n.get_obj(1); - match n.as_ctor().tag() { - 1 => Name::str(pre, pos.as_string().to_string()), - 2 => Name::num(pre, LeanNat::to_nat(&pos)), - tag => unreachable!("Invalid Lean.Name tag: {tag}"), - } - }; - - // Insert and return (entry API handles races gracefully) - global.names.entry(ptr).or_insert(name).clone() -} - -/// Decode an `@& Array Lean.Name` FFI argument into a `Vec`. -/// -/// Uses a fresh `GlobalCache` to deduplicate shared sub-names within the -/// array (the cache keys by pointer identity, so repeat prefixes like -/// `Lean.Meta.Grind.Arith.Cutsat` are decoded once). Callers don't need -/// to manage the cache; it's dropped when this function returns. -/// -/// Preferred over going through `String` + `parse_name` at the FFI -/// boundary: Lean's `Name.toString` adds `«»` escaping for components -/// that aren't valid identifiers, and the resulting string doesn't -/// round-trip through a naive split-on-`.` parser. By decoding the -/// structured `Lean.Name` directly we match the kernel's stored `Name`s -/// exactly (same component strings, same content hash). -pub fn decode_name_array(arr: &LeanArray>) -> Vec { - let global = GlobalCache::new(); - arr.map(|obj| decode_name(obj, &global)) -} - -fn decode_level(obj: LeanBorrowed<'_>, cache: &mut Cache<'_>) -> Level { - let ptr = obj.as_raw(); - if let Some(cached) = cache.local.univs.get(&ptr) { - return cached.clone(); - } - let level = if obj.is_scalar() { - Level::zero() - } else { - let l = LeanIxLevel::from_ctor(obj.as_ctor()); - match l.as_ctor().tag() { - 1 => Level::succ(decode_level(l.get_obj(0), cache)), - 2 => Level::max( - decode_level(l.get_obj(0), cache), - decode_level(l.get_obj(1), cache), - ), - 3 => Level::imax( - decode_level(l.get_obj(0), cache), - decode_level(l.get_obj(1), cache), - ), - 4 => Level::param(decode_name(l.get_obj(0), cache.global)), - 5 => Level::mvar(decode_name(l.get_obj(0), cache.global)), - tag => unreachable!("Invalid Lean.Level tag: {tag}"), - } - }; - cache.local.univs.insert(ptr, level.clone()); - level -} - -fn decode_substring(obj: LeanBorrowed<'_>) -> Substring { - let s = LeanIxSubstring::from_ctor(obj.as_ctor()); - let str = s.get_obj(0).as_string().to_string(); - let start_pos = LeanNat::to_nat(&s.get_obj(1)); - let stop_pos = LeanNat::to_nat(&s.get_obj(2)); - Substring { str, start_pos, stop_pos } -} - -fn decode_source_info(obj: LeanBorrowed<'_>) -> SourceInfo { - if obj.is_scalar() { - return SourceInfo::None; - } - let si = LeanIxSourceInfo::from_ctor(obj.as_ctor()); - match si.as_ctor().tag() { - 0 => { - let leading = decode_substring(si.get_obj(0)); - let pos = LeanNat::to_nat(&si.get_obj(1)); - let trailing = decode_substring(si.get_obj(2)); - let end_pos = LeanNat::to_nat(&si.get_obj(3)); - SourceInfo::Original(leading, pos, trailing, end_pos) - }, - 1 => { - let pos = LeanNat::to_nat(&si.get_obj(0)); - let end_pos = LeanNat::to_nat(&si.get_obj(1)); - let canonical = si.get_num_8(0) != 0; - SourceInfo::Synthetic(pos, end_pos, canonical) - }, - tag => unreachable!("Invalid Lean.SourceInfo tag: {tag}"), - } -} - -fn decode_syntax_preresolved( - obj: LeanBorrowed<'_>, - cache: &mut Cache<'_>, -) -> SyntaxPreresolved { - let p = LeanIxSyntaxPreresolved::from_ctor(obj.as_ctor()); - match p.as_ctor().tag() { - 0 => { - let name = decode_name(p.get_obj(0), cache.global); - SyntaxPreresolved::Namespace(name) - }, - 1 => { - let name = decode_name(p.get_obj(0), cache.global); - let fields: Vec = p - .get_obj(1) - .as_list() - .iter() - .map(|o| o.as_string().to_string()) - .collect(); - SyntaxPreresolved::Decl(name, fields) - }, - tag => unreachable!("Invalid Lean.Syntax.Preresolved tag: {tag}"), - } -} - -fn decode_syntax(obj: LeanBorrowed<'_>, cache: &mut Cache<'_>) -> Syntax { - if obj.is_scalar() { - return Syntax::Missing; - } - let s = LeanIxSyntax::from_ctor(obj.as_ctor()); - match s.as_ctor().tag() { - 1 => { - let info = decode_source_info(s.get_obj(0)); - let kind = decode_name(s.get_obj(1), cache.global); - let args: Vec<_> = s - .get_obj(2) - .as_array() - .iter() - .map(|o| decode_syntax(o, cache)) - .collect(); - Syntax::Node(info, kind, args) - }, - 2 => { - let info = decode_source_info(s.get_obj(0)); - Syntax::Atom(info, s.get_obj(1).as_string().to_string()) - }, - 3 => { - let info = decode_source_info(s.get_obj(0)); - let raw_val = decode_substring(s.get_obj(1)); - let val = decode_name(s.get_obj(2), cache.global); - let preresolved = collect_list_borrowed(s.get_obj(3).as_list()) - .into_iter() - .map(|o| decode_syntax_preresolved(o, cache)) - .collect(); - Syntax::Ident(info, raw_val, val, preresolved) - }, - tag => unreachable!("Invalid Lean.Syntax tag: {tag}"), - } -} - -fn decode_name_data_value( - obj: LeanBorrowed<'_>, - cache: &mut Cache<'_>, -) -> (Name, DataValue) { - // Outer Prod (Name × DataValue) has no public LeanProd - // constructor, so read the two fields through the raw ctor. - let pair = obj.as_ctor(); - let name = decode_name(pair.get(0), cache.global); - let dv = LeanIxDataValue::from_ctor(pair.get(1).as_ctor()); - let data_value = match dv.as_ctor().tag() { - 0 => DataValue::OfString(dv.get_obj(0).as_string().to_string()), - 1 => DataValue::OfBool(dv.get_num_8(0) != 0), - 2 => DataValue::OfName(decode_name(dv.get_obj(0), cache.global)), - 3 => DataValue::OfNat(LeanNat::to_nat(&dv.get_obj(0))), - 4 => { - // `Lean.Int` is a builtin runtime type sharing `Nat`'s - // representation — a tagged scalar or a GMP mpz object, never an - // `ofNat`/`negSucc` ctor cell (`Int.ofNat` compiles to the - // identity `lean_nat_to_int`). Decode by value and rebuild the - // ctor view; `LeanIxInt` describes only the `Ix.Int` mirror - // inductive, which really is a two-ctor object. - let iobj = dv.get_obj(0); - let int = if iobj.is_scalar() { - // Scalars box `v` as `(v << 1) | 1`; an arithmetic shift - // recovers the signed value. - let v = (iobj.as_raw() as isize) >> 1; - // Both magnitudes below are non-negative by construction -- the - // branch settles the sign, and `negSucc n = -(n + 1)` so `n` is - // `-(v + 1)` for `v < 0` -- and an `isize` magnitude always fits - // `u64` on supported targets, so neither conversion can fail. - let mag = - |n: isize| u64::try_from(n).expect("isize magnitude fits u64"); - if v >= 0 { - Int::OfNat(Nat::from(mag(v))) - } else { - Int::NegSucc(Nat::from(mag(-(v + 1)))) - } - } else { - let neg = unsafe { - lean_ffi::include::lean_int_dec_lt( - iobj.as_raw(), - lean_ffi::include::lean_box(0), - ) - } != 0; - let abs = unsafe { - LeanOwned::from_raw(lean_ffi::include::lean_nat_abs(iobj.as_raw())) - }; - let mag = LeanNat::to_nat(&abs); - if neg { - // negSucc n = -(n + 1), so n = |v| - 1 (|v| ≥ 1 here). - Int::NegSucc(Nat(mag.0 - 1u32)) - } else { - Int::OfNat(mag) - } - }; - DataValue::OfInt(int) - }, - 5 => DataValue::OfSyntax(decode_syntax(dv.get_obj(0), cache).into()), - tag => unreachable!("Invalid Lean.DataValue tag: {tag}"), - }; - (name, data_value) -} - -pub fn decode_expr(obj: LeanBorrowed<'_>, cache: &mut Cache<'_>) -> Expr { - let ptr = obj.as_raw(); - if let Some(cached) = cache.local.exprs.get(&ptr) { - return cached.clone(); - } - let e = LeanIxExpr::from_ctor(obj.as_ctor()); - let decode_binder_info = |b: u8| match b { - 0 => BinderInfo::Default, - 1 => BinderInfo::Implicit, - 2 => BinderInfo::StrictImplicit, - 3 => BinderInfo::InstImplicit, - _ => unreachable!("Invalid Lean.BinderInfo tag: {b}"), - }; - let expr = match e.as_ctor().tag() { - 0 => Expr::bvar(LeanNat::to_nat(&e.get_obj(0))), - 1 => Expr::fvar(decode_name(e.get_obj(0), cache.global)), - 2 => Expr::mvar(decode_name(e.get_obj(0), cache.global)), - 3 => Expr::sort(decode_level(e.get_obj(0), cache)), - 4 => { - let name = decode_name(e.get_obj(0), cache.global); - let levels = collect_list_borrowed(e.get_obj(1).as_list()) - .into_iter() - .map(|o| decode_level(o, cache)) - .collect(); - Expr::cnst(name, levels) - }, - 5 => { - let f = decode_expr(e.get_obj(0), cache); - let a = decode_expr(e.get_obj(1), cache); - Expr::app(f, a) - }, - 6 => { - let binder_name = decode_name(e.get_obj(0), cache.global); - let binder_typ = decode_expr(e.get_obj(1), cache); - let body = decode_expr(e.get_obj(2), cache); - let binder_info = decode_binder_info(e.get_num_8(0)); - Expr::lam(binder_name, binder_typ, body, binder_info) - }, - 7 => { - let binder_name = decode_name(e.get_obj(0), cache.global); - let binder_typ = decode_expr(e.get_obj(1), cache); - let body = decode_expr(e.get_obj(2), cache); - let binder_info = decode_binder_info(e.get_num_8(0)); - Expr::all(binder_name, binder_typ, body, binder_info) - }, - 8 => { - let decl_name = decode_name(e.get_obj(0), cache.global); - let typ = decode_expr(e.get_obj(1), cache); - let value = decode_expr(e.get_obj(2), cache); - let body = decode_expr(e.get_obj(3), cache); - let nondep = e.get_num_8(0) != 0; - Expr::letE(decl_name, typ, value, body, nondep) - }, - 9 => { - let lit = LeanIxLiteral::from_ctor(e.get_obj(0).as_ctor()); - let inner = lit.get_obj(0); - match lit.as_ctor().tag() { - 0 => Expr::lit(Literal::NatVal(LeanNat::to_nat(&inner))), - 1 => Expr::lit(Literal::StrVal(inner.as_string().to_string())), - tag => unreachable!("Invalid Lean.Literal tag: {tag}"), - } - }, - 10 => { - let kv_map: Vec<_> = collect_list_borrowed(e.get_obj(0).as_list()) - .into_iter() - .map(|o| decode_name_data_value(o, cache)) - .collect(); - let expr = decode_expr(e.get_obj(1), cache); - Expr::mdata(kv_map, expr) - }, - 11 => { - let typ_name = decode_name(e.get_obj(0), cache.global); - let idx = LeanNat::to_nat(&e.get_obj(1)); - let struct_expr = decode_expr(e.get_obj(2), cache); - Expr::proj(typ_name, idx, struct_expr) - }, - tag => unreachable!("Invalid Lean.Expr tag: {tag}"), - }; - cache.local.exprs.insert(ptr, expr.clone()); - expr -} - -fn decode_recursor_rule( - obj: LeanBorrowed<'_>, - cache: &mut Cache<'_>, -) -> RecursorRule { - let r = LeanIxRecursorRule::from_ctor(obj.as_ctor()); - let ctor_name = decode_name(r.get_obj(0), cache.global); - let n_fields = LeanNat::to_nat(&r.get_obj(1)); - let rhs = decode_expr(r.get_obj(2), cache); - RecursorRule { ctor: ctor_name, n_fields, rhs } -} - -fn decode_constant_val( - obj: LeanBorrowed<'_>, - cache: &mut Cache<'_>, -) -> ConstantVal { - let cv = LeanIxConstantVal::from_ctor(obj.as_ctor()); - let name = decode_name(cv.get_obj(0), cache.global); - let level_params: Vec<_> = collect_list_borrowed(cv.get_obj(1).as_list()) - .into_iter() - .map(|o| decode_name(o, cache.global)) - .collect(); - let typ = decode_expr(cv.get_obj(2), cache); - ConstantVal { name, level_params, typ } -} - -pub fn decode_constant_info( - obj: LeanBorrowed<'_>, - cache: &mut Cache<'_>, -) -> ConstantInfo { - let outer = LeanIxConstantInfo::from_ctor(obj.as_ctor()); - let inner_obj = outer.get_obj(0); - - match outer.as_ctor().tag() { - 0 => { - let inner = LeanIxAxiomVal::from_ctor(inner_obj.as_ctor()); - let constant_val = decode_constant_val(inner.get_obj(0), cache); - let is_unsafe = inner.get_num_8(0) != 0; - ConstantInfo::AxiomInfo(AxiomVal { cnst: constant_val, is_unsafe }) - }, - 1 => { - let inner = LeanIxDefinitionVal::from_ctor(inner_obj.as_ctor()); - let constant_val = decode_constant_val(inner.get_obj(0), cache); - let value = decode_expr(inner.get_obj(1), cache); - let hints_obj = inner.get_obj(2); - let hints = if hints_obj.is_scalar() { - match hints_obj.unbox_usize() { - 0 => ReducibilityHints::Opaque, - 1 => ReducibilityHints::Abbrev, - tag => unreachable!("Invalid scalar ReducibilityHints tag: {tag}"), - } - } else { - let h = LeanIxReducibilityHints::from_ctor(hints_obj.as_ctor()); - ReducibilityHints::Regular(h.get_num_32(0)) - }; - let all: Vec<_> = collect_list_borrowed(inner.get_obj(3).as_list()) - .into_iter() - .map(|o| decode_name(o, cache.global)) - .collect(); - let safety = match inner.get_num_8(0) { - 0 => DefinitionSafety::Unsafe, - 1 => DefinitionSafety::Safe, - 2 => DefinitionSafety::Partial, - b => unreachable!("Invalid DefinitionSafety byte: {b}"), - }; - ConstantInfo::DefnInfo(DefinitionVal { - cnst: constant_val, - value, - hints, - safety, - all, - }) - }, - 2 => { - let inner = LeanIxTheoremVal::from_ctor(inner_obj.as_ctor()); - let constant_val = decode_constant_val(inner.get_obj(0), cache); - let value = decode_expr(inner.get_obj(1), cache); - let all: Vec<_> = collect_list_borrowed(inner.get_obj(2).as_list()) - .into_iter() - .map(|o| decode_name(o, cache.global)) - .collect(); - ConstantInfo::ThmInfo(TheoremVal { cnst: constant_val, value, all }) - }, - 3 => { - let inner = LeanIxOpaqueVal::from_ctor(inner_obj.as_ctor()); - let constant_val = decode_constant_val(inner.get_obj(0), cache); - let value = decode_expr(inner.get_obj(1), cache); - let all: Vec<_> = collect_list_borrowed(inner.get_obj(2).as_list()) - .into_iter() - .map(|o| decode_name(o, cache.global)) - .collect(); - let is_unsafe = inner.get_num_8(0) != 0; - ConstantInfo::OpaqueInfo(OpaqueVal { - cnst: constant_val, - value, - is_unsafe, - all, - }) - }, - 4 => { - let inner = LeanIxQuotVal::from_ctor(inner_obj.as_ctor()); - let constant_val = decode_constant_val(inner.get_obj(0), cache); - let kind = match inner.get_num_8(0) { - 0 => QuotKind::Type, - 1 => QuotKind::Ctor, - 2 => QuotKind::Lift, - 3 => QuotKind::Ind, - b => unreachable!("Invalid QuotKind byte: {b}"), - }; - ConstantInfo::QuotInfo(QuotVal { cnst: constant_val, kind }) - }, - 5 => { - let inner = LeanIxInductiveVal::from_ctor(inner_obj.as_ctor()); - let constant_val = decode_constant_val(inner.get_obj(0), cache); - let num_params = LeanNat::to_nat(&inner.get_obj(1)); - let num_indices = LeanNat::to_nat(&inner.get_obj(2)); - let all: Vec<_> = collect_list_borrowed(inner.get_obj(3).as_list()) - .into_iter() - .map(|o| decode_name(o, cache.global)) - .collect(); - let ctors: Vec<_> = collect_list_borrowed(inner.get_obj(4).as_list()) - .into_iter() - .map(|o| decode_name(o, cache.global)) - .collect(); - let num_nested = LeanNat::to_nat(&inner.get_obj(5)); - let is_rec = inner.get_num_8(0) != 0; - let is_unsafe = inner.get_num_8(1) != 0; - let is_reflexive = inner.get_num_8(2) != 0; - ConstantInfo::InductInfo(InductiveVal { - cnst: constant_val, - num_params, - num_indices, - all, - ctors, - num_nested, - is_rec, - is_unsafe, - is_reflexive, - }) - }, - 6 => { - let inner = LeanIxConstructorVal::from_ctor(inner_obj.as_ctor()); - let constant_val = decode_constant_val(inner.get_obj(0), cache); - let induct = decode_name(inner.get_obj(1), cache.global); - let cidx = LeanNat::to_nat(&inner.get_obj(2)); - let num_params = LeanNat::to_nat(&inner.get_obj(3)); - let num_fields = LeanNat::to_nat(&inner.get_obj(4)); - let is_unsafe = inner.get_num_8(0) != 0; - ConstantInfo::CtorInfo(ConstructorVal { - cnst: constant_val, - induct, - cidx, - num_params, - num_fields, - is_unsafe, - }) - }, - 7 => { - let inner = LeanIxRecursorVal::from_ctor(inner_obj.as_ctor()); - let constant_val = decode_constant_val(inner.get_obj(0), cache); - let all: Vec<_> = collect_list_borrowed(inner.get_obj(1).as_list()) - .into_iter() - .map(|o| decode_name(o, cache.global)) - .collect(); - let num_params = LeanNat::to_nat(&inner.get_obj(2)); - let num_indices = LeanNat::to_nat(&inner.get_obj(3)); - let num_motives = LeanNat::to_nat(&inner.get_obj(4)); - let num_minors = LeanNat::to_nat(&inner.get_obj(5)); - let rules: Vec<_> = collect_list_borrowed(inner.get_obj(6).as_list()) - .into_iter() - .map(|o| decode_recursor_rule(o, cache)) - .collect(); - let k = inner.get_num_8(0) != 0; - let is_unsafe = inner.get_num_8(1) != 0; - ConstantInfo::RecInfo(RecursorVal { - cnst: constant_val, - all, - num_params, - num_indices, - num_motives, - num_minors, - rules, - k, - is_unsafe, - }) - }, - tag => unreachable!("Invalid Lean.ConstantInfo tag: {tag}"), - } -} - -/// Decode a single (Name, ConstantInfo) pair. -fn decode_name_constant_info( - obj: LeanBorrowed<'_>, - global: &GlobalCache, -) -> (Name, ConstantInfo) { - let mut cache = Cache::new(global); - // Outer Prod (Name × ConstantInfo) has no public LeanProd - // constructor, so read the two fields through the raw ctor. - let pair = obj.as_ctor(); - let name = decode_name(pair.get(0), global); - let constant_info = decode_constant_info(pair.get(1), &mut cache); - (name, constant_info) -} - -/// Resident-decoded-constant bound for [`decode_env_lazy`]'s cache. -/// -/// Sized by sweep, not by hit rate: on FLT (511k consts) wall time is -/// flat from 4096 through 1M entries (60–67 s; misses hide in the -/// scheduler's dependency-stall slack) while peak RSS climbs from -/// 11.5 GiB (4k) → 13.0 (64k) → 24.7 (1M); Mathlib (737k consts) -/// confirms parity at this size (87.5 s / 17.0 GiB vs 87.3 s / -/// 18.3 GiB at 64k). So the cache buys RAM, not speed, and the bound -/// sits at the small end with headroom over the 4k floor measured to -/// still not thrash. -const LAZY_ENV_CACHE_ENTRIES: usize = 16384; - -/// Decode for the production compile FFI entries: an on-demand view -/// that avoids materializing the full Rust copy of the environment — -/// the eager copy is the single largest memory term at scale. The -/// on-demand decode costs a few percent of wall (per-constant fetches -/// vs one batched decode); `IX_COMPILE_EAGER=1` buys that back on -/// machines with RAM to spare. Measured on FLT: −8 % wall for -/// +13 GiB peak; Mathlib eager exceeds a 56 GB machine outright -/// (the copy sits alongside the Lean-held env). Test and roundtrip -/// entries keep [`decode_env`] (they re-read the env structurally -/// throughout). -pub fn decode_env_for_compile(list: LeanList>) -> Env { - if std::env::var("IX_COMPILE_EAGER").as_deref() == Ok("1") { - return decode_env(list); - } - decode_env_lazy(list, LAZY_ENV_CACHE_ENTRIES) -} - -/// Lazy variant of [`decode_env`]: decode only the *names* eagerly, -/// keep a per-constant `LeanShared` handle, and decode `ConstantInfo`s -/// on demand through `Env`'s bounded cache. -/// -/// Thread-safety is identical to the eager path, which already decodes -/// in parallel: `lean_mark_mt` (via `LeanShared::new`) transitions the -/// reachable graph to atomic refcounting, structural reads through -/// `LeanBorrowed` accessors are refcount-silent, and each element's -/// owned handle keeps its Lean objects alive for the `Env`'s lifetime -/// regardless of what the Lean side does after the FFI call. -pub fn decode_env_lazy( - list: LeanList>, - cache_entries: usize, -) -> Env { - let shared_list = LeanShared::new(list.inner().to_owned_ref()); - let objs = collect_list_shared(shared_list.borrow().as_list()); - let global = Arc::new(GlobalCache::with_capacity(objs.len() * 3)); - - // Name index (parallel; bodies untouched). Each element is a - // `Prod (Name × ConstantInfo)` ctor — field 0 is the name. - let named: Vec<(Name, LeanShared)> = objs - .into_par_iter() - .map(|o| { - let name = decode_name(o.borrow().as_ctor().get(0), &global); - (name, o) - }) - .collect(); - let names: Vec = named.iter().map(|(n, _)| n.clone()).collect(); - let handles: FxHashMap = named.into_iter().collect(); - - let fetch = move |name: &Name| { - let obj = handles.get(name)?; - let mut cache = Cache::new(&global); - Some(decode_constant_info(obj.borrow().as_ctor().get(1), &mut cache)) - }; - Env::new_lazy(names, Box::new(fetch), cache_entries) -} - -// Decode a Lean environment in parallel with hybrid caching. -pub fn decode_env(list: LeanList>) -> Env { - // Phase 1: Mark entire list graph as MT, then collect elements as LeanShared. - // lean_mark_mt recursively marks all reachable objects. Subsequent - // LeanShared::new calls on elements are a fast no-op (single is_st check). - let shared_list = LeanShared::new(list.inner().to_owned_ref()); - let objs = collect_list_shared(shared_list.borrow().as_list()); - - if objs.len() < PARALLEL_THRESHOLD { - // Sequential fallback for small environments — no MT overhead needed, - // but objects are already marked. Just borrow directly. - let global = GlobalCache::new(); - let mut env = Env::default(); - for o in &objs { - let (name, constant_info) = - decode_name_constant_info(o.borrow(), &global); - env.insert(name, constant_info); - } - return env; - } - - // Estimate: ~3 unique names per constant on average - let global = GlobalCache::with_capacity(objs.len() * 3); - - // Phase 2: Decode in parallel with shared global name cache - let pairs: Vec<(Name, ConstantInfo)> = objs - .into_par_iter() - .map(|shared| decode_name_constant_info(shared.borrow(), &global)) - .collect(); - - // Phase 3: Build final map - let mut env = Env::default(); - for (name, constant_info) in pairs { - env.insert(name, constant_info); - } - env -} - -// Debug/analysis entry point invoked via the `rust-compile` test flag in -// `Tests/Main.lean`. Exercises the full compile→decompile→check→serialize -// roundtrip and size analysis with phased logging. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -extern "C" fn rs_tmp_decode_const_map( - obj: LeanList>, -) -> usize { - let env = decode_env(obj); - let n = env.len(); - let env = Arc::new(env); - let t0 = std::time::Instant::now(); - - // Phase 1: Compile - eprintln!("[rust-compile] Phase 1: Compiling {n} constants..."); - let stt = match compile_env_with_options(&env, CompileOptions::default()) { - Ok(s) => s, - Err(e) => { - eprintln!("[rust-compile] Phase 1 FAILED: {e:?}"); - return n; - }, - }; - if !stt.ungrounded.is_empty() { - eprintln!( - "[rust-compile] Phase 1 FAILED: {} ungrounded input declarations", - stt.ungrounded.len(), - ); - let mut refusals: Vec<_> = stt - .ungrounded - .iter() - .map(|entry| (entry.key().pretty(), entry.value().clone())) - .collect(); - refusals.sort(); - for (name, reason) in refusals.iter().take(16) { - eprintln!("[rust-compile] ungrounded {name}: {reason}"); - } - return n; - } - eprintln!( - "[rust-compile] Phase 1 done in {:.2}s ({} consts, {} named, {} names, {} blobs)", - t0.elapsed().as_secs_f32(), - stt.env.const_count(), - stt.env.named.len(), - stt.env.names.len(), - stt.env.blob_count(), - ); - - // Phase 2: Decompile - eprintln!("[rust-compile] Phase 2: Decompiling..."); - let t1 = std::time::Instant::now(); - let dstt = match decompile_env(&stt) { - Ok(d) => d, - Err(e) => { - eprintln!( - "[rust-compile] Phase 2 FAILED after {:.2}s: {e:?}", - t1.elapsed().as_secs_f32() - ); - return n; - }, - }; - eprintln!( - "[rust-compile] Phase 2 done in {:.2}s ({} constants)", - t1.elapsed().as_secs_f32(), - dstt.env.len() - ); - - // Phase 3: Check roundtrip. `failures` accumulates roundtrip breakage - // here and in Phase 6; the function returns it, so a non-zero total fails - // the Lean `rust-compile` test. (Fatal phase aborts above return `n`, - // which is likewise non-zero — an abort is also a failure.) - eprintln!("[rust-compile] Phase 3: Checking decompile roundtrip..."); - let t2 = std::time::Instant::now(); - let mut failures = match check_decompile(env.as_ref(), &stt, &dstt) { - Ok(c) => c.failures(|_| false), - Err(e) => { - eprintln!("[rust-compile] Phase 3 check FAILED: {e:?}"); - 1 - }, - }; - eprintln!( - "[rust-compile] Phase 3 done in {:.2}s", - t2.elapsed().as_secs_f32() - ); - - // Phase 4: Size analysis - eprintln!("[rust-compile] Phase 4: Size analysis..."); - let _ = stt.env.serialized_size_breakdown(); - analyze_const_size(&stt, "Nat.add_comm"); - - // Phase 5: Serialize - eprintln!("[rust-compile] Phase 5: Serializing env..."); - let t3 = std::time::Instant::now(); - let mut serialized = Vec::new(); - if let Err(e) = stt.env.put(&mut serialized) { - eprintln!("[rust-compile] Phase 5 FAILED: {e}"); - return n; - } - eprintln!( - "[rust-compile] Phase 5 done: {} bytes in {:.2}s", - serialized.len(), - t3.elapsed().as_secs_f32() - ); - - // Phase 6: Deserialize + re-decompile - eprintln!("[rust-compile] Phase 6: Deserializing and re-decompiling..."); - let t4 = std::time::Instant::now(); - let mut buf: &[u8] = &serialized; - match ixon::env::Env::get(&mut buf) { - Ok(fresh_env) => { - let fresh_stt = ix_compile::compile::CompileState { - env: fresh_env, - sharing_limits: stt.sharing_limits.clone(), - ..Default::default() - }; - for entry in fresh_stt.env.named.iter() { - fresh_stt - .name_to_addr - .insert(entry.key().clone(), entry.value().addr.clone()); - } - match decompile_env(&fresh_stt) { - Ok(dstt2) => match check_decompile(env.as_ref(), &fresh_stt, &dstt2) { - Ok(c) => failures += c.failures(|_| false), - Err(e) => { - eprintln!("[rust-compile] Phase 6 check FAILED: {e:?}"); - failures += 1; - }, - }, - Err(e) => { - eprintln!("[rust-compile] Phase 6 re-decompile FAILED: {e:?}"); - return n; - }, - } - }, - Err(e) => { - eprintln!("[rust-compile] Phase 6 deserialize FAILED: {e}"); - return n; - }, - } - eprintln!( - "[rust-compile] Phase 6 done in {:.2}s", - t4.elapsed().as_secs_f32() - ); - - eprintln!( - "[rust-compile] All phases complete. Total: {:.2}s ({failures} roundtrip failure(s))", - t0.elapsed().as_secs_f32() - ); - failures -} - -#[cfg(feature = "test-ffi")] -/// Size breakdown for a constant: alpha-invariant vs metadata -#[derive(Default, Clone)] -struct ConstSizeBreakdown { - alpha_size: usize, // Alpha-invariant constant data - meta_size: usize, // Metadata (names, binder info, etc.) -} - -#[cfg(feature = "test-ffi")] -impl ConstSizeBreakdown { - fn total(&self) -> usize { - self.alpha_size + self.meta_size - } -} - -#[cfg(feature = "test-ffi")] -/// Analyze the serialized size of a constant and its transitive dependencies. -fn analyze_const_size(stt: &ix_compile::compile::CompileState, name_str: &str) { - // Build a global name index for metadata serialization - let name_index = build_name_index(stt); - - // Parse the name (e.g., "Nat.add_comm" -> Name::str(Name::str(Name::anon(), "Nat"), "add_comm")) - let name = parse_name(name_str); - - // Look up the constant's address - let addr = match stt.name_to_addr.get(&name) { - Some(a) => a.clone(), - None => { - println!("\n=== Size analysis for {} ===", name_str); - println!(" Constant not found"); - return; - }, - }; - - // Get the constant (materialize from the lazy entry). - let constant = match stt.env.get_const(&addr) { - Some(c) => c, - None => { - println!("\n=== Size analysis for {} ===", name_str); - println!(" Constant data not found at address"); - return; - }, - }; - - // Compute direct sizes (alpha-invariant and metadata) - let direct_breakdown = - compute_const_size_breakdown(&constant, &name, stt, &name_index); - - // Reuse the shared transitive-closure walker on Env. - let dep_addrs = stt.env.transitive_deps_excl(&addr); - let mut dep_breakdowns: Vec<(String, ConstSizeBreakdown)> = Vec::new(); - for dep_addr in dep_addrs { - let Some(dep_const) = stt.env.get_const(&dep_addr) else { - continue; - }; - // Get the name for this dependency (scan named entries) - let dep_name_opt: Option = stt - .env - .named - .iter() - .find(|e| e.value().addr == dep_addr) - .map(|e| e.key().clone()); - let dep_name_str = dep_name_opt - .as_ref() - .map_or_else(|| format!("{:.12}", dep_addr.hex()), |n| n.pretty()); - - let breakdown = if let Some(ref dep_name) = dep_name_opt { - compute_const_size_breakdown(&dep_const, dep_name, stt, &name_index) - } else { - ConstSizeBreakdown { - alpha_size: serialized_const_size(&dep_const), - meta_size: 0, - } - }; - - dep_breakdowns.push((dep_name_str, breakdown)); - } - - // Sort by total size descending - dep_breakdowns.sort_by_key(|entry| std::cmp::Reverse(entry.1.total())); - - let total_deps_alpha: usize = - dep_breakdowns.iter().map(|(_, b)| b.alpha_size).sum(); - let total_deps_meta: usize = - dep_breakdowns.iter().map(|(_, b)| b.meta_size).sum(); - let total_deps_size = total_deps_alpha + total_deps_meta; - - let total_alpha = direct_breakdown.alpha_size + total_deps_alpha; - let total_meta = direct_breakdown.meta_size + total_deps_meta; - let total_size = total_alpha + total_meta; - - println!("\n=== Size analysis for {} ===", name_str); - println!( - " Direct alpha-invariant size: {} bytes", - direct_breakdown.alpha_size - ); - println!(" Direct metadata size: {} bytes", direct_breakdown.meta_size); - println!(" Direct total size: {} bytes", direct_breakdown.total()); - println!(); - println!(" Transitive dependencies: {} constants", dep_breakdowns.len()); - println!( - " Dependencies alpha-invariant: {} bytes ({:.2} KB)", - total_deps_alpha, - total_deps_alpha as f64 / 1024.0 - ); - println!( - " Dependencies metadata: {} bytes ({:.2} KB)", - total_deps_meta, - total_deps_meta as f64 / 1024.0 - ); - println!( - " Dependencies total: {} bytes ({:.2} KB)", - total_deps_size, - total_deps_size as f64 / 1024.0 - ); - println!(); - println!( - " TOTAL alpha-invariant: {} bytes ({:.2} KB)", - total_alpha, - total_alpha as f64 / 1024.0 - ); - println!( - " TOTAL metadata: {} bytes ({:.2} KB)", - total_meta, - total_meta as f64 / 1024.0 - ); - println!( - " TOTAL size: {} bytes ({:.2} KB)", - total_size, - total_size as f64 / 1024.0 - ); - - // Show top 10 largest dependencies - if !dep_breakdowns.is_empty() { - println!("\n Top 10 largest dependencies (by total size):"); - for (name, breakdown) in dep_breakdowns.iter().take(10) { - println!( - " {} bytes (alpha: {}, meta: {}): {}", - breakdown.total(), - breakdown.alpha_size, - breakdown.meta_size, - name - ); - } - } -} - -/// Build a name index for metadata serialization. -#[cfg(feature = "test-ffi")] -fn build_name_index( - stt: &ix_compile::compile::CompileState, -) -> ixon::metadata::NameIndex { - use ix_common::address::Address; - use ixon::metadata::NameIndex; - - let mut idx = NameIndex::new(); - let mut counter: u64 = 0; - - // Add all names from the names map - for entry in stt.env.names.iter() { - idx.insert(entry.key().clone(), counter); - counter += 1; - } - - // Add anonymous name - let anon_addr = Address::from_blake3_hash(*Name::anon().get_hash()); - idx.entry(anon_addr).or_insert(counter); - - idx -} - -/// Compute size breakdown for a constant (alpha-invariant vs metadata). -#[cfg(feature = "test-ffi")] -fn compute_const_size_breakdown( - constant: &ixon::constant::Constant, - name: &Name, - stt: &ix_compile::compile::CompileState, - name_index: &ixon::metadata::NameIndex, -) -> ConstSizeBreakdown { - // Alpha-invariant size - let alpha_size = serialized_const_size(constant); - - // Metadata size - let meta_size = if let Some(named) = stt.env.named.get(name) { - serialized_meta_size(&named.meta(), name_index) - } else { - 0 - }; - - ConstSizeBreakdown { alpha_size, meta_size } -} - -/// Compute the serialized size of constant metadata. -#[cfg(feature = "test-ffi")] -fn serialized_meta_size( - meta: &ixon::metadata::ConstantMeta, - name_index: &ixon::metadata::NameIndex, -) -> usize { - let mut buf = Vec::new(); - meta - .put_with(ixon::metadata::NamePut::Indexed(name_index), &mut buf) - .expect("metadata serialization failed"); - buf.len() -} - -/// Parse a dotted name string into a Name. -/// -/// Simple best-effort parser for `analyze_const_size`'s CLI-like input — -/// splits on `.` and stores each segment as a string component. Does NOT -/// handle Lean's `«…»` escape syntax, so it's unsuitable for names -/// containing special characters; callers that receive Lean-originated -/// names should instead pass the structured `Lean.Name` across FFI and -/// use `decode_name`, as done by `crates/ffi/src/kernel.rs`. -#[cfg(feature = "test-ffi")] -pub fn parse_name(s: &str) -> Name { - let parts: Vec<&str> = s.split('.').collect(); - let mut name = Name::anon(); - for part in parts { - name = Name::str(name, part.to_string()); - } - name -} - -/// Compute the serialized size of a constant. -#[cfg(feature = "test-ffi")] -fn serialized_const_size(constant: &ixon::constant::Constant) -> usize { - let mut buf = Vec::new(); - constant.put(&mut buf); - buf.len() -} diff --git a/crates/ffi/src/lean_ixon.rs b/crates/ffi/src/lean_ixon.rs index b6c20da4f..ba31ae7e1 100644 --- a/crates/ffi/src/lean_ixon.rs +++ b/crates/ffi/src/lean_ixon.rs @@ -3,8 +3,6 @@ //! This module provides build/decode/roundtrip functions for Ixon types used in //! cross-implementation compatibility testing and serialization. -#[cfg(feature = "test-ffi")] -pub mod compare; pub mod constant; pub mod contract; pub mod diff; @@ -15,6 +13,439 @@ pub mod meta; pub mod pack; #[cfg(feature = "test-ffi")] pub mod serialize; -#[cfg(feature = "test-ffi")] -pub mod sharing; pub mod univ; + +use std::time::Instant; + +use ix_common::address::Address; +use ix_common::env::normalize_displayed_name; +use ixon::env::Env as IxonEnv; +use lean_ffi::object::{ + LeanArray, LeanBool, LeanBorrowed, LeanByteArray, LeanExternal, LeanIOResult, + LeanOwned, LeanString, +}; +use rustc_hash::FxHashMap; + +/// FFI: extract the named constants' dependency closure from a serialized +/// env into a standalone `.ixe` — genuine constant bytes, blobs, and +/// reducibility hints (via the anon view), plus every closure constant's +/// Named entry (via the full view) so names still resolve downstream — all +/// without recompiling from source. Each name extracts its covering work +/// item (a mutual-block member pulls the whole block). +#[unsafe(no_mangle)] +pub extern "C" fn rs_env_extract( + env_path: LeanString>, + names: LeanArray>, + out_path: LeanString>, + quiet: LeanBool>, +) -> LeanIOResult { + use ixon::shard::{ + block_of_addr, build_anon_work, build_sub_env_named, work_block_addr, + }; + + let quiet = quiet.to_bool(); + let path = env_path.to_string(); + let out = out_path.to_string(); + let names_vec: Vec = names.map(|obj| obj.as_string().to_string()); + if names_vec.is_empty() { + return LeanIOResult::error_string( + "rs_env_extract: no constant names given", + ); + } + + let bytes = match std::fs::read(&path) { + Ok(bytes) => bytes, + Err(e) => { + return LeanIOResult::error_string(&format!( + "rs_env_extract: failed to read {path}: {e}" + )); + }, + }; + let mut slice: &[u8] = &bytes; + let full = match IxonEnv::get(&mut slice) { + Ok(env) => env, + Err(e) => { + return LeanIOResult::error_string(&format!( + "rs_env_extract: failed to deserialize {path}: {e}" + )); + }, + }; + // Resolve displayed names → addresses through the full env's `named` + // metadata (the anon view discards it). Keys and requests both go + // through `normalize_displayed_name` (Lean `«»`-escaped ↔ Rust bare). + let by_name: FxHashMap = full + .named + .iter() + .map(|e| { + (normalize_displayed_name(&e.key().to_string()), e.value().addr.clone()) + }) + .collect(); + let mut resolved: Vec
= Vec::with_capacity(names_vec.len()); + let mut missing: Vec<&str> = Vec::new(); + for n in &names_vec { + match by_name.get(&normalize_displayed_name(n)) { + Some(a) => resolved.push(a.clone()), + None => missing.push(n), + } + } + if !missing.is_empty() { + return LeanIOResult::error_string(&format!( + "rs_env_extract: no constant(s) named [{}] in {path}", + missing.join(", ") + )); + } + + let mut slice: &[u8] = &bytes; + let anon = match IxonEnv::get_anon(&mut slice) { + Ok(env) => env, + Err(e) => { + return LeanIOResult::error_string(&format!( + "rs_env_extract: failed to deserialize (anon) {path}: {e}" + )); + }, + }; + + // Roots: each name's covering work item's proven targets (standalone → + // itself; a mutual-block member → every sibling, checked atomically). + let work = match build_anon_work(&anon) { + Ok(work) => work, + Err(e) => { + return LeanIOResult::error_string(&format!( + "rs_env_extract: build_anon_work: {e}" + )); + }, + }; + let by_block: FxHashMap = work + .iter() + .enumerate() + .map(|(i, w)| (work_block_addr(&anon, w), i)) + .collect(); + let mut roots: Vec
= Vec::new(); + for addr in &resolved { + let block = block_of_addr(&anon, addr); + match by_block.get(&block) { + Some(&i) => roots.extend(work[i].proven_targets()), + None => { + return LeanIOResult::error_string(&format!( + "rs_env_extract: no work item covers block {}…", + &block.hex()[..16] + )); + }, + } + } + + let sub_bytes = match build_sub_env_named(&anon, &full, &roots) { + Ok(b) => b, + Err(e) => { + return LeanIOResult::error_string(&format!("rs_env_extract: {e}")); + }, + }; + if let Err(e) = std::fs::write(&out, &sub_bytes) { + return LeanIOResult::error_string(&format!( + "rs_env_extract: failed to write {out}: {e}" + )); + } + if !quiet { + eprintln!( + "[rs_env_extract] {} name(s) → {} ({} bytes) from {path}", + names_vec.len(), + out, + sub_bytes.len(), + ); + } + LeanIOResult::ok(LeanOwned::box_usize(0)) +} + +/// FFI: partition a `.ixe` with the STATIC strategy — no out-of-circuit +/// profiling run. An explicit nonzero `num_shards` fixes the count. Otherwise, +/// `ram_gib` seeds it from the static block-shape score after the profile is +/// loaded ([`ixon::shard::static_seed_shards`]). Builds the static block +/// profile ([`static_block_profile`]), then either the byte-balanced min-cut +/// over the walk-edge nets with the predicted-cost rebalance post-pass +/// (`ixon::shard::shard_static`, `layout` = `mincut`) or contiguous +/// ranges of a dependency order (`ixon::shard::shard_static_ordered`, +/// `layout` = `ordered`). Writes a `.ixes` manifest. +#[allow(clippy::cast_precision_loss)] // balance_pct is a small percentage +#[unsafe(no_mangle)] +pub extern "C" fn rs_shard_env_static( + env_path: LeanString>, + requested_shards: usize, + ram_gib: u64, + balance_pct: u64, + layout: LeanString>, + out_path: LeanString>, + exec_ahead: usize, +) -> LeanIOResult { + let path = env_path.to_string(); + let layout = layout.to_string(); + if layout != "mincut" && layout != "ordered" { + return LeanIOResult::error_string(&format!( + "rs_shard_env_static: unknown layout `{layout}` (mincut or ordered)" + )); + } + if requested_shards == 0 && ram_gib == 0 { + return LeanIOResult::error_string( + "rs_shard_env_static: pass a positive shard count or RAM budget", + ); + } + let balance = (balance_pct as f64) / 100.0; + let out = out_path.to_string(); + let out_opt = if out.is_empty() { None } else { Some(out.as_str()) }; + let t0 = Instant::now(); + let env = match IxonEnv::get_anon_mmap(std::path::Path::new(&path)) { + Ok(e) => e, + Err(e) => { + return LeanIOResult::error_string(&format!( + "rs_shard_env_static: failed to load {path}: {e}" + )); + }, + }; + let t_load = t0.elapsed(); + let t1 = Instant::now(); + let profile = ixon::shard::static_block_profile(&env); + eprintln!( + "[rs_shard_static] env load {:.1?}, static graph {:.1?} ({} blocks, {} edges)", + t_load, + t1.elapsed(), + profile.num_blocks(), + profile.num_edges() + ); + let num_shards = if requested_shards > 0 { + requested_shards + } else if cfg!(feature = "cuda") { + // The trace-shard prover: seed against one execution's record share, + // computed as `ix prove --lanes` computes it. + let cells = crate::aiur::protocol::trace_shard_max_cells(); + let budget = usize::try_from(ram_gib << 30).unwrap_or(usize::MAX); + let share = + crate::aiur::protocol::record_share(budget, cells, exec_ahead + 2); + let tenths = + u32::try_from(share.saturating_mul(10) >> 30).unwrap_or(u32::MAX); + let share_gib = f64::from(tenths) / 10.0; + let bytes = ixon::shard::static_env_bytes(&profile); + let n = ixon::shard::gpu_seed_shards(&profile, share_gib); + eprintln!( + "[shard] trace-shard seed (cuda build): bytes={bytes:.4e}; round({} x (bytes/{:.4e}) x ({} GiB / {share_gib:.1} GiB record share: {ram_gib} GiB per worker, {cells} cells, {exec_ahead} executions ahead)) -> {n} shard(s) (heuristic; the record cap names any shard over its share)", + ixon::shard::GPU_SEED_REFERENCE_SHARDS, + ixon::shard::GPU_SEED_REFERENCE_BYTES, + ixon::shard::GPU_SEED_REFERENCE_SHARE_GIB, + ); + n + } else { + let score = ixon::shard::static_env_score(&profile); + let n = ixon::shard::static_seed_shards(&profile, ram_gib); + eprintln!( + "[shard] static seed (cpu prover model): score={score:.3e}; round({} x (score/{:.3e})^{:.2} x ({}/{ram_gib})^{:.2}) -> {n} shard(s) (heuristic; gated execution corrects every boundary)", + ixon::shard::STATIC_SEED_REFERENCE_SHARDS, + ixon::shard::STATIC_SEED_REFERENCE_SCORE, + ixon::shard::STATIC_SEED_SCORE_EXPONENT, + ixon::shard::STATIC_SEED_REFERENCE_RAM_GIB, + ixon::shard::STATIC_SEED_BUDGET_EXPONENT, + ); + n + }; + let report = if layout == "ordered" { + let (depends, primitives) = + ixon::shard::static_block_dependencies(&env, &profile); + ixon::shard::shard_static_ordered( + &profile, + &depends, + &primitives, + num_shards, + out_opt, + ) + } else { + ixon::shard::shard_static(&profile, num_shards, balance, out_opt) + }; + match report { + Ok(report) => { + eprintln!("[rs_shard_static]\n{report}"); + LeanIOResult::ok(LeanOwned::box_usize(0)) + }, + Err(e) => LeanIOResult::error_string(&format!("rs_shard_env_static: {e}")), + } +} + +/// Decode `rs_shard_manifest_refine`'s refinements blob: `count(u32)`, then +/// per refined leaf `id(u32) ‖ nparts(u32)` and per part +/// `nblocks(u32) ‖ 32·nblocks ‖ peak(u64)`. +fn decode_refinements( + bytes: &[u8], +) -> Result, String> { + struct Cur<'a> { + b: &'a [u8], + p: usize, + } + impl<'a> Cur<'a> { + fn take(&mut self, n: usize) -> Result<&'a [u8], String> { + let end = self + .p + .checked_add(n) + .filter(|&end| end <= self.b.len()) + .ok_or("refinements blob: truncated")?; + let s = &self.b[self.p..end]; + self.p = end; + Ok(s) + } + fn u32(&mut self) -> Result { + Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()) as usize) + } + fn u64(&mut self) -> Result { + Ok(u64::from_le_bytes(self.take(8)?.try_into().unwrap())) + } + } + let mut c = Cur { b: bytes, p: 0 }; + let count = c.u32()?; + let mut out = Vec::with_capacity(count); + for _ in 0..count { + let id = u32::try_from(c.u32()?) + .map_err(|e| format!("refinements blob: bad id: {e}"))?; + let nparts = c.u32()?; + let mut parts = Vec::with_capacity(nparts); + let mut part_peaks = Vec::with_capacity(nparts); + for _ in 0..nparts { + let nblocks = c.u32()?; + let raw = c.take(nblocks * 32)?; + parts.push( + raw + .as_chunks::<32>() + .0 + .iter() + .map(|chunk| Address::from_slice(chunk).unwrap()) + .collect(), + ); + part_peaks.push(c.u64()?); + } + out.push(ixon::shard::LeafRefinement { id, parts, part_peaks }); + } + if c.p != bytes.len() { + return Err("refinements blob: trailing bytes".into()); + } + Ok(out) +} + +/// FFI: refine an existing `.ixes` manifest by cutting some of its leaves +/// into parts (`ShardManifest::refine`): every other leaf keeps its block +/// list, record, id and place in the aggregation tree; a refined leaf's +/// place becomes a balanced subtree over its parts, part 0 keeping the +/// leaf's id and later parts taking fresh ids after the last existing one. +/// `measured_blob` is empty or one `u64` analytic prover peak per SOURCE +/// shard in id order (nonzero overrides the peak carried forward for an +/// unsplit leaf). The new partition is validated as an exact, disjoint +/// cover of the env's block profile; the manifest summary goes to stderr. +/// Returns the parts' new ids: `count(u32)`, then per refinement +/// `n(u32) ‖ n × id(u32)`. +#[allow(clippy::cast_possible_truncation)] // ids and counts are u32 by construction +#[unsafe(no_mangle)] +pub extern "C" fn rs_shard_manifest_refine( + env_handle: LeanExternal< + ixvm_codegen::env_handle::EnvHandle, + LeanBorrowed<'_>, + >, + source_path: LeanString>, + refinements_blob: LeanByteArray>, + measured_blob: LeanByteArray>, + out_path: LeanString>, +) -> LeanIOResult { + let source_path = source_path.to_string(); + let out = out_path.to_string(); + let fail = |e: String| { + LeanIOResult::error_string(&format!("rs_shard_manifest_refine: {e}")) + }; + let source = match std::fs::read(&source_path) + .map_err(|e| format!("read {source_path}: {e}")) + .and_then(|bytes| ixon::shard::ShardManifest::from_bytes(&bytes)) + { + Ok(m) => m, + Err(e) => return fail(e), + }; + let refinements = match decode_refinements(refinements_blob.as_bytes()) { + Ok(r) => r, + Err(e) => return fail(e), + }; + let measured: Vec = measured_blob + .as_bytes() + .as_chunks::<8>() + .0 + .iter() + .map(|c| u64::from_le_bytes(*c)) + .collect(); + let profile = ixon::shard::static_block_profile(&env_handle.get().env); + match ixon::shard::shard_manifest_refine( + &profile, + &source, + &refinements, + &measured, + &out, + ) { + Ok((summary, part_ids)) => { + eprintln!("[shard-refine] {out}: {summary}"); + let mut bytes = Vec::new(); + bytes.extend_from_slice(&(part_ids.len() as u32).to_le_bytes()); + for ids in &part_ids { + bytes.extend_from_slice(&(ids.len() as u32).to_le_bytes()); + for id in ids { + bytes.extend_from_slice(&id.to_le_bytes()); + } + } + let ids: LeanOwned = LeanByteArray::from_bytes(&bytes).into(); + LeanIOResult::ok(ids) + }, + Err(e) => fail(e), + } +} + +/// FFI: dump the static block-level reference graph of a `.ixe` as text — +/// `block ` per ingress unit (a Muts block or a +/// standalone constant) and `edge ` per deduped +/// claim-walk edge (`ixon::shard_claim::walk_edges`, mapped to block +/// granularity, self-edges dropped). The graph is the input for offline +/// partitioner prototypes in the sharding campaign: the walk-edge +/// relation is exactly what generates a shard's thin frontier, so a +/// partition's ingress cost is computable from this file alone. Same +/// data as [`static_block_profile`], serialized for offline use. +#[allow(clippy::cast_possible_truncation)] // block ids are u32 by construction +#[unsafe(no_mangle)] +pub extern "C" fn rs_shard_static_graph( + env_path: LeanString>, + out_path: LeanString>, +) -> LeanIOResult { + let path = env_path.to_string(); + let out = out_path.to_string(); + let env = match IxonEnv::get_anon_mmap(std::path::Path::new(&path)) { + Ok(e) => e, + Err(e) => { + return LeanIOResult::error_string(&format!( + "rs_shard_static_graph: failed to load {path}: {e}" + )); + }, + }; + let profile = ixon::shard::static_block_profile(&env); + let mut buf = String::new(); + for b in profile.blocks() { + buf.push_str(&format!( + "block {} {} {}\n", + b.addr.hex(), + b.serialized_size, + b.const_count + )); + } + for c in 0..profile.num_blocks() as u32 { + let ca = profile.block(c).addr.hex(); + for &p in profile.producers(c) { + buf.push_str(&format!("edge {} {}\n", ca, profile.block(p).addr.hex())); + } + } + if let Err(e) = std::fs::write(&out, buf) { + return LeanIOResult::error_string(&format!( + "rs_shard_static_graph: failed to write {out}: {e}" + )); + } + eprintln!( + "[rs_shard_static_graph] {} blocks, {} edges → {}", + profile.num_blocks(), + profile.num_edges(), + out + ); + LeanIOResult::ok(LeanOwned::box_usize(0)) +} diff --git a/crates/ffi/src/lean_ixon/compare.rs b/crates/ffi/src/lean_ixon/compare.rs deleted file mode 100644 index 6890d89d1..000000000 --- a/crates/ffi/src/lean_ixon/compare.rs +++ /dev/null @@ -1,237 +0,0 @@ -//! Cross-implementation compilation comparison FFI. - -use std::collections::HashMap; - -use crate::lean::{LeanIxBlockCompareDetail, LeanIxBlockCompareResult}; -use ix_common::env::Name; -use ix_compile::compile::{ - BlockCache, CompileState, compile_env, compile_expr, -}; -use ix_compile::mutual::MutCtx; -use ixon::serialize::put_expr; -use lean_ffi::object::{LeanBorrowed, LeanByteArray, LeanList, LeanOwned}; - -use crate::lean_env::{ - Cache as LeanCache, GlobalCache, decode_expr, decode_name, -}; - -/// Rust-side compiled environment for block comparison. -pub struct RustBlockEnv { - pub blocks: HashMap, usize)>, // (serialized bytes, sharing count) -} - -/// Compare Lean's compiled expression output with Rust's compilation of the same input. -#[unsafe(no_mangle)] -pub extern "C" fn rs_compare_expr_compilation( - lean_expr_ptr: LeanOwned, - lean_output: LeanByteArray, - univ_ctx_size: u64, -) -> bool { - // Decode Lean.Expr to Rust's representation - let global_cache = GlobalCache::default(); - let mut cache = LeanCache::new(&global_cache); - let lean_expr = decode_expr(lean_expr_ptr.borrow(), &mut cache); - - // Create universe params for de Bruijn indexing (u0, u1, u2, ...) - let univ_params: Vec = (0..univ_ctx_size) - .map(|i| Name::str(Name::anon(), format!("u{}", i))) - .collect(); - let mut_ctx = MutCtx::default(); - - // Create minimal compile state (no environment needed for simple exprs) - let compile_stt = CompileState::new_empty(); - let mut block_cache = BlockCache::default(); - - // Compile with Rust - let rust_expr = match compile_expr( - &lean_expr, - &univ_params, - &mut_ctx, - &mut block_cache, - &compile_stt, - ) { - Ok(expr) => expr, - Err(_) => return false, - }; - - // Serialize Rust's output - let mut rust_bytes = Vec::new(); - put_expr(&rust_expr, &mut rust_bytes); - - // Compare byte-for-byte - let lean_bytes = lean_output.as_bytes(); - rust_bytes == lean_bytes -} - -impl LeanIxBlockCompareResult { - /// Build a BlockCompareResult Lean object. - fn build( - matched: bool, - not_found: bool, - lean_size: u64, - rust_size: u64, - first_diff_offset: u64, - ) -> Self { - if matched { - // match (tag 0, no fields) — scalar - Self::new(LeanOwned::box_usize(0)) - } else if not_found { - // notFound (tag 2, no fields) — scalar - Self::new(LeanOwned::box_usize(2)) - } else { - // mismatch (tag 1) - let ctor = LeanIxBlockCompareResult::alloc(1); - ctor.set_num_64(0, lean_size); - ctor.set_num_64(1, rust_size); - ctor.set_num_64(2, first_diff_offset); - ctor - } - } -} - -impl LeanIxBlockCompareDetail { - /// Build a BlockCompareDetail Lean object. - fn build( - result: LeanIxBlockCompareResult, - lean_sharing_len: u64, - rust_sharing_len: u64, - ) -> Self { - let ctor = Self::alloc(0); - ctor.set_obj(0, result); - ctor.set_num_64(0, lean_sharing_len); - ctor.set_num_64(1, rust_sharing_len); - ctor - } -} - -/// Compare a single block by lowlink name. -/// -/// # Safety -/// -/// `rust_env` must be a valid pointer to a `RustBlockEnv`. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn rs_compare_block_v2( - rust_env: *const RustBlockEnv, - lowlink_name: LeanOwned, - lean_bytes: LeanByteArray, - lean_sharing_len: u64, -) -> LeanIxBlockCompareDetail { - let global_cache = GlobalCache::default(); - let name = decode_name(lowlink_name.borrow(), &global_cache); - - let rust_env = unsafe { &*rust_env }; - let lean_data = lean_bytes.as_bytes(); - - // Look up Rust's compiled block - let (rust_bytes, rust_sharing_len) = match rust_env.blocks.get(&name) { - Some((bytes, sharing_len)) => (bytes, *sharing_len as u64), - None => { - // Block not found in Rust compilation - let result = LeanIxBlockCompareResult::build( - false, - true, - lean_data.len() as u64, - 0, - 0, - ); - return LeanIxBlockCompareDetail::build(result, lean_sharing_len, 0); - }, - }; - - // Compare bytes - if rust_bytes == lean_data { - // Match - let result = LeanIxBlockCompareResult::build( - true, - false, - lean_data.len() as u64, - rust_bytes.len() as u64, - 0, - ); - return LeanIxBlockCompareDetail::build( - result, - lean_sharing_len, - rust_sharing_len, - ); - } - - // Mismatch: find first differing byte - let first_diff_offset = rust_bytes - .iter() - .zip(lean_data.iter()) - .position(|(a, b)| a != b) - .map_or_else( - || { - // One is a prefix of the other - rust_bytes.len().min(lean_data.len()) as u64 - }, - |i| i as u64, - ); - - let result = LeanIxBlockCompareResult::build( - false, - false, - lean_data.len() as u64, - rust_bytes.len() as u64, - first_diff_offset, - ); - LeanIxBlockCompareDetail::build(result, lean_sharing_len, rust_sharing_len) -} - -/// Free a RustBlockEnv pointer. -/// -/// # Safety -/// -/// `ptr` must be a valid pointer returned by `rs_build_compiled_env`, or null. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn rs_free_compiled_env(ptr: *mut RustBlockEnv) { - if !ptr.is_null() { - unsafe { - drop(Box::from_raw(ptr)); - } - } -} - -/// Build a RustBlockEnv from a Lean environment. -#[unsafe(no_mangle)] -pub extern "C" fn rs_build_compiled_env( - env_consts_ptr: LeanList>, -) -> *mut RustBlockEnv { - use crate::lean_env::decode_env; - - // Decode Lean environment - let rust_env = decode_env(env_consts_ptr); - let rust_env = std::sync::Arc::new(rust_env); - - // Compile - let compile_stt = match compile_env(&rust_env) { - Ok(stt) => stt, - Err(_) => { - // Return empty env on error - return Box::into_raw(Box::new(RustBlockEnv { blocks: HashMap::new() })); - }, - }; - - // Collect blocks - let mut blocks = HashMap::new(); - let mut seen_addrs = std::collections::HashSet::new(); - - for entry in compile_stt.name_to_addr.iter() { - let name = entry.key().clone(); - let addr = entry.value().clone(); - - if seen_addrs.contains(&addr) { - continue; - } - seen_addrs.insert(addr.clone()); - - if let Some(constant) = compile_stt.env.get_const(&addr) { - let mut bytes = Vec::new(); - constant.put(&mut bytes); - let sharing_len = constant.sharing.len(); - blocks.insert(name, (bytes, sharing_len)); - } - } - - Box::into_raw(Box::new(RustBlockEnv { blocks })) -} diff --git a/crates/ffi/src/lean_ixon/diff.rs b/crates/ffi/src/lean_ixon/diff.rs index 18d660eac..cb113a5a1 100644 --- a/crates/ffi/src/lean_ixon/diff.rs +++ b/crates/ffi/src/lean_ixon/diff.rs @@ -161,7 +161,7 @@ impl LeanIxonEnvDiff { } /// Inputs at least this large get incremental progress on stderr -/// (`[rs_diff_envs] …` lines, matching the `[rs_compile_env]` idiom). +/// (`[rs_diff_envs] …` lines). /// Small inputs — unit tests, property tests, tiny bundles — stay /// silent. const PROGRESS_MIN_BYTES: usize = 100 * 1024 * 1024; diff --git a/crates/ffi/src/lean_ixon/env.rs b/crates/ffi/src/lean_ixon/env.rs index 3ad6dbb80..9d8e70f4c 100644 --- a/crates/ffi/src/lean_ixon/env.rs +++ b/crates/ffi/src/lean_ixon/env.rs @@ -22,7 +22,7 @@ use lean_ffi::object::{ LeanProd, LeanRef, }; -use crate::builder::LeanBuildCache; +use crate::lean::LeanBuildCache; use crate::lean::LeanIxAddress; // ============================================================================= @@ -43,14 +43,6 @@ impl LeanIxonRawConst { ctor.set_obj(1, LeanIxonConstant::build(&rc.constant)); ctor } - - /// Build from individual parts (used by compile.rs). - pub fn build_from_parts(addr: &Address, constant: &IxonConstant) -> Self { - let ctor = LeanIxonRawConst::alloc(0); - ctor.set_obj(0, LeanIxAddress::build(addr)); - ctor.set_obj(1, LeanIxonConstant::build(constant)); - ctor - } } impl LeanIxonRawConst { @@ -151,24 +143,6 @@ impl LeanIxonRawNamed { ctor.set_obj(4, build_opt_hints(rn.hints)); ctor } - - /// Build from individual parts (used by compile.rs). - pub fn build_from_parts( - cache: &mut LeanBuildCache, - name: &Name, - addr: &Address, - meta: &ConstantMeta, - original: &Option<(Address, ConstantMeta)>, - hints: Option, - ) -> Self { - let ctor = LeanIxonRawNamed::alloc(0); - ctor.set_obj(0, LeanIxName::build(cache, name)); - ctor.set_obj(1, LeanIxAddress::build(addr)); - ctor.set_obj(2, LeanIxonConstantMeta::build(meta)); - ctor.set_obj(3, build_opt_original(original)); - ctor.set_obj(4, build_opt_hints(hints)); - ctor - } } impl LeanIxonRawNamed { @@ -205,7 +179,8 @@ impl LeanIxonRawBlob { ctor } - /// Build from individual parts (used by compile.rs). + /// Build from an address and borrowed bytes, without a [`DecodedRawBlob`] + /// (`build_raw_env_lazy`). pub fn build_from_parts(addr: &Address, bytes: &[u8]) -> Self { let ctor = LeanIxonRawBlob::alloc(0); ctor.set_obj(0, LeanIxAddress::build(addr)); @@ -244,14 +219,6 @@ impl LeanIxonRawComm { ctor.set_obj(1, LeanIxonComm::build(&rc.comm)); ctor } - - /// Build from individual parts (used by compile.rs). - pub fn build_from_parts(addr: &Address, comm: &Comm) -> Self { - let ctor = LeanIxonRawComm::alloc(0); - ctor.set_obj(0, LeanIxAddress::build(addr)); - ctor.set_obj(1, LeanIxonComm::build(comm)); - ctor - } } impl LeanIxonRawComm { @@ -425,45 +392,6 @@ impl LeanIxonRawEnv { } } -/// Populate the trailing constructor slots (5-8: main, assumptions, -/// anonHints, roleBinding) of a `RawEnv` being built field-by-field — -/// the compile FFI assembles its RawEnv manually instead of going -/// through [`LeanIxonRawEnv::build`], and leaving these slots unset is -/// uninitialized memory (segfault on GC). Set-shaped fields are sorted -/// for a deterministic transfer. -pub fn set_raw_env_bundle_fields( - ctor: &LeanIxonRawEnv, - env: &IxonEnv, -) { - let main_obj: LeanOwned = match &env.main { - None => LeanOption::none().into(), - Some(addr) => LeanOption::some(LeanIxAddress::build(addr)).into(), - }; - let mut assumptions: Vec
= env.assumptions.iter().cloned().collect(); - assumptions.sort_unstable(); - let assumptions_arr = LeanArray::alloc(assumptions.len()); - for (i, addr) in assumptions.iter().enumerate() { - assumptions_arr.set(i, LeanIxAddress::build(addr)); - } - let mut hints: Vec<(Address, ReducibilityHints)> = - env.anon_hints.iter().map(|e| (e.key().clone(), *e.value())).collect(); - hints.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - let hints_arr = LeanArray::alloc(hints.len()); - for (i, (addr, hint)) in hints.iter().enumerate() { - hints_arr.set( - i, - LeanProd::new( - LeanIxAddress::build(addr), - LeanIxReducibilityHints::build(hint), - ), - ); - } - ctor.set_obj(5, main_obj); - ctor.set_obj(6, assumptions_arr); - ctor.set_obj(7, hints_arr); - ctor.set_obj(8, build_opt_role_binding(&env.role_binding)); -} - impl LeanIxonRawEnv { /// Decode Ixon.RawEnv from Lean pointer. pub fn decode(&self) -> DecodedRawEnv { @@ -821,3 +749,13 @@ pub extern "C" fn rs_de_env_lazy( Err(e) => LeanExcept::error_string(&format!("rs_de_env_lazy: {e}")), } } + +/// Round-trip Ixon.RawEnv across the FFI boundary. +#[cfg(feature = "test-ffi")] +#[unsafe(no_mangle)] +pub extern "C" fn rs_roundtrip_raw_env( + obj: LeanIxonRawEnv>, +) -> LeanIxonRawEnv { + let decoded = obj.decode(); + LeanIxonRawEnv::build(&decoded) +} diff --git a/crates/ffi/src/lean_ixon/meta.rs b/crates/ffi/src/lean_ixon/meta.rs index 5737ad861..c46857c6b 100644 --- a/crates/ffi/src/lean_ixon/meta.rs +++ b/crates/ffi/src/lean_ixon/meta.rs @@ -9,7 +9,7 @@ use crate::lean::{ LeanIxonUnivPatch, }; use ix_common::address::Address; -use ix_common::env::{BinderInfo, ReducibilityHints}; +use ix_common::env::ReducibilityHints; use ixon::Comm; use ixon::env::{AuxLayout, Named}; use ixon::metadata::{ @@ -323,14 +323,7 @@ impl LeanIxonExprMetaData { .decode(); let ty_child = self.get_num_64(0); let body_child = self.get_num_64(1); - let info_byte = self.get_num_8(0); - let info = match info_byte { - 0 => BinderInfo::Default, - 1 => BinderInfo::Implicit, - 2 => BinderInfo::StrictImplicit, - 3 => BinderInfo::InstImplicit, - _ => panic!("Invalid BinderInfo tag: {}", info_byte), - }; + let info = LeanIxBinderInfo::from_u8(self.get_num_8(0)); ExprMetaData::Binder { name, info, children: [ty_child, body_child] } }, diff --git a/crates/ffi/src/lean_ixon/serialize.rs b/crates/ffi/src/lean_ixon/serialize.rs index ba4d489d2..115b956db 100644 --- a/crates/ffi/src/lean_ixon/serialize.rs +++ b/crates/ffi/src/lean_ixon/serialize.rs @@ -3,8 +3,6 @@ //! Contains FFI functions for comparing Lean and Rust serialization outputs, //! and Env serialization roundtrip testing. -use std::sync::Arc; - use crate::lean::{ LeanIxonConstant, LeanIxonExpr, LeanIxonRawEnv, LeanIxonUniv, }; @@ -26,32 +24,6 @@ pub extern "C" fn rs_eq_univ_serialization( buf == bytes_data } -/// P5 mirror-parity check (canonicity §10.6): does Rust's `canon_univ` -/// of `univ` equal Lean's `Ixon.canonUniv` result `expected` -/// structurally? Byte equality follows from structural equality -/// (`put_univ` is a function of the tree). -#[unsafe(no_mangle)] -pub extern "C" fn rs_canon_univ_matches( - univ_obj: LeanIxonUniv>, - expected_obj: LeanIxonUniv>, -) -> bool { - let univ = Arc::new(univ_obj.decode()); - let expected = expected_obj.decode(); - *ixon::canon_univ::canon_univ(&univ) == expected -} - -/// Twin parity check for the frozen `mk*` rebuild closure -/// (`reduce_univ` vs `Ixon.reduceUniv`). -#[unsafe(no_mangle)] -pub extern "C" fn rs_reduce_univ_matches( - univ_obj: LeanIxonUniv>, - expected_obj: LeanIxonUniv>, -) -> bool { - let univ = Arc::new(univ_obj.decode()); - let expected = expected_obj.decode(); - *ixon::canon_univ::reduce_univ(&univ) == expected -} - /// Check if Lean's Ixon.Expr serialization matches Rust. #[unsafe(no_mangle)] pub extern "C" fn rs_eq_expr_serialization( diff --git a/crates/ffi/src/lean_ixon/sharing.rs b/crates/ffi/src/lean_ixon/sharing.rs deleted file mode 100644 index c4fd04d76..000000000 --- a/crates/ffi/src/lean_ixon/sharing.rs +++ /dev/null @@ -1,154 +0,0 @@ -//! Exact and canonical sharing FFI (test hooks for the Lean/Rust -//! differential, `Tests/Ix/SharingExactFFI.lean`). - -use ixon::sharing_exact::ExactSharingLimits; -use lean_ffi::object::{LeanBorrowed, LeanByteArray, LeanExcept, LeanOwned}; - -/// The limits of every hook: the defaults (the compiler's) in the checked -/// mode (`full_check`), so that the differential also builds and checks -/// every candidate of the tiered construction. The checked mode changes no -/// output and no limit outcome; it fails only on an internal discrepancy. -fn parity_limits() -> ExactSharingLimits { - ExactSharingLimits { full_check: true, ..ExactSharingLimits::default() } -} - -/// FFI: exact-minimum sharing of one serialized Constant (the width-state -/// search, a test oracle; not the compiler path). -/// -/// Lean signature: -/// `@[extern "rs_exact_sharing_normalize"] -/// opaque exactSharingNormalize : @& ByteArray → Except String ByteArray` -/// -/// Decodes exactly one Constant (trailing bytes are rejected), expands and -/// validates its sharing table under the backward-reference rule, and -/// returns the serialized exact-minimum encoding computed with the default -/// limits ([`parity_limits`]). Error strings start with `decode:`, -/// `malformed sharing:`, `format bound:`, `resource exhausted:` or -/// `internal error:`; no error carries a partial encoding. -#[unsafe(no_mangle)] -extern "C" fn rs_exact_sharing_normalize( - bytes_obj: LeanByteArray>, -) -> LeanExcept { - use ixon::sharing_exact::normalize_constant_bytes; - match normalize_constant_bytes(bytes_obj.as_bytes(), &parity_limits()) { - Ok(bytes) => LeanExcept::ok(LeanByteArray::from_bytes(&bytes)), - Err(err) => LeanExcept::error_string(&err.to_string()), - } -} - -/// FFI: uniform-width exact sharing of one serialized Constant (phase 1 of -/// the canonical construction, at a given width). -/// -/// Lean signature: -/// `@[extern "rs_uniform_sharing_normalize"] -/// opaque uniformSharingNormalize : UInt64 → @& ByteArray → Except String ByteArray` -/// -/// Every Share is priced `w` bytes (the uniform-width model of -/// `Ix.Sharing.Exact.Uniform`); the output uses real table indices. Uses the -/// default limits ([`parity_limits`]); `w = 0` is a `format bound:` error. -#[unsafe(no_mangle)] -extern "C" fn rs_uniform_sharing_normalize( - w: u64, - bytes_obj: LeanByteArray>, -) -> LeanExcept { - use ixon::sharing_exact::normalize_constant_bytes_uniform; - match normalize_constant_bytes_uniform( - w, - bytes_obj.as_bytes(), - &parity_limits(), - ) { - Ok(bytes) => LeanExcept::ok(LeanByteArray::from_bytes(&bytes)), - Err(err) => LeanExcept::error_string(&err.to_string()), - } -} - -/// FFI: tiered canonical sharing of one serialized Constant. -/// -/// Lean signature: -/// `@[extern "rs_tiered_sharing_normalize"] -/// opaque tieredSharingNormalize : @& ByteArray → Except String ByteArray` -/// -/// Expands the Constant's table and re-shares it with the canonical tiered -/// construction under the TagN layout and the default limits in the checked -/// mode ([`parity_limits`]); the output is serialized with the wire (TagN) -/// Share code. -#[unsafe(no_mangle)] -extern "C" fn rs_tiered_sharing_normalize( - bytes_obj: LeanByteArray>, -) -> LeanExcept { - use ixon::sharing_exact::{ShareLayout, normalize_constant_bytes_tiered}; - match normalize_constant_bytes_tiered( - ShareLayout::TagN, - bytes_obj.as_bytes(), - &parity_limits(), - ) { - Ok(bytes) => LeanExcept::ok(LeanByteArray::from_bytes(&bytes)), - Err(err) => LeanExcept::error_string(&err.to_string()), - } -} - -/// FFI: build one block Constant through the compiler's sharing functions. -/// -/// Lean signature: -/// `@[extern "rs_compiler_sharing_build"] -/// opaque compilerSharingBuild : @& ByteArray → Except String ByteArray` -/// -/// Decodes exactly one Constant whose roots carry no sharing table, and -/// rebuilds it with `ix_compile::compile::apply_sharing_to_*_with_limits` -/// (the explicit-limits forms of the functions every compile and -/// kernel-egress path calls) under the default -/// limits in the checked mode ([`parity_limits`]; the compiler itself runs -/// the default mode, which writes the same bytes). Projections are returned -/// unchanged. Errors are the compile error's text, prefixed `decode:` for -/// input errors. -#[unsafe(no_mangle)] -extern "C" fn rs_compiler_sharing_build( - bytes_obj: LeanByteArray>, -) -> LeanExcept { - use ix_compile::compile::{ - apply_sharing_to_axiom_with_limits, - apply_sharing_to_definition_with_limits, - apply_sharing_to_mutual_block_with_limits, - apply_sharing_to_quotient_with_limits, - apply_sharing_to_recursor_with_limits, - }; - use ixon::constant::{Constant, ConstantInfo}; - let limits = parity_limits(); - let mut input = bytes_obj.as_bytes(); - let c = match Constant::get(&mut input) { - Ok(c) => c, - Err(e) => return LeanExcept::error_string(&format!("decode: {e}")), - }; - if !input.is_empty() || !c.sharing.is_empty() { - return LeanExcept::error_string( - "decode: expected one Constant with an empty sharing table", - ); - } - let (refs, univs) = (c.refs.clone(), c.univs.clone()); - let built = match c.info.clone() { - ConstantInfo::Defn(d) => { - apply_sharing_to_definition_with_limits(&limits, d, refs, univs) - }, - ConstantInfo::Recr(r) => { - apply_sharing_to_recursor_with_limits(&limits, r, refs, univs) - }, - ConstantInfo::Axio(a) => { - apply_sharing_to_axiom_with_limits(&limits, a, refs, univs) - }, - ConstantInfo::Quot(q) => { - apply_sharing_to_quotient_with_limits(&limits, q, refs, univs) - }, - ConstantInfo::Muts(ms) => { - apply_sharing_to_mutual_block_with_limits(&limits, ms, refs, univs) - }, - _ => Ok(c), - }; - match built { - Ok(out) => { - let mut buf = Vec::new(); - out.put(&mut buf); - LeanExcept::ok(LeanByteArray::from_bytes(&buf)) - }, - Err(e) => LeanExcept::error_string(&e.to_string()), - } -} diff --git a/crates/ffi/src/lib.rs b/crates/ffi/src/lib.rs index d0e9fe096..8b970dc59 100644 --- a/crates/ffi/src/lib.rs +++ b/crates/ffi/src/lib.rs @@ -14,7 +14,6 @@ pub mod aiur; ))] pub mod iroh; pub mod keccak; -pub mod lean_env; #[cfg(all( feature = "net", not(all(target_os = "macos", target_arch = "aarch64")) @@ -23,29 +22,40 @@ pub mod lean_iroh; pub mod texray; pub mod unsigned; -pub mod builder; pub mod catalog; -pub mod compile; mod compression; -pub mod graph; -pub mod ix; -pub mod kernel; pub mod lean_ixon; pub mod primitives; #[cfg(feature = "test-ffi")] pub mod refcount; pub mod lean; -pub mod lean_build; mod sha256; #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; +use lean_ffi::object::{LeanIOResult, LeanOwned}; + +unsafe extern "C" { + fn lean_internal_set_thread_stack_size(size: usize); +} + +/// Set the stack size (in bytes) for subsequently spawned Lean runtime threads, +/// adapting the bare `void lean_internal_set_thread_stack_size(size_t)` runtime +/// call to the `BaseIO Unit` ABI. +#[unsafe(no_mangle)] +pub extern "C" fn rs_lean_set_thread_stack_size( + size: usize, +) -> LeanIOResult { + unsafe { + lean_internal_set_thread_stack_size(size); + } + LeanIOResult::ok(LeanOwned::box_usize(0)) +} + #[cfg(feature = "test-ffi")] -use lean_ffi::object::{ - LeanArray, LeanBorrowed, LeanByteArray, LeanOwned, LeanRef, -}; +use lean_ffi::object::{LeanArray, LeanBorrowed, LeanByteArray, LeanRef}; #[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] diff --git a/crates/ffi/src/primitives.rs b/crates/ffi/src/primitives.rs index 894081890..273bc7667 100644 --- a/crates/ffi/src/primitives.rs +++ b/crates/ffi/src/primitives.rs @@ -3,11 +3,26 @@ //! Roundtrip FFI functions for AssocList, DHashMap.Raw, and HashMap //! (ix-specific types not covered by the lean-ffi test suite). +use ix_common::prim_addrs::PrimAddrs; use lean_ffi::nat::Nat; -use lean_ffi::object::LeanNat; #[cfg(feature = "test-ffi")] -use lean_ffi::object::{LeanArray, LeanCtor, LeanOwned}; -use lean_ffi::object::{LeanBorrowed, LeanRef}; +use lean_ffi::object::LeanCtor; +use lean_ffi::object::LeanNat; +use lean_ffi::object::{ + LeanArray, LeanBorrowed, LeanIOResult, LeanOwned, LeanProd, LeanRef, + LeanString, +}; + +/// Expose `PrimAddrs::lean_parity_table()` to Lean as `IO (Array (String × String))`. +#[unsafe(no_mangle)] +pub extern "C" fn rs_prim_addrs_canonical() -> LeanIOResult { + let table = PrimAddrs::lean_parity_table(); + let arr = LeanArray::alloc(table.len()); + for (i, (name, hex)) in table.iter().enumerate() { + arr.set(i, LeanProd::new(LeanString::new(name), LeanString::new(hex))); + } + LeanIOResult::ok(arr) +} // ============================================================================= // AssocList / HashMap roundtrip FFI functions diff --git a/crates/ffi/src/refcount.rs b/crates/ffi/src/refcount.rs index a68345991..30d80ee89 100644 --- a/crates/ffi/src/refcount.rs +++ b/crates/ffi/src/refcount.rs @@ -1,17 +1,6 @@ //! Reference counting and ownership tests for the typed lean-ffi API. //! -//! Gated behind the `test-ffi` Cargo feature. These FFI functions exercise -//! ownership semantics that would catch double-free, use-after-free, and -//! refcount leaks: -//! -//! - Borrowed params (`@&`): must NOT lean_dec on drop -//! - to_owned_ref: must lean_inc correctly -//! - Clone on LeanOwned: must lean_inc correctly -//! - Owned drop: must lean_dec correctly -//! - Nested field access from borrowed refs -//! - Cache deduplication via Clone -//! - Repeated alloc/dealloc cycles -//! - Multi-threaded access via LeanShared +//! Gated behind the `test-ffi` Cargo feature. use std::thread; @@ -19,15 +8,10 @@ use lean_ffi::LeanShared; use lean_ffi::nat::Nat; use lean_ffi::object::LeanNat; use lean_ffi::object::{ - LeanArray, LeanBorrowed, LeanList, LeanOwned, LeanRef, LeanString, + LeanArray, LeanBorrowed, LeanOwned, LeanRef, LeanString, }; -use crate::builder::LeanBuildCache; -use crate::lean::{LeanIxExpr, LeanIxLevel, LeanIxName}; - -// ============================================================================= -// Borrow-to-owned: take @&, return owned copy via to_owned_ref -// ============================================================================= +use crate::lean::{LeanBuildCache, LeanIxName}; /// Take a borrowed Ix.Name, convert to owned via to_owned_ref, return it. #[unsafe(no_mangle)] @@ -37,28 +21,7 @@ pub extern "C" fn rs_refcount_borrow_to_owned_name( LeanIxName::new(name.inner().to_owned_ref()) } -/// Same for Ix.Level. -#[unsafe(no_mangle)] -pub extern "C" fn rs_refcount_borrow_to_owned_level( - level: LeanIxLevel>, -) -> LeanIxLevel { - LeanIxLevel::new(level.inner().to_owned_ref()) -} - -/// Same for Ix.Expr. -#[unsafe(no_mangle)] -pub extern "C" fn rs_refcount_borrow_to_owned_expr( - expr: LeanIxExpr>, -) -> LeanIxExpr { - LeanIxExpr::new(expr.inner().to_owned_ref()) -} - -// ============================================================================= -// Multiple borrows from same parent -// ============================================================================= - /// Read all fields of an Ix.Name.str twice, return concatenated strings. -/// Tests that multiple .get() calls from the same ctor don't corrupt. #[unsafe(no_mangle)] pub extern "C" fn rs_refcount_multi_borrow_name( name: LeanIxName>, @@ -72,36 +35,7 @@ pub extern "C" fn rs_refcount_multi_borrow_name( LeanString::new(&result) } -// ============================================================================= -// Deep borrow traversal -// ============================================================================= - -/// Count nodes in an Ix.Expr tree via borrowed traversal. Returns count as Nat. -#[unsafe(no_mangle)] -pub extern "C" fn rs_refcount_deep_borrow_expr( - expr: LeanIxExpr>, -) -> LeanOwned { - fn count_nodes(expr: &LeanIxExpr) -> u64 { - let ctor = expr.as_ctor(); - match ctor.tag() { - 5 => { - // app: fn, arg, hash - let fn_expr = LeanIxExpr(ctor.get(0)); - let arg_expr = LeanIxExpr(ctor.get(1)); - 1 + count_nodes(&fn_expr) + count_nodes(&arg_expr) - }, - _ => 1, - } - } - LeanNat::from_nat(&Nat::from(count_nodes(&expr))).into() -} - -// ============================================================================= -// Owned drop: take owned, extract data, verify drop doesn't crash -// ============================================================================= - /// Take an owned Array of Names, decode them all, return count. -/// The owned array is dropped at the end → lean_dec_ref on each element. #[unsafe(no_mangle)] pub extern "C" fn rs_refcount_owned_array_drop( arr: LeanArray, @@ -113,23 +47,6 @@ pub extern "C" fn rs_refcount_owned_array_drop( LeanNat::from_nat(&Nat::from(count as u64)).into() } -/// Take an owned List of Exprs, consume and count. -#[unsafe(no_mangle)] -pub extern "C" fn rs_refcount_owned_list_drop( - list: LeanList, -) -> LeanOwned { - let mut count: u64 = 0; - for elem in list.iter() { - let _ = LeanIxExpr(elem).decode(); - count += 1; - } - LeanNat::from_nat(&Nat::from(count)).into() -} - -// ============================================================================= -// Clone then drop: clone an owned value, decode both independently -// ============================================================================= - /// Take a borrowed Name, make two owned clones, decode both, /// verify they produce the same result. Returns 1 if equal, 0 otherwise. #[unsafe(no_mangle)] @@ -140,15 +57,10 @@ pub extern "C" fn rs_refcount_clone_and_compare( let owned2 = owned1.clone(); let decoded1 = owned1.decode(); let decoded2 = owned2.decode(); - // owned1 and owned2 both dropped → two lean_dec calls LeanNat::from_nat(&Nat::from(if decoded1 == decoded2 { 1u64 } else { 0u64 })) .into() } -// ============================================================================= -// Roundtrip loop: stresses alloc/dealloc cycles -// ============================================================================= - /// Roundtrip an Ix.Name N times. Each iteration: decode → build → drop old. #[unsafe(no_mangle)] pub extern "C" fn rs_refcount_roundtrip_loop( @@ -160,38 +72,12 @@ pub extern "C" fn rs_refcount_roundtrip_loop( let mut cache = LeanBuildCache::new(); let lean_name = LeanIxName::build(&mut cache, &rust_name); rust_name = lean_name.decode(); - // lean_name dropped here → lean_dec_ref } let mut cache = LeanBuildCache::new(); LeanIxName::build(&mut cache, &rust_name) } -// ============================================================================= -// Nested collection traversal with borrows -// ============================================================================= - -/// Take a borrowed Array of (Name × Level) pairs, decode all, return count. -#[unsafe(no_mangle)] -pub extern "C" fn rs_refcount_nested_borrow( - arr: LeanArray>, -) -> LeanOwned { - let mut count: u64 = 0; - for elem in arr.iter() { - let pair = elem.as_ctor(); - let _name = LeanIxName(pair.get(0)).decode(); - let _level = LeanIxLevel(pair.get(1)).decode(); - count += 1; - } - LeanNat::from_nat(&Nat::from(count)).into() -} - -// ============================================================================= -// Cache deduplication: build same subterm multiple times -// ============================================================================= - /// Build an array of N copies of the same Name from cache. -/// The LeanBuildCache deduplicates via Clone (lean_inc). -/// Each element's lean_dec on drop must work correctly. #[unsafe(no_mangle)] pub extern "C" fn rs_refcount_cache_dedup( name: LeanIxName>, @@ -207,10 +93,6 @@ pub extern "C" fn rs_refcount_cache_dedup( arr } -// ============================================================================= -// Persistent object handling -// ============================================================================= - /// Check if a borrowed Name is persistent (m_rc == 0). #[unsafe(no_mangle)] pub extern "C" fn rs_refcount_is_persistent( @@ -229,10 +111,6 @@ pub extern "C" fn rs_refcount_persistent_roundtrip( LeanIxName::build(&mut cache, &decoded) } -// ============================================================================= -// String lifecycle -// ============================================================================= - /// Build a new string from borrowed input. The output is independent. #[unsafe(no_mangle)] pub extern "C" fn rs_refcount_string_lifecycle( @@ -243,12 +121,7 @@ pub extern "C" fn rs_refcount_string_lifecycle( LeanString::new(&reversed) } -// ============================================================================= -// Build and immediately discard: stress alloc then drop -// ============================================================================= - /// Build N Lean Names from a borrowed input, immediately drop each. -/// Returns the input unchanged. Stresses alloc/dealloc without leaking. #[unsafe(no_mangle)] pub extern "C" fn rs_refcount_alloc_drop_loop( name: LeanIxName>, @@ -258,40 +131,12 @@ pub extern "C" fn rs_refcount_alloc_drop_loop( for _ in 0..n { let mut cache = LeanBuildCache::new(); let _lean_name = LeanIxName::build(&mut cache, &decoded); - // _lean_name dropped immediately } let mut cache = LeanBuildCache::new(); LeanIxName::build(&mut cache, &decoded) } -// ============================================================================= -// Array build and element extraction -// ============================================================================= - -/// Build an array of Levels, extract each via borrowed access, rebuild. -/// Tests that borrowed array elements survive while array is alive. -#[unsafe(no_mangle)] -pub extern "C" fn rs_refcount_array_element_borrow( - arr: LeanArray>, -) -> LeanArray { - let len = arr.len(); - let out = LeanArray::alloc(len); - for i in 0..len { - let borrowed_elem = arr.get(i); - let level = LeanIxLevel(borrowed_elem).decode(); - let mut cache = LeanBuildCache::new(); - out.set(i, LeanIxLevel::build(&mut cache, &level)); - } - out -} - -// ============================================================================= -// Multi-threaded tests using LeanShared on ix domain types -// ============================================================================= - /// Mark an array of Ix.Names as MT, decode in parallel from N threads. -/// Each thread decodes all names and returns the count. -/// Tests LeanShared + lean_mark_mt on complex Ix object graphs. #[unsafe(no_mangle)] pub extern "C" fn rs_mt_parallel_decode_names( arr: LeanArray>, @@ -318,49 +163,8 @@ pub extern "C" fn rs_mt_parallel_decode_names( LeanNat::from_nat(&Nat::from(total)).into() } -/// Mark an array of Ix.Exprs as MT, decode in parallel from N threads. -/// Each thread decodes all exprs and counts nodes. -#[unsafe(no_mangle)] -pub extern "C" fn rs_mt_parallel_decode_exprs( - arr: LeanArray>, - n_threads: usize, -) -> LeanOwned { - fn count_expr_nodes(expr: &LeanIxExpr) -> u64 { - let ctor = expr.as_ctor(); - match ctor.tag() { - 5 => { - // app - 1 + count_expr_nodes(&LeanIxExpr(ctor.get(0))) - + count_expr_nodes(&LeanIxExpr(ctor.get(1))) - }, - _ => 1, - } - } - - let shared = LeanShared::new(arr.inner().to_owned_ref()); - - let handles: Vec<_> = (0..n_threads) - .map(|_| { - let shared_clone = shared.clone(); - thread::spawn(move || { - let borrowed_arr = shared_clone.borrow().as_array(); - let mut total: u64 = 0; - for elem in borrowed_arr.iter() { - total += count_expr_nodes(&LeanIxExpr(elem)); - } - total - }) - }) - .collect(); - - let total: u64 = handles.into_iter().map(|h| h.join().unwrap()).sum(); - LeanNat::from_nat(&Nat::from(total)).into() -} - /// Parallel roundtrip: mark array of Names as MT, each thread decodes -/// all names and rebuilds them. Returns an array of rebuilt names from -/// one thread. Tests that building new Lean objects while reading -/// MT-marked objects works correctly. +/// all names and rebuilds them. #[unsafe(no_mangle)] pub extern "C" fn rs_mt_parallel_roundtrip_names( arr: LeanArray>, @@ -385,17 +189,14 @@ pub extern "C" fn rs_mt_parallel_roundtrip_names( }) .collect(); - // Collect results from all threads, use the first one to rebuild let all_results: Vec> = handles.into_iter().map(|h| h.join().unwrap()).collect(); - // Verify all threads decoded the same values let first = &all_results[0]; for result in &all_results[1..] { assert_eq!(first, result, "MT decode inconsistency"); } - // Rebuild from the first thread's results let mut cache = LeanBuildCache::new(); let out = LeanArray::alloc(first.len()); for (i, name) in first.iter().enumerate() { @@ -403,32 +204,3 @@ pub extern "C" fn rs_mt_parallel_roundtrip_names( } out } - -/// Stress test: clone/drop LeanShared rapidly from many threads -/// on a complex Ix.Expr graph. -#[unsafe(no_mangle)] -pub extern "C" fn rs_mt_shared_expr_stress( - expr: LeanIxExpr>, - n_threads: usize, - clones_per_thread: usize, -) -> LeanOwned { - let shared = LeanShared::new(expr.inner().to_owned_ref()); - - let handles: Vec<_> = (0..n_threads) - .map(|_| { - let shared_clone = shared.clone(); - thread::spawn(move || { - for _ in 0..clones_per_thread { - let tmp = shared_clone.clone(); - // Borrow and read tag to ensure the object is valid - let _ = tmp.borrow().as_ctor().tag(); - // tmp dropped → atomic lean_dec - } - 1u64 - }) - }) - .collect(); - - let total: u64 = handles.into_iter().map(|h| h.join().unwrap()).sum(); - LeanNat::from_nat(&Nat::from(total)).into() -} diff --git a/crates/ixon/Cargo.toml b/crates/ixon/Cargo.toml index f161eabaf..3ba14160c 100644 --- a/crates/ixon/Cargo.toml +++ b/crates/ixon/Cargo.toml @@ -4,25 +4,15 @@ version.workspace = true edition.workspace = true license.workspace = true -[features] -# Old unbounded solvers are available only to unit tests/test-ffi. -sharing-reference = [] -# Scoped phase timers of the canonical sharing construction -# (`sharing_exact::profile_report`); diagnostic only, off by default. -sharing-profile = [] - [dependencies] bignat = { workspace = true } blake3 = { workspace = true } dashmap = { workspace = true } -indexmap = { workspace = true } ix-common = { workspace = true } memmap2 = { workspace = true } nom = { workspace = true } num-bigint = { workspace = true } rustc-hash = { workspace = true } -sha2 = { workspace = true } -tiny-keccak = { workspace = true } [target.'cfg(not(target_arch = "riscv64"))'.dependencies] dashmap = { workspace = true, features = ["rayon"] } @@ -30,8 +20,6 @@ rayon = { workspace = true } [dev-dependencies] ix-common = { workspace = true, features = ["quickcheck"] } -# The process allocator of the `ix` binary, for the `sharing_corpus` runner. -mimalloc = { workspace = true } quickcheck = { workspace = true } quickcheck_macros = { workspace = true } diff --git a/crates/ixon/examples/sharing_corpus.rs b/crates/ixon/examples/sharing_corpus.rs deleted file mode 100644 index 0f4ecfcf8..000000000 --- a/crates/ixon/examples/sharing_corpus.rs +++ /dev/null @@ -1,685 +0,0 @@ -//! Corpus runner for the canonical sharing construction. -//! -//! Runs the tiered canonical construction (`sharing_exact`, TagN layout) on -//! every constant of an `.ixe` file (or a deterministic subset), in parallel, -//! and prints a report: certified count, failures with names and candidate -//! counts, serialized and TagN-priced byte totals against the stored and -//! unshared encodings, the per-constant delta distribution, phase -//! statistics, the slowest constants, wall time and peak RSS. The -//! `--select-out` address lists feed the Lean/Rust corpus differential -//! (`IX_SHARING_CORPUS_SELECT` of the `exact-sharing-ffi` suite). -//! -//! ```text -//! cargo run --release -p ixon --example sharing_corpus -- -//! [--threads N] worker threads (default: all cores) -//! [--limit N] only the first N constants (address order) -//! [--csv PATH] per-constant CSV (with the blake3 of the -//! output and, per phase-1 width, the final -//! bytes, model bytes, states and work) -//! [--select-out PATH] write a selection: every --select-stride-th -//! constant plus those with more than -//! --select-min-cand R1/R2 candidates -//! [--select-stride N] (default 50) -//! [--select-min-cand N] (default 2000) -//! [--only HEX] only constants whose address starts with HEX -//! (repeatable) -//! [--par-widths N] [--par-components N] [--par-materialize N] -//! thread budgets inside one constant -//! [--serial-constants] one constant at a time (per-constant latency) -//! [--check-sequential] also run the sequential reference (under the -//! same limits) and compare -//! [--max-states N] override the max_states limit -//! [--sharing-limits SPEC] override limits (`ExactSharingLimits:: -//! with_overrides`, the grammar of `ix compile -//! --sharing-limits`), after --max-states -//! [--full-check] the checked mode (`ExactSharingLimits:: -//! full_check`): build and check every candidate -//! and cross-check every shortcut; the output -//! and every column but `ms` are those of the -//! default mode, and a discrepancy is a failure -//! with an `internal error` status -//! [--canonical-tsv PATH] fixed K16 production receipts in address order -//! [--canonical-export DIR] exact input/successful output .ixn files -//! requires --canonical-tsv; never overwrites -//! ``` -//! -//! Candidate counts are reported two ways: `cand`, the R1/R2 search -//! candidates (`candidate_terms`: at least two expanded occurrences and an -//! unshared encoding longer than one byte), and `k`, the tiered count -//! (compact in-degree >= 2 and unshared length >= 2). - -#![allow(clippy::cast_precision_loss)] -#![allow(clippy::cast_possible_truncation)] - -use std::collections::BTreeMap; -use std::io::Write; -use std::sync::Arc; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::time::Instant; - -use ix_common::address::Address; -use ixon::Env; -use ixon::constant::{Constant, ConstantInfo}; -use ixon::sharing_exact::{ - ExactSharingLimits, Parallelism, ShareLayout, SharingDag, SharingError, - TieredSharingResult, candidate_terms, constant_fixed_len, constant_len, - layout_bytes, normalize_constant_sharing_tiered, - normalize_constant_sharing_tiered_par, -}; -use rayon::prelude::*; - -#[path = "sharing_corpus/canonical.rs"] -mod canonical; - -// The allocator of the `ix` binary (`crates/ffi`), so per-constant times -// match the compiler's. -#[global_allocator] -static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; - -/// The Share layout of the construction: TagN, the wire Share code. -const LAYOUT: ShareLayout = ShareLayout::TagN; - -struct Args { - path: String, - threads: Option, - limit: Option, - csv: Option, - select_out: Option, - select_stride: usize, - select_min_cand: usize, - parallel: Parallelism, - check_sequential: bool, - only: Vec, - serial_constants: bool, - max_states: Option, - sharing_limits: Option, - full_check: bool, - canonical_tsv: Option, - canonical_export: Option, -} - -fn parse_args() -> Result { - let mut it = std::env::args().skip(1); - let mut a = Args { - path: String::new(), - threads: None, - limit: None, - csv: None, - select_out: None, - select_stride: 50, - select_min_cand: 2000, - parallel: Parallelism::SEQUENTIAL, - check_sequential: false, - only: Vec::new(), - serial_constants: false, - max_states: None, - sharing_limits: None, - full_check: false, - canonical_tsv: None, - canonical_export: None, - }; - let num = |v: Option| -> Result { - v.ok_or("missing value")?.parse::().map_err(|e| e.to_string()) - }; - while let Some(arg) = it.next() { - match arg.as_str() { - "--threads" => a.threads = Some(num(it.next())?), - "--limit" => a.limit = Some(num(it.next())?), - "--csv" => a.csv = it.next(), - "--select-out" => a.select_out = it.next(), - "--select-stride" => a.select_stride = num(it.next())?, - "--select-min-cand" => a.select_min_cand = num(it.next())?, - "--par-widths" => a.parallel.widths = num(it.next())?, - "--par-components" => a.parallel.components = num(it.next())?, - "--par-materialize" => a.parallel.materialize = num(it.next())?, - "--check-sequential" => a.check_sequential = true, - "--only" => a.only.push(it.next().ok_or("missing value")?), - "--serial-constants" => a.serial_constants = true, - "--max-states" => a.max_states = Some(num(it.next())? as u64), - "--sharing-limits" => { - a.sharing_limits = Some(it.next().ok_or("missing value")?); - }, - "--full-check" => a.full_check = true, - "--canonical-tsv" => { - a.canonical_tsv = Some(it.next().ok_or("missing value")?) - }, - "--canonical-export" => { - a.canonical_export = Some(it.next().ok_or("missing value")?) - }, - p if !p.starts_with("--") && a.path.is_empty() => a.path = p.to_string(), - other => return Err(format!("unknown argument {other}")), - } - } - if a.path.is_empty() { - return Err("usage: sharing_corpus [options]".into()); - } - if a.canonical_export.is_some() && a.canonical_tsv.is_none() { - return Err("--canonical-export requires --canonical-tsv".into()); - } - Ok(a) -} - -/// One processed constant. -#[derive(Default, Clone)] -struct Row { - idx: usize, - addr: String, - status: String, - kind: &'static str, - raw: u64, - n: u64, - cand: u64, - k: u64, - /// Serialized bytes of the output (the wire Share code). - real: u64, - /// The same output priced by the TagN layout (`layout_bytes`); the - /// construction checks that it equals `real`. - tagn: u64, - unshared: Option, - w: u64, - uncertain: u64, - max_comp: u64, - comps: u64, - slot_states: u64, - /// `--check-sequential`: how the sequential reference compares. - seq_check: &'static str, - uniform_states: u64, - /// blake3 of the serialized output Constant (empty on failure). - out_hash: String, - /// Per phase-1 width 1, 2, 3: `(final layout bytes, phase-1 model bytes, - /// states_created, work)` of that candidate's run. - per_width: Vec<(u64, u64, u64, u64)>, - /// Candidates whose phase 3 ran per prefix. - per_prefix: u64, - ms: f64, -} - -fn kind_of(c: &Constant) -> &'static str { - match &c.info { - ConstantInfo::Defn(_) => "defn", - ConstantInfo::Recr(_) => "recr", - ConstantInfo::Axio(_) => "axio", - ConstantInfo::Quot(_) => "quot", - ConstantInfo::CPrj(_) => "cprj", - ConstantInfo::RPrj(_) => "rprj", - ConstantInfo::IPrj(_) => "iprj", - ConstantInfo::DPrj(_) => "dprj", - ConstantInfo::Muts(_) => "muts", - } -} - -fn projection_block(c: &Constant) -> Option
{ - match &c.info { - ConstantInfo::CPrj(p) => Some(p.block.clone()), - ConstantInfo::RPrj(p) => Some(p.block.clone()), - ConstantInfo::IPrj(p) => Some(p.block.clone()), - ConstantInfo::DPrj(p) => Some(p.block.clone()), - _ => None, - } -} - -fn process( - idx: usize, - addr: &Address, - mut bytes: &[u8], - limits: &ExactSharingLimits, - par: Parallelism, - check_sequential: bool, -) -> Row { - let mut row = - Row { idx, addr: addr.hex(), raw: bytes.len() as u64, ..Row::default() }; - let c = match Constant::get(&mut bytes) { - Ok(c) => c, - Err(e) => { - row.status = format!("decode: {e}"); - return row; - }, - }; - row.kind = kind_of(&c); - if let Ok(dag) = - SharingDag::from_constant(&c, &ExactSharingLimits::unbounded()) - { - row.n = dag.len() as u64; - row.cand = candidate_terms(&dag).len() as u64; - } - let start = Instant::now(); - let r = normalize_constant_sharing_tiered_par(LAYOUT, &c, limits, par); - row.ms = start.elapsed().as_secs_f64() * 1e3; - if check_sequential { - // The sequential reference under the same limits: a difference is a - // difference of the parallel path, not of the limits. - let s = normalize_constant_sharing_tiered(LAYOUT, &c, limits); - row.seq_check = compare_sequential(&r, &s); - } - match r { - Ok((out, res)) => { - row.status = "ok".into(); - let real = constant_len(&out).unwrap_or(u64::MAX); - let fixed = constant_fixed_len(&c).unwrap_or(0); - row.real = real; - row.tagn = fixed - + layout_bytes(LAYOUT, &res.sharing, &res.roots) - .unwrap_or(u64::MAX - fixed); - row.unshared = res.unshared_len.map(|u| fixed + u); - row.k = res.stats.candidate_count; - row.w = res.stats.w; - row.uncertain = res.phase1.uncertain.len() as u64; - row.comps = res.phase1.components.len() as u64; - row.max_comp = - res.phase1.components.iter().map(Vec::len).max().unwrap_or(0) as u64; - row.slot_states = res.stats.slot_states; - row.uniform_states = res.phase1.states_visited; - let mut bytes = Vec::new(); - out.put(&mut bytes); - row.out_hash = blake3::hash(&bytes).to_hex().to_string(); - row.per_prefix = res.stats.per_prefix_candidates; - row.per_width = res - .stats - .candidate_lengths - .iter() - .zip(&res.stats.candidate_meters) - .map(|(&(_, len), &(_, model, states, work))| { - (len, model, states, work) - }) - .collect(); - }, - Err(SharingError::ResourceExhausted(e)) => { - row.status = format!("resource:{:?}", e.resource); - }, - Err(e) => row.status = format!("error: {e}"), - } - row -} - -type Normalized = Result<(Constant, TieredSharingResult), SharingError>; - -/// How a run compares with the sequential reference (`--check-sequential`). -fn compare_sequential(p: &Normalized, s: &Normalized) -> &'static str { - match (p, s) { - (Ok((pc, pr)), Ok((sc, sr))) => { - let (mut a, mut b) = (Vec::new(), Vec::new()); - pc.put(&mut a); - sc.put(&mut b); - if a != b { - "bytes differ" - } else if pr != sr { - "result differs" - } else { - "same" - } - }, - (Err(e), Err(f)) if e == f => "same error", - ( - Err(SharingError::ResourceExhausted(_)), - Err(SharingError::ResourceExhausted(_)), - ) => "both exhausted, other resource", - (Err(_), Err(_)) => "errors differ", - _ => "outcome differs", - } -} - -fn signed(x: u64) -> i64 { - i64::try_from(x).unwrap_or(i64::MAX) -} - -fn peak_rss_kib() -> Option { - let s = std::fs::read_to_string("/proc/self/status").ok()?; - let line = s.lines().find(|l| l.starts_with("VmHWM:"))?; - line.split_whitespace().nth(1)?.parse().ok() -} - -fn percentiles(mut xs: Vec) -> String { - if xs.is_empty() { - return "n/a".into(); - } - xs.sort_unstable(); - // Nearest-rank percentile, rounding the rank down. - let at = |num: usize, den: usize| xs[(xs.len() - 1) * num / den]; - format!( - "min {} p1 {} p10 {} p50 {} p90 {} p99 {} p99.9 {} max {}", - xs[0], - at(1, 100), - at(10, 100), - at(50, 100), - at(90, 100), - at(99, 100), - at(999, 1000), - xs[xs.len() - 1] - ) -} - -fn main() -> Result<(), String> { - let args = parse_args()?; - if let Some(t) = args.threads { - rayon::ThreadPoolBuilder::new() - .num_threads(t) - .build_global() - .map_err(|e| e.to_string())?; - } - let t0 = Instant::now(); - let file = std::fs::File::open(&args.path).map_err(|e| e.to_string())?; - // SAFETY: the corpus file is not modified while it is mapped. - let mmap = - Arc::new(unsafe { memmap2::Mmap::map(&file) }.map_err(|e| e.to_string())?); - let index = Env::parse_lazy_index(&mmap)?; - let load_ms = t0.elapsed().as_millis(); - let mut consts = index.consts.clone(); - if let Some(n) = args.limit { - consts.truncate(n); - } - if !args.only.is_empty() { - consts.retain(|c| { - let h = c.addr.hex(); - args.only.iter().any(|p| h.starts_with(p.as_str())) - }); - } - eprintln!( - "[sharing_corpus] {}: {} constants, {} names, index parsed in {load_ms} ms; processing {} under {:?}", - args.path, - index.consts.len(), - index.named.len(), - consts.len(), - LAYOUT - ); - // Names: the least name of each address; anonymous mutual blocks get the - // least name of a projection into them. - let window: BTreeMap<&Address, (usize, usize)> = - index.consts.iter().map(|c| (&c.addr, (c.offset, c.len))).collect(); - let mut names: BTreeMap = BTreeMap::new(); - for n in &index.named { - let s = n.name.pretty(); - let e = names.entry(n.addr.clone()).or_insert_with(|| s.clone()); - if s < *e { - *e = s; - } - } - let mut block_names: BTreeMap = BTreeMap::new(); - for (addr, name) in &names { - if let Some(&(off, len)) = window.get(addr) - && let Ok(c) = Constant::get(&mut &mmap[off..off + len]) - && let Some(b) = projection_block(&c) - { - let e = block_names.entry(b).or_insert_with(|| format!("{name} [block]")); - let cand = format!("{name} [block]"); - if cand < *e { - *e = cand; - } - } - } - let name_of = |hex: &str, addr: &Address| -> String { - names - .get(addr) - .or_else(|| block_names.get(addr)) - .cloned() - .unwrap_or_else(|| format!("<{}>", &hex[..16])) - }; - let mut limits = ExactSharingLimits::default(); - if let Some(m) = args.max_states { - limits.max_states = m; - } - if let Some(spec) = &args.sharing_limits { - limits = limits.with_overrides(spec)?; - } - limits.full_check = args.full_check; - if args.canonical_tsv.is_some() { - return canonical::run(&args, &limits, &mmap, &consts); - } - let done = AtomicUsize::new(0); - let t1 = Instant::now(); - let total = consts.len(); - let one = |(i, c): (usize, &ixon::env::LazyConstSlice)| { - let row = process( - i, - &c.addr, - &mmap[c.offset..c.offset + c.len], - &limits, - args.parallel, - args.check_sequential, - ); - let d = done.fetch_add(1, Ordering::Relaxed) + 1; - if d.is_multiple_of(50_000) { - eprintln!( - "[sharing_corpus] {d}/{total} in {:.1} s", - t1.elapsed().as_secs_f64() - ); - } - row - }; - // `--serial-constants`: one constant at a time, so the pool serves only the - // parallelism inside a constant (per-constant latency). - let rows: Vec = if args.serial_constants { - consts.iter().enumerate().map(one).collect() - } else { - consts.par_iter().enumerate().map(one).collect() - }; - let run_s = t1.elapsed().as_secs_f64(); - // CSV. - if let Some(path) = &args.csv { - let mut f = std::io::BufWriter::new( - std::fs::File::create(path).map_err(|e| e.to_string())?, - ); - writeln!(f, "idx,addr,name,kind,status,raw,real,tagn,unshared,n,cand,k,w,uncertain,comps,max_comp,slot_states,uniform_states,out_hash,len1,len2,len3,model1,model2,model3,states1,states2,states3,work1,work2,work3,ms").map_err(|e| e.to_string())?; - for (r, c) in rows.iter().zip(&consts) { - let col = |k: usize, f: fn(&(u64, u64, u64, u64)) -> u64| { - r.per_width.get(k).map_or(String::new(), |x| f(x).to_string()) - }; - let cols = |f: fn(&(u64, u64, u64, u64)) -> u64| { - format!("{},{},{}", col(0, f), col(1, f), col(2, f)) - }; - writeln!( - f, - "{},{},\"{}\",{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{:.3}", - r.idx, - r.addr, - name_of(&r.addr, &c.addr).replace('"', "'"), - r.kind, - r.status, - r.raw, - r.real, - r.tagn, - r.unshared.map_or(String::new(), |u| u.to_string()), - r.n, - r.cand, - r.k, - r.w, - r.uncertain, - r.comps, - r.max_comp, - r.slot_states, - r.uniform_states, - r.out_hash, - cols(|x| x.0), - cols(|x| x.1), - cols(|x| x.2), - cols(|x| x.3), - r.ms - ) - .map_err(|e| e.to_string())?; - } - } - // Selection for the Lean differential. - if let Some(path) = &args.select_out { - let mut f = std::io::BufWriter::new( - std::fs::File::create(path).map_err(|e| e.to_string())?, - ); - let mut count = 0; - for r in &rows { - if r.idx % args.select_stride == 0 || r.cand > args.select_min_cand as u64 - { - writeln!(f, "{}", r.addr).map_err(|e| e.to_string())?; - count += 1; - } - } - eprintln!("[sharing_corpus] wrote {count} selected addresses to {path}"); - } - // Report. - let ok: Vec<&Row> = rows.iter().filter(|r| r.status == "ok").collect(); - let failed: Vec<(&Row, &Address)> = rows - .iter() - .zip(&consts) - .filter(|(r, _)| r.status != "ok") - .map(|(r, c)| (r, &c.addr)) - .collect(); - let sum = |f: &dyn Fn(&Row) -> u64| ok.iter().map(|r| f(r)).sum::(); - println!("# sharing_corpus report"); - println!( - "- corpus: {} ({} constants; {} processed); layout {:?}{}", - args.path, - index.consts.len(), - rows.len(), - LAYOUT, - if args.full_check { "; checked mode (--full-check)" } else { "" } - ); - if args.max_states.is_some() || args.sharing_limits.is_some() { - println!("- limits: {limits:?}"); - } - println!( - "- wall: index {load_ms} ms, processing {run_s:.1} s with {} threads; peak RSS {} KiB", - rayon::current_num_threads(), - peak_rss_kib().map_or("?".into(), |k| k.to_string()) - ); - println!( - "- certified: {} / {}; failed: {}", - ok.len(), - rows.len(), - failed.len() - ); - let mut by_status: BTreeMap<&str, usize> = BTreeMap::new(); - for (r, _) in &failed { - *by_status.entry(r.status.as_str()).or_default() += 1; - } - println!("- failures by status: {by_status:?}"); - if args.check_sequential { - let mut by_check: BTreeMap<&str, usize> = BTreeMap::new(); - for r in &rows { - *by_check.entry(r.seq_check).or_default() += 1; - } - println!( - "- sequential check (parallel {:?} vs the sequential reference): {by_check:?}", - args.parallel - ); - for (r, a) in rows.iter().zip(&consts) { - if r.seq_check != "same" && r.seq_check != "same error" { - println!( - " - {}: {} ({})", - r.seq_check, - name_of(&r.addr, &a.addr), - &r.addr[..16] - ); - } - } - } - for (r, a) in &failed { - println!( - " - FAILED {} ({}): {}; N {}, cand {}, raw {} B, {:.0} ms", - name_of(&r.addr, a), - &r.addr[..16], - r.status, - r.n, - r.cand, - r.raw, - r.ms - ); - } - let raw = sum(&|r| r.raw); - let real = sum(&|r| r.real); - let tagn = sum(&|r| r.tagn); - let un_known: Vec<&&Row> = - ok.iter().filter(|r| r.unshared.is_some()).collect(); - let unshared: u64 = un_known.iter().map(|r| r.unshared.unwrap_or(0)).sum(); - let raw_known: u64 = un_known.iter().map(|r| r.raw).sum(); - let real_known: u64 = un_known.iter().map(|r| r.real).sum(); - println!( - "- certified constants: stored {raw} B; serialized output {real} B ({:+.2}%); under TagN {tagn} B ({:+.2}%)", - (real as f64 - raw as f64) / raw as f64 * 100.0, - (tagn as f64 - raw as f64) / raw as f64 * 100.0 - ); - println!( - "- unshared (where it fits u64, {} constants): {unshared} B; stored {raw_known} B; serialized output {real_known} B", - un_known.len() - ); - let deltas: Vec = - ok.iter().map(|r| signed(r.real) - signed(r.raw)).collect(); - let (neg, zero, pos) = ( - deltas.iter().filter(|d| **d < 0).count(), - deltas.iter().filter(|d| **d == 0).count(), - deltas.iter().filter(|d| **d > 0).count(), - ); - println!( - "- serialized output - stored per constant: {neg} smaller, {zero} equal, {pos} larger; {}", - percentiles(deltas) - ); - let tdeltas: Vec = - ok.iter().map(|r| signed(r.tagn) - signed(r.raw)).collect(); - println!("- TagN price - stored per constant: {}", percentiles(tdeltas)); - println!( - "- candidates with a per-prefix phase 3 (order not closed under stored \ - descendants): {}; largest work charged to one candidate: {}", - ok.iter().map(|r| r.per_prefix).sum::(), - ok.iter().flat_map(|r| r.per_width.iter().map(|x| x.3)).max().unwrap_or(0) - ); - println!( - "- phase-1 width w: {:?}", - ok.iter().fold(BTreeMap::::new(), |mut m, r| { - *m.entry(r.w).or_default() += 1; - m - }) - ); - println!( - "- uncertain terms per constant: {}", - percentiles(ok.iter().map(|r| signed(r.uncertain)).collect()) - ); - println!( - "- largest component per constant: {}", - percentiles(ok.iter().map(|r| signed(r.max_comp)).collect()) - ); - println!( - "- uniform search states per constant: {}", - percentiles(ok.iter().map(|r| signed(r.uniform_states)).collect()) - ); - println!( - "- first-tier search states per constant: {}", - percentiles(ok.iter().map(|r| signed(r.slot_states)).collect()) - ); - println!( - "- R1/R2 candidates per constant (all): {}", - percentiles(rows.iter().map(|r| signed(r.cand)).collect()) - ); - println!( - "- milliseconds per constant (certified): {}", - percentiles(ok.iter().map(|r| r.ms.round() as i64).collect()) - ); - let mut slow: Vec<(&Row, &Address)> = - rows.iter().zip(&consts).map(|(r, c)| (r, &c.addr)).collect(); - slow.sort_by(|a, b| b.0.ms.total_cmp(&a.0.ms)); - println!("- slowest 10:"); - for (r, a) in slow.iter().take(10) { - println!( - " - {:.0} ms {} ({}): {} {}, N {}, cand {}, k {}, w {}, uncertain {}, components {} (largest {}), states uniform {} first-tier {}", - r.ms, - name_of(&r.addr, a), - &r.addr[..16], - r.kind, - r.status, - r.n, - r.cand, - r.k, - r.w, - r.uncertain, - r.comps, - r.max_comp, - r.uniform_states, - r.slot_states - ); - } - // Phase timers (only with the `sharing-profile` feature of ixon). - let profile = ixon::sharing_exact::profile_report(); - if !profile.is_empty() { - println!("- phase timers (summed over threads):"); - for (name, ns, calls) in profile { - println!(" - {name}: {:.3} s in {calls} scopes", ns as f64 / 1e9); - } - } - println!("- total wall {:.1} s", t0.elapsed().as_secs_f64()); - Ok(()) -} diff --git a/crates/ixon/examples/sharing_corpus/canonical.rs b/crates/ixon/examples/sharing_corpus/canonical.rs deleted file mode 100644 index 3eed9cf41..000000000 --- a/crates/ixon/examples/sharing_corpus/canonical.rs +++ /dev/null @@ -1,108 +0,0 @@ -//! Fixed-rule corpus receipts through the ordinary production API. -//! No variable gate, classification observer or noncanonical entry point. - -use super::*; - -fn preserve(path: &std::path::Path, bytes: &[u8]) -> Result<(), String> { - match std::fs::OpenOptions::new().write(true).create_new(true).open(path) { - Ok(mut file) => file.write_all(bytes).map_err(|e| e.to_string()), - Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => { - if std::fs::read(path).map_err(|e| e.to_string())? == bytes { - Ok(()) - } else { - Err(format!("refusing differing artifact {}", path.display())) - } - }, - Err(e) => Err(e.to_string()), - } -} - -pub(super) fn run( - args: &Args, - limits: &ExactSharingLimits, - mmap: &[u8], - consts: &[ixon::env::LazyConstSlice], -) -> Result<(), String> { - let path = args.canonical_tsv.as_ref().ok_or("missing canonical TSV")?; - let mut out = std::io::BufWriter::new( - std::fs::OpenOptions::new() - .write(true) - .create_new(true) - .open(path) - .map_err(|e| e.to_string())?, - ); - writeln!(out, "canonical\tv1\trule=sharing-k16-greedy-v1\tK=16\thash=blake3\tAPI=normalize_constant_sharing_tiered_par\tlimits={limits:?}") - .map_err(|e| e.to_string())?; - writeln!( - out, - "invocation\t{:?}\tconstant_order=serial\ttiming=includes-receipt-IO", - std::env::args().collect::>() - ) - .map_err(|e| e.to_string())?; - let export = args.canonical_export.as_ref().map(std::path::Path::new); - if let Some(dir) = export { - std::fs::create_dir_all(dir).map_err(|e| e.to_string())?; - } - let mut failed = 0; - for entry in consts { - let addr = entry.addr.hex(); - let bytes = &mmap[entry.offset..entry.offset + entry.len]; - let start = Instant::now(); - writeln!( - out, - "begin\t{addr}\tinput_bytes={}\tinput_hash={}", - bytes.len(), - blake3::hash(bytes) - ) - .map_err(|e| e.to_string())?; - out.flush().map_err(|e| e.to_string())?; - eprintln!("[CANONICAL] BEGIN {addr}"); - let result = (|| -> Result<(), String> { - if let Some(dir) = export { - preserve(&dir.join(format!("{addr}.input.ixn")), bytes)?; - } - let mut input = bytes; - let c = Constant::get(&mut input).map_err(|e| format!("decode: {e}"))?; - if !input.is_empty() { - return Err("decode: trailing constant bytes".into()); - } - let (candidate, result) = normalize_constant_sharing_tiered_par( - LAYOUT, - &c, - limits, - args.parallel, - ) - .map_err(|e| e.to_string())?; - let mut encoded = Vec::new(); - candidate.put(&mut encoded); - if let Some(dir) = export { - preserve(&dir.join(format!("{addr}.canonical.ixn")), &encoded)?; - } - writeln!(out, "output\t{addr}\tbytes={}\tinput_equal={}\thash={}\tselected_width={}\tselected_width_max_component={}\tmeters={:?}", - encoded.len(), encoded == bytes, blake3::hash(&encoded), result.stats.w, - result.phase1.components.iter().map(Vec::len).max().unwrap_or(0), result.stats.candidate_meters) - .map_err(|e| e.to_string())?; - Ok(()) - })(); - let status = match result { - Ok(()) => "ok-canonical".to_string(), - Err(e) => { - failed += 1; - format!("failed:{e}") - }, - }; - writeln!( - out, - "end\t{addr}\t{status}\tms={:.3}", - start.elapsed().as_secs_f64() * 1e3 - ) - .map_err(|e| e.to_string())?; - out.flush().map_err(|e| e.to_string())?; - eprintln!("[CANONICAL] END {addr} {status}"); - } - if failed == 0 { - Ok(()) - } else { - Err(format!("{failed} canonical constants failed")) - } -} diff --git a/crates/ixon/examples/sharing_numeric.rs b/crates/ixon/examples/sharing_numeric.rs deleted file mode 100644 index 6c7e045ba..000000000 --- a/crates/ixon/examples/sharing_numeric.rs +++ /dev/null @@ -1,230 +0,0 @@ -//! Offline numeric diagnostic; no production algorithm or limit changes. -//! Usage: sharing_numeric EXPORT_DIR DEPTH (0..=512). -//! Closed term: fun (A : Type) (f : A -> A -> A) (z : A) => T_depth, -//! T_0=z, T_(i+1)=f T_i T_i. Every sharing row uses the same A/f/z scope. -//! Typing follows this construction; this executable does not run a kernel. - -use std::io::Write; -use std::path::Path; -use std::sync::Arc; -use std::time::Instant; - -use ix_common::env::DefinitionSafety; -use ixon::Env; -use ixon::constant::{Constant, ConstantInfo, DefKind, Definition}; -use ixon::expr::Expr; -use ixon::sharing_exact::{ - ExactSharingLimits, ShareLayout, SharingDag, - normalize_constant_sharing_tiered, -}; -use ixon::univ::Univ; -use sha2::{Digest, Sha256}; - -fn tower(depth: u64) -> Constant { - let binary = Expr::all(Expr::var(0), Expr::all(Expr::var(1), Expr::var(2))); - let typ = Expr::all( - Expr::sort(0), - Expr::all(binary.clone(), Expr::all(Expr::var(1), Expr::var(2))), - ); - let mut sharing = vec![Expr::var(0)]; - for i in 0..depth { - let previous = Arc::new(Expr::Share(i)); - sharing - .push(Expr::app(Expr::app(Expr::var(1), previous.clone()), previous)); - } - let value = Expr::lam( - Expr::sort(0), - Expr::lam(binary, Expr::lam(Expr::var(1), Arc::new(Expr::Share(depth)))), - ); - Constant::with_tables( - ConstantInfo::Defn(Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls: 0, - typ, - value, - }), - sharing, - vec![], - vec![Univ::succ(Univ::zero())], - ) -} - -// Independent scoped-subgrammar check; it never expands a Share. -fn scoped(e: &Expr, binders: u64, prior: u64) -> bool { - match e { - Expr::Var(i) => *i < binders, - Expr::Sort(i) => *i == 0, - Expr::Share(i) => binders == 3 && *i < prior, - Expr::App(f, a) => scoped(f, binders, prior) && scoped(a, binders, prior), - Expr::Lam(_, ty, body) | Expr::All(_, _, ty, body) => { - scoped(ty, binders, prior) && scoped(body, binders + 1, prior) - }, - _ => false, - } -} - -fn validate(c: &Constant) -> Result { - let ConstantInfo::Defn(d) = &c.info else { - return Err("not a definition".into()); - }; - let count = u64::try_from(c.sharing.len()) - .map_err(|e| format!("sharing length exceeds u64: {e}"))?; - if d.kind != DefKind::Definition - || d.safety != DefinitionSafety::Safe - || d.lvls != 0 - || !c.refs.is_empty() - || c.univs != vec![Univ::succ(Univ::zero())] - || !scoped(&d.typ, 0, 0) - || !scoped(&d.value, 0, count) - || c - .sharing - .iter() - .enumerate() - .any(|(i, e)| !u64::try_from(i).is_ok_and(|prior| scoped(e, 3, prior))) - { - return Err("fixture scope/fixed-field validation failed".into()); - } - let mut bytes = Vec::new(); - c.put(&mut bytes); - let mut rest = bytes.as_slice(); - let decoded = Constant::get(&mut rest)?; - if !rest.is_empty() || decoded != *c { - return Err("constant roundtrip mismatch".into()); - } - SharingDag::from_constant(c, &ExactSharingLimits::default()) - .map_err(|e| e.to_string()) -} - -fn preserve(path: &Path, bytes: &[u8]) -> Result<(), String> { - match std::fs::OpenOptions::new().write(true).create_new(true).open(path) { - Ok(mut file) => file.write_all(bytes).map_err(|e| e.to_string()), - Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => { - if std::fs::read(path).map_err(|e| e.to_string())? == bytes { - Ok(()) - } else { - Err(format!("refusing differing artifact {}", path.display())) - } - }, - Err(e) => Err(e.to_string()), - } -} - -fn run(dir: &Path, depth: u64) -> Result<(), String> { - let c = tower(depth); - let dag = validate(&c)?; - let (addr, bytes) = c.commit(); - std::fs::create_dir_all(dir).map_err(|e| e.to_string())?; - preserve(&dir.join(format!("depth-{depth:04}.input.ixn")), &bytes)?; - let env = Env::new(); - env.store_const(addr.clone(), c.clone()); - let mut envelope = Vec::new(); - env.put(&mut envelope)?; - let mut rest = envelope.as_slice(); - let loaded = Env::get(&mut rest)?; - if !rest.is_empty() || loaded.consts.len() != 1 { - return Err("env roundtrip mismatch".into()); - } - preserve(&dir.join(format!("depth-{depth:04}.input.ixe")), &envelope)?; - println!( - "INPUT\tdepth={depth}\tbody_unshared_bits={}\tbytes={}\tdag_nodes={}\taddress={}\tsha256={:x}\tixe_sha256={:x}", - if depth == 0 { 1 } else { depth + 2 }, - bytes.len(), - dag.len(), - addr.hex(), - Sha256::digest(&bytes), - Sha256::digest(&envelope) - ); - println!( - "VALID\tcodec-roundtrip\tbackward-shares\tclosed-binders\trefs-empty\tunivs=[SuccZero]\ttyping=construction-only\tlimits=default\tK=16\trule=sharing-k16-greedy-v1" - ); - std::io::stdout().flush().map_err(|e| e.to_string())?; - let limits = ExactSharingLimits::default(); - let start = Instant::now(); - match normalize_constant_sharing_tiered(ShareLayout::TagN, &c, &limits) { - Err(e) => println!( - "NORMALIZE\tdepth={depth}\tms={}\terror={e:?}\tdisplay={e}", - start.elapsed().as_millis() - ), - Ok((out, result)) => { - let other = - SharingDag::from_constant(&out, &limits).map_err(|e| e.to_string())?; - let ConstantInfo::Defn(d) = &out.info else { - return Err("output kind changed".into()); - }; - if other.nodes() != dag.nodes() - || other.roots() != dag.roots() - || out.refs != c.refs - || out.univs != c.univs - || d.kind != DefKind::Definition - || d.safety != DefinitionSafety::Safe - || d.lvls != 0 - { - return Err("output DAG/fixed-field mismatch".into()); - } - let mut encoded = Vec::new(); - out.put(&mut encoded); - preserve( - &dir.join(format!("depth-{depth:04}.K16.canonical.ixn")), - &encoded, - )?; - println!( - "NORMALIZE\tdepth={depth}\tms={}\tok\tbytes={}\tsha256={:x}\twidth={}\tmeters={:?}", - start.elapsed().as_millis(), - encoded.len(), - Sha256::digest(&encoded), - result.stats.w, - result.stats.candidate_meters - ); - }, - } - Ok(()) // Refusal is recorded data; validation/IO failures remain errors. -} - -fn main() -> Result<(), String> { - let args: Vec = std::env::args().collect(); - if args.len() != 3 { - return Err("usage: sharing_numeric EXPORT_DIR DEPTH".into()); - } - let depth: u64 = - args[2].parse().map_err(|e| format!("invalid depth: {e}"))?; - if depth > 512 { - return Err("diagnostic depth exceeds 512".into()); - } - run(Path::new(&args[1]), depth) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn scoped_backward_towers_and_rejections() { - for depth in [0, 1, 62, 63, 64, 125, 126, 127, 128, 130, 256] { - assert!(validate(&tower(depth)).is_ok(), "depth {depth}"); - } - let mut bad = tower(2); - bad.sharing[0] = Arc::new(Expr::Share(0)); - assert!(validate(&bad).is_err()); - assert!( - SharingDag::from_constant(&bad, &ExactSharingLimits::default()).is_err() - ); - let mut bad = tower(2); - bad.sharing[0] = Expr::var(3); - assert!(validate(&bad).is_err()); - let mut bad = tower(2); - bad.univs.clear(); - assert!(validate(&bad).is_err()); - } - - #[test] - fn small_unshared_body_length_oracle() { - let mut body = Expr::var(0); - for depth in 0..=10 { - let mut bytes = Vec::new(); - ixon::serialize::put_expr(&body, &mut bytes); - assert_eq!(bytes.len(), 3 * (1usize << depth) - 2); - body = Expr::app(Expr::app(Expr::var(1), body.clone()), body); - } - } -} diff --git a/crates/ixon/src/canon_univ.rs b/crates/ixon/src/canon_univ.rs deleted file mode 100644 index 980df4eaa..000000000 --- a/crates/ixon/src/canon_univ.rs +++ /dev/null @@ -1,1112 +0,0 @@ -//! Universe-level canonicalization (canonicity §10.6). -//! -//! `canon_univ = linearize ∘ subsumption ∘ normalize_aux` — a total, -//! deterministic canonical representative of a stored level's Géran -//! semantic-equality class, kernel-free and next to the wire type so the -//! compilers, the Tc egress, and probes can all use it (the three kernel -//! `NormLevel` implementations stay untouched and serve as the P4 -//! oracle). Mirrors `Ix/IxonUniv.lean`; the Géran machinery is the -//! transliteration of `Ix/Tc/Level.lean:227-472` / -//! `crates/kernel/src/level.rs` onto `ixon::univ::Univ`. -//! -//! The frozen kernel `mk*` rule set (M1–M8 / I1–I6, from Lean's -//! `kernel/level.cpp:81-120` via the three kernel mirrors) is kept with -//! its kernel-rebuild role: [`reduce_univ`] is the stage-1 -//! decoration-presence test and the P6 oracle, and P3 (canonical forms -//! are `mk*` fixpoints) is what makes kernel ingress the identity on -//! canonical content. -//! -//! Property set (tested here; Verify-layer proofs are the D7 follow-up): -//! - P0 value preservation: `canon_univ u` and `u` agree at every -//! valuation of the params (the compiler must not change the universe -//! a declaration states); -//! - P1 idempotence: `canon_univ (canon_univ u) = canon_univ u`; -//! - P2 roundtrip-fixpoint: `normalize (linearize L) = L` for reachable -//! `L` — exact on non-empty entries; `subsumption` can leave EMPTY -//! entries behind (constant 0, no vars) which `linearize` cannot (and -//! should not) re-express, so the strict form holds modulo empty -//! entries ([`norm_eq_semantic`]). See the O1 note below. -//! - P3 mk*-fixpoint: `reduce_univ (canon_univ u) = canon_univ u`; -//! - P4 soundness: `univEq u (canon_univ u)` against the kernel oracle -//! (lives in the kernel/Tc test suites — this crate cannot depend on -//! the kernels), modulo the same empty-entry caveat; -//! - P5 mirror parity: byte-for-byte agreement with `Ix/IxonUniv.lean` -//! (FFI cross-check in the Lean test suite); -//! - P6 mk* absorption: `canon_univ (reduce_univ u) = canon_univ u`. -//! -//! O1 empty-entry note: kernel `normLevelEq` compares subsumption output -//! maps INCLUDING empty entries, which makes kernel `univEq` strictly -//! finer than semantic level equality on rare shapes (34 of 3,253,373 -//! whole-Mathlib table entries — e.g. `succ (max (u+1) (imax (u+1) v))` -//! keeps an empty `[u,v]` entry that its semantic equal -//! `max (u+2) (v+1)` never creates). `canon_univ` canonicalizes BY -//! SEMANTIC CLASS (empties carry no value), which is the "all the way to -//! Géran" endpoint; aligning the kernels' `normLevelEq` to ignore empty -//! entries (a completeness improvement — `normLevelLe` already ignores -//! them) is the pending kernel-side counterpart decision. - -use std::collections::{BTreeMap, HashSet}; -use std::sync::Arc; - -use super::univ::Univ; - -// ============================================================================ -// Frozen kernel mk* rule set (M1-M8 / I1-I6) -// ============================================================================ - -fn is_explicit(u: &Univ) -> bool { - match u { - Univ::Zero => true, - Univ::Succ(i) => is_explicit(i), - _ => false, - } -} - -fn is_never_zero(u: &Univ) -> bool { - match u { - Univ::Succ(_) => true, - Univ::Max(a, b) => is_never_zero(a) || is_never_zero(b), - Univ::IMax(_, b) => is_never_zero(b), - _ => false, - } -} - -/// Peel the outermost succ chain: `(base, n)` with `u = succ^n base`. -fn peel_offset(u: &Arc) -> (Arc, u64) { - match u.as_ref() { - Univ::Succ(i) => { - let (b, n) = peel_offset(i); - (b, n + 1) - }, - _ => (u.clone(), 0), - } -} - -/// `mkMax` (Lean `kernel/level.cpp:81-103`; `Ix/Tc/Level.lean` `mkMax`; -/// `crates/kernel/src/level.rs` `KUniv::max`) transliterated to `Univ`. -/// First applicable rule wins: M1 numerals → the larger (ties → `a`); -/// M2 `max a a = a`; M3/M4 zero sides; M5/M6 absorption; M7 same-base -/// offsets; M8 raw. -pub fn n_max(a: Arc, b: Arc) -> Arc { - if is_explicit(&a) && is_explicit(&b) { - let (_, na) = peel_offset(&a); - let (_, nb) = peel_offset(&b); - return if na >= nb { a } else { b }; - } - if a == b { - return a; - } - if matches!(a.as_ref(), Univ::Zero) { - return b; - } - if matches!(b.as_ref(), Univ::Zero) { - return a; - } - if let Univ::Max(bl, br) = b.as_ref() - && (*bl == a || *br == a) - { - return b; - } - if let Univ::Max(al, ar) = a.as_ref() - && (*al == b || *ar == b) - { - return a; - } - let (base_a, off_a) = peel_offset(&a); - let (base_b, off_b) = peel_offset(&b); - if base_a == base_b { - return if off_a >= off_b { a } else { b }; - } - Univ::max(a, b) -} - -/// `mkIMax` (Lean `kernel/level.cpp:112-120` and kernel mirrors): I1 -/// never-zero right → `n_max`; I2 `imax a 0 = 0`; I3 `imax 0 b = b`; -/// I4 `imax 1 b = b`; I5 `imax a a = a`; I6 raw. -pub fn n_imax(a: Arc, b: Arc) -> Arc { - if is_never_zero(&b) { - return n_max(a, b); - } - if matches!(b.as_ref(), Univ::Zero) { - return b; - } - if matches!(a.as_ref(), Univ::Zero) { - return b; - } - if let Univ::Succ(i) = a.as_ref() - && matches!(i.as_ref(), Univ::Zero) - { - return b; - } - if a == b { - return a; - } - Univ::imax(a, b) -} - -/// The kernel-rebuild closure on stored trees: bottom-up rebuild through -/// the simplifying constructors — exactly what anon/meta ingress does. -/// Mirrors `Ix.Tc.reduceIxonUniv`. A non-fixpoint entry reaches the -/// kernel changed (the stage-1 decoration-presence test), and P6 pins -/// that this rebuild refines into the Géran classes. -pub fn reduce_univ(u: &Arc) -> Arc { - match u.as_ref() { - Univ::Zero | Univ::Var(_) => u.clone(), - Univ::Succ(i) => { - let ri = reduce_univ(i); - if ri == *i { u.clone() } else { Univ::succ(ri) } - }, - Univ::Max(a, b) => n_max(reduce_univ(a), reduce_univ(b)), - Univ::IMax(a, b) => n_imax(reduce_univ(a), reduce_univ(b)), - } -} - -// ============================================================================ -// Géran canonical form (Ix/Tc/Level.lean:231-431 on Univ) -// ============================================================================ - -/// An imax-conditioning chain: sorted param indices. -pub type Path = Vec; - -/// Per-path node: a constant plus variable contributions `(idx, offset)` -/// sorted by ascending idx. Invariant maintained by the normalizer: -/// every var's idx is a member of its own path (self-gating is free). -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct Node { - pub constant: u64, - pub vars: Vec<(u64, u64)>, -} - -impl Node { - pub fn is_empty(&self) -> bool { - self.constant == 0 && self.vars.is_empty() - } -} - -/// Canonical form: map from imax-paths to nodes (lexicographic order). -pub type NormLevel = BTreeMap; - -/// Insert `(idx, k)` into the sorted var list, max-merging offsets. `k` -/// must be the current succ-accumulator (the classic port bug is -/// dropping it — `Ix/Tc/Level.lean:249-252`). -fn node_add_var(n: &mut Node, idx: u64, k: u64) { - match n.vars.iter().position(|v| idx <= v.0) { - Some(p) => { - if n.vars[p].0 == idx { - n.vars[p].1 = n.vars[p].1.max(k); - } else { - n.vars.insert(p, (idx, k)); - } - }, - None => n.vars.push((idx, k)), - } -} - -fn add_var(acc: &mut NormLevel, idx: u64, k: u64, path: &Path) { - node_add_var(acc.entry(path.clone()).or_default(), idx, k); -} - -fn add_const(acc: &mut NormLevel, k: u64, path: &Path) { - if k == 0 || (k == 1 && !path.is_empty()) { - return; - } - let n = acc.entry(path.clone()).or_default(); - n.constant = n.constant.max(k); -} - -fn ordered_insert(a: u64, path: &Path) -> Option { - match path.binary_search(&a) { - Ok(_) => None, - Err(p) => { - let mut out = path.clone(); - out.insert(p, a); - Some(out) - }, - } -} - -/// The calls the normalizer already made (Lean's `CanonUniv.NState`). -/// Every contribution of `normalize_aux` and `normalize_imax_dispatch` is a -/// max-merge into one entry of the form (`add_const`, `add_var`), so a call -/// adds the same contributions whatever the form it starts from, and a call -/// made a second time adds nothing: the sets skip repeated calls, and the -/// form is the one the calls without them build. The `imax` distributions -/// repeat calls: on a chain `imax a (imax a (… (imax a u)))` (one `imax` per -/// binder, as Lean's `Meta.getLevel` returns for a long `∀`) the calls -/// without the sets are `2ⁿ`, with them `O(n²)`. -#[derive(Default)] -struct Seen { - aux: HashSet<(Univ, Path, u64)>, - dispatch: HashSet<(Univ, Univ, Path, u64)>, -} - -fn normalize_aux( - l: &Univ, - path: &Path, - k: u64, - acc: &mut NormLevel, - seen: &mut Seen, -) { - if !seen.aux.insert((l.clone(), path.clone(), k)) { - return; - } - match l { - Univ::Zero => add_const(acc, k, path), - Univ::Succ(i) => normalize_aux(i, path, k + 1, acc, seen), - Univ::Max(a, b) => { - normalize_aux(a, path, k, acc, seen); - normalize_aux(b, path, k, acc, seen); - }, - Univ::IMax(u, b) => normalize_imax_dispatch(u, b, path, k, acc, seen), - Univ::Var(idx) => match ordered_insert(*idx, path) { - Some(new_path) => { - // When param(idx) = 0, imax(u, 0) = 0 — the outer k succs - // still contribute at the un-extended path. - add_const(acc, k, path); - add_var(acc, *idx, k, &new_path); - }, - None => { - if k != 0 { - add_var(acc, *idx, k, path); - } - }, - }, - } -} - -/// Dispatch `imax(a, b)` on `b`'s shape, including the distributions -/// `imax(a, max(v, w)) = max(imax(a, v), imax(a, w))` and -/// `imax(a, imax(v, w)) = max(imax(a, w), imax(v, w))` (these duplicate -/// calls: without `Seen`, the §3.5 worst-case-exponential compile-time -/// cost). -fn normalize_imax_dispatch( - a: &Univ, - b: &Univ, - path: &Path, - k: u64, - acc: &mut NormLevel, - seen: &mut Seen, -) { - if !seen.dispatch.insert((a.clone(), b.clone(), path.clone(), k)) { - return; - } - match b { - Univ::Zero => add_const(acc, k, path), - Univ::Succ(v) => { - normalize_aux(a, path, k, acc, seen); - normalize_aux(v, path, k + 1, acc, seen); - }, - Univ::Max(v, w) => { - normalize_imax_dispatch(a, v, path, k, acc, seen); - normalize_imax_dispatch(a, w, path, k, acc, seen); - }, - Univ::IMax(v, w) => { - normalize_imax_dispatch(a, w, path, k, acc, seen); - normalize_imax_dispatch(v, w, path, k, acc, seen); - }, - Univ::Var(idx) => match ordered_insert(*idx, path) { - Some(new_path) => { - add_const(acc, k, path); - add_var(acc, *idx, k, &new_path); - normalize_aux(a, &new_path, k, acc, seen); - }, - None => { - if k != 0 { - add_var(acc, *idx, k, path); - } - normalize_aux(a, path, k, acc, seen); - }, - }, - } -} - -fn is_subset(p2: &[u64], p1: &[u64]) -> bool { - let mut i1 = 0; - for x in p2 { - loop { - if i1 >= p1.len() { - return false; - } - if p1[i1] == *x { - i1 += 1; - break; - } - if p1[i1] > *x { - return false; - } - i1 += 1; - } - } - true -} - -/// Keep only the `xs` entries not dominated by a `ys` entry (merge-walk -/// over sorted var lists). Mirrors `Ix.Tc.Level.subsumeVars`. -fn subsume_vars(xs: &[(u64, u64)], ys: &[(u64, u64)]) -> Vec<(u64, u64)> { - let mut out = Vec::new(); - let (mut xi, mut yi) = (0, 0); - while xi < xs.len() { - if yi >= ys.len() { - out.extend_from_slice(&xs[xi..]); - break; - } - let x = xs[xi]; - let y = ys[yi]; - if x.0 < y.0 { - out.push(x); - xi += 1; - } else if x.0 == y.0 { - if x.1 > y.1 { - out.push(x); - } - xi += 1; - yi += 1; - } else { - yi += 1; - } - } - out -} - -/// Drop contributions dominated by entries at sub-paths. Iterates -/// entries and the pre-pass snapshot in ascending key order, exactly -/// like the kernel mirrors (in-loop `n1` mutations are order-sensitive). -fn subsumption(acc: NormLevel) -> NormLevel { - let snapshot: Vec<(Path, Node)> = - acc.iter().map(|(p, n)| (p.clone(), n.clone())).collect(); - let mut result = acc; - for (p1, n1_0) in &snapshot { - let mut n1 = n1_0.clone(); - for (p2, n2) in &snapshot { - if is_subset(p2, p1) { - let same = p1.len() == p2.len(); - if n1.constant != 0 { - let max_var_offset = n1.vars.iter().fold(0, |m, v| m.max(v.1)); - let keep_const = (same || n1.constant > n2.constant) - && (n2.vars.is_empty() || n1.constant > max_var_offset + 1); - if !keep_const { - n1.constant = 0; - } - } - if !same && !n2.vars.is_empty() { - n1.vars = subsume_vars(&n1.vars, &n2.vars); - } - } - } - result.insert(p1.clone(), n1); - } - result -} - -/// Normalize a stored level to Géran's canonical form (the kernel's -/// comparison structure, on `Univ`). -pub fn normalize(u: &Univ) -> NormLevel { - let mut acc = NormLevel::new(); - acc.insert(Vec::new(), Node::default()); - normalize_aux(u, &Vec::new(), 0, &mut acc, &mut Seen::default()); - subsumption(acc) -} - -/// Semantic canonical-form equality: entries compared modulo EMPTY -/// entries (constant 0, no vars — value-free subsumption artifacts). -/// `normLevelLe` in the kernels is already empty-insensitive; strict -/// `normLevelEq` is not (the O1 note in the module doc). -pub fn norm_eq_semantic(a: &NormLevel, b: &NormLevel) -> bool { - let mut ai = a.iter().filter(|(_, n)| !n.is_empty()); - let mut bi = b.iter().filter(|(_, n)| !n.is_empty()); - loop { - match (ai.next(), bi.next()) { - (None, None) => return true, - (Some(x), Some(y)) if x == y => {}, - _ => return false, - } - } -} - -// ============================================================================ -// Linearization (§3.2 — the O1 construction) -// ============================================================================ - -fn succs(mut u: Arc, n: u64) -> Arc { - for _ in 0..n { - u = Univ::succ(u); - } - u -} - -/// Is a `u_i = 0` fallout of `k` dominated under `ctx`? Some entry at a -/// subset path must guarantee ≥ k whenever `ctx` is active: a constant -/// ≥ k, or a var atom `(q, off)` with `off + 1 ≥ k` (its `u_q` is ≥ 1 -/// under the context). This is `subsumption`'s own domination logic, -/// read back. Exact: at `u_i = 0`, with the context's params at 1 and -/// every other param at 0, the map's value is the best such guarantee. -fn covered(norm: &NormLevel, k: u64, ctx: &[u64]) -> bool { - if k == 0 { - return true; - } - norm.iter().any(|(q, n)| { - q.iter().all(|x| ctx.contains(x)) - && (n.constant >= k || n.vars.iter().any(|(_, off)| off + 1 >= k)) - }) -} - -/// Gate-nesting order for a context (outermost first), and whether it -/// is leak-free. The greedy pick is the smallest remaining gate `g` with -/// a `(g, ·)` atom at some non-empty map path inside `chosen ∪ {g}`: its -/// creation site, the absorber of its leak. The binary `imax` chain -/// leaks each gate's own value under weaker conditions than the full -/// context (`imax(t, u_g) ≥ u_g` wherever the outer gates are active), -/// and an absorber atom is exactly what dominates that leak. -/// Absorbability only grows with `chosen`, so the greedy finds a full -/// order whenever one exists. Every path of a normalizer-reachable map -/// has one (its gating chain starts at a singleton, and subsumption only -/// removes an atom in favor of a dominator at a sub-path); a -/// self-stripped context `P∖{i}` need not, which the second component -/// reports. When no remaining gate is absorbable, the smallest is taken -/// for totality and the order is reported as leaking (`false`). -fn gate_order(norm: &NormLevel, ctx: &[u64]) -> (Vec, bool) { - let mut order: Vec = Vec::new(); - let mut leak_free = true; - let mut remaining: Vec = ctx.to_vec(); - while !remaining.is_empty() { - let found = remaining.iter().copied().find(|g| { - norm.iter().any(|(p, n)| { - !p.is_empty() - && p.iter().all(|x| *x == *g || order.contains(x)) - && n.vars.iter().any(|(i, _)| i == g) - }) - }); - let pick = found.unwrap_or_else(|| { - leak_free = false; - remaining[0] - }); - order.push(pick); - remaining.retain(|x| *x != pick); - } - (order, leak_free) -} - -/// The canonical representative of a canonical form, by per-atom gate -/// inversion. -/// -/// Map semantics: `val = max` over entries `(P, {c, vars})` — active -/// when every `u_p, p ∈ P` is ≥ 1 — of `max(c, max (u_i + k))`. The -/// normalizer maintains two invariants this inversion leans on: every -/// var atom's idx is a member of its own path, and each gating step -/// writes a `(q, k)` atom at the singleton extension of its context -/// plus a fallout constant `addConst(k, path)` (skipped for k ≤ 1 in -/// gated contexts). -/// -/// Inversion: -/// 1. explode entries into per-atom items; self-strip each atom -/// `(i, k)@P` to context `P∖{i}` when BOTH -/// - its `u_i = 0` fallout `k` is covered there ([`covered`]: some -/// entry at a sub-path of the context guarantees `≥ k`), and -/// - the context's own gates are leak-free ([`gate_order`] finds an -/// absorber for every gate of `P∖{i}`): the emitted chain -/// `imax(…, u_g)` is `≥ u_g` wherever its outer gates are active, -/// so a gate without an absorber at a path inside its prefix would -/// raise the value. With the first condition alone, -/// `imax (imax (imax u w + 1) u) v` would canonicalize to a level -/// that is `2` at `u = 0, v = 1, w = 2` where it is `1`; -/// -/// otherwise it stays fully gated at `P` (every map path is -/// leak-free: its creation chain is a gate order); -/// 2. group items by context; a context group emits -/// `imax(…imax(body, u_o(m-1))…, u_o0)` along the recovered gate order -/// `o` (outermost first), with body = atoms ascending by idx, then the -/// gated constant; each `(o_j, 0)` marker item at the context -/// `{o_0..o_(j-1)}` is consumed (gate `o_j` re-supplies it); -/// 3. unconsumed markers emit as bare vars; the root constant emits -/// last, unless absorbed by a top-level atom with `k ≥ c`. -/// -/// Output shape: a right-nested `max` chain — root atoms (ascending -/// idx), then gate groups in lexicographic context order, then the root -/// constant. Everything is inherited from the map's own ordering — no -/// fresh choices — and the result is a `mk*` fixpoint (P3). Every emitted -/// term is at most the map's value, and every map contribution is at most -/// some emitted term, so the value is preserved (P0). -pub fn linearize(norm: &NormLevel) -> Arc { - let const_at = |p: &[u64]| -> u64 { norm.get(p).map_or(0, |n| n.constant) }; - let c_root = const_at(&[]); - // Context groups: constant + atoms (max-merged per idx). - #[derive(Default)] - struct Group { - constant: u64, - atoms: BTreeMap, // idx -> offset - } - let mut groups: BTreeMap = BTreeMap::new(); - for (path, node) in norm { - if !path.is_empty() && node.constant > 0 { - let g = groups.entry(path.clone()).or_default(); - g.constant = g.constant.max(node.constant); - } - for (i, k) in &node.vars { - let ctx: Path = path.iter().copied().filter(|p| p != i).collect(); - let home = if covered(norm, *k, &ctx) && gate_order(norm, &ctx).1 { - ctx - } else { - path.clone() - }; - let g = groups.entry(home).or_default(); - let slot = g.atoms.entry(*i).or_insert(0); - *slot = (*slot).max(*k); - } - } - // Marker consumption: re-normalizing the emitted chain recreates gate - // `order[j]`'s `(order[j], 0)` marker at the path `{order[0..=j]}`, - // which self-strips to the item context `{order[0..j]}` — so a marker - // item already there is re-supplied by the gate, not re-emitted. - let mut consumed: HashSet<(Path, u64)> = HashSet::new(); - for (ctx, g) in &groups { - if ctx.is_empty() || (g.constant == 0 && g.atoms.is_empty()) { - continue; - } - let (order, _) = gate_order(norm, ctx); - for (j, p) in order.iter().enumerate() { - let mut mctx: Path = order[..j].to_vec(); - mctx.sort_unstable(); - if groups.get(&mctx).is_some_and(|sg| sg.atoms.get(p) == Some(&0)) { - consumed.insert((mctx, *p)); - } - } - } - // Emission. - let mut terms: Vec> = Vec::new(); - let mut root_c_absorbed = false; - if let Some(top) = groups.get(&Vec::new()) { - for (i, k) in &top.atoms { - if *k == 0 && consumed.contains(&(Vec::new(), *i)) { - continue; - } - terms.push(succs(Univ::var(*i), *k)); - if *k >= c_root { - root_c_absorbed = true; - } - } - } - for (ctx, g) in &groups { - if ctx.is_empty() { - continue; - } - let mut atoms: Vec> = Vec::new(); - for (i, k) in &g.atoms { - if *k == 0 && consumed.contains(&(ctx.clone(), *i)) { - continue; - } - atoms.push(succs(Univ::var(*i), *k)); - } - if g.constant > 0 { - atoms.push(succs(Univ::zero(), g.constant)); - } - let Some(body) = ({ - let mut it = atoms.into_iter().rev(); - it.next().map(|last| it.fold(last, |acc, a| Univ::max(a, acc))) - }) else { - continue; - }; - // Wrap gates innermost-to-outermost following the recovered order. - let (order, _) = gate_order(norm, ctx); - let term = - order.iter().rev().fold(body, |acc, p| Univ::imax(acc, Univ::var(*p))); - terms.push(term); - } - if c_root > 0 && !root_c_absorbed { - terms.push(succs(Univ::zero(), c_root)); - } - let mut it = terms.into_iter().rev(); - match it.next() { - None => Univ::zero(), - Some(last) => it.fold(last, |acc, t| Univ::max(t, acc)), - } -} - -/// The canonical representative of `u`'s Géran class. -pub fn canon_univ(u: &Arc) -> Arc { - linearize(&normalize(u)) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::univ::tests::arbitrary_univ; - use quickcheck::{Arbitrary, Gen}; - use quickcheck_macros::quickcheck; - - #[derive(Clone, Debug)] - struct AU(Arc); - - impl Arbitrary for AU { - fn arbitrary(g: &mut Gen) -> Self { - AU(arbitrary_univ(g)) - } - } - - fn v(i: u64) -> Arc { - Univ::var(i) - } - fn s(u: Arc) -> Arc { - Univ::succ(u) - } - fn z() -> Arc { - Univ::zero() - } - fn m(a: Arc, b: Arc) -> Arc { - Univ::max(a, b) - } - fn im(a: Arc, b: Arc) -> Arc { - Univ::imax(a, b) - } - - // ---- fixed vectors: one per mk* rule ---- - - #[test] - fn reduce_univ_rule_table() { - // M1 numerals → larger. - assert_eq!(reduce_univ(&m(s(z()), s(s(z())))), s(s(z()))); - // M2 max a a = a. - assert_eq!(reduce_univ(&m(v(0), v(0))), v(0)); - // M3 / M4 zero sides. - assert_eq!(reduce_univ(&m(z(), v(0))), v(0)); - assert_eq!(reduce_univ(&m(v(0), z())), v(0)); - // M5 absorption into b. - assert_eq!(reduce_univ(&m(v(0), m(v(0), v(1)))), m(v(0), v(1))); - // M6 absorption into a. - assert_eq!(reduce_univ(&m(m(v(0), v(1)), v(1))), m(v(0), v(1))); - // M7 same-base offsets. - assert_eq!(reduce_univ(&m(s(v(0)), s(s(v(0))))), s(s(v(0)))); - // M8 raw survives. - assert_eq!(reduce_univ(&m(v(0), v(1))), m(v(0), v(1))); - // I1 never-zero right → max. - assert_eq!(reduce_univ(&im(v(0), s(v(1)))), m(v(0), s(v(1)))); - // I2 imax a 0 = 0. - assert_eq!(reduce_univ(&im(v(0), z())), z()); - // I3 imax 0 b = b. - assert_eq!(reduce_univ(&im(z(), v(0))), v(0)); - // I4 imax 1 b = b. - assert_eq!(reduce_univ(&im(s(z()), v(0))), v(0)); - // I5 imax a a = a. - assert_eq!(reduce_univ(&im(v(0), v(0))), v(0)); - // I6 raw survives. - assert_eq!(reduce_univ(&im(v(0), v(1))), im(v(0), v(1))); - // The Mathlib WF eq_def shape: imax (imax 1 u) u → u. - assert_eq!(reduce_univ(&im(im(s(z()), v(0)), v(0))), v(0)); - } - - // ---- fixed vectors: canonical representatives ---- - - #[test] - fn canon_fixpoints() { - // Common source spellings are their own representatives. - for u in [ - z(), - s(z()), - v(0), - s(v(0)), - s(s(v(3))), - m(v(0), v(1)), - m(s(v(0)), s(v(1))), - im(v(0), v(1)), - im(s(v(1)), v(0)), - im(im(s(v(0)), v(1)), v(2)), - m(v(0), m(v(1), v(2))), - ] { - assert_eq!(canon_univ(&u), u, "expected fixpoint: {u:?}"); - } - } - - #[test] - fn canon_collapses_twins() { - // Commutative order twins. - assert_eq!(canon_univ(&m(v(1), v(0))), m(v(0), v(1))); - // Reassociation. - assert_eq!(canon_univ(&m(m(v(0), v(1)), v(2))), m(v(0), m(v(1), v(2)))); - // Succ distribution. - assert_eq!(canon_univ(&s(m(v(0), v(1)))), m(s(v(0)), s(v(1)))); - // mk*-reducible spellings land on the reduced class. - assert_eq!(canon_univ(&im(im(s(z()), v(0)), v(0))), v(0)); - assert_eq!(canon_univ(&m(v(0), v(0))), v(0)); - // Redundant numeral absorption. - assert_eq!( - canon_univ(&m(m(s(v(0)), s(v(1))), s(z()))), - m(s(v(0)), s(v(1))) - ); - } - - // ---- properties ---- - // `#[quickcheck]` requires by-value `Arbitrary` arguments; the - // needless_pass_by_value lint doesn't see through the macro. - - #[quickcheck] - #[allow(clippy::needless_pass_by_value)] - fn p1_idempotent(u: AU) -> bool { - let c = canon_univ(&u.0); - canon_univ(&c) == c - } - - #[quickcheck] - #[allow(clippy::needless_pass_by_value)] - fn p2_roundtrip_fixpoint(u: AU) -> bool { - let n = normalize(&u.0); - norm_eq_semantic(&normalize(&linearize(&n)), &n) - } - - #[quickcheck] - #[allow(clippy::needless_pass_by_value)] - fn p3_mk_fixpoint(u: AU) -> bool { - let c = canon_univ(&u.0); - reduce_univ(&c) == c - } - - #[quickcheck] - #[allow(clippy::needless_pass_by_value)] - fn p6_mk_absorption(u: AU) -> bool { - canon_univ(&reduce_univ(&u.0)) == canon_univ(&u.0) - } - - /// The linearizer picks a representative of the SEMANTIC class: its - /// canonical form matches the input's modulo empty entries. - #[quickcheck] - #[allow(clippy::needless_pass_by_value)] - fn canon_stays_in_class(u: AU) -> bool { - norm_eq_semantic(&normalize(&canon_univ(&u.0)), &normalize(&u.0)) - } - - // ---- P0: value preservation ---- - - /// The value of a level at a valuation of its params (`u_i ↦ vals[i]`). - fn eval(u: &Univ, vals: &[u64]) -> u64 { - match u { - Univ::Zero => 0, - Univ::Succ(i) => eval(i, vals) + 1, - Univ::Max(a, b) => eval(a, vals).max(eval(b, vals)), - Univ::IMax(a, b) => { - let vb = eval(b, vals); - if vb == 0 { 0 } else { eval(a, vals).max(vb) } - }, - Univ::Var(i) => vals[usize::try_from(*i).unwrap()], - } - } - - /// The largest `succ` nesting. - fn max_offset(u: &Univ) -> u64 { - match u { - Univ::Zero | Univ::Var(_) => 0, - Univ::Succ(i) => max_offset(i) + 1, - Univ::Max(a, b) | Univ::IMax(a, b) => max_offset(a).max(max_offset(b)), - } - } - - /// The first valuation of params `0..params` at which `a` and `b` - /// differ, over values `{0, 1, 2, M-1, M}` with `M` four above the - /// largest offset. Exact: by Géran's decomposition, a sublevel of one - /// side not dominated by a single sublevel of the other is exposed by - /// setting its condition params to 1, its variable to `M` and every - /// other param to 0, so two levels that differ anywhere differ here. - fn differ_at(a: &Univ, b: &Univ, params: usize) -> Option> { - let m = max_offset(a).max(max_offset(b)) + 4; - let values = [0, 1, 2, m - 1, m]; - let mut vals = vec![0u64; params]; - let total = values.len().pow(u32::try_from(params).unwrap()); - for code in 0..total { - let mut c = code; - for x in &mut vals { - *x = values[c % values.len()]; - c /= values.len(); - } - if eval(a, &vals) != eval(b, &vals) { - return Some(vals); - } - } - None - } - - /// Does `subsumption` leave a sublevel that a single other sublevel - /// dominates? A constant `c@P` is dominated by a constant `≥ c` at a - /// strict sub-path, or by an atom `(y, k)` with `k + 1 ≥ c` at a - /// sub-path; an atom `(x, k)@P` by an atom `(x, ≥ k)` at a strict - /// sub-path. `subsumption` mirrors the kernels' normalizers, which - /// test a constant against the vars of its own node instead of the - /// dominator's (for example `max (v+1) (imax (imax 2 u) v)` - /// keeps the constant `2` at `[u, v]`, dominated by `v + 1`). Only - /// such leftovers make two equal levels' normal forms differ, so a - /// slip-free normal form is the unique one of its class. - fn has_slip(n: &NormLevel) -> bool { - n.iter().any(|(p, node)| { - let c = node.constant; - let const_dominated = c > 0 - && n.iter().any(|(q, m)| { - is_subset(q, p) - && ((q.len() < p.len() && m.constant >= c) - || m.vars.iter().any(|(_, k)| k + 1 >= c)) - }); - let var_dominated = node.vars.iter().any(|(x, k)| { - n.iter().any(|(q, m)| { - q.len() < p.len() - && is_subset(q, p) - && m.vars.iter().any(|(y, k2)| y == x && k2 >= k) - }) - }); - const_dominated || var_dominated - }) - } - - /// P0–P3, P6 and class stability of one level over params - /// `0..params`; failures are appended to `out`. P0 and P3 are checked - /// unconditionally. With `strict`, so are the rest; otherwise P1, P2 - /// and class stability are checked when neither `u`'s nor its - /// canonical form's normal form has a subsumption leftover - /// ([`has_slip`]), and P6 when neither `u`'s nor `reduce_univ u`'s - /// has; the skipped draws are counted in `slips`. - fn check_canon( - u: &Arc, - params: usize, - strict: bool, - out: &mut Vec, - slips: &mut usize, - ) { - let n = normalize(u); - let c = canon_univ(u); - if let Some(vals) = differ_at(u, &c, params) { - out.push(format!( - "P0 {u:?} → {c:?}: {} vs {} at {vals:?}", - eval(u, &vals), - eval(&c, &vals) - )); - } - if reduce_univ(&c) != c { - out.push(format!("P3 {u:?} → {c:?} (reduces to {:?})", reduce_univ(&c))); - } - let nc = normalize(&c); - if strict || !(has_slip(&n) || has_slip(&nc)) { - if canon_univ(&c) != c { - out.push(format!("P1 {u:?} → {c:?} → {:?}", canon_univ(&c))); - } - if !norm_eq_semantic(&normalize(&linearize(&n)), &n) { - out.push(format!("P2 {u:?} → {c:?}")); - } - if !norm_eq_semantic(&nc, &n) { - out.push(format!("CLASS {u:?} → {c:?}")); - } - } else { - *slips += 1; - } - let r = reduce_univ(u); - if (strict || !(has_slip(&n) || has_slip(&normalize(&r)))) - && canon_univ(&r) != c - { - out.push(format!("P6 {u:?} → {c:?}")); - } - } - - /// `imax (a (n-1)) (imax (a (n-2)) (… (imax (a 0) last)))`: one `imax` - /// per binder, the shape Lean's `Meta.getLevel` returns for a long `∀`. - fn imax_chain( - a: &dyn Fn(usize) -> Arc, - last: Arc, - n: usize, - ) -> Arc { - (0..n).fold(last, |acc, i| im(a(i), acc)) - } - - /// FU item 10 (the Lean mirror tests the same chains): the `imax` - /// distributions repeat the normalizer's calls (`2ⁿ` on these chains); - /// `Seen` skips the repeats, so chains of 64 finish, keep their value and - /// are idempotent. - #[test] - fn long_imax_chains() { - let cutsat = |n| imax_chain(&|_| m(s(z()), v(0)), v(0), n); - let mixed = |n: usize| { - imax_chain( - &|i| match i % 3 { - 0 => m(s(z()), v(0)), - 1 => s(v(1)), - _ => im(v(1), v(0)), - }, - v(if n.is_multiple_of(2) { 0 } else { 1 }), - n, - ) - }; - assert_eq!(canon_univ(&cutsat(64)), v(0)); - for n in [0, 1, 2, 5, 10, 24, 40, 64] { - for l in [cutsat(n), mixed(n)] { - let c = canon_univ(&l); - assert_eq!(differ_at(&l, &c, 2), None, "P0 n={n}: {c:?}"); - assert_eq!(canon_univ(&c), c, "P1 n={n}"); - assert_eq!(reduce_univ(&c), c, "P3 n={n}"); - } - } - } - - /// The smallest level found whose canonical form changes its value - /// without the leak-free proviso (`imax (imax (imax u w + 1) u) v`): - /// the self-strip of `u + 1` from `[u, v, w]` to `[v, w]` would leave - /// gate `w` without an absorber, leaking `w` at `u = 0`. - #[test] - fn p0_witness() { - let (u, vv, w) = (v(0), v(1), v(2)); - let l = im(im(s(im(u.clone(), w.clone())), u.clone()), vv.clone()); - let c = canon_univ(&l); - assert_eq!(eval(&l, &[0, 1, 2]), 1); - assert_eq!(eval(&c, &[0, 1, 2]), 1, "canon {c:?}"); - // The representative itself, pinned (the Lean mirror pins the same). - assert_eq!( - c, - m( - im(im(s(w.clone()), u.clone()), vv.clone()), - im(im(im(s(u.clone()), w.clone()), u.clone()), vv.clone()) - ) - ); - let (mut failures, mut slips) = (Vec::new(), 0); - for x in witness_family() { - check_canon(&x, 4, true, &mut failures, &mut slips); - } - assert!(failures.is_empty(), "{}", failures.join("\n")); - } - - /// The witness under every renaming of its params (and a fourth), - /// with deeper offsets, under `max 1`, `succ` and a further gate, - /// plus one other shrunk counterexample of that linearization. - fn witness_family() -> Vec> { - let mut out = Vec::new(); - let perms: [[u64; 3]; 8] = [ - [0, 1, 2], - [0, 2, 1], - [1, 0, 2], - [1, 2, 0], - [2, 0, 1], - [2, 1, 0], - [3, 1, 0], - [0, 3, 2], - ]; - for [a, b, c] in perms { - let (u, vv, w) = (v(a), v(b), v(c)); - for k in 1..=3 { - let inner = succs(im(u.clone(), w.clone()), k); - let l = im(im(inner, u.clone()), vv.clone()); - out.push(l.clone()); - out.push(m(s(z()), l.clone())); - out.push(s(l.clone())); - out.push(im(l.clone(), w.clone())); - out.push(m(l.clone(), im(vv.clone(), u.clone()))); - } - } - // `imax(imax(imax(imax(0,u0),u2)+1,u0),u1)+1` (canon-shrink kind 0). - out.push(s(im(im(s(im(im(z(), v(0)), v(2))), v(0)), v(1)))); - out - } - - /// The pseudo-random generator of the kernel level comparison - /// (`Tests.Ix.Kernel.LevelComparison.biasedLevel`): levels biased towards `imax` by a parameter, - /// offsets and `max`. Mirrored by `Tests.Gen.Ixon.biasedUniv`. - fn lcg_next(seed: u64) -> u64 { - seed - .wrapping_mul(6_364_136_223_846_793_005) - .wrapping_add(1_442_695_040_888_963_407) - } - - fn biased(params: u64, size: usize, seed: u64) -> (Arc, u64) { - let seed = lcg_next(seed); - let pick = (seed >> 33) % 10; - let p = v((seed >> 40) % params); - if size <= 1 || pick < 2 { - (if pick == 0 { z() } else { p }, seed) - } else if pick < 4 { - let (a, seed) = biased(params, size - 1, seed); - (s(a), seed) - } else if pick < 6 { - let (a, seed) = biased(params, size / 2, seed); - let (b, seed) = biased(params, size / 2, seed); - (m(a, b), seed) - } else if pick < 9 { - let (a, seed) = biased(params, size - 1, seed); - (im(a, p), seed) - } else { - let (a, seed) = biased(params, size / 2, seed); - let (b, seed) = biased(params, size / 2, seed); - (im(a, b), seed) - } - } - - /// P0–P3/P6/class over biased random levels and the levels type - /// inference builds from them (`max 1 a`, `max a b`, `imax a b`, - /// `succ a`): 20,000 draws over three params at size 10 (the - /// differential's family; before the gate check, 109 of its levels and - /// 2,128 of all 125,000 changed value) and 5,000 over four at size 12. - /// P1/P2/class/P6 are conditional on slip-free normal forms - /// ([`check_canon`]): 239 of the 125,000 draws have a normal form - /// with a subsumption leftover, and 173 of those are not idempotent. - #[test] - fn p0_biased_random() { - let (mut failures, mut slips) = (Vec::new(), 0); - let mut total = 0; - for (params, size, count, mut seed) in - [(3u64, 10usize, 20_000usize, 41u64), (4, 12, 5_000, 43)] - { - let count = if cfg!(debug_assertions) { count / 10 } else { count }; - let ps = usize::try_from(params).unwrap(); - for _ in 0..count { - let (a, s1) = biased(params, size, seed); - let (b, s2) = biased(params, size, s1); - seed = s2; - for x in [ - a.clone(), - m(s(z()), a.clone()), - m(a.clone(), b.clone()), - im(a.clone(), b.clone()), - s(a.clone()), - ] { - total += 1; - check_canon(&x, ps, false, &mut failures, &mut slips); - } - } - } - assert!( - failures.is_empty(), - "{}", - failures[..failures.len().min(12)].join("\n") - ); - // The slip is rare; a jump here means the normal forms changed. - assert!(slips * 200 < total, "{slips} of {total} draws hit the slip"); - } - - /// Exhaustive enumeration of every level term up to `size` constructor - /// nodes over three params — deterministic minimal counterexamples - /// where quickcheck only reports failure. - fn enumerate(size: usize) -> Vec> { - let mut by_size: Vec>> = vec![Vec::new(); size + 1]; - if size >= 1 { - by_size[1] = vec![z(), v(0), v(1), v(2)]; - } - for n in 2..=size { - let mut out: Vec> = Vec::new(); - for u in &by_size[n - 1] { - out.push(s(u.clone())); - } - for k in 1..n - 1 { - for a in &by_size[k].clone() { - for b in &by_size[n - 1 - k] { - out.push(m(a.clone(), b.clone())); - out.push(im(a.clone(), b.clone())); - } - } - } - by_size[n] = out; - } - by_size.into_iter().flatten().collect() - } - - /// Every term of up to 8 nodes in release (112,000 terms; the witness - /// has 8), 6 in debug. - #[test] - fn exhaustive_small_terms() { - let (mut failures, mut slips) = (Vec::new(), 0); - for u in enumerate(if cfg!(debug_assertions) { 6 } else { 8 }) { - check_canon(&u, 3, true, &mut failures, &mut slips); - if failures.len() >= 12 { - break; - } - } - assert!(failures.is_empty(), "{}", failures.join("\n")); - } -} diff --git a/crates/ixon/src/contract.rs b/crates/ixon/src/contract.rs index 58b453f67..392fa3b0d 100644 --- a/crates/ixon/src/contract.rs +++ b/crates/ixon/src/contract.rs @@ -3,8 +3,6 @@ use crate::expr::{Owned, Uses}; use crate::serialize::get_u8; -pub mod source; - #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] #[repr(u8)] pub enum Locality { diff --git a/crates/ixon/src/contract/source.rs b/crates/ixon/src/contract/source.rs deleted file mode 100644 index f96e51640..000000000 --- a/crates/ixon/src/contract/source.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! Version-3 semantic source frames shared by compiler and host adapters. -//! -//! These frames describe source binders. Optional Ixon metadata is not an -//! authority for contracts; Ixon consumers must read the scalar payload. - -use crate::CompileError; -use crate::contract::{BinderContract, LetKind, ValueContract}; -use crate::expr::Expr; -use bignat::Nat; -use ix_common::env::{DataValue, Expr as Source, ExprData, Name, NameData}; -use std::sync::Arc; - -fn error(desc: &str) -> CompileError { - CompileError::UnsupportedExpr { desc: desc.into() } -} - -fn root(name: &Name) -> bool { - matches!(name.as_data(), NameData::Str(parent, text, _) if text == "contract" && - matches!(parent.as_data(), NameData::Str(parent, text, _) if text == "ix" && - matches!(parent.as_data(), NameData::Anonymous(_)))) -} - -pub fn reserved(mut name: &Name) -> bool { - loop { - if root(name) { - return true; - } - match name.as_data() { - NameData::Str(parent, ..) | NameData::Num(parent, ..) => name = parent, - NameData::Anonymous(_) => return false, - } - } -} - -pub fn has_metadata(data: &[(Name, DataValue)]) -> bool { - data.iter().any(|(name, _)| reserved(name)) -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Kind { - Lam, - All, - Let, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct Contract { - pub kind: Kind, - pub binder: BinderContract, - pub result: ValueContract, - pub let_kind: LetKind, -} - -impl Contract { - pub fn is_default(self) -> bool { - self.binder == BinderContract::default() - && self.result == ValueContract::shared() - && self.let_kind == LetKind::Value - } - - pub fn kind_code(self) -> u64 { - match self.kind { - Kind::Lam => 0, - Kind::All => 1, - Kind::Let => 2, - } - } - pub fn code(self) -> u8 { - let low = self.binder.to_bits(); - match self.kind { - Kind::Lam => low, - Kind::All => low | (self.result.to_bits() << 4), - Kind::Let => { - low | if self.let_kind == LetKind::BorrowShared { 16 } else { 0 } - }, - } - } - pub fn order_key(self) -> u64 { - self.kind_code() * 64 + u64::from(self.code()) - } - pub fn metadata(self) -> Vec<(Name, DataValue)> { - let key = - Name::str(Name::str(Name::anon(), "ix".into()), "contract".into()); - // Lean's KVMap.set appends a new field. - vec![ - (key.clone(), DataValue::OfNat(Nat::from(3u64))), - ( - Name::str(key.clone(), "kind".into()), - DataValue::OfNat(Nat::from(self.kind_code())), - ), - ( - Name::str(key, "code".into()), - DataValue::OfNat(Nat::from(u64::from(self.code()))), - ), - ] - } - pub fn attach(self, source: Source) -> Source { - Source::mdata(self.metadata(), source) - } - pub fn lower( - self, - source: &Source, - compiled: &Arc, - ) -> Result, CompileError> { - Ok(Arc::new(match (self.kind, source.as_data(), compiled.as_ref()) { - (Kind::Lam, ExprData::Lam(..), Expr::Lam(_, typ, body)) => { - Expr::Lam(self.binder, typ.clone(), body.clone()) - }, - (Kind::All, ExprData::ForallE(..), Expr::All(_, _, typ, body)) => { - Expr::All(self.binder, self.result, typ.clone(), body.clone()) - }, - (Kind::Let, ExprData::LetE(..), Expr::Let(c, typ, value, body)) => { - Expr::Let( - crate::contract::LetContract { - binder: self.binder, - kind: self.let_kind, - ..*c - }, - typ.clone(), - value.clone(), - body.clone(), - ) - }, - _ => { - return Err(error( - "semantic contract is not attached to its original binder", - )); - }, - })) - } -} - -pub fn decode(kind: u64, code: u64) -> Result { - let kind = match kind { - 0 => Kind::Lam, - 1 => Kind::All, - 2 => Kind::Let, - _ => return Err(error("invalid semantic contract kind")), - }; - let limit = match kind { - Kind::Lam => 16, - Kind::All => 64, - Kind::Let => 32, - }; - if code >= limit { - return Err(error("reserved semantic contract bits")); - } - let code = u8::try_from(code) - .map_err(|_error| error("semantic contract code overflow"))?; - let binder = BinderContract::from_bits(code % 16) - .ok_or_else(|| error("invalid semantic binder contract"))?; - let result = if kind == Kind::All { - ValueContract::from_bits(code / 16) - .ok_or_else(|| error("invalid semantic result contract"))? - } else { - ValueContract::shared() - }; - let let_kind = if kind == Kind::Let && code >= 16 { - LetKind::BorrowShared - } else { - LetKind::Value - }; - if let_kind == LetKind::BorrowShared - && binder.value != ValueContract::local_shared() - { - return Err(error("shared borrow requires a shared local view")); - } - Ok(Contract { kind, binder, result, let_kind }) -} - -pub fn read(data: &[(Name, DataValue)]) -> Result { - if data.len() != 3 { - return Err(error("malformed semantic contract frame")); - } - let (mut version, mut kind, mut code) = (None, None, None); - for (name, value) in data { - let DataValue::OfNat(value) = value else { - return Err(error("semantic contract fields must be natural numbers")); - }; - let value = value - .to_u64() - .ok_or_else(|| error("semantic contract field is too large"))?; - let slot = if root(name) { - &mut version - } else { - match name.as_data() { - NameData::Str(parent, text, _) if root(parent) && text == "kind" => { - &mut kind - }, - NameData::Str(parent, text, _) if root(parent) && text == "code" => { - &mut code - }, - _ => return Err(error("unknown or duplicate semantic contract field")), - } - }; - if slot.replace(value).is_some() { - return Err(error("unknown or duplicate semantic contract field")); - } - } - if version != Some(3) { - return Err(error("unsupported semantic contract version")); - } - decode( - kind.ok_or_else(|| error("missing semantic contract kind"))?, - code.ok_or_else(|| error("missing semantic contract code"))?, - ) -} - -/// A source frame must be attached directly to its matching binder. -pub fn read_attached( - data: &[(Name, DataValue)], - node: &Source, -) -> Result { - let contract = read(data)?; - if !matches!( - (contract.kind, node.as_data()), - (Kind::Lam, ExprData::Lam(..)) - | (Kind::All, ExprData::ForallE(..)) - | (Kind::Let, ExprData::LetE(..)) - ) { - return Err(error( - "semantic contract is not attached to its original binder", - )); - } - Ok(contract) -} - -/// Validate a peeled direct-source metadata chain without treating unrelated -/// display layers as contracts. A semantic frame must be the innermost layer. -pub fn source_layers( - node: &Source, - layers: &[Vec<(Name, DataValue)>], -) -> Result, CompileError> { - let mut contract = None; - for (i, layer) in layers.iter().enumerate() { - if has_metadata(layer) { - if i + 1 != layers.len() { - return Err(error( - "semantic contract is not attached to its original binder", - )); - } - contract = Some(read_attached(layer, node)?); - } - } - Ok(contract) -} - -/// Reconstruct source frames from authoritative kernel payloads. Existing -/// direct-source spelling is retained only when it agrees exactly; it never -/// overrides the payload. Optional Ixon metadata is refused before ingress. -pub fn egress_layers( - actual: Option, - layers: &[Vec<(Name, DataValue)>], -) -> Result>, CompileError> { - if layers.is_empty() && actual.is_none_or(Contract::is_default) { - return Ok(Vec::new()); - } - if let Some(actual) = actual - && decode(actual.kind_code(), u64::from(actual.code()))? != actual - { - return Err(error("noncanonical semantic contract payload")); - } - let mut supplied = false; - for (i, layer) in layers.iter().enumerate() { - if has_metadata(layer) { - if i + 1 != layers.len() || Some(read(layer)?) != actual { - return Err(error( - "semantic contract metadata conflicts with kernel payload", - )); - } - supplied = true; - } - } - let mut out = layers.to_vec(); - if !supplied - && let Some(actual) = actual - && !actual.is_default() - { - out.push(actual.metadata()); - } - Ok(out) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::expr::Uses; - - fn key(suffix: Option<&str>) -> Name { - let root = - Name::str(Name::str(Name::anon(), "ix".into()), "contract".into()); - suffix.map_or(root.clone(), |s| Name::str(root, s.into())) - } - - #[test] - fn version_three_spelling_and_independent_fields() { - let contract = Contract { - kind: Kind::All, - binder: BinderContract { - uses: Uses::Linear, - value: ValueContract::local_unique(), - }, - result: ValueContract::local_shared(), - let_kind: LetKind::Value, - }; - let expected = vec![ - (key(None), DataValue::OfNat(Nat::from(3u64))), - (key(Some("kind")), DataValue::OfNat(Nat::from(1u64))), - (key(Some("code")), DataValue::OfNat(Nat::from(57u64))), - ]; - assert_eq!(contract.metadata(), expected); - assert_eq!(read(&expected).unwrap(), contract); - let reversed = expected.into_iter().rev().collect::>(); - assert_eq!(read(&reversed).unwrap(), contract); - } - - #[test] - fn version_and_duplicate_errors_keep_the_existing_boundary() { - let mut frame = vec![ - (key(None), DataValue::OfNat(Nat::from(2u64))), - (key(Some("kind")), DataValue::OfNat(Nat::from(0u64))), - (key(Some("code")), DataValue::OfNat(Nat::from(7u64))), - ]; - let message = |result: Result| match result { - Err(CompileError::UnsupportedExpr { desc }) => desc, - other => panic!("unexpected protocol result: {other:?}"), - }; - assert_eq!(message(read(&frame)), "unsupported semantic contract version"); - frame[0] = frame[1].clone(); - assert_eq!( - message(read(&frame)), - "unknown or duplicate semantic contract field" - ); - } -} diff --git a/crates/ixon/src/diff.rs b/crates/ixon/src/diff.rs index bd06de1c9..20d4a3433 100644 --- a/crates/ixon/src/diff.rs +++ b/crates/ixon/src/diff.rs @@ -2633,328 +2633,4 @@ mod tests { } } } - - // ========================================================================== - // Universe-level canonicalization probe (canonicity §10.6 / §17.9, D8) - // ========================================================================== - // - // Prototype `canonUniv` = draft linearizer ∘ subsumption ∘ normalizeAux - // (the Géran machinery ported from `Ix/Tc/Level.lean:227-472` / - // `crates/kernel/src/level.rs`) plus the frozen kernel `mk*` reducer, - // both transliterated onto `ixon::univ::Univ`. Promoted to the real - // implementation in stage 2 (the linearizer gating construction is the - // O1 open detail — P2 violations are counted below as its sanity - // signal, not asserted). - - /// Local shim for the census probe: the real machinery lives in - /// `crate::canon_univ` (promoted from the probe prototype). - mod univ_probe { - - use crate::univ::Univ; - - pub(super) use crate::canon_univ::{ - NormLevel as PNorm, linearize, normalize, reduce_univ, - }; - - pub(super) fn geran(u: &Univ) -> PNorm { - normalize(u) - } - - pub(super) fn univ_size(u: &Univ) -> u64 { - match u { - Univ::Zero | Univ::Var(_) => 1, - Univ::Succ(i) => 1 + univ_size(i), - Univ::Max(a, b) | Univ::IMax(a, b) => 1 + univ_size(a) + univ_size(b), - } - } - } - - /// Universe canonicalization blast-radius probe (canonicity §17.9, D8). - /// Per §2 constant: both censuses over the univ table (mk*-reducible - /// vs Géran-noncanonical), the per-constant collision census (two - /// spellings of one class — the Design-A killer), P2 sanity of the - /// draft linearizer, occurrence counts over the unshared expression - /// trees, refs-reverse dependent closure, pinned-primitive impact, and - /// canonical-size blowup stats. Run: - /// IXE_A= cargo test -p ixon --release \ - /// dump_reducible_univs -- --ignored --nocapture - #[test] - #[ignore] - fn dump_reducible_univs() { - use std::collections::{HashMap, HashSet, VecDeque}; - - use univ_probe::*; - - let Ok(path) = std::env::var("IXE_A") else { - eprintln!("[dump_reducible_univs] IXE_A unset — manual probe, skipping"); - return; - }; - let sample_count: u64 = std::env::var("IXE_SAMPLES") - .ok() - .and_then(|s| s.parse().ok()) - .unwrap_or(0); - let bytes = std::fs::read(&path).expect("read env file"); - let index = Env::parse_lazy_index(&bytes).expect("lazy index"); - println!("===== {path}: {} §2 constants", index.consts.len()); - - let mut total_entries = 0u64; - let mut mk_reducible_entries = 0u64; - let mut geran_noncanon_entries = 0u64; - let mut mk_consts: HashSet
= HashSet::new(); - let mut geran_consts: HashSet
= HashSet::new(); - let mut collision_consts = 0u64; - let mut p2_violations = 0u64; - let mut max_blowup: (u64, u64) = (0, 0); // (src, canon) of worst ratio - let mut sum_src = 0u64; - let mut sum_canon = 0u64; - // Occurrences (unshared logical tree) referencing affected entries. - let mut mk_occurrences = 0u64; - let mut geran_occurrences = 0u64; - let mut geran_patch_bytes = 0u64; - // Forward edges: dependency addr -> dependents. - let mut dependents: HashMap> = HashMap::new(); - - // Walk the share-deduped expression DAG counting sort/ref/recur - // occurrence nodes whose level list touches `affected`. Each share - // subtree contributes ONCE — repeated identical subtrees share one - // compile-side arena root (the compile expr cache is - // spelling-injective), so DAG-distinct occurrences approximate the - // per-arena-root patch/decoration population; the unshared logical - // tree would be exponentially larger and does not correspond to - // stored metadata. - fn count_occ( - e: &Expr, - sharing: &[Arc], - affected: &HashSet, - seen_shares: &mut HashSet, - ) -> (u64, u64) { - use Expr as E; - match e { - E::Var(_) | E::Nat(_) | E::Str(_) => (0, 0), - E::Sort(u) => { - if affected.contains(u) { - (1, 1) - } else { - (0, 0) - } - }, - E::Ref(_, us) | E::Rec(_, us) => { - if us.iter().any(|u| affected.contains(u)) { - (1, us.len() as u64) - } else { - (0, 0) - } - }, - E::App(f, a) => { - let (o1, s1) = count_occ(f, sharing, affected, seen_shares); - let (o2, s2) = count_occ(a, sharing, affected, seen_shares); - (o1 + o2, s1 + s2) - }, - E::Lam(_, t, b) | E::All(_, _, t, b) => { - let (o1, s1) = count_occ(t, sharing, affected, seen_shares); - let (o2, s2) = count_occ(b, sharing, affected, seen_shares); - (o1 + o2, s1 + s2) - }, - E::Let(_, t, v, b) => { - let (o1, s1) = count_occ(t, sharing, affected, seen_shares); - let (o2, s2) = count_occ(v, sharing, affected, seen_shares); - let (o3, s3) = count_occ(b, sharing, affected, seen_shares); - (o1 + o2 + o3, s1 + s2 + s3) - }, - E::Prj(_, _, v) => count_occ(v, sharing, affected, seen_shares), - E::Share(i) => { - if !seen_shares.insert(*i) { - return (0, 0); - } - match usize::try_from(*i).ok().and_then(|i| sharing.get(i)) { - Some(sub) => count_occ(sub, sharing, affected, seen_shares), - None => (0, 0), - } - }, - } - } - - for slice in &index.consts { - let window = &bytes[slice.offset..slice.offset + slice.len]; - let cnst = Constant::get(&mut &window[..]) - .unwrap_or_else(|e| panic!("parse {}: {e}", slice.addr.hex())); - for r in &cnst.refs { - dependents.entry(r.clone()).or_default().push(slice.addr.clone()); - } - if cnst.univs.is_empty() { - continue; - } - total_entries += cnst.univs.len() as u64; - let mut mk_idxs: HashSet = HashSet::new(); - let mut geran_idxs: HashSet = HashSet::new(); - let mut classes: HashMap>> = HashMap::new(); - for (i, u) in cnst.univs.iter().enumerate() { - let reduced = reduce_univ(u); - if reduced != *u { - mk_reducible_entries += 1; - mk_idxs.insert(i as u64); - } - let norm = geran(u); - let canon = linearize(&norm); - sum_src += univ_size(u); - sum_canon += univ_size(&canon); - if canon != *u { - geran_noncanon_entries += 1; - geran_idxs.insert(i as u64); - // metaUnivs extension entry carrying the original spelling. - let mut spelling = Vec::new(); - crate::univ::put_univ(u, &mut spelling); - geran_patch_bytes += spelling.len() as u64; - if geran_noncanon_entries <= sample_count { - println!(" sample: {u:?} => {canon:?}"); - } - } - let (s, c) = (univ_size(u), univ_size(&canon)); - if c * max_blowup.0 > max_blowup.1 * s { - max_blowup = (s, c); - } - if geran(&canon) != norm { - p2_violations += 1; - if p2_violations <= 8 { - println!(" P2 violation: {u:?}"); - println!(" canon: {canon:?}"); - println!(" norm: {norm:?}"); - println!(" renorm: {:?}", geran(&canon)); - } - } - classes.entry(format!("{norm:?}")).or_default().insert(u); - } - if classes.values().any(|spellings| spellings.len() > 1) { - collision_consts += 1; - } - if !mk_idxs.is_empty() { - mk_consts.insert(slice.addr.clone()); - } - if !geran_idxs.is_empty() { - geran_consts.insert(slice.addr.clone()); - } - if mk_idxs.is_empty() && geran_idxs.is_empty() { - continue; - } - // Occurrence counts over the constant's expression bodies. - let mut exprs: Vec<&Arc> = Vec::new(); - { - use crate::constant::MutConst; - match &cnst.info { - ConstantInfo::Defn(d) => { - exprs.push(&d.typ); - exprs.push(&d.value); - }, - ConstantInfo::Axio(a) => exprs.push(&a.typ), - ConstantInfo::Quot(q) => exprs.push(&q.typ), - ConstantInfo::Recr(r) => { - exprs.push(&r.typ); - for rule in &r.rules { - exprs.push(&rule.rhs); - } - }, - ConstantInfo::Muts(ms) => { - for m in ms { - match m { - MutConst::Defn(d) => { - exprs.push(&d.typ); - exprs.push(&d.value); - }, - MutConst::Indc(ind) => { - exprs.push(&ind.typ); - for c in &ind.ctors { - exprs.push(&c.typ); - } - }, - MutConst::Recr(r) => { - exprs.push(&r.typ); - for rule in &r.rules { - exprs.push(&rule.rhs); - } - }, - } - } - }, - _ => {}, - } - } - let mut memo_mk: HashSet = HashSet::new(); - let mut memo_geran: HashSet = HashSet::new(); - for e in &exprs { - let (o, _) = count_occ(e, &cnst.sharing, &mk_idxs, &mut memo_mk); - mk_occurrences += o; - let (o, slots) = - count_occ(e, &cnst.sharing, &geran_idxs, &mut memo_geran); - geran_occurrences += o; - // UnivPatch wire estimate: TagN arenaIdx (~3B) + TagN len (1B) - // + TagN per index (~2B). - geran_patch_bytes += o * 4 + slots * 2; - } - } - - println!("univ entries: {total_entries}"); - println!( - "mk*-reducible: {mk_reducible_entries} entries in {} constants", - mk_consts.len() - ); - println!( - "Géran-noncanonical: {geran_noncanon_entries} entries in {} constants \ - (Δ vs mk*: {} entries)", - geran_consts.len(), - geran_noncanon_entries - mk_reducible_entries - ); - println!( - "collision constants (≥2 spellings of one class): {collision_consts}" - ); - println!( - "P2 violations (draft linearizer, informational): {p2_violations}" - ); - println!( - "occurrences — stage-1 decorations (mk*): {mk_occurrences}; \ - stage-2 patches (Géran): {geran_occurrences} \ - (~{geran_patch_bytes} patch+spelling bytes)" - ); - println!( - "size: src total {sum_src}, canonical total {sum_canon}; \ - worst blowup {}→{}", - max_blowup.0, max_blowup.1 - ); - - // Transitive dependent closure of the Géran-affected set. - let mut closure: HashSet
= geran_consts.clone(); - let mut queue: VecDeque
= geran_consts.iter().cloned().collect(); - while let Some(a) = queue.pop_front() { - if let Some(users) = dependents.get(&a) { - for u in users { - if closure.insert(u.clone()) { - queue.push_back(u.clone()); - } - } - } - } - println!( - "dependent closure (Géran set + transitive dependents): {} of {} \ - constants", - closure.len(), - index.consts.len() - ); - - // Pinned-primitive impact (§9.2): closure ∩ prim addresses. - let prim_hits: Vec<&'static str> = - ix_common::prim_addrs::PrimAddrs::lean_parity_table() - .into_iter() - .filter_map(|(name, hex)| { - closure.iter().any(|a| a.hex() == hex).then_some(name) - }) - .collect(); - if prim_hits.is_empty() { - println!("pinned-primitive closure impact: NONE"); - } else { - println!( - "pinned-primitive closure impact: {} prims — {:?}", - prim_hits.len(), - prim_hits - ); - } - } } diff --git a/crates/ixon/src/env.rs b/crates/ixon/src/env.rs index f7f4519d8..2f4c8c4ff 100644 --- a/crates/ixon/src/env.rs +++ b/crates/ixon/src/env.rs @@ -804,8 +804,8 @@ impl Env { /// Addresses of the named constants the kernel typechecker would /// iterate via `check_const` — every entry in `env.named` minus the /// `Muts` mutual-block pointers (which aren't standalone checkables). - /// Matches `crates/ffi/src/kernel.rs::all_checkable_ixon_names` but - /// returns addresses rather than names and skips the sort. + /// Sorted, and as names, these were the retired Rust check FFI's + /// `all_checkable_ixon_names`. pub fn checkable_addrs(&self) -> Vec
{ self .named @@ -1011,8 +1011,8 @@ impl Env { /// ingress to resolve through `named` ([`ConstantMeta::named_refs`]: /// `all`, `ctx`, `ctors`) are carried too, with their Named /// entries: a `mutual` definition's `all` names siblings its value - /// need not reach, and without them `ix check-rs` (meta) fails the - /// bundle with `resolve_all: Named entry … missing`. + /// need not reach, and without them a meta-mode check of the bundle + /// fails on the missing `Named` entry. /// /// Errors if a reached address is in neither `consts`, `blobs`, nor /// `assumed` — the source env cannot produce a closed bundle for diff --git a/crates/ixon/src/error.rs b/crates/ixon/src/error.rs deleted file mode 100644 index 7ddfbf7b5..000000000 --- a/crates/ixon/src/error.rs +++ /dev/null @@ -1,245 +0,0 @@ -//! Custom error types for Ixon serialization and compilation. - -use ix_common::address::Address; - -/// Errors during serialization/deserialization. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum SerializeError { - /// Unexpected end of buffer - UnexpectedEof { expected: String }, - /// Invalid tag byte - InvalidTag { tag: u8, context: String }, - /// Invalid flag in tag - InvalidFlag { flag: u8, context: String }, - /// Invalid variant discriminant - InvalidVariant { variant: u64, context: String }, - /// Invalid boolean value - InvalidBool { value: u8 }, - /// Address parsing error - AddressError, - /// Invalid Share index - InvalidShareIndex { idx: u64, max: usize }, -} - -impl std::fmt::Display for SerializeError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::UnexpectedEof { expected } => { - write!(f, "unexpected EOF, expected {expected}") - }, - Self::InvalidTag { tag, context } => { - write!(f, "invalid tag 0x{tag:02X} in {context}") - }, - Self::InvalidFlag { flag, context } => { - write!(f, "invalid flag {flag} in {context}") - }, - Self::InvalidVariant { variant, context } => { - write!(f, "invalid variant {variant} in {context}") - }, - Self::InvalidBool { value } => write!(f, "invalid bool value {value}"), - Self::AddressError => write!(f, "address parsing error"), - Self::InvalidShareIndex { idx, max } => { - write!(f, "invalid Share index {idx}, max is {max}") - }, - } - } -} - -impl std::error::Error for SerializeError {} - -/// Errors during compilation (Lean → Ixon). -/// -/// Variant order matches Lean constructor tags (0–7). -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum CompileError { - /// Referenced constant not found (tag 0). - /// `caller` identifies which compilation step triggered the lookup. - MissingConstant { name: String, caller: String }, - /// Address not found in store (tag 1) - MissingAddress(Address), - /// Invalid mutual block structure (tag 2) - InvalidMutualBlock { reason: String }, - /// Unsupported expression variant (tag 3) - UnsupportedExpr { desc: String }, - /// Unknown universe parameter (tag 4) - UnknownUnivParam { curr: String, param: String }, - /// Serialization error during compilation (tag 5) - Serialize(SerializeError), - /// Compilation could not stay within its resource budget (tag 6). - ResourceLimit { reason: String }, - /// The sharing construction failed other than by resource exhaustion, - /// which is `ResourceLimit` (tag 7). - SharingConstruction { reason: String }, -} - -impl std::fmt::Display for CompileError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::MissingConstant { name, caller } => { - write!(f, "missing constant: {name} (from {caller})") - }, - Self::MissingAddress(addr) => write!(f, "missing address: {addr:?}"), - Self::InvalidMutualBlock { reason } => { - write!(f, "invalid mutual block: {reason}") - }, - Self::UnsupportedExpr { desc } => { - write!(f, "unsupported expression: {desc}") - }, - Self::UnknownUnivParam { curr, param } => { - write!(f, "unknown universe parameter: compiling {curr}, param {param}") - }, - Self::Serialize(e) => write!(f, "serialization error: {e}"), - Self::ResourceLimit { reason } => write!(f, "resource limit: {reason}"), - Self::SharingConstruction { reason } => { - write!(f, "sharing construction: {reason}") - }, - } - } -} - -impl std::error::Error for CompileError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::Serialize(e) => Some(e), - _ => None, - } - } -} - -impl From for CompileError { - fn from(e: SerializeError) -> Self { - Self::Serialize(e) - } -} - -/// Errors during decompilation (Ixon → Lean). -/// -/// Variant order matches Lean constructor tags (0–11). -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum DecompileError { - /// Invalid Ref(idx) reference - refs table too small (tag 0) - InvalidRefIndex { idx: u64, refs_len: usize, constant: String }, - /// Invalid universe index - univs table too small (tag 1) - InvalidUnivIndex { idx: u64, univs_len: usize, constant: String }, - /// Invalid Share(idx) reference - sharing vector too small (tag 2) - InvalidShareIndex { idx: u64, max: usize, constant: String }, - /// Invalid Rec(idx) reference - mutual context doesn't have this index (tag 3) - InvalidRecIndex { idx: u64, ctx_size: usize, constant: String }, - /// Invalid Univ::Var(idx) reference - level names too small (tag 4) - InvalidUnivVarIndex { idx: u64, max: usize, constant: String }, - /// Address not found in store (tag 5) - MissingAddress(Address), - /// Metadata not found for address (tag 6) - MissingMetadata(Address), - /// Blob not found at address (tag 7) - BlobNotFound(Address), - /// Bad blob format at address (tag 8) - BadBlobFormat { addr: Address, expected: String }, - /// Bad constant format (tag 9) - BadConstantFormat { msg: String }, - /// Serialization error during decompilation (tag 10) - Serialize(SerializeError), - /// `Share(idx)` occurring in `meta_sharing[entry]` outside that entry's - /// index space (`ConstantMeta::meta_sharing`; tag 11): with `primary_len` - /// primary and `meta_len` metadata entries the valid indices are - /// `idx < primary_len + entry`. `idx >= primary_len + meta_len` is out of - /// range; any other rejected index is a forward or self reference. - InvalidMetaShareIndex { - idx: u64, - entry: u64, - primary_len: usize, - meta_len: usize, - constant: String, - }, -} - -impl std::fmt::Display for DecompileError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::InvalidRefIndex { idx, refs_len, constant } => { - write!( - f, - "invalid Ref({idx}) in '{constant}': refs table has {refs_len} entries" - ) - }, - Self::InvalidUnivIndex { idx, univs_len, constant } => { - write!( - f, - "invalid univ index {idx} in '{constant}': univs table has {univs_len} entries" - ) - }, - Self::InvalidShareIndex { idx, max, constant } => { - write!( - f, - "invalid Share({idx}) in '{constant}': sharing vector has {max} entries" - ) - }, - Self::InvalidRecIndex { idx, ctx_size, constant } => { - write!( - f, - "invalid Rec({idx}) in '{constant}': mutual context has {ctx_size} entries" - ) - }, - Self::InvalidUnivVarIndex { idx, max, constant } => { - write!( - f, - "invalid Univ::Var({idx}) in '{constant}': only {max} level params" - ) - }, - Self::MissingAddress(addr) => write!(f, "missing address: {addr:?}"), - Self::MissingMetadata(addr) => { - write!(f, "missing metadata for: {addr:?}") - }, - Self::BlobNotFound(addr) => write!(f, "blob not found at: {addr:?}"), - Self::BadBlobFormat { addr, expected } => { - write!(f, "bad blob format at {addr:?}, expected {expected}") - }, - Self::BadConstantFormat { msg } => { - write!(f, "bad constant format: {msg}") - }, - Self::Serialize(e) => write!(f, "serialization error: {e}"), - Self::InvalidMetaShareIndex { - idx, - entry, - primary_len, - meta_len, - constant, - } => { - let end = u64::try_from(primary_len.saturating_add(*meta_len)) - .unwrap_or(u64::MAX); - if *idx >= end { - write!( - f, - "invalid Share({idx}) in meta_sharing[{entry}] of '{constant}': \ - out of range ({primary_len} primary + {meta_len} metadata entries)" - ) - } else { - let bound = u64::try_from(*primary_len) - .unwrap_or(u64::MAX) - .saturating_add(*entry); - write!( - f, - "invalid Share({idx}) in meta_sharing[{entry}] of '{constant}': \ - forward or self reference (this entry may reference only \ - indices below {bound})" - ) - } - }, - } - } -} - -impl std::error::Error for DecompileError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::Serialize(e) => Some(e), - _ => None, - } - } -} - -impl From for DecompileError { - fn from(e: SerializeError) -> Self { - Self::Serialize(e) - } -} diff --git a/crates/ixon/src/expr.rs b/crates/ixon/src/expr.rs index c124f22e2..54330a4bc 100644 --- a/crates/ixon/src/expr.rs +++ b/crates/ixon/src/expr.rs @@ -219,36 +219,6 @@ impl Expr { } } - /// Rebuild a node without dropping any scalar or contract field. - pub fn with_children(&self, children: &[Arc]) -> Result { - if self.children().len() != children.len() { - return Err("expression reconstruction child count mismatch".into()); - } - Ok(match self { - Self::Sort(_) - | Self::Var(_) - | Self::Ref(..) - | Self::Rec(..) - | Self::Str(_) - | Self::Nat(_) - | Self::Share(_) => self.clone(), - Self::Prj(t, f, _) => Self::Prj(*t, *f, children[0].clone()), - Self::App(..) => Self::App(children[0].clone(), children[1].clone()), - Self::Lam(c, ..) => { - Self::Lam(*c, children[0].clone(), children[1].clone()) - }, - Self::All(c, v, ..) => { - Self::All(*c, *v, children[0].clone(), children[1].clone()) - }, - Self::Let(c, ..) => Self::Let( - *c, - children[0].clone(), - children[1].clone(), - children[2].clone(), - ), - }) - } - /// Count nested applications for telescope compression. pub fn app_telescope_count(&self) -> u64 { let mut count = 0u64; diff --git a/crates/ixon/src/lazy.rs b/crates/ixon/src/lazy.rs index 0ba087515..c1a075dd8 100644 --- a/crates/ixon/src/lazy.rs +++ b/crates/ixon/src/lazy.rs @@ -187,24 +187,6 @@ impl LazyConstant { } } - /// [`Self::from_constant`] (`cache` true) or - /// [`Self::from_constant_uncached`] (`cache` false) for a constant the - /// caller has already serialized: `bytes` must be `c`'s serialization, - /// which is not computed again. - pub fn from_serialized(c: Constant, bytes: Vec, cache: bool) -> Self { - debug_assert!({ - let mut buf = Vec::new(); - c.put(&mut buf); - buf == bytes - }); - LazyConstant { - bytes: BytesSource::Heap(bytes.into()), - cache: cache.then(|| Arc::new(c)), - pending_addr: None, - verified: Arc::new(AtomicBool::new(false)), - } - } - /// Like [`Self::from_constant`] but drops the structured value after /// serializing (`cache: None`), so the entry costs only its bytes and /// `get()` re-parses per access — the same policy as diff --git a/crates/ixon/src/lib.rs b/crates/ixon/src/lib.rs index ae7f27eda..72f56440a 100644 --- a/crates/ixon/src/lib.rs +++ b/crates/ixon/src/lib.rs @@ -9,7 +9,6 @@ pub mod assumption_tree; #[cfg(not(target_arch = "riscv64"))] mod binding_support; -pub mod canon_univ; pub mod catalog; pub mod comm; pub mod constant; @@ -17,7 +16,6 @@ pub mod contract; #[cfg(not(target_arch = "riscv64"))] pub mod diff; pub mod env; -pub mod error; pub mod expr; #[cfg(not(target_arch = "riscv64"))] pub mod host_admission; @@ -26,11 +24,9 @@ pub mod map; pub mod merkle; pub mod metadata; pub mod proof; -pub mod resource; pub mod role_table; pub mod serialize; pub mod shard_claim; -pub mod sharing_exact; pub mod syntax; pub mod tag; pub mod univ; @@ -53,7 +49,6 @@ pub use diff::{ diff_env_bytes, diff_envs, diff_envs_lazy, diff_envs_with, }; pub use env::{Env, Named}; -pub use error::{CompileError, DecompileError, SerializeError}; pub use expr::Expr; pub use lazy::LazyConstant; pub use metadata::{ @@ -499,3 +494,6 @@ mod doc_examples { ); } } + +#[cfg(not(target_arch = "riscv64"))] +pub mod shard; diff --git a/crates/ixon/src/map.rs b/crates/ixon/src/map.rs index 81c63d570..0b548badc 100644 --- a/crates/ixon/src/map.rs +++ b/crates/ixon/src/map.rs @@ -98,4 +98,13 @@ mod riscv_impl { self.0.next().map(|(k, v)| MapRef { k, v }) } } + + /// `for entry in &map`, as `DashMap` allows. + impl<'a, K: Eq + Hash, V> IntoIterator for &'a IxonMap { + type Item = MapRef<'a, K, V>; + type IntoIter = Iter<'a, K, V>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } + } } diff --git a/crates/ixon/src/metadata.rs b/crates/ixon/src/metadata.rs index 250a71a48..0738e7be5 100644 --- a/crates/ixon/src/metadata.rs +++ b/crates/ixon/src/metadata.rs @@ -13,7 +13,7 @@ use std::collections::HashMap; use std::sync::Arc; use ix_common::address::Address; -use ix_common::env::{self, BinderInfo, Name}; +use ix_common::env::{BinderInfo, Name}; use super::env::AuxLayout; use super::expr::Expr; @@ -203,10 +203,10 @@ pub struct ConstantMeta { /// before it; `i >= p + q` (out of range) and `p + j <= i < p + q` (a /// forward or self reference) are reader errors, so expansion is well /// founded. A `Share` in a primary expression always denotes a primary - /// entry: metadata never changes how primary bytes decode. Readers: the - /// Rust and Lean decompilers (`ix_compile::decompile::ShareScope`, Lean - /// `Ix.DecompileM.ShareScope`); kernel ingress never reads this table. - /// The compilers emit no `Share` here today. + /// entry: metadata never changes how primary bytes decode. Reader: the + /// Lean decompiler (`Ix.DecompileM.ShareScope`); kernel ingress never + /// reads this table. + /// The compiler emits no `Share` here today. pub meta_sharing: Vec>, /// Extension refs table (addresses referenced by collapsed arg expressions). pub meta_refs: Vec
, @@ -486,192 +486,6 @@ impl MetadataSource for super::env::Env { } } -/// Resolve address-based metadata. Env callers keep their existing behavior; -/// admission callers supply an authenticated read-only blob/name source. -pub fn resolve_kvmap( - kvm: &KVMap, - ixon_env: &(impl MetadataSource + ?Sized), -) -> Vec<(Name, env::DataValue)> { - kvm - .iter() - .filter_map(|(addr, dv)| { - let name = ixon_env.get_name(addr)?; - let resolved = match dv { - DataValue::OfString(a) => { - let bytes = ixon_env.get_blob(a)?; - env::DataValue::OfString(String::from_utf8(bytes).ok()?) - }, - DataValue::OfBool(b) => env::DataValue::OfBool(*b), - DataValue::OfName(a) => { - let n = ixon_env.get_name(a)?; - env::DataValue::OfName(n) - }, - DataValue::OfNat(a) => { - let bytes = ixon_env.get_blob(a)?; - env::DataValue::OfNat(bignat::Nat::from_le_bytes(&bytes)) - }, - DataValue::OfInt(a) => { - let bytes = ixon_env.get_blob(a)?; - let int = deser_int(&bytes)?; - env::DataValue::OfInt(int) - }, - DataValue::OfSyntax(a) => { - // Deserialize the Syntax tree from its blob. Mirrors - // `compile.rs::serialize_syntax_inner`; the deserializer only - // needs `Env::get_blob` + `Env::get_name`, so it lives here - // rather than in `decompile.rs` (which depends on CompileState). - let bytes = ixon_env.get_blob(a)?; - let mut buf = bytes.as_slice(); - let syn = deser_syntax(&mut buf, ixon_env)?; - env::DataValue::OfSyntax(Box::new(syn)) - }, - }; - Some((name, resolved)) - }) - .collect() -} - -// =========================================================================== -// Syntax deserialization from blobs -// =========================================================================== -// -// These mirror the compile-side `serialize_syntax_inner` / -// `serialize_source_info` / `serialize_substring` / `serialize_preresolved` -// in `src/ix/compile.rs`. They live here (not `decompile.rs`) so that -// `resolve_kvmap` can materialize `DataValue::OfSyntax` entries during -// kernel ingress — the decompile-side helpers depend on `CompileState`, -// which isn't available in the ingress path. All we need is the `Env` -// (for blob + name lookups). - -fn deser_u8(buf: &mut &[u8]) -> Option { - let (&x, rest) = buf.split_first()?; - *buf = rest; - Some(x) -} - -fn deser_tagn0(buf: &mut &[u8]) -> Option { - TagN::get(0, buf).ok().map(|t| t.value) -} - -fn deser_addr(buf: &mut &[u8]) -> Option
{ - if buf.len() < 32 { - return None; - } - let (bytes, rest) = buf.split_at(32); - *buf = rest; - Address::from_slice(bytes).ok() -} - -/// Deserialize a signed `Int` from bytes (mirrors compile-side encoding in -/// `compile_data_value` / `DataValue::OfInt`). -fn deser_int(bytes: &[u8]) -> Option { - let (&tag, rest) = bytes.split_first()?; - match tag { - 0 => Some(env::Int::OfNat(bignat::Nat::from_le_bytes(rest))), - 1 => Some(env::Int::NegSucc(bignat::Nat::from_le_bytes(rest))), - _ => None, - } -} - -fn deser_substring( - buf: &mut &[u8], - ixon_env: &(impl MetadataSource + ?Sized), -) -> Option { - let str_addr = deser_addr(buf)?; - let s = String::from_utf8(ixon_env.get_blob(&str_addr)?).ok()?; - let start_pos = bignat::Nat::from(deser_tagn0(buf)?); - let stop_pos = bignat::Nat::from(deser_tagn0(buf)?); - Some(env::Substring { str: s, start_pos, stop_pos }) -} - -fn deser_source_info( - buf: &mut &[u8], - ixon_env: &(impl MetadataSource + ?Sized), -) -> Option { - match deser_u8(buf)? { - 0 => { - let leading = deser_substring(buf, ixon_env)?; - let leading_pos = bignat::Nat::from(deser_tagn0(buf)?); - let trailing = deser_substring(buf, ixon_env)?; - let trailing_pos = bignat::Nat::from(deser_tagn0(buf)?); - Some(env::SourceInfo::Original( - leading, - leading_pos, - trailing, - trailing_pos, - )) - }, - 1 => { - let start = bignat::Nat::from(deser_tagn0(buf)?); - let end = bignat::Nat::from(deser_tagn0(buf)?); - let canonical = deser_u8(buf)? != 0; - Some(env::SourceInfo::Synthetic(start, end, canonical)) - }, - 2 => Some(env::SourceInfo::None), - _ => None, - } -} - -fn deser_preresolved( - buf: &mut &[u8], - ixon_env: &(impl MetadataSource + ?Sized), -) -> Option { - match deser_u8(buf)? { - 0 => { - let name = ixon_env.get_name(&deser_addr(buf)?)?; - Some(env::SyntaxPreresolved::Namespace(name)) - }, - 1 => { - let name = ixon_env.get_name(&deser_addr(buf)?)?; - let count = deser_tagn0(buf)? as usize; - let mut fields = Vec::with_capacity(count.min(buf.len())); - for _ in 0..count { - let addr = deser_addr(buf)?; - fields.push(String::from_utf8(ixon_env.get_blob(&addr)?).ok()?); - } - Some(env::SyntaxPreresolved::Decl(name, fields)) - }, - _ => None, - } -} - -fn deser_syntax( - buf: &mut &[u8], - ixon_env: &(impl MetadataSource + ?Sized), -) -> Option { - match deser_u8(buf)? { - 0 => Some(env::Syntax::Missing), - 1 => { - let info = deser_source_info(buf, ixon_env)?; - let kind = ixon_env.get_name(&deser_addr(buf)?)?; - let arg_count = deser_tagn0(buf)? as usize; - let mut args = Vec::with_capacity(arg_count.min(buf.len())); - for _ in 0..arg_count { - args.push(deser_syntax(buf, ixon_env)?); - } - Some(env::Syntax::Node(info, kind, args)) - }, - 2 => { - let info = deser_source_info(buf, ixon_env)?; - let val_addr = deser_addr(buf)?; - let val = String::from_utf8(ixon_env.get_blob(&val_addr)?).ok()?; - Some(env::Syntax::Atom(info, val)) - }, - 3 => { - let info = deser_source_info(buf, ixon_env)?; - let raw_val = deser_substring(buf, ixon_env)?; - let val = ixon_env.get_name(&deser_addr(buf)?)?; - let pr_count = deser_tagn0(buf)? as usize; - let mut preresolved = Vec::with_capacity(pr_count.min(buf.len())); - for _ in 0..pr_count { - preresolved.push(deser_preresolved(buf, ixon_env)?); - } - Some(env::Syntax::Ident(info, raw_val, val, preresolved)) - }, - _ => None, - } -} - // =========================================================================== // Serialization helpers // =========================================================================== diff --git a/crates/ixon/src/resource.rs b/crates/ixon/src/resource.rs deleted file mode 100644 index cc9d4a3ca..000000000 --- a/crates/ixon/src/resource.rs +++ /dev/null @@ -1,1838 +0,0 @@ -//! Resource checking for Ixon. Mirrors `Ix/Resource/{Basic,Check}.lean`. -//! -//! The address adapter supplies a globally indexed program whose erased types -//! have been checked. External interfaces and special primitives are explicit -//! profile assumptions. No result here certifies unverified typechecking or -//! an unbound primitive profile. - -pub mod addressed; - -use crate::contract::{BinderContract, LetKind, Locality, ValueContract}; -use crate::expr::{Expr, Owned, Uses}; -use std::sync::Arc; - -pub type Term = Arc; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Error { - Budget, - Unbound(usize), - BadReference(u64), - UnresolvedSharing(u64), - Unsupported(&'static str), - TypeMismatch, - BinderMismatch, - Usage(usize, Uses, Uses), - Moved(usize), - SharedToUnique, - ActiveLoan(usize), - Escape(usize, usize), - UnrestrictedEscape(usize), - Nonduplicable, - InvalidBorrow, - InvalidPlace, - InvalidScope, -} - -impl Error { - pub const fn code(&self) -> &'static str { - match self { - Self::Budget => "budget", - Self::Unbound(_) => "unbound", - Self::BadReference(_) => "reference", - Self::UnresolvedSharing(_) => "sharing", - Self::Unsupported(_) => "unsupported", - Self::TypeMismatch => "type", - Self::BinderMismatch => "binder", - Self::Usage(..) => "usage", - Self::Moved(_) => "moved", - Self::SharedToUnique => "ownership", - Self::ActiveLoan(_) => "loan", - Self::Escape(..) => "escape", - Self::UnrestrictedEscape(_) => "unrestricted", - Self::Nonduplicable => "capture", - Self::InvalidBorrow => "borrow", - Self::InvalidPlace => "place", - Self::InvalidScope => "scope", - } - } -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "resource {}: {self:?}", self.code()) - } -} -impl std::error::Error for Error {} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct Limits { - pub depth: usize, - pub steps: usize, -} -impl Default for Limits { - fn default() -> Self { - Self { depth: 256, steps: 100_000 } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum DeclKind { - Definition, - Assumption, - Constructor, - TypeConstructor, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Declaration { - pub typ: Term, - pub body: Option, - pub kind: DeclKind, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Field { - pub type_ref: u64, - pub index: u64, - pub typ: Term, - pub contract: ValueContract, -} - -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct Program { - pub declarations: Vec, - pub groups: Vec>, - /// Additional executable syntax, including addressed recursor rules. - pub auxiliary: Vec<(usize, Term)>, - pub sharing: Vec, - pub fields: Vec, - pub nat_type: Option, - pub string_type: Option, - pub shareable_types: Vec, - /// Profile-bound non-strict selection, with exactly three arguments. - pub choices: Vec, -} - -#[derive(Clone, Debug)] -struct Value { - typ: Term, - owned: Owned, - origins: Vec, - duplicable: bool, - place: Option, - loan_root: Option, -} -impl Value { - fn shared(typ: Term) -> Self { - Self { - typ, - owned: Owned::Shared, - origins: vec![], - duplicable: true, - place: None, - loan_root: None, - } - } - fn fresh(typ: Term) -> Self { - Self { owned: Owned::Unique, ..Self::shared(typ) } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -struct Owner { - live: bool, - unique: bool, - loans: Vec, -} -impl Owner { - fn move_out(&self, id: usize) -> Result { - if !self.live { - return Err(Error::Moved(id)); - } - if !self.unique { - return Err(Error::SharedToUnique); - } - if !self.loans.is_empty() { - return Err(Error::ActiveLoan(id)); - } - Ok(Self { live: false, ..self.clone() }) - } - fn share(&self, id: usize) -> Result { - if !self.live { - return Err(Error::Moved(id)); - } - Ok(Self { unique: false, ..self.clone() }) - } - fn begin_loan(&mut self, id: usize, scope: usize) -> Result<(), Error> { - if !self.live { - return Err(Error::Moved(id)); - } - self.loans.insert(0, scope); - Ok(()) - } - fn end_loan(&mut self, scope: usize) { - self.loans.retain(|x| *x != scope); - } - fn join(&self, other: &Self) -> Self { - Self { - live: self.live && other.live, - unique: self.unique && other.unique, - loans: union(&self.loans, &other.loans), - } - } -} - -#[derive(Clone, Debug)] -struct Binding { - contract: BinderContract, - value: Value, - owner: Owner, - demand: Uses, - touched: bool, -} - -#[derive(Clone, Debug)] -struct State { - bindings: Vec, - parents: Vec>, - remaining: usize, -} - -#[derive(Clone, Debug, Default)] -struct Context { - vars: Vec, - scope: usize, -} - -fn add(a: Uses, b: Uses) -> Uses { - match (a, b) { - (Uses::Erased, x) | (x, Uses::Erased) => x, - _ => Uses::Many, - } -} -fn scale(a: Uses, b: Uses) -> Uses { - match (a, b) { - (Uses::Erased, _) | (_, Uses::Erased) => Uses::Erased, - (Uses::Linear, x) | (x, Uses::Linear) => x, - (Uses::Affine, Uses::Affine) => Uses::Affine, - _ => Uses::Many, - } -} -fn join(a: Uses, b: Uses) -> Uses { - match (a, b) { - (Uses::Erased, Uses::Erased) => Uses::Erased, - (Uses::Linear, Uses::Linear) => Uses::Linear, - (Uses::Many, _) | (_, Uses::Many) => Uses::Many, - _ => Uses::Affine, - } -} -fn covers(declared: Uses, actual: Uses) -> bool { - match declared { - Uses::Many => true, - Uses::Affine => actual != Uses::Many, - Uses::Linear => actual == Uses::Linear, - Uses::Erased => actual == Uses::Erased, - } -} -fn union(a: &[usize], b: &[usize]) -> Vec { - let mut result = a.to_vec(); - for x in b { - if !result.contains(x) { - result.push(*x); - } - } - result -} - -struct Checker<'a> { - program: &'a Program, - state: State, -} -type Arrow = (BinderContract, ValueContract, Term, Term); -type Expected = Option<(Term, ValueContract)>; - -impl<'a> Checker<'a> { - fn new(program: &'a Program, limits: Limits) -> Self { - Self { - program, - state: State { - bindings: vec![], - parents: vec![None], - remaining: limits.steps, - }, - } - } - fn tick(&mut self, fuel: usize) -> Result { - if fuel == 0 || self.state.remaining == 0 { - return Err(Error::Budget); - } - self.state.remaining -= 1; - Ok(fuel - 1) - } - fn declaration(&self, index: u64) -> Result<&Declaration, Error> { - let i = - usize::try_from(index).map_err(|_error| Error::BadReference(index))?; - self.program.declarations.get(i).ok_or(Error::BadReference(index)) - } - fn expansion(&self, index: u64) -> Result { - let i = usize::try_from(index) - .map_err(|_error| Error::UnresolvedSharing(index))?; - self.program.sharing.get(i).cloned().ok_or(Error::UnresolvedSharing(index)) - } - fn binding(&self, id: usize) -> Result<&Binding, Error> { - self.state.bindings.get(id).ok_or(Error::Unbound(id)) - } - fn binding_mut(&mut self, id: usize) -> Result<&mut Binding, Error> { - self.state.bindings.get_mut(id).ok_or(Error::Unbound(id)) - } - fn push(&mut self, contract: BinderContract, value: Value) -> usize { - let id = self.state.bindings.len(); - let owner = - Owner { live: true, unique: value.owned == Owned::Unique, loans: vec![] }; - self.state.bindings.push(Binding { - contract, - value, - owner, - demand: Uses::Erased, - touched: false, - }); - id - } - fn scope(&mut self, parent: usize) -> Result { - if parent >= self.state.parents.len() { - return Err(Error::InvalidScope); - } - let id = self.state.parents.len(); - self.state.parents.push(Some(parent)); - Ok(id) - } - fn outlives(&self, origin: usize, mut destination: usize) -> bool { - for _ in 0..=self.state.parents.len() { - if origin >= self.state.parents.len() - || destination >= self.state.parents.len() - { - return false; - } - if origin == destination { - return true; - } - match self.state.parents[destination] { - Some(parent) => destination = parent, - None => return false, - } - } - false - } - fn check_origins( - &self, - origins: &[usize], - destination: usize, - ) -> Result<(), Error> { - for origin in origins { - if !self.outlives(*origin, destination) { - return Err(Error::Escape(*origin, destination)); - } - } - Ok(()) - } - fn charge(&mut self, id: usize, uses: Uses) -> Result<(), Error> { - let b = self.binding_mut(id)?; - if !b.owner.live { - return Err(Error::Moved(id)); - } - b.demand = add(b.demand, uses); - b.touched = true; - Ok(()) - } - fn validate_usage(&self, id: usize) -> Result<(), Error> { - let b = self.binding(id)?; - if !covers(b.contract.uses, b.demand) { - return Err(Error::Usage(id, b.contract.uses, b.demand)); - } - Ok(()) - } - /// Perform a place's ownership action once. Scope exit materializes its - /// result before ending loans, even when the result is being inferred. - fn materialize(&mut self, mut v: Value) -> Result { - if let Some(id) = v.place { - let b = self.binding_mut(id)?; - b.owner = match v.owned { - Owned::Unique => b.owner.move_out(id)?, - Owned::Shared => b.owner.share(id)?, - }; - } - v.place = None; - Ok(v) - } - fn consume_as( - &mut self, - required: ValueContract, - destination: usize, - mut v: Value, - ) -> Result { - match required.locality { - Locality::Unrestricted => { - if let Some(origin) = v.origins.first() { - return Err(Error::UnrestrictedEscape(*origin)); - } - }, - Locality::Local => self.check_origins(&v.origins, destination)?, - } - if required.owned == Owned::Unique && v.owned != Owned::Unique { - return Err(Error::SharedToUnique); - } - if required.owned == Owned::Shared && !v.duplicable { - return Err(Error::Nonduplicable); - } - v.owned = required.owned; - v = self.materialize(v)?; - if required.locality == Locality::Local { - v.origins = union(&v.origins, &[destination]); - } - Ok(v) - } - fn invoke(&mut self, v: &Value) -> Result<(), Error> { - if v.owned == Owned::Shared && !v.duplicable { - return Err(Error::Nonduplicable); - } - if let Some(id) = v.place { - let b = self.binding_mut(id)?; - if v.owned == Owned::Unique { - b.owner = b.owner.move_out(id)?; - } else if !b.owner.live { - return Err(Error::Moved(id)); - } - } - Ok(()) - } - fn close_scope(&mut self, scope: usize, result: &Value) -> Result<(), Error> { - let parent = self - .state - .parents - .get(scope) - .copied() - .flatten() - .ok_or(Error::InvalidScope)?; - self.check_origins(&result.origins, parent)?; - for b in &mut self.state.bindings { - b.owner.end_loan(scope); - } - Ok(()) - } - fn shift( - &mut self, - fuel: usize, - e: &Term, - amount: u64, - cutoff: u64, - ) -> Result { - let fuel = self.tick(fuel)?; - Ok(match e.as_ref() { - Expr::Var(i) if *i >= cutoff => Expr::var( - i.checked_add(amount) - .ok_or(Error::Unsupported("variable index overflow"))?, - ), - Expr::App(f, a) => Expr::app( - self.shift(fuel, f, amount, cutoff)?, - self.shift(fuel, a, amount, cutoff)?, - ), - Expr::Lam(c, t, b) => Expr::lam_contract( - *c, - self.shift(fuel, t, amount, cutoff)?, - self.shift( - fuel, - b, - amount, - cutoff.checked_add(1).ok_or(Error::Budget)?, - )?, - ), - Expr::All(c, r, t, b) => Expr::all_contract( - *c, - *r, - self.shift(fuel, t, amount, cutoff)?, - self.shift( - fuel, - b, - amount, - cutoff.checked_add(1).ok_or(Error::Budget)?, - )?, - ), - Expr::Let(c, t, v, b) => Expr::let_contract( - *c, - self.shift(fuel, t, amount, cutoff)?, - self.shift(fuel, v, amount, cutoff)?, - self.shift( - fuel, - b, - amount, - cutoff.checked_add(1).ok_or(Error::Budget)?, - )?, - ), - Expr::Prj(t, i, v) => { - Expr::prj(*t, *i, self.shift(fuel, v, amount, cutoff)?) - }, - Expr::Share(i) => { - self.shift(fuel, &self.expansion(*i)?, amount, cutoff)? - }, - Expr::Rec(..) => { - return Err(Error::Unsupported("unresolved recursive reference")); - }, - _ => e.clone(), - }) - } - fn substitute( - &mut self, - fuel: usize, - e: &Term, - value: &Term, - depth: u64, - ) -> Result { - let fuel = self.tick(fuel)?; - Ok(match e.as_ref() { - Expr::Var(i) if *i == depth => self.shift(fuel, value, depth, 0)?, - Expr::Var(i) if *i > depth => Expr::var(i - 1), - Expr::App(f, a) => Expr::app( - self.substitute(fuel, f, value, depth)?, - self.substitute(fuel, a, value, depth)?, - ), - Expr::Lam(c, t, b) => Expr::lam_contract( - *c, - self.substitute(fuel, t, value, depth)?, - self.substitute( - fuel, - b, - value, - depth.checked_add(1).ok_or(Error::Budget)?, - )?, - ), - Expr::All(c, r, t, b) => Expr::all_contract( - *c, - *r, - self.substitute(fuel, t, value, depth)?, - self.substitute( - fuel, - b, - value, - depth.checked_add(1).ok_or(Error::Budget)?, - )?, - ), - Expr::Let(c, t, v, b) => Expr::let_contract( - *c, - self.substitute(fuel, t, value, depth)?, - self.substitute(fuel, v, value, depth)?, - self.substitute( - fuel, - b, - value, - depth.checked_add(1).ok_or(Error::Budget)?, - )?, - ), - Expr::Prj(t, i, v) => { - Expr::prj(*t, *i, self.substitute(fuel, v, value, depth)?) - }, - Expr::Share(i) => { - self.substitute(fuel, &self.expansion(*i)?, value, depth)? - }, - Expr::Rec(..) => { - return Err(Error::Unsupported("unresolved recursive reference")); - }, - _ => e.clone(), - }) - } - fn whnf(&mut self, fuel: usize, e: &Term) -> Result { - let fuel = self.tick(fuel)?; - match e.as_ref() { - Expr::Share(i) => self.whnf(fuel, &self.expansion(*i)?), - Expr::Let(_, _, v, b) => { - let body = self.substitute(fuel, b, v, 0)?; - self.whnf(fuel, &body) - }, - Expr::App(f, a) => { - let f = self.whnf(fuel, f)?; - if let Expr::Lam(_, _, b) = f.as_ref() { - let b = self.substitute(fuel, b, a, 0)?; - self.whnf(fuel, &b) - } else { - Ok(Expr::app(f, a.clone())) - } - }, - Expr::Ref(i, _) => match self.declaration(*i)?.body.clone() { - Some(body) => self.whnf(fuel, &body), - None => Ok(e.clone()), - }, - Expr::Rec(..) => { - Err(Error::Unsupported("unresolved recursive reference")) - }, - _ => Ok(e.clone()), - } - } - fn compatible( - &mut self, - fuel: usize, - a: &Term, - b: &Term, - ) -> Result { - let fuel = self.tick(fuel)?; - if a == b { - return Ok(true); - } - let a = self.whnf(fuel, a)?; - let b = self.whnf(fuel, b)?; - Ok(match (a.as_ref(), b.as_ref()) { - (Expr::All(c, r, t, b), Expr::All(d, s, u, e)) => { - c == d - && r == s - && self.compatible(fuel, t, u)? - && self.compatible(fuel, b, e)? - }, - (Expr::Lam(c, t, b), Expr::Lam(d, u, e)) => { - c == d && self.compatible(fuel, t, u)? && self.compatible(fuel, b, e)? - }, - (Expr::App(f, a), Expr::App(g, b)) => { - self.compatible(fuel, f, g)? && self.compatible(fuel, a, b)? - }, - (Expr::Prj(t, i, v), Expr::Prj(u, j, w)) => { - t == u && i == j && self.compatible(fuel, v, w)? - }, - (Expr::Sort(_), Expr::Sort(_)) => true, - (Expr::Ref(i, _), Expr::Ref(j, _)) => i == j, - _ => a == b, - }) - } - fn require_type( - &mut self, - fuel: usize, - actual: &Term, - expected: &Term, - ) -> Result<(), Error> { - if self.compatible(fuel, actual, expected)? { - Ok(()) - } else { - Err(Error::TypeMismatch) - } - } - fn arrow(&mut self, fuel: usize, typ: &Term) -> Result { - let t = self.whnf(fuel, typ)?; - match t.as_ref() { - Expr::All(c, r, t, b) => Ok((*c, *r, t.clone(), b.clone())), - _ => Err(Error::Unsupported( - "a callable interface must expose its arrow contracts", - )), - } - } - fn is_shareable(&mut self, fuel: usize, typ: &Term) -> Result { - let typ = self.whnf(fuel, typ)?; - Ok(match typ.as_ref() { - Expr::Ref(i, _) => self.program.shareable_types.contains(i), - Expr::Sort(_) => true, - _ => false, - }) - } - fn literal_type(typ: &Option) -> Result { - typ.clone().ok_or(Error::Unsupported( - "literal type is absent from the addressed profile", - )) - } - fn read_variable( - &mut self, - fuel: usize, - ctx: &Context, - index: u64, - uses: Uses, - ) -> Result { - let n = usize::try_from(index).map_err(|_error| Error::Budget)?; - let pos = ctx - .vars - .len() - .checked_sub(n) - .and_then(|v| v.checked_sub(1)) - .ok_or(Error::Unbound(n))?; - let id = ctx.vars[pos]; - let b = self.binding(id)?.clone(); - self.charge(id, uses)?; - if let Some(root) = b.value.loan_root - && !self.binding(root)?.owner.live - { - return Err(Error::Moved(root)); - } - let typ = self.shift( - fuel, - &b.value.typ, - index.checked_add(1).ok_or(Error::Budget)?, - 0, - )?; - Ok(Value { - typ, - place: Some(id), - owned: if b.owner.unique { Owned::Unique } else { Owned::Shared }, - ..b.value - }) - } - fn field(&self, type_ref: u64, index: u64) -> Result { - self - .program - .fields - .iter() - .find(|f| f.type_ref == type_ref && f.index == index) - .cloned() - .ok_or(Error::Unsupported("projection has no checked field rule")) - } - fn place( - &mut self, - fuel: usize, - ctx: &Context, - e: &Term, - ) -> Result { - let fuel = self.tick(fuel)?; - match e.as_ref() { - Expr::Var(i) => { - let value = self.read_variable(fuel, ctx, *i, Uses::Erased)?; - let id = value.place.ok_or(Error::InvalidPlace)?; - if self.binding(id)?.contract.uses == Uses::Erased { - return Err(Error::Unsupported( - "an erased binding cannot be borrowed", - )); - } - Ok(value) - }, - Expr::Share(i) => self.place(fuel, ctx, &self.expansion(*i)?), - Expr::Prj(t, i, v) => { - let mut v = self.place(fuel, ctx, v)?; - let f = self.field(*t, *i)?; - v.duplicable = f.contract.owned == Owned::Shared - || self.is_shareable(fuel, &f.typ)?; - v.typ = f.typ; - if v.owned != Owned::Unique || f.contract.owned != Owned::Unique { - v.owned = Owned::Shared; - } - Ok(v) - }, - _ => Err(Error::InvalidPlace), - } - } - fn type_of( - &mut self, - fuel: usize, - ctx: &Context, - e: &Term, - ) -> Result { - let fuel = self.tick(fuel)?; - match e.as_ref() { - Expr::Var(i) => { - let n = usize::try_from(*i).map_err(|_error| Error::Budget)?; - let pos = ctx - .vars - .len() - .checked_sub(n) - .and_then(|v| v.checked_sub(1)) - .ok_or(Error::Unbound(n))?; - let typ = self.binding(ctx.vars[pos])?.value.typ.clone(); - self.shift(fuel, &typ, i.checked_add(1).ok_or(Error::Budget)?, 0) - }, - Expr::Ref(i, _) => Ok(self.declaration(*i)?.typ.clone()), - Expr::Share(i) => self.type_of(fuel, ctx, &self.expansion(*i)?), - Expr::Rec(..) => { - Err(Error::Unsupported("unresolved recursive reference")) - }, - Expr::Sort(_) | Expr::All(..) => Ok(Expr::sort(0)), - Expr::Nat(_) => Self::literal_type(&self.program.nat_type), - Expr::Str(_) => Self::literal_type(&self.program.string_type), - Expr::Prj(t, i, _) => Ok(self.field(*t, *i)?.typ), - Expr::App(f, a) => { - let t = self.type_of(fuel, ctx, f)?; - let (_, _, _, b) = self.arrow(fuel, &t)?; - self.substitute(fuel, &b, a, 0) - }, - Expr::Lam(c, t, b) => { - let before = self.state.bindings.clone(); - let id = self.push(*c, Value::shared(t.clone())); - let mut inner = ctx.clone(); - inner.vars.push(id); - let codomain = self.type_of(fuel, &inner, b)?; - self.state.bindings = before; - Ok(Expr::all_contract(*c, ValueContract::shared(), t.clone(), codomain)) - }, - Expr::Let(c, t, v, b) => { - let before = self.state.bindings.clone(); - let id = self.push(c.binder, Value::shared(t.clone())); - let mut inner = ctx.clone(); - inner.vars.push(id); - let typ = self.type_of(fuel, &inner, b)?; - self.state.bindings = before; - self.substitute(fuel, &typ, v, 0) - }, - } - } - fn finish( - &mut self, - fuel: usize, - destination: usize, - expected: Expected, - value: Value, - ) -> Result { - match expected { - None => Ok(value), - Some((typ, contract)) => { - self.require_type(fuel, &value.typ, &typ)?; - self.consume_as(contract, destination, value) - }, - } - } - fn ordinary_call( - &mut self, - fuel: usize, - ctx: &Context, - f: &Term, - a: &Term, - destination: usize, - grade: Uses, - ) -> Result { - let fn_type = self.type_of(fuel, ctx, f)?; - let (input, result, domain, codomain) = self.arrow(fuel, &fn_type)?; - let scope = - if result.locality == Locality::Local { destination } else { ctx.scope }; - let f = self.analyze(fuel, ctx, f, None, scope, grade)?; - self.require_type(fuel, &f.typ, &fn_type)?; - self.invoke(&f)?; - let arg = self.analyze( - fuel, - ctx, - a, - Some((domain, input.value)), - scope, - scale(grade, input.uses), - )?; - let typ = self.substitute(fuel, &codomain, a, 0)?; - let origins = if result.locality == Locality::Local { - union(&union(&f.origins, &arg.origins), &[scope]) - } else { - vec![] - }; - let duplicable = - result.owned == Owned::Shared || self.is_shareable(fuel, &typ)?; - Ok(Value { owned: result.owned, origins, duplicable, ..Value::shared(typ) }) - } - fn selection( - &mut self, - fuel: usize, - ctx: &Context, - index: u64, - args: &[Term], - destination: usize, - grade: Uses, - ) -> Result { - let typ = self.declaration(index)?.typ.clone(); - let (c, _, selector_type, tail) = self.arrow(fuel, &typ)?; - self.analyze( - fuel, - ctx, - &args[0], - Some((selector_type, c.value)), - ctx.scope, - scale(grade, c.uses), - )?; - let tail = self.substitute(fuel, &tail, &args[0], 0)?; - let (_, _, left_type, tail) = self.arrow(fuel, &tail)?; - let tail = self.substitute(fuel, &tail, &args[1], 0)?; - let (_, result, right_type, output_type) = self.arrow(fuel, &tail)?; - let output_type = self.substitute(fuel, &output_type, &args[2], 0)?; - self.require_type(fuel, &left_type, &right_type)?; - self.require_type(fuel, &left_type, &output_type)?; - let before = self.state.bindings.clone(); - let left = self.analyze( - fuel, - ctx, - &args[1], - Some((left_type, result)), - destination, - grade, - )?; - let left_bindings = self.state.bindings.clone(); - self.state.bindings = before.clone(); - let right = self.analyze( - fuel, - ctx, - &args[2], - Some((right_type, result)), - destination, - grade, - )?; - let mut bindings = vec![]; - for (i, mut b) in left_bindings.into_iter().take(before.len()).enumerate() { - let r = self.binding(i)?; - b.owner = b.owner.join(&r.owner); - b.demand = join(b.demand, r.demand); - b.touched |= r.touched; - bindings.push(b); - } - self.state.bindings = bindings; - Ok(Value { - typ: output_type, - owned: if left.owned == Owned::Unique && right.owned == Owned::Unique { - Owned::Unique - } else { - Owned::Shared - }, - origins: union(&left.origins, &right.origins), - duplicable: left.duplicable && right.duplicable, - place: if left.place == right.place { left.place } else { None }, - loan_root: if left.loan_root == right.loan_root { - left.loan_root - } else { - None - }, - }) - } - fn analyze( - &mut self, - fuel: usize, - ctx: &Context, - e: &Term, - expected: Expected, - destination: usize, - grade: Uses, - ) -> Result { - let fuel = self.tick(fuel)?; - if grade == Uses::Erased { - let typ = self.type_of(fuel, ctx, e)?; - if let Some((expected_type, _)) = expected { - self.require_type(fuel, &typ, &expected_type)?; - } - return Ok(Value::fresh(typ)); - } - if let Some((t, _)) = &expected - && matches!(self.whnf(fuel, t)?.as_ref(), Expr::Sort(_)) - { - return Ok(Value::fresh(t.clone())); - } - if grade != Uses::Linear { - let before = self.state.bindings.clone(); - for b in &mut self.state.bindings { - b.demand = Uses::Erased; - b.touched = false; - } - let output = - self.analyze(fuel, ctx, e, expected, destination, Uses::Linear)?; - for (id, old) in before.into_iter().enumerate() { - let used = self.binding_mut(id)?; - used.demand = add(old.demand, scale(grade, used.demand)); - used.touched |= old.touched; - } - return Ok(output); - } - let value = match e.as_ref() { - Expr::Share(i) => self.analyze( - fuel, - ctx, - &self.expansion(*i)?, - expected.clone(), - destination, - grade, - )?, - Expr::Var(i) => self.read_variable(fuel, ctx, *i, grade)?, - Expr::Ref(i, _) => { - if self.program.choices.contains(i) { - return Err(Error::Unsupported( - "selection primitives cannot escape as function values", - )); - } - Value::shared(self.declaration(*i)?.typ.clone()) - }, - Expr::Rec(..) => { - return Err(Error::Unsupported("unresolved recursive reference")); - }, - Expr::Sort(_) | Expr::All(..) => Value::fresh(Expr::sort(0)), - Expr::Nat(_) => Value::fresh(Self::literal_type(&self.program.nat_type)?), - Expr::Str(_) => { - Value::fresh(Self::literal_type(&self.program.string_type)?) - }, - Expr::Lam(input, domain, body) => { - let (result, codomain) = match &expected { - Some((t, _)) => { - let (declared, r, t, b) = self.arrow(fuel, t)?; - if *input != declared { - return Err(Error::BinderMismatch); - } - self.require_type(fuel, domain, &t)?; - (r, Some(b)) - }, - None => (ValueContract::shared(), None), - }; - let before = self.state.bindings.clone(); - for b in &mut self.state.bindings { - b.demand = Uses::Erased; - b.touched = false; - } - let caller_scope = self.scope(ctx.scope)?; - let body_scope = self.scope(caller_scope)?; - let duplicable = input.value.owned == Owned::Shared - || self.is_shareable(fuel, domain)?; - let parameter = self.push( - *input, - Value { - owned: input.value.owned, - duplicable, - origins: if input.value.locality == Locality::Local { - vec![caller_scope] - } else { - vec![] - }, - ..Value::shared(domain.clone()) - }, - ); - let mut inner = ctx.clone(); - inner.vars.push(parameter); - inner.scope = body_scope; - let body_expected = codomain.as_ref().map(|t| (t.clone(), result)); - let output = self.analyze( - fuel, - &inner, - body, - body_expected.clone(), - caller_scope, - Uses::Linear, - )?; - let output = if body_expected.is_some() { - output - } else { - self.consume_as(result, caller_scope, output)? - }; - self.validate_usage(parameter)?; - let mut origins = vec![]; - let mut duplicable = true; - let mut bindings = vec![]; - for (id, old) in before.into_iter().enumerate() { - let used = self.binding(id)?; - let mut owner = used.owner.clone(); - let mut demand = used.demand; - if used.touched { - origins = union(&origins, &old.value.origins); - if old.contract.uses != Uses::Many || !old.value.duplicable { - duplicable = false; - } - if old.owner.unique { - old.owner.move_out(id)?; - owner.live = false; - if demand == Uses::Erased { - demand = Uses::Linear; - } - duplicable = false; - } - } - let demand = add(old.demand, scale(grade, demand)); - let touched = old.touched || used.touched; - bindings.push(Binding { owner, demand, touched, ..old }); - } - self.state.bindings = bindings; - Value { - origins, - duplicable, - ..Value::fresh(Expr::all_contract( - *input, - result, - domain.clone(), - codomain.unwrap_or(output.typ), - )) - } - }, - Expr::Let(contract, typ, initializer, body) => { - let scope = self.scope(ctx.scope)?; - let initialized = match contract.kind { - LetKind::Value => self.analyze( - fuel, - ctx, - initializer, - Some((typ.clone(), contract.binder.value)), - if contract.binder.value.locality == Locality::Local { - scope - } else { - destination - }, - scale(grade, contract.binder.uses), - )?, - LetKind::BorrowShared => { - if contract.binder.value != ValueContract::local_shared() { - return Err(Error::InvalidBorrow); - } - let mut v = self.place(fuel, ctx, initializer)?; - self.require_type(fuel, &v.typ, typ)?; - if !self.is_shareable(fuel, typ)? && !v.duplicable { - return Err(Error::Nonduplicable); - } - let owner_place = v.place.ok_or(Error::InvalidPlace)?; - let root = v.loan_root.unwrap_or(owner_place); - let b = self.binding_mut(root)?; - b.owner.begin_loan(root, scope)?; - v.owned = Owned::Shared; - v.duplicable = true; - v.origins = union(&v.origins, &[scope]); - v.place = None; - v.loan_root = Some(root); - v - }, - }; - let id = - self.push(contract.binder, Value { typ: typ.clone(), ..initialized }); - let mut inner = ctx.clone(); - inner.vars.push(id); - inner.scope = scope; - let body_expected = match &expected { - None => None, - Some((t, c)) => Some((self.shift(fuel, t, 1, 0)?, *c)), - }; - let output = self.analyze( - fuel, - &inner, - body, - body_expected, - destination, - grade, - )?; - self.validate_usage(id)?; - let mut output = self.materialize(output)?; - self.close_scope(scope, &output)?; - output.typ = self.substitute(fuel, &output.typ, initializer, 0)?; - output - }, - Expr::Prj(t, i, value) => { - let mut v = self.analyze(fuel, ctx, value, None, destination, grade)?; - let field = self.field(*t, *i)?; - v.duplicable = field.contract.owned == Owned::Shared - || self.is_shareable(fuel, &field.typ)?; - v.typ = field.typ; - if v.owned != Owned::Unique || field.contract.owned != Owned::Unique { - v.owned = Owned::Shared; - } - v - }, - Expr::App(f, a) => { - let mut head = e.clone(); - let mut args = vec![]; - let mut split_fuel = fuel; - loop { - split_fuel = self.tick(split_fuel)?; - match head.as_ref() { - Expr::App(f, a) => { - args.push(a.clone()); - head = f.clone(); - }, - Expr::Share(index) => head = self.expansion(*index)?, - _ => break, - } - } - args.reverse(); - if let Expr::Ref(i, _) = head.as_ref() { - if self.program.choices.contains(i) { - if args.len() != 3 { - return Err(Error::Unsupported( - "selection primitives require a complete application", - )); - } - self.selection(fuel, ctx, *i, &args, destination, grade)? - } else { - self.ordinary_call(fuel, ctx, f, a, destination, grade)? - } - } else { - self.ordinary_call(fuel, ctx, f, a, destination, grade)? - } - }, - }; - self.finish(fuel, destination, expected, value) - } -} - -pub fn check_definition( - program: &Program, - index: usize, - limits: Limits, -) -> Result<(), Error> { - let declaration = - program.declarations.get(index).ok_or(Error::BadReference(index as u64))?; - let body = declaration - .body - .as_ref() - .ok_or(Error::Unsupported("definition has no body"))?; - let mut checker = Checker::new(program, limits); - let scope = checker.scope(0)?; - checker.analyze( - limits.depth, - &Context { scope, vars: vec![] }, - body, - Some((declaration.typ.clone(), ValueContract::shared())), - scope, - Uses::Linear, - )?; - Ok(()) -} - -#[derive(Clone, Debug, Default)] -struct Scan { - annotated: bool, - dependencies: Vec, -} -impl Scan { - fn merge(mut self, other: Self) -> Self { - self.annotated |= other.annotated; - self.dependencies.extend(other.dependencies); - self - } -} - -impl Checker<'_> { - fn scan(&mut self, fuel: usize, e: &Term) -> Result { - if fuel == 0 { - return Err(Error::Budget); - } - let mut finished = std::collections::HashSet::new(); - let mut active = std::collections::HashSet::new(); - let mut stack = vec![(e.clone(), false)]; - let mut scan = Scan::default(); - while let Some((e, closing)) = stack.pop() { - let key = e.clone(); - if closing { - active.remove(&key); - finished.insert(key); - continue; - } - if active.contains(&key) { - return Err(Error::Unsupported("cyclic sharing")); - } - if finished.contains(&key) { - continue; - } - self.tick(1)?; - active.insert(key); - stack.push((e.clone(), true)); - match e.as_ref() { - Expr::Ref(i, _) => { - self.declaration(*i)?; - scan.dependencies.push( - usize::try_from(*i).map_err(|_error| Error::BadReference(*i))?, - ); - }, - Expr::Rec(..) => { - return Err(Error::Unsupported("unresolved recursive reference")); - }, - Expr::Share(i) => stack.push((self.expansion(*i)?, false)), - Expr::Prj(i, _, v) => { - self.declaration(*i)?; - scan.dependencies.push( - usize::try_from(*i).map_err(|_error| Error::BadReference(*i))?, - ); - stack.push((v.clone(), false)); - }, - Expr::App(f, a) => { - stack.push((a.clone(), false)); - stack.push((f.clone(), false)); - }, - Expr::Lam(c, t, b) => { - scan.annotated |= *c != BinderContract::default(); - stack.push((b.clone(), false)); - stack.push((t.clone(), false)); - }, - Expr::All(c, r, t, b) => { - scan.annotated |= - *c != BinderContract::default() || *r != ValueContract::shared(); - stack.push((b.clone(), false)); - stack.push((t.clone(), false)); - }, - Expr::Let(c, t, v, b) => { - scan.annotated |= - c.binder != BinderContract::default() || c.kind != LetKind::Value; - stack.push((b.clone(), false)); - stack.push((v.clone(), false)); - stack.push((t.clone(), false)); - }, - _ => {}, - } - } - Ok(scan) - } - fn check_constructor_type( - &mut self, - fuel: usize, - typ: &Term, - has_finite: bool, - has_local: bool, - ) -> Result<(), Error> { - let fuel = self.tick(fuel)?; - let typ = self.whnf(fuel, typ)?; - if let Expr::All(input, result, domain, codomain) = typ.as_ref() { - let domain = self.whnf(fuel, domain)?; - let erased = - input.uses == Uses::Erased || matches!(domain.as_ref(), Expr::Sort(_)); - if !covers(input.uses, if erased { Uses::Erased } else { Uses::Linear }) { - return Err(Error::Unsupported( - "constructor input quantity does not cover its stored occurrence", - )); - } - let finite = has_finite - || (!erased - && (input.uses != Uses::Many || input.value.owned == Owned::Unique)); - let local_capture = - has_local || (!erased && input.value.locality == Locality::Local); - if finite && result.owned != Owned::Unique { - return Err(Error::Unsupported( - "constructor result drops a finite or unique capture", - )); - } - if local_capture && result.locality != Locality::Local { - return Err(Error::Unsupported( - "constructor result drops a local capture", - )); - } - self.check_constructor_type(fuel, codomain, finite, local_capture)?; - } - Ok(()) - } -} - -/// Dependencies propagate resource relevance to callers and to every member -/// of a mutually addressed block. Only wholly ordinary components bypass the -/// resource analysis; erased typechecking is still required for them. -pub fn relevance( - program: &Program, - limits: Limits, -) -> Result, Error> { - let size = program.declarations.len(); - let mut reverse = vec![vec![]; size]; - let mut marked = vec![false; size]; - let mut queue = vec![]; - for (i, d) in program.declarations.iter().enumerate() { - let mut checker = Checker::new(program, limits); - let scan = checker.scan(limits.depth, &d.typ)?; - let scan = match &d.body { - None => scan, - Some(body) => scan.merge(checker.scan(limits.depth, body)?), - }; - if scan.annotated { - marked[i] = true; - queue.push(i); - } - for dependency in scan.dependencies { - reverse - .get_mut(dependency) - .ok_or(Error::BadReference(dependency as u64))? - .push(i); - } - } - for (i, expression) in &program.auxiliary { - if *i >= size { - return Err(Error::BadReference(*i as u64)); - } - let mut checker = Checker::new(program, limits); - let scan = checker.scan(limits.depth, expression)?; - if scan.annotated && !marked[*i] { - marked[*i] = true; - queue.push(*i); - } - for dependency in scan.dependencies { - reverse - .get_mut(dependency) - .ok_or(Error::BadReference(dependency as u64))? - .push(*i); - } - } - for f in &program.fields { - let i = usize::try_from(f.type_ref) - .map_err(|_error| Error::BadReference(f.type_ref))?; - let m = marked.get_mut(i).ok_or(Error::BadReference(f.type_ref))?; - if f.contract != ValueContract::shared() && !*m { - *m = true; - queue.push(i); - } - } - for group in &program.groups { - if let Some(first) = group.first().copied() { - if first >= size { - return Err(Error::BadReference(first as u64)); - } - for member in group { - if *member >= size { - return Err(Error::BadReference(*member as u64)); - } - reverse[first].push(*member); - reverse[*member].push(first); - } - } - } - let mut cursor = 0; - while cursor < queue.len() { - let i = queue[cursor]; - cursor += 1; - for dependent in &reverse[i] { - if !marked[*dependent] { - marked[*dependent] = true; - queue.push(*dependent); - } - } - } - Ok(marked) -} - -#[derive(Clone, Debug, Default)] -pub struct Policy { - pub assumptions: Vec, - pub shareable_types: Vec, - pub choices: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct AdmissionError { - pub declaration: Option, - pub error: Error, -} - -pub fn admit_program( - program: &Program, - policy: &Policy, - limits: Limits, -) -> Result<(), AdmissionError> { - for index in &program.choices { - let index = usize::try_from(*index).map_err(|_error| AdmissionError { - declaration: None, - error: Error::BadReference(*index), - })?; - if !policy.choices.contains(&index) || !policy.assumptions.contains(&index) - { - return Err(AdmissionError { - declaration: Some(index), - error: Error::Unsupported( - "selection behavior is absent from the bound profile", - ), - }); - } - let d = program.declarations.get(index).ok_or(AdmissionError { - declaration: None, - error: Error::BadReference(index as u64), - })?; - if d.kind != DeclKind::Assumption { - return Err(AdmissionError { - declaration: Some(index), - error: Error::Unsupported( - "selection behavior requires an external primitive interface", - ), - }); - } - } - for index in &program.shareable_types { - let index = usize::try_from(*index).map_err(|_error| AdmissionError { - declaration: None, - error: Error::BadReference(*index), - })?; - if !policy.shareable_types.contains(&index) { - return Err(AdmissionError { - declaration: Some(index), - error: Error::Unsupported( - "shareable representation is absent from the bound profile", - ), - }); - } - } - let marked = relevance(program, limits) - .map_err(|error| AdmissionError { declaration: None, error })?; - for (i, d) in program.declarations.iter().enumerate() { - if marked[i] { - let result = match d.kind { - DeclKind::Definition => check_definition(program, i, limits), - DeclKind::Assumption => { - if policy.assumptions.contains(&i) { - Ok(()) - } else { - Err(Error::Unsupported( - "external resource interface is absent from the bound profile", - )) - } - }, - DeclKind::Constructor => Checker::new(program, limits) - .check_constructor_type(limits.depth, &d.typ, false, false), - DeclKind::TypeConstructor => Ok(()), - }; - result.map_err(|error| AdmissionError { declaration: Some(i), error })?; - } - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn b(uses: Uses, value: ValueContract) -> BinderContract { - BinderContract { uses, value } - } - fn nat() -> Term { - Expr::reference(0, vec![]) - } - fn arr( - input: BinderContract, - result: ValueContract, - domain: Term, - codomain: Term, - ) -> Term { - Expr::all_contract(input, result, domain, codomain) - } - fn unary(input: BinderContract, result: ValueContract) -> Term { - arr(input, result, nat(), nat()) - } - fn choice(result: ValueContract) -> Term { - arr( - b(Uses::Linear, ValueContract::shared()), - ValueContract::shared(), - nat(), - arr( - b(Uses::Affine, result), - ValueContract::shared(), - nat(), - unary(b(Uses::Affine, result), result), - ), - ) - } - fn assumption(typ: Term) -> Declaration { - Declaration { typ, body: None, kind: DeclKind::Assumption } - } - fn identity(input: BinderContract, result: ValueContract) -> Declaration { - Declaration { - typ: unary(input, result), - body: Some(Expr::lam_contract(input, nat(), Expr::var(0))), - kind: DeclKind::Definition, - } - } - fn program() -> Program { - let mut p = Program { - declarations: vec![ - Declaration { - typ: Expr::sort(0), - body: None, - kind: DeclKind::TypeConstructor, - }, - assumption(unary( - b(Uses::Many, ValueContract::local_shared()), - ValueContract::shared(), - )), - identity( - b(Uses::Linear, ValueContract::local_shared()), - ValueContract::local_shared(), - ), - identity( - b(Uses::Linear, ValueContract::unique()), - ValueContract::unique(), - ), - assumption(unary( - b(Uses::Erased, ValueContract::shared()), - ValueContract::shared(), - )), - assumption(unary( - b(Uses::Many, ValueContract::shared()), - ValueContract::shared(), - )), - assumption(choice(ValueContract::shared())), - Declaration { - typ: Expr::sort(0), - body: None, - kind: DeclKind::TypeConstructor, - }, - assumption(arr( - b(Uses::Linear, ValueContract::local_shared()), - ValueContract::local_shared(), - nat(), - Expr::reference(7, vec![]), - )), - assumption(choice(ValueContract::local_shared())), - assumption(choice(ValueContract::unique())), - ], - ..Default::default() - }; - p.fields.push(Field { - type_ref: 7, - index: 0, - typ: nat(), - contract: ValueContract::unique(), - }); - p.fields.push(Field { - type_ref: 7, - index: 1, - typ: unary( - b(Uses::Many, ValueContract::shared()), - ValueContract::shared(), - ), - contract: ValueContract::unique(), - }); - p.fields.push(Field { - type_ref: 7, - index: 2, - typ: unary( - b(Uses::Many, ValueContract::shared()), - ValueContract::shared(), - ), - contract: ValueContract::shared(), - }); - p.nat_type = Some(nat()); - p.string_type = Some(nat()); - p.sharing.push(Expr::var(0)); - p.sharing.push(Expr::reference(6, vec![])); - p.shareable_types = vec![0, 7]; - p.choices = vec![6, 9, 10]; - p - } - fn parse(hex: &str) -> Term { - let bytes: Vec<_> = hex - .as_bytes() - .as_chunks::<2>() - .0 - .iter() - .map(|pair| { - u8::from_str_radix(std::str::from_utf8(pair).unwrap(), 16).unwrap() - }) - .collect(); - assert_eq!(bytes.len() * 2, hex.len()); - let mut input = bytes.as_slice(); - let expr = crate::serialize::get_expr(&mut input).unwrap(); - assert!(input.is_empty()); - expr - } - - #[test] - fn shared_resource_acceptance_and_rejection_fixtures() { - let mut count = 0; - for line in include_str!("../../../Tests/Fixtures/ixon-v4/resource.tsv") - .lines() - .filter(|l| !l.starts_with('#') && !l.is_empty()) - { - let cols: Vec<_> = line.split('\t').collect(); - assert_eq!(cols.len(), 4); - let mut p = program(); - let index = p.declarations.len(); - p.declarations.push(Declaration { - typ: parse(cols[2]), - body: Some(parse(cols[3])), - kind: DeclKind::Definition, - }); - let actual = check_definition(&p, index, Limits::default()); - let code = actual.as_ref().map_or_else(|e| e.code(), |()| "ok"); - assert_eq!(code, cols[1], "{}: {actual:?}", cols[0]); - count += 1; - } - assert!(count >= 55); - } - - #[test] - fn every_input_result_contract_is_checked() { - for input in 0..16 { - let input = BinderContract::from_bits(input).unwrap(); - for result in 0..4 { - let result = ValueContract::from_bits(result).unwrap(); - let mut p = program(); - let index = p.declarations.len(); - let body = - if input.uses == Uses::Erased { Expr::nat(0) } else { Expr::var(0) }; - p.declarations.push(Declaration { - typ: unary(input, result), - body: Some(Expr::lam_contract(input, nat(), body)), - kind: DeclKind::Definition, - }); - let allowed = input.uses == Uses::Erased - || ((input.value.owned == Owned::Unique - || result.owned == Owned::Shared) - && (input.value.locality == Locality::Unrestricted - || result.locality == Locality::Local)); - assert_eq!( - check_definition(&p, index, Limits::default()).is_ok(), - allowed, - "{input:?} -> {result:?}" - ); - } - } - } - - #[test] - fn scope_exit_never_restores_moved_or_shared_owners() { - for live in [false, true] { - for unique in [false, true] { - for loans in [vec![], vec![1], vec![1, 2]] { - let owner = Owner { live, unique, loans }; - let mut ended = owner.clone(); - ended.end_loan(1); - assert_eq!((ended.live, ended.unique), (live, unique)); - assert_eq!( - owner.move_out(0).is_ok(), - live && unique && owner.loans.is_empty() - ); - if let Ok(shared) = owner.share(0) { - assert!(!shared.unique); - } - for other_live in [false, true] { - for other_unique in [false, true] { - let other = - Owner { live: other_live, unique: other_unique, loans: vec![] }; - let joined = owner.join(&other); - assert_eq!(joined.live, live && other_live); - assert_eq!(joined.unique, unique && other_unique); - } - } - } - } - } - } - - #[test] - fn cyclic_sharing_exhausts_the_bound() { - let mut p = program(); - p.sharing = vec![Arc::new(Expr::Share(0))]; - let mut c = Checker::new(&p, Limits::default()); - assert_eq!( - c.analyze( - 20, - &Context::default(), - &Arc::new(Expr::Share(0)), - None, - 0, - Uses::Linear - ) - .unwrap_err(), - Error::Budget - ); - } - - #[test] - fn literals_require_an_addressed_type() { - let mut p = program(); - p.nat_type = None; - let mut c = Checker::new(&p, Limits::default()); - assert!(matches!( - c.analyze(20, &Context::default(), &Expr::nat(0), None, 0, Uses::Linear), - Err(Error::Unsupported(_)) - )); - } - - fn policy() -> Policy { - Policy { - assumptions: vec![1, 4, 6, 8, 9, 10], - shareable_types: vec![0, 7], - choices: vec![6, 9, 10], - } - } - - #[test] - fn admission_binds_external_contracts_behavior_and_representations() { - let p = program(); - assert!(admit_program(&p, &policy(), Limits::default()).is_ok()); - let mut missing = policy(); - missing.assumptions = vec![6, 9, 10]; - assert!(admit_program(&p, &missing, Limits::default()).is_err()); - let mut missing = policy(); - missing.choices.clear(); - assert!(admit_program(&p, &missing, Limits::default()).is_err()); - let mut missing = policy(); - missing.shareable_types.clear(); - assert!(admit_program(&p, &missing, Limits::default()).is_err()); - } - - #[test] - fn relevance_scans_shared_dags_and_rejects_cycles() { - let mut sharing = vec![Expr::sort(0)]; - for i in 0..40 { - sharing.push(Expr::app(Expr::share(i), Expr::share(i))); - } - let mut p = Program { - declarations: vec![Declaration { - typ: Expr::sort(0), - body: Some(Expr::share(40)), - kind: DeclKind::Definition, - }], - sharing, - ..Default::default() - }; - assert_eq!( - relevance(&p, Limits { depth: 1, steps: 200 }).unwrap(), - vec![false] - ); - p.sharing = vec![Expr::share(0)]; - p.declarations[0].body = Some(Expr::share(0)); - assert!(relevance(&p, Limits::default()).is_err()); - } - - #[test] - fn relevance_reaches_aliases_and_mutual_siblings() { - let mut p = program(); - let typ = - unary(b(Uses::Many, ValueContract::shared()), ValueContract::shared()); - p.declarations.push(Declaration { - typ: typ.clone(), - body: Some(Expr::reference(2, vec![])), - kind: DeclKind::Definition, - }); - p.declarations.push(Declaration { - typ, - body: Some(Expr::reference(11, vec![])), - kind: DeclKind::Definition, - }); - let marked = relevance(&p, Limits::default()).unwrap(); - assert!(marked[11] && marked[12]); - let rejected = admit_program(&p, &policy(), Limits::default()).unwrap_err(); - assert_eq!(rejected.declaration, Some(11)); - assert_eq!(rejected.error, Error::TypeMismatch); - let mut p = program(); - p.declarations.push(Declaration { - typ: nat(), - body: Some(Expr::nat(0)), - kind: DeclKind::Definition, - }); - p.groups.push(vec![2, 11]); - assert!(relevance(&p, Limits::default()).unwrap()[11]); - assert!(admit_program(&p, &policy(), Limits::default()).is_ok()); - } - - #[test] - fn constructors_keep_captures_in_all_results() { - let linear = b(Uses::Linear, ValueContract::shared()); - let many = b(Uses::Many, ValueContract::shared()); - let local = b(Uses::Many, ValueContract::local_shared()); - for (typ, allowed) in [ - (unary(linear, ValueContract::shared()), false), - (unary(linear, ValueContract::unique()), true), - (unary(local, ValueContract::shared()), false), - (unary(local, ValueContract::local_shared()), true), - ( - arr( - linear, - ValueContract::unique(), - nat(), - unary(many, ValueContract::shared()), - ), - false, - ), - ( - arr( - linear, - ValueContract::unique(), - nat(), - unary(many, ValueContract::unique()), - ), - true, - ), - ( - arr( - b(Uses::Erased, ValueContract::shared()), - ValueContract::shared(), - Expr::sort(0), - nat(), - ), - true, - ), - (arr(linear, ValueContract::unique(), Expr::sort(0), nat()), false), - ] { - let mut p = program(); - p.declarations.push(Declaration { - typ, - body: None, - kind: DeclKind::Constructor, - }); - assert_eq!( - admit_program(&p, &policy(), Limits::default()).is_ok(), - allowed - ); - } - } -} diff --git a/crates/ixon/src/resource/addressed.rs b/crates/ixon/src/resource/addressed.rs deleted file mode 100644 index 818224069..000000000 --- a/crates/ixon/src/resource/addressed.rs +++ /dev/null @@ -1,1053 +0,0 @@ -//! Addressed resource interfaces. Mirrors `Ix/Resource/Addressed.lean`. -//! Resource admission and erased typing are separate prerequisites; `prepare` -//! resolves bytes and interfaces but deliberately creates no certificate. - -use super::*; -use crate::constant::{ - Constant, ConstantInfo, Definition, MutConst, Recursor, ctor_proj_constant, - defn_proj_constant, indc_proj_constant, recr_proj_constant, -}; -use crate::env::Env; -use crate::tag::TagN; -use ix_common::address::Address; -use ix_common::env::DefinitionSafety; -use rustc_hash::FxHashMap; - -pub const VALIDATOR_ID: &str = "ixon-v4/resource-v1"; - -/// Immutable raw entries for resource preparation. The existing adapter below -/// authenticates and canonically decodes each entry independently of its cache. -pub trait RawSource { - fn constants(&self) -> Vec<(Address, crate::lazy::LazyConstant)>; - fn blobs(&self) -> Vec<(Address, Vec)>; -} -impl RawSource for Env { - fn constants(&self) -> Vec<(Address, crate::lazy::LazyConstant)> { - self.consts.iter().map(|r| (r.key().clone(), r.value().clone())).collect() - } - fn blobs(&self) -> Vec<(Address, Vec)> { - self.blobs.iter().map(|r| (r.key().clone(), r.value().clone())).collect() - } -} - -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct Profile { - pub assumptions: Vec
, - pub shareable_types: Vec
, - pub choices: Vec
, - pub nat_type: Option
, - pub string_type: Option
, - pub limits: Limits, -} - -impl Profile { - pub fn validate(&self) -> Result<(), String> { - for list in [&self.assumptions, &self.shareable_types, &self.choices] { - if list.windows(2).any(|pair| pair[0] >= pair[1]) { - return Err( - "resource profile: address lists must be strictly increasing".into(), - ); - } - } - if self.choices.iter().any(|c| !self.assumptions.contains(c)) { - return Err( - "resource profile: selection primitive must be an explicit assumption" - .into(), - ); - } - if self.limits.depth == 0 || self.limits.steps == 0 { - return Err("resource profile: invalid checker limits".into()); - } - Ok(()) - } - - pub fn bytes(&self) -> Vec { - let mut bytes = format!("{VALIDATOR_ID}/profile\0").into_bytes(); - for list in [&self.assumptions, &self.shareable_types, &self.choices] { - TagN::put(0, 0, list.len() as u64, &mut bytes); - for address in list { - bytes.extend_from_slice(address.as_bytes()); - } - } - for address in [&self.nat_type, &self.string_type] { - match address { - None => bytes.push(0), - Some(address) => { - bytes.push(1); - bytes.extend_from_slice(address.as_bytes()); - }, - } - } - TagN::put(0, 0, self.limits.depth as u64, &mut bytes); - TagN::put(0, 0, self.limits.steps as u64, &mut bytes); - bytes - } - - pub fn address(&self) -> Result { - self.validate()?; - Ok(Address::hash(&self.bytes())) - } - - pub fn from_bytes(mut bytes: &[u8]) -> Result { - let prefix = format!("{VALIDATOR_ID}/profile\0"); - bytes = bytes - .strip_prefix(prefix.as_bytes()) - .ok_or("resource profile: wrong validator identifier")?; - fn list(bytes: &mut &[u8]) -> Result, String> { - let count = TagN::get(0, bytes)?.value; - if count > (bytes.len() / 32) as u64 { - return Err("resource profile: impossible address count".into()); - } - let mut result = vec![]; - for _ in 0..count { - let (head, tail) = bytes.split_at(32); - result.push( - Address::from_slice(head) - .map_err(|_error| "resource profile: invalid address")?, - ); - *bytes = tail; - } - Ok(result) - } - fn optional(bytes: &mut &[u8]) -> Result, String> { - match crate::serialize::get_u8(bytes)? { - 0 => Ok(None), - 1 => { - let (head, tail) = bytes - .split_at_checked(32) - .ok_or("resource profile: truncated address")?; - *bytes = tail; - Ok(Some( - Address::from_slice(head) - .map_err(|_error| "resource profile: invalid address")?, - )) - }, - _ => Err("resource profile: invalid optional address tag".into()), - } - } - let profile = Self { - assumptions: list(&mut bytes)?, - shareable_types: list(&mut bytes)?, - choices: list(&mut bytes)?, - nat_type: optional(&mut bytes)?, - string_type: optional(&mut bytes)?, - limits: Limits { - depth: usize::try_from(TagN::get(0, &mut bytes)?.value) - .map_err(|_error| "resource profile: depth overflow")?, - steps: usize::try_from(TagN::get(0, &mut bytes)?.value) - .map_err(|_error| "resource profile: steps overflow")?, - }, - }; - if !bytes.is_empty() { - return Err("resource profile: trailing bytes".into()); - } - profile.validate()?; - Ok(profile) - } -} - -#[derive(Clone, Debug)] -pub struct AddressedProgram { - pub program: Program, - pub policy: Policy, - pub addresses: Vec
, - pub profile_address: Address, -} - -struct Pending { - address: Address, - parent: Address, - constant: Arc, - members: Vec
, - declaration: Declaration, - auxiliary: Vec, -} - -fn definition(d: &Definition) -> Declaration { - Declaration { - typ: d.typ.clone(), - body: Some(d.value.clone()), - kind: if d.safety == DefinitionSafety::Safe { - DeclKind::Definition - } else { - DeclKind::Assumption - }, - } -} - -fn recursor(d: &Recursor) -> Declaration { - Declaration { typ: d.typ.clone(), body: None, kind: DeclKind::Assumption } -} - -fn member_wrapper(block: &Address, index: u64, member: &MutConst) -> Constant { - match member { - MutConst::Defn(_) => defn_proj_constant(index, block.clone()), - MutConst::Indc(_) => indc_proj_constant(index, block.clone()), - MutConst::Recr(_) => recr_proj_constant(index, block.clone()), - } -} - -fn require_wrapper( - constants: &FxHashMap>, - expected: &Constant, -) -> Result { - let address = expected.commit().0; - match constants.get(&address) { - Some(actual) if actual.as_ref() == expected => Ok(address), - _ => Err(format!( - "resource adapter: missing or invalid canonical projection {address:?}" - )), - } -} - -fn index( - indices: &FxHashMap, - address: &Address, -) -> Result { - indices.get(address).copied().ok_or_else(|| { - format!("resource adapter: missing typed interface {address:?}") - }) -} - -fn native_index(index: u64) -> Result { - usize::try_from(index) - .map_err(|_error| "resource adapter: index overflow".into()) -} - -struct Tables<'a> { - refs: &'a [Address], - members: &'a [Address], - indices: &'a FxHashMap, - kinds: &'a [DeclKind], - blobs: &'a FxHashMap, - univ_count: usize, - share_count: usize, - share_offset: usize, -} - -impl Tables<'_> { - fn reference(&self, i: u64) -> Result<&Address, String> { - let i = usize::try_from(i) - .map_err(|_error| "resource adapter: reference index overflow")?; - self - .refs - .get(i) - .ok_or_else(|| "resource adapter: reference index out of bounds".into()) - } - - fn universes(&self, levels: &[u64]) -> Result<(), String> { - if levels.iter().any(|l| *l >= self.univ_count as u64) { - return Err("resource adapter: universe index out of bounds".into()); - } - Ok(()) - } - - fn remap(&self, fuel: usize, e: &Term) -> Result { - let fuel = fuel - .checked_sub(1) - .ok_or("resource adapter: expression depth exceeded")?; - Ok(Arc::new(match e.as_ref() { - Expr::Var(_) => return Ok(e.clone()), - Expr::Sort(level) => { - self.universes(&[*level])?; - return Ok(e.clone()); - }, - Expr::Ref(i, levels) => { - self.universes(levels)?; - Expr::Ref(index(self.indices, self.reference(*i)?)?, levels.clone()) - }, - Expr::Rec(i, levels) => { - self.universes(levels)?; - let i = usize::try_from(*i) - .map_err(|_error| "resource adapter: recursive index overflow")?; - let address = self - .members - .get(i) - .ok_or("resource adapter: recursive index out of bounds")?; - Expr::Ref(index(self.indices, address)?, levels.clone()) - }, - Expr::Nat(i) | Expr::Str(i) => { - let address = self.reference(*i)?; - let id = *self.blobs.get(address).ok_or_else(|| { - format!("resource adapter: missing literal blob {address:?}") - })?; - match e.as_ref() { - Expr::Nat(_) => Expr::Nat(id), - _ => Expr::Str(id), - } - }, - Expr::Share(i) => { - let i = usize::try_from(*i) - .map_err(|_error| "resource adapter: sharing index overflow")?; - if i >= self.share_count { - return Err("resource adapter: sharing index out of bounds".into()); - } - Expr::Share( - self - .share_offset - .checked_add(i) - .and_then(|n| u64::try_from(n).ok()) - .ok_or("resource adapter: index overflow")?, - ) - }, - Expr::Prj(i, field, value) => { - let id = index(self.indices, self.reference(*i)?)?; - if self.kinds.get(native_index(id)?) != Some(&DeclKind::TypeConstructor) - { - return Err( - "resource adapter: projection owner is not an inductive type" - .into(), - ); - } - Expr::Prj(id, *field, self.remap(fuel, value)?) - }, - Expr::App(f, a) => Expr::App(self.remap(fuel, f)?, self.remap(fuel, a)?), - Expr::Lam(c, t, b) => { - Expr::Lam(*c, self.remap(fuel, t)?, self.remap(fuel, b)?) - }, - Expr::All(c, r, t, b) => { - Expr::All(*c, *r, self.remap(fuel, t)?, self.remap(fuel, b)?) - }, - Expr::Let(c, t, v, b) => Expr::Let( - *c, - self.remap(fuel, t)?, - self.remap(fuel, v)?, - self.remap(fuel, b)?, - ), - })) - } -} - -fn closed( - checker: &mut Checker<'_>, - fuel: usize, - depth: u64, - e: &Term, -) -> Result { - let fuel = checker.tick(fuel)?; - Ok(match e.as_ref() { - Expr::Var(i) => *i < depth, - Expr::Share(i) => closed(checker, fuel, depth, &checker.expansion(*i)?)?, - Expr::Rec(..) => false, - Expr::App(f, a) => { - closed(checker, fuel, depth, f)? && closed(checker, fuel, depth, a)? - }, - Expr::Lam(_, t, b) | Expr::All(_, _, t, b) => { - closed(checker, fuel, depth, t)? && closed(checker, fuel, depth + 1, b)? - }, - Expr::Let(_, t, v, b) => { - closed(checker, fuel, depth, t)? - && closed(checker, fuel, depth, v)? - && closed(checker, fuel, depth + 1, b)? - }, - Expr::Prj(_, _, v) => closed(checker, fuel, depth, v)?, - _ => true, - }) -} - -fn fields( - program: &Program, - limits: Limits, - type_ref: u64, - ctor: u64, - count: u64, -) -> Result, Error> { - let mut checker = Checker::new(program, limits); - let mut typ = checker.declaration(ctor)?.typ.clone(); - let mut fields = vec![]; - for index in 0..count { - typ = checker.whnf(limits.depth, &typ)?; - let Expr::All(input, _, domain, body) = typ.as_ref() else { - return Err(Error::Unsupported("constructor field telescope")); - }; - if input.uses != Uses::Many - || !closed(&mut checker, limits.depth, 0, domain)? - { - return Err(Error::Unsupported( - "dependent or finite-use projection field", - )); - } - fields.push(Field { - type_ref, - index, - typ: domain.clone(), - contract: input.value, - }); - typ = body.clone(); - } - Ok(fields) -} - -pub fn prepare( - env: &(impl RawSource + ?Sized), - profile: &Profile, -) -> Result { - let profile_address = profile.address()?; - let mut entries = env.constants(); - entries.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - let mut constants = FxHashMap::default(); - for (address, lazy) in &entries { - let raw = lazy.raw_bytes(); - if Address::hash(raw) != *address { - return Err(format!( - "resource adapter: constant hash mismatch {address:?}" - )); - } - let mut input = raw; - let constant = Constant::get(&mut input)?; - let mut canonical = vec![]; - constant.put(&mut canonical); - if !input.is_empty() || canonical != raw { - return Err(format!( - "resource adapter: noncanonical constant {address:?}" - )); - } - constants.insert(address.clone(), Arc::new(constant)); - } - let mut pending = vec![]; - let mut groups = vec![]; - let mut field_specs = vec![]; - for (address, _) in &entries { - let constant = &constants[address]; - let parent = address.clone(); - let mut push = |address: Address, - members: Vec
, - declaration: Declaration, - auxiliary: Vec| { - pending.push(Pending { - address, - parent: parent.clone(), - constant: constant.clone(), - members, - declaration, - auxiliary, - }); - }; - match &constant.info { - ConstantInfo::Defn(d) => { - push(address.clone(), vec![address.clone()], definition(d), vec![]) - }, - ConstantInfo::Recr(r) => push( - address.clone(), - vec![address.clone()], - recursor(r), - r.rules.iter().map(|r| r.rhs.clone()).collect(), - ), - ConstantInfo::Axio(a) => push( - address.clone(), - vec![], - Declaration { - typ: a.typ.clone(), - body: None, - kind: DeclKind::Assumption, - }, - vec![], - ), - ConstantInfo::Quot(q) => push( - address.clone(), - vec![], - Declaration { - typ: q.typ.clone(), - body: None, - kind: DeclKind::Assumption, - }, - vec![], - ), - ConstantInfo::Muts(members) => { - if members.is_empty() { - return Err("resource adapter: empty mutual block".into()); - } - let addresses = members - .iter() - .enumerate() - .map(|(i, m)| { - require_wrapper(&constants, &member_wrapper(address, i as u64, m)) - }) - .collect::, _>>()?; - let mut group = addresses.clone(); - for (i, member) in members.iter().enumerate() { - let addr = &addresses[i]; - match member { - MutConst::Defn(d) => { - push(addr.clone(), addresses.clone(), definition(d), vec![]) - }, - MutConst::Recr(r) => push( - addr.clone(), - addresses.clone(), - recursor(r), - r.rules.iter().map(|r| r.rhs.clone()).collect(), - ), - MutConst::Indc(ind) => { - push( - addr.clone(), - addresses.clone(), - Declaration { - typ: ind.typ.clone(), - body: None, - kind: if ind.is_unsafe { - DeclKind::Assumption - } else { - DeclKind::TypeConstructor - }, - }, - vec![], - ); - for (j, ctor) in ind.ctors.iter().enumerate() { - let ctor_addr = require_wrapper( - &constants, - &ctor_proj_constant(i as u64, j as u64, address.clone()), - )?; - if ctor.cidx != j as u64 - || ctor.params != ind.params - || ctor.lvls != ind.lvls - { - return Err( - "resource adapter: inconsistent constructor header".into(), - ); - } - push( - ctor_addr.clone(), - addresses.clone(), - Declaration { - typ: ctor.typ.clone(), - body: None, - kind: if ctor.is_unsafe { - DeclKind::Assumption - } else { - DeclKind::Constructor - }, - }, - vec![], - ); - group.push(ctor_addr.clone()); - if !ind.is_unsafe - && !ctor.is_unsafe - && ind.params == 0 - && ind.indices == 0 - && ind.ctors.len() == 1 - { - field_specs.push((addr.clone(), ctor_addr, ctor.fields)); - } - } - }, - } - } - groups.push(group); - }, - _ => {}, - } - } - pending.sort_unstable_by(|a, b| a.address.cmp(&b.address)); - let mut indices = FxHashMap::default(); - let mut addresses = vec![]; - for (i, p) in pending.iter().enumerate() { - if indices.insert(p.address.clone(), i as u64).is_some() { - return Err("resource adapter: duplicate resolved declaration".into()); - } - addresses.push(p.address.clone()); - } - for (address, constant) in &constants { - if matches!( - constant.info, - ConstantInfo::DPrj(_) - | ConstantInfo::IPrj(_) - | ConstantInfo::RPrj(_) - | ConstantInfo::CPrj(_) - ) && !indices.contains_key(address) - { - return Err(format!( - "resource adapter: invalid or orphan projection {address:?}" - )); - } - } - let mut blob_entries = env.blobs(); - blob_entries.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - let mut blobs = FxHashMap::default(); - for (address, bytes) in blob_entries { - if Address::hash(&bytes) != address { - return Err(format!("resource adapter: blob hash mismatch {address:?}")); - } - blobs.insert(address, blobs.len() as u64); - } - let kinds: Vec<_> = pending.iter().map(|p| p.declaration.kind).collect(); - let mut program = Program::default(); - let mut offsets = FxHashMap::default(); - for p in &pending { - let tables = Tables { - refs: &p.constant.refs, - members: &p.members, - indices: &indices, - kinds: &kinds, - blobs: &blobs, - univ_count: p.constant.univs.len(), - share_count: p.constant.sharing.len(), - share_offset: offsets - .get(&p.parent) - .copied() - .unwrap_or(program.sharing.len()), - }; - if offsets.insert(p.parent.clone(), tables.share_offset).is_none() { - for e in &p.constant.sharing { - program.sharing.push(tables.remap(profile.limits.depth, e)?); - } - } - let i = program.declarations.len(); - program.declarations.push(Declaration { - typ: tables.remap(profile.limits.depth, &p.declaration.typ)?, - body: p - .declaration - .body - .as_ref() - .map(|e| tables.remap(profile.limits.depth, e)) - .transpose()?, - kind: p.declaration.kind, - }); - for e in &p.auxiliary { - program.auxiliary.push((i, tables.remap(profile.limits.depth, e)?)); - } - } - program.groups = groups - .iter() - .map(|g| { - g.iter().map(|a| index(&indices, a).and_then(native_index)).collect() - }) - .collect::>()?; - let resolve = |list: &[Address]| { - list.iter().map(|a| index(&indices, a)).collect::, String>>() - }; - let assumptions = resolve(&profile.assumptions)?; - let shareable = resolve(&profile.shareable_types)?; - let choices = resolve(&profile.choices)?; - let literal_type = |a: &Address| -> Result { - let i = index(&indices, a)?; - if kinds.get(native_index(i)?) != Some(&DeclKind::TypeConstructor) { - return Err( - "resource adapter: literal type must be a checked nominal type".into(), - ); - } - Ok(Expr::reference(i, vec![])) - }; - program.nat_type = profile.nat_type.as_ref().map(literal_type).transpose()?; - program.string_type = - profile.string_type.as_ref().map(literal_type).transpose()?; - program.shareable_types = shareable.clone(); - program.choices = choices.clone(); - for (typ, ctor, count) in field_specs { - if let Ok(derived) = fields( - &program, - profile.limits, - index(&indices, &typ)?, - index(&indices, &ctor)?, - count, - ) { - program.fields.extend(derived); - } - } - Ok(AddressedProgram { - program, - addresses, - profile_address, - policy: Policy { - assumptions: assumptions - .into_iter() - .map(native_index) - .collect::>()?, - shareable_types: shareable - .into_iter() - .map(native_index) - .collect::>()?, - choices: choices - .into_iter() - .map(native_index) - .collect::>()?, - }, - }) -} - -/// Resource-only admission; the combined validator also checks erased typing. -pub fn check_resources( - env: &(impl RawSource + ?Sized), - profile: &Profile, -) -> Result { - let resolved = prepare(env, profile)?; - admit_program(&resolved.program, &resolved.policy, profile.limits).map_err( - |error| { - format!( - "resource admission {:?}: {:?}", - error.declaration.and_then(|i| resolved.addresses.get(i)), - error.error - ) - }, - )?; - Ok(resolved) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::constant::{Axiom, Constructor, DefKind, Inductive, RecursorRule}; - use crate::univ::Univ; - - /// Validator identity follows checked grammar, independently of the storage - /// envelope version. - #[test] - fn validator_id_follows_the_wire_format() { - assert_eq!( - crate::WIRE_FORMAT_ID, - format!("ixon-v{}", Env::CHECKED_FORMAT_VERSION) - ); - assert_eq!(VALIDATOR_ID, format!("{}/resource-v1", crate::WIRE_FORMAT_ID)); - } - - fn store(env: &Env, c: Constant) -> Address { - let addr = c.commit().0; - env.store_const(addr.clone(), c); - addr - } - fn unit_block() -> Constant { - Constant::with_tables( - ConstantInfo::Muts(vec![MutConst::Indc(Inductive { - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - typ: Expr::sort(0), - ctors: vec![Constructor { - is_unsafe: false, - lvls: 0, - cidx: 0, - params: 0, - fields: 0, - typ: Expr::rec(0, vec![]), - }], - })]), - vec![], - vec![], - vec![Univ::zero()], - ) - } - fn unit_env() -> (Env, Address) { - let env = Env::new(); - let block = store(&env, unit_block()); - let typ = store(&env, indc_proj_constant(0, block.clone())); - store(&env, ctor_proj_constant(0, 0, block)); - (env, typ) - } - fn linear_local() -> BinderContract { - BinderContract { uses: Uses::Linear, value: ValueContract::local_shared() } - } - fn identity( - unit: &Address, - input: BinderContract, - result: ValueContract, - ) -> Constant { - Constant::with_tables( - ConstantInfo::Defn(Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: Expr::all_contract( - input, - result, - Expr::reference(0, vec![]), - Expr::reference(0, vec![]), - ), - value: Expr::lam_contract( - input, - Expr::reference(0, vec![]), - Expr::share(0), - ), - }), - vec![Expr::var(0)], - vec![unit.clone()], - vec![], - ) - } - fn profile(unit: &Address) -> Profile { - Profile { - nat_type: Some(unit.clone()), - shareable_types: vec![unit.clone()], - ..Default::default() - } - } - - #[test] - fn interfaces_profiles_and_integrity() { - let (env, unit) = unit_env(); - let p = profile(&unit); - let id_const = - identity(&unit, linear_local(), ValueContract::local_shared()); - let id = store(&env, id_const.clone()); - let prepared = check_resources(&env, &p).unwrap(); - assert_eq!(prepared.program.declarations.len(), 3); - assert_eq!(prepared.program.groups.len(), 1); - assert!( - prepare(&env, &Profile { nat_type: Some(id.clone()), ..p.clone() }) - .is_err() - ); - assert!( - Profile { - shareable_types: vec![unit.clone(), unit.clone()], - ..p.clone() - } - .address() - .is_err() - ); - assert!( - Profile { choices: vec![id.clone()], ..p.clone() }.address().is_err() - ); - let (bad_escape, _) = unit_env(); - store( - &bad_escape, - identity(&unit, linear_local(), ValueContract::shared()), - ); - assert!(check_resources(&bad_escape, &p).is_err()); - let missing = Env::new(); - store(&missing, id_const.clone()); - assert!(prepare(&missing, &p).is_err()); - let bad_hash = env.clone(); - bad_hash.store_const(Address::hash(&[9]), id_const.clone()); - assert!(prepare(&bad_hash, &p).is_err()); - let (trailing, _) = unit_env(); - let mut raw = id_const.commit().1; - raw.push(0); - trailing.store_const_lazy(Address::hash(&raw), raw.into()); - assert!(prepare(&trailing, &p).is_err()); - let (orphan, _) = unit_env(); - store(&orphan, indc_proj_constant(99, unit_block().commit().0)); - assert!(prepare(&orphan, &p).is_err()); - let (missing_ctor, _) = unit_env(); - missing_ctor - .consts - .remove(&ctor_proj_constant(0, 0, unit_block().commit().0).commit().0); - assert!(prepare(&missing_ctor, &p).is_err()); - let (extra_tables, _) = unit_env(); - let mut wrapper = indc_proj_constant(0, unit_block().commit().0); - wrapper.univs.push(Univ::zero()); - store(&extra_tables, wrapper); - assert!(prepare(&extra_tables, &p).is_err()); - } - - #[test] - fn explicit_assumptions_and_rule_dependencies() { - let (env, unit) = unit_env(); - let mut p = profile(&unit); - let external = Constant::with_tables( - ConstantInfo::Axio(Axiom { - is_unsafe: false, - lvls: 0, - typ: Expr::all_contract( - linear_local(), - ValueContract::shared(), - Expr::reference(0, vec![]), - Expr::reference(0, vec![]), - ), - }), - vec![], - vec![unit.clone()], - vec![], - ); - let external = store(&env, external); - assert!(check_resources(&env, &p).is_err()); - p.assumptions.push(external); - check_resources(&env, &p).unwrap(); - let (rec_env, _) = unit_env(); - let rec = Constant::with_tables( - ConstantInfo::Recr(Recursor { - k: false, - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - motives: 0, - minors: 0, - typ: Expr::reference(0, vec![]), - rules: vec![RecursorRule { - fields: 0, - rhs: Expr::lam_contract( - linear_local(), - Expr::reference(0, vec![]), - Expr::var(0), - ), - }], - }), - vec![], - vec![unit.clone()], - vec![], - ); - let rec = store(&rec_env, rec); - assert!(check_resources(&rec_env, &profile(&unit)).is_err()); - check_resources( - &rec_env, - &Profile { assumptions: vec![rec], ..profile(&unit) }, - ) - .unwrap(); - } - - #[test] - fn global_blob_identities_and_integrity() { - let (env, unit) = unit_env(); - for byte in [0, 1] { - let blob = env.store_blob(vec![byte]); - store( - &env, - Constant::with_tables( - ConstantInfo::Defn(Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: Expr::reference(0, vec![]), - value: Expr::nat(1), - }), - vec![], - vec![unit.clone(), blob], - vec![], - ), - ); - } - let resolved = prepare(&env, &profile(&unit)).unwrap(); - let bodies: Vec<_> = resolved - .program - .declarations - .iter() - .filter_map(|d| d.body.as_ref()) - .collect(); - assert_eq!(bodies.len(), 2); - assert_ne!(bodies[0], bodies[1]); - env.blobs.insert(Address::hash(&[9]), vec![0]); - assert!(prepare(&env, &profile(&unit)).is_err()); - } - - #[test] - fn mutual_recursive_interfaces() { - let (env, unit) = unit_env(); - let input = - BinderContract { uses: Uses::Linear, value: ValueContract::unique() }; - let typ = Expr::all_contract( - input, - ValueContract::unique(), - Expr::reference(0, vec![]), - Expr::reference(0, vec![]), - ); - let members = [1, 0] - .map(|i| { - MutConst::Defn(Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: typ.clone(), - value: Expr::lam_contract( - input, - Expr::reference(0, vec![]), - Expr::app(Expr::rec(i, vec![]), Expr::var(0)), - ), - }) - }) - .to_vec(); - let block = store( - &env, - Constant::with_tables( - ConstantInfo::Muts(members), - vec![], - vec![unit.clone()], - vec![], - ), - ); - store(&env, defn_proj_constant(0, block.clone())); - store(&env, defn_proj_constant(1, block)); - let resolved = check_resources(&env, &profile(&unit)).unwrap(); - assert_eq!(resolved.program.groups.len(), 2); - } - - #[test] - fn fields_are_derived_from_constructor_interfaces() { - let (env, unit) = unit_env(); - let unique = - BinderContract { uses: Uses::Many, value: ValueContract::unique() }; - let typ = Expr::all_contract( - unique, - ValueContract::unique(), - Expr::reference(0, vec![]), - Expr::rec(0, vec![]), - ); - let block = store( - &env, - Constant::with_tables( - ConstantInfo::Muts(vec![MutConst::Indc(Inductive { - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - typ: Expr::sort(0), - ctors: vec![Constructor { - is_unsafe: false, - lvls: 0, - cidx: 0, - params: 0, - fields: 1, - typ, - }], - })]), - vec![], - vec![unit.clone()], - vec![Univ::zero()], - ), - ); - let nominal = store(&env, indc_proj_constant(0, block.clone())); - store(&env, ctor_proj_constant(0, 0, block)); - let resolved = check_resources(&env, &profile(&unit)).unwrap(); - assert_eq!(resolved.program.fields.len(), 1); - let field = &resolved.program.fields[0]; - assert_eq!( - resolved.addresses[usize::try_from(field.type_ref).unwrap()], - nominal - ); - assert_eq!(field.contract, ValueContract::unique()); - } - - #[test] - fn canonical_cross_language_fixtures() { - let (_, unit) = unit_env(); - let cases = [ - ("unit", unit_block()), - ( - "identity", - identity(&unit, linear_local(), ValueContract::local_shared()), - ), - ( - "identity_unique", - identity( - &unit, - BinderContract { uses: Uses::Linear, value: ValueContract::unique() }, - ValueContract::unique(), - ), - ), - ]; - let fixture = - include_str!("../../../../Tests/Fixtures/ixon-v4/addressed.tsv"); - for (name, constant) in cases { - let (address, bytes) = constant.commit(); - let hex: String = bytes.iter().map(|b| format!("{b:02x}")).collect(); - let line = format!("{name}\t{}\t{hex}", address.hex()); - assert!(fixture.lines().any(|s| s == line), "fixture mismatch: {line}"); - } - let p = profile(&unit); - let hex: String = p.bytes().iter().map(|b| format!("{b:02x}")).collect(); - let line = format!("profile\t{}\t{hex}", p.address().unwrap().hex()); - assert!( - fixture.lines().any(|s| s == line), - "profile fixture mismatch: {line}" - ); - let bytes = p.bytes(); - assert_eq!(Profile::from_bytes(&bytes).unwrap(), p); - for length in 0..bytes.len() { - assert!(Profile::from_bytes(&bytes[..length]).is_err()); - } - let mut trailing = bytes.clone(); - trailing.push(0); - assert!(Profile::from_bytes(&trailing).is_err()); - let mut wrong_validator = bytes; - wrong_validator[0] ^= 1; - assert!(Profile::from_bytes(&wrong_validator).is_err()); - } -} diff --git a/crates/ixon/src/serialize.rs b/crates/ixon/src/serialize.rs index a80539d8e..166b1ef8c 100644 --- a/crates/ixon/src/serialize.rs +++ b/crates/ixon/src/serialize.rs @@ -1966,11 +1966,9 @@ impl Env { } /// [`Env::put_file`] with the sorted const-address table and its - /// merkle root supplied by the caller. `rs_compile_env` needs the - /// root even on a fail-closed abort where no file is written, so it - /// computes both up front and hands them in here — previously the - /// table was collected+sorted and the ~2·N-node tree hashed twice - /// per compile. `const_addrs` MUST be exactly + /// merkle root supplied by a caller that already has them (the + /// catalog merge), so the table is not collected and sorted, nor the + /// ~2·N-node tree hashed, a second time. `const_addrs` MUST be exactly /// `self.consts` keys lex-sorted, and `root` their canonical merkle /// root; debug builds assert the former. #[cfg(not(target_arch = "riscv64"))] diff --git a/crates/kernel/src/shard.rs b/crates/ixon/src/shard.rs similarity index 78% rename from crates/kernel/src/shard.rs rename to crates/ixon/src/shard.rs index 828e45c6a..ec5244db1 100644 --- a/crates/kernel/src/shard.rs +++ b/crates/ixon/src/shard.rs @@ -1,67 +1,5 @@ -//! Sharding: partition an environment's blocks into shards that minimize -//! cross-shard delta-unfold ingress (see `plans/sharding.md`). Two driving -//! modes share the same min-cut machinery: -//! -//! - **Fixed count** ([`Hypergraph::partition`], explicit `--shards N` and the -//! static strategy's `--max-ram` score seed): recursive *balanced* min-cut -//! bisection into exactly `N` shards — even per-shard work, the bisection tree -//! doubling as the aggregation tree. -//! - **Cap / packing** ([`partition_for_cycle_cap`], the profiled strategy's -//! default and `--max-cycles`/`--max-ram` paths): **bin-pack to the cap** — the -//! fewest shards that each stay under a per-shard cycle (hence prover-RAM) -//! budget, each filled as full as the dependency structure allows. This does -//! *not* balance: uniformity over-shards (every shard left partly empty), -//! whereas packing hits `≈⌈total/cap⌉` shards. It still uses a fine min-cut -//! pre-partition for a **cut-coherent order** so dependency overlap packs into -//! the same shard (overlap paid once, not re-ingressed per shard). -//! -//! Min-cut keeps dependency-overlapping blocks together, reducing repeated -//! ingress and bounding each shard's injected closure. The cap planner -//! ([`partition_for_cycle_cap`]) is order-agnostic, so the ordering remains a -//! pluggable choice. -//! -//! ## Model -//! -//! Both modes view the env as a **weighted hypergraph** under the -//! connectivity-1 (km1) metric: -//! -//! - **Vertices** are blocks, weighted by `heartbeats` (the balance metric). -//! - **Nets** are blocks `p` that get delta-unfolded by someone; the net -//! connects `p`'s home plus every block that unfolds `p`, and is weighted by -//! `serialized_size(p)` (the ingress cost paid to pull `p`'s body into a -//! foreign shard). -//! - The objective is `Σ_p size(p) · (λ(p) − 1)`, where `λ(p)` is the number -//! of distinct shards spanning `net(p)`. Because the home block is always a -//! pin, `λ(p) − 1` counts exactly the foreign shards that must ingress `p` — -//! i.e. total cross-shard ingress bytes. -//! -//! ## Algorithm -//! -//! **Recursive bisection.** Balanced min-cut bisection recursively splits the -//! block hypergraph, allocating the `N`-shard budget between the two sides at -//! each step (so any `N` works, not just powers of two); the bisection tree is -//! the future proof-aggregation tree. On recursion we apply **cut-net -//! splitting**: a net already cut at an upper level is restricted to its pins -//! within each half so it is not recounted deeper. -//! -//! **Multilevel coarsening.** Each individual bisection is solved with a -//! multilevel V-cycle rather than flat local search: we *coarsen* the -//! sub-hypergraph by repeatedly merging tightly-coupled blocks into -//! supervertices (heavy-edge matching) until only a few hundred remain, decide -//! the cut once on that tiny graph (greedy graph-growing + Fiduccia–Mattheyses -//! to convergence), then *uncoarsen* — projecting the cut back down and -//! boundary-refining with FM at each level. Deciding global structure on the -//! small graph and only ever refining locally on the big ones is what keeps -//! large environments (mathlib ≈ 631k blocks) partitioning in seconds. The -//! partitioner is fully self-contained — no external partitioner dependency. - -// This module works throughout with `u32` block/vertex ids, `u64`/`u128` weights -// (heartbeats, ingress bytes), and `f64` balance ratios. The narrowing/precision -// casts between them are pervasive and intentional — envs are far below `u32` -// limits and the balance arithmetic tolerates `f64` rounding — so the lossy-cast -// lints are allowed module-wide rather than annotated at ~30 sites. The binary -// manifest decoder maps decode failures to a single message (`map_err_ignore`), -// and a few matching loops mutate the array they index (`needless_range_loop`). +//! Static DAG hypergraph sharder and sub-environment extraction. + #![allow( clippy::cast_possible_truncation, clippy::cast_precision_loss, @@ -70,13 +8,15 @@ clippy::needless_range_loop )] -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use std::cmp::Reverse; use std::collections::BinaryHeap; use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; use std::time::Instant; -use crate::profile::{BlockEntry, BlockProfile}; +use crate::constant::ConstantInfo as IxonCI; +use crate::env::Env as IxonEnv; +use crate::lazy::ConstVariantTag; use ix_common::address::Address; /// Recurse the two halves of a bisection in parallel (rayon) when the parent @@ -134,14 +74,846 @@ const FM_NET_CAP: usize = 400; /// oscillation; passes normally converge well before this). const MAX_FM_PASSES: u32 = 10; -/// Cap-sharding granularity: the fine min-cut pre-partition that supplies the -/// **cut-coherent block order** for [`partition_for_cycle_cap`] is sized to -/// roughly this many pieces per cap of work. Finer pieces let the greedy packer -/// fill each shard closer to the cap and keep dependency-overlapping blocks in -/// the same run (so cross-ingress only appears at the seams between coherent -/// runs); the count is still bounded by the block count, so the pre-partition -/// stays cheap. -const PACK_PIECES_PER_CAP: u128 = 32; +// =========================================================================== +// Cost model +// =========================================================================== + +/// Calibrated instruction weight per substitution step. +pub const COST_PER_SUBST: u64 = 196_600; +/// Calibrated instruction weight per WHNF step. +pub const COST_PER_WHNF: u64 = 1_797_600; +/// Calibrated instruction weight per definitional-equality step. +pub const COST_PER_DEF_EQ: u64 = 567_100; +/// Calibrated instruction weight per arena-interning step. +pub const COST_PER_INTERN: u64 = 28_400; +/// Fraction of host RAM considered usable when sizing shards. +pub const RAM_USABLE_FRAC: f64 = 0.85; + +/// Compute the weighted cycle estimate for a single [`BlockEntry`]. +pub fn block_step_cost(b: &BlockEntry) -> u64 { + 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)) +} + +#[derive(Default, Debug, Clone, Copy)] +pub struct OpCounts { + pub subst_nodes: u64, + 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); + } +} + +/// 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 = 2; + +/// Per-block recorded statistics. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BlockEntry { + /// Content address of the block (a `Muts` block or a standalone constant). + pub addr: Address, + /// Total recursive fuel (heartbeats) consumed checking this block's members. + pub heartbeats: u64, + /// Serialized byte length of the block (ingress cost / net weight). + pub serialized_size: u32, + /// Number of constants in the block (1 for standalone constants). + pub const_count: u32, + /// Substitution-node visits (`instantiate_rev`) checking this block — the + /// 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. +/// +/// Blocks are indexed by stable id `0..num_blocks`. The delta graph is stored +/// in CSR form: `producers(c)` are the block ids whose definition bodies block +/// `c` delta-unfolds (the "consumer → producer" direction). Self-edges are +/// dropped; producer lists are sorted and deduplicated. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BlockProfile { + blocks: Vec, + /// CSR row offsets into `delta_col`, length `blocks.len() + 1`. + delta_row: Vec, + /// CSR column indices: producer block ids, grouped by consumer. + delta_col: Vec, +} + +impl BlockProfile { + /// Number of blocks (vertices). + pub fn num_blocks(&self) -> usize { + self.blocks.len() + } + + /// Number of delta edges (consumer → producer pairs). + pub fn num_edges(&self) -> usize { + self.delta_col.len() + } + + /// The block entries, indexed by block id. + pub fn blocks(&self) -> &[BlockEntry] { + &self.blocks + } + + /// The entry for block id `i`. + pub fn block(&self, i: u32) -> &BlockEntry { + &self.blocks[i as usize] + } + + /// Producer block ids unfolded by consumer block `c` (sorted, deduped, no + /// self-edges). + pub fn producers(&self, c: u32) -> &[u32] { + let lo = self.delta_row[c as usize]; + let hi = self.delta_row[c as usize + 1]; + &self.delta_col[lo..hi] + } + + /// Build the reverse delta adjacency: for each producer block, the sorted set + /// of consumer blocks that unfold it. This is the natural form for the + /// partition hypergraph, where `net(p) = {p} ∪ consumers_of(p)`. + pub fn consumers_csr(&self) -> (Vec, Vec) { + let n = self.num_blocks(); + let mut counts = vec![0usize; n + 1]; + for &p in &self.delta_col { + counts[p as usize + 1] += 1; + } + for i in 0..n { + counts[i + 1] += counts[i]; + } + let row = counts.clone(); + let mut col = vec![0u32; self.delta_col.len()]; + let mut cursor = counts; + for c in 0..n as u32 { + for &p in self.producers(c) { + let slot = cursor[p as usize]; + col[slot] = c; + cursor[p as usize] += 1; + } + } + (row, col) + } + + /// Serialize to the `.ixprof` binary format. + pub fn to_bytes(&self) -> Vec { + let n = self.blocks.len(); + let mut out = Vec::with_capacity( + 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()); + out.extend_from_slice(&(n as u32).to_le_bytes()); + for b in &self.blocks { + out.extend_from_slice(b.addr.as_bytes()); + out.extend_from_slice(&b.heartbeats.to_le_bytes()); + 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. + for &off in &self.delta_row { + out.extend_from_slice(&(off as u64).to_le_bytes()); + } + for &p in &self.delta_col { + out.extend_from_slice(&p.to_le_bytes()); + } + out + } + + /// Deserialize from the `.ixprof` binary format. + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut r = Reader::new(bytes); + let magic = r.take(8)?; + if magic != MAGIC { + return Err(ProfileError::BadMagic); + } + let version = r.u32()?; + if version != VERSION { + return Err(ProfileError::BadVersion(version)); + } + let n = r.u32()? as usize; + let mut blocks = Vec::with_capacity(n); + for _ in 0..n { + let addr = Address::from_slice(r.take(32)?) + .map_err(|_| ProfileError::Truncated)?; + let heartbeats = r.u64()?; + 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; + let mut delta_row = Vec::with_capacity(n + 1); + for _ in 0..n + 1 { + delta_row.push(r.u64()? as usize); + } + let mut delta_col = Vec::with_capacity(num_edges); + for _ in 0..num_edges { + delta_col.push(r.u32()?); + } + // Structural validation: monotone offsets bounded by edge count, in-range ids. + if delta_row.len() != n + 1 + || delta_row.first() != Some(&0) + || delta_row.last() != Some(&num_edges) + { + return Err(ProfileError::Corrupt); + } + for w in delta_row.windows(2) { + if w[0] > w[1] { + return Err(ProfileError::Corrupt); + } + } + for &p in &delta_col { + if p as usize >= n { + return Err(ProfileError::Corrupt); + } + } + Ok(BlockProfile { blocks, delta_row, delta_col }) + } +} + +/// Errors from decoding a `.ixprof` file. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ProfileError { + BadMagic, + BadVersion(u32), + Truncated, + Corrupt, +} + +impl std::fmt::Display for ProfileError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProfileError::BadMagic => write!(f, "not an .ixprof file (bad magic)"), + ProfileError::BadVersion(v) => { + write!(f, "unsupported .ixprof version {v}") + }, + ProfileError::Truncated => write!(f, "truncated .ixprof file"), + ProfileError::Corrupt => write!(f, "corrupt .ixprof file"), + } + } +} + +impl std::error::Error for ProfileError {} + +/// Minimal little-endian byte reader with bounds checking. +struct Reader<'a> { + buf: &'a [u8], + pos: usize, +} + +impl<'a> Reader<'a> { + fn new(buf: &'a [u8]) -> Self { + Reader { buf, pos: 0 } + } + fn take(&mut self, n: usize) -> Result<&'a [u8], ProfileError> { + let end = self.pos.checked_add(n).ok_or(ProfileError::Truncated)?; + if end > self.buf.len() { + return Err(ProfileError::Truncated); + } + let s = &self.buf[self.pos..end]; + self.pos = end; + Ok(s) + } + fn u32(&mut self) -> Result { + Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap())) + } + fn u64(&mut self) -> Result { + Ok(u64::from_le_bytes(self.take(8)?.try_into().unwrap())) + } +} + +/// Accumulates block-level statistics and delta edges (keyed by address), then +/// freezes into a [`BlockProfile`] with stable, address-sorted block ids. +/// +/// Phase A (the kernel recorder) feeds this with one `block(..)` per checked +/// block and one `delta_edge(consumer, producer)` per recorded cross-block +/// unfold. The builder is the merge point for per-worker accumulators: calling +/// `block`/`delta_edge` is commutative and idempotent w.r.t. edge sets, so +/// merge order does not affect the result. +#[derive(Default)] +pub struct ProfileBuilder { + blocks: FxHashMap, +} + +#[derive(Default)] +struct Accum { + heartbeats: u64, + serialized_size: u32, + const_count: u32, + ops: OpCounts, + producers: FxHashSet
, +} + +impl ProfileBuilder { + pub fn new() -> Self { + Self::default() + } + + /// 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, + 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.ops.add(&ops); + } + + /// Record that `consumer` delta-unfolds the body of `producer`. Self-edges + /// are ignored. Ensures both endpoints exist as blocks (with zeroed stats if + /// not yet seen) so the graph is well-formed even if a producer is only ever + /// referenced, never directly checked. + pub fn delta_edge(&mut self, consumer: Address, producer: Address) { + if consumer == producer { + return; + } + self.blocks.entry(producer.clone()).or_default(); + self.blocks.entry(consumer).or_default().producers.insert(producer); + } + + /// Freeze into an immutable [`BlockProfile`]. Block ids are assigned by + /// sorting addresses, so the result is deterministic regardless of insertion + /// order. + pub fn finish(self) -> BlockProfile { + let mut addrs: Vec
= self.blocks.keys().cloned().collect(); + addrs.sort(); + let id_of: FxHashMap = + addrs.iter().enumerate().map(|(i, a)| (a.clone(), i as u32)).collect(); + + let mut blocks = Vec::with_capacity(addrs.len()); + let mut delta_row = Vec::with_capacity(addrs.len() + 1); + let mut delta_col = Vec::new(); + delta_row.push(0usize); + + for addr in &addrs { + let a = &self.blocks[addr]; + blocks.push(BlockEntry { + addr: addr.clone(), + heartbeats: a.heartbeats, + serialized_size: a.serialized_size, + const_count: a.const_count, + 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(); + prods.dedup(); + delta_col.extend_from_slice(&prods); + delta_row.push(delta_col.len()); + } + + BlockProfile { blocks, delta_row, delta_col } + } +} + +#[derive(Clone, Debug)] +pub enum AnonWorkItem { + /// A standalone (Defn/Recr/Axio/Quot) constant. `addr` is both the + /// kernel-checked target and the only address this item covers. + Standalone { addr: Address }, + /// A Muts block. Checking `primary` (the first member's projection + /// address) drives the kernel's block coordination, which in turn + /// typechecks every entry in `targets`. `targets[0] == primary`. + /// `block_addr` is the block's own `env.consts` key (the `Tag::Muts` + /// entry) — distinct from the projection `targets`, and the address + /// other constants' `refs` use to reference the block. Carried so the + /// set of `consts` keys a checked item certifies ([`proven_targets`]) + /// is in the same address space as `Constant.refs`. + /// + /// [`proven_targets`]: AnonWorkItem::proven_targets + Block { block_addr: Address, primary: Address, targets: Vec
}, +} + +impl AnonWorkItem { + /// The address to pass to the Ixon whole-unit checking entry. + pub fn primary(&self) -> &Address { + match self { + AnonWorkItem::Standalone { addr } => addr, + AnonWorkItem::Block { primary, .. } => primary, + } + } + + /// Every kernel-checked target this item covers (one for + /// `Standalone`, `targets.len()` for `Block`). + pub fn targets(&self) -> &[Address] { + match self { + AnonWorkItem::Standalone { addr } => core::slice::from_ref(addr), + AnonWorkItem::Block { targets, .. } => targets, + } + } + + /// Every `env.consts` key this item certifies (proves) well-typed when + /// its `primary` is checked — the *proven targets*, in the same address + /// space as `Constant.refs`, so a dependency ref can be matched against + /// the union of `proven_targets()` over all checked items: refs in that + /// union are already certified, the rest are the claim's open assumptions + /// (resolved later against other proofs). Unlike + /// [`targets`](Self::targets) (the addresses the kernel actually walks), + /// a `Block`'s proven set also includes the block's own `consts` key: + /// for a `Standalone` it's just its address; for a `Block` it's + /// `block_addr` plus every projection target (members + ctors). The + /// set includes synthesized semantic members whose projection records the + /// input may omit. It is not a physical-record coverage set; a host checks + /// carried records separately and does not add synthetic bytes to its input. + pub fn proven_targets(&self) -> Vec
{ + match self { + AnonWorkItem::Standalone { addr } => vec![addr.clone()], + AnonWorkItem::Block { block_addr, targets, .. } => { + let mut v = Vec::with_capacity(1 + targets.len()); + v.push(block_addr.clone()); + v.extend(targets.iter().cloned()); + v + }, + } + } +} + +fn mutual_work( + address: &Address, + members: &[crate::constant::MutConst], +) -> Result { + use crate::constant::MutConst; + if members.is_empty() { + return Err("empty mutual block".into()); + } + let has_defn = members.iter().any(|m| matches!(m, MutConst::Defn(_))); + let has_indc = members.iter().any(|m| matches!(m, MutConst::Indc(_))); + let has_recr = members.iter().any(|m| matches!(m, MutConst::Recr(_))); + if has_defn && (has_indc || has_recr) { + return Err("mixed mutual block".into()); + } + let mut targets = Vec::new(); + for (index, member) in members.iter().enumerate() { + let index = u64::try_from(index).map_err(|error| error.to_string())?; + targets.push(match member { + MutConst::Defn(_) => crate::constant::defn_proj_address(index, address), + MutConst::Indc(_) => crate::constant::indc_proj_address(index, address), + MutConst::Recr(_) => crate::constant::recr_proj_address(index, address), + }); + if let MutConst::Indc(inductive) = member { + for constructor in 0..inductive.ctors.len() { + targets.push(crate::constant::ctor_proj_address( + index, + u64::try_from(constructor).map_err(|error| error.to_string())?, + address, + )); + } + } + } + Ok(AnonWorkItem::Block { + block_addr: address.clone(), + primary: targets[0].clone(), + targets, + }) +} + +/// Enumerate the anon-mode kernel work set from `env.consts`. +/// +/// Returns one `AnonWorkItem` per kernel-checkable group of +/// constants. The total number of checked target addresses is +/// `work.iter().map(|w| w.targets().len()).sum()`. +/// +/// Refuses corrupted entries and unsupported serialized mutual shapes before +/// promising coverage for their member targets. +pub fn build_anon_work(env: &IxonEnv) -> Result, String> { + use crate::constant::ConstantInfo as CI; + use ConstVariantTag as Tag; + + let mut work: Vec = Vec::new(); + + // Sort keys for deterministic ordering across runs. + let mut keys: Vec
= + env.consts.iter().map(|e| e.key().clone()).collect(); + keys.sort_unstable(); + + for addr in keys { + let lc = env.consts.get(&addr).ok_or_else(|| { + format!("build_anon_work: missing const at {}", addr.hex()) + })?; + let tag = lc.value().peek_variant().map_err(|e| { + format!("build_anon_work: peek_variant {}: {e}", addr.hex()) + })?; + match tag { + Tag::IPrj | Tag::CPrj | Tag::RPrj | Tag::DPrj => { + // Skip — covered by parent block. + }, + Tag::Defn | Tag::Recr | Tag::Axio | Tag::Quot => { + work.push(AnonWorkItem::Standalone { addr: addr.clone() }); + }, + Tag::Muts => { + // Materialize once to enumerate members; the `Arc` + // drops at the end of this arm — no cache retention. + let constant = lc.value().get().map_err(|e| { + format!("build_anon_work: materialize Muts {}: {e}", addr.hex()) + })?; + let CI::Muts(members) = &constant.info else { + return Err(format!( + "build_anon_work: Tag::Muts but ConstantInfo is {:?} at {}", + constant.info.variant(), + addr.hex() + )); + }; + work.push(mutual_work(&addr, members)?); + }, + } + } + + Ok(work) +} + +/// The full dependency-closure address set of `roots`: their transitive Expr +/// `refs` PLUS each projection's structural `block: Address`. A refs-only walk +/// misses the block — projections (IPrj/CPrj/RPrj/DPrj) point at their Muts +/// block via a struct field, not the Expr ref table — so the kernel couldn't +/// resolve the projection and would spuriously fail. The returned set includes +/// constant AND referenced-blob addresses (external refs absent from `source` +/// are included as addresses but have no bytes to copy). +pub fn closure_addrs( + source: &IxonEnv, + roots: &[Address], +) -> std::collections::HashSet
{ + // The traversal lives in `ixon` (`Env::bfs_closure`) so bundle + // pruning/validation and the shard sub-env share one edge + // definition; this wrapper keeps the kernel-side std-HashSet + // signature. + source.bfs_closure(roots).into_iter().collect() +} + +/// Build a closure sub-env: serialize only the BFS dependency closure of +/// `roots` (their transitive `Constant.refs` plus referenced literal blobs), +/// copying each constant's GENUINE bytes via `store_const_lazy` so the guest's +/// per-const integrity check (`hash(bytes) == addr`) and the env merkle root +/// still hold. The guest decodes this instead of the whole env, so it pays only +/// its closure's decode, bounding memory use for large environments. +/// Missing hints are +/// performance-only: ingress falls back to `Regular(0)`, so the typecheck +/// result — and thus the committed claim — is unchanged. +/// External refs (not in `source`) are omitted and remain open assumptions, +/// exactly as in whole-env. +/// +/// Host-only: builds an `Env` via the `store_*` helpers, which are gated to +/// non-riscv64 (see `Env::store_blob`). The guest receives an already-built +/// sub-env from the host and only enumerates it via `build_anon_work`. +#[cfg(not(target_arch = "riscv64"))] +pub fn build_sub_env( + source: &IxonEnv, + roots: &[Address], +) -> Result, String> { + let sub = sub_env_of(source, roots); + let mut buf = Vec::new(); + sub.put(&mut buf).map_err(|e| format!("sub-env serialize: {e}"))?; + Ok(buf) +} + +/// The in-memory closure sub-env behind [`build_sub_env`]: copy the BFS +/// dependency closure of `roots` (genuine bytes + blobs + reducibility +/// hints), no Named section. A bound source's binding is copied with the +/// closure of the records it names, so the sub-env carries every record its +/// role admission reads and every unit a verdict on `roots` rests on (see +/// [`verdict_work`]). A binding that does not decode is copied as is, and +/// the sub-env's reader refuses it. +#[cfg(not(target_arch = "riscv64"))] +fn sub_env_of(source: &IxonEnv, roots: &[Address]) -> IxonEnv { + let mut roots = roots.to_vec(); + if let Some(binding) = &source.role_binding + && let Ok(table) = crate::role_table::RoleTable::from_bytes(&binding.bytes) + { + roots.extend(table.records().cloned()); + } + let closure = closure_addrs(source, &roots); + let mut sub = IxonEnv::new(); + sub.role_binding = source.role_binding.clone(); + for addr in &closure { + if let Some(bytes) = source.get_const_bytes(addr) { + sub.store_const_lazy(addr.clone(), bytes); + } else if let Some(blob) = source.get_blob(addr) { + sub.store_blob(blob); + } + // else: external ref absent from `source` — omit; stays an open assumption. + // Carry the constant's reducibility hint so the guest reproduces vanilla + // kernel behavior; without hints the kernel forces `Regular(0)` and does + // extra def-eq work (the ~30% check overhead). + if let Some(h) = source.anon_hints.get(addr) { + sub.anon_hints.insert(addr.clone(), *h); + } + } + sub +} + +/// [`build_sub_env`] plus a name→address entry for every closure constant +/// named in the FULL view of the same env — a standalone `.ixe` whose names +/// still resolve (for `--consts`-style tools), extracted without recompiling +/// from source. +/// +/// METADATA IS DROPPED: each copied entry is `Named::with_addr` (empty +/// `ConstantMeta`), because real metadata references name addresses +/// throughout its tree and carrying it would require the full env's +/// hash-consed name index. The extract serves the ANON pipeline +/// (the zkVM hosts, `ix profile`/`ix shard`, +/// `bench-typecheck`), where metadata is never read and reducibility hints +/// travel in the `anon_hints` section instead. Meta-mode tools need the +/// source env. +#[cfg(not(target_arch = "riscv64"))] +pub fn build_sub_env_named( + source: &IxonEnv, + full: &IxonEnv, + roots: &[Address], +) -> Result, String> { + use ix_common::env::NameData; + + let sub = sub_env_of(source, roots); + // The Named section serializes keys as name HASHES resolved through the + // names section, so each key's component chain must be interned too. + fn intern_chain(sub: &IxonEnv, name: &ix_common::env::Name) { + let addr = Address::from_blake3_hash(*name.get_hash()); + if sub.get_name(&addr).is_some() { + return; + } + match name.as_data() { + NameData::Anonymous(_) => {}, + NameData::Str(parent, _, _) | NameData::Num(parent, _, _) => { + intern_chain(sub, parent); + }, + } + sub.store_name(addr, name.clone()); + } + for e in full.named.iter() { + if sub.get_const(&e.value().addr).is_some() { + intern_chain(&sub, e.key()); + sub.register_name( + e.key().clone(), + crate::env::Named::with_addr(e.value().addr.clone()), + ); + } + } + let mut buf = Vec::new(); + sub.put(&mut buf).map_err(|e| format!("sub-env serialize: {e}"))?; + Ok(buf) +} + +/// The ingress-block address that owns `addr`: a projection (IPrj/CPrj/RPrj/ +/// DPrj) maps to its Muts `block`; anything else is its own block. Used to map +/// a resolved constant address to the `build_anon_work` item that covers it +/// (standalone → itself; a mutual-block member → the whole block). +pub fn block_of_addr(env: &IxonEnv, addr: &Address) -> Address { + use crate::constant::ConstantInfo as CI; + match env.get_const(addr) { + Some(c) => match &c.info { + CI::IPrj(p) => p.block.clone(), + CI::CPrj(p) => p.block.clone(), + CI::RPrj(p) => p.block.clone(), + CI::DPrj(p) => p.block.clone(), + _ => addr.clone(), + }, + None => addr.clone(), + } +} + +/// The ingress-block address of a work item's `primary` — the key for matching +/// a resolved target constant to the work item that covers it. +pub fn work_block_addr(env: &IxonEnv, item: &AnonWorkItem) -> Address { + block_of_addr(env, item.primary()) +} + +/// Build the STATIC block profile of an env — derived from +/// the serialized constants alone, no kernel run. +/// +/// - Vertices: ingress units (a Muts block or a standalone constant), +/// `serialized_size` real, `const_count` = member constants, +/// `heartbeats` := size (report-only). +/// - Balance weight: the partitioner's vertex weight is `block_step_cost` +/// (a linear model over the op counters), so the byte weight is carried +/// in `subst` — `vweight ∝ size` exactly, matching the measured-best +/// bisection weight ([`STATIC_OWNED_PER_BYTE`] docs). +/// - Edges: the claim-walk relation (`crate::shard_claim::walk_edges` — +/// positively-used refs + projection block pointers) at block +/// granularity, deduped, self-edges dropped. These are exactly the +/// edges that generate a shard's thin frontier, i.e. its ingress. +#[allow(clippy::cast_possible_truncation)] // block sizes clamped to u32::MAX +pub fn static_block_profile(env: &IxonEnv) -> BlockProfile { + use rayon::prelude::*; + let addrs: Vec
= + env.consts.iter().map(|e| e.key().clone()).collect(); + // Phase 1 (parallel): parse each constant exactly ONCE. Lazy mmap + // entries re-parse on every `get()` (see `LazyConstant`'s cache + // policy), so the home block is derived from the constant we already + // hold instead of `profile_block_of` (which would parse again), and + // edge targets are kept as raw addresses to be resolved in phase 2 + // through the addr→home table this pass produces. + struct ConstRow { + addr: Address, + home: Address, + targets: Vec
, + } + let rows: Vec = addrs + .par_iter() + .filter_map(|addr| { + let c = env.get_const(addr)?; + let home = match &c.info { + IxonCI::IPrj(p) => p.block.clone(), + IxonCI::CPrj(p) => p.block.clone(), + IxonCI::RPrj(p) => p.block.clone(), + IxonCI::DPrj(p) => p.block.clone(), + _ => addr.clone(), + }; + let mut targets = Vec::new(); + crate::shard_claim::walk_edges(&c, &mut targets); + Some(ConstRow { addr: addr.clone(), home, targets }) + }) + .collect(); + // Phase 2 (serial, no parsing): addr→home table, per-block member + // counts, and block-level edges. A target absent from the table is a + // blob payload (or an unparseable constant) — skipped, matching the + // claim walk's blob sentinel. + let home_of: FxHashMap<&Address, &Address> = + rows.iter().map(|r| (&r.addr, &r.home)).collect(); + let mut counts: FxHashMap<&Address, u32> = FxHashMap::default(); + for r in &rows { + *counts.entry(&r.home).or_insert(0) += 1; + } + let mut builder = ProfileBuilder::new(); + for r in &rows { + for t in &r.targets { + if let Some(&pb) = home_of.get(t) + && *pb != r.home + { + builder.delta_edge(r.home.clone(), pb.clone()); + } + } + } + for (home, cnt) in counts { + // Size without materializing: `raw_bytes().len()` on the lazy entry + // (`profile_block_size` would copy the bytes into a fresh Arc). + let size = env + .consts + .get(home) + .map_or(0, |e| e.value().raw_bytes().len().min(u32::MAX as usize)) + as u32; + let ops = OpCounts { subst_nodes: u64::from(size), ..OpCounts::default() }; + builder.block(home.clone(), u64::from(size), size, cnt, ops); + } + builder.finish() +} + +/// The blocks each block of `profile` references, by block id — every +/// constant's `refs` mapped to their home blocks, the edges a byte scope +/// (`Env::bfs_closure`) follows between blocks; projections reference +/// their own block and a block its wrappers, so those edges never leave a +/// block — and the blocks of the primitives the environment carries, which +/// every byte scope also starts from. A superset of the profile's walk +/// edges, which follow only the references a constant's body uses. +pub fn static_block_dependencies( + env: &IxonEnv, + profile: &BlockProfile, +) -> (Vec>, Vec) { + use ix_common::prim_addrs::PrimAddrs; + use rayon::prelude::*; + let id_of: FxHashMap<&Address, u32> = profile + .blocks() + .iter() + .enumerate() + .map(|(i, b)| (&b.addr, u32::try_from(i).expect("block ids are u32"))) + .collect(); + let addrs: Vec
= + env.consts.iter().map(|e| e.key().clone()).collect(); + let rows: Vec<(Address, Address, Vec
)> = addrs + .par_iter() + .filter_map(|addr| { + let c = env.get_const(addr)?; + let home = match &c.info { + IxonCI::IPrj(p) => p.block.clone(), + IxonCI::CPrj(p) => p.block.clone(), + IxonCI::RPrj(p) => p.block.clone(), + IxonCI::DPrj(p) => p.block.clone(), + _ => addr.clone(), + }; + Some((addr.clone(), home, c.refs.clone())) + }) + .collect(); + // Every constant's block id, projections included; a blob has none. + let block_of: FxHashMap<&Address, u32> = rows + .iter() + .filter_map(|(addr, home, _)| id_of.get(home).map(|&id| (addr, id))) + .collect(); + let mut depends: Vec> = vec![Vec::new(); profile.num_blocks()]; + for (addr, _, refs) in &rows { + let Some(&consumer) = block_of.get(addr) else { continue }; + for r in refs { + if let Some(&producer) = block_of.get(r) + && producer != consumer + { + depends[consumer as usize].push(producer); + } + } + } + for d in &mut depends { + d.sort_unstable(); + d.dedup(); + } + let mut primitives: Vec = PrimAddrs::lean_parity_table() + .iter() + .filter_map(|(_, hex)| Address::from_hex(hex)) + .chain(PrimAddrs::reserved_marker_addrs().iter().map(|(_, a)| a.clone())) + .filter_map(|a| block_of.get(&a).copied()) + .collect(); + primitives.sort_unstable(); + primitives.dedup(); + (depends, primitives) +} /// A weighted hypergraph derived from a [`BlockProfile`]. Vertex ids are block /// ids (identical to the profile's). Nets are stored with global pins. @@ -2254,58 +3026,6 @@ fn ordered_layout( (shard_of, backward) } -/// Read a `.ixprof`, partition into `num_shards` shards, and emit a manifest with -/// per-shard cost metrics, foreign-block sets, and (delta-based) assumption -/// roots. Optionally writes the manifest (`.ixes`). Returns a what-if report. -/// -/// The assumption root here is the Merkle root over the foreign *blocks* whose -/// bodies the shard must ingress (the cost-model-tight set). The fully-sound -/// variant for proof generation uses the static reference closure over member -/// constants and additionally needs the `.ixe`; see `plans/sharding.md` §4. -pub fn shard_esp( - esp_path: &str, - num_shards: usize, - balance: f64, - parallelism: usize, - out_path: Option<&str>, -) -> Result { - let bytes = - std::fs::read(esp_path).map_err(|e| format!("read {esp_path}: {e}"))?; - let profile = BlockProfile::from_bytes(&bytes) - .map_err(|e| format!("parse {esp_path}: {e}"))?; - let h = Hypergraph::from_profile(&profile); - let (shard_of, tree) = h.partition_with_tree(num_shards, balance); - let mut manifest = - ShardManifest::build(&profile, &shard_of, num_shards).with_tree(tree); - seal_and_write(&mut manifest, out_path)?; - // The largest single block's heartbeats is the *floor* on achievable - // per-shard balance: a mutual block is atomic and cannot be split, so no - // partition can drive max-shard heartbeats below it. When the heaviest shard - // is pinned at this floor, adding more shards only worsens imbalance — a - // signal to stop raising the shard count or to split the block. - let max_block_hb = - profile.blocks().iter().map(|b| b.heartbeats).max().unwrap_or(0); - let max_shard_hb = - manifest.shards.iter().map(|s| s.heartbeats).max().unwrap_or(0); - let floored = - num_shards > 1 && max_shard_hb <= max_block_hb.saturating_mul(11) / 10; - let note = if floored { - " [balance floored by largest atomic block — more shards won't help]" - } else { - "" - }; - Ok(format!( - "blocks={} delta_edges={} nets={}\n{}\n{}\nlargest_block_hb={}{}", - profile.num_blocks(), - profile.num_edges(), - h.num_nets(), - manifest.summary(), - prove_report(&profile, &shard_of, &manifest, parallelism), - max_block_hb, - note, - )) -} - /// Seal every shard's assumption root (the canonical merkle root over /// its foreign blocks) and write the manifest when a path is given. /// Shared tail of every manifest-producing entry point. @@ -2315,7 +3035,7 @@ fn seal_and_write( ) -> Result<(), String> { for shard in &mut manifest.shards { shard.assumption_root = - ixon::merkle::merkle_root_canonical(&shard.foreign_blocks); + crate::merkle::merkle_root_canonical(&shard.foreign_blocks); } if let Some(op) = out_path { std::fs::write(op, manifest.to_bytes()) @@ -2324,39 +3044,6 @@ fn seal_and_write( Ok(()) } -/// Write a `.ixes` manifest for an EXPLICIT shard assignment — the -/// partition a prove run actually produced (splits included) rather -/// than a planner's output. Same manifest construction as -/// [`shard_static`]'s tail: [`ShardManifest::build`] recomputes -/// own sizes, foreign blocks and cross-ingress from the profile, and -/// each shard's assumption root is the canonical merkle root of its -/// foreign blocks. No aggregation tree is attached (consumers fall -/// back to the flat tree-fold); heartbeat figures carry whatever proxy -/// the profile was built with (serialized size, on the static path). -/// `peaks` (empty, or one measured prover-peak per shard in order) -/// fills each shard's `measured_peak_bytes` — the run's measurement -/// riding along for schedulers to bin-pack on. -pub fn shard_manifest_explicit( - profile: &BlockProfile, - shard_of: &[u32], - num_shards: usize, - peaks: &[u64], - out_path: &str, -) -> Result { - if peaks.len() != num_shards { - return Err(format!( - "measured peaks: {} values for {num_shards} shards", - peaks.len() - )); - } - let mut manifest = ShardManifest::build(profile, shard_of, num_shards); - for (shard, &peak) in manifest.shards.iter_mut().zip(peaks) { - shard.measured_peak_bytes = peak; - } - seal_and_write(&mut manifest, Some(out_path))?; - Ok(manifest.summary()) -} - /// One leaf of a source manifest cut into parts: the parts' block lists in /// order (each a nonempty subset of the leaf's blocks) and, per part, the /// measured prover peak (`0` = unmeasured). @@ -2376,7 +3063,7 @@ impl ShardManifest { profile: &BlockProfile, ids: &[u32], ) -> Result<(ShardManifest, Vec>), String> { - let block_ids: rustc_hash::FxHashMap<_, _> = profile + let block_ids: FxHashMap<_, _> = profile .blocks() .iter() .enumerate() @@ -2599,481 +3286,9 @@ pub fn shard_manifest_refine( Ok((manifest.summary(), part_ids)) } -/// Like [`shard_esp`] but sized to a per-shard estimated **cycle** budget -/// (`max_cycles`) rather than a fixed shard count: grows `N` until the heaviest -/// splittable shard fits the budget (see [`partition_for_cycle_cap`]). Use -/// [`cycle_cap_for_ram`] to derive `max_cycles` from a host-RAM target. -pub fn shard_esp_cap( - esp_path: &str, - max_cycles: u64, - balance: f64, - parallelism: usize, - out_path: Option<&str>, -) -> Result { - let bytes = - std::fs::read(esp_path).map_err(|e| format!("read {esp_path}: {e}"))?; - let profile = BlockProfile::from_bytes(&bytes) - .map_err(|e| format!("parse {esp_path}: {e}"))?; - let plan = partition_for_cycle_cap(&profile, max_cycles, balance); - let mut manifest = - ShardManifest::build(&profile, &plan.shard_of, plan.num_shards) - .with_tree(plan.tree); - seal_and_write(&mut manifest, out_path)?; - let note = if plan.infeasible_atomic_floor { - "\n [INFEASIBLE: a single atomic block exceeds the cap — split it upstream, raise the cap, or use a bigger box]" - } else { - "" - }; - Ok(format!( - "blocks={} delta_edges={} max_cycles={} → {} shards (packed to cap)\n{}\n{}\nmax_shard_steps={} (cap {}, incl. floor {} + cross-ingress) largest_block_steps={}{}", - profile.num_blocks(), - profile.num_edges(), - max_cycles, - plan.num_shards, - manifest.summary(), - prove_report(&profile, &plan.shard_of, &manifest, parallelism), - plan.max_shard_steps, - max_cycles, - SHARD_COST_FLOOR, - plan.largest_block_steps, - note, - )) -} - -/// Legacy guest-cost estimate for the profiled partitioner. Cost, rather -/// than 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 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; - -/// Estimated 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)) -} - -/// Estimated 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 { - 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 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. -/// 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_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 `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_BCOST * 1e9 / COST_MODEL_HEADROOM) as u64 -} - -/// Measured single-GPU **leaf prove time**: `≈ PROVE_SETUP_SECS + -/// 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 = 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_BCOST * (steps as f64 / 1e9) -} - -/// Whole-workload prove-time estimate over a partition's per-shard step counts. -pub struct ProveEstimate { - /// Σ predicted guest STEPS over all shards (incl. per-shard floor + ingress). - pub total_steps: u64, - /// Sum of per-shard leaf prove times — wall clock with a single prover. - pub seq_secs: f64, - /// Wall clock with `parallelism` provers; `seq_secs` at parallelism 1. - pub wall_secs: f64, - /// Provers assumed (≥1). - pub parallelism: usize, -} - -/// Estimate whole-workload prove time from per-shard predicted STEPS. With -/// `parallelism` provers the wall clock is bounded below by both an even split -/// and the single slowest shard (a makespan can beat neither); for the -/// near-uniform shards the packer produces the even split dominates. -pub fn prove_estimate( - per_shard_steps: &[u64], - parallelism: usize, -) -> ProveEstimate { - let p = parallelism.max(1); - let total_steps = - per_shard_steps.iter().fold(0u64, |a, &s| a.saturating_add(s)); - let seq_secs: f64 = - per_shard_steps.iter().map(|&s| shard_prove_secs(s)).sum(); - let max_secs = - per_shard_steps.iter().map(|&s| shard_prove_secs(s)).fold(0.0, f64::max); - let wall_secs = (seq_secs / p as f64).max(max_secs); - ProveEstimate { total_steps, seq_secs, wall_secs, parallelism: p } -} - -/// Per-shard predicted full STEPS (floor + member step-cost + cross-ingress), -/// indexed by shard id, from a built manifest and its block assignment. -fn per_shard_steps( - profile: &BlockProfile, - shard_of: &[u32], - manifest: &ShardManifest, -) -> Vec { - let mut steps = vec![0u64; manifest.shards.len()]; - for (b, &s) in shard_of.iter().enumerate() { - steps[s as usize] = steps[s as usize] - .saturating_add(block_step_cost(profile.block(b as u32))); - } - for sh in &manifest.shards { - let i = sh.id as usize; - steps[i] = steps[i] - .saturating_add(SHARD_COST_FLOOR) - .saturating_add(COST_PER_INGRESS_BYTE.saturating_mul(sh.cross_ingress)); - } - steps -} - -/// Human-readable duration for the prove-time report. -fn fmt_duration(secs: f64) -> String { - if secs >= 3600.0 { - format!("{:.1}h", secs / 3600.0) - } else if secs >= 60.0 { - format!("{:.0}m", secs / 60.0) - } else { - format!("{secs:.0}s") - } -} - -/// One-line prove-time report fragment shared by [`shard_esp`] and -/// [`shard_esp_cap`]: total steps across all shards plus the wall-clock estimate -/// at the requested `parallelism` (and the sequential baseline). -fn prove_report( - profile: &BlockProfile, - shard_of: &[u32], - manifest: &ShardManifest, - parallelism: usize, -) -> String { - let est = - 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_BCOST:.0}s·Bsteps/shard, aggregation extra", - est.total_steps, - fmt_duration(est.wall_secs), - est.parallelism, - fmt_duration(est.seq_secs), - ) -} - -/// A partition sized to a per-shard estimated **cycle** budget rather than a -/// fixed shard count. See [`partition_for_cycle_cap`]. -pub struct BudgetPlan { - /// Chosen shard count. - pub num_shards: usize, - /// Shard assignment per block id (as [`Hypergraph::partition`] returns). - 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_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_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, - /// Largest single (atomic) block's [`block_step_cost`] — the hard floor on - /// `max_shard_steps` that no shard count can beat. - pub largest_block_steps: u64, - /// True when the largest atomic block alone exceeds the cap: the budget is - /// infeasible by sharding (split that mutual block upstream, or raise the cap - /// / use a bigger box). - pub infeasible_atomic_floor: bool, -} - -/// Size a partition to a per-shard **cycle** (guest-STEP) budget by **bin-packing -/// to the cap**, not by balancing into a fixed shard count. -/// -/// `max_cycles` is the ceiling on a single shard's estimated guest steps -/// — the leaf prover's trace size, which is what sets peak prover RAM. Get it -/// from a host-RAM budget with [`cycle_cap_for_ram`] (the measured prover model -/// `peak_RAM_GiB ≈ 50 + 33 × steps_billions`). -/// -/// **Why packing, not balancing.** A shard's predicted STEPS is -/// `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 -/// growing `N` whenever imbalance pushed the heaviest shard over the cap (the -/// old approach) *over-sharded*: it spread work evenly, so every shard sat -/// partially full and a single un-equalizable block forced extra shards. Packing -/// instead fills each shard as close to the cap as the dependency structure -/// allows and only opens a new shard when the next block won't fit — giving -/// `⌈total/cap⌉` shards plus a small packing remainder, each maximally -/// performant. Lumpy (a near-full shard beside a partial one) is fine. -/// -/// **How dependency overlap is packed.** We first cut a *fine* min-cut partition -/// ([`PACK_PIECES_PER_CAP`] pieces per cap) purely to obtain a **cut-coherent -/// order**: a DFS of the bisection tree visits each tightly-coupled subtree -/// before moving on, so dependency-overlapping blocks are contiguous. Greedy -/// next-fit along that order therefore packs overlapping blocks into the same -/// shard — keeping their shared dependencies in-shard so they are not re-paid as -/// cross-ingress — and only the thin seams between coherent runs cross a shard -/// boundary. The cap test includes the live cross-ingress, so a shard packed -/// "to the cap" still executes under it. -/// -/// Flags `infeasible_atomic_floor` when one block alone (its floor + reduction + -/// own + dependency ingress) exceeds the cap — it cannot be split, so the cap is -/// unreachable by sharding (split that mutual block upstream, or use a bigger -/// box). The block is still emitted in its own shard, best-effort. -pub fn partition_for_cycle_cap( - profile: &BlockProfile, - max_cycles: u64, - epsilon: f64, -) -> BudgetPlan { - // 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_COST_FLOOR).max(1); - let nblocks = profile.num_blocks(); - let largest_block = - profile.blocks().iter().map(block_step_cost).max().unwrap_or(0); - let size = |b: u32| u64::from(profile.block(b).serialized_size); - - if nblocks == 0 { - return BudgetPlan { - num_shards: 1, - shard_of: Vec::new(), - tree: AggNode::Leaf(0), - step_cap, - max_shard_steps: SHARD_COST_FLOOR, - largest_block_steps: 0, - infeasible_atomic_floor: false, - }; - } - - // 1. Cut-coherent block order. A fine min-cut pre-partition's bisection tree, - // read in DFS order, lays tightly-coupled blocks contiguously so the packer - // keeps dependency overlap within a shard. Sized to ~PACK_PIECES_PER_CAP - // pieces per cap (bounded by the block count); when everything fits one cap - // this collapses to a trivial order. - let total: u128 = - profile.blocks().iter().map(|b| u128::from(block_step_cost(b))).sum(); - let pieces = ((total.saturating_mul(PACK_PIECES_PER_CAP)) - / u128::from(step_cap)) as usize; - let n_fine = pieces.clamp(1, nblocks); - let order: Vec = if n_fine < 2 { - (0..nblocks as u32).collect() - } else { - let h = Hypergraph::from_profile(profile); - let (fine_of, fine_tree) = h.partition_with_tree(n_fine, epsilon); - let mut leaf_order = Vec::with_capacity(n_fine); - dfs_leaf_order(&fine_tree, &mut leaf_order); - let mut rank = vec![0u32; n_fine]; - for (r, &sid) in leaf_order.iter().enumerate() { - rank[sid as usize] = r as u32; - } - let mut order: Vec = (0..nblocks as u32).collect(); - order.sort_by_key(|&b| (rank[fine_of[b as usize] as usize], b)); - order - }; - - // 2. Greedy next-fit packing to the cap, with live cross-ingress accounting. - // A shard accumulates members until adding the next block would push its - // full predicted STEPS over `max_cycles`, then a new shard opens. Foreign - // dependency bytes (`foreign`) shrink when a later member is itself one of - // the current shard's pending dependencies — exactly the overlap packing - // saves. - let mut shard_of = vec![0u32; nblocks]; - let mut members: FxHashSet = FxHashSet::default(); - let mut foreign: FxHashSet = FxHashSet::default(); - let mut seen: FxHashSet = FxHashSet::default(); - let mut member_cost = 0u64; - let mut foreign_bytes = 0u64; - let mut cur = 0u32; - let mut max_shard_steps = 0u64; - let mut infeasible = false; - let predicted = |member_cost: u64, foreign_bytes: u64| -> u64 { - SHARD_COST_FLOOR - .saturating_add(member_cost) - .saturating_add(COST_PER_INGRESS_BYTE.saturating_mul(foreign_bytes)) - }; - - for (i, &b) in order.iter().enumerate() { - let bc = block_step_cost(profile.block(b)); - // Foreign-byte delta if `b` joins the current shard: `b` itself drops out of - // foreign if a prior member depended on it; `b`'s own producers join unless - // already member/foreign. - seen.clear(); - let mut added = 0u64; - for &p in profile.producers(b) { - if p != b - && !members.contains(&p) - && !foreign.contains(&p) - && seen.insert(p) - { - added = added.saturating_add(size(p)); - } - } - let removed = if foreign.contains(&b) { size(b) } else { 0 }; - let tent_fbytes = - foreign_bytes.saturating_add(added).saturating_sub(removed); - let tentative = predicted(member_cost.saturating_add(bc), tent_fbytes); - - // Open a new shard when the current one is non-empty and `b` won't fit. The - // first block of a run always goes in (a lone over-cap block is flagged - // infeasible below, not split — it can't be). - if !members.is_empty() && tentative > max_cycles { - max_shard_steps = - max_shard_steps.max(predicted(member_cost, foreign_bytes)); - cur += 1; - members.clear(); - foreign.clear(); - member_cost = 0; - foreign_bytes = 0; - } - - // Commit `b` into the current shard. - if foreign.remove(&b) { - foreign_bytes = foreign_bytes.saturating_sub(size(b)); - } - members.insert(b); - member_cost = member_cost.saturating_add(bc); - for &p in profile.producers(b) { - if p != b && !members.contains(&p) && foreign.insert(p) { - foreign_bytes = foreign_bytes.saturating_add(size(p)); - } - } - shard_of[b as usize] = cur; - if members.len() == 1 && predicted(member_cost, foreign_bytes) > max_cycles - { - infeasible = true; // this block alone overflows the cap - } - if i == order.len() - 1 { - max_shard_steps = - max_shard_steps.max(predicted(member_cost, foreign_bytes)); - } - } - - let num_shards = (cur + 1) as usize; - // 3. Balanced aggregation tree over the shard sequence. Shards are numbered in - // packing (cut-coherent) order, so adjacent ids are coupled — a balanced - // binary tree over `0..num_shards` keeps coupled shards as siblings, so - // cross-shard assumptions discharge low in the fold. - let tree = balanced_agg_tree(0, num_shards as u32); - - BudgetPlan { - num_shards, - shard_of, - tree, - step_cap, - max_shard_steps, - largest_block_steps: largest_block, - infeasible_atomic_floor: infeasible, - } -} - -/// Collect the leaf shard ids of an [`AggNode`] in left-to-right DFS order. -fn dfs_leaf_order(node: &AggNode, out: &mut Vec) { - match node { - AggNode::Leaf(id) => out.push(*id), - AggNode::Internal(l, r) => { - dfs_leaf_order(l, out); - dfs_leaf_order(r, out); - }, - } -} - -/// A balanced binary [`AggNode`] over the contiguous shard-id range `lo..hi`. -fn balanced_agg_tree(lo: u32, hi: u32) -> AggNode { - debug_assert!(hi > lo); - if hi - lo <= 1 { - AggNode::Leaf(lo) - } else { - let mid = lo + (hi - lo) / 2; - AggNode::Internal( - Box::new(balanced_agg_tree(lo, mid)), - Box::new(balanced_agg_tree(mid, hi)), - ) - } -} - #[cfg(test)] mod tests { use super::*; - use crate::profile::{OpCounts, ProfileBuilder}; fn addr(byte: u8) -> Address { Address::from_slice(&[byte; 32]).unwrap() @@ -3350,120 +3565,6 @@ mod tests { ); } - /// Full predicted STEPS of a shard under a partition: floor + member step-cost - /// + cross-ingress (foreign producers' bytes). Mirrors the packer's cap test. - fn shard_predicted(p: &BlockProfile, shard_of: &[u32], s: u32) -> u64 { - let members: FxHashSet = (0..p.num_blocks() as u32) - .filter(|&b| shard_of[b as usize] == s) - .collect(); - let member_cost: u64 = - members.iter().map(|&b| block_step_cost(p.block(b))).sum(); - let mut foreign: FxHashSet = FxHashSet::default(); - for &b in &members { - for &pr in p.producers(b) { - if !members.contains(&pr) { - foreign.insert(pr); - } - } - } - let foreign_bytes: u64 = - foreign.iter().map(|&pr| u64::from(p.block(pr).serialized_size)).sum(); - SHARD_COST_FLOOR + member_cost + COST_PER_INGRESS_BYTE * foreign_bytes - } - - #[test] - fn cap_packs_to_minimal_shards_under_cap() { - // 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, ops(1000)); - } - for i in 1..40u8 { - b.delta_edge(addr(i), addr(i + 1)); - } - let p = b.finish(); - 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); - for s in 0..plan.num_shards as u32 { - let pred = shard_predicted(&p, &plan.shard_of, s); - assert!( - pred <= max_cycles, - "shard {s} predicted {pred} exceeds cap {max_cycles}" - ); - } - assert!(plan.max_shard_steps <= max_cycles); - // Every block assigned to a real shard; tree leaves match the shard ids. - assert_eq!(plan.tree.num_leaves(), plan.num_shards); - } - - #[test] - fn cap_cross_ingress_counts_against_the_cap() { - // Two blocks that each delta-unfold a heavy shared dependency `dep` (large - // serialized_size). If they land in different shards, BOTH re-ingress `dep`. - // 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, 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(); - // Cap large enough to hold everything in one shard. - let plan = partition_for_cycle_cap(&p, 10_000_000_000, 0.05); - for s in 0..plan.num_shards as u32 { - let pred = shard_predicted(&p, &plan.shard_of, s); - assert!(pred <= 10_000_000_000, "shard {s} over cap: {pred}"); - } - } - - #[test] - fn cap_flags_infeasible_atomic_block() { - // 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, 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"); - } - - #[test] - fn shard_esp_file_roundtrip() { - // Exercise the CLI's shard path: write a .ixprof, run shard_esp, read back - // the .ixes manifest. - let p = two_clusters(); - let dir = std::env::temp_dir(); - let pid = std::process::id(); - let prof = dir.join(format!("ix_shard_rt_{pid}.ixprof")); - let shard = dir.join(format!("ix_shard_rt_{pid}.ixes")); - std::fs::write(&prof, p.to_bytes()).unwrap(); - - let report = shard_esp( - prof.to_str().unwrap(), - 2, - 0.10, - 1, - Some(shard.to_str().unwrap()), - ) - .unwrap(); - assert!(report.contains("shards=2"), "report was: {report}"); - - let m = ShardManifest::from_bytes(&std::fs::read(&shard).unwrap()).unwrap(); - assert_eq!(m.shards.len(), 2); - assert_eq!(m.total_cross_ingress, 1000); - - let _ = std::fs::remove_file(&prof); - let _ = std::fs::remove_file(&shard); - } - #[test] fn contract_sums_weights_and_drops_internal_nets() { // 4 vertices, 3 nets. Merge {0,1}->super0 and {2,3}->super1. @@ -3750,7 +3851,7 @@ mod tests { let mut m = ShardManifest::build(&p, &shard_of, 3).with_tree(tree); for sh in &mut m.shards { sh.assumption_root = - ixon::merkle::merkle_root_canonical(&sh.foreign_blocks); + crate::merkle::merkle_root_canonical(&sh.foreign_blocks); } (p, m) } @@ -3805,7 +3906,7 @@ mod tests { let mut sealed = q.clone(); for sh in &mut sealed.shards { sh.assumption_root = - ixon::merkle::merkle_root_canonical(&sh.foreign_blocks); + crate::merkle::merkle_root_canonical(&sh.foreign_blocks); } assert_eq!(sealed.shards[0], m.shards[0]); assert_eq!(sealed.shards[2], m.shards[2]); @@ -3909,7 +4010,7 @@ mod tests { let mut sealed = q.clone(); for sh in &mut sealed.shards { sh.assumption_root = - ixon::merkle::merkle_root_canonical(&sh.foreign_blocks); + crate::merkle::merkle_root_canonical(&sh.foreign_blocks); } assert_eq!(sealed.shards[1], m.shards[1]); assert_eq!( @@ -3927,7 +4028,7 @@ mod tests { let mut sealed = q; for sh in &mut sealed.shards { sh.assumption_root = - ixon::merkle::merkle_root_canonical(&sh.foreign_blocks); + crate::merkle::merkle_root_canonical(&sh.foreign_blocks); } assert_eq!(sealed.to_bytes(), m.to_bytes()); // A nonzero measurement overrides a carried peak; zero keeps it. diff --git a/crates/ixon/src/sharing_exact.rs b/crates/ixon/src/sharing_exact.rs deleted file mode 100644 index 4db2bb56c..000000000 --- a/crates/ixon/src/sharing_exact.rs +++ /dev/null @@ -1,877 +0,0 @@ -//! Sharing tables of anonymous Constants: the canonical construction and its -//! reference searches (`docs/sharing-minimum.md`). -//! -//! The compiler shares every block with the tiered canonical construction -//! ([`canonical_sharing_tiered`], `tiered`; Lean -//! `Ix.Sharing.Exact.canonicalSharingTiered`): phase 1 uses the fixed K16 -//! component families (`uniform`) at widths 1, 2 and 3, phase 2 -//! allocates the table slots, phase 3 re-materializes every part under the -//! real TagN widths, and the candidate with the fewest bytes wins. -//! -//! The optional test-only `sharing-reference` feature also exposes the -//! global exact minimum of -//! `docs/sharing-minimum.md` §3, computed by the width-state search -//! (`optimize_sharing`, `normalize_constant_sharing`, `search`). It is -//! exponential in the candidates and is a test oracle for small inputs, not -//! on the compiler path. Given a resolved anonymous Constant (or its ordered -//! expanded roots), it returns the feasible backward-reference sharing -//! encoding with the lexicographically least key -//! -//! ```text -//! K(e) = (L(e), Q(e), bytes(e)) -//! ``` -//! -//! where `L` is the byte length of the complete serialized Constant, `Q` the -//! vector of structural term IDs (§3.2) of the table entries in stored order -//! and `bytes` the serialized Constant. All non-sharing fields, root order and -//! the refs/univs tables are fixed; only the table sequence and per-occurrence -//! inline/reference choices vary. A table entry may reference only earlier -//! entries (the backward-reference class of §3.1); this module does not claim -//! optimality over forward-acyclic tables. -//! -//! Structure: -//! - [`SharingDag`] (`dag`): collision-independent hash-consing of the -//! expanded roots with §3.2 structural IDs, built from expanded roots or by -//! bounded, validated expansion of an existing table; also the re-expansion -//! checks of an encoding. -//! - `cost`: exact serialized lengths of expressions and Constants, mirroring -//! the writers of `serialize.rs`, and the [`Len`] overflow sentinel. -//! - `roots`: the ordered roots of a `ConstantInfo` and its reassembly. -//! - `dict`: the fixed-dictionary recurrence `C_M` (§5) with explicit -//! telescope cuts, its incremental and lazy evaluations of a growing -//! dictionary, and byte-least materialization. -//! - `search`: the sparse width-state dynamic program (§6) with the proved -//! reductions R1/R2, the optimistic-dictionary lower bound (§4.1) and a -//! materialization lower bound (the test oracle above). -//! - `uniform`: the fixed-family optimizer for a uniform Share width (phase 1), -//! with the old exact optimizer isolated behind the reference feature. -//! - `tiered`: the canonical tiered construction, its checks and its -//! checked mode ([`ExactSharingLimits::full_check`]). -//! - `par`: deterministic task splitting for [`Parallelism`]; `prof`: phase -//! timers behind the `sharing-profile` feature. -//! - `oracle` (tests only): a tiny exhaustive reference search; `mss` (tests -//! only): the maximal-structural-sharing reference encoding. -//! -//! Every limit in [`ExactSharingLimits`] is enforced with deterministic -//! counters where it applies (`max_candidates` and the layer and transition -//! limits bound the width-state search only); exhausting one returns -//! [`SharingError::ResourceExhausted`] and never a best-so-far encoding. -//! Lengths use checked arithmetic or the exact overflow sentinel [`Len`]. -//! -//! # Pinned interpretations -//! -//! These choices are part of the canonical result and must match any other -//! implementation of the rule: -//! -//! - **ID domain.** Structural IDs number exactly the distinct subterms -//! reachable from the ordered roots after expansion. Entries of an -//! incoming table that no root reaches are validated but do not receive -//! IDs, so they cannot influence `Q`. -//! - **Admission.** Expansion accepts only the backward-reference class: -//! a Share in entry `i` must name an entry `< i`, a Share in a root must -//! name an entry `< len`. Out-of-range, forward and cyclic references are -//! [`MalformedSharing`] errors, including in unreachable entries. -//! The reference `optimize_sharing` rejects any Share leaf in its input. -//! - **Byte tie.** For a fixed table sequence each entry and root is -//! materialized as its byte-least minimum-length representation. This -//! equals the rule: an inline constructor precedes a Share of the same -//! length (flags `0x0..=0xA` < `0xB`); among telescope prefixes of one -//! node the least TagN header bytes win (distinct prefix lengths always -//! differ inside the header); then children independently. -//! - **Variable length.** [`UniformSharingResult::variable_len`] is the sum of -//! the root encodings, the table's TagN count and the entry encodings; the -//! rest of the Constant is fixed and checked against `Constant::put`. -//! -//! Pruning (the §4.1 bound strengthened with `share_width(k)` for future -//! entries, the materialization bound, and the greedy upper bound seed) -//! never changes a successful result, only which inputs finish -//! within the limits; `search` documents the proofs. - -mod cost; -mod dag; -mod dict; -mod measure; -mod par; -mod prof; -mod roots; -#[cfg(any(test, feature = "sharing-reference"))] -mod search; -mod tiered; -mod uniform; - -#[cfg(test)] -mod mss; -#[cfg(test)] -mod oracle; -#[cfg(test)] -mod tests; - -use std::fmt; -#[cfg(any(test, feature = "sharing-reference"))] -use std::sync::Arc; - -pub use cost::{ - Len, byte_count, constant_fixed_len, constant_len, expr_len, expr_len_with, - share_width, sharing_table_len, tag0_len, tag4_len, -}; -pub use dag::{Children, Node, NodeKey, SharingDag, TermId}; -pub use dict::{FixedDictionary, dictionary_cost, materialize_with_dictionary}; -// Diagnostic only (empty without the `sharing-profile` feature). -pub use measure::{candidate_terms, sequence_len}; -#[doc(hidden)] -pub use prof::profile_report; -pub use roots::{ - constant_info_root_count, constant_info_root_exprs, rebuild_constant_info, -}; -pub use tiered::{ - ShareLayout, TAGN_RUNG1_END, TAGN_RUNG2_END, TAGN_RUNG3_END, TAGN_RUNG4_END, - TAGN_RUNG5_END, TieredSharingResult, TieredStats, canonical_sharing_tiered, - layout_bytes, normalize_constant_bytes_tiered, - normalize_constant_sharing_tiered, normalize_constant_sharing_tiered_par, - tagn_width, -}; -pub use uniform::UniformSharingResult; -#[cfg(any(test, feature = "sharing-reference"))] -pub use uniform::{ - normalize_constant_sharing_uniform, optimize_sharing_uniform, -}; -// Variable-K family traces are private unit-test controls. -#[cfg(test)] -pub(crate) use uniform::family_trace; -// The first-tier search of phase 2, for the tests; not part of the API. -#[cfg(test)] -pub(crate) use tiered::first_tier; - -#[cfg(any(test, feature = "sharing-reference"))] -use crate::constant::Constant; -#[cfg(any(test, feature = "sharing-reference"))] -use crate::expr::Expr; - -/// Deterministic resource limits and search options. Exhausting a limit is -/// an error. Limits and the pruning and checking options can change success -/// into failure but never the bytes of a success. -/// -/// The defaults are a safety net, not a budget: each production limit is at -/// least 2^8 times the previous default, under which every Mathlib constant -/// built. `max_candidates` bounds only the exhaustive width-state search (a -/// test oracle, not the compiler path) and stays at 2^16; the canonical -/// construction records its candidate count without limiting it, as Lean -/// does. A run that reaches a limit fails closed and names it -/// ([`Resource::key`]); [`ExactSharingLimits::with_overrides`] raises it. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ExactSharingLimits { - /// Pointer-distinct input expression nodes visited during expansion. - pub max_input_nodes: u64, - /// Distinct structural nodes interned (including unreachable entries). - pub max_distinct_nodes: u64, - /// Maximum height of the expanded DAG. - pub max_height: u64, - /// Sharing candidates remaining after reductions R1/R2 (the width-state - /// search only). - pub max_candidates: u64, - /// Reference cost calls precharged by the K16 component families; width - /// states created by the dynamic program on the test-only exact route. - pub max_states: u64, - /// States in one layer (entries stored) of the dynamic program. - pub max_layer_states: u64, - /// Candidate transitions examined. - pub max_transitions: u64, - /// Logical evaluation work, per candidate of the tiered construction. - /// K16 phase 1 precharges `(calls + 1) * closure + storedSum`, matching - /// Lean's family recipe even when native evaluations reuse prior values. - /// Its phase 3 charges one evaluation of the whole - /// table when the order is closed under stored descendants (one pass), and - /// one evaluation per table prefix otherwise, so the two paths charge - /// different amounts for the same output (see `tiered`). - pub max_work: u64, - /// Length of the complete serialized output. - pub max_output_bytes: u64, - /// Cells `(components + 1) * (cap + 1)` of the uniform optimizer's - /// table-count knapsack (Lean `maxKnapsackCells`), checked before it runs - /// and counted separately from `max_states`. - pub max_knapsack_cells: u64, - /// Prune states by proved lower bounds. Disabling it explores every - /// reachable width state (a reference mode for tests). - pub lower_bound_pruning: bool, - /// Seed the upper bound with a deterministic greedy table sequence. - /// Affects pruning only. - pub greedy_upper_bound: bool, - /// Also prune by the materialization bound (see `search`), in addition to - /// the §4.1 dictionary bound. - pub materialization_bound: bool, - /// Uniform-width search: use the plain subset enumeration of each - /// component instead of the reclassifying branch and bound. A test - /// oracle, not on the compiler path (off by default; the Lean optimality - /// theorems assume it is off). The result must not change. - pub uniform_subset_search: bool, - /// Tiered construction: build and check every candidate, not only the - /// returned one, and cross-check every shortcut against the computation - /// it replaces (the checked mode; see `tiered`, "Checks"). Off on the - /// compiler path; on in the unit tests, the Lean/Rust FFI parity hooks and - /// `sharing_corpus --full-check`. The output, the statistics and the - /// outcome of every limit are those of the default mode; a discrepancy is - /// an [`SharingError::Internal`] error. - pub full_check: bool, - /// Tiered construction: run phase 3 per table prefix for every candidate, - /// also when the order is closed under stored descendants (the reference - /// of the one-pass phase 3; off by default). The bytes do not change; the - /// `work` charged does (see `max_work`). - pub phase3_per_prefix: bool, -} - -/// Thread budgets for the tiered construction inside one constant (Rust -/// only; [`normalize_constant_sharing_tiered_par`]). A budget is the number of -/// tasks a level is split into on the current rayon pool; 0 and 1 run that -/// level sequentially, and [`Parallelism::SEQUENTIAL`] is the reference path -/// of every other entry point. Every budget gives byte-identical output. -/// -/// * `widths`: the three phase-1 widths of the best of three run as separate -/// tasks, each under its own meter (as in the sequential path), and are -/// compared afterwards by the same rule. An error at any width fails the -/// call with the error of the lowest failing width, as sequentially. -/// * `components`: retained for the test-only unbounded reference solver. -/// The production K16 family fold is sequential, checking each component's -/// state precharge before its closure/work precharge regardless of this -/// budget. Reference tasks accumulate counters in component order. -/// * `materialize`: the per-prefix phase 3 (an order not closed under -/// stored descendants, [`ExactSharingLimits::phase3_per_prefix`], and the -/// per-prefix reference of [`ExactSharingLimits::full_check`]) evaluates -/// the entries (each under the entries before it) and the roots as -/// separate tasks; their work is charged in entry order afterwards, so -/// counters, errors and output equal the sequential path's. The one-pass -/// phase 3, which every order of Init and Mathlib takes, has no tasks to -/// split and ignores this budget. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct Parallelism { - pub widths: usize, - pub components: usize, - pub materialize: usize, -} - -impl Parallelism { - /// Every level sequential (the reference). - pub const SEQUENTIAL: Parallelism = - Parallelism { widths: 1, components: 1, materialize: 1 }; -} - -impl Default for Parallelism { - fn default() -> Self { - Self::SEQUENTIAL - } -} - -impl Default for ExactSharingLimits { - fn default() -> Self { - ExactSharingLimits { - max_input_nodes: 1 << 40, - max_distinct_nodes: 1 << 32, - max_height: 1 << 32, - max_candidates: 1 << 16, - max_states: 1 << 40, - max_layer_states: 1 << 40, - max_transitions: 1 << 40, - max_work: 1 << 56, - max_output_bytes: 1 << 40, - max_knapsack_cells: 1 << 28, - greedy_upper_bound: true, - lower_bound_pruning: true, - materialization_bound: true, - uniform_subset_search: false, - full_check: false, - phase3_per_prefix: false, - } - } -} - -impl ExactSharingLimits { - /// No limits except those of the representation itself. - pub fn unbounded() -> Self { - ExactSharingLimits { - max_input_nodes: u64::MAX, - max_distinct_nodes: u64::MAX, - max_height: u64::MAX, - max_candidates: u64::MAX, - max_states: u64::MAX, - max_layer_states: u64::MAX, - max_transitions: u64::MAX, - max_work: u64::MAX, - max_output_bytes: u64::MAX, - max_knapsack_cells: u64::MAX, - ..Self::default() - } - } - - /// Limit keys of the Lean implementation (`Ix.Sharing.Exact.Limits`) that - /// the Rust limits do not have. [`Self::with_overrides`] accepts and - /// ignores them, so one override string serves both compilers; `states`, - /// `transitions` and `output_bytes` name a limit in both. - pub const LEAN_ONLY_KEYS: [&'static str; 6] = [ - "expr_visits", - "depth", - "nodes", - "cost_evals", - "materialize", - "materialize_work", - ]; - - /// Set the limit named `key` ([`Resource::key`]); `false` if the key names - /// no Rust limit. - pub fn set_limit(&mut self, key: &str, value: u64) -> bool { - let slot = match key { - "input_nodes" => &mut self.max_input_nodes, - "distinct_nodes" => &mut self.max_distinct_nodes, - "height" => &mut self.max_height, - "candidates" => &mut self.max_candidates, - "states" => &mut self.max_states, - "layer_states" => &mut self.max_layer_states, - "transitions" => &mut self.max_transitions, - "work" => &mut self.max_work, - "output_bytes" => &mut self.max_output_bytes, - "knapsack_cells" => &mut self.max_knapsack_cells, - _ => return false, - }; - *slot = value; - true - } - - /// Apply a limit override: comma-separated items, each `key=value` - /// ([`Resource::key`] names; values ASCII decimal digits, `2^k` with - /// `k <= 63`, or `max`; no sign or separator) or `unbounded` (every limit - /// at `u64::MAX`), applied left to right. Items, keys and values are - /// trimmed of space, tab, line feed and carriage return only. Lean-only - /// keys ([`Self::LEAN_ONLY_KEYS`]) are accepted and ignored; any other key - /// is an error. This is the grammar of Lean - /// `Ix.Sharing.Exact.Limits.withOverrides`, of `ix compile - /// --sharing-limits` and of `IX_SHARING_LIMITS`; the test list - /// `LIMIT_SPEC_PARITY` pins it for both languages. - pub fn with_overrides(mut self, spec: &str) -> Result { - for raw in spec.split(',') { - let item = trim_ascii(raw); - if item.is_empty() { - continue; - } - if item == "unbounded" { - for key in [ - "input_nodes", - "distinct_nodes", - "height", - "candidates", - "states", - "layer_states", - "transitions", - "work", - "output_bytes", - "knapsack_cells", - ] { - self.set_limit(key, u64::MAX); - } - continue; - } - let Some((key, value)) = item.split_once('=') else { - return Err(format!( - "sharing limit item {item}: expected key=value or unbounded" - )); - }; - let key = trim_ascii(key); - let value = parse_limit_value(value)?; - if !self.set_limit(key, value) && !Self::LEAN_ONLY_KEYS.contains(&key) { - return Err(format!( - "unknown sharing limit {key} (expected input_nodes, distinct_nodes, \ - height, candidates, states, layer_states, transitions, work, \ - output_bytes, knapsack_cells, or a Lean key: {})", - Self::LEAN_ONLY_KEYS.join(", ") - )); - } - } - Ok(self) - } -} - -/// Trim the whitespace Lean `String.trimAscii` trims: space, tab, line feed -/// and carriage return. -fn trim_ascii(s: &str) -> &str { - s.trim_matches(|c: char| matches!(c, ' ' | '\t' | '\n' | '\r')) -} - -/// A limit value: ASCII decimal digits, `2^k` (`k <= 63`, `k` digits) or -/// `max` (`u64::MAX`). No sign, separator or other spelling: the grammar of -/// Lean `parseLimitValue`. -fn parse_limit_value(raw: &str) -> Result { - let s = trim_ascii(raw); - if s == "max" { - return Ok(u64::MAX); - } - let digits = |t: &str| !t.is_empty() && t.bytes().all(|b| b.is_ascii_digit()); - if let Some(k) = s.strip_prefix("2^") { - if !digits(k) { - return Err(format!( - "sharing limit value {s}: expected digits, 2^k or max" - )); - } - return match k.parse::() { - Ok(k) if k <= 63 => Ok(1u64 << k), - Ok(_) => Err(format!("sharing limit value {s}: exponent above 63")), - Err(e) => Err(format!( - "sharing limit value {s}: expected digits, 2^k or max: {e}" - )), - }; - } - if !digits(s) { - return Err(format!( - "sharing limit value {s}: expected digits, 2^k or max" - )); - } - s.parse::().map_err(|e| { - format!( - "sharing limit value {s}: expected digits, 2^k or max (at most 2^64 - \ - 1): {e}" - ) - }) -} - -/// Non-semantic statistics of one invocation. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct ExactSharingStats { - pub input_nodes: u64, - pub distinct_nodes: u64, - pub height: u64, - pub candidates: u64, - pub states_created: u64, - pub states_expanded: u64, - pub states_pruned: u64, - pub transitions: u64, - pub max_layer: u64, - pub layers: u64, - pub work: u64, - /// Variable length of the greedy seed, when it was computed. - pub greedy_len: Option, - /// Variable length of the unshared encoding (`None` if it overflows). - pub unshared_len: Option, -} - -/// Where a Share reference occurred. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ShareLocation { - Entry(u64), - Root(u64), -} - -/// The input is not a valid backward-reference sharing encoding. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum MalformedSharing { - /// A Share index is not below the table length. - ShareOutOfRange { location: ShareLocation, index: u64, table_len: u64 }, - /// Entry `entry` references a later entry without closing a cycle. - ForwardShare { entry: u64, index: u64 }, - /// Entry `entry` references `index >= entry` and that closes a cycle. - CyclicShare { entry: u64, index: u64 }, - /// A Share leaf in input that must already be expanded. - UnresolvedShare { root: u64, index: u64 }, - /// Reassembly did not consume exactly the ConstantInfo's roots. - RootCountMismatch { expected: u64, actual: u64 }, -} - -/// A value exceeds a representable index or length domain. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum FormatBound { - /// A serialized length reaches `u64::MAX`. - LengthOverflow, - /// A uniform Share width of 0. - UniformWidth { w: u64 }, - /// A telescope spine of length at least `bound` (Lean's `formatBound - /// "telescope spine length" teleSubaddEnd`): below it the TagN Share-flag - /// width is subadditive, which the reference route's optimality argument - /// relies on. The production family route checks no spine bound. - TelescopeSpine { bound: u64 }, -} - -/// Which deterministic limit was exhausted. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Resource { - InputNodes, - DistinctNodes, - Height, - Candidates, - States, - LayerStates, - Transitions, - Work, - OutputBytes, - /// The uniform optimizer's table-count knapsack (`max_knapsack_cells`). - KnapsackCells, -} - -impl Resource { - /// The name of the resource in error messages and limit overrides - /// ([`ExactSharingLimits::with_overrides`], `--sharing-limits`). - pub fn key(self) -> &'static str { - match self { - Resource::InputNodes => "input_nodes", - Resource::DistinctNodes => "distinct_nodes", - Resource::Height => "height", - Resource::Candidates => "candidates", - Resource::States => "states", - Resource::LayerStates => "layer_states", - Resource::Transitions => "transitions", - Resource::Work => "work", - Resource::OutputBytes => "output_bytes", - Resource::KnapsackCells => "knapsack_cells", - } - } -} - -/// A deterministic resource limit was reached before certification. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ResourceExhausted { - pub resource: Resource, - pub limit: u64, -} - -/// Failure of an exact-sharing operation. No variant carries a partial or -/// uncertified encoding. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum SharingError { - Malformed(MalformedSharing), - FormatBound(FormatBound), - ResourceExhausted(ResourceExhausted), - /// An internal invariant failed; this is a bug, reported instead of a - /// wrong answer. - Internal(String), -} - -impl fmt::Display for SharingError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SharingError::Malformed(m) => write!(f, "malformed sharing: {m:?}"), - SharingError::FormatBound(b) => write!(f, "format bound: {b:?}"), - SharingError::ResourceExhausted(r) => { - write!( - f, - "resource exhausted: {} (limit {})", - r.resource.key(), - r.limit - ) - }, - SharingError::Internal(s) => write!(f, "internal error: {s}"), - } - } -} - -impl std::error::Error for SharingError {} - -/// Deterministic work accounting against [`ExactSharingLimits`]. -pub(crate) struct Meter<'l> { - limits: &'l ExactSharingLimits, - pub(crate) stats: ExactSharingStats, - /// Thread budgets of the tiered construction (sequential by default). - pub(crate) parallel: Parallelism, -} - -impl<'l> Meter<'l> { - pub(crate) fn new(limits: &'l ExactSharingLimits) -> Self { - Meter { - limits, - stats: ExactSharingStats::default(), - parallel: Parallelism::SEQUENTIAL, - } - } - - pub(crate) fn with_parallelism( - limits: &'l ExactSharingLimits, - parallel: Parallelism, - ) -> Self { - Meter { parallel, ..Meter::new(limits) } - } - - pub(crate) fn limits(&self) -> &'l ExactSharingLimits { - self.limits - } - - fn exhausted(resource: Resource, limit: u64) -> SharingError { - SharingError::ResourceExhausted(ResourceExhausted { resource, limit }) - } - - pub(crate) fn input_node(&mut self) -> Result<(), SharingError> { - self.stats.input_nodes = self.stats.input_nodes.saturating_add(1); - if self.stats.input_nodes > self.limits.max_input_nodes { - return Err(Self::exhausted( - Resource::InputNodes, - self.limits.max_input_nodes, - )); - } - Ok(()) - } - - pub(crate) fn distinct_nodes(&mut self, n: u64) -> Result<(), SharingError> { - self.stats.distinct_nodes = self.stats.distinct_nodes.max(n); - if n > self.limits.max_distinct_nodes { - return Err(Self::exhausted( - Resource::DistinctNodes, - self.limits.max_distinct_nodes, - )); - } - Ok(()) - } - - pub(crate) fn height(&mut self, h: u64) -> Result<(), SharingError> { - self.stats.height = self.stats.height.max(h); - if h > self.limits.max_height { - return Err(Self::exhausted(Resource::Height, self.limits.max_height)); - } - Ok(()) - } - - #[cfg(any(test, feature = "sharing-reference"))] - pub(crate) fn candidates(&mut self, k: u64) -> Result<(), SharingError> { - self.stats.candidates = k; - if k > self.limits.max_candidates { - return Err(Self::exhausted( - Resource::Candidates, - self.limits.max_candidates, - )); - } - Ok(()) - } - - pub(crate) fn work(&mut self, n: u64) -> Result<(), SharingError> { - self.stats.work = self.stats.work.saturating_add(n); - if self.stats.work > self.limits.max_work { - return Err(Self::exhausted(Resource::Work, self.limits.max_work)); - } - Ok(()) - } - - #[cfg(any(test, feature = "sharing-reference"))] - pub(crate) fn state(&mut self) -> Result<(), SharingError> { - self.stats.states_created = self.stats.states_created.saturating_add(1); - if self.stats.states_created > self.limits.max_states { - return Err(Self::exhausted(Resource::States, self.limits.max_states)); - } - Ok(()) - } - - #[cfg(any(test, feature = "sharing-reference"))] - pub(crate) fn transition(&mut self) -> Result<(), SharingError> { - self.stats.transitions = self.stats.transitions.saturating_add(1); - if self.stats.transitions > self.limits.max_transitions { - return Err(Self::exhausted( - Resource::Transitions, - self.limits.max_transitions, - )); - } - Ok(()) - } - - #[cfg(any(test, feature = "sharing-reference"))] - pub(crate) fn layer(&mut self, size: u64) -> Result<(), SharingError> { - self.stats.max_layer = self.stats.max_layer.max(size); - if size > self.limits.max_layer_states { - return Err(Self::exhausted( - Resource::LayerStates, - self.limits.max_layer_states, - )); - } - Ok(()) - } - - /// Add the `states_created` and `work` counted on another meter (one - /// parallel task), checking `states_created` and then `work`. - #[cfg(any(test, feature = "sharing-reference"))] - pub(crate) fn absorb( - &mut self, - other: &ExactSharingStats, - ) -> Result<(), SharingError> { - self.stats.states_created = - self.stats.states_created.saturating_add(other.states_created); - if self.stats.states_created > self.limits.max_states { - return Err(Self::exhausted(Resource::States, self.limits.max_states)); - } - self.work(other.work) - } - - pub(crate) fn output(&mut self, len: u64) -> Result<(), SharingError> { - if len > self.limits.max_output_bytes { - return Err(Self::exhausted( - Resource::OutputBytes, - self.limits.max_output_bytes, - )); - } - Ok(()) - } -} - -/// A certified exact-minimum sharing encoding of ordered expanded roots. -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg(any(test, feature = "sharing-reference"))] -pub struct ExactSharingResult { - /// Rewritten roots, in input order. - pub roots: Vec>, - /// The sharing table; entry `i` references only entries `< i`. - pub sharing: Vec>, - /// `Q`: structural term ID of each table entry, in stored order. - pub table_terms: Vec, - /// Exact variable bytes: roots + table TagN count + table entries. - pub variable_len: u64, - /// Nonsemantic statistics. - pub stats: ExactSharingStats, -} - -/// Exact minimum sharing of ordered, fully expanded roots (no Share leaves): -/// the width-state search, a test oracle (not the compiler path, which is -/// [`canonical_sharing_tiered`]). -/// -/// The full-Constant key differs from the variable length only by bytes that -/// do not depend on sharing, so the result is the global minimum of any -/// Constant with these ordered roots. -#[cfg(any(test, feature = "sharing-reference"))] -pub fn optimize_sharing( - roots: &[Arc], - limits: &ExactSharingLimits, -) -> Result { - let mut meter = Meter::new(limits); - let dag = SharingDag::build(roots, None, &mut meter)?; - search::optimize(&dag, 0, &mut meter) -} - -/// Expand `c`'s table (validating backward references) and return the -/// exact-minimum Constant together with its search result (the width-state -/// search, a test oracle; see [`optimize_sharing`]). -#[cfg(any(test, feature = "sharing-reference"))] -pub fn normalize_constant_sharing_with_stats( - c: &Constant, - limits: &ExactSharingLimits, -) -> Result<(Constant, ExactSharingResult), SharingError> { - let mut meter = Meter::new(limits); - let roots = constant_info_root_exprs(&c.info); - let dag = SharingDag::build(&roots, Some(&c.sharing), &mut meter)?; - let fixed = constant_fixed_len(c) - .ok_or(SharingError::FormatBound(FormatBound::LengthOverflow))?; - let result = search::optimize(&dag, fixed, &mut meter)?; - let info = rebuild_constant_info(&c.info, &result.roots)?; - let out = Constant { - info, - sharing: result.sharing.clone(), - refs: c.refs.clone(), - univs: c.univs.clone(), - }; - // Fixed-cost accounting: the real serializer must agree with the - // decomposition the search optimized. - let mut bytes = Vec::new(); - out.put(&mut bytes); - let expected = fixed.checked_add(result.variable_len); - if u64::try_from(bytes.len()).ok() != expected { - return Err(SharingError::Internal(format!( - "serialized length {} differs from fixed {fixed} + variable {}", - bytes.len(), - result.variable_len - ))); - } - Ok((out, result)) -} - -/// Expand `c`'s table and return its exact-minimum encoding (the test -/// oracle of [`optimize_sharing`]). -#[cfg(any(test, feature = "sharing-reference"))] -pub fn normalize_constant_sharing( - c: &Constant, - limits: &ExactSharingLimits, -) -> Result { - normalize_constant_sharing_with_stats(c, limits).map(|(c, _)| c) -} - -/// Outcome of [`check_exact_minimum`]. -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg(any(test, feature = "sharing-reference"))] -pub enum ExactMinimumCheck { - /// `c` is its exact-minimum encoding. - Minimum, - /// `c` differs from its exact-minimum encoding `expected`. - NotMinimum { expected: Box }, -} - -/// Whether `c` is byte-identical to its exact-minimum encoding: a check -/// against the width-state search, the test oracle of [`optimize_sharing`] -/// (Lean `checkExactMinimum`). It does not check the compiler's -/// canonical construction ([`canonical_sharing_tiered`]). -#[cfg(any(test, feature = "sharing-reference"))] -pub fn check_exact_minimum( - c: &Constant, - limits: &ExactSharingLimits, -) -> Result { - let expected = normalize_constant_sharing(c, limits)?; - let mut have = Vec::new(); - c.put(&mut have); - let mut want = Vec::new(); - expected.put(&mut want); - Ok(if have == want { - ExactMinimumCheck::Minimum - } else { - ExactMinimumCheck::NotMinimum { expected: Box::new(expected) } - }) -} - -/// Failure of byte-level normalization, including the canonical tiered entry. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum NormalizeBytesError { - /// The input is not exactly one wire-valid serialized Constant. - Decode(String), - Sharing(SharingError), -} - -impl fmt::Display for NormalizeBytesError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - NormalizeBytesError::Decode(e) => write!(f, "decode: {e}"), - NormalizeBytesError::Sharing(e) => write!(f, "{e}"), - } - } -} - -impl std::error::Error for NormalizeBytesError {} - -/// Byte-level normalization: decode exactly one serialized Constant (no -/// trailing bytes), expand and validate its table, and return the serialized -/// exact-minimum Constant (the test oracle of [`optimize_sharing`]). -#[cfg(any(test, feature = "sharing-reference"))] -pub fn normalize_constant_bytes( - bytes: &[u8], - limits: &ExactSharingLimits, -) -> Result, NormalizeBytesError> { - let mut input = bytes; - let c = Constant::get(&mut input).map_err(NormalizeBytesError::Decode)?; - if !input.is_empty() { - return Err(NormalizeBytesError::Decode(format!( - "{} trailing bytes after the Constant", - input.len() - ))); - } - let out = normalize_constant_sharing(&c, limits) - .map_err(NormalizeBytesError::Sharing)?; - let mut buf = Vec::new(); - out.put(&mut buf); - Ok(buf) -} - -/// Byte-level uniform-width normalization: decode exactly one serialized -/// Constant, re-share it with the uniform-width optimum for Share width `w`, -/// and return the serialized result. -#[cfg(any(test, feature = "sharing-reference"))] -pub fn normalize_constant_bytes_uniform( - w: u64, - bytes: &[u8], - limits: &ExactSharingLimits, -) -> Result, NormalizeBytesError> { - let mut input = bytes; - let c = Constant::get(&mut input).map_err(NormalizeBytesError::Decode)?; - if !input.is_empty() { - return Err(NormalizeBytesError::Decode(format!( - "{} trailing bytes after the Constant", - input.len() - ))); - } - let (out, _) = normalize_constant_sharing_uniform(w, &c, limits) - .map_err(NormalizeBytesError::Sharing)?; - let mut buf = Vec::new(); - out.put(&mut buf); - Ok(buf) -} diff --git a/crates/ixon/src/sharing_exact/cost.rs b/crates/ixon/src/sharing_exact/cost.rs deleted file mode 100644 index e734e8b64..000000000 --- a/crates/ixon/src/sharing_exact/cost.rs +++ /dev/null @@ -1,360 +0,0 @@ -//! Exact serialized-length helpers for the expression and Constant grammar -//! (TagN integers). -//! -//! Every function here mirrors a writer in `serialize.rs` byte for byte; the -//! tests compare them against the real serializer. Arithmetic is checked: -//! helpers that measure caller data return `None` instead of wrapping. - -use std::cmp::Ordering; -use std::sync::Arc; - -use rustc_hash::FxHashMap; - -use super::roots::constant_info_root_exprs; -use crate::constant::{ - Constant, ConstantInfo, Constructor, Inductive, MutConst, Recursor, -}; -use crate::expr::Expr; -use crate::tag::TagN; -use crate::univ::put_univ; - -/// Serialized byte length with an explicit overflow sentinel. -/// -/// Values below `u64::MAX` are exact. [`Len::OVERFLOW`] stands for every -/// length `>= u64::MAX`: addition saturates into it, so it compares strictly -/// greater than every exact length and two exact lengths always compare -/// exactly. The optimizer only ever returns exact lengths; an overflowed -/// candidate can lose a comparison but can never be selected. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Len(u64); - -impl Len { - pub const ZERO: Len = Len(0); - pub const OVERFLOW: Len = Len(u64::MAX); - - /// `v == u64::MAX` is read as [`Len::OVERFLOW`]. - pub const fn new(v: u64) -> Len { - Len(v) - } - - #[must_use] - pub const fn plus(self, other: Len) -> Len { - Len(self.0.saturating_add(other.0)) - } - - #[must_use] - pub const fn plus_u64(self, other: u64) -> Len { - Len(self.0.saturating_add(other)) - } - - pub const fn is_exact(self) -> bool { - self.0 != u64::MAX - } - - /// The exact value, or `None` for [`Len::OVERFLOW`]. - pub const fn exact(self) -> Option { - if self.0 == u64::MAX { None } else { Some(self.0) } - } - - /// Raw value; `u64::MAX` is the overflow sentinel. - pub const fn raw(self) -> u64 { - self.0 - } -} - -/// Minimal little-endian byte count of `x` (0 for 0), as `u64_byte_count`. -pub fn byte_count(x: u64) -> u64 { - (64 - u64::from(x.leading_zeros())).div_ceil(8) -} - -/// Encoded length of a TagN (f = 4) integer `size` for any flag: -/// 1, 2, 3, 4, 5 or 9 bytes ([`TagN::byte_width`]). -pub fn tag4_len(size: u64) -> u64 { - TagN::byte_width(4, size) as u64 -} - -/// Encoded length of a TagN (f = 0) integer `size`: 1, 2, 3, 4, 5 or 9 bytes. -pub fn tag0_len(size: u64) -> u64 { - TagN::byte_width(0, size) as u64 -} - -/// Byte width of `Expr::Share(index)`. -pub fn share_width(index: u64) -> u64 { - tag4_len(index) -} - -/// Unsigned lexicographic order of the TagN (f = 4) encodings of two values -/// under the same flag (flag 0 stands for it: the flag bits are equal). TagN -/// is a prefix code, so distinct values differ inside the shorter encoding. -pub(crate) fn tag4_bytes_cmp(a: u64, b: u64) -> Ordering { - let mut xa = Vec::with_capacity(9); - let mut xb = Vec::with_capacity(9); - TagN::put(4, 0, a, &mut xa); - TagN::put(4, 0, b, &mut xb); - xa.cmp(&xb) -} - -fn usize_u64(n: usize) -> Option { - u64::try_from(n).ok() -} - -/// Exact `put_expr` length of an expression (Share leaves allowed). -/// -/// Iterative, memoized by pointer, so an `Arc` DAG is measured without -/// walking its occurrence tree. Returns `None` if the length overflows. -pub fn expr_len(e: &Expr) -> Option { - expr_len_with(e, &tag4_len) -} - -/// [`expr_len`] with every `Share(i)` priced `share(i)` bytes instead of its -/// TagN width (a Share-width layout or model). -pub fn expr_len_with(e: &Expr, share: &dyn Fn(u64) -> u64) -> Option { - expr_len_memo(e, share, &mut FxHashMap::default()) -} - -/// `tag0_len(count)` plus the [`expr_len_with`] of every expression, with -/// one pointer memo for all of them (the length of an expression does not -/// depend on where it occurs); `None` if the sum overflows. -pub(crate) fn exprs_len_with<'a>( - count: u64, - exprs: impl Iterator>, - share: &dyn Fn(u64) -> u64, -) -> Option { - let mut memo: FxHashMap<*const Expr, ExprLenInfo> = FxHashMap::default(); - let mut total = tag0_len(count); - for e in exprs { - total = total.checked_add(expr_len_memo(e, share, &mut memo)?)?; - } - Some(total) -} - -#[derive(Clone, Copy)] -pub(crate) struct ExprLenInfo { - /// Standalone length. - len: u64, - /// Telescope count, summed side bytes and tail length when this node is - /// the top of its App/Lam/All spine. - count: u64, - sum: u64, - tail: u64, -} - -/// The children of an expression node, without allocating. -pub(crate) fn expr_children(e: &Expr) -> [Option<&Arc>; 3] { - match e { - Expr::Prj(_, _, v) => [Some(v), None, None], - Expr::App(a, b) | Expr::Lam(_, a, b) | Expr::All(_, _, a, b) => { - [Some(a), Some(b), None] - }, - Expr::Let(_, a, b, c) => [Some(a), Some(b), Some(c)], - _ => [None, None, None], - } -} - -/// [`expr_len_with`] with a caller-provided pointer memo. -fn expr_len_memo( - e: &Expr, - share: &dyn Fn(u64) -> u64, - memo: &mut FxHashMap<*const Expr, ExprLenInfo>, -) -> Option { - let mut stack: Vec<(&Expr, bool)> = vec![(e, false)]; - while let Some((node, ready)) = stack.pop() { - let key = std::ptr::from_ref(node); - if memo.contains_key(&key) { - continue; - } - let children = expr_children(node); - if !ready && children[0].is_some() { - stack.push((node, true)); - for child in children.into_iter().flatten() { - if !memo.contains_key(&Arc::as_ptr(child)) { - stack.push((child.as_ref(), false)); - } - } - continue; - } - let info = expr_len_info(node, &|c| memo[&Arc::as_ptr(c)], share)?; - memo.insert(key, info); - } - memo.get(&std::ptr::from_ref(e)).map(|i| i.len) -} - -/// The [`ExprLenInfo`] of one node from those of its children (`None` if -/// a length overflows). -pub(crate) fn expr_len_info( - node: &Expr, - get: &dyn Fn(&Arc) -> ExprLenInfo, - share: &dyn Fn(u64) -> u64, -) -> Option { - type Info = ExprLenInfo; - fn leaf(len: u64) -> Info { - Info { len, count: 0, sum: 0, tail: 0 } - } - Some(match node { - Expr::Sort(n) | Expr::Var(n) | Expr::Str(n) | Expr::Nat(n) => { - leaf(tag4_len(*n)) - }, - Expr::Share(n) => leaf(share(*n)), - Expr::Ref(n, us) | Expr::Rec(n, us) => { - let mut len = tag4_len(usize_u64(us.len())?).checked_add(tag0_len(*n))?; - for u in us { - len = len.checked_add(tag0_len(*u))?; - } - leaf(len) - }, - Expr::Prj(t, f, v) => { - leaf(tag4_len(*f).checked_add(tag0_len(*t))?.checked_add(get(v).len)?) - }, - Expr::Let(c, ty, v, b) => leaf( - tag4_len(c.flags()) - .checked_add(1)? - .checked_add(get(ty).len)? - .checked_add(get(v).len)? - .checked_add(get(b).len)?, - ), - Expr::App(f, a) => { - let (count, sum, tail) = if matches!(f.as_ref(), Expr::App(..)) { - let fi = get(f); - (fi.count.checked_add(1)?, fi.sum.checked_add(get(a).len)?, fi.tail) - } else { - (1, get(a).len, get(f).len) - }; - let len = tag4_len(count).checked_add(sum)?.checked_add(tail)?; - Info { len, count, sum, tail } - }, - Expr::Lam(_, ty, b) | Expr::All(_, _, ty, b) => { - let all = matches!(node, Expr::All(..)); - let same = if all { - matches!(b.as_ref(), Expr::All(..)) - } else { - matches!(b.as_ref(), Expr::Lam(..)) - }; - let side = get(ty).len.checked_add(1)?; - let (count, sum, tail) = if same { - let bi = get(b); - (bi.count.checked_add(1)?, bi.sum.checked_add(side)?, bi.tail) - } else { - (1, side, get(b).len) - }; - let len = tag4_len(count).checked_add(sum)?.checked_add(tail)?; - Info { len, count, sum, tail } - }, - }) -} - -impl ExprLenInfo { - pub(crate) const ZERO: ExprLenInfo = - ExprLenInfo { len: 0, count: 0, sum: 0, tail: 0 }; - - /// The standalone length. - pub(crate) fn len(self) -> u64 { - self.len - } -} - -/// Exact length of a sharing table: its TagN count plus every entry. -pub fn sharing_table_len(table: &[Arc]) -> Option { - let mut len = tag0_len(usize_u64(table.len())?); - for e in table { - len = len.checked_add(expr_len(e)?)?; - } - Some(len) -} - -fn definition_fixed(lvls: u64) -> u64 { - 1 + tag0_len(lvls) -} - -fn recursor_fixed(r: &Recursor) -> Option { - let mut len = 1 + tag0_len(r.lvls); - for x in [r.params, r.indices, r.motives, r.minors] { - len = len.checked_add(tag0_len(x))?; - } - len = len.checked_add(tag0_len(usize_u64(r.rules.len())?))?; - for rule in &r.rules { - len = len.checked_add(tag0_len(rule.fields))?; - } - Some(len) -} - -fn constructor_fixed(c: &Constructor) -> Option { - let mut len = 1u64; - for x in [c.lvls, c.cidx, c.params, c.fields] { - len = len.checked_add(tag0_len(x))?; - } - Some(len) -} - -fn inductive_fixed(i: &Inductive) -> Option { - let mut len = 1u64; - for x in [i.lvls, i.params, i.indices] { - len = len.checked_add(tag0_len(x))?; - } - len = len.checked_add(tag0_len(usize_u64(i.ctors.len())?))?; - for c in &i.ctors { - len = len.checked_add(constructor_fixed(c)?)?; - } - Some(len) -} - -fn mut_const_fixed(m: &MutConst) -> Option { - let body = match m { - MutConst::Defn(d) => definition_fixed(d.lvls), - MutConst::Indc(i) => inductive_fixed(i)?, - MutConst::Recr(r) => recursor_fixed(r)?, - }; - body.checked_add(1) -} - -/// Exact length of every byte of `c` except its root expressions (as listed -/// by [`constant_info_root_exprs`]) and its whole sharing table, including -/// the table's TagN count. -pub fn constant_fixed_len(c: &Constant) -> Option { - let info = match &c.info { - ConstantInfo::Muts(ms) => { - let mut len = tag4_len(usize_u64(ms.len())?); - for m in ms { - len = len.checked_add(mut_const_fixed(m)?)?; - } - len - }, - other => { - let header = tag4_len(other.variant()?); - let body = match other { - ConstantInfo::Defn(d) => definition_fixed(d.lvls), - ConstantInfo::Recr(r) => recursor_fixed(r)?, - ConstantInfo::Axio(a) => definition_fixed(a.lvls), - ConstantInfo::Quot(q) => definition_fixed(q.lvls), - ConstantInfo::CPrj(p) => { - tag0_len(p.idx).checked_add(tag0_len(p.cidx))?.checked_add(32)? - }, - ConstantInfo::RPrj(p) => tag0_len(p.idx).checked_add(32)?, - ConstantInfo::IPrj(p) => tag0_len(p.idx).checked_add(32)?, - ConstantInfo::DPrj(p) => tag0_len(p.idx).checked_add(32)?, - ConstantInfo::Muts(_) => return None, - }; - header.checked_add(body)? - }, - }; - let refs = usize_u64(c.refs.len())?; - let mut len = info - .checked_add(tag0_len(refs))? - .checked_add(refs.checked_mul(32)?)? - .checked_add(tag0_len(usize_u64(c.univs.len())?))?; - let mut scratch = Vec::new(); - for u in &c.univs { - scratch.clear(); - put_univ(u, &mut scratch); - len = len.checked_add(usize_u64(scratch.len())?)?; - } - Some(len) -} - -/// Exact `Constant::put` length, decomposed as fixed bytes + roots + table. -pub fn constant_len(c: &Constant) -> Option { - let mut len = constant_fixed_len(c)?; - for root in constant_info_root_exprs(&c.info) { - len = len.checked_add(expr_len(&root)?)?; - } - len.checked_add(sharing_table_len(&c.sharing)?) -} diff --git a/crates/ixon/src/sharing_exact/dag.rs b/crates/ixon/src/sharing_exact/dag.rs deleted file mode 100644 index be46d7913..000000000 --- a/crates/ixon/src/sharing_exact/dag.rs +++ /dev/null @@ -1,903 +0,0 @@ -//! Hash-consed structural DAG of the expanded roots and its §3.2 term IDs. -//! -//! Interning uses a hash map keyed by the complete structural [`Node`]; the -//! hash only accelerates lookup and equality is decided by `Eq` on every -//! constructor, scalar, contract and child, so hash collisions cannot merge -//! distinct terms. Share leaves of an input table resolve to the interned ID -//! of the referenced entry, which expands the table without materializing its -//! occurrence tree. - -use std::sync::Arc; - -use rustc_hash::{FxHashMap, FxHashSet}; - -use super::cost::{Len, tag0_len, tag4_len}; -use super::{MalformedSharing, Meter, ShareLocation, SharingError}; -use crate::contract::{ - BinderContract, LetContract, ValueContract, pack_all_contract, -}; -use crate::expr::Expr; - -/// Structural term ID: position in the §3.2 order. -/// -/// A `usize`, so every term of a DAG held in memory has an ID: the node -/// vector has fewer than `usize::MAX` elements, which also leaves -/// `TermId::MAX` free as the "no term" sentinel of internal tables. Heights -/// are below the node count and use the same type. Wire indices are `u64` -/// and are converted, checked, only where they are written. -pub type TermId = usize; - -/// The vector index of a term ID. -pub(crate) const fn ix(t: TermId) -> usize { - t -} - -/// One distinct expanded subterm; children are term IDs. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum Node { - Sort(u64), - Var(u64), - Ref(u64, Vec), - Rec(u64, Vec), - Prj(u64, u64, TermId), - Str(u64), - Nat(u64), - App(TermId, TermId), - Lam(BinderContract, TermId, TermId), - All(BinderContract, ValueContract, TermId, TermId), - Let(LetContract, TermId, TermId, TermId), -} - -/// Ordered children of a node (at most three). -#[derive(Clone, Copy, Debug)] -pub struct Children { - ids: [TermId; 3], - len: u8, -} - -impl Children { - pub fn as_slice(&self) -> &[TermId] { - &self.ids[..usize::from(self.len)] - } -} - -/// Telescope family of App, Lam and All nodes. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum Family { - App, - Lam, - All, -} - -/// §3.2 sort key: constructor tag, scalar payload, ordered child IDs. The -/// derived order compares fields in that order, integers numerically and -/// vectors lexicographically with a proper prefix first. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct NodeKey { - pub tag: u8, - pub scalars: Vec, - pub children: Vec, -} - -fn u64_len(n: usize) -> u64 { - u64::try_from(n).unwrap_or(u64::MAX) -} - -impl Node { - /// Constructor tag (the TagN expression flag). - pub fn tag(&self) -> u8 { - match self { - Node::Sort(_) => Expr::FLAG_SORT, - Node::Var(_) => Expr::FLAG_VAR, - Node::Ref(..) => Expr::FLAG_REF, - Node::Rec(..) => Expr::FLAG_REC, - Node::Prj(..) => Expr::FLAG_PRJ, - Node::Str(_) => Expr::FLAG_STR, - Node::Nat(_) => Expr::FLAG_NAT, - Node::App(..) => Expr::FLAG_APP, - Node::Lam(..) => Expr::FLAG_LAM, - Node::All(..) => Expr::FLAG_ALL, - Node::Let(..) => Expr::FLAG_LET, - } - } - - /// The §3.2 scalar payload vector. - pub fn scalars(&self) -> Vec { - match self { - Node::Sort(n) | Node::Var(n) | Node::Str(n) | Node::Nat(n) => vec![*n], - Node::Ref(n, us) | Node::Rec(n, us) => { - let mut v = Vec::with_capacity(us.len() + 2); - v.push(*n); - v.push(u64_len(us.len())); - v.extend_from_slice(us); - v - }, - Node::Prj(t, f, _) => vec![*t, *f], - Node::App(..) => vec![], - Node::Lam(c, ..) => vec![u64::from(c.to_bits())], - Node::All(c, v, ..) => vec![u64::from(pack_all_contract(*c, *v))], - Node::Let(c, ..) => vec![c.flags(), u64::from(c.binder.to_bits())], - } - } - - pub fn children(&self) -> Children { - let (ids, len) = match self { - Node::Sort(_) - | Node::Var(_) - | Node::Ref(..) - | Node::Rec(..) - | Node::Str(_) - | Node::Nat(_) => ([0; 3], 0), - Node::Prj(_, _, v) => ([*v, 0, 0], 1), - Node::App(a, b) | Node::Lam(_, a, b) | Node::All(_, _, a, b) => { - ([*a, *b, 0], 2) - }, - Node::Let(_, a, b, c) => ([*a, *b, *c], 3), - }; - Children { ids, len } - } - - pub(crate) fn family(&self) -> Option { - match self { - Node::App(..) => Some(Family::App), - Node::Lam(..) => Some(Family::Lam), - Node::All(..) => Some(Family::All), - _ => None, - } - } - - /// Bytes this node contributes besides its children and, for App/Lam/All, - /// besides the per-telescope TagN header: the whole encoding of a leaf, - /// Prj/Let headers, and the contract byte of each Lam/All binder. - pub(crate) fn own_len(&self) -> Len { - match self { - Node::Sort(n) | Node::Var(n) | Node::Str(n) | Node::Nat(n) => { - Len::new(tag4_len(*n)) - }, - Node::Ref(n, us) | Node::Rec(n, us) => { - let mut len = - Len::new(tag4_len(u64_len(us.len()))).plus_u64(tag0_len(*n)); - for u in us { - len = len.plus_u64(tag0_len(*u)); - } - len - }, - Node::Prj(t, f, _) => Len::new(tag4_len(*f)).plus_u64(tag0_len(*t)), - Node::App(..) => Len::ZERO, - Node::Lam(..) | Node::All(..) => Len::new(1), - Node::Let(c, ..) => Len::new(tag4_len(c.flags())).plus_u64(1), - } - } - - fn map_children(&self, f: impl Fn(TermId) -> TermId) -> Node { - match self { - Node::Sort(_) - | Node::Var(_) - | Node::Ref(..) - | Node::Rec(..) - | Node::Str(_) - | Node::Nat(_) => self.clone(), - Node::Prj(t, i, v) => Node::Prj(*t, *i, f(*v)), - Node::App(a, b) => Node::App(f(*a), f(*b)), - Node::Lam(c, a, b) => Node::Lam(*c, f(*a), f(*b)), - Node::All(c, v, a, b) => Node::All(*c, *v, f(*a), f(*b)), - Node::Let(c, a, b, d) => Node::Let(*c, f(*a), f(*b), f(*d)), - } - } - - /// Rebuild an expression node with the given child expressions. - pub fn to_expr(&self, child: impl Fn(TermId) -> Arc) -> Expr { - match self { - Node::Sort(n) => Expr::Sort(*n), - Node::Var(n) => Expr::Var(*n), - Node::Ref(n, us) => Expr::Ref(*n, us.clone()), - Node::Rec(n, us) => Expr::Rec(*n, us.clone()), - Node::Prj(t, i, v) => Expr::Prj(*t, *i, child(*v)), - Node::Str(n) => Expr::Str(*n), - Node::Nat(n) => Expr::Nat(*n), - Node::App(a, b) => Expr::App(child(*a), child(*b)), - Node::Lam(c, a, b) => Expr::Lam(*c, child(*a), child(*b)), - Node::All(c, v, a, b) => Expr::All(*c, *v, child(*a), child(*b)), - Node::Let(c, a, b, d) => Expr::Let(*c, child(*a), child(*b), child(*d)), - } - } - - pub(crate) fn from_leaf(e: &Expr) -> Option { - Some(match e { - Expr::Sort(n) => Node::Sort(*n), - Expr::Var(n) => Node::Var(*n), - Expr::Ref(n, us) => Node::Ref(*n, us.clone()), - Expr::Rec(n, us) => Node::Rec(*n, us.clone()), - Expr::Str(n) => Node::Str(*n), - Expr::Nat(n) => Node::Nat(*n), - _ => return None, - }) - } -} - -/// The distinct expanded subterms of ordered roots, indexed by §3.2 term ID. -/// -/// IDs are assigned by increasing height, then by [`NodeKey`]; every child -/// therefore has a smaller ID than its parent. Only subterms reachable from -/// the roots are present, so unreachable entries of an input table cannot -/// influence IDs. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SharingDag { - nodes: Vec, - heights: Vec, - roots: Vec, -} - -/// How Share leaves are resolved while interning one expression. -#[derive(Clone, Copy)] -enum ShareCtx<'a> { - /// Input must be fully expanded; `root` names the root for errors. - Forbidden { root: u64 }, - /// Table expansion: `resolved` are the IDs of the entries this location - /// may reference (a prefix of `table`). - Table { - table: &'a [Arc], - resolved: &'a [TermId], - location: ShareLocation, - }, -} - -struct Builder<'m, 'l> { - nodes: Vec, - heights: Vec, - index: FxHashMap, - meter: &'m mut Meter<'l>, -} - -impl Builder<'_, '_> { - fn intern(&mut self, node: Node) -> Result { - if let Some(&id) = self.index.get(&node) { - return Ok(id); - } - // The children were interned first, so their heights are below the - // node count: neither the height nor the new ID (the node count) - // overflows. - let mut height: TermId = 0; - for &c in node.children().as_slice() { - height = height.max(self.heights[ix(c)] + 1); - } - self.meter.height(u64_len(height))?; - let id: TermId = self.nodes.len(); - self.meter.distinct_nodes(u64_len(id).saturating_add(1))?; - self.nodes.push(node.clone()); - self.heights.push(height); - self.index.insert(node, id); - Ok(id) - } - - fn resolve_share( - &mut self, - index: u64, - ctx: ShareCtx<'_>, - ) -> Result { - match ctx { - ShareCtx::Forbidden { root } => { - Err(SharingError::Malformed(MalformedSharing::UnresolvedShare { - root, - index, - })) - }, - ShareCtx::Table { table, resolved, location } => { - if let Some(&id) = - usize::try_from(index).ok().and_then(|i| resolved.get(i)) - { - return Ok(id); - } - let table_len = u64_len(table.len()); - match location { - ShareLocation::Entry(entry) if index < table_len => { - Err(classify_nonbackward(table, entry, index, self.meter)) - }, - _ => { - Err(SharingError::Malformed(MalformedSharing::ShareOutOfRange { - location, - index, - table_len, - })) - }, - } - }, - } - } - - /// Intern one expression tree, iteratively and memoized by pointer. - /// - /// The pointer memo is shared across the entries and roots of one build. - /// They are processed in order of increasing reference bound, so a - /// pointer validated under a smaller bound stays valid under a larger one. - fn intern_expr( - &mut self, - root: &Arc, - memo: &mut FxHashMap<*const Expr, TermId>, - ctx: ShareCtx<'_>, - ) -> Result { - let mut stack: Vec<(&Expr, bool)> = vec![(root.as_ref(), false)]; - while let Some((e, ready)) = stack.pop() { - let key = std::ptr::from_ref(e); - if memo.contains_key(&key) { - continue; - } - if !ready { - self.meter.input_node()?; - if let Expr::Share(i) = e { - let id = self.resolve_share(*i, ctx)?; - memo.insert(key, id); - continue; - } - if let Some(leaf) = Node::from_leaf(e) { - let id = self.intern(leaf)?; - memo.insert(key, id); - continue; - } - stack.push((e, true)); - for c in super::cost::expr_children(e).into_iter().flatten() { - if !memo.contains_key(&Arc::as_ptr(c)) { - stack.push((c.as_ref(), false)); - } - } - continue; - } - let id = |c: &Arc| memo[&Arc::as_ptr(c)]; - let node = match e { - Expr::Prj(t, f, v) => Node::Prj(*t, *f, id(v)), - Expr::App(a, b) => Node::App(id(a), id(b)), - Expr::Lam(c, a, b) => Node::Lam(*c, id(a), id(b)), - Expr::All(c, v, a, b) => Node::All(*c, *v, id(a), id(b)), - Expr::Let(c, a, b, d) => Node::Let(*c, id(a), id(b), id(d)), - _ => { - return Err(SharingError::Internal( - "leaf reached the compound interning path".into(), - )); - }, - }; - let id = self.intern(node)?; - memo.insert(key, id); - } - memo.get(&Arc::as_ptr(root)).copied().ok_or_else(|| { - SharingError::Internal("interned root missing from memo".into()) - }) - } - - /// Keep the subterms reachable from `roots` and renumber them by §3.2. - fn finalize( - self, - roots: &[TermId], - ) -> Result<(SharingDag, Vec), SharingError> { - let n = self.nodes.len(); - let mut reach = vec![false; n]; - for &r in roots { - reach[ix(r)] = true; - } - // Children were interned before their parents, so they have smaller - // provisional IDs and a descending sweep propagates reachability. - for p in (0..n).rev() { - if reach[p] { - for &c in self.nodes[p].children().as_slice() { - reach[ix(c)] = true; - } - } - } - let max_h = - (0..n).filter(|&p| reach[p]).map(|p| self.heights[p]).max().unwrap_or(0); - let mut buckets: Vec> = vec![Vec::new(); max_h + 1]; - for p in (0..n).filter(|&p| reach[p]) { - buckets[self.heights[p]].push(p); - } - let mut new_id = vec![TermId::MAX; n]; - let mut nodes = Vec::new(); - let mut heights = Vec::new(); - let mut next: TermId = 0; - for (h, mut keyed) in buckets.into_iter().enumerate() { - // Sorted by the [`NodeKey`] order (compared in place), then by the - // provisional ID, as sorting `(NodeKey, usize)` pairs. - let key_cmp = |a: usize, b: usize| { - node_key_cmp(&self.nodes[a], &self.nodes[b], &new_id) - }; - keyed.sort_by(|&a, &b| key_cmp(a, b).then(a.cmp(&b))); - for w in keyed.windows(2) { - if key_cmp(w[0], w[1]) == std::cmp::Ordering::Equal { - return Err(SharingError::Internal( - "two interned nodes share one structural key".into(), - )); - } - } - for p in keyed { - // At most `n` terms are renumbered, so `next` stays below `n`. - new_id[p] = next; - next += 1; - nodes.push(self.nodes[p].map_children(|c| new_id[ix(c)])); - heights.push(h); - } - } - let roots = roots.iter().map(|&r| new_id[ix(r)]).collect(); - let dag = SharingDag { nodes, heights, roots }; - Ok((dag, new_id)) - } -} - -/// The scalar payload of a node other than `Ref`/`Rec` ([`Node::scalars`], -/// at most two values), without allocating. -fn small_scalars(node: &Node) -> ([u64; 2], usize) { - match node { - Node::Sort(n) | Node::Var(n) | Node::Str(n) | Node::Nat(n) => ([*n, 0], 1), - Node::Prj(t, f, _) => ([*t, *f], 2), - Node::Lam(c, ..) => ([u64::from(c.to_bits()), 0], 1), - Node::All(c, v, ..) => ([u64::from(pack_all_contract(*c, *v)), 0], 1), - Node::Let(c, ..) => ([c.flags(), u64::from(c.binder.to_bits())], 2), - Node::App(..) | Node::Ref(..) | Node::Rec(..) => ([0, 0], 0), - } -} - -/// The order of the [`NodeKey`]s of two nodes (tag, scalar payload, child -/// IDs mapped by `ids`), compared in place. -fn node_key_cmp(a: &Node, b: &Node, ids: &[TermId]) -> std::cmp::Ordering { - a.tag() - .cmp(&b.tag()) - .then_with(|| match (a, b) { - ( - Node::Ref(n, us) | Node::Rec(n, us), - Node::Ref(m, vs) | Node::Rec(m, vs), - ) => { - // `[n, |us|, us...]`, lexicographically. - n.cmp(m).then(us.len().cmp(&vs.len())).then_with(|| us.cmp(vs)) - }, - _ => { - let ((x, i), (y, j)) = (small_scalars(a), small_scalars(b)); - x[..i].cmp(&y[..j]) - }, - }) - .then_with(|| { - let (ca, cb) = (a.children(), b.children()); - ca.as_slice() - .iter() - .map(|&c| ids[ix(c)]) - .cmp(cb.as_slice().iter().map(|&c| ids[ix(c)])) - }) -} - -/// Classify a reference from `entry` to `index`, with -/// `entry <= index < table.len()`: a cycle if `index` can reach `entry` -/// through Share edges, otherwise a forward reference. -fn classify_nonbackward( - table: &[Arc], - entry: u64, - index: u64, - meter: &mut Meter<'_>, -) -> SharingError { - let forward = - SharingError::Malformed(MalformedSharing::ForwardShare { entry, index }); - let cyclic = - SharingError::Malformed(MalformedSharing::CyclicShare { entry, index }); - if index == entry { - return cyclic; - } - let mut visited: FxHashSet = FxHashSet::default(); - let mut todo = vec![index]; - while let Some(j) = todo.pop() { - if !visited.insert(j) { - continue; - } - let Some(e) = usize::try_from(j).ok().and_then(|j| table.get(j)) else { - continue; - }; - let mut seen: FxHashSet<*const Expr> = FxHashSet::default(); - let mut stack: Vec<&Expr> = vec![e.as_ref()]; - while let Some(x) = stack.pop() { - if !seen.insert(std::ptr::from_ref(x)) { - continue; - } - if let Err(err) = meter.input_node() { - return err; - } - if let Expr::Share(k) = x { - if *k == entry { - return cyclic; - } - todo.push(*k); - } - stack.extend(x.children().into_iter().map(|c| c.as_ref())); - } - } - forward -} - -impl SharingDag { - /// Build from roots; with `table`, Share leaves are expanded against it - /// under the backward-reference rule, otherwise they are rejected. - pub(crate) fn build( - roots: &[Arc], - table: Option<&[Arc]>, - meter: &mut Meter<'_>, - ) -> Result { - Self::build_full(roots, table, meter).map(|(dag, _)| dag) - } - - /// [`SharingDag::build`], also returning the term ID each table entry - /// expands to (`None` for an entry no root reaches). - pub(crate) fn build_full( - roots: &[Arc], - table: Option<&[Arc]>, - meter: &mut Meter<'_>, - ) -> Result<(SharingDag, Vec>), SharingError> { - let mut b = Builder { - nodes: Vec::new(), - heights: Vec::new(), - index: FxHashMap::default(), - meter, - }; - let mut memo: FxHashMap<*const Expr, TermId> = FxHashMap::default(); - let mut root_ids = Vec::with_capacity(roots.len()); - let mut entry_ids: Vec = Vec::new(); - match table { - None => { - for (i, r) in roots.iter().enumerate() { - let ctx = ShareCtx::Forbidden { root: u64_len(i) }; - root_ids.push(b.intern_expr(r, &mut memo, ctx)?); - } - }, - Some(table) => { - let mut resolved: Vec = Vec::with_capacity(table.len()); - for (j, e) in table.iter().enumerate() { - let ctx = ShareCtx::Table { - table, - resolved: &resolved, - location: ShareLocation::Entry(u64_len(j)), - }; - let id = b.intern_expr(e, &mut memo, ctx)?; - resolved.push(id); - entry_ids.push(id); - } - for (i, r) in roots.iter().enumerate() { - let ctx = ShareCtx::Table { - table, - resolved: &resolved, - location: ShareLocation::Root(u64_len(i)), - }; - root_ids.push(b.intern_expr(r, &mut memo, ctx)?); - } - }, - } - let (dag, new_id) = b.finalize(&root_ids)?; - let entries = entry_ids - .iter() - .map(|&p| Some(new_id[ix(p)]).filter(|&id| id != TermId::MAX)) - .collect(); - Ok((dag, entries)) - } - - /// Whether the table `entries` and `roots` expand exactly to the terms - /// `order` and to this DAG's roots, decided without interning: every - /// expression is matched top-down against the term it must denote, a Share - /// in entry `i` must name an entry `< i` and one in a root an entry `< - /// entries.len()`, memoized by pointer (a pointer checked under a smaller - /// bound stays valid under a larger one). On success returns the number of - /// pointer-distinct expression nodes reachable from `entries` and - /// `roots`. - /// - /// A success implies that [`SharingDag::build_full`]`(roots, - /// Some(entries))` returns this DAG and the entry IDs `order` (the - /// expansions are the same terms, and the DAG and its IDs are a function of - /// the terms reachable from the roots), and that its only possible error is - /// exhausting `max_input_nodes`: it charges one input node per - /// pointer-distinct node (its pointer memo is shared by all entries and - /// roots, and a pointer is only revisited after it was memoized), interns - /// the same distinct nodes at the same heights as the build of this DAG, - /// and finds no malformed Share. `None` means a mismatch; callers then run - /// the build itself. - pub(crate) fn check_encoding( - &self, - order: &[TermId], - entries: &[Arc], - roots: &[Arc], - ) -> Option { - if entries.len() != order.len() || roots.len() != self.roots.len() { - return None; - } - let mut memo: FxHashMap<*const Expr, TermId> = FxHashMap::default(); - let mut stack: Vec<(&Expr, TermId)> = Vec::new(); - let tasks = entries - .iter() - .zip(order) - .enumerate() - .map(|(i, (e, &t))| (e, t, i)) - .chain( - roots.iter().zip(&self.roots).map(|(e, &t)| (e, t, entries.len())), - ); - for (e, t, bound) in tasks { - stack.push((e.as_ref(), t)); - while let Some((x, t)) = stack.pop() { - let key = std::ptr::from_ref(x); - match memo.get(&key) { - Some(&u) if u == t => continue, - Some(_) => return None, - None => {}, - } - let node = self.nodes.get(ix(t))?; - let ok = match (x, node) { - (Expr::Share(k), _) => { - usize::try_from(*k).ok().is_some_and(|k| k < bound && order[k] == t) - }, - (Expr::Sort(a), Node::Sort(b)) - | (Expr::Var(a), Node::Var(b)) - | (Expr::Str(a), Node::Str(b)) - | (Expr::Nat(a), Node::Nat(b)) => a == b, - (Expr::Ref(a, us), Node::Ref(b, vs)) - | (Expr::Rec(a, us), Node::Rec(b, vs)) => a == b && us == vs, - (Expr::Prj(a, f, v), Node::Prj(b, g, tv)) => { - stack.push((v.as_ref(), *tv)); - a == b && f == g - }, - (Expr::App(f, a), Node::App(tf, ta)) => { - stack.push((a.as_ref(), *ta)); - stack.push((f.as_ref(), *tf)); - true - }, - (Expr::Lam(c, ty, b), Node::Lam(d, tty, tb)) => { - stack.push((b.as_ref(), *tb)); - stack.push((ty.as_ref(), *tty)); - c == d - }, - (Expr::All(c, v, ty, b), Node::All(d, u, tty, tb)) => { - stack.push((b.as_ref(), *tb)); - stack.push((ty.as_ref(), *tty)); - c == d && v == u - }, - (Expr::Let(c, ty, v, b), Node::Let(d, tty, tv, tb)) => { - stack.push((b.as_ref(), *tb)); - stack.push((v.as_ref(), *tv)); - stack.push((ty.as_ref(), *tty)); - c == d - }, - _ => false, - }; - if !ok { - return None; - } - memo.insert(key, t); - } - } - u64::try_from(memo.len()).ok() - } - - /// [`SharingDag::check_encoding`] and, in the same walk with the same - /// pointer memo, the length `exprs_len_with(entries.len(), entries ++ - /// roots, share)`. `None` on a mismatch or a length overflow (callers then - /// run the separate checks); otherwise the visited count and the length. - pub(crate) fn check_and_measure( - &self, - order: &[TermId], - entries: &[Arc], - roots: &[Arc], - share: &dyn Fn(u64) -> u64, - ) -> Option<(u64, u64)> { - use super::cost::{ExprLenInfo, expr_children, expr_len_info}; - if entries.len() != order.len() || roots.len() != self.roots.len() { - return None; - } - // Per pointer: the term it denotes and, once its children are done, its - // length information. - let mut memo: FxHashMap<*const Expr, (TermId, Option)> = - FxHashMap::default(); - let mut stack: Vec<(&Expr, TermId, bool)> = Vec::new(); - let mut total = tag0_len(u64_len(entries.len())); - let tasks = entries - .iter() - .zip(order) - .enumerate() - .map(|(i, (e, &t))| (e, t, i)) - .chain( - roots.iter().zip(&self.roots).map(|(e, &t)| (e, t, entries.len())), - ); - for (e, t, bound) in tasks { - stack.push((e.as_ref(), t, false)); - while let Some((x, t, ready)) = stack.pop() { - let key = std::ptr::from_ref(x); - if ready { - let info = { - let get = |c: &Arc| { - memo - .get(&Arc::as_ptr(c)) - .and_then(|m| m.1) - .unwrap_or(ExprLenInfo::ZERO) - }; - expr_len_info(x, &get, share)? - }; - memo.insert(key, (t, Some(info))); - continue; - } - match memo.get(&key) { - Some(&(u, _)) if u == t => continue, - Some(_) => return None, - None => {}, - } - let node = self.nodes.get(ix(t))?; - let mut kids: [Option; 3] = [None; 3]; - let ok = match (x, node) { - (Expr::Share(k), _) => { - usize::try_from(*k).ok().is_some_and(|k| k < bound && order[k] == t) - }, - (Expr::Sort(a), Node::Sort(b)) - | (Expr::Var(a), Node::Var(b)) - | (Expr::Str(a), Node::Str(b)) - | (Expr::Nat(a), Node::Nat(b)) => a == b, - (Expr::Ref(a, us), Node::Ref(b, vs)) - | (Expr::Rec(a, us), Node::Rec(b, vs)) => a == b && us == vs, - (Expr::Prj(a, f, _), Node::Prj(b, g, tv)) => { - kids[0] = Some(*tv); - a == b && f == g - }, - (Expr::App(..), Node::App(tf, ta)) => { - kids = [Some(*tf), Some(*ta), None]; - true - }, - (Expr::Lam(c, ..), Node::Lam(d, tty, tb)) => { - kids = [Some(*tty), Some(*tb), None]; - c == d - }, - (Expr::All(c, v, ..), Node::All(d, u, tty, tb)) => { - kids = [Some(*tty), Some(*tb), None]; - c == d && v == u - }, - (Expr::Let(c, ..), Node::Let(d, tty, tv, tb)) => { - kids = [Some(*tty), Some(*tv), Some(*tb)]; - c == d - }, - _ => false, - }; - if !ok { - return None; - } - memo.insert(key, (t, None)); - stack.push((x, t, true)); - for (c, tc) in expr_children(x).into_iter().zip(kids).rev() { - if let (Some(c), Some(tc)) = (c, tc) { - stack.push((c.as_ref(), tc, false)); - } - } - } - total = total - .checked_add(memo.get(&std::ptr::from_ref(e.as_ref()))?.1?.len())?; - } - Some((u64::try_from(memo.len()).ok()?, total)) - } - - /// The only error the re-expansion build can report once - /// [`SharingDag::check_encoding`] (or [`SharingDag::check_and_measure`]) - /// succeeded with `visited` nodes: `max_input_nodes` exhausted. - pub(crate) fn check_input_nodes( - &self, - visited: u64, - limits: &super::ExactSharingLimits, - ) -> Result<(), SharingError> { - if visited > limits.max_input_nodes { - return Err(SharingError::ResourceExhausted(super::ResourceExhausted { - resource: super::Resource::InputNodes, - limit: limits.max_input_nodes, - })); - } - Ok(()) - } - - /// The re-expansion check of an encoding of this DAG with table terms - /// `order`: `build_full(roots, Some(entries))` under a fresh meter of - /// `limits` must return this DAG and the entry IDs `order`, otherwise the - /// internal error `ast_msg` or `ids_msg`. - /// - /// When [`SharingDag::check_encoding`] succeeds the build would succeed, - /// unless its node count exceeds `max_input_nodes`, so only that limit is - /// checked; otherwise the build itself runs and reports. - pub(crate) fn check_reexpansion( - &self, - order: &[TermId], - entries: &[Arc], - roots: &[Arc], - limits: &super::ExactSharingLimits, - ast_msg: &str, - ids_msg: &str, - ) -> Result<(), SharingError> { - if let Some(visited) = self.check_encoding(order, entries, roots) { - return self.check_input_nodes(visited, limits); - } - let mut check_meter = Meter::new(limits); - let (check, entry_ids) = - SharingDag::build_full(roots, Some(entries), &mut check_meter)?; - if check != *self { - return Err(SharingError::Internal(ast_msg.into())); - } - if entry_ids - .iter() - .map(|x| x.unwrap_or(TermId::MAX)) - .ne(order.iter().copied()) - { - return Err(SharingError::Internal(ids_msg.into())); - } - Ok(()) - } - - /// Build from fully expanded roots; any Share leaf is an error. - pub fn from_expanded_roots( - roots: &[Arc], - limits: &super::ExactSharingLimits, - ) -> Result { - Self::build(roots, None, &mut Meter::new(limits)) - } - - /// Build by expanding `roots` against `table` (entry `i` may reference - /// only entries `< i`; roots may reference any entry). - pub fn from_shared( - roots: &[Arc], - table: &[Arc], - limits: &super::ExactSharingLimits, - ) -> Result { - Self::build(roots, Some(table), &mut Meter::new(limits)) - } - - /// Expand a Constant's ordered roots against its own sharing table. - pub fn from_constant( - c: &crate::constant::Constant, - limits: &super::ExactSharingLimits, - ) -> Result { - let roots = super::constant_info_root_exprs(&c.info); - Self::build(&roots, Some(&c.sharing), &mut Meter::new(limits)) - } - - /// Nodes indexed by term ID. - pub fn nodes(&self) -> &[Node] { - &self.nodes - } - - pub fn node(&self, t: TermId) -> &Node { - &self.nodes[ix(t)] - } - - /// Heights indexed by term ID (leaves are 0). - pub fn heights(&self) -> &[TermId] { - &self.heights - } - - /// Term IDs of the ordered roots. - pub fn roots(&self) -> &[TermId] { - &self.roots - } - - /// Number of distinct subterms `N`. - pub fn len(&self) -> usize { - self.nodes.len() - } - - pub fn is_empty(&self) -> bool { - self.nodes.is_empty() - } - - /// The §3.2 key of `t`. - pub fn key(&self, t: TermId) -> NodeKey { - let node = self.node(t); - NodeKey { - tag: node.tag(), - scalars: node.scalars(), - children: node.children().as_slice().to_vec(), - } - } - - /// Every term as an expanded expression, indexed by term ID. Built bottom - /// up with one `Arc` per distinct term. - pub fn term_exprs(&self) -> Vec> { - let mut out: Vec> = Vec::with_capacity(self.nodes.len()); - for node in &self.nodes { - let e = node.to_expr(|c| out[ix(c)].clone()); - out.push(Arc::new(e)); - } - out - } - - /// The expanded roots, in order. - pub fn root_exprs(&self) -> Vec> { - let terms = self.term_exprs(); - self.roots.iter().map(|&r| terms[ix(r)].clone()).collect() - } -} diff --git a/crates/ixon/src/sharing_exact/dict.rs b/crates/ixon/src/sharing_exact/dict.rs deleted file mode 100644 index 185809183..000000000 --- a/crates/ixon/src/sharing_exact/dict.rs +++ /dev/null @@ -1,1303 +0,0 @@ -//! The fixed-dictionary optimizer `C_M` (§5) and byte-least materialization. -//! -//! For a dictionary `M` (available terms and their Share widths), `C_M(t)` -//! is the minimum length of a standalone expression expanding to `t`. The -//! options at `t` are a Share of `t` (if available) and its inline -//! constructor. For App, Lam and All the inline constructor is a telescope -//! over the maximal same-family spine `t = t_0, t_1, ..., t_l` (App follows -//! the function, Lam/All the body; `t_l` is the first node of another -//! constructor). A telescope of `j` spine nodes costs its TagN header for -//! `j`, every side child's standalone cost (App arguments; Lam/All contract -//! byte plus binder type), and its head: for `j < l` the head `t_j` continues -//! the family, so the canonical writer would merge any inline form of it -//! back into the telescope and the only legal head is a Share of `t_j`; for -//! `j = l` the head is the natural tail at its standalone cost. Every -//! canonical telescope boundary is one of these cases, so the recurrence is -//! complete; it is sound because each case is realized by a concrete -//! expression that the canonical writer emits with exactly that header. -//! -//! Children are standalone expressions and their lengths add, so the -//! recurrence runs bottom-up in term-ID order (children have smaller IDs). - -use std::cmp::Ordering; -use std::sync::Arc; - -use rustc_hash::FxHashMap; - -use super::SharingError; -use super::cost::{Len, share_width, tag4_bytes_cmp, tag4_len}; -use super::dag::{Node, SharingDag, TermId, ix}; -use crate::expr::Expr; - -/// Share widths available to one evaluation. -pub(crate) trait Widths { - fn width(&self, t: TermId) -> Option; -} - -/// A dictionary with actual table indices, needed to materialize Shares. -pub(crate) trait Indices: Widths { - fn index(&self, t: TermId) -> Option; -} - -/// The empty dictionary. -pub(crate) struct NoWidths; - -impl Widths for NoWidths { - fn width(&self, _t: TermId) -> Option { - None - } -} - -/// A fixed dictionary: available terms and their actual table indices. -/// Indices need not be consecutive, which lets tests force any width. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct FixedDictionary { - index: FxHashMap, -} - -impl FixedDictionary { - pub fn new() -> Self { - Self::default() - } - - /// Make `t` available as `Share(index)`; returns a previous index. - pub fn insert(&mut self, t: TermId, index: u64) -> Option { - self.index.insert(t, index) - } - - pub fn index_of(&self, t: TermId) -> Option { - self.index.get(&t).copied() - } - - pub fn len(&self) -> usize { - self.index.len() - } - - pub fn is_empty(&self) -> bool { - self.index.is_empty() - } -} - -impl Widths for FixedDictionary { - fn width(&self, t: TermId) -> Option { - self.index.get(&t).map(|&i| share_width(i)) - } -} - -impl Indices for FixedDictionary { - fn index(&self, t: TermId) -> Option { - self.index_of(t) - } -} - -/// A dense prefix dictionary for table sequences (indices `< 2^32`). -pub(crate) struct DenseIndex { - index: Vec, -} - -impl DenseIndex { - const NONE: u64 = u64::MAX; - - pub(crate) fn new(n: usize) -> Self { - DenseIndex { index: vec![Self::NONE; n] } - } - - pub(crate) fn set(&mut self, t: TermId, i: u64) { - self.index[ix(t)] = i; - } -} - -impl Widths for DenseIndex { - fn width(&self, t: TermId) -> Option { - self.index(t).map(share_width) - } -} - -impl Indices for DenseIndex { - fn index(&self, t: TermId) -> Option { - let i = self.index[ix(t)]; - (i != Self::NONE).then_some(i) - } -} - -/// The representation chosen for one standalone occurrence. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum Choice { - /// A Share of this term's table entry. - Share, - /// The node's own constructor with standalone children (leaf, Prj, Let). - Inline, - /// A telescope of `j >= 1` spine nodes (see the module docs). - Telescope(u64), -} - -/// Scan the telescope options of an App/Lam/All node `t`. -/// -/// Returns the least inline length found and its prefix length `j`. With -/// `ties` unset the scan may stop once no remaining option can be strictly -/// below `min(best, share)`; with `ties` set it stops only once none can -/// reach that value, and among equal lengths it keeps the `j` whose TagN -/// header bytes are least. Remaining options are bounded below by -/// `header(j) + s + 2`: headers never shrink, a further spine node adds a -/// side cost of at least one byte, and every head costs at least one byte. -#[allow(clippy::too_many_arguments)] -fn scan_telescope Len>( - nodes: &[Node], - own: &[Len], - t: TermId, - widths: &W, - cost: &C, - ties: bool, - share: Len, - work: &mut u64, -) -> (Len, u64) { - let family = nodes[ix(t)].family(); - let mut best = Len::OVERFLOW; - let mut best_j: u64 = 0; - let mut cur = t; - let mut j: u64 = 0; - let mut s = Len::ZERO; - loop { - *work = work.saturating_add(1); - let (side, next) = match &nodes[ix(cur)] { - Node::App(f, a) => (cost(*a), *f), - Node::Lam(_, ty, b) | Node::All(_, _, ty, b) => { - (own[ix(cur)].plus(cost(*ty)), *b) - }, - _ => break, - }; - s = s.plus(side); - j = j.saturating_add(1); - let header = Len::new(tag4_len(j)); - let continues = nodes[ix(next)].family() == family; - let head = if continues { - widths.width(next).map(Len::new) - } else { - Some(cost(next)) - }; - if let Some(h) = head { - let len = header.plus(s).plus(h); - if len < best - || (ties && len == best && tag4_bytes_cmp(j, best_j) == Ordering::Less) - { - best = len; - best_j = j; - } - } - if !continues { - break; - } - let lower = header.plus(s).plus_u64(2); - let cap = best.min(share); - if (ties && lower > cap) || (!ties && lower >= cap) { - break; - } - cur = next; - } - (best, best_j) -} - -/// `C_M(t)` given the minimum standalone lengths `cost` of every smaller -/// term. With `ties`, also the pinned byte-least choice among options of -/// that length: any inline form precedes a Share (expression flags -/// `0x0..=0xA` are below the Share flag `0xB`), and telescopes of one node -/// are ordered by their TagN header bytes, which differ for distinct `j`. -/// Once the head is fixed the remaining segments have fixed lengths, so -/// their least bytes are chosen independently by the same rule. -pub(crate) fn eval_node Len>( - nodes: &[Node], - own: &[Len], - t: TermId, - widths: &W, - cost: &C, - ties: bool, - work: &mut u64, -) -> (Len, Choice) { - *work = work.saturating_add(1); - let share = widths.width(t).map_or(Len::OVERFLOW, Len::new); - let o = own[ix(t)]; - let (inline, choice) = match &nodes[ix(t)] { - Node::Sort(_) - | Node::Var(_) - | Node::Ref(..) - | Node::Rec(..) - | Node::Str(_) - | Node::Nat(_) => (o, Choice::Inline), - Node::Prj(_, _, v) => (o.plus(cost(*v)), Choice::Inline), - Node::Let(_, a, b, c) => { - (o.plus(cost(*a)).plus(cost(*b)).plus(cost(*c)), Choice::Inline) - }, - Node::App(..) | Node::Lam(..) | Node::All(..) => { - let (len, j) = - scan_telescope(nodes, own, t, widths, cost, ties, share, work); - (len, Choice::Telescope(j)) - }, - }; - if share < inline { (share, Choice::Share) } else { (inline, choice) } -} - -/// `C_M` for every term, bottom-up. -pub(crate) fn all_costs( - nodes: &[Node], - own: &[Len], - widths: &W, - work: &mut u64, -) -> Vec { - let mut costs: Vec = Vec::with_capacity(nodes.len()); - for t in 0..nodes.len() { - let c = eval_node(nodes, own, t, widths, &|x| costs[ix(x)], false, work).0; - costs.push(c); - } - costs -} - -/// `C_M` of every term under a dictionary that grows one term at a time -/// ([`IncrementalCosts::add`]), with the work count of a full [`all_costs`] -/// evaluation of the current dictionary ([`IncrementalCosts::work`]). -/// -/// Only terms whose evaluation can read a changed value are evaluated again. -/// [`eval_node`] at `t` reads `width(t)`; for a non-telescope node the costs -/// of its children; for an App/Lam/All node, along its spine `t = s_0, s_1, -/// ...` ([`scan_telescope`], which may stop earlier, so this is a superset), -/// the cost of every side child, the width of every spine successor of the -/// same family and the cost of the first successor of another family. So -/// after `width(t)` changes, `t` is evaluated again, and so is every parent -/// `p` of an evaluated term `x` such that `p` reads the cost of `x` and it -/// changed, or `x` continues the spine of `p` and either `x = t` or `x` is -/// spine-dirty (a value read along the spine of `x` changed). Terms are -/// evaluated in increasing ID, so children come first. Every other term reads -/// the same values as in the previous evaluation, so its cost and its work -/// are unchanged; the work count is the sum of the per-term work, which is -/// what [`all_costs`] counts. -pub(crate) struct IncrementalCosts<'a> { - nodes: &'a [Node], - own: &'a [Len], - edges: &'a ReadEdges, - eval: Evaluation, - /// Stamps of the current step: cost changed, spine-dirty. - changed: Vec, - spine_dirty: Vec, - epoch: u32, - /// The terms queued for evaluation in the current step, as a bit set. - queued: Vec, -} - -/// One evaluation of a dictionary ([`all_costs`]) with the work of every -/// term. -#[derive(Clone)] -pub(crate) struct Evaluation { - pub(crate) costs: Vec, - term_work: Vec, - total: u128, -} - -impl Evaluation { - /// [`all_costs`] of `widths`, keeping each term's work. - pub(crate) fn new( - nodes: &[Node], - own: &[Len], - widths: &W, - ) -> Self { - let n = nodes.len(); - let mut costs: Vec = Vec::with_capacity(n); - let mut term_work: Vec = Vec::with_capacity(n); - let mut total: u128 = 0; - for t in 0..n { - let mut w = 0u64; - let c = - eval_node(nodes, own, t, widths, &|x| costs[ix(x)], false, &mut w).0; - costs.push(c); - term_work.push(w); - total += u128::from(w); - } - Evaluation { costs, term_work, total } - } - - /// The work [`all_costs`] counts (it saturates; the per-term counts are - /// small). - pub(crate) fn work(&self) -> u64 { - u64::try_from(self.total).unwrap_or(u64::MAX) - } -} - -/// The parent edges of a DAG grouped by child (`edges[start[x]..start[x + -/// 1]]` are the distinct parents of `x`), each tagged with what the parent's -/// evaluation reads of `x`: [`READS_COST`] and/or [`READS_SPINE`]. -pub(crate) struct ReadEdges { - start: Vec, - edges: Vec<(TermId, u8)>, -} - -/// The parent reads the cost of the child (a non-telescope child, a side -/// child, or a spine successor of another family). -const READS_COST: u8 = 1; -/// The child continues the parent's spine: the parent reads its width and -/// the values along its spine. -const READS_SPINE: u8 = 2; - -impl ReadEdges { - pub(crate) fn new(nodes: &[Node]) -> Self { - let n = nodes.len(); - let reads = |p: &Node| -> [(TermId, u8); 3] { - match spine_parts(p) { - Some((side, next)) => { - let next_reads = if nodes[ix(next)].family() == p.family() { - READS_SPINE - } else { - READS_COST - }; - if side == next { - [(side, READS_COST | next_reads), (0, 0), (0, 0)] - } else { - [(side, READS_COST), (next, next_reads), (0, 0)] - } - }, - None => { - let mut out = [(0, 0); 3]; - for (slot, &c) in p.children().as_slice().iter().enumerate() { - match out[..slot].iter().position(|&(d, f)| f != 0 && d == c) { - Some(_) => {}, - None => out[slot] = (c, READS_COST), - } - } - out - }, - } - }; - // Offsets are `usize`: there are up to three edges per node, more than - // `u32` holds for the largest DAGs the limits admit. - let mut start = vec![0usize; n + 1]; - for node in nodes { - for (c, f) in reads(node) { - if f != 0 { - start[ix(c) + 1] += 1; - } - } - } - for i in 0..n { - start[i + 1] += start[i]; - } - let mut fill: Vec = start[..n].to_vec(); - let mut edges: Vec<(TermId, u8)> = vec![(0, 0); start[n]]; - for (p, node) in nodes.iter().enumerate() { - for (c, f) in reads(node) { - if f != 0 { - edges[fill[ix(c)]] = (p, f); - fill[ix(c)] += 1; - } - } - } - ReadEdges { start, edges } - } - - fn of(&self, x: usize) -> &[(TermId, u8)] { - &self.edges[self.start[x]..self.start[x + 1]] - } -} - -impl<'a> IncrementalCosts<'a> { - /// Continue from the evaluation `eval` of the current dictionary. - pub(crate) fn new( - nodes: &'a [Node], - own: &'a [Len], - edges: &'a ReadEdges, - eval: Evaluation, - ) -> Self { - let n = nodes.len(); - IncrementalCosts { - nodes, - own, - edges, - eval, - changed: vec![0; n], - spine_dirty: vec![0; n], - epoch: 0, - queued: vec![0; n.div_ceil(64)], - } - } - - /// `C_M` of every term under the current dictionary. - pub(crate) fn costs(&self) -> &[Len] { - &self.eval.costs - } - - /// The work [`all_costs`] counts for the current dictionary (saturating). - pub(crate) fn work(&self) -> u64 { - self.eval.work() - } - - /// `widths` is the previous dictionary with `t`'s width changed (`t` was - /// added); update every cost. - pub(crate) fn add(&mut self, t: TermId, widths: &W) { - if self.epoch == u32::MAX { - self.changed.fill(0); - self.spine_dirty.fill(0); - self.epoch = 0; - } - self.epoch += 1; - let ep = self.epoch; - let nodes = self.nodes; - // Queued terms are only ever larger than the term being evaluated, so - // one forward sweep over the bit set evaluates them in increasing ID. - let mut last = ix(t); - self.queued[last / 64] |= 1 << (last % 64); - let mut word = last / 64; - while word <= last / 64 { - let bits = self.queued[word]; - if bits == 0 { - word += 1; - continue; - } - let bit = bits.trailing_zeros() as usize; - self.queued[word] &= !(1u64 << bit); - let xi = word * 64 + bit; - let x: TermId = xi; - let mut w = 0u64; - let c = { - let costs = &self.eval.costs; - eval_node(nodes, self.own, x, widths, &|y| costs[ix(y)], false, &mut w) - .0 - }; - let ev = &mut self.eval; - ev.total = ev.total - u128::from(ev.term_work[xi]) + u128::from(w); - ev.term_work[xi] = w; - if c != ev.costs[xi] { - ev.costs[xi] = c; - self.changed[xi] = ep; - } - let changed = |y: TermId| self.changed[ix(y)] == ep; - // Whether a value read along the spine of `x` (side costs, widths of - // continuing successors, the cost of the natural tail) changed. - if let Some((side, next)) = spine_parts(&nodes[xi]) - && (changed(side) - || if nodes[ix(next)].family() == nodes[xi].family() { - next == t || self.spine_dirty[ix(next)] == ep - } else { - changed(next) - }) - { - self.spine_dirty[xi] = ep; - } - let x_changed = changed(x); - let x_spine = x == t || self.spine_dirty[xi] == ep; - if !x_changed && !x_spine { - continue; - } - for &(p, f) in self.edges.of(xi) { - if (f & READS_COST != 0 && x_changed) - || (f & READS_SPINE != 0 && x_spine) - { - let pi = ix(p); - self.queued[pi / 64] |= 1 << (pi % 64); - last = last.max(pi); - } - } - } - } -} - -/// The phase-3 evaluation of a growing dictionary that evaluates a term only -/// when its cost or its work is needed, with the work count of a full -/// [`all_costs`] evaluation of the current dictionary at every step. -/// -/// **Work.** [`eval_node`] counts 1 for a term plus, for an App/Lam/All term -/// `t`, one per telescope option [`scan_telescope`] visits. When no term of -/// `t`'s spine (`t` and its same-family successors) is available, the scan -/// cannot stop before the natural end: no head is found before it and `t` -/// has no Share, so the bound it compares with, `min(best, share)`, stays -/// [`Len::OVERFLOW`], which no unsaturated length reaches. Its work is then -/// `1 + spine length` whatever the costs, and a non-telescope term's work is -/// always 1. Only the telescope terms with an available term on their spine -/// (`spined`; the set only grows) can have cost-dependent work; they are -/// evaluated again at every step where one of their inputs may have -/// changed. Every other term's last recorded work is its current work, so -/// the sum of the recorded work is the full evaluation's count. Saturation -/// is excluded up front ([`LazyCosts::applies`]): every cost of the empty -/// dictionary is below `2^62`, costs only decrease as terms become -/// available, and every partial length of a scan is at most the term's cost -/// of the empty dictionary plus a header and 2. -/// -/// **Costs.** When a term becomes available, it and all its ancestors are -/// marked stale (the stale set is closed upward), and a stale term is -/// evaluated again only when it is needed: before a materialization, every -/// stale descendant of the targets (children first, so its inputs are -/// current), and every stale spined term at each step. Evaluating a term -/// from current inputs gives what a full evaluation gives. -pub(crate) struct LazyCosts<'a> { - nodes: &'a [Node], - own: &'a [Len], - edges: &'a ReadEdges, - eval: Evaluation, - stale: Vec, - spined: Vec, - /// Scratch of `ensure`: visited marks of the current call. - mark: Vec, - epoch: u32, - stack: Vec, - order: Vec<(TermId, bool)>, -} - -impl<'a> LazyCosts<'a> { - /// Whether the work argument applies: every cost of the empty dictionary - /// (`base`) is below `2^62`. - pub(crate) fn applies(base: &Evaluation) -> bool { - base.costs.iter().all(|c| c.raw() < 1 << 62) - } - - /// Continue from `eval`, the full evaluation of `widths`. - pub(crate) fn new( - nodes: &'a [Node], - own: &'a [Len], - edges: &'a ReadEdges, - eval: Evaluation, - widths: &W, - ) -> Self { - let n = nodes.len(); - // `reach[t]`: `t` or a same-family successor on its spine is available. - let mut reach = vec![false; n]; - let mut spined = vec![false; n]; - for t in 0..n { - let avail = widths.width(t).is_some(); - if let Some((_, next)) = spine_parts(&nodes[t]) { - let same = nodes[ix(next)].family() == nodes[t].family(); - reach[t] = avail || (same && reach[ix(next)]); - spined[t] = reach[t]; - } else { - reach[t] = avail; - } - } - LazyCosts { - nodes, - own, - edges, - eval, - stale: vec![false; n], - spined, - mark: vec![0; n], - epoch: 0, - stack: Vec::new(), - order: Vec::new(), - } - } - - /// `C_M` of every term that is not stale; [`LazyCosts::prepare`] makes the - /// descendants of targets current. - pub(crate) fn costs(&self) -> &[Len] { - &self.eval.costs - } - - /// The work [`all_costs`] counts for the current dictionary (saturating). - pub(crate) fn work(&self) -> u64 { - self.eval.work() - } - - /// Make the costs of `targets` and of all their descendants current. - pub(crate) fn prepare(&mut self, targets: &[TermId], widths: &W) { - for &t in targets { - self.ensure(t, widths); - } - } - - /// `widths` is the previous dictionary with `t` added. - pub(crate) fn add(&mut self, t: TermId, widths: &W) { - let nodes = self.nodes; - // `t` and every ancestor become stale. - let mut due: Vec = Vec::new(); - self.stack.clear(); - if !self.stale[ix(t)] { - self.stale[ix(t)] = true; - self.stack.push(t); - } - while let Some(x) = self.stack.pop() { - if self.spined[ix(x)] { - due.push(x); - } - for &(p, _) in self.edges.of(ix(x)) { - if !self.stale[ix(p)] { - self.stale[ix(p)] = true; - self.stack.push(p); - } - } - } - // `t` and the terms whose spine reaches it are spined from now on. The - // spined set is closed under spine predecessors, so the walk stops at a - // term that already is. - self.stack.clear(); - if spine_parts(&nodes[ix(t)]).is_some() && !self.spined[ix(t)] { - self.spined[ix(t)] = true; - self.stack.push(t); - } - while let Some(x) = self.stack.pop() { - due.push(x); - for &(p, f) in self.edges.of(ix(x)) { - if f & READS_SPINE != 0 && !self.spined[ix(p)] { - self.spined[ix(p)] = true; - self.stack.push(p); - } - } - } - for x in due { - self.ensure(x, widths); - } - } - - /// Evaluate the stale terms among `x` and its descendants, children - /// first. The stale set is closed upward, so they are reached through - /// stale terms only. - fn ensure(&mut self, x: TermId, widths: &W) { - if !self.stale[ix(x)] { - return; - } - if self.epoch == u32::MAX { - self.mark.fill(0); - self.epoch = 0; - } - self.epoch += 1; - let ep = self.epoch; - let nodes = self.nodes; - // Post-order over the stale region: a term is evaluated after all its - // stale descendants (which its evaluation may read). A term popped again - // after its first visit is done: it cannot be its own descendant. - let mut post: Vec<(TermId, bool)> = std::mem::take(&mut self.order); - post.clear(); - post.push((x, false)); - while let Some((y, expanded)) = post.pop() { - let yi = ix(y); - if !expanded { - if self.mark[yi] == ep { - continue; - } - self.mark[yi] = ep; - post.push((y, true)); - for &c in nodes[yi].children().as_slice() { - if self.stale[ix(c)] && self.mark[ix(c)] != ep { - post.push((c, false)); - } - } - continue; - } - let mut w = 0u64; - let c = { - let costs = &self.eval.costs; - eval_node(nodes, self.own, y, widths, &|z| costs[ix(z)], false, &mut w) - .0 - }; - let ev = &mut self.eval; - ev.total = ev.total - u128::from(ev.term_work[yi]) + u128::from(w); - ev.term_work[yi] = w; - ev.costs[yi] = c; - self.stale[yi] = false; - } - self.order = post; - } -} - -/// Side child and spine successor of an App/Lam/All node. -fn spine_parts(node: &Node) -> Option<(TermId, TermId)> { - match node { - Node::App(f, a) => Some((*a, *f)), - Node::Lam(_, ty, b) | Node::All(_, _, ty, b) => Some((*ty, *b)), - _ => None, - } -} - -fn internal(msg: &str) -> SharingError { - SharingError::Internal(msg.to_string()) -} - -/// The spine successor of a telescope node. -fn spine_next(node: &Node) -> Option { - match node { - Node::App(f, _) => Some(*f), - Node::Lam(_, _, b) | Node::All(_, _, _, b) => Some(*b), - _ => None, - } -} - -/// Visit the terms whose standalone representations choice `ch` at `t` -/// needs: the children of an inline node; for a telescope of `j` spine -/// nodes, the side child of each spine node (one unit of `work` per spine -/// node), then the node after the spine unless the telescope stops at a -/// Share of it (a cut). Returns whether the choice is a telescope with a -/// cut. -fn for_each_needed( - nodes: &[Node], - t: TermId, - ch: Choice, - work: &mut u64, - mut need: impl FnMut(TermId), -) -> Result { - match ch { - Choice::Share => Ok(false), - Choice::Inline => { - for &c in nodes[ix(t)].children().as_slice() { - need(c); - } - Ok(false) - }, - Choice::Telescope(j) => { - if j == 0 { - return Err(internal("telescope choice without an option")); - } - let mut cur = t; - for _ in 0..j { - *work = work.saturating_add(1); - let node = &nodes[ix(cur)]; - match node { - Node::App(_, a) => need(*a), - Node::Lam(_, ty, _) | Node::All(_, _, ty, _) => need(*ty), - _ => return Err(internal("telescope walked off its spine")), - } - cur = spine_next(node).ok_or_else(|| internal("spine"))?; - } - let cut = nodes[ix(cur)].family() == nodes[ix(t)].family(); - if !cut { - need(cur); - } - Ok(cut) - }, - } -} - -/// The spine nodes of a telescope choice of `j` nodes at `t`, the node -/// after them, and whether the telescope stops at a Share of it (a cut). -fn telescope_parts( - nodes: &[Node], - t: TermId, - j: u64, -) -> Result<(Vec, TermId, bool), SharingError> { - let mut spine = Vec::new(); - let mut cur = t; - for _ in 0..j { - spine.push(cur); - cur = spine_next(&nodes[ix(cur)]).ok_or_else(|| internal("spine"))?; - } - let cut = nodes[ix(cur)].family() == nodes[ix(t)].family(); - Ok((spine, cur, cut)) -} - -/// The standalone representation of `c` built earlier, from `rep`. -fn built( - rep: &[Option>], - c: TermId, -) -> Result, SharingError> { - rep.get(ix(c)).cloned().flatten().ok_or_else(|| internal("missing child")) -} - -/// The expression of `t` written with choice `ch` under `dict`, the -/// standalone representations of the terms it needs read from `rep`. -fn build_choice( - nodes: &[Node], - dict: &D, - t: TermId, - ch: Choice, - rep: &[Option>], -) -> Result, SharingError> { - let node = &nodes[ix(t)]; - Ok(match ch { - Choice::Share => Arc::new(Expr::Share( - dict.index(t).ok_or_else(|| internal("share index"))?, - )), - Choice::Inline => { - let mut kids: Vec> = Vec::new(); - for &c in node.children().as_slice() { - kids.push(built(rep, c)?); - } - let slot = |c: TermId| { - let pos = node.children().as_slice().iter().position(|&x| x == c); - kids[pos.unwrap_or(0)].clone() - }; - Arc::new(node.to_expr(slot)) - }, - Choice::Telescope(j) => { - let (spine, cur, cut) = telescope_parts(nodes, t, j)?; - let mut e = if cut { - Arc::new(Expr::Share( - dict.index(cur).ok_or_else(|| internal("cut index"))?, - )) - } else { - built(rep, cur)? - }; - for &s in spine.iter().rev() { - e = Arc::new(match &nodes[ix(s)] { - Node::App(_, a) => Expr::App(e, built(rep, *a)?), - Node::Lam(c, ty, _) => Expr::Lam(*c, built(rep, *ty)?, e), - Node::All(c, v, ty, _) => Expr::All(*c, *v, built(rep, *ty)?, e), - _ => return Err(internal("telescope rebuild")), - }); - } - e - }, - }) -} - -/// Materialize the byte-least minimum-length standalone expression of each -/// target under a fixed dictionary, given `costs = C_M` for every term. -pub(crate) fn materialize( - nodes: &[Node], - own: &[Len], - dict: &D, - costs: &[Len], - targets: &[TermId], - work: &mut u64, -) -> Result>, SharingError> { - let Some(&top) = targets.iter().max() else { - return Ok(Vec::new()); - }; - let top = ix(top); - let mut choice: Vec> = vec![None; top + 1]; - let mut needed = vec![false; top + 1]; - for &t in targets { - needed[ix(t)] = true; - } - // Top-down: decide every needed standalone occurrence. Needed children - // have smaller IDs, so one descending sweep suffices. - for t in (0..=top).rev() { - if !needed[t] { - continue; - } - let (_, ch) = eval_node(nodes, own, t, dict, &|x| costs[ix(x)], true, work); - choice[t] = Some(ch); - for_each_needed(nodes, t, ch, work, |c| needed[ix(c)] = true)?; - } - // Bottom-up: build each needed standalone representation once. - let mut rep: Vec>> = vec![None; top + 1]; - for t in 0..=top { - let Some(ch) = choice[t] else { continue }; - rep[t] = Some(build_choice(nodes, dict, t, ch, &rep)?); - } - targets.iter().map(|&t| built(&rep, t)).collect() -} - -/// [`materialize`] with scratch space reused across calls on one DAG: the -/// same choices, expressions and work, but the time of a call is linear in -/// the terms it writes, not in the largest target's ID. -/// -/// The needed terms are decided in decreasing ID (a max-heap; a needed term -/// is only ever marked by a larger one, so every term is decided once, after -/// all its users, exactly as the descending sweep of [`materialize`]), then -/// built in increasing ID. -pub(crate) struct Materializer { - /// `stamp[t] == epoch`: `t` is needed in the current call. - stamp: Vec, - epoch: u32, - rep: Vec>>, - heap: std::collections::BinaryHeap, -} - -impl Materializer { - pub(crate) fn new(n: usize) -> Self { - Materializer { - stamp: vec![0; n], - epoch: 0, - rep: vec![None; n], - heap: std::collections::BinaryHeap::new(), - } - } - - fn need(&mut self, t: TermId) { - let i = ix(t); - if self.stamp[i] != self.epoch { - self.stamp[i] = self.epoch; - self.heap.push(t); - } - } - - /// [`materialize`]`(nodes, own, dict, costs, targets, work)`. - #[cfg(test)] - pub(crate) fn run( - &mut self, - nodes: &[Node], - own: &[Len], - dict: &D, - costs: &[Len], - targets: &[TermId], - work: &mut u64, - ) -> Result>, SharingError> { - let plan = self.decide(nodes, own, dict, costs, targets, work)?; - self.build(nodes, dict, &plan) - } - - /// The decisions of [`Materializer::run`] (with all its work) without - /// building the expressions: the needed terms with their choices, and the - /// number of expression nodes [`Materializer::build`] allocates for them. - pub(crate) fn decide( - &mut self, - nodes: &[Node], - own: &[Len], - dict: &D, - costs: &[Len], - targets: &[TermId], - work: &mut u64, - ) -> Result { - if self.epoch == u32::MAX { - self.stamp.fill(0); - self.epoch = 0; - } - self.epoch += 1; - self.heap.clear(); - for &t in targets { - self.need(t); - } - let mut plan = - Plan { decided: Vec::new(), targets: targets.to_vec(), nodes: 0 }; - // Top-down: decide every needed standalone occurrence. - while let Some(tid) = self.heap.pop() { - let (_, ch) = - eval_node(nodes, own, tid, dict, &|x| costs[ix(x)], true, work); - plan.decided.push((tid, ch)); - plan.nodes = plan.nodes.saturating_add(match ch { - Choice::Share | Choice::Inline => 1, - Choice::Telescope(j) => j, - }); - if for_each_needed(nodes, tid, ch, work, |c| self.need(c))? { - // The cut: a Share of the spine node where the telescope stops. - plan.nodes = plan.nodes.saturating_add(1); - } - } - Ok(plan) - } - - /// Build the expressions of `plan` (from [`Materializer::decide`] under a - /// dictionary with the same indices as `dict` for the terms it shares). - pub(crate) fn build( - &mut self, - nodes: &[Node], - dict: &D, - plan: &Plan, - ) -> Result>, SharingError> { - // Bottom-up: build each needed standalone representation once. - for &(tid, ch) in plan.decided.iter().rev() { - let e = build_choice(nodes, dict, tid, ch, &self.rep)?; - self.rep[ix(tid)] = Some(e); - } - let out: Result>, SharingError> = - plan.targets.iter().map(|&t| built(&self.rep, t)).collect(); - for &(t, _) in &plan.decided { - self.rep[ix(t)] = None; - } - out - } -} - -/// The decisions of one materialization ([`Materializer::decide`]): the -/// needed terms in decision (decreasing) order with their choices, the -/// targets, and the number of expression nodes the build allocates (one -/// per Share or inline node, one per spine node of a telescope and one for -/// a telescope's cut Share). -#[derive(Debug, PartialEq, Eq)] -pub(crate) struct Plan { - decided: Vec<(TermId, Choice)>, - targets: Vec, - pub(crate) nodes: u64, -} - -/// `C_M(t)` for a fixed dictionary (§5). -pub fn dictionary_cost( - dag: &SharingDag, - dict: &FixedDictionary, - t: TermId, -) -> Len { - let own: Vec = dag.nodes().iter().map(Node::own_len).collect(); - let mut work = 0; - let costs = all_costs(dag.nodes(), &own, dict, &mut work); - costs.get(ix(t)).copied().unwrap_or(Len::OVERFLOW) -} - -/// The byte-least minimum-length standalone expression of each target under -/// a fixed dictionary; Shares use the dictionary's actual indices. -pub fn materialize_with_dictionary( - dag: &SharingDag, - dict: &FixedDictionary, - targets: &[TermId], -) -> Result>, SharingError> { - if targets.iter().any(|&t| ix(t) >= dag.len()) { - return Err(internal("target out of range")); - } - let own: Vec = dag.nodes().iter().map(Node::own_len).collect(); - let mut work = 0; - let costs = all_costs(dag.nodes(), &own, dict, &mut work); - materialize(dag.nodes(), &own, dict, &costs, targets, &mut work) -} - -/// A dictionary with one term's own Share hidden: the top of that term's -/// table entry, which may not reference itself. -pub(crate) struct Hide<'a, D> { - pub(crate) inner: &'a D, - pub(crate) hidden: TermId, -} - -impl Widths for Hide<'_, D> { - fn width(&self, t: TermId) -> Option { - if t == self.hidden { None } else { self.inner.width(t) } - } -} - -impl Indices for Hide<'_, D> { - fn index(&self, t: TermId) -> Option { - if t == self.hidden { None } else { self.inner.index(t) } - } -} - -/// Real table indices with every Share priced at one uniform width. -pub(crate) struct UniformIndex { - pub(crate) index: Vec>, - pub(crate) width: u64, -} - -impl Widths for UniformIndex { - fn width(&self, t: TermId) -> Option { - self.index[ix(t)].map(|_| self.width) - } -} - -impl Indices for UniformIndex { - fn index(&self, t: TermId) -> Option { - self.index[ix(t)] - } -} - -/// Materialize a table given in dependency order (every stored descendant -/// of an entry precedes it) and the roots, from one evaluation `costs` of the -/// whole dictionary `dict`. In such an order each entry can use every stored -/// term that can occur inside it and no other, so the full dictionary prices -/// and builds it exactly. Entry tops take their best non-Share choice; every -/// nested occurrence takes its standalone choice. Returns entries, roots and -/// the predicted cost `sum inline(entry) + sum C(root)` (without the count). -pub(crate) fn decide_dependent( - nodes: &[Node], - own: &[Len], - dict: &D, - costs: &[Len], - table: &[TermId], - roots: &[TermId], - work: &mut u64, -) -> Result { - let n = nodes.len(); - let mut predicted = Len::ZERO; - let mut entry_choice: Vec = Vec::with_capacity(table.len()); - let mut needed = vec![false; n]; - let mark_children = - |t: TermId, ch: Choice, needed: &mut Vec, work: &mut u64| { - for_each_needed(nodes, t, ch, work, |c| needed[ix(c)] = true).map(|_| ()) - }; - for &t in table { - let hide = Hide { inner: dict, hidden: t }; - let (c, ch) = - eval_node(nodes, own, t, &hide, &|x| costs[ix(x)], true, work); - if ch == Choice::Share { - return Err(internal("entry top chose a Share")); - } - predicted = predicted.plus(c); - mark_children(t, ch, &mut needed, work)?; - entry_choice.push(ch); - } - for &r in roots { - needed[ix(r)] = true; - predicted = predicted.plus(costs[ix(r)]); - } - let mut choice: Vec> = vec![None; n]; - for t in (0..n).rev() { - if !needed[t] { - continue; - } - let (c, ch) = eval_node(nodes, own, t, dict, &|x| costs[ix(x)], true, work); - if c != costs[t] { - return Err(internal("standalone choice differs from C_M")); - } - choice[t] = Some(ch); - mark_children(t, ch, &mut needed, work)?; - } - Ok(DependentPlan { entry_choice, choice, predicted }) -} - -/// The decisions of [`materialize_dependent`]: each entry's top choice, -/// the standalone choice of every needed term, and the predicted length. -pub(crate) struct DependentPlan { - entry_choice: Vec, - choice: Vec>, - pub(crate) predicted: Len, -} - -/// What the expressions of a [`DependentPlan`] contain, computed without -/// building them: per table position the number of Shares of that entry in -/// all the expression trees (entries and roots, with multiplicity, as a walk -/// of each tree counts them), per entry the entries its expression shares -/// in pre-order of first occurrence, and the number of expression nodes the -/// build allocates (all pointer-distinct). -#[derive(Clone, Debug)] -pub(crate) struct PlanShares { - pub(crate) refs: Vec, - pub(crate) deps: Vec>, - pub(crate) nodes: u64, -} - -/// One item of the pre-order walk of [`DependentPlan::shares`]. -#[derive(Clone, Copy)] -enum WalkItem { - /// The standalone representation of a term (shared by its occurrences). - Rep(TermId), - /// A Share of a term. - Shr(TermId), -} - -impl DependentPlan { - /// The children of the expression of `t` written with choice `ch`, in - /// expression order (`Expr` children, left to right). - fn items( - nodes: &[Node], - t: TermId, - ch: Choice, - out: &mut Vec, - ) -> Result { - out.clear(); - match ch { - Choice::Share => { - out.push(WalkItem::Shr(t)); - Ok(1) - }, - Choice::Inline => { - for &c in nodes[ix(t)].children().as_slice() { - out.push(WalkItem::Rep(c)); - } - Ok(1) - }, - Choice::Telescope(j) => { - let (spine, cur, cut) = telescope_parts(nodes, t, j)?; - let head = if cut { WalkItem::Shr(cur) } else { WalkItem::Rep(cur) }; - // App: `App(App(head, a_{j-1}) .., a_0)`, so the head, then the - // arguments from the deepest spine node up. Lam/All: - // `Lam(ty_0, Lam(ty_1, .. head))`, so the binder types from the top, - // then the head. - if matches!(nodes[ix(t)], Node::App(..)) { - out.push(head); - for &s in spine.iter().rev() { - if let Node::App(_, a) = nodes[ix(s)] { - out.push(WalkItem::Rep(a)); - } - } - } else { - for &s in &spine { - match nodes[ix(s)] { - Node::Lam(_, ty, _) | Node::All(_, _, ty, _) => { - out.push(WalkItem::Rep(ty)); - }, - _ => return Err(internal("telescope walked off its spine")), - } - } - out.push(head); - } - Ok(j + u64::from(cut)) - }, - } - } - - /// See [`PlanShares`]; `pos[t]` is the table position of a stored term. - pub(crate) fn shares( - &self, - nodes: &[Node], - table: &[TermId], - roots: &[TermId], - pos: &dyn Fn(TermId) -> Option, - ) -> Result { - let n = nodes.len(); - let mut refs = vec![0u64; table.len()]; - let add_ref = |refs: &mut Vec, x: TermId, m: u64| { - if let Some(r) = pos(x).and_then(|i| refs.get_mut(i)) { - *r = r.saturating_add(m); - } - }; - let mut items: Vec = Vec::new(); - // Occurrence counts of the standalone representations in all trees. - let mut mult = vec![0u64; n]; - let mut count = 0u64; - for &r in roots { - mult[ix(r)] = mult[ix(r)].saturating_add(1); - } - for (&t, &ch) in table.iter().zip(&self.entry_choice) { - count = count.saturating_add(Self::items(nodes, t, ch, &mut items)?); - for &it in &items { - match it { - WalkItem::Rep(c) => mult[ix(c)] = mult[ix(c)].saturating_add(1), - WalkItem::Shr(x) => add_ref(&mut refs, x, 1), - } - } - } - for t in (0..n).rev() { - let Some(ch) = self.choice[t] else { continue }; - count = count.saturating_add(Self::items(nodes, t, ch, &mut items)?); - let m = mult[t]; - if m == 0 { - continue; - } - for &it in &items { - match it { - WalkItem::Rep(c) => mult[ix(c)] = mult[ix(c)].saturating_add(m), - WalkItem::Shr(x) => add_ref(&mut refs, x, m), - } - } - } - // Per entry, the shared entries in pre-order of first occurrence. A - // representation met again contributes nothing new, so each is walked - // once per entry. - let mut deps = Vec::with_capacity(table.len()); - let mut seen_rep = vec![0usize; n]; - let mut seen_dep = vec![0usize; n]; - let mut stack: Vec = Vec::new(); - for (e, (&t, &ch)) in table.iter().zip(&self.entry_choice).enumerate() { - let ep = e + 1; - let mut ds: Vec = Vec::new(); - Self::items(nodes, t, ch, &mut items)?; - stack.clear(); - stack.extend(items.iter().rev()); - while let Some(it) = stack.pop() { - match it { - WalkItem::Shr(x) => { - if seen_dep[ix(x)] != ep { - seen_dep[ix(x)] = ep; - ds.push(x); - } - }, - WalkItem::Rep(c) => { - if seen_rep[ix(c)] == ep { - continue; - } - seen_rep[ix(c)] = ep; - let cch = - self.choice[ix(c)].ok_or_else(|| internal("missing choice"))?; - Self::items(nodes, c, cch, &mut items)?; - stack.extend(items.iter().rev()); - }, - } - } - deps.push(ds); - } - Ok(PlanShares { refs, deps, nodes: count }) - } -} - -/// The expressions of the decisions of [`decide_dependent`] for the same -/// `table` and `roots`: entries and roots. -pub(crate) fn build_dependent( - nodes: &[Node], - dict: &D, - plan: &DependentPlan, - table: &[TermId], - roots: &[TermId], -) -> Result<(Vec>, Vec>), SharingError> { - let n = nodes.len(); - let DependentPlan { entry_choice, choice, .. } = plan; - let mut rep: Vec>> = vec![None; n]; - for t in 0..n { - if let Some(ch) = choice[t] { - rep[t] = Some(build_choice(nodes, dict, t, ch, &rep)?); - } - } - let mut entries = Vec::with_capacity(table.len()); - for (&t, &ch) in table.iter().zip(entry_choice) { - entries.push(build_choice(nodes, dict, t, ch, &rep)?); - } - let roots_out = - roots.iter().map(|&r| built(&rep, r)).collect::>()?; - Ok((entries, roots_out)) -} diff --git a/crates/ixon/src/sharing_exact/measure.rs b/crates/ixon/src/sharing_exact/measure.rs deleted file mode 100644 index 003a81ecd..000000000 --- a/crates/ixon/src/sharing_exact/measure.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Bounded graph/sequence measurements shared with the reference tests. - -use super::SharingError; -use super::cost::{Len, tag0_len}; -use super::dag::{Node, SharingDag, TermId, ix}; -use super::dict::{DenseIndex, NoWidths, all_costs}; - -fn internal(msg: impl Into) -> SharingError { - SharingError::Internal(msg.into()) -} -/// Expanded occurrence counts (saturating) of every term. -pub(crate) fn occurrences(dag: &SharingDag) -> Vec { - let nodes = dag.nodes(); - let mut occ = vec![0u64; nodes.len()]; - for &r in dag.roots() { - occ[ix(r)] = occ[ix(r)].saturating_add(1); - } - // Parents have larger IDs than children: a descending sweep sees each - // node's final count before propagating it along every child edge. - for t in (0..nodes.len()).rev() { - let o = occ[t]; - if o == 0 { - continue; - } - for &c in nodes[t].children().as_slice() { - occ[ix(c)] = occ[ix(c)].saturating_add(o); - } - } - occ -} - -pub(super) fn candidates_of(occ: &[u64], base: &[Len]) -> Vec { - (0..occ.len()).filter(|&t| occ[t] >= 2 && base[t] > Len::new(1)).collect() -} - -/// The terms some minimum may store after reductions R1 (at least two -/// expanded occurrences) and R2 (inline encoding longer than one byte), in -/// term-ID order. Its length is the search's candidate count. -pub fn candidate_terms(dag: &SharingDag) -> Vec { - let own: Vec = dag.nodes().iter().map(Node::own_len).collect(); - let base = all_costs(dag.nodes(), &own, &NoWidths, &mut 0); - candidates_of(&occurrences(dag), &base) -} - -/// Exact variable length of the table sequence `q` with every entry and -/// root at its minimum: `sum C_{q Result { - let n = dag.len(); - let mut seen = vec![false; n]; - for &t in q { - if ix(t) >= n || seen[ix(t)] { - return Err(internal("sequence term out of range or repeated")); - } - seen[ix(t)] = true; - } - let own: Vec = dag.nodes().iter().map(Node::own_len).collect(); - let mut dict = DenseIndex::new(n); - let mut work = 0u64; - let k = u64::try_from(q.len()).map_err(|_e| internal("length"))?; - let mut total = Len::new(tag0_len(k)); - for (i, &t) in q.iter().enumerate() { - let costs = all_costs(dag.nodes(), &own, &dict, &mut work); - total = total.plus(costs[ix(t)]); - dict.set(t, u64::try_from(i).map_err(|_e| internal("index"))?); - } - let costs = all_costs(dag.nodes(), &own, &dict, &mut work); - for &r in dag.roots() { - total = total.plus(costs[ix(r)]); - } - Ok(total) -} diff --git a/crates/ixon/src/sharing_exact/mss.rs b/crates/ixon/src/sharing_exact/mss.rs deleted file mode 100644 index bdd49b230..000000000 --- a/crates/ixon/src/sharing_exact/mss.rs +++ /dev/null @@ -1,154 +0,0 @@ -//! Maximal structural sharing (MSS), a reference encoding for the tests; it -//! is not on the compiler path. The tests use it as a valid Share-bearing -//! input that the exact constructions must normalize from and never exceed. -//! -//! MSS stores every candidate (compact in-degree at least 2, unshared length -//! at least 2), orders the table by the Kahn priority order of compact -//! in-degree (repeatedly the available entry, every stored term its body -//! references already placed, with the largest in-degree; ties by the -//! smaller structural ID), and replaces every occurrence of a stored term by -//! a Share, in roots and in other entries. - -use std::collections::BTreeSet; -use std::sync::Arc; - -use super::cost::Len; -use super::dag::{Node, SharingDag, TermId, ix}; -use super::dict::all_costs; -use super::uniform::graph_facts; -use super::{ - ExactSharingLimits, Meter, SharingError, constant_info_root_exprs, - rebuild_constant_info, -}; -use crate::constant::Constant; -use crate::expr::Expr; - -fn internal(msg: &str) -> SharingError { - SharingError::Internal(msg.to_string()) -} - -/// An MSS encoding of a DAG. -struct MssEncoding { - /// Stored terms in table order. - table_terms: Vec, - sharing: Vec>, - roots: Vec>, -} - -/// MSS of a DAG. -fn mss_dag(dag: &SharingDag) -> Result { - let nodes = dag.nodes(); - let n = nodes.len(); - let own: Vec = nodes.iter().map(Node::own_len).collect(); - let mut work = 0u64; - let base = all_costs(nodes, &own, &super::dict::NoWidths, &mut work); - let facts = graph_facts(dag); - let stored: Vec = - (0..n).map(|t| facts.deg[t] >= 2 && base[t] >= Len::new(2)).collect(); - // Dependencies: the stored terms reachable from a stored term's children - // through unstored terms (the Shares of its MSS body). - let mut deps: Vec> = vec![Vec::new(); n]; - let mut stamp = vec![usize::MAX; n]; - for t in 0..n { - if !stored[t] { - continue; - } - let mut stack: Vec = nodes[t].children().as_slice().to_vec(); - while let Some(c) = stack.pop() { - let c = ix(c); - if stamp[c] == t { - continue; - } - stamp[c] = t; - if stored[c] { - deps[t].push(TermId::try_from(c).map_err(|_e| internal("id"))?); - } else { - stack.extend_from_slice(nodes[c].children().as_slice()); - } - } - } - // Kahn priority order by in-degree, ties by the smaller ID. - let mut pend = vec![0usize; n]; - let mut users: Vec> = vec![Vec::new(); n]; - for t in 0..n { - if stored[t] { - pend[t] = deps[t].len(); - for &d in &deps[t] { - users[ix(d)].push(t); - } - } - } - let key = |t: usize| (std::cmp::Reverse(facts.deg[t]), t); - let mut ready: BTreeSet<(std::cmp::Reverse, usize)> = - (0..n).filter(|&t| stored[t] && pend[t] == 0).map(key).collect(); - let mut order: Vec = Vec::new(); - while let Some((_, t)) = ready.pop_first() { - order.push(t); - for &u in &users[t] { - pend[u] -= 1; - if pend[u] == 0 { - ready.insert(key(u)); - } - } - } - if order.len() != stored.iter().filter(|&&s| s).count() { - return Err(internal("MSS order is incomplete")); - } - let mut index = vec![None; n]; - for (i, &t) in order.iter().enumerate() { - index[t] = Some(i as u64); - } - // Shared form of every term (children have smaller IDs). - let mut shared: Vec> = Vec::with_capacity(n); - let mut body: Vec>> = vec![None; n]; - for t in 0..n { - let e = Arc::new(nodes[t].to_expr(|c| shared[ix(c)].clone())); - if let Some(i) = index[t] { - body[t] = Some(e); - shared.push(Expr::share(i)); - } else { - shared.push(e); - } - } - let sharing: Vec> = order - .iter() - .map(|&t| body[t].clone().ok_or_else(|| internal("missing body"))) - .collect::>()?; - let roots: Vec> = - dag.roots().iter().map(|&r| shared[ix(r)].clone()).collect(); - let table_terms = order - .iter() - .map(|&t| TermId::try_from(t).map_err(|_e| internal("id"))) - .collect::>()?; - Ok(MssEncoding { table_terms, sharing, roots }) -} - -/// MSS of a constant (its table expanded first), checked to expand to the -/// same DAG. -pub(crate) fn mss_constant( - c: &Constant, - limits: &ExactSharingLimits, -) -> Result { - let mut meter = Meter::new(limits); - let roots = constant_info_root_exprs(&c.info); - let dag = SharingDag::build(&roots, Some(&c.sharing), &mut meter)?; - let enc = mss_dag(&dag)?; - let mut check_meter = Meter::new(limits); - let (check, ids) = - SharingDag::build_full(&enc.roots, Some(&enc.sharing), &mut check_meter)?; - if check != dag - || ids - .iter() - .map(|x| x.unwrap_or(TermId::MAX)) - .ne(enc.table_terms.iter().copied()) - { - return Err(internal("MSS encoding does not expand to the input")); - } - let info = rebuild_constant_info(&c.info, &enc.roots)?; - Ok(Constant { - info, - sharing: enc.sharing, - refs: c.refs.clone(), - univs: c.univs.clone(), - }) -} diff --git a/crates/ixon/src/sharing_exact/oracle.rs b/crates/ixon/src/sharing_exact/oracle.rs deleted file mode 100644 index c1f9390cb..000000000 --- a/crates/ixon/src/sharing_exact/oracle.rs +++ /dev/null @@ -1,330 +0,0 @@ -//! A deliberately simple exact reference search for tiny inputs (§6.3): a -//! test oracle (compiled for tests only), not on the compiler path. -//! -//! It shares no search code with the optimizer: IDs are recomputed from -//! expression values, every ordered sequence of distinct expanded subterms is -//! tried as the table (not only R1/R2 candidates), every table entry and -//! root enumerates every per-occurrence choice between its inline constructor -//! and a Share of an equal earlier entry, and lengths and bytes come from the -//! real serializer. Candidates are compared by the full key `(L, Q, bytes)`. -//! -//! For one table sequence the entries and roots are separate byte segments of -//! the Constant: a minimum-length Constant has every segment at its minimum -//! length, so segment positions are fixed and the byte-least Constant takes -//! the byte-least minimum segment everywhere. [`oracle_full_product`] checks -//! this separability by enumerating complete Constants. - -use std::collections::HashMap; -use std::sync::Arc; - -use super::roots::{constant_info_root_exprs, rebuild_constant_info}; -use crate::constant::Constant; -use crate::contract::pack_all_contract; -use crate::expr::Expr; -use crate::serialize::put_expr; - -fn collect(e: &Arc, heights: &mut HashMap) -> u32 { - if let Some(&h) = heights.get(e.as_ref()) { - return h; - } - assert!(!matches!(e.as_ref(), Expr::Share(_)), "oracle input has Share"); - let h = e.children().into_iter().map(|c| collect(c, heights) + 1).max(); - let h = h.unwrap_or(0); - heights.insert(e.as_ref().clone(), h); - h -} - -fn key(e: &Expr, ids: &HashMap) -> (u8, Vec, Vec) { - let id = |c: &Arc| ids[c.as_ref()]; - match e { - Expr::Sort(u) => (0, vec![*u], vec![]), - Expr::Var(i) => (1, vec![*i], vec![]), - Expr::Ref(r, us) | Expr::Rec(r, us) => { - let tag = if matches!(e, Expr::Ref(..)) { 2 } else { 3 }; - let mut s = vec![*r, us.len() as u64]; - s.extend(us.iter().copied()); - (tag, s, vec![]) - }, - Expr::Prj(t, f, v) => (4, vec![*t, *f], vec![id(v)]), - Expr::Str(r) => (5, vec![*r], vec![]), - Expr::Nat(r) => (6, vec![*r], vec![]), - Expr::App(f, a) => (7, vec![], vec![id(f), id(a)]), - Expr::Lam(c, ty, b) => { - (8, vec![u64::from(c.to_bits())], vec![id(ty), id(b)]) - }, - Expr::All(c, v, ty, b) => { - (9, vec![u64::from(pack_all_contract(*c, *v))], vec![id(ty), id(b)]) - }, - Expr::Let(c, ty, v, b) => ( - 10, - vec![c.flags(), u64::from(c.binder.to_bits())], - vec![id(ty), id(v), id(b)], - ), - Expr::Share(_) => panic!("oracle input has Share"), - } -} - -/// Independent §3.2 IDs of the distinct subterms of `roots`, and the terms -/// in ID order. -pub(crate) fn oracle_ids( - roots: &[Arc], -) -> (HashMap, Vec>) { - let mut heights: HashMap = HashMap::new(); - for r in roots { - collect(r, &mut heights); - } - let max_h = heights.values().copied().max().unwrap_or(0); - let mut ids: HashMap = HashMap::new(); - let mut terms: Vec> = Vec::new(); - for h in 0..=max_h { - let mut level: Vec<(u8, Vec, Vec, Expr)> = heights - .iter() - .filter(|(_, hh)| **hh == h) - .map(|(e, _)| { - let (t, s, c) = key(e, &ids); - (t, s, c, e.clone()) - }) - .collect(); - level.sort_by(|a, b| (&a.0, &a.1, &a.2).cmp(&(&b.0, &b.1, &b.2))); - for (_, _, _, e) in level { - let id = terms.len(); - ids.insert(e.clone(), id); - terms.push(Arc::new(e)); - } - } - (ids, terms) -} - -/// Every representation of `e` using the available `(expanded, index)` -/// entries: at each occurrence, the inline constructor or a Share of an -/// equal entry. -pub(crate) fn variants( - e: &Arc, - avail: &[(Arc, u64)], -) -> Vec> { - let mut out: Vec> = avail - .iter() - .filter(|(x, _)| x == e) - .map(|(_, i)| Expr::share(*i)) - .collect(); - let kids = e.children(); - if kids.is_empty() { - out.push(e.clone()); - return out; - } - let mut combos: Vec>> = vec![vec![]]; - for c in kids { - let vs = variants(c, avail); - let mut next = Vec::with_capacity(combos.len() * vs.len()); - for prefix in &combos { - for v in &vs { - let mut row = prefix.clone(); - row.push(v.clone()); - next.push(row); - } - } - assert!(next.len() <= 2_000_000, "oracle variant explosion"); - combos = next; - } - out.extend(combos.iter().map(|cs| Arc::new(e.with_children(cs).unwrap()))); - out -} - -pub(crate) fn bytes_of(e: &Expr) -> Vec { - let mut b = Vec::new(); - put_expr(e, &mut b); - b -} - -/// The byte-least minimum-length variant and every variant's bytes. -fn best_variant(e: &Arc, avail: &[(Arc, u64)]) -> Arc { - variants(e, avail) - .into_iter() - .map(|v| (bytes_of(&v), v)) - .min_by(|a, b| (a.0.len(), &a.0).cmp(&(b.0.len(), &b.0))) - .unwrap() - .1 -} - -/// Result of the exhaustive search. -#[derive(Debug)] -pub(crate) struct OracleResult { - pub len: u64, - pub q: Vec, - pub bytes: Vec, - pub constant: Constant, - /// Every table sequence (as oracle IDs) that attains the minimum length. - pub minimal_sequences: Vec>, - pub sequences: u64, -} - -fn constant_bytes(c: &Constant) -> Vec { - let mut b = Vec::new(); - c.put(&mut b); - b -} - -fn for_each_sequence(n: usize, f: &mut impl FnMut(&[usize])) { - fn go(n: usize, order: &mut Vec, f: &mut impl FnMut(&[usize])) { - f(order); - for i in 0..n { - if !order.contains(&i) { - order.push(i); - go(n, order, f); - order.pop(); - } - } - } - go(n, &mut Vec::new(), f); -} - -fn available(terms: &[Arc], order: &[usize]) -> Vec<(Arc, u64)> { - order.iter().enumerate().map(|(i, &t)| (terms[t].clone(), i as u64)).collect() -} - -/// Exhaustive minimum of the full key over all tables and occurrence -/// choices. `template` supplies every non-sharing field; its roots must be -/// fully expanded and its table is ignored. -pub(crate) fn oracle_optimum(template: &Constant) -> OracleResult { - let roots = constant_info_root_exprs(&template.info); - let (_, terms) = oracle_ids(&roots); - let mut best: Option<(u64, Vec, Vec, Constant)> = None; - let mut min_len = u64::MAX; - let mut minimal: Vec> = Vec::new(); - let mut sequences = 0u64; - for_each_sequence(terms.len(), &mut |order| { - sequences += 1; - let mut table = Vec::with_capacity(order.len()); - for i in 0..order.len() { - let avail = available(&terms, &order[..i]); - table.push(best_variant(&terms[order[i]], &avail)); - } - let avail = available(&terms, order); - let reps: Vec> = - roots.iter().map(|r| best_variant(r, &avail)).collect(); - let info = rebuild_constant_info(&template.info, &reps).unwrap(); - let c = Constant { - info, - sharing: table, - refs: template.refs.clone(), - univs: template.univs.clone(), - }; - let bytes = constant_bytes(&c); - let len = bytes.len() as u64; - let q: Vec = order.to_vec(); - if len < min_len { - min_len = len; - minimal.clear(); - } - if len == min_len { - minimal.push(q.clone()); - } - let better = match &best { - None => true, - Some((bl, bq, bb, _)) => (len, &q, &bytes) < (*bl, bq, bb), - }; - if better { - best = Some((len, q, bytes, c)); - } - }); - let (len, q, bytes, constant) = best.unwrap(); - minimal.sort(); - OracleResult { - len, - q, - bytes, - constant, - minimal_sequences: minimal, - sequences, - } -} - -/// The minimum key over the complete Cartesian product of every entry's and -/// root's variants, for every table sequence. Returns `None` if some -/// sequence has more than `cap` complete Constants. -pub(crate) fn oracle_full_product( - template: &Constant, - cap: usize, -) -> Option<(u64, Vec, Vec, u64)> { - let roots = constant_info_root_exprs(&template.info); - let (_, terms) = oracle_ids(&roots); - let mut best: Option<(u64, Vec, Vec)> = None; - let mut complete = 0u64; - let mut too_big = false; - for_each_sequence(terms.len(), &mut |order| { - if too_big { - return; - } - let mut components: Vec>> = Vec::new(); - for i in 0..order.len() { - components - .push(variants(&terms[order[i]], &available(&terms, &order[..i]))); - } - let k = components.len(); - let avail = available(&terms, order); - for r in &roots { - components.push(variants(r, &avail)); - } - let total: usize = components.iter().map(Vec::len).product(); - if total > cap { - too_big = true; - return; - } - let q: Vec = order.to_vec(); - let mut pick = vec![0usize; components.len()]; - loop { - let table: Vec> = - (0..k).map(|i| components[i][pick[i]].clone()).collect(); - let reps: Vec> = - (k..components.len()).map(|i| components[i][pick[i]].clone()).collect(); - let info = rebuild_constant_info(&template.info, &reps).unwrap(); - let c = Constant { - info, - sharing: table, - refs: template.refs.clone(), - univs: template.univs.clone(), - }; - let bytes = constant_bytes(&c); - complete += 1; - let len = bytes.len() as u64; - let better = match &best { - None => true, - Some((bl, bq, bb)) => (len, &q, &bytes) < (*bl, bq, bb), - }; - if better { - best = Some((len, q.clone(), bytes)); - } - // Odometer increment. - let mut d = 0; - loop { - if d == pick.len() { - return; - } - pick[d] += 1; - if pick[d] < components[d].len() { - break; - } - pick[d] = 0; - d += 1; - } - } - }); - if too_big { - return None; - } - best.map(|(l, q, b)| (l, q, b, complete)) -} - -/// Number of representations [`variants`] would enumerate (saturating). -pub(crate) fn variant_count(e: &Arc, avail: &[(Arc, u64)]) -> u128 { - let shares = avail.iter().filter(|(x, _)| x == e).count() as u128; - let kids = e.children(); - if kids.is_empty() { - return shares + 1; - } - kids - .into_iter() - .map(|c| variant_count(c, avail)) - .fold(1u128, u128::saturating_mul) - .saturating_add(shares) -} diff --git a/crates/ixon/src/sharing_exact/par.rs b/crates/ixon/src/sharing_exact/par.rs deleted file mode 100644 index d08ca5cc1..000000000 --- a/crates/ixon/src/sharing_exact/par.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! Deterministic task splitting for the parallel paths of the tiered -//! construction. Results always come back in index order, so the callers -//! combine them exactly as the sequential path does. - -use std::ops::Range; - -/// Split `0..n` into at most `budget` contiguous ranges of nearly equal -/// length (none empty). -pub(crate) fn split(n: usize, budget: usize) -> Vec> { - let tasks = budget.clamp(1, n.max(1)); - let size = n.div_ceil(tasks).max(1); - (0..n).step_by(size).map(|s| s..(s + size).min(n)).collect() -} - -/// Run `f` on the ranges of [`split`]`(n, budget)`, in parallel on the -/// current rayon pool when `budget > 1`, and concatenate the results in -/// range order. With `budget <= 1` (or without rayon) this is `f(0..n)`. -pub(crate) fn map_ranges(n: usize, budget: usize, f: F) -> Vec -where - R: Send, - F: Fn(Range) -> Vec + Sync, -{ - if budget <= 1 || n <= 1 { - return f(0..n); - } - #[cfg(not(target_arch = "riscv64"))] - { - use rayon::prelude::*; - let parts: Vec> = split(n, budget).into_par_iter().map(&f).collect(); - parts.into_iter().flatten().collect() - } - #[cfg(target_arch = "riscv64")] - { - f(0..n) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn split_covers_in_order() { - for n in 0..40 { - for b in 0..12 { - let r = split(n, b); - let flat: Vec = r.iter().cloned().flatten().collect(); - assert_eq!(flat, (0..n).collect::>()); - assert!(r.len() <= b.max(1)); - assert!(r.iter().all(|x| !x.is_empty())); - } - } - let v = map_ranges(37, 5, |r| r.map(|i| i * 2).collect()); - assert_eq!(v, (0..37).map(|i| i * 2).collect::>()); - } -} diff --git a/crates/ixon/src/sharing_exact/prof.rs b/crates/ixon/src/sharing_exact/prof.rs deleted file mode 100644 index da061df9f..000000000 --- a/crates/ixon/src/sharing_exact/prof.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Scoped phase timers of the tiered construction, for profiling only. -//! -//! With the `sharing-profile` feature every [`scope`] adds its wall time -//! (per thread, summed over threads) to a process-wide counter of its -//! [`Phase`]; [`report`] reads the counters. Without the feature (the -//! default) [`scope`] is an empty value and nothing is measured. Timing never -//! affects the construction. - -/// A timed part of the construction. Scopes of different phases do not -/// nest, except [`Phase::Total`], which wraps one construction after its DAG -/// is built (the [`Phase::Dag`] scope comes before it). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum Phase { - /// One whole tiered construction (DAG built). - Total, - /// Expansion of the input into the structural DAG. - Dag, - /// Per width: the candidate count and graph facts of `tiered_at`. - Prep, - /// Phase 1: graph facts, spine tables, bounds and the classification. - Classify, - /// Phase 1: the component searches. - Search, - /// Phase 1: the table-count knapsack. - Knapsack, - /// Phase 1: model length, pinned order and decisions of the uniform - /// optimum, and the building of its expressions where they are built. - UniformMaterialize, - /// Phase 1: the dry run (what the expressions contain, their real length), - /// or the re-expansion check and real length of built expressions. - UniformCheck, - /// Phase 2: reference counts, first tier, Kahn order. - Allocate, - /// Phase 3: dictionary evaluations (the whole table, or every prefix). - RematerializeCosts, - /// Phase 3: materialization decisions of the entries and roots. - RematerializeDecide, - /// Phase 3: building the expressions of the decisions. - RematerializeBuild, - /// Phase 3: price, re-expansion and length checks. - RematerializeCheck, - /// Reassembly and serialization of the output Constant. - Output, -} - -impl Phase { - #[cfg_attr(not(feature = "sharing-profile"), allow(dead_code))] - pub(crate) const ALL: [Phase; 14] = [ - Phase::Total, - Phase::Dag, - Phase::Prep, - Phase::Classify, - Phase::Search, - Phase::Knapsack, - Phase::UniformMaterialize, - Phase::UniformCheck, - Phase::Allocate, - Phase::RematerializeCosts, - Phase::RematerializeDecide, - Phase::RematerializeBuild, - Phase::RematerializeCheck, - Phase::Output, - ]; - - #[cfg_attr(not(feature = "sharing-profile"), allow(dead_code))] - fn name(self) -> &'static str { - match self { - Phase::Total => "total", - Phase::Dag => "dag", - Phase::Prep => "prep", - Phase::Classify => "p1.classify", - Phase::Search => "p1.search", - Phase::Knapsack => "p1.knapsack", - Phase::UniformMaterialize => "p1.materialize", - Phase::UniformCheck => "p1.check", - Phase::Allocate => "p2.allocate", - Phase::RematerializeCosts => "p3.costs", - Phase::RematerializeDecide => "p3.decide", - Phase::RematerializeBuild => "p3.build", - Phase::RematerializeCheck => "p3.check", - Phase::Output => "output", - } - } -} - -#[cfg(feature = "sharing-profile")] -mod imp { - use std::sync::atomic::{AtomicU64, Ordering}; - use std::time::Instant; - - use super::Phase; - - const N: usize = Phase::ALL.len(); - static NANOS: [AtomicU64; N] = [const { AtomicU64::new(0) }; N]; - static CALLS: [AtomicU64; N] = [const { AtomicU64::new(0) }; N]; - - pub(crate) struct Scope { - phase: Phase, - start: Instant, - } - - impl Drop for Scope { - fn drop(&mut self) { - let ns = u64::try_from(self.start.elapsed().as_nanos()).unwrap_or(0); - NANOS[self.phase as usize].fetch_add(ns, Ordering::Relaxed); - CALLS[self.phase as usize].fetch_add(1, Ordering::Relaxed); - } - } - - pub(crate) fn scope(phase: Phase) -> Scope { - Scope { phase, start: Instant::now() } - } - - pub(crate) fn report() -> Vec<(&'static str, u64, u64)> { - Phase::ALL - .iter() - .map(|&p| { - ( - p.name(), - NANOS[p as usize].load(Ordering::Relaxed), - CALLS[p as usize].load(Ordering::Relaxed), - ) - }) - .collect() - } -} - -#[cfg(not(feature = "sharing-profile"))] -mod imp { - use super::Phase; - - pub(crate) struct Scope; - - // Scopes are ended with `drop`, as with the feature. - impl Drop for Scope { - fn drop(&mut self) {} - } - - #[inline(always)] - pub(crate) fn scope(_phase: Phase) -> Scope { - Scope - } - - pub(crate) fn report() -> Vec<(&'static str, u64, u64)> { - Vec::new() - } -} - -pub(crate) use imp::scope; - -/// Per phase: name, summed nanoseconds and scope count (empty without the -/// `sharing-profile` feature). -pub fn profile_report() -> Vec<(&'static str, u64, u64)> { - imp::report() -} diff --git a/crates/ixon/src/sharing_exact/roots.rs b/crates/ixon/src/sharing_exact/roots.rs deleted file mode 100644 index 7c4085ba7..000000000 --- a/crates/ixon/src/sharing_exact/roots.rs +++ /dev/null @@ -1,205 +0,0 @@ -//! Ordered expression roots of a ConstantInfo and their exact reassembly. -//! -//! The order mirrors Lean's `Ix.CompileM.constantInfoRootExprs` and the Rust -//! compiler's `apply_sharing_to_*` layout: definitions `[type, value]`; -//! axioms and quotients `[type]`; recursors `type` then every rule `rhs`; -//! mutual blocks flatten each member in order (an inductive contributes its -//! type then each constructor type); projections contribute nothing. - -use std::sync::Arc; - -use super::{MalformedSharing, SharingError}; -use crate::constant::{ - ConstantInfo, Constructor, Definition, Inductive, MutConst, Recursor, - RecursorRule, -}; -use crate::expr::Expr; - -fn push_mut_const_roots(m: &MutConst, out: &mut Vec>) { - match m { - MutConst::Defn(d) => { - out.push(d.typ.clone()); - out.push(d.value.clone()); - }, - MutConst::Indc(i) => { - out.push(i.typ.clone()); - out.extend(i.ctors.iter().map(|c| c.typ.clone())); - }, - MutConst::Recr(r) => push_recursor_roots(r, out), - } -} - -fn push_recursor_roots(r: &Recursor, out: &mut Vec>) { - out.push(r.typ.clone()); - out.extend(r.rules.iter().map(|rule| rule.rhs.clone())); -} - -/// The ordered sharing roots of `info`. -pub fn constant_info_root_exprs(info: &ConstantInfo) -> Vec> { - let mut out = Vec::new(); - match info { - ConstantInfo::Defn(d) => { - out.push(d.typ.clone()); - out.push(d.value.clone()); - }, - ConstantInfo::Recr(r) => push_recursor_roots(r, &mut out), - ConstantInfo::Axio(a) => out.push(a.typ.clone()), - ConstantInfo::Quot(q) => out.push(q.typ.clone()), - ConstantInfo::CPrj(_) - | ConstantInfo::RPrj(_) - | ConstantInfo::IPrj(_) - | ConstantInfo::DPrj(_) => {}, - ConstantInfo::Muts(ms) => { - for m in ms { - push_mut_const_roots(m, &mut out); - } - }, - } - out -} - -/// Number of roots [`constant_info_root_exprs`] returns, without cloning. -pub fn constant_info_root_count(info: &ConstantInfo) -> usize { - fn mut_count(m: &MutConst) -> usize { - match m { - MutConst::Defn(_) => 2, - MutConst::Indc(i) => 1 + i.ctors.len(), - MutConst::Recr(r) => 1 + r.rules.len(), - } - } - match info { - ConstantInfo::Defn(_) => 2, - ConstantInfo::Recr(r) => 1 + r.rules.len(), - ConstantInfo::Axio(_) | ConstantInfo::Quot(_) => 1, - ConstantInfo::CPrj(_) - | ConstantInfo::RPrj(_) - | ConstantInfo::IPrj(_) - | ConstantInfo::DPrj(_) => 0, - ConstantInfo::Muts(ms) => ms.iter().map(mut_count).sum(), - } -} - -struct Feed<'a> { - roots: &'a [Arc], - next: usize, -} - -impl Feed<'_> { - fn take(&mut self) -> Option> { - let e = self.roots.get(self.next)?.clone(); - self.next += 1; - Some(e) - } -} - -fn rebuild_definition( - d: &Definition, - feed: &mut Feed<'_>, -) -> Option { - Some(Definition { - kind: d.kind, - safety: d.safety, - lvls: d.lvls, - typ: feed.take()?, - value: feed.take()?, - }) -} - -fn rebuild_recursor(r: &Recursor, feed: &mut Feed<'_>) -> Option { - let typ = feed.take()?; - let mut rules = Vec::with_capacity(r.rules.len()); - for rule in &r.rules { - rules.push(RecursorRule { fields: rule.fields, rhs: feed.take()? }); - } - Some(Recursor { - k: r.k, - is_unsafe: r.is_unsafe, - lvls: r.lvls, - params: r.params, - indices: r.indices, - motives: r.motives, - minors: r.minors, - typ, - rules, - }) -} - -fn rebuild_inductive(i: &Inductive, feed: &mut Feed<'_>) -> Option { - let typ = feed.take()?; - let mut ctors = Vec::with_capacity(i.ctors.len()); - for c in &i.ctors { - ctors.push(Constructor { - is_unsafe: c.is_unsafe, - lvls: c.lvls, - cidx: c.cidx, - params: c.params, - fields: c.fields, - typ: feed.take()?, - }); - } - Some(Inductive { - is_unsafe: i.is_unsafe, - lvls: i.lvls, - params: i.params, - indices: i.indices, - typ, - ctors, - }) -} - -fn rebuild_with( - info: &ConstantInfo, - feed: &mut Feed<'_>, -) -> Option { - Some(match info { - ConstantInfo::Defn(d) => ConstantInfo::Defn(rebuild_definition(d, feed)?), - ConstantInfo::Recr(r) => ConstantInfo::Recr(rebuild_recursor(r, feed)?), - ConstantInfo::Axio(a) => { - let mut a = a.clone(); - a.typ = feed.take()?; - ConstantInfo::Axio(a) - }, - ConstantInfo::Quot(q) => { - let mut q = q.clone(); - q.typ = feed.take()?; - ConstantInfo::Quot(q) - }, - ConstantInfo::CPrj(_) - | ConstantInfo::RPrj(_) - | ConstantInfo::IPrj(_) - | ConstantInfo::DPrj(_) => info.clone(), - ConstantInfo::Muts(ms) => { - let mut out = Vec::with_capacity(ms.len()); - for m in ms { - out.push(match m { - MutConst::Defn(d) => MutConst::Defn(rebuild_definition(d, feed)?), - MutConst::Indc(i) => MutConst::Indc(rebuild_inductive(i, feed)?), - MutConst::Recr(r) => MutConst::Recr(rebuild_recursor(r, feed)?), - }); - } - ConstantInfo::Muts(out) - }, - }) -} - -/// Replace the roots of `info`, in [`constant_info_root_exprs`] order, by -/// `roots`. Every non-expression field is kept. Fails unless the rebuild -/// consumes exactly `roots.len()` expressions. -pub fn rebuild_constant_info( - info: &ConstantInfo, - roots: &[Arc], -) -> Result { - let expected = constant_info_root_count(info); - let mismatch = || { - SharingError::Malformed(MalformedSharing::RootCountMismatch { - expected: u64::try_from(expected).unwrap_or(u64::MAX), - actual: u64::try_from(roots.len()).unwrap_or(u64::MAX), - }) - }; - let mut feed = Feed { roots, next: 0 }; - let rebuilt = rebuild_with(info, &mut feed).ok_or_else(mismatch)?; - if feed.next != roots.len() || expected != roots.len() { - return Err(mismatch()); - } - Ok(rebuilt) -} diff --git a/crates/ixon/src/sharing_exact/search.rs b/crates/ixon/src/sharing_exact/search.rs deleted file mode 100644 index 5279054a2..000000000 --- a/crates/ixon/src/sharing_exact/search.rs +++ /dev/null @@ -1,775 +0,0 @@ -//! Exact table selection and ordering (§6): the width-state search for the -//! global minimum of `docs/sharing-minimum.md` §3. Exponential in the -//! candidates, it is a test oracle for small inputs (and the reference of -//! the uniform optimizer's tests), not on the compiler path. -//! -//! A width state assigns each stored term the Share width of its index. By -//! §6.2 every future length depends only on the state, so histories reaching -//! one state merge: the state keeps its least accumulated body length `F` -//! and, among histories of that length, the least term-ID prefix. States are -//! sparse sorted `(term, width)` vectors, expanded layer by layer (layer `k` -//! holds states with `k` entries) in sorted key order, so work counters and -//! failures are deterministic. -//! -//! Candidates are the terms that some minimum may store: R1 drops terms with -//! one expanded occurrence and R2 terms whose inline encoding is one byte. -//! Both reductions strictly shorten any encoding that violates them, so they -//! preserve every minimum-length encoding and hence the canonical key. -//! -//! # Pruning -//! -//! A state is dropped only if a lower bound on every completion is strictly -//! above the length of a feasible encoding, so states that could tie the -//! minimum survive for the tie-break. Two bounds are used; either may prune. -//! -//! **Dictionary bound (§4.1).** At state `M` with `k` entries every later -//! entry has an index `>= k`, hence a Share width `>= share_width(k)`. Let -//! `M+` extend `M` with every absent candidate at width `share_width(k)` -//! (the plan's width 1 is the case `k < 8`). `C` is antitone in -//! availability and monotone in widths, later bodies cost `>= 0` and the -//! table count prefix never shrinks, so every completion has length at -//! least `F(M) + tag0(k) + sum C_{M+}(root)`. For a successor -//! `M + (t, share_width(k))` the same sum is still a lower bound, because -//! `t` keeps the width `M+` gave it and other widths only grow. -//! -//! **Materialization bound.** Let `E(M)` be the terms that occur in the -//! expansions of the entries of `M`. Those entries emit only terms of -//! `E(M)`. Every other term `t` must still have its constructor emitted -//! inline at least once, in a later entry or a root: follow any occurrence -//! through Shares until a representation emits it. Pick one such emission -//! per non-leaf `t` outside `E(M)`. It owns bytes no other picked emission -//! owns: `t`'s own bytes (Prj/Let headers, a Lam/All contract byte) and, at -//! each child position that holds a leaf, that leaf's inline encoding or a -//! Share (at least one byte for a candidate leaf; the inline length for any -//! other leaf, since only candidates are ever stored). An App/Lam/All node -//! that is never the same-family spine child of any parent starts a -//! telescope wherever it is emitted inline, so its picked emission also owns -//! one header byte (a "header top"). No other telescope header and no Share -//! byte at a non-leaf position is counted. Each root position adds bytes not -//! yet counted: a leaf root as a leaf position; a root in `E(M)` contains no -//! picked emission, so its whole representation, at least `C_{M+}(root)`; -//! an App/Lam/All root outside `E(M)` that is not a header top, its -//! telescope header or Share byte; any other root outside `E(M)`, a Share -//! or unpicked header byte at every position except possibly the one -//! holding its picked emission. The picked bytes lie outside the bodies -//! counted by `F(M)`, so `F(M) + tag0(k) + (picked) + (root bytes)` bounds -//! every completion. -//! -//! Both bounds hold for every completion by candidates, and every -//! minimum-length encoding is such a completion of each state on its path, -//! so no state on the path of a minimum is ever pruned. -//! -//! # Upper bounds -//! -//! The initial feasible length is the lesser of the unshared encoding and a -//! deterministic greedy table sequence. They only tighten pruning; neither is -//! returned unless the search certifies it. - -use std::sync::Arc; - -use rustc_hash::FxHashMap; - -use super::cost::{Len, expr_len, share_width, sharing_table_len, tag0_len}; -use super::dag::{Node, SharingDag, TermId, ix}; -use super::dict::{ - DenseIndex, NoWidths, Widths, all_costs, eval_node, materialize, -}; -use super::measure::{candidates_of, occurrences}; -use super::{ExactSharingResult, FormatBound, Meter, SharingError}; -use crate::expr::Expr; - -fn internal(msg: impl Into) -> SharingError { - SharingError::Internal(msg.into()) -} - -/// Per-DAG data shared by every state evaluation. -pub(crate) struct Prepared { - pub(crate) own: Vec, - /// `C` under the empty dictionary: the unshared standalone lengths. - pub(crate) base: Vec, - pub(crate) cands: Vec, - pub(crate) is_cand: Vec, - /// Materialization-bound bytes of one picked emission of each non-leaf - /// term (0 for leaves), and their sum. - pub(crate) picked: Vec, - pub(crate) picked_total: Len, - /// Lower bound on the bytes at a position holding each leaf. - pub(crate) leaf_pos: Vec, - /// App/Lam/All nodes whose picked emission owns a telescope header. - pub(crate) header_top: Vec, - parent_start: Vec, - parents: Vec, -} - -impl Prepared { - fn parents_of(&self, t: TermId) -> &[TermId] { - &self.parents[self.parent_start[ix(t)]..self.parent_start[ix(t) + 1]] - } -} - -/// Every candidate at one width. -struct Candidates<'a> { - is_cand: &'a [bool], - width: u64, -} - -impl Widths for Candidates<'_> { - fn width(&self, t: TermId) -> Option { - self.is_cand[ix(t)].then_some(self.width) - } -} - -pub(crate) fn prepare( - dag: &SharingDag, - meter: &mut Meter<'_>, -) -> Result { - prepare_with(dag, meter, |_| true) -} - -/// [`prepare`] keeping only the R1/R2 candidates accepted by `keep`. -pub(crate) fn prepare_with( - dag: &SharingDag, - meter: &mut Meter<'_>, - keep: impl Fn(TermId) -> bool, -) -> Result { - let nodes = dag.nodes(); - let n = nodes.len(); - let own: Vec = nodes.iter().map(Node::own_len).collect(); - let mut work = 0u64; - let base = all_costs(nodes, &own, &NoWidths, &mut work); - let occ = occurrences(dag); - let cands: Vec = - candidates_of(&occ, &base).into_iter().filter(|&t| keep(t)).collect(); - meter.candidates(u64::try_from(cands.len()).unwrap_or(u64::MAX))?; - let mut is_cand = vec![false; n]; - for &t in &cands { - is_cand[ix(t)] = true; - } - let leaf_pos: Vec = - (0..n).map(|t| if is_cand[t] { Len::new(1) } else { base[t] }).collect(); - // A node that is never the same-family spine child of a parent starts a - // telescope at every inline emission, so its picked emission owns that - // telescope's header. - let mut spine_child = vec![false; n]; - for node in nodes { - let next = match node { - Node::App(f, _) => Some(*f), - Node::Lam(_, _, b) | Node::All(_, _, _, b) => Some(*b), - _ => None, - }; - if let Some(c) = next - && nodes[ix(c)].family() == node.family() - { - spine_child[ix(c)] = true; - } - } - let mut header_top = vec![false; n]; - let mut picked = vec![Len::ZERO; n]; - let mut picked_total = Len::ZERO; - for (t, node) in nodes.iter().enumerate() { - let kids = node.children(); - if kids.as_slice().is_empty() { - continue; - } - let mut p = own[t]; - if node.family().is_some() && !spine_child[t] { - header_top[t] = true; - p = p.plus_u64(1); - } - for &c in kids.as_slice() { - if nodes[ix(c)].children().as_slice().is_empty() { - p = p.plus(leaf_pos[ix(c)]); - } - } - picked[t] = p; - picked_total = picked_total.plus(p); - } - // Distinct parents of every node, in CSR form. - let mut count = vec![0usize; n + 1]; - for node in nodes { - let kids = node.children(); - let kids = kids.as_slice(); - for (i, &c) in kids.iter().enumerate() { - if !kids[..i].contains(&c) { - count[ix(c)] += 1; - } - } - } - let mut parent_start = vec![0usize; n + 1]; - for i in 0..n { - parent_start[i + 1] = parent_start[i] + count[i]; - } - let mut fill = parent_start.clone(); - let mut parents = vec![0; parent_start[n]]; - for (p, node) in nodes.iter().enumerate() { - let kids = node.children(); - let kids = kids.as_slice(); - for (i, &c) in kids.iter().enumerate() { - if !kids[..i].contains(&c) { - parents[fill[ix(c)]] = p; - fill[ix(c)] += 1; - } - } - } - meter.work(work)?; - Ok(Prepared { - own, - base, - cands, - is_cand, - picked, - picked_total, - leaf_pos, - header_top, - parent_start, - parents, - }) -} - -struct DenseWidths<'a>(&'a [u8]); - -impl Widths for DenseWidths<'_> { - fn width(&self, t: TermId) -> Option { - match self.0[ix(t)] { - 0 => None, - w => Some(u64::from(w)), - } - } -} - -/// `M+`: the state's widths, and `plus` for every other candidate. -struct PlusWidths<'a> { - m: &'a [u8], - is_cand: &'a [bool], - plus: u64, -} - -impl Widths for PlusWidths<'_> { - fn width(&self, t: TermId) -> Option { - match self.m[ix(t)] { - 0 => self.is_cand[ix(t)].then_some(self.plus), - w => Some(u64::from(w)), - } - } -} - -/// Costs of one evaluated state. -struct StateCosts { - /// `sum C_M(root)`. - roots: Len, - /// `sum C_{M+}(root)`: the dictionary bound's variable part. - roots_plus: Len, - /// Picked and root bytes of the materialization bound. - materialization: Len, -} - -/// Scratch space for evaluating one state at a time. -/// -/// `C_M(t)` differs from the unshared `C(t)` only if `t` or a descendant is -/// stored, and `C_{M+}(t)` differs from its all-candidates value under the -/// same condition. Only those ancestors are recomputed. -struct StateEval<'p> { - prep: &'p Prepared, - nodes: &'p [Node], - roots: &'p [TermId], - width_m: Vec, - stamp: Vec, - covered: Vec, - seen_root: Vec, - epoch: u32, - cost_m: Vec, - cost_p: Vec, - affected: Vec, - todo: Vec, - /// Width given to absent candidates by `M+`, and `C` under exactly that - /// dictionary (every candidate at `plus`). - plus: u64, - plus_base: Vec, -} - -impl<'p> StateEval<'p> { - fn new(prep: &'p Prepared, dag: &'p SharingDag, work: &mut u64) -> Self { - let n = dag.len(); - let plus_base = all_costs( - dag.nodes(), - &prep.own, - &Candidates { is_cand: &prep.is_cand, width: 1 }, - work, - ); - StateEval { - prep, - nodes: dag.nodes(), - roots: dag.roots(), - width_m: vec![0; n], - stamp: vec![0; n], - covered: vec![0; n], - seen_root: vec![0; n], - epoch: 0, - cost_m: vec![Len::ZERO; n], - cost_p: vec![Len::ZERO; n], - affected: Vec::new(), - todo: Vec::new(), - plus: 1, - plus_base, - } - } - - /// Switch `M+` to give absent candidates width `plus`. - fn set_plus(&mut self, plus: u64, work: &mut u64) { - if plus != self.plus { - self.plus = plus; - self.plus_base = all_costs( - self.nodes, - &self.prep.own, - &Candidates { is_cand: &self.prep.is_cand, width: plus }, - work, - ); - } - } - - fn cost_m(&self, t: TermId) -> Len { - if self.stamp[ix(t)] == self.epoch { - self.cost_m[ix(t)] - } else { - self.prep.base[ix(t)] - } - } - - fn cost_p(&self, t: TermId) -> Len { - if self.stamp[ix(t)] == self.epoch { - self.cost_p[ix(t)] - } else { - self.plus_base[ix(t)] - } - } - - fn eval(&mut self, m: &[(TermId, u8)], work: &mut u64) -> StateCosts { - if self.epoch == u32::MAX { - self.stamp.fill(0); - self.covered.fill(0); - self.seen_root.fill(0); - self.epoch = 0; - } - self.epoch += 1; - let ep = self.epoch; - // Ancestors of stored terms: the only terms whose costs change. - self.affected.clear(); - for &(t, w) in m { - self.width_m[ix(t)] = w; - self.stamp[ix(t)] = ep; - self.affected.push(t); - } - let mut i = 0; - while i < self.affected.len() { - let x = self.affected[i]; - i += 1; - for &p in self.prep.parents_of(x) { - *work = work.saturating_add(1); - if self.stamp[ix(p)] != ep { - self.stamp[ix(p)] = ep; - self.affected.push(p); - } - } - } - self.affected.sort_unstable(); - for a in 0..self.affected.len() { - let t = self.affected[a]; - let cm = eval_node( - self.nodes, - &self.prep.own, - t, - &DenseWidths(&self.width_m), - &|c| self.cost_m(c), - false, - work, - ) - .0; - self.cost_m[ix(t)] = cm; - let widths = PlusWidths { - m: &self.width_m, - is_cand: &self.prep.is_cand, - plus: self.plus, - }; - let cp = eval_node( - self.nodes, - &self.prep.own, - t, - &widths, - &|c| self.cost_p(c), - false, - work, - ) - .0; - self.cost_p[ix(t)] = cp; - } - // E(M): descendants-or-self of stored terms, and their picked bytes. - let mut covered_bytes = Len::ZERO; - self.todo.clear(); - for &(t, _) in m { - if self.covered[ix(t)] != ep { - self.covered[ix(t)] = ep; - self.todo.push(t); - } - } - while let Some(x) = self.todo.pop() { - *work = work.saturating_add(1); - covered_bytes = covered_bytes.plus(self.prep.picked[ix(x)]); - for &c in self.nodes[ix(x)].children().as_slice() { - if self.covered[ix(c)] != ep { - self.covered[ix(c)] = ep; - self.todo.push(c); - } - } - } - let mut roots = Len::ZERO; - let mut roots_plus = Len::ZERO; - let mut root_bytes = Len::ZERO; - for &r in self.roots { - roots = roots.plus(self.cost_m(r)); - let cp = self.cost_p(r); - roots_plus = roots_plus.plus(cp); - let node = &self.nodes[ix(r)]; - let extra = if node.children().as_slice().is_empty() { - self.prep.leaf_pos[ix(r)] - } else if self.covered[ix(r)] == ep { - cp - } else if (node.family().is_some() && !self.prep.header_top[ix(r)]) - || self.seen_root[ix(r)] == ep - { - // An uncounted header or Share byte at this position. - Len::new(1) - } else { - // Possibly the position of the picked emission: count nothing. - self.seen_root[ix(r)] = ep; - Len::ZERO - }; - root_bytes = root_bytes.plus(extra); - } - // picked_total >= covered_bytes: E(M) is a set of distinct terms. - let outside = Len::new( - self.prep.picked_total.raw().saturating_sub(covered_bytes.raw()), - ); - let materialization = if self.prep.picked_total.is_exact() { - outside.plus(root_bytes) - } else { - Len::ZERO - }; - StateCosts { roots, roots_plus, materialization } - } - - fn reset(&mut self, m: &[(TermId, u8)]) { - for &(t, _) in m { - self.width_m[ix(t)] = 0; - } - } -} - -type StateKey = Vec<(TermId, u8)>; - -/// How a Share at table index `k` is priced. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum WidthModel { - /// The real TagN width of index `k`. - Ixon, - /// Every Share costs `w` bytes (the uniform-width cost model; the tests' - /// reference of the uniform optimizer). - #[cfg(test)] - Uniform(u8), -} - -impl WidthModel { - pub(crate) fn width(self, k: u64) -> u64 { - match self { - WidthModel::Ixon => share_width(k), - #[cfg(test)] - WidthModel::Uniform(w) => u64::from(w), - } - } -} - -/// Whether `prefix ++ [t]` is lexicographically below `other` (same length). -fn extension_less(prefix: &[TermId], t: TermId, other: &[TermId]) -> bool { - match prefix.cmp(&other[..prefix.len()]) { - std::cmp::Ordering::Less => true, - std::cmp::Ordering::Greater => false, - std::cmp::Ordering::Equal => t < other[prefix.len()], - } -} - -/// Insert `(t, w)` into a sorted state key. -fn extend_key(key: &[(TermId, u8)], t: TermId, w: u8) -> StateKey { - let pos = key.partition_point(|&(x, _)| x < t); - let mut out = Vec::with_capacity(key.len() + 1); - out.extend_from_slice(&key[..pos]); - out.push((t, w)); - out.extend_from_slice(&key[pos..]); - out -} - -/// A feasible upper bound: starting from the empty table, repeatedly append -/// the candidate whose addition gives the shortest encoding, while that is -/// strictly shorter than the current one. Candidates are tried in term-ID -/// order and ties keep the first, so the result is deterministic. The value -/// is the exact length of a real table sequence. -fn greedy_upper_bound( - eval: &mut StateEval<'_>, - prep: &Prepared, - meter: &mut Meter<'_>, - model: WidthModel, -) -> Result { - let mut key: StateKey = Vec::new(); - let mut f = Len::ZERO; - let mut k: u64 = 0; - let mut work = 0u64; - let costs = eval.eval(&key, &mut work); - let mut current = f.plus(Len::new(tag0_len(0))).plus(costs.roots); - let mut bodies: Vec<(TermId, Len)> = - prep.cands.iter().map(|&t| (t, eval.cost_m(t))).collect(); - eval.reset(&key); - meter.work(std::mem::take(&mut work))?; - loop { - let w = u8::try_from(model.width(k)) - .map_err(|_e| internal("share width exceeds u8"))?; - let count = Len::new(tag0_len(k.saturating_add(1))); - let mut step: Option<(Len, TermId, Len)> = None; - for &(t, c) in &bodies { - let nf = f.plus(c); - let next = extend_key(&key, t, w); - let total = nf.plus(count).plus(eval.eval(&next, &mut work).roots); - eval.reset(&next); - meter.work(std::mem::take(&mut work))?; - if total < current && step.is_none_or(|(best, _, _)| total < best) { - step = Some((total, t, nf)); - } - } - let Some((total, t, nf)) = step else { - return Ok(current); - }; - key = extend_key(&key, t, w); - f = nf; - k = k.saturating_add(1); - current = total; - eval.eval(&key, &mut work); - bodies = prep - .cands - .iter() - .filter(|&&x| eval.width_m[ix(x)] == 0) - .map(|&x| (x, eval.cost_m(x))) - .collect(); - eval.reset(&key); - meter.work(std::mem::take(&mut work))?; - } -} - -/// The width-state dynamic program. Returns the least `(variable length, -/// term-ID sequence)` over all table sequences, or a resource error. -pub(crate) fn width_state_search( - dag: &SharingDag, - prep: &Prepared, - mut ub: Len, - meter: &mut Meter<'_>, - model: WidthModel, -) -> Result<(Len, Vec), SharingError> { - let prune = meter.limits().lower_bound_pruning; - let materialization = meter.limits().materialization_bound; - let mut work = 0u64; - let mut eval = StateEval::new(prep, dag, &mut work); - meter.work(work)?; - if prune && meter.limits().greedy_upper_bound && !prep.cands.is_empty() { - let g = greedy_upper_bound(&mut eval, prep, meter, model)?; - meter.stats.greedy_len = g.exact(); - ub = ub.min(g); - } - let mut best: Option<(Len, Vec)> = None; - let mut layer: Vec<(StateKey, Len, Vec)> = - vec![(Vec::new(), Len::ZERO, Vec::new())]; - meter.state()?; - let mut k: u64 = 0; - while !layer.is_empty() { - meter.stats.layers += 1; - meter.layer(u64::try_from(layer.len()).unwrap_or(u64::MAX))?; - let count_k = Len::new(tag0_len(k)); - let count_next = Len::new(tag0_len(k.saturating_add(1))); - let width_k = model.width(k); - let width_next = - u8::try_from(width_k).map_err(|_e| internal("share width exceeds u8"))?; - let mut work = 0u64; - eval.set_plus(width_k, &mut work); - meter.work(work)?; - let mut next: FxHashMap)> = - FxHashMap::default(); - for (key, f, prefix) in layer { - meter.stats.states_expanded += 1; - let mut work = 0u64; - let costs = eval.eval(&key, &mut work); - meter.work(work)?; - let dict_lower = f.plus(count_k).plus(costs.roots_plus); - let mat_lower = if materialization { - f.plus(count_k).plus(costs.materialization) - } else { - Len::ZERO - }; - if prune && dict_lower.max(mat_lower) > ub { - meter.stats.states_pruned += 1; - eval.reset(&key); - continue; - } - let total = f.plus(count_k).plus(costs.roots); - let improves = match &best { - None => true, - Some((bt, bq)) => (total, &prefix) < (*bt, bq), - }; - if improves { - best = Some((total, prefix.clone())); - } - ub = ub.min(total); - let lower_next = count_next.plus(costs.roots_plus); - for &t in &prep.cands { - if eval.width_m[ix(t)] != 0 { - continue; - } - meter.transition()?; - let nf = f.plus(eval.cost_m(t)); - if !nf.is_exact() || (prune && nf.plus(lower_next) > ub) { - continue; - } - match next.entry(extend_key(&key, t, width_next)) { - std::collections::hash_map::Entry::Vacant(v) => { - meter.state()?; - let mut np = prefix.clone(); - np.push(t); - v.insert((nf, np)); - }, - std::collections::hash_map::Entry::Occupied(mut o) => { - let (of, op) = o.get(); - if nf < *of || (nf == *of && extension_less(&prefix, t, op)) { - let mut np = prefix.clone(); - np.push(t); - o.insert((nf, np)); - } - }, - } - } - eval.reset(&key); - } - let mut v: Vec<(StateKey, Len, Vec)> = - next.into_iter().map(|(key, (f, p))| (key, f, p)).collect(); - v.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - layer = v; - k = k.saturating_add(1); - } - best.ok_or_else(|| internal("every width state was pruned")) -} - -/// Materialize the canonical representation of table sequence `q`. -pub(crate) fn materialize_sequence( - dag: &SharingDag, - own: &[Len], - q: &[TermId], - meter: &mut Meter<'_>, -) -> Result<(Vec>, Vec>), SharingError> { - let nodes = dag.nodes(); - let mut dict = DenseIndex::new(dag.len()); - let mut table = Vec::with_capacity(q.len()); - let mut work = 0u64; - for (i, &t) in q.iter().enumerate() { - let costs = all_costs(nodes, own, &dict, &mut work); - let mut e = materialize(nodes, own, &dict, &costs, &[t], &mut work)?; - table.push(e.pop().ok_or_else(|| internal("entry"))?); - dict.set(t, u64::try_from(i).map_err(|_e| internal("index"))?); - meter.work(std::mem::take(&mut work))?; - } - let costs = all_costs(nodes, own, &dict, &mut work); - let roots = materialize(nodes, own, &dict, &costs, dag.roots(), &mut work)?; - meter.work(work)?; - Ok((roots, table)) -} - -/// Run the exact optimizer on `dag`. `fixed` is the byte length of the -/// non-sharing part of the enclosing Constant (0 for bare roots); it only -/// enters the output-size limit. -pub(crate) fn optimize( - dag: &SharingDag, - fixed: u64, - meter: &mut Meter<'_>, -) -> Result { - let prep = prepare(dag, meter)?; - let mut unshared = Len::new(tag0_len(0)); - for &r in dag.roots() { - unshared = unshared.plus(prep.base[ix(r)]); - } - meter.stats.unshared_len = unshared.exact(); - let (total, q) = - width_state_search(dag, &prep, unshared, meter, WidthModel::Ixon)?; - let variable = total - .exact() - .ok_or(SharingError::FormatBound(FormatBound::LengthOverflow))?; - let output = fixed - .checked_add(variable) - .filter(|&x| x != u64::MAX) - .ok_or(SharingError::FormatBound(FormatBound::LengthOverflow))?; - meter.output(output)?; - let (roots, sharing) = materialize_sequence(dag, &prep.own, &q, meter)?; - // Certification: the real encodings have the optimized length and expand, - // under the backward-reference rule, to exactly the input DAG. - let mut actual = sharing_table_len(&sharing) - .ok_or(SharingError::FormatBound(FormatBound::LengthOverflow))?; - for r in &roots { - actual = expr_len(r) - .and_then(|l| actual.checked_add(l)) - .ok_or(SharingError::FormatBound(FormatBound::LengthOverflow))?; - } - if actual != variable { - return Err(internal(format!( - "materialized length {actual} differs from optimized length {variable}" - ))); - } - let mut check_meter = Meter::new(meter.limits()); - let check = SharingDag::build(&roots, Some(&sharing), &mut check_meter)?; - if check != *dag { - return Err(internal("materialized encoding changes the expanded AST")); - } - Ok(ExactSharingResult { - roots, - sharing, - table_terms: q, - variable_len: variable, - stats: meter.stats.clone(), - }) -} - -/// Reference for the uniform-width model: the width-state search with every -/// Share priced `w`, over the R1/R2 candidates (optionally only those of -/// compact in-degree at least 2). Returns the minimum model length and the -/// least term-ID table sequence attaining it. Exponential; for tests. -#[cfg(test)] -pub(crate) fn uniform_reference_dag( - dag: &SharingDag, - w: u8, - min_in_degree2: bool, - meter: &mut Meter<'_>, -) -> Result<(u64, Vec), SharingError> { - if w == 0 { - return Err(SharingError::FormatBound(FormatBound::UniformWidth { w: 0 })); - } - let mut deg = vec![0u64; dag.len()]; - for &r in dag.roots() { - deg[ix(r)] += 1; - } - for node in dag.nodes() { - for &c in node.children().as_slice() { - deg[ix(c)] += 1; - } - } - let prep = prepare_with(dag, meter, |t| !min_in_degree2 || deg[ix(t)] >= 2)?; - let mut unshared = Len::new(tag0_len(0)); - for &r in dag.roots() { - unshared = unshared.plus(prep.base[ix(r)]); - } - let (total, q) = - width_state_search(dag, &prep, unshared, meter, WidthModel::Uniform(w))?; - let total = total - .exact() - .ok_or(SharingError::FormatBound(FormatBound::LengthOverflow))?; - Ok((total, q)) -} diff --git a/crates/ixon/src/sharing_exact/tests.rs b/crates/ixon/src/sharing_exact/tests.rs deleted file mode 100644 index dc46e5fd5..000000000 --- a/crates/ixon/src/sharing_exact/tests.rs +++ /dev/null @@ -1,3989 +0,0 @@ -//! Tests for exact minimum sharing: serializer-length helpers, structural -//! IDs, expansion validation, the fixed-dictionary recurrence against -//! enumeration, the width-state search against the tiny exhaustive oracle, -//! the §2 fixtures, representation independence and resource behavior. - -#![allow(clippy::cast_possible_truncation)] - -use std::collections::HashMap; - -use rustc_hash::FxHashMap; -use std::sync::Arc; - -use ix_common::address::Address; -use ix_common::env::{DefinitionSafety, QuotKind}; - -use super::cost::tag4_bytes_cmp; -use super::oracle::{ - bytes_of, oracle_full_product, oracle_ids, oracle_optimum, variant_count, - variants, -}; -use super::search::materialize_sequence; -use super::tiered::normalize_constant_sharing_tiered_at_width; -use super::*; -use crate::constant::{ - Axiom, Constant, ConstantInfo, Constructor, ConstructorProj, DefKind, - Definition, DefinitionProj, Inductive, InductiveProj, MutConst, Quotient, - Recursor, RecursorProj, RecursorRule, -}; -use crate::contract::{BinderContract, LetContract, ValueContract}; -use crate::expr::Expr; -use crate::serialize::put_expr; -use crate::tag::{TagN, u64_byte_count}; -use crate::univ::Univ; - -// =========================================================================== -// Helpers -// =========================================================================== - -type E = Arc; - -mod family; -mod wide; - -fn hex(b: &[u8]) -> String { - b.iter().map(|x| format!("{x:02x}")).collect() -} - -fn put(c: &Constant) -> Vec { - let mut b = Vec::new(); - c.put(&mut b); - b -} - -fn roundtrip(c: &Constant) -> Vec { - let b = put(c); - let mut s = b.as_slice(); - let d = Constant::get(&mut s).expect("decode"); - assert!(s.is_empty(), "trailing bytes"); - assert_eq!(&d, c); - b -} - -fn univs(n: usize) -> Vec> { - let mut out = vec![Univ::zero()]; - for _ in 1..n { - let last = out.last().unwrap().clone(); - out.push(Univ::succ(last)); - } - out -} - -fn axiom(root: E, univ_count: usize) -> Constant { - Constant { - info: ConstantInfo::Axio(Axiom { is_unsafe: false, lvls: 0, typ: root }), - sharing: vec![], - refs: vec![], - univs: univs(univ_count), - } -} - -/// `T_n = Prop -> ... -> Prop` with `n` binders. -fn chain(n: usize) -> E { - (0..n).fold(Expr::sort(0), |body, _| Expr::all(Expr::sort(0), body)) -} - -/// The default limits in the checked mode (`full_check`): every test of the -/// tiered construction builds and checks all its candidates. -fn limits() -> ExactSharingLimits { - ExactSharingLimits { full_check: true, ..ExactSharingLimits::default() } -} - -/// The constant with every root expanded and an empty table. -fn unshared(c: &Constant) -> Constant { - let dag = SharingDag::from_constant(c, &limits()).unwrap(); - let info = rebuild_constant_info(&c.info, &dag.root_exprs()).unwrap(); - Constant { - info, - sharing: vec![], - refs: c.refs.clone(), - univs: c.univs.clone(), - } -} - -/// Bytes of a hex string. -fn unhex(s: &str) -> Vec { - (0..s.len()) - .step_by(2) - .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) - .collect() -} - -/// The 19-byte encoding of the T2 -> T2 witness written by the former -/// production heuristic (`docs/sharing-minimum.md` §2): a valid, longer -/// incoming encoding. -fn former_heuristic_t2() -> Constant { - let bytes = unhex("d200009117b1b10291170000911700b0000100"); - let mut slice = bytes.as_slice(); - let c = Constant::get(&mut slice).unwrap(); - assert!(slice.is_empty()); - c -} - -fn normalize(c: &Constant) -> Constant { - normalize_constant_sharing(c, &limits()).unwrap() -} - -/// Rebuild an expression with no pointer sharing at all. -fn deep_clone(e: &E) -> E { - let kids: Vec = e.children().into_iter().map(deep_clone).collect(); - if kids.is_empty() { - Arc::new(e.as_ref().clone()) - } else { - Arc::new(e.with_children(&kids).unwrap()) - } -} - -struct Rng(u64); - -impl Rng { - fn next(&mut self) -> u64 { - self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15); - let mut z = self.0; - z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); - z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); - z ^ (z >> 31) - } - fn below(&mut self, n: u64) -> u64 { - self.next() % n - } - fn pct(&mut self, p: u64) -> bool { - self.below(100) < p - } - fn pick<'a, T>(&mut self, xs: &'a [T]) -> &'a T { - &xs[self.below(xs.len() as u64) as usize] - } -} - -fn small_index(rng: &mut Rng) -> u64 { - if rng.pct(85) { rng.below(3) } else { *rng.pick(&[7, 8, 200, 300]) } -} - -fn value_contract(rng: &mut Rng) -> ValueContract { - ValueContract::from_bits(rng.below(4) as u8).unwrap() -} - -fn binder_contract(rng: &mut Rng) -> BinderContract { - BinderContract::from_bits(rng.below(16) as u8).unwrap() -} - -fn let_contract(rng: &mut Rng) -> LetContract { - LetContract::from_flags(rng.below(4), binder_contract(rng)).unwrap() -} - -/// Random expressions over every constructor, reusing earlier subterms so -/// that sharing matters. Contracts are drawn from a small set per call so -/// equal-shaped binders repeat. -struct ExprGen { - rng: Rng, - pool: Vec, - reuse: u64, - contracts: Vec<(BinderContract, ValueContract)>, -} - -impl ExprGen { - fn new(seed: u64, reuse: u64) -> Self { - let mut rng = Rng(seed); - let contracts = - (0..2).map(|_| (binder_contract(&mut rng), value_contract(&mut rng))); - let contracts = contracts.collect(); - ExprGen { rng, pool: vec![], reuse, contracts } - } - - fn leaf(&mut self) -> E { - let r = &mut self.rng; - match r.below(7) { - 0 | 1 => Expr::sort(small_index(r)), - 2 => Expr::var(small_index(r)), - 3 => { - let us = (0..r.below(3)).map(|_| r.below(2)).collect(); - Expr::reference(small_index(r), us) - }, - 4 => { - let us = (0..r.below(2)).map(|_| r.below(2)).collect(); - Expr::rec(r.below(2), us) - }, - 5 => Expr::str(small_index(r)), - _ => Expr::nat(small_index(r)), - } - } - - fn expr(&mut self, depth: u32) -> E { - if !self.pool.is_empty() && self.rng.pct(self.reuse) { - return self.rng.pick(&self.pool).clone(); - } - let e = if depth == 0 || self.rng.pct(30) { - self.leaf() - } else { - let (bc, vc) = *self.rng.pick(&self.contracts.clone()); - match self.rng.below(9) { - 0 | 1 => { - let f = self.expr(depth - 1); - let a = self.expr(depth - 1); - Expr::app(f, a) - }, - 2 | 3 => { - let ty = self.expr(depth - 1); - let b = self.expr(depth - 1); - Expr::lam_contract(bc, ty, b) - }, - 4 | 5 => { - let ty = self.expr(depth - 1); - let b = self.expr(depth - 1); - Expr::all_contract(bc, vc, ty, b) - }, - 6 => { - let lc = let_contract(&mut self.rng); - let ty = self.expr(depth - 1); - let v = self.expr(depth - 1); - let b = self.expr(depth - 1); - Expr::let_contract(lc, ty, v, b) - }, - 7 => { - let v = self.expr(depth - 1); - Expr::prj(small_index(&mut self.rng), self.rng.below(9), v) - }, - _ => { - // A same-family spine continuation. - let f = self.expr(depth - 1); - let a = self.expr(depth - 1); - let inner = Expr::app(f, a); - let b = self.expr(depth - 1); - Expr::app(inner, b) - }, - } - }; - self.pool.push(e.clone()); - e - } -} - -/// Wrap ordered roots in a random ConstantInfo that has exactly that many -/// roots, with random refs/univs tables. -fn wrap(rng: &mut Rng, roots: Vec) -> Constant { - let n = roots.len(); - let mut it = roots.into_iter(); - let mut take = || it.next().unwrap(); - let lvls = *rng.pick(&[0u64, 1, 3, 200]); - let def = |take: &mut dyn FnMut() -> E, rng: &mut Rng| Definition { - kind: *rng.pick(&[DefKind::Definition, DefKind::Opaque, DefKind::Theorem]), - safety: *rng.pick(&[ - DefinitionSafety::Safe, - DefinitionSafety::Unsafe, - DefinitionSafety::Partial, - ]), - lvls, - typ: take(), - value: take(), - }; - let rec = |take: &mut dyn FnMut() -> E, rules: usize, rng: &mut Rng| { - let typ = take(); - Recursor { - k: rng.pct(50), - is_unsafe: rng.pct(50), - lvls, - params: rng.below(3), - indices: rng.below(300), - motives: 1, - minors: rng.below(3), - typ, - rules: (0..rules) - .map(|_| RecursorRule { fields: rng.below(3), rhs: take() }) - .collect(), - } - }; - let ind = |take: &mut dyn FnMut() -> E, ctors: usize, rng: &mut Rng| { - let typ = take(); - Inductive { - is_unsafe: rng.pct(50), - lvls, - params: rng.below(3), - indices: rng.below(3), - typ, - ctors: (0..ctors) - .map(|i| Constructor { - is_unsafe: false, - lvls, - cidx: i as u64, - params: 1, - fields: rng.below(200), - typ: take(), - }) - .collect(), - } - }; - let addr = |i: u64| Address::hash(&i.to_le_bytes()); - let info = match (n, rng.below(4)) { - (0, 0) => { - ConstantInfo::CPrj(ConstructorProj { idx: 1, cidx: 2, block: addr(1) }) - }, - (0, 1) => ConstantInfo::RPrj(RecursorProj { idx: 0, block: addr(2) }), - (0, 2) => ConstantInfo::IPrj(InductiveProj { idx: 300, block: addr(3) }), - (0, _) => ConstantInfo::DPrj(DefinitionProj { idx: 4, block: addr(4) }), - (1, 0) => { - ConstantInfo::Axio(Axiom { is_unsafe: rng.pct(50), lvls, typ: take() }) - }, - (1, 1) => { - ConstantInfo::Quot(Quotient { kind: QuotKind::Lift, lvls, typ: take() }) - }, - (2, 0 | 1) => ConstantInfo::Defn(def(&mut take, rng)), - (n, 2) => { - ConstantInfo::Muts(vec![MutConst::Indc(ind(&mut take, n - 1, rng))]) - }, - (n, 3) if n >= 3 => ConstantInfo::Muts(vec![ - MutConst::Defn(def(&mut take, rng)), - MutConst::Recr(rec(&mut take, n - 3, rng)), - ]), - (n, _) => ConstantInfo::Recr(rec(&mut take, n - 1, rng)), - }; - let refs = (0..rng.below(3)).map(addr).collect(); - let univs = (0..rng.below(3)).map(Univ::var).collect(); - Constant { info, sharing: vec![], refs, univs } -} - -/// A random constant whose expanded roots have between `min_n` and `max_n` -/// distinct subterms. -fn gen_constant(seed: u64, min_n: usize, max_n: usize, depth: u32) -> Constant { - let mut attempt = 0u64; - loop { - let mut g = - ExprGen::new(seed.wrapping_mul(1_000_003).wrapping_add(attempt), 45); - attempt += 1; - let nroots = 1 + g.rng.below(3) as usize; - let roots: Vec = (0..nroots).map(|_| g.expr(depth)).collect(); - let (ids, _) = oracle_ids(&roots); - if ids.len() < min_n || ids.len() > max_n { - continue; - } - let mut rng = Rng(seed ^ 0xABCD); - return wrap(&mut rng, roots); - } -} - -// =========================================================================== -// Serializer lengths -// =========================================================================== - -fn boundary_sizes() -> Vec { - let mut sizes = vec![0u64, 1, 7, 8, 9, 127, 128, 129, u64::MAX - 1, u64::MAX]; - for b in 1..8u32 { - let x = 1u64 << (8 * b); - sizes.extend([x - 1, x, x + 1]); - } - for f in [0u32, 2, 4] { - for e in [ - TagN::end1(f), - TagN::end2(f), - TagN::end3(f), - TagN::end4(f), - TagN::end5(f), - ] { - sizes.extend([e - 1, e, e + 1]); - } - } - sizes -} - -#[test] -fn tag_lengths_match_encoders() { - for s in boundary_sizes() { - assert_eq!(byte_count(s), u64::from(u64_byte_count(s)), "byte_count {s}"); - let mut b = Vec::new(); - TagN::put(4, Expr::FLAG_SHARE, s, &mut b); - assert_eq!(tag4_len(s), b.len() as u64, "tag4 {s}"); - assert_eq!(share_width(s), b.len() as u64, "share {s}"); - assert_eq!(expr_len(&Expr::Share(s)), Some(b.len() as u64)); - let mut b = Vec::new(); - TagN::put(0, 0, s, &mut b); - assert_eq!(tag0_len(s), b.len() as u64, "tag0 {s}"); - } - // Pinned header order: TagN bytes of one flag, unsigned lexicographic. - for a in boundary_sizes() { - for b in boundary_sizes() { - let (mut x, mut y) = (Vec::new(), Vec::new()); - TagN::put(4, Expr::FLAG_APP, a, &mut x); - TagN::put(4, Expr::FLAG_APP, b, &mut y); - assert_eq!(tag4_bytes_cmp(a, b), x.cmp(&y), "{a} vs {b}"); - } - } - // Share width boundaries named by the plan. - for (i, w) in [ - (7u64, 1u64), - (8, 2), - (1031, 2), - (1032, 3), - (66567, 3), - (66568, 4), - (16_843_783, 4), - (16_843_784, 5), - (4_311_811_079, 5), - (4_311_811_080, 9), - ] { - assert_eq!(share_width(i), w); - } - for (n, w) in [ - (127u64, 1u64), - (128, 2), - (16511, 2), - (16512, 3), - (82048, 4), - (16_859_264, 5), - (4_311_826_560, 9), - ] { - assert_eq!(tag0_len(n), w); - } -} - -fn telescope(kind: u8, n: usize, tail: E, rng: &mut Rng) -> E { - let mut e = tail; - for i in 0..n { - let side = if i % 3 == 0 { Expr::var(i as u64) } else { Expr::sort(1) }; - e = match kind { - 0 => Expr::app(e, side), - 1 => Expr::lam_contract(binder_contract(rng), side, e), - _ => { - Expr::all_contract(binder_contract(rng), value_contract(rng), side, e) - }, - }; - } - e -} - -#[test] -fn expr_len_matches_put_expr() { - let check = |e: &Expr| { - let mut b = Vec::new(); - put_expr(e, &mut b); - assert_eq!(expr_len(e), Some(b.len() as u64), "{e:?}"); - }; - let mut rng = Rng(7); - for kind in 0..3u8 { - for n in [1usize, 2, 7, 8, 9, 255, 256, 257] { - for tail in [ - Expr::var(0), - Expr::share(300), - telescope((kind + 1) % 3, 2, Expr::sort(0), &mut rng), - ] { - check(&telescope(kind, n, tail, &mut rng)); - } - } - } - // Share leaves at every width boundary inside telescopes and heads. - for s in boundary_sizes() { - check(&Expr::app(Expr::app(Expr::share(s), Expr::share(s)), Expr::var(0))); - check(&Expr::lam(Expr::share(s), Expr::share(s))); - check(&Expr::prj(s, s, Expr::share(s))); - check(&Expr::reference(s, vec![s, 0, s])); - } - // Pointer-shared DAGs. - let mut e = Expr::var(0); - for _ in 0..40 { - e = Expr::app(e.clone(), e); - } - let mut b = Vec::new(); - put_expr(&Expr::app(Expr::var(1), Expr::var(2)), &mut b); - assert!(expr_len(&e).is_some()); - for seed in 0..300 { - let mut g = ExprGen::new(seed, 30); - check(&g.expr(6)); - } - // Seeded, like every other random test here. - for seed in 0..300 { - let mut qc = quickcheck::Gen::from_size_and_seed(20, seed); - check(&crate::expr::tests::arbitrary_expr(&mut qc)); - } - // The 2^40-leaf doubling DAG above is measured without expansion. - let mut d = Expr::var(0); - for _ in 0..70 { - d = Expr::app(d.clone(), d); - } - assert_eq!(expr_len(&d), None, "length above u64 must be reported"); -} - -fn random_table(rng: &mut Rng, n: usize) -> Vec { - (0..n) - .map(|i| { - if i > 0 && rng.pct(50) { - Expr::app(Expr::share(rng.below(i as u64)), Expr::var(rng.below(9))) - } else { - Expr::var(rng.below(300)) - } - }) - .collect() -} - -#[test] -fn constant_len_matches_serializer() { - let mut checked = 0; - for seed in 0..400u64 { - let mut rng = Rng(seed); - let mut g = ExprGen::new(seed, 30); - let n = rng.below(5) as usize; - let roots: Vec = (0..n).map(|_| g.expr(4)).collect(); - let mut c = wrap(&mut rng, roots); - let table_len = *rng.pick(&[0usize, 1, 5, 127, 128, 255, 256]); - c.sharing = random_table(&mut rng, table_len); - c.refs = (0..*rng.pick(&[0u64, 1, 127, 128])) - .map(|i| Address::hash(&i.to_le_bytes())) - .collect(); - let b = put(&c); - assert_eq!(constant_len(&c), Some(b.len() as u64), "seed {seed}"); - let mut parts = constant_fixed_len(&c).unwrap(); - for r in constant_info_root_exprs(&c.info) { - parts += expr_len(&r).unwrap(); - } - parts += sharing_table_len(&c.sharing).unwrap(); - assert_eq!(parts, b.len() as u64); - checked += 1; - } - assert_eq!(checked, 400); -} - -// =========================================================================== -// Roots -// =========================================================================== - -#[test] -fn root_order_mirrors_constant_info_root_exprs() { - let v = |i: u64| Expr::var(i); - let def = |a, b| Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: v(a), - value: v(b), - }; - let rec = |t, rs: &[u64]| Recursor { - k: false, - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - motives: 0, - minors: 0, - typ: v(t), - rules: rs.iter().map(|&r| RecursorRule { fields: 0, rhs: v(r) }).collect(), - }; - let ctor = |t| Constructor { - is_unsafe: false, - lvls: 0, - cidx: 0, - params: 0, - fields: 0, - typ: v(t), - }; - let cases: Vec<(ConstantInfo, Vec)> = vec![ - (ConstantInfo::Defn(def(0, 1)), vec![0, 1]), - (ConstantInfo::Recr(rec(0, &[1, 2, 3])), vec![0, 1, 2, 3]), - ( - ConstantInfo::Axio(Axiom { is_unsafe: false, lvls: 0, typ: v(0) }), - vec![0], - ), - ( - ConstantInfo::Quot(Quotient { kind: QuotKind::Type, lvls: 0, typ: v(0) }), - vec![0], - ), - ( - ConstantInfo::DPrj(DefinitionProj { idx: 0, block: Address::hash(b"") }), - vec![], - ), - ( - ConstantInfo::Muts(vec![ - MutConst::Defn(def(0, 1)), - MutConst::Indc(Inductive { - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - typ: v(2), - ctors: vec![ctor(3), ctor(4)], - }), - MutConst::Recr(rec(5, &[6])), - ]), - vec![0, 1, 2, 3, 4, 5, 6], - ), - ]; - for (info, want) in cases { - let roots = constant_info_root_exprs(&info); - let got: Vec = roots - .iter() - .map(|e| match e.as_ref() { - Expr::Var(i) => *i, - _ => unreachable!(), - }) - .collect(); - assert_eq!(got, want); - assert_eq!(constant_info_root_count(&info), want.len()); - assert_eq!(rebuild_constant_info(&info, &roots).unwrap(), info); - let fresh: Vec = (100..100 + want.len() as u64).map(v).collect(); - let rebuilt = rebuild_constant_info(&info, &fresh).unwrap(); - assert_eq!(constant_info_root_exprs(&rebuilt), fresh); - let mut short = fresh.clone(); - short.pop(); - let mut long = fresh.clone(); - long.push(v(9)); - for bad in [long, short].into_iter().filter(|b| b.len() != want.len()) { - assert!(matches!( - rebuild_constant_info(&info, &bad), - Err(SharingError::Malformed( - MalformedSharing::RootCountMismatch { .. } - )) - )); - } - } -} - -// =========================================================================== -// Structural IDs and expansion -// =========================================================================== - -#[test] -fn structural_ids_follow_height_then_key() { - let p = Expr::sort(0); - let t = Expr::sort(1); - let roots = vec![ - Expr::all(p.clone(), t.clone()), - Expr::app(Expr::var(0), p.clone()), - Expr::reference(1, vec![]), - Expr::reference(1, vec![0]), - Expr::reference(0, vec![5]), - Expr::nat(0), - Expr::rec(0, vec![]), - Expr::str(0), - ]; - let dag = SharingDag::from_expanded_roots(&roots, &limits()).unwrap(); - let terms = dag.term_exprs(); - let order: Vec = vec![ - Expr::sort(0), - Expr::sort(1), - Expr::var(0), - Expr::reference(0, vec![5]), - Expr::reference(1, vec![]), - Expr::reference(1, vec![0]), - Expr::rec(0, vec![]), - Expr::str(0), - Expr::nat(0), - Expr::app(Expr::var(0), p.clone()), - Expr::all(p, t), - ]; - assert_eq!(terms, order); - assert_eq!(dag.roots(), &[10, 9, 4, 5, 3, 8, 6, 7]); - for (i, node) in dag.nodes().iter().enumerate() { - for &c in node.children().as_slice() { - assert!(c < i); - } - if i > 0 { - let (h0, h1) = (dag.heights()[i - 1], dag.heights()[i]); - assert!(h0 < h1 || (h0 == h1 && dag.key(i - 1) < dag.key(i))); - } - } -} - -#[test] -fn structural_ids_match_independent_oracle_ids() { - for seed in 0..200 { - let c = gen_constant(seed, 1, 60, 5); - let roots = constant_info_root_exprs(&c.info); - let dag = SharingDag::from_expanded_roots(&roots, &limits()).unwrap(); - let (ids, terms) = oracle_ids(&roots); - assert_eq!(dag.term_exprs(), terms, "seed {seed}"); - for (r, &id) in roots.iter().zip(dag.roots()) { - assert_eq!(ids[r.as_ref()], id); - } - } -} - -#[test] -fn expansion_rejects_bad_tables() { - let v0 = Expr::var(0); - let err = |roots: Vec, table: Vec| { - SharingDag::from_shared(&roots, &table, &limits()).unwrap_err() - }; - let m = |x: MalformedSharing| SharingError::Malformed(x); - assert_eq!( - err(vec![Expr::share(1)], vec![v0.clone()]), - m(MalformedSharing::ShareOutOfRange { - location: ShareLocation::Root(0), - index: 1, - table_len: 1 - }) - ); - assert_eq!( - err( - vec![v0.clone()], - vec![Expr::app(Expr::share(5), v0.clone()), v0.clone()] - ), - m(MalformedSharing::ShareOutOfRange { - location: ShareLocation::Entry(0), - index: 5, - table_len: 2 - }) - ); - assert_eq!( - err( - vec![v0.clone()], - vec![Expr::app(Expr::share(1), v0.clone()), Expr::var(1)] - ), - m(MalformedSharing::ForwardShare { entry: 0, index: 1 }) - ); - assert_eq!( - err(vec![v0.clone()], vec![Expr::app(Expr::share(0), v0.clone())]), - m(MalformedSharing::CyclicShare { entry: 0, index: 0 }) - ); - assert_eq!( - err( - vec![v0.clone()], - vec![ - Expr::app(Expr::share(2), v0.clone()), - Expr::var(3), - Expr::lam(Expr::share(0), v0.clone()) - ] - ), - m(MalformedSharing::CyclicShare { entry: 0, index: 2 }) - ); - assert_eq!( - optimize_sharing( - &[v0.clone(), Expr::app(v0.clone(), Expr::share(0))], - &limits() - ) - .unwrap_err(), - m(MalformedSharing::UnresolvedShare { root: 1, index: 0 }) - ); - // The same errors surface through the Constant API, including for - // unreachable entries. - let mut c = axiom(Expr::sort(0), 1); - c.sharing = vec![Expr::var(1), Expr::app(Expr::share(1), v0)]; - assert_eq!( - normalize_constant_sharing(&c, &limits()).unwrap_err(), - m(MalformedSharing::CyclicShare { entry: 1, index: 1 }) - ); - assert!(matches!( - check_exact_minimum(&c, &limits()), - Err(SharingError::Malformed(_)) - )); -} - -#[test] -fn expansion_ignores_unreachable_entries_and_incoming_order() { - // T2 -> T2 in several valid backward encodings, including a table whose - // first entry inlines a subterm that a later entry stores, and garbage - // entries that nothing references. - let p = || Expr::sort(0); - let t1 = || Expr::all(p(), p()); - let t2 = Expr::all(p(), t1()); - let base = axiom(Expr::all(t2.clone(), t2.clone()), 1); - let mut a = base.clone(); - a.sharing = vec![t2.clone(), t1(), Expr::var(77)]; - a.info = ConstantInfo::Axio(Axiom { - is_unsafe: false, - lvls: 0, - typ: Expr::all(Expr::share(0), Expr::share(0)), - }); - let mut b = base.clone(); - b.sharing = vec![ - p(), - Expr::all(Expr::share(0), Expr::share(0)), - Expr::all(Expr::share(0), Expr::share(1)), - ]; - b.info = ConstantInfo::Axio(Axiom { - is_unsafe: false, - lvls: 0, - typ: Expr::all(Expr::share(2), Expr::all(Expr::share(0), Expr::share(1))), - }); - let dag = SharingDag::from_constant(&base, &limits()).unwrap(); - for c in [&a, &b, &former_heuristic_t2()] { - roundtrip(c); - assert_eq!(SharingDag::from_constant(c, &limits()).unwrap(), dag); - assert_eq!(put(&normalize(c)), put(&normalize(&base))); - } -} - -// =========================================================================== -// Fixed-dictionary recurrence (§5) against enumeration -// =========================================================================== - -/// Minimum length and least bytes among all variants. -fn brute_best(e: &E, avail: &[(E, u64)]) -> (u64, Vec) { - variants(e, avail) - .iter() - .map(|v| bytes_of(v)) - .map(|b| (b.len() as u64, b)) - .min() - .unwrap() -} - -#[test] -fn fixed_dictionary_matches_enumeration() { - let interesting = - [0u64, 1, 5, 7, 8, 9, 200, 255, 256, 65535, 65536, 1 << 32, 1 << 40]; - let mut cases = 0u64; - let mut skipped = 0u64; - let mut with_self_share = 0u64; - for seed in 0..500u64 { - let c = gen_constant(seed, 2, 9, 4); - let roots = constant_info_root_exprs(&c.info); - let dag = SharingDag::from_expanded_roots(&roots, &limits()).unwrap(); - let terms = dag.term_exprs(); - let mut rng = Rng(seed.wrapping_mul(31)); - for _ in 0..4 { - // An arbitrary dictionary: random subset, random order, and either - // consecutive or boundary-straddling indices. - let mut chosen: Vec = - (0..dag.len()).filter(|_| rng.pct(45)).collect(); - for i in (1..chosen.len()).rev() { - chosen.swap(i, rng.below(i as u64 + 1) as usize); - } - let sparse = rng.pct(50); - let mut pool: Vec = interesting.to_vec(); - let mut dict = FixedDictionary::new(); - let mut avail: Vec<(E, u64)> = Vec::new(); - for (pos, &t) in chosen.iter().enumerate() { - let idx = if sparse { - let k = rng.below(pool.len() as u64) as usize; - pool.swap_remove(k) - } else { - pos as u64 - }; - dict.insert(t, idx); - avail.push((terms[t].clone(), idx)); - } - for (t, term) in terms.iter().enumerate() { - if variant_count(term, &avail) > 20_000 { - skipped += 1; - continue; - } - let (len, bytes) = brute_best(term, &avail); - assert_eq!( - dictionary_cost(&dag, &dict, t), - Len::new(len), - "seed {seed} term {t}" - ); - let got = materialize_with_dictionary(&dag, &dict, &[t]).unwrap(); - assert_eq!(bytes_of(&got[0]), bytes, "seed {seed} term {t}"); - if chosen.contains(&t) { - with_self_share += 1; - } - cases += 1; - } - } - } - eprintln!( - "fixed-dictionary cases compared with enumeration: {cases} \ - ({with_self_share} with the term itself available, {skipped} skipped \ - above 20000 variants)" - ); - assert!(cases > 5000 && skipped * 20 < cases); -} - -// =========================================================================== -// §2 fixtures -// =========================================================================== - -#[test] -fn fixture_t2_witness_is_17_bytes() { - let t2 = chain(2); - let c = axiom(Expr::all(t2.clone(), t2), 1); - let unshared_bytes = roundtrip(&c); - assert_eq!(hex(&unshared_bytes), "d200009317921700170000170017000000000100"); - let h = roundtrip(&former_heuristic_t2()); - assert_eq!(hex(&h), "d200009117b1b10291170000911700b0000100"); - assert_eq!( - SharingDag::from_constant(&former_heuristic_t2(), &limits()).unwrap(), - SharingDag::from_constant(&c, &limits()).unwrap() - ); - let (exact, res) = - normalize_constant_sharing_with_stats(&c, &limits()).unwrap(); - let bytes = roundtrip(&exact); - eprintln!( - "T2->T2: former heuristic {} unshared {} exact {} {} Q={:?} stats={:?}", - h.len(), - unshared_bytes.len(), - bytes.len(), - hex(&bytes), - res.table_terms, - res.stats - ); - assert_eq!(hex(&bytes), "d200009117b0b001921700170000000100"); - let o = oracle_optimum(&c); - assert_eq!(o.bytes, bytes); - assert_eq!(o.q, res.table_terms); - assert_eq!(o.minimal_sequences, vec![res.table_terms.clone()]); - assert_eq!(put(&o.constant), o.bytes); - eprintln!("T2->T2 oracle: {} sequences", o.sequences); - assert_eq!(o.sequences, 65); -} - -#[test] -fn fixture_t16_improves_on_store_only_t16() { - let t16 = chain(16); - let c = axiom(Expr::all(t16.clone(), t16.clone()), 1); - let unshared_len = roundtrip(&c).len(); - let mut store_only = c.clone(); - store_only.sharing = vec![t16]; - store_only.info = ConstantInfo::Axio(Axiom { - is_unsafe: false, - lvls: 0, - typ: Expr::all(Expr::share(0), Expr::share(0)), - }); - let store_only_len = roundtrip(&store_only).len(); - assert_eq!((unshared_len, store_only_len), (78, 46)); - let (exact, res) = - normalize_constant_sharing_with_stats(&c, &limits()).unwrap(); - let bytes = roundtrip(&exact); - eprintln!( - "T16->T16: unshared {unshared_len} store-only {store_only_len} exact {} {} Q={:?} stats={:?}", - bytes.len(), - hex(&bytes), - res.table_terms, - res.stats - ); - assert!(bytes.len() <= 46); - // Certification is independent of the greedy seed and of limits. - let mut no_g = limits(); - no_g.greedy_upper_bound = false; - assert_eq!(put(&normalize_constant_sharing(&c, &no_g).unwrap()), bytes); - let exact_q_len = sequence_len( - &SharingDag::from_constant(&c, &limits()).unwrap(), - &res.table_terms, - ) - .unwrap(); - assert_eq!( - exact_q_len.exact().unwrap() + constant_fixed_len(&c).unwrap(), - bytes.len() as u64 - ); -} - -/// Nine independent 5-byte Ref atoms, in the order of the blake3 hashes of -/// their encodings (as Lean `nineRef`); the last one is used 100 times, the -/// others twice (the probe fixture of `docs/sharing-minimum.md` §2). -fn nine_ref_fixture() -> (Constant, E) { - let mut atoms: Vec = - (0..9).map(|i| Expr::reference(i, vec![0, 0, 0])).collect(); - atoms.sort_by_key(|e| { - let mut buf = Vec::new(); - put_expr(e, &mut buf); - *blake3::hash(&buf).as_bytes() - }); - let hot = atoms.last().unwrap().clone(); - let mut roots: Vec = - atoms.iter().flat_map(|a| [a.clone(), a.clone()]).collect(); - for _ in 0..98 { - roots.push(hot.clone()); - } - let c = Constant { - info: ConstantInfo::Recr(Recursor { - k: false, - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - motives: 0, - minors: 0, - typ: roots[0].clone(), - rules: roots[1..] - .iter() - .map(|e| RecursorRule { fields: 0, rhs: e.clone() }) - .collect(), - }), - sharing: vec![], - refs: (0u64..9).map(|i| Address::hash(&i.to_le_bytes())).collect(), - univs: vec![Univ::zero()], - }; - (c, hot) -} - -#[test] -fn fixture_nine_refs_minimum_is_578() { - // The former heuristic wrote 676 bytes in its order and 578 reordered by - // frequency (docs/sharing-minimum.md §2). - let (c, hot) = nine_ref_fixture(); - assert_eq!(hot.as_ref(), &Expr::Ref(2, vec![0, 0, 0])); - let (exact, res) = - normalize_constant_sharing_with_stats(&c, &limits()).unwrap(); - let bytes = roundtrip(&exact); - eprintln!( - "nine refs: exact {} Q={:?} stats={:?}", - bytes.len(), - res.table_terms, - res.stats - ); - assert_eq!(bytes.len(), 578); - // Ref(i,[0,0,0]) has term ID i; the least tied sequence keeps ID order, - // which already gives the hot atom (ID 2) a one-byte index. - assert_eq!(res.table_terms, (0..9).collect::>()); - assert_eq!(exact.sharing[2].as_ref(), hot.as_ref()); -} - -#[test] -fn fixture_two_minima_pins_the_tie() { - let p = || Expr::sort(0); - let a = Expr::all(p(), p()); - let b = Expr::all(p(), Expr::sort(1)); - let root = - Expr::all(a.clone(), Expr::all(a.clone(), Expr::all(b.clone(), b.clone()))); - let c = axiom(root, 2); - // Both table orders, written out by hand. - let order = |first: &E, second: &E, sa: u64, sb: u64| { - let mut x = c.clone(); - x.sharing = vec![first.clone(), second.clone()]; - x.info = ConstantInfo::Axio(Axiom { - is_unsafe: false, - lvls: 0, - typ: Expr::all( - Expr::share(sa), - Expr::all(Expr::share(sa), Expr::all(Expr::share(sb), Expr::share(sb))), - ), - }); - roundtrip(&x) - }; - let ab = order(&a, &b, 0, 1); - let ba = order(&b, &a, 1, 0); - assert_eq!(hex(&ab), "d200009317b017b017b1b10291170000911700010002000100"); - assert_eq!(hex(&ba), "d200009317b117b117b0b00291170001911700000002000100"); - let (exact, res) = - normalize_constant_sharing_with_stats(&c, &limits()).unwrap(); - let bytes = roundtrip(&exact); - let dag = SharingDag::from_constant(&c, &limits()).unwrap(); - let terms = dag.term_exprs(); - let id = |e: &E| terms.iter().position(|t| t == e).unwrap(); - eprintln!( - "two minima: exact {} {} Q={:?} (A={}, B={})", - bytes.len(), - hex(&bytes), - res.table_terms, - id(&a), - id(&b) - ); - assert_eq!(bytes, ab, "the pinned tie stores A (lower term ID) first"); - assert_eq!(res.table_terms, vec![id(&a), id(&b)]); - let o = oracle_optimum(&c); - assert_eq!(o.bytes, bytes); - assert_eq!(o.q, res.table_terms); - assert_eq!( - o.minimal_sequences, - vec![vec![id(&a), id(&b)], vec![id(&b), id(&a)]] - ); - eprintln!( - "two minima oracle: {} sequences, minimal {:?}", - o.sequences, o.minimal_sequences - ); -} - -#[test] -fn prop_chains_match_probe_and_oracle() { - // Production-probe measurements: (n, unshared, store-only-Tn). - let probe = [ - (1usize, 16usize, 15usize), - (2, 20, 17), - (3, 24, 19), - (4, 28, 21), - (7, 41, 27), - (8, 46, 30), - (9, 50, 32), - (16, 78, 46), - (32, 142, 78), - ]; - for (n, u, s) in probe { - let t = chain(n); - let c = axiom(Expr::all(t.clone(), t), 1); - assert_eq!(roundtrip(&c).len(), u, "unshared n={n}"); - let bytes = roundtrip(&normalize(&c)); - eprintln!("chain {n}: exact {} ({})", bytes.len(), hex(&bytes)); - assert!(bytes.len() <= s, "n={n}"); - if n <= 3 { - let o = oracle_optimum(&c); - assert_eq!(o.bytes, bytes, "oracle n={n}"); - } - } -} - -// =========================================================================== -// Width-state search against the exhaustive oracle -// =========================================================================== - -#[test] -fn optimizer_matches_exhaustive_oracle() { - let mut kinds: HashMap = HashMap::new(); - let mut shared = 0u64; - let mut parent_first = 0u64; - let cases = 160u64; - for seed in 0..cases { - let c = gen_constant(seed, 2, 6, 4); - let (exact, res) = - normalize_constant_sharing_with_stats(&c, &limits()).unwrap(); - let bytes = roundtrip(&exact); - let o = oracle_optimum(&c); - assert_eq!( - (o.len, &o.q, &o.bytes), - (bytes.len() as u64, &res.table_terms, &bytes), - "seed {seed}: {c:?}" - ); - // Record coverage. - let kind = match &c.info { - ConstantInfo::Muts(_) => "muts".to_string(), - other => format!("{}", other.variant().unwrap()), - }; - *kinds.entry(kind).or_default() += 1; - if !res.table_terms.is_empty() { - shared += 1; - } - let dag = SharingDag::from_constant(&c, &limits()).unwrap(); - let q = &res.table_terms; - let terms = dag.term_exprs(); - for i in 0..q.len() { - for j in i + 1..q.len() { - if contains(&terms[q[i]], &terms[q[j]]) { - parent_first += 1; - } - } - } - } - eprintln!( - "oracle agreement: {cases} constants, {shared} with a nonempty optimum table, \ - {parent_first} parent-before-descendant pairs, kinds {kinds:?}" - ); -} - -fn contains(hay: &E, needle: &E) -> bool { - hay == needle || hay.children().into_iter().any(|c| contains(c, needle)) -} - -#[test] -fn oracle_separability_matches_full_product() { - let t1 = chain(1); - let mut cases = vec![axiom(Expr::all(t1.clone(), t1), 1)]; - for seed in 0..40 { - cases.push(gen_constant(seed + 5000, 2, 4, 3)); - } - let mut complete = 0u64; - let mut compared = 0u64; - for c in &cases { - let Some((len, q, bytes, n)) = oracle_full_product(c, 20_000) else { - continue; - }; - let o = oracle_optimum(c); - assert_eq!((len, &q, &bytes), (o.len, &o.q, &o.bytes)); - let exact = roundtrip(&normalize(c)); - assert_eq!(exact, bytes); - complete += n; - compared += 1; - } - eprintln!( - "full-product oracle: {compared} constants, {complete} complete encodings" - ); - assert!(compared >= 20); -} - -// =========================================================================== -// Invariance, idempotence and comparison with other encodings -// =========================================================================== - -/// Rebuild with random partial pointer sharing. -fn remix(e: &E, rng: &mut Rng, memo: &mut HashMap) -> E { - if rng.pct(50) - && let Some(x) = memo.get(e.as_ref()) - { - return x.clone(); - } - let kids: Vec = - e.children().into_iter().map(|c| remix(c, rng, memo)).collect(); - let out = if kids.is_empty() { - Arc::new(e.as_ref().clone()) - } else { - Arc::new(e.with_children(&kids).unwrap()) - }; - memo.insert(e.as_ref().clone(), out.clone()); - out -} - -#[test] -fn representation_independence() { - // Results (or errors) that do not depend on the input's pointer layout. - fn outcome( - r: Result, - ) -> Result<(Vec, u64, Vec), SharingError> { - r.map(|x| { - let mut b = Vec::new(); - for e in x.roots.iter().chain(&x.sharing) { - put_expr(e, &mut b); - } - (x.table_terms, x.variable_len, b) - }) - } - let mut solved = 0; - for seed in 0..120 { - let c = gen_constant(seed + 900, 3, 30, 5); - let roots = constant_info_root_exprs(&c.info); - let reference = outcome(optimize_sharing(&roots, &limits())); - let dag = SharingDag::from_expanded_roots(&roots, &limits()).unwrap(); - let mut rng = Rng(seed); - let layouts: Vec> = - vec![roots.iter().map(deep_clone).collect(), dag.root_exprs(), { - let mut memo = HashMap::new(); - roots.iter().map(|r| remix(r, &mut rng, &mut memo)).collect() - }]; - for layout in layouts { - assert_eq!( - SharingDag::from_expanded_roots(&layout, &limits()).unwrap(), - dag - ); - assert_eq!( - outcome(optimize_sharing(&layout, &limits())), - reference, - "seed {seed}" - ); - } - if reference.is_ok() { - solved += 1; - } - } - eprintln!( - "representation independence: {solved}/120 solved, all layouts identical" - ); - assert!(solved >= 110); -} - -#[test] -fn exact_is_idempotent_and_never_worse() { - let mut stats = (0u64, 0u64, 0u64, 0u64); - let mut most_states = 0u64; - let mut l = limits(); - l.max_states = 200_000; - l.max_layer_states = 100_000; - for seed in 0..150 { - let c = gen_constant(seed + 20_000, 4, 45, 6); - let h = mss::mss_constant(&c, &limits()).unwrap(); - let u = unshared(&c); - let exact = match normalize_constant_sharing_with_stats(&c, &l) { - Ok((x, r)) => { - most_states = most_states.max(r.stats.states_created); - x - }, - Err(SharingError::ResourceExhausted(r)) => { - stats.3 += 1; - eprintln!("seed {seed}: resource limit {r:?}"); - continue; - }, - Err(e) => panic!("seed {seed}: {e}"), - }; - let eb = roundtrip(&exact); - let hb = roundtrip(&h); - let ub = roundtrip(&u); - assert!(eb.len() <= ub.len() && eb.len() <= hb.len(), "seed {seed}"); - stats.0 += 1; - stats.1 += (hb.len() - eb.len()) as u64; - stats.2 += (ub.len() - eb.len()) as u64; - // Expand-then-normalize is a fixpoint, from every incoming encoding. - for x in [&exact, &h, &u] { - assert_eq!(put(&normalize(x)), eb, "seed {seed}"); - } - assert_eq!( - check_exact_minimum(&exact, &limits()).unwrap(), - ExactMinimumCheck::Minimum - ); - if hb != eb { - assert!(matches!( - check_exact_minimum(&h, &limits()).unwrap(), - ExactMinimumCheck::NotMinimum { .. } - )); - } - } - eprintln!( - "never-worse: {} solved, saved {} bytes vs MSS and {} vs unshared, \ - {} resource errors, most states in a solved case {most_states}", - stats.0, stats.1, stats.2, stats.3 - ); - assert!(stats.0 >= 140); -} - -// =========================================================================== -// Resources, bounds and forced states -// =========================================================================== - -#[test] -fn resource_limits_fail_closed() { - let t16 = chain(16); - let c = axiom(Expr::all(t16.clone(), t16), 1); - let want = put(&normalize(&c)); - let cases: Vec<(Resource, Box)> = vec![ - (Resource::InputNodes, Box::new(|l| l.max_input_nodes = 3)), - (Resource::DistinctNodes, Box::new(|l| l.max_distinct_nodes = 5)), - (Resource::Height, Box::new(|l| l.max_height = 4)), - (Resource::Candidates, Box::new(|l| l.max_candidates = 3)), - (Resource::States, Box::new(|l| l.max_states = 5)), - (Resource::LayerStates, Box::new(|l| l.max_layer_states = 2)), - (Resource::Transitions, Box::new(|l| l.max_transitions = 4)), - (Resource::Work, Box::new(|l| l.max_work = 50)), - (Resource::OutputBytes, Box::new(|l| l.max_output_bytes = 45)), - ]; - for (resource, set) in cases { - let mut l = limits(); - l.greedy_upper_bound = false; - set(&mut l); - match normalize_constant_sharing(&c, &l) { - Err(SharingError::ResourceExhausted(r)) => { - assert_eq!(r.resource, resource) - }, - other => panic!("{resource:?}: expected exhaustion, got {other:?}"), - } - } - // Every successful invocation returns the same bytes, whatever the limits. - let mut tight = limits(); - let res = normalize_constant_sharing_with_stats(&c, &tight).unwrap().1; - tight.max_states = res.stats.states_created; - tight.max_transitions = res.stats.transitions; - tight.max_work = res.stats.work; - tight.max_output_bytes = want.len() as u64; - assert_eq!(put(&normalize_constant_sharing(&c, &tight).unwrap()), want); - let mut unbounded = ExactSharingLimits::unbounded(); - unbounded.greedy_upper_bound = false; - assert_eq!(put(&normalize_constant_sharing(&c, &unbounded).unwrap()), want); - // Counters are deterministic. - let again = normalize_constant_sharing_with_stats(&c, &limits()).unwrap().1; - assert_eq!(again.stats, res.stats); -} - -#[test] -fn overflowing_lengths_are_exact_or_errors() { - // x_{i+1} = App(x_i, x_i): the unshared length exceeds u64. - let mut x = Expr::var(0); - for _ in 0..70 { - x = Expr::app(x.clone(), x); - } - let mut l = limits(); - l.max_states = 20_000; - l.max_transitions = 2_000_000; - match optimize_sharing(std::slice::from_ref(&x), &l) { - Ok(r) => { - eprintln!( - "doubling-70 solved: {} bytes, Q len {}", - r.variable_len, - r.table_terms.len() - ); - assert_eq!(r.stats.unshared_len, None); - }, - Err(SharingError::ResourceExhausted(r)) => { - eprintln!("doubling-70: resource limit {r:?}"); - }, - Err(e) => panic!("unexpected {e}"), - } - // A small doubling DAG is solved exactly and beats the unshared form. - let mut y = Expr::var(0); - for _ in 0..6 { - y = Expr::app(y.clone(), y); - } - let r = optimize_sharing(std::slice::from_ref(&y), &limits()).unwrap(); - let u = r.stats.unshared_len.unwrap(); - eprintln!( - "doubling-6: exact {} unshared {u} Q={:?}", - r.variable_len, r.table_terms - ); - assert!(r.variable_len < u); -} - -#[test] -fn forced_states_cross_tag0_and_share_width_boundaries() { - // 300 independent atoms, each used twice, as one recursor. - let atoms: Vec = (0..300).map(|i| Expr::reference(i, vec![])).collect(); - let roots: Vec = - atoms.iter().flat_map(|a| [a.clone(), a.clone()]).collect(); - let c = Constant { - info: ConstantInfo::Recr(Recursor { - k: false, - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - motives: 0, - minors: 0, - typ: roots[0].clone(), - rules: roots[1..] - .iter() - .map(|e| RecursorRule { fields: 0, rhs: e.clone() }) - .collect(), - }), - sharing: vec![], - refs: vec![], - univs: vec![], - }; - let dag = SharingDag::from_constant(&c, &limits()).unwrap(); - let fixed = constant_fixed_len(&c).unwrap(); - let lim = limits(); - let mut meter = Meter::new(&lim); - for k in [0usize, 7, 8, 9, 127, 128, 129, 255, 256, 257, 300] { - // Store the atoms in reverse ID order so every width class is used. - let q: Vec = (0..k).rev().collect(); - let predicted = sequence_len(&dag, &q).unwrap().exact().unwrap(); - let (rw, table) = materialize_sequence( - &dag, - &dag.nodes().iter().map(Node::own_len).collect::>(), - &q, - &mut meter, - ) - .unwrap(); - let mut x = c.clone(); - x.info = rebuild_constant_info(&c.info, &rw).unwrap(); - x.sharing = table; - let b = roundtrip(&x); - assert_eq!(fixed + predicted, b.len() as u64, "k={k}"); - assert_eq!(constant_len(&x), Some(b.len() as u64)); - assert_eq!(SharingDag::from_constant(&x, &limits()).unwrap(), dag); - } -} - -#[test] -fn projections_and_empty_roots() { - let c = Constant { - info: ConstantInfo::IPrj(InductiveProj { - idx: 3, - block: Address::hash(b"b"), - }), - sharing: vec![Expr::var(0), Expr::app(Expr::share(0), Expr::share(0))], - refs: vec![Address::hash(b"r")], - univs: vec![Univ::zero()], - }; - let n = normalize(&c); - assert!(n.sharing.is_empty()); - assert_eq!(n.info, c.info); - assert_eq!(normalize(&n), n); - let r = optimize_sharing(&[], &limits()).unwrap(); - assert_eq!((r.roots.len(), r.sharing.len(), r.variable_len), (0, 0, 1)); -} - -#[test] -fn pruning_never_changes_the_result() { - // (lower-bound pruning, materialization bound, greedy seed) - const MODES: [(bool, bool, bool); 5] = [ - (false, false, false), - (true, false, false), - (true, false, true), - (true, true, false), - (true, true, true), - ]; - let mut compared = 0u64; - let mut states = [0u64; MODES.len()]; - for seed in 0..150 { - let c = gen_constant(seed + 40_000, 4, 18, 5); - let mut results: Vec<(Vec, Vec)> = Vec::new(); - let mut seen = [0u64; MODES.len()]; - let mut complete = true; - for (i, (p, m, g)) in MODES.into_iter().enumerate() { - let mut l = limits(); - l.lower_bound_pruning = p; - l.materialization_bound = m; - l.greedy_upper_bound = g; - l.max_states = 300_000; - match normalize_constant_sharing_with_stats(&c, &l) { - Ok((x, r)) => { - seen[i] = r.stats.states_expanded; - results.push((put(&x), r.table_terms)); - }, - Err(SharingError::ResourceExhausted(_)) => complete = false, - Err(e) => panic!("seed {seed}: {e}"), - } - } - assert!(!results.is_empty(), "seed {seed}: every mode exhausted"); - for r in &results[1..] { - assert_eq!(r, &results[0], "seed {seed}"); - } - if complete { - compared += 1; - for i in 0..MODES.len() { - states[i] += seen[i]; - } - } - } - eprintln!( - "pruning modes agree on {compared} constants with every mode complete; \ - states expanded per mode {states:?}" - ); - assert!(compared >= 100); -} - -#[test] -fn ordering_parent_stored_before_its_inlined_descendant() { - // Seven hot atoms and a hot parent P take the eight one-byte slots; P's - // descendant D is also stored, but only in slot 8, so P's entry must - // inline D. Storing D first would push P to a two-byte index. - let hot: Vec = (0..7).map(|i| Expr::reference(i, vec![0, 0, 0])).collect(); - let d = Expr::reference(20, vec![0, 0, 0, 0, 0]); - let p = Expr::app(Expr::var(0), d.clone()); - let mut roots: Vec = Vec::new(); - for h in &hot { - roots.extend(std::iter::repeat_n(h.clone(), 50)); - } - roots.extend(std::iter::repeat_n(p.clone(), 40)); - roots.extend(std::iter::repeat_n(d.clone(), 3)); - let c = Constant { - info: ConstantInfo::Recr(Recursor { - k: false, - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - motives: 0, - minors: 0, - typ: roots[0].clone(), - rules: roots[1..] - .iter() - .map(|e| RecursorRule { fields: 0, rhs: e.clone() }) - .collect(), - }), - sharing: vec![], - refs: vec![], - univs: vec![Univ::zero()], - }; - let dag = SharingDag::from_constant(&c, &limits()).unwrap(); - let terms = dag.term_exprs(); - let id = |e: &E| terms.iter().position(|t| t == e).unwrap(); - let (exact, res) = - normalize_constant_sharing_with_stats(&c, &limits()).unwrap(); - let bytes = roundtrip(&exact); - let hot_ids: Vec = hot.iter().map(id).collect(); - let mut want = hot_ids.clone(); - want.extend([id(&p), id(&d)]); - eprintln!( - "parent-first: exact {} bytes Q={:?} (P={}, D={})", - bytes.len(), - res.table_terms, - id(&p), - id(&d) - ); - assert_eq!(res.table_terms, want); - // P's entry inlines D, which is stored separately afterwards. - assert_eq!(exact.sharing[7].as_ref(), p.as_ref()); - assert_eq!(exact.sharing[8].as_ref(), d.as_ref()); - // Storing D before P is feasible but strictly longer. - let mut child_first = hot_ids; - child_first.extend([id(&d), id(&p)]); - let parent_first = sequence_len(&dag, &res.table_terms).unwrap(); - let child_first = sequence_len(&dag, &child_first).unwrap(); - eprintln!("parent-first {parent_first:?} vs child-first {child_first:?}"); - assert!(parent_first < child_first); -} - -#[test] -fn byte_level_normalization() { - let t2 = chain(2); - let c = axiom(Expr::all(t2.clone(), t2), 1); - let incoming = put(&former_heuristic_t2()); - assert_eq!(put(&normalize(&former_heuristic_t2())), put(&normalize(&c))); - let out = normalize_constant_bytes(&incoming, &limits()).unwrap(); - assert_eq!(hex(&out), "d200009117b0b001921700170000000100"); - assert_eq!(normalize_constant_bytes(&out, &limits()).unwrap(), out); - let mut trailing = out.clone(); - trailing.push(0); - assert!(matches!( - normalize_constant_bytes(&trailing, &limits()), - Err(NormalizeBytesError::Decode(_)) - )); - assert!(matches!( - normalize_constant_bytes(&out[..5], &limits()), - Err(NormalizeBytesError::Decode(_)) - )); - let mut cyclic = c.clone(); - cyclic.sharing = vec![Expr::app(Expr::share(0), Expr::var(0))]; - assert_eq!( - normalize_constant_bytes(&put(&cyclic), &limits()).unwrap_err().to_string(), - "malformed sharing: CyclicShare { entry: 0, index: 0 }" - ); -} - -#[test] -fn telescope_cuts_at_header_boundaries_match_enumeration() { - let mut rng = Rng(99); - let mut checked = 0u64; - for kind in 0..3u8 { - for n in [1usize, 6, 7, 8, 9, 15, 16, 17, 255, 256, 257] { - // Mixed contracts and side children; the tail is another family. - let tail = telescope((kind + 1) % 3, 2, Expr::sort(0), &mut rng); - let e = telescope(kind, n, tail, &mut rng); - let dag = - SharingDag::from_expanded_roots(std::slice::from_ref(&e), &limits()) - .unwrap(); - let terms = dag.term_exprs(); - // The spine suffixes of the root, outermost first. - let mut spine: Vec = vec![dag.roots()[0]]; - loop { - let cur = *spine.last().unwrap(); - let next = match dag.node(cur) { - Node::App(f, _) => *f, - Node::Lam(_, _, b) | Node::All(_, _, _, b) => *b, - _ => break, - }; - if dag.node(next).family() != dag.node(cur).family() { - break; - } - spine.push(next); - } - assert_eq!(spine.len(), n); - for trial in 0..6 { - let mut dict = FixedDictionary::new(); - let mut avail: Vec<(E, u64)> = Vec::new(); - let indices = [0u64, 7, 8, 255, 256, 65535, 65536]; - for (slot, _) in (0..1 + trial % 3).enumerate() { - let t = spine[rng.below(spine.len() as u64) as usize]; - if dict.index_of(t).is_some() { - continue; - } - let idx = indices[(slot + trial) % indices.len()]; - dict.insert(t, idx); - avail.push((terms[t].clone(), idx)); - } - for &t in &[spine[0], spine[spine.len() / 2]] { - assert!(variant_count(&terms[t], &avail) <= 64); - let (len, bytes) = brute_best(&terms[t], &avail); - assert_eq!( - dictionary_cost(&dag, &dict, t), - Len::new(len), - "{kind} {n}" - ); - let got = materialize_with_dictionary(&dag, &dict, &[t]).unwrap(); - assert_eq!(bytes_of(&got[0]), bytes, "{kind} {n}"); - checked += 1; - } - } - } - } - eprintln!("telescope boundary cases compared with enumeration: {checked}"); -} - -#[test] -fn every_valid_incoming_encoding_normalizes_identically() { - let lim = limits(); - let mut encodings = 0u64; - for seed in 0..80 { - let c = gen_constant(seed + 60_000, 3, 25, 5); - let want = put(&normalize(&c)); - let dag = SharingDag::from_constant(&c, &lim).unwrap(); - let own: Vec = dag.nodes().iter().map(Node::own_len).collect(); - let mut rng = Rng(seed); - for _ in 0..8 { - // Any distinct subterms in any order, including non-candidates and - // entries nothing will reference. - let mut q: Vec = (0..dag.len()).filter(|_| rng.pct(35)).collect(); - for i in (1..q.len()).rev() { - q.swap(i, rng.below(i as u64 + 1) as usize); - } - let mut meter = Meter::new(&lim); - let (roots, table) = - materialize_sequence(&dag, &own, &q, &mut meter).unwrap(); - let mut x = c.clone(); - x.info = rebuild_constant_info(&c.info, &roots).unwrap(); - x.sharing = table; - let b = roundtrip(&x); - assert_eq!( - constant_fixed_len(&c).unwrap() - + sequence_len(&dag, &q).unwrap().exact().unwrap(), - b.len() as u64 - ); - assert_eq!(SharingDag::from_constant(&x, &lim).unwrap(), dag); - assert!(want.len() <= b.len()); - assert_eq!(put(&normalize(&x)), want, "seed {seed} q {q:?}"); - encodings += 1; - } - } - eprintln!("incoming encodings normalized identically: {encodings}"); -} - -/// Extended oracle agreement; run with -/// `cargo test --release -p ixon oracle_extended -- --ignored --nocapture`. -#[test] -#[ignore = "long-running; run explicitly in release mode"] -fn oracle_extended() { - let mut tags = [0u64; 11]; - let mut contracts = std::collections::BTreeSet::new(); - let mut cases = 0u64; - let mut seven = 0u64; - let mut skipped = 0u64; - for seed in 0..1500u64 { - let max_n = if seed % 25 == 0 { 7 } else { 6 }; - let c = gen_constant(seed + 100_000, 2, max_n, 4); - if !oracle_tractable(&c, 5_000) { - skipped += 1; - continue; - } - let dag = SharingDag::from_constant(&c, &limits()).unwrap(); - for node in dag.nodes() { - tags[node.tag() as usize] += 1; - match node { - Node::Lam(bc, ..) => { - contracts.insert((8u8, u64::from(bc.to_bits()))); - }, - Node::All(bc, vc, ..) => { - contracts.insert(( - 9, - u64::from(crate::contract::pack_all_contract(*bc, *vc)), - )); - }, - Node::Let(lc, ..) => { - contracts - .insert((10, lc.flags() * 16 + u64::from(lc.binder.to_bits()))); - }, - _ => {}, - } - } - let (exact, res) = - normalize_constant_sharing_with_stats(&c, &limits()).unwrap(); - let bytes = roundtrip(&exact); - let o = oracle_optimum(&c); - assert_eq!( - (o.len, &o.q, &o.bytes), - (bytes.len() as u64, &res.table_terms, &bytes), - "seed {seed}" - ); - cases += 1; - if dag.len() == 7 { - seven += 1; - } - if seed % 100 == 99 { - eprintln!("oracle_extended: {} seeds, {cases} compared", seed + 1); - } - } - eprintln!( - "extended oracle agreement: {cases} constants ({seven} with N = 7, \ - {skipped} skipped as intractable for the oracle), \ - node tags {tags:?}, distinct binder/let contract codes {}", - contracts.len() - ); -} - -/// Independent exhaustive table-order search: every ordered sequence of -/// distinct terms from `pool`, each evaluated exactly, without state -/// merging or pruning. Returns the least `(variable length, Q)`. -fn brute_force_tables( - dag: &SharingDag, - pool: &[TermId], -) -> (Len, Vec, u64) { - use super::dict::{DenseIndex, all_costs}; - fn go( - dag: &SharingDag, - own: &[Len], - pool: &[TermId], - dict: &mut DenseIndex, - q: &mut Vec, - f: Len, - best: &mut (Len, Vec), - visited: &mut u64, - ) { - *visited += 1; - let mut work = 0; - let costs = all_costs(dag.nodes(), own, &*dict, &mut work); - let mut total = f.plus(Len::new(tag0_len(q.len() as u64))); - for &r in dag.roots() { - total = total.plus(costs[r]); - } - if (total, &*q) < (best.0, &best.1) { - *best = (total, q.clone()); - } - for &t in pool { - if q.contains(&t) { - continue; - } - let nf = f.plus(costs[t]); - dict.set(t, q.len() as u64); - q.push(t); - go(dag, own, pool, dict, q, nf, best, visited); - q.pop(); - dict.set(t, u64::MAX); - } - } - let own: Vec = dag.nodes().iter().map(Node::own_len).collect(); - let mut dict = DenseIndex::new(dag.len()); - let mut best = (Len::OVERFLOW, Vec::new()); - let mut visited = 0; - go( - dag, - &own, - pool, - &mut dict, - &mut Vec::new(), - Len::ZERO, - &mut best, - &mut visited, - ); - (best.0, best.1, visited) -} - -fn check_against_brute_force(c: &Constant) -> u64 { - let dag = SharingDag::from_constant(c, &limits()).unwrap(); - let pool: Vec = (0..dag.len()).collect(); - let (len, q, visited) = brute_force_tables(&dag, &pool); - let (exact, res) = - normalize_constant_sharing_with_stats(c, &limits()).unwrap(); - assert_eq!((Len::new(res.variable_len), &res.table_terms), (len, &q)); - let lim = limits(); - let mut meter = Meter::new(&lim); - let own: Vec = dag.nodes().iter().map(Node::own_len).collect(); - let (roots, table) = - materialize_sequence(&dag, &own, &q, &mut meter).unwrap(); - let mut x = c.clone(); - x.info = rebuild_constant_info(&c.info, &roots).unwrap(); - x.sharing = table; - assert_eq!(put(&x), put(&exact)); - visited -} - -#[test] -fn width_classes_match_exhaustive_table_orders() { - // Nine independent atoms: every table of 9 entries puts one in slot 8. - let (c, _) = nine_ref_fixture(); - let visited = check_against_brute_force(&c); - eprintln!("nine refs: brute force visited {visited} table sequences"); - assert_eq!(visited, 986_410); -} - -/// Multi-width agreement on random instances; run with -/// `cargo test --release -p ixon width_classes_extended -- --ignored`. -#[test] -#[ignore = "long-running; run explicitly in release mode"] -fn width_classes_extended() { - let mut total = 0u64; - let mut wide = 0u64; - for seed in 0..60u64 { - let mut rng = Rng(seed + 7_000); - // Up to ten distinct terms, all of which may be stored, with skewed - // use counts so the choice of the one-byte slots matters. - let n_atoms = 8 + rng.below(2); - let atoms: Vec = (0..n_atoms) - .map(|i| Expr::reference(i, vec![0; 1 + rng.below(4) as usize])) - .collect(); - let mut roots: Vec = Vec::new(); - for a in &atoms { - for _ in 0..2 + rng.below(12) { - roots.push(a.clone()); - } - } - if n_atoms == 8 { - let pair = Expr::app(atoms[0].clone(), atoms[1].clone()); - for _ in 0..1 + rng.below(6) { - roots.push(pair.clone()); - } - } - let c = wrap(&mut rng, roots); - let dag = SharingDag::from_constant(&c, &limits()).unwrap(); - assert!(dag.len() <= 10); - total += check_against_brute_force(&c); - let (_, res) = - normalize_constant_sharing_with_stats(&c, &limits()).unwrap(); - if res.table_terms.len() > 8 { - wide += 1; - } - } - eprintln!( - "width classes: 60 instances agree with exhaustive table orders \ - ({wide} optima with more than 8 entries, {total} sequences visited)" - ); - assert!(wide >= 10); -} - -/// T16 -> T16 with every pruning rule and seed disabled: the full -/// width-state space (about 3.3M states) must give the same result. Run with -/// `cargo test --release -p ixon t16_without_pruning -- --ignored`. -#[test] -#[ignore = "long-running; run explicitly in release mode"] -fn t16_without_pruning() { - let t16 = chain(16); - let c = axiom(Expr::all(t16.clone(), t16), 1); - let mut full = ExactSharingLimits::unbounded(); - full.lower_bound_pruning = false; - full.materialization_bound = false; - full.greedy_upper_bound = false; - let (a, ra) = normalize_constant_sharing_with_stats(&c, &full).unwrap(); - let (b, rb) = normalize_constant_sharing_with_stats(&c, &limits()).unwrap(); - eprintln!( - "T16 unpruned: {} bytes, Q={:?}, states {} (pruned search: {} states)", - put(&a).len(), - ra.table_terms, - ra.stats.states_created, - rb.stats.states_created - ); - assert_eq!(put(&a), put(&b)); - assert_eq!(ra.table_terms, rb.table_terms); -} - -/// Whether the exhaustive oracle stays tractable on `c`: the number of -/// occurrence variants of every root with every subterm available. -fn oracle_tractable(c: &Constant, cap: u128) -> bool { - let roots = constant_info_root_exprs(&c.info); - let (_, terms) = oracle_ids(&roots); - let avail: Vec<(E, u64)> = - terms.iter().enumerate().map(|(i, t)| (t.clone(), i as u64)).collect(); - roots.iter().all(|r| variant_count(r, &avail) <= cap) -} - -#[test] -fn deep_inputs_are_handled_iteratively() { - // A 3000-binder telescope whose repeated domain is worth sharing. - let dom = Expr::app(Expr::var(0), Expr::var(1)); - let mut e = Expr::var(0); - for i in 0..3000 { - let ty = if i % 2 == 0 { dom.clone() } else { Expr::sort(0) }; - e = Expr::lam(ty, e); - } - let c = axiom(e, 1); - let (exact, res) = - normalize_constant_sharing_with_stats(&c, &limits()).unwrap(); - let bytes = roundtrip(&exact); - // The innermost binder has height 2 (its domain is an App). - assert_eq!(res.stats.height, 3001); - assert_eq!(exact.sharing.len(), 1); - assert_eq!(exact.sharing[0].as_ref(), dom.as_ref()); - assert_eq!(constant_len(&exact), Some(bytes.len() as u64)); - assert!(bytes.len() < put(&c).len()); -} - -/// Deep inputs normalize as bytes on a 1 MiB stack: the traversals use -/// explicit stacks, and the decoded input and the outputs drop -/// iteratively. -#[test] -fn deep_inputs_normalize_on_a_small_stack() { - std::thread::Builder::new() - .stack_size(1 << 20) - .spawn(|| { - let limits = ExactSharingLimits::default(); - for shape in 0..2u64 { - let mut e = Expr::var(0); - for i in 0..100_000u64 { - e = if shape == 0 { - Expr::app(Expr::var(i % 2), e) - } else { - Expr::prj(0, i % 2, e) - }; - } - let bytes = put(&axiom(e, 1)); - let out = - normalize_constant_bytes_tiered(ShareLayout::TagN, &bytes, &limits) - .unwrap(); - let again = - normalize_constant_bytes_tiered(ShareLayout::TagN, &out, &limits) - .unwrap(); - assert_eq!(again, out); - } - }) - .unwrap() - .join() - .unwrap(); -} - -#[test] -fn candidate_terms_apply_r1_and_r2() { - // T2 -> T2: Prop is one byte (R2), the root occurs once (R1). - let t2 = chain(2); - let c = axiom(Expr::all(t2.clone(), t2), 1); - let dag = SharingDag::from_constant(&c, &limits()).unwrap(); - assert_eq!(candidate_terms(&dag), vec![1, 2]); - let (_, res) = normalize_constant_sharing_with_stats(&c, &limits()).unwrap(); - assert_eq!(res.stats.candidates, 2); -} - -// =========================================================================== -// Uniform Share width (port of Tests/Ix/SharingUniform.lean) -// =========================================================================== - -use super::dict::{Hide, UniformIndex, all_costs, eval_node}; -use super::search::uniform_reference_dag; -use super::uniform::set_prec; - -fn uni(w: u64, roots: &[E]) -> UniformSharingResult { - optimize_sharing_uniform(w, roots, &limits()).unwrap() -} - -fn uni_reference(w: u64, roots: &[E], deg2: bool) -> (u64, Vec) { - let lim = limits(); - let mut meter = Meter::new(&lim); - let dag = SharingDag::from_expanded_roots(roots, &lim).unwrap(); - uniform_reference_dag(&dag, w as u8, deg2, &mut meter).unwrap() -} - -/// Exact uniform-model length of a stored set (entries in any dependency -/// order see every stored descendant). -fn uniform_set_len(dag: &SharingDag, w: u64, set: &[TermId]) -> u64 { - let nodes = dag.nodes(); - let own: Vec = nodes.iter().map(Node::own_len).collect(); - let mut index = vec![None; nodes.len()]; - for &t in set { - index[t] = Some(0); - } - let dict = UniformIndex { index, width: w }; - let mut work = 0; - let costs = all_costs(nodes, &own, &dict, &mut work); - let mut total = Len::new(tag0_len(set.len() as u64)); - for &t in set { - let hide = Hide { inner: &dict, hidden: t }; - total = total.plus( - eval_node(nodes, &own, t, &hide, &|x| costs[x], false, &mut work).0, - ); - } - for &r in dag.roots() { - total = total.plus(costs[r]); - } - total.exact().unwrap() -} - -fn in_degrees(dag: &SharingDag) -> Vec { - let mut deg = vec![0u64; dag.len()]; - for &r in dag.roots() { - deg[r] += 1; - } - for node in dag.nodes() { - for &c in node.children().as_slice() { - deg[c] += 1; - } - } - deg -} - -/// Exhaustive canonical uniform optimum over a candidate pool: the least -/// `(model length, set)` with sets ordered by `set_prec`. -fn uniform_brute( - dag: &SharingDag, - w: u64, - pool: &[TermId], -) -> (u64, Vec) { - let mut best: Option<(u64, Vec)> = None; - for mask in 0u64..(1u64 << pool.len()) { - let set: Vec = (0..pool.len()) - .filter(|&i| mask >> i & 1 == 1) - .map(|i| pool[i]) - .collect(); - let l = uniform_set_len(dag, w, &set); - let better = match &best { - None => true, - Some((bl, bs)) => l < *bl || (l == *bl && set_prec(&set, bs)), - }; - if better { - best = Some((l, set)); - } - } - best.unwrap() -} - -#[test] -fn family_trace_exact_bytes_and_component_costs() { - use super::family_trace::{self, Event}; - let mut rows_checked = 0; - for seed in 0..80 { - let c = gen_constant(seed + 913, 1, 16, 3); - let lim = limits(); - let dag = SharingDag::from_constant(&c, &lim).unwrap(); - let roots = constant_info_root_exprs(&c.info); - let classes: Vec<_> = - (1..=3).map(|w| uni(w, &roots).certain_stored).collect(); - let baseline = - normalize_constant_sharing_tiered(ShareLayout::TagN, &c, &lim).unwrap().0; - for k in [0, 24] { - let mut events = Vec::new(); - let (candidate, _) = family_trace::normalize(&c, &lim, k, &mut |e| { - events.push(e.clone()); - Ok(()) - }) - .unwrap(); - assert_eq!(SharingDag::from_constant(&candidate, &lim).unwrap(), dag); - if k == 24 { - assert_eq!(put(&candidate), put(&baseline)); - } - for event in events { - if let Event::Row { width, count, delta, selected, .. } = event { - let certain = &classes[width as usize - 1]; - let mut all = certain.clone(); - all.extend(&selected); - all.sort_unstable(); - let body = |s: &[TermId]| { - i128::from(uniform_set_len(&dag, width, s)) - - i128::from(tag0_len(s.len() as u64)) - }; - assert_eq!(count, selected.len()); - assert_eq!(delta, body(&all) - body(certain)); - rows_checked += 1; - } - } - } - } - assert!(rows_checked > 0); -} - -#[test] -fn family_trace_classification_does_not_search_and_retains_source_metrics() { - use super::family_trace::{self, Event}; - let mut c = axiom(Expr::app(Expr::var(18), Expr::var(18)), 0); - // This unreachable table payload must still count as source input. - c.sharing.push(Arc::new(Expr::Ref(3, vec![u64::MAX; 17]))); - let lim = ExactSharingLimits { max_states: 0, max_work: 0, ..limits() }; - let mut events = Vec::new(); - family_trace::classify(&c, &lim, &mut |e| { - events.push(e.clone()); - Ok(()) - }) - .unwrap(); - assert_eq!( - events.iter().filter(|e| matches!(e, Event::Width { .. })).count(), - 3 - ); - assert!(events.iter().all(|e| !matches!(e, Event::Row { .. }))); - assert!( - events - .iter() - .any(|e| matches!(e, Event::Input { interned_nodes, dag_nodes, - table_entries: 1, .. } if *interned_nodes > *dag_nodes as u64)) - ); - assert!(family_trace::normalize(&c, &lim, 12, &mut |_| Ok(())).is_err()); - c.sharing[0] = Expr::share(1); - assert!(family_trace::classify(&c, &lim, &mut |_| Ok(())).is_err()); -} - -#[test] -fn uniform_witness_t2() { - let t2 = chain(2); - let roots = vec![Expr::all(t2.clone(), t2)]; - let u1 = uni(1, &roots); - assert_eq!( - ( - u1.certain_stored.clone(), - u1.stored.clone(), - u1.model_len, - u1.variable_len - ), - (vec![2], vec![2], 11, 11) - ); - let u2 = uni(2, &roots); - // T2 has gain 1 at w = 2; no count bracket is within reach, so the - // threshold is 1 and T2 is certain-stored. - assert_eq!( - (u2.certain_stored.clone(), u2.stored.clone(), u2.model_len), - (vec![2], vec![2], 13) - ); - let u3 = uni(3, &roots); - assert_eq!( - (u3.uncertain.clone(), u3.stored.is_empty(), u3.model_len), - (vec![2], true, 14) - ); - assert!(u3.certain_excluded.contains(&1)); - for w in 1..=3 { - assert_eq!(uni(w, &roots).model_len, uni_reference(w, &roots, false).0); - } - assert_eq!( - optimize_sharing_uniform(0, &roots, &limits()).unwrap_err(), - SharingError::FormatBound(FormatBound::UniformWidth { w: 0 }) - ); - eprintln!( - "uniform T2: w=1 {} bytes {:?}; w=2 classes cs={:?} unc={:?}; w=3 model {}", - u1.model_len, u1.table_terms, u2.certain_stored, u2.uncertain, u3.model_len - ); -} - -#[test] -fn uniform_brackets_and_atoms() { - let atoms = |n: u64| -> Vec { - (0..n).map(|i| Expr::reference(i, vec![0])).collect() - }; - let roots_of = |n: u64| -> Vec { - atoms(n).into_iter().flat_map(|a| [a.clone(), a]).collect() - }; - let u = uni(1, &roots_of(128)); - assert_eq!(u.uncertain.len(), 128); - assert_eq!(u.stored.len(), 127); - assert!(!u.stored.contains(&0) && u.lower_bracket); - assert_eq!(u.model_len, 642); - let u = uni(1, &roots_of(129)); - assert_eq!( - (u.stored.len(), u.lower_bracket, u.certain_stored.clone(), u.model_len), - (129, false, vec![128], 648) - ); - // Atom brute force with mixed sizes and uses. - let atoms10: Vec = - (0..10u64).map(|i| Expr::reference(i, vec![0; (i % 3) as usize])).collect(); - let occs: Vec = (0..10).map(|i| 1 + i % 4).collect(); - let roots: Vec = (0..10) - .flat_map(|i| std::iter::repeat_n(atoms10[i].clone(), occs[i] as usize)) - .collect(); - for w in 1..=3u64 { - let mut best = u64::MAX; - for mask in 0u64..1024 { - let ins = |i: usize| mask >> i & 1 == 1; - let mut cost = tag0_len((0..10).filter(|&i| ins(i)).count() as u64); - for i in 0..10 { - let s = expr_len(&atoms10[i]).unwrap(); - cost += if ins(i) { s + occs[i] * w } else { occs[i] * s }; - } - best = best.min(cost); - } - assert_eq!(uni(w, &roots).model_len, best, "w={w}"); - } -} - -#[test] -fn uniform_in_degree_one_tie() { - // t (a 20-byte leaf) has the single parent p = App(Var0, t), which only - // continues two App telescopes. Storing {t} and {p} tie; the canonical - // class (in-degree >= 2) stores p. - let t = Expr::reference(1, vec![0; 18]); - let p = Expr::app(Expr::var(0), t.clone()); - let roots = - vec![Expr::app(p.clone(), Expr::var(1)), Expr::app(p, Expr::var(2))]; - let dag = SharingDag::from_expanded_roots(&roots, &limits()).unwrap(); - let id = |e: &E| dag.term_exprs().iter().position(|x| x == e).unwrap(); - let (tid_, pid) = (id(&t), id(&Expr::app(Expr::var(0), t.clone()))); - let u = uni(1, &roots); - let len_t = uniform_set_len(&dag, 1, &[tid_]); - let len_p = uniform_set_len(&dag, 1, &[pid]); - eprintln!( - "in-degree-1 tie: {{t}} {len_t} bytes, {{p}} {len_p} bytes, uniform stores {:?}", - u.stored - ); - assert_eq!(len_t, len_p); - assert_eq!(u.model_len, len_p); - assert_eq!(u.stored, vec![pid]); - assert_eq!(uni_reference(1, &roots, false).0, u.model_len); - // Over all R1/R2 candidates the pinned order also leaves out the smaller - // ID (t), so here the in-degree restriction does not change the result. - let pool = candidate_terms(&dag); - assert_eq!(uniform_brute(&dag, 1, &pool), (len_t, vec![pid])); -} - -fn gen_uniform_roots(rng: &mut Rng) -> Vec { - let mut g = ExprGen::new(rng.next(), 30); - let leaf = |g: &mut ExprGen| g.leaf(); - match rng.below(6) { - 0 => { - let (f, a, b) = (leaf(&mut g), leaf(&mut g), leaf(&mut g)); - let pre = if rng.pct(50) { - Expr::app(f, a) - } else { - Expr::app(Expr::app(f, a), b) - }; - (0..2 + rng.below(4)) - .map(|i| { - let x = Expr::var(i % 3); - if rng.pct(50) { - Expr::app(pre.clone(), x) - } else { - Expr::app(Expr::app(pre.clone(), x), leaf(&mut g)) - } - }) - .collect() - }, - 1 => { - let e = Expr::app(Expr::var(rng.below(2)), Expr::var(1)); - (0..2 + rng.below(5)) - .map(|_| match rng.below(3) { - 0 => Expr::app(Expr::reference(1, vec![]), e.clone()), - 1 => Expr::lam(e.clone(), Expr::var(0)), - _ => Expr::all(e.clone(), Expr::sort(0)), - }) - .collect() - }, - 2 => { - let pool: Vec = (0..3).map(|_| leaf(&mut g)).collect(); - let c = Expr::app( - pool[rng.below(3) as usize].clone(), - pool[rng.below(3) as usize].clone(), - ); - let p = match rng.below(3) { - 0 => Expr::app(c.clone(), pool[0].clone()), - 1 => Expr::all(pool[1].clone(), c.clone()), - _ => Expr::prj(0, 1, c.clone()), - }; - let mut roots = Vec::new(); - for _ in 0..1 + rng.below(4) { - roots.push(Expr::app(Expr::var(2), p.clone())); - } - for _ in 0..1 + rng.below(4) { - roots.push(Expr::lam(c.clone(), Expr::var(0))); - } - roots - }, - 3 => { - let leaves: Vec = (0..3).map(|_| leaf(&mut g)).collect(); - let mut u = Expr::app(leaves[0].clone(), leaves[1].clone()); - let mut roots = Vec::new(); - for _ in 0..3 + rng.below(6) { - for _ in 0..1 + rng.below(2) { - roots.push(if rng.pct(50) { - Expr::app(Expr::var(7), u.clone()) - } else { - Expr::all(u.clone(), Expr::sort(0)) - }); - } - u = Expr::app(u, leaves[rng.below(3) as usize].clone()); - } - roots.push(u); - roots - }, - _ => (0..1 + rng.below(3)).map(|_| g.expr(4)).collect(), - } -} - -#[test] -fn uniform_agrees_with_reference_and_brute_force() { - let mut rng = Rng(61); - let (mut checked, mut cs, mut ce, mut un, mut max_comp, mut with_unc) = - (0, 0, 0, 0, 0, 0); - let mut brute_checked = 0; - let (mut full_checked, mut restricted_differs) = (0, 0); - let mut attempts = 0; - while checked < 400 { - attempts += 1; - assert!(attempts < 20_000); - let roots = gen_uniform_roots(&mut rng); - let w = [1u64, 2, 3, 5][rng.below(4) as usize]; - let dag = SharingDag::from_expanded_roots(&roots, &limits()).unwrap(); - let cands = candidate_terms(&dag); - if cands.len() > 11 { - continue; - } - let u = uni(w, &roots); - family::compare_production(w, &roots, &u); - let (rm, rq) = uni_reference(w, &roots, false); - let (rrm, rrq) = uni_reference(w, &roots, true); - assert_eq!((u.model_len, rrm), (rm, rm), "w={w} roots={roots:?}"); - assert!( - u.certain_stored.iter().all(|t| rrq.contains(t)), - "w={w} {roots:?}" - ); - assert!( - u.certain_excluded.iter().all(|t| !rq.contains(t) && !rrq.contains(t)) - ); - assert!(u.model_len <= u.unshared_len.unwrap()); - // The full key over the restricted class, by exhaustive subsets. - let deg = in_degrees(&dag); - let pool: Vec = - cands.iter().copied().filter(|&t| deg[t] >= 2).collect(); - if pool.len() <= 10 { - assert_eq!( - uniform_brute(&dag, w, &pool), - (u.model_len, u.stored.clone()), - "w={w} {roots:?}" - ); - brute_checked += 1; - if cands.len() <= 10 { - let full = uniform_brute(&dag, w, &cands); - assert_eq!(full.0, u.model_len); - if full.1 != u.stored { - restricted_differs += 1; - eprintln!( - "restricted != unrestricted canonical set: w={w} {:?} vs {:?} roots={roots:?}", - u.stored, full.1 - ); - } - full_checked += 1; - } - } - checked += 1; - cs += u.certain_stored.len(); - ce += u.certain_excluded.len(); - un += u.uncertain.len(); - max_comp = - max_comp.max(u.components.iter().map(Vec::len).max().unwrap_or(0)); - if !u.uncertain.is_empty() { - with_unc += 1; - } - } - eprintln!( - "uniform vs reference: {checked} inputs ({brute_checked} also by exhaustive subsets): \ - {cs} certain-stored, {ce} certain-excluded, {un} uncertain; {with_unc} inputs with \ - uncertain terms; largest component {max_comp}; unrestricted subsets checked \ - {full_checked}, canonical set differs from the restricted class {restricted_differs}" - ); - assert!(with_unc > 50 && brute_checked > 300); -} - -#[test] -fn uniform_t16_and_properties() { - let t16 = chain(16); - let roots = vec![Expr::all(t16.clone(), t16)]; - let u = uni(1, &roots); - let (rm, _) = uni_reference(1, &roots, false); - eprintln!( - "uniform T16 w=1: model {} = reference {rm}; classes {}/{}/{}; {} states", - u.model_len, - u.certain_stored.len(), - u.uncertain.len(), - u.certain_excluded.len(), - u.states_visited - ); - assert_eq!(u.model_len, rm); - let mut checked = 0; - for seed in 0..200u64 { - let c = gen_constant(seed + 70_000, 6, 16, 4); - let w = [1u64, 2, 4][(seed % 3) as usize]; - let (n, r) = normalize_constant_sharing_uniform(w, &c, &limits()).unwrap(); - let bytes = roundtrip(&n); - let (again, _) = - normalize_constant_sharing_uniform(w, &n, &limits()).unwrap(); - assert_eq!(put(&again), bytes, "seed {seed}"); - let fresh = Constant::get(&mut put(&c).as_slice()).unwrap(); - let (f, _) = - normalize_constant_sharing_uniform(w, &fresh, &limits()).unwrap(); - assert_eq!(put(&f), bytes); - assert!(r.model_len <= r.unshared_len.unwrap()); - checked += 1; - } - assert_eq!(checked, 200); -} - -#[test] -fn uniform_byte_level() { - let t2 = chain(2); - let c = axiom(Expr::all(t2.clone(), t2), 1); - let out = normalize_constant_bytes_uniform(1, &put(&c), &limits()).unwrap(); - // At w = 1 the uniform optimum is the 17-byte exact minimum. - assert_eq!(hex(&out), "d200009117b0b001921700170000000100"); - let out3 = normalize_constant_bytes_uniform(3, &put(&c), &limits()).unwrap(); - assert_eq!(out3, put(&c)); - assert!(matches!( - normalize_constant_bytes_uniform(0, &put(&c), &limits()), - Err(NormalizeBytesError::Sharing(SharingError::FormatBound(_))) - )); -} - -// =========================================================================== -// Tiered construction (port of Tests/Ix/SharingTiered.lean) -// =========================================================================== - -const LAYOUTS: [ShareLayout; 1] = [ShareLayout::TagN]; - -#[test] -fn tiered_layout_widths() { - let at = [ - 7u64, - 8, - 1031, - 1032, - 66567, - 66568, - 66568 + (1 << 24) - 1, - 66568 + (1 << 24), - 66568 + (1 << 24) + (1 << 32) - 1, - 66568 + (1 << 24) + (1 << 32), - ]; - let got: Vec = - at.iter().map(|&i| ShareLayout::TagN.width_at(i)).collect(); - assert_eq!(got, vec![1, 2, 2, 3, 3, 4, 4, 5, 5, 9]); - assert_eq!( - ( - TAGN_RUNG1_END, - TAGN_RUNG2_END, - TAGN_RUNG3_END, - TAGN_RUNG4_END, - TAGN_RUNG5_END - ), - (8, 1032, 66568, 66568 + (1 << 24), 66568 + (1 << 24) + (1 << 32)) - ); - // The Share pricing is the wire width of the Share code. - for i in at { - assert_eq!(tagn_width(i), TagN::byte_width(4, i) as u64); - } - assert_eq!(ShareLayout::TagN.width_at(u64::MAX), 9); - assert_eq!(ShareLayout::wire(), ShareLayout::TagN); -} - -#[test] -fn tiered_fixtures() { - let t2 = chain(2); - let w2 = axiom(Expr::all(t2.clone(), t2), 1); - let t16 = chain(16); - let w16 = axiom(Expr::all(t16.clone(), t16), 1); - let (nine, hot) = nine_ref_fixture(); - for l in LAYOUTS { - let (n, _) = normalize_constant_sharing_tiered(l, &w2, &limits()).unwrap(); - assert_eq!( - hex(&roundtrip(&n)), - "d200009117b0b001921700170000000100", - "{l:?}" - ); - let (n, _) = normalize_constant_sharing_tiered(l, &w16, &limits()).unwrap(); - assert_eq!(roundtrip(&n).len(), 46, "{l:?}"); - let (n, r) = - normalize_constant_sharing_tiered(l, &nine, &limits()).unwrap(); - let pos = n.sharing.iter().position(|e| e == &hot).unwrap(); - eprintln!( - "tiered nine refs {l:?}: {} bytes, w={}, first tier {:?}, phase-1 layout {}, final {}, kept phase-1 order {}", - roundtrip(&n).len(), - r.stats.w, - r.stats.first_tier, - r.stats.phase1_layout_bytes, - r.stats.phase3_layout_bytes, - r.stats.kept_phase1_order - ); - assert_eq!(roundtrip(&n).len(), 578); - // The candidate at w = 2 alone writes the same bytes. - let (m, _) = - normalize_constant_sharing_tiered_at_width(l, &nine, &limits(), 2) - .unwrap(); - assert_eq!(put(&m), put(&n)); - assert!(r.stats.first_tier.contains(&2) && pos < 8); - } -} - -/// A heavy parent over two lighter children next to more than eight atoms -/// used a few times each (Lean `genHeavyParent`). -fn gen_heavy_parent(rng: &mut Rng) -> Vec { - let n_atoms = 9 + rng.below(4); - let atoms: Vec = (0..n_atoms) - .map(|j| Expr::reference(j + 10, vec![0; (1 + j % 3) as usize])) - .collect(); - let c1 = Expr::reference(1, vec![0, 0, 0]); - let c2 = Expr::reference(2, vec![0, 0, 1]); - let parent = if rng.pct(50) { - Expr::app(c1.clone(), c2.clone()) - } else { - Expr::all(c1.clone(), c2.clone()) - }; - let mut roots = Vec::new(); - for a in &atoms { - for _ in 0..2 + rng.below(3) { - roots.push(a.clone()); - } - } - for _ in 0..8 + rng.below(15) { - roots.push(Expr::app(Expr::var(1), parent.clone())); - } - roots.push(c1); - roots.push(c2); - roots -} - -#[test] -fn tiered_allocation_properties() { - let mut rng = Rng(71); - let (mut checked, mut changed, mut saved, mut kept) = (0, 0, 0, 0); - for i in 0..120u64 { - let c = if i % 2 == 0 { - let roots = gen_heavy_parent(&mut rng); - wrap(&mut rng, roots) - } else { - gen_constant(i + 80_000, 6, 30, 5) - }; - for l in LAYOUTS { - let (n, r) = normalize_constant_sharing_tiered(l, &c, &limits()).unwrap(); - let s = &r.stats; - assert!(s.phase3_layout_bytes <= s.phase1_layout_bytes, "case {i} {l:?}"); - assert!(s.final_ref_cost <= s.phase1_ref_cost, "case {i} {l:?}"); - assert_eq!( - roundtrip(&n).len() as u64, - constant_fixed_len(&c).unwrap() + r.variable_len - ); - if l == ShareLayout::wire() { - assert_eq!(r.model_len, r.variable_len); - } - let (again, _) = - normalize_constant_sharing_tiered(l, &n, &limits()).unwrap(); - assert_eq!(put(&again), put(&n), "case {i} {l:?} not idempotent"); - checked += 1; - if s.final_ref_cost < s.phase1_ref_cost { - changed += 1; - } - if s.savings > 0 { - saved += 1; - } - if s.kept_phase1_order { - kept += 1; - } - } - } - eprintln!( - "tiered: {checked} runs; allocation lowered the reference cost in {changed}, \ - positive savings in {saved}, phase-1 order kept in {kept}" - ); - assert!(checked == 120 && changed > 0); -} - -/// Brute force over all subsets: dependency-closed, at most `cap` terms, -/// maximum weight, ties by the greatest indicator vector in the order -/// (weight descending, ID ascending). -fn brute_tier( - n: usize, - weight: &[u64], - deps: &[Vec], - cap: usize, -) -> Vec { - let mut items: Vec = (0..n).collect(); - items.sort_by(|&a, &b| weight[b].cmp(&weight[a]).then(a.cmp(&b))); - let mut best: Option<(u64, Vec)> = None; - for mask in 0u32..(1 << n) { - let ins = |t: TermId| mask >> t & 1 == 1; - let members: Vec = (0..n).filter(|&t| ins(t)).collect(); - if members.len() > cap - || !members.iter().all(|&t| deps[t].iter().all(|&d| ins(d))) - { - continue; - } - let wsum: u64 = members.iter().map(|&t| weight[t]).sum(); - let vec: Vec = items.iter().map(|&t| ins(t)).collect(); - let better = match &best { - None => true, - Some((bw, bv)) => { - wsum > *bw - || (wsum == *bw - && vec - .iter() - .zip(bv) - .find(|(a, b)| a != b) - .is_some_and(|(a, _)| *a)) - }, - }; - if better { - best = Some((wsum, vec)); - } - } - let (_, vec) = best.unwrap(); - let mut out: Vec = - items.iter().zip(&vec).filter(|(_, b)| **b).map(|(t, _)| *t).collect(); - out.sort_unstable(); - out -} - -/// The first-tier search against brute force; half of the cases shift the -/// term IDs across the old 32-bit ID limit. -#[test] -fn tiered_first_tier_matches_brute_force() { - let mut rng = Rng(73); - for case in 0..300 { - let n = 1 + rng.below(12) as usize; - let mut weight = Vec::new(); - let mut deps: Vec> = Vec::new(); - for t in 0..n { - weight.push(1 + rng.below(4)); - deps.push((0..t).filter(|_| rng.below(4) == 0).collect()); - } - let cap = 8.min(1 + rng.below(n as u64) as usize); - let base: TermId = if case % 2 == 1 { (1 << 32) - 6 } else { 0 }; - let wmap: FxHashMap = - (0..n).map(|t| (base + t, weight[t])).collect(); - let dmap: FxHashMap> = (0..n) - .map(|t| (base + t, deps[t].iter().map(|&d| base + d).collect())) - .collect(); - let stored: Vec = (base..base + n).collect(); - let (s, _) = first_tier(&stored, &wmap, &dmap, cap, &limits()).unwrap(); - let want: Vec = - brute_tier(n, &weight, &deps, cap).iter().map(|&t| base + t).collect(); - assert_eq!( - s, want, - "case {case}: n={n} cap={cap} w={weight:?} deps={deps:?}" - ); - } -} - -#[test] -fn tiered_byte_level() { - let t2 = chain(2); - let c = axiom(Expr::all(t2.clone(), t2), 1); - for l in LAYOUTS { - let out = normalize_constant_bytes_tiered(l, &put(&c), &limits()).unwrap(); - assert_eq!(hex(&out), "d200009117b0b001921700170000000100"); - } -} - -// --------------------------------------------------------------------------- -// Reclassifying branch and bound vs the subset enumeration -// --------------------------------------------------------------------------- - -/// `mk a1 ... an`-style prefix chains: `u_k = u_{k-1} a_k`, each prefix also -/// used once as the head of a longer spine with other arguments, plus a few -/// repeated arguments. -fn gen_prefix_chain(rng: &mut Rng) -> Vec { - let mut g = ExprGen::new(rng.next(), 0); - let pool: Vec = (0..4).map(|_| g.leaf()).collect(); - let len = 3 + rng.below(14); - let mut u = pool[0].clone(); - let mut roots = Vec::new(); - for k in 0..len { - let a = if rng.below(3) == 0 { - rng.pick(&pool).clone() - } else { - Expr::var(k % 5) - }; - u = Expr::app(u, a); - let mut x = Expr::app(u.clone(), Expr::sort(1)); - for _ in 0..rng.below(4) { - x = Expr::app(x, rng.pick(&pool).clone()); - } - roots.push(if rng.below(2) == 0 { x } else { Expr::all(x, Expr::sort(0)) }); - } - roots.push(u); - roots -} - -/// Nested binder telescopes whose prefixes and bodies repeat. -fn gen_telescopes(rng: &mut Rng) -> Vec { - let mut g = ExprGen::new(rng.next(), 0); - let pool: Vec = (0..4).map(|_| g.leaf()).collect(); - let mut body = pool[0].clone(); - let mut roots = Vec::new(); - for _ in 0..2 + rng.below(10) { - let ty = rng.pick(&pool).clone(); - body = match rng.below(3) { - 0 => Expr::lam_contract(binder_contract(rng), ty, body), - 1 => Expr::all(ty, body), - _ => Expr::app(body, ty), - }; - if rng.below(2) == 0 { - roots.push(Expr::app(Expr::var(3), body.clone())); - } - } - roots.push(body); - roots -} - -/// Long App/binder spines, each used twice. -fn gen_spines(rng: &mut Rng) -> Vec { - let mut g = ExprGen::new(rng.next(), 0); - let mut roots = Vec::new(); - for _ in 0..1 + rng.below(3) { - let mut e = g.leaf(); - for _ in 0..5 + rng.below(40) { - let x = g.leaf(); - e = match rng.below(4) { - 0 | 1 => Expr::app(e, x), - 2 => Expr::lam_contract(binder_contract(rng), x, e), - _ => Expr::all(x, e), - }; - } - roots.push(e.clone()); - roots.push(Expr::app(e.clone(), e)); - } - roots -} - -fn gen_search_roots(rng: &mut Rng) -> Vec { - match rng.below(7) { - 0 => gen_prefix_chain(rng), - 1 => gen_telescopes(rng), - 2 | 3 => gen_spines(rng), - 4 => gen_uniform_roots(rng), - _ => { - let mut g = ExprGen::new(rng.next(), 40); - (0..1 + rng.below(5)).map(|_| g.expr(5)).collect() - }, - } -} - -fn subset_reference_limits() -> ExactSharingLimits { - ExactSharingLimits { - uniform_subset_search: true, - max_states: 1 << 16, - ..limits() - } -} - -// The subset oracle can retain early discoveries outside the final slack -// band; compare every count inside that band, including its chosen set. -fn retained_component_counts( - u: &UniformSharingResult, -) -> Vec)>> { - let cs = u.certain_stored.len() as u64; - let slack = - i128::from(tag0_len(cs + u.uncertain.len() as u64) - tag0_len(cs)); - u.component_tables - .iter() - .map(|table| { - let delta = |d: &num_bigint::BigInt| i128::try_from(d).unwrap(); - let best = table.iter().flatten().map(|(d, _)| delta(d)).min().unwrap(); - table - .iter() - .enumerate() - .filter_map(|(k, entry)| { - entry - .as_ref() - .filter(|(d, _)| delta(d) <= best + slack) - .map(|(d, set)| (k, delta(d), set.clone())) - }) - .collect() - }) - .collect() -} - -/// The reclassifying branch and bound returns exactly what the subset -/// enumeration returns (same set, so the same tie-break, and the same -/// output), on inputs with larger uncertain components. -#[test] -fn uniform_branch_and_bound_matches_subset_enumeration() { - let mut rng = Rng(71); - let reference = subset_reference_limits(); - let (mut checked, mut skipped, mut max_comp) = (0, 0, 0); - for i in 0..4000 { - if checked >= 600 { - break; - } - let roots = gen_search_roots(&mut rng); - let w = [1u64, 2, 3, 5][rng.below(4) as usize]; - let u = optimize_sharing_uniform(w, &roots, &limits()) - .unwrap_or_else(|e| panic!("case {i} w={w}: {e:?} roots={roots:?}")); - let comp = u.components.iter().map(Vec::len).max().unwrap_or(0); - if comp < 2 { - continue; - } - family::compare_production(w, &roots, &u); - match optimize_sharing_uniform(w, &roots, &reference) { - Ok(r) => { - assert_eq!(u.components, r.components); - assert_eq!( - retained_component_counts(&u), - retained_component_counts(&r), - "per-count optimum/tie: case {i} w={w} roots={roots:?}" - ); - assert_eq!( - (&u.stored, u.model_len, u.lower_bracket), - (&r.stored, r.model_len, r.lower_bracket), - "case {i} w={w} roots={roots:?}" - ); - assert_eq!( - (&u.table_terms, &u.sharing, &u.roots), - (&r.table_terms, &r.sharing, &r.roots) - ); - }, - Err(SharingError::ResourceExhausted(_)) => skipped += 1, - Err(e) => panic!("case {i} w={w}: subset enumeration error {e:?}"), - } - checked += 1; - max_comp = max_comp.max(comp); - } - eprintln!( - "uniform branch and bound vs subset enumeration: {checked} inputs with a \ - component of >= 2 uncertain terms, largest component {max_comp}, \ - {skipped} skipped where the enumeration exceeded 2^16 states" - ); - assert_eq!(checked, 600); - assert!(checked - skipped >= 400); -} - -/// Search agreement across the 128-entry table-count bracket: 120 one-byte -/// atoms used twice (uncertain, gain 1) plus an App prefix chain. -#[test] -fn uniform_branch_and_bound_across_a_bracket() { - let mut roots = Vec::new(); - for i in 0..120 { - let a = Expr::reference(i, vec![0]); - roots.push(a.clone()); - roots.push(a); - } - roots.extend(gen_prefix_chain(&mut Rng(5))); - let reference = - ExactSharingLimits { uniform_subset_search: true, ..limits() }; - for w in 1..=3 { - let u = optimize_sharing_uniform(w, &roots, &limits()).unwrap(); - let r = optimize_sharing_uniform(w, &roots, &reference).unwrap(); - assert_eq!((&u.stored, u.model_len), (&r.stored, r.model_len), "w={w}"); - assert_eq!( - retained_component_counts(&u), - retained_component_counts(&r), - "w={w}" - ); - } -} - -// --------------------------------------------------------------------------- -// Width selection against the single-width candidates -// --------------------------------------------------------------------------- - -/// The canonical construction is the single-width candidate (`tiered_at`) -/// with the fewest layout bytes (ties to the lower width), and reports every -/// candidate length; every candidate is a valid encoding of the same -/// constant, priced and serialized consistently. -#[test] -fn tiered_selects_the_best_width_candidate() { - let mut rng = Rng(73); - for i in 0..60u64 { - let c = if i % 2 == 0 { - let roots = gen_heavy_parent(&mut rng); - wrap(&mut rng, roots) - } else { - gen_constant(i + 90_000, 6, 30, 5) - }; - for l in LAYOUTS { - let (n, r) = normalize_constant_sharing_tiered(l, &c, &limits()).unwrap(); - let mut lengths = Vec::new(); - let mut best: Option<(u64, u64)> = None; - for w in 1..=3 { - let (m, s) = - normalize_constant_sharing_tiered_at_width(l, &c, &limits(), w) - .unwrap(); - assert_eq!(s.stats.w, w); - assert_eq!( - roundtrip(&m).len() as u64, - constant_fixed_len(&c).unwrap() + s.variable_len - ); - assert_eq!( - put(&unshared(&m)), - put(&unshared(&c)), - "case {i} {l:?} w={w}: different constant" - ); - assert_eq!(s.stats.candidate_lengths, vec![(w, s.model_len)]); - lengths.push((w, s.model_len)); - if best.is_none_or(|(_, b)| s.model_len < b) { - best = Some((w, s.model_len)); - } - // The meters: the returned candidate's include the work of its - // one-pass decisions, as the single candidate's do; the others' do - // not (they make none). - let (all, one) = ( - r.stats.candidate_meters[w as usize - 1], - s.stats.candidate_meters[0], - ); - assert_eq!((all.0, all.1, all.2), (one.0, one.1, one.2)); - assert!(all.3 <= one.3, "case {i} w={w}"); - if w == r.stats.w { - assert_eq!((put(&m), s.model_len), (put(&n), r.model_len)); - assert_eq!(all, one, "case {i} w={w}"); - } - } - // The selection rule: fewest layout bytes, ties to the lower width. - assert_eq!(best, Some((r.stats.w, r.model_len)), "case {i} {l:?}"); - assert_eq!(r.stats.candidate_lengths, lengths); - } - } -} - -// --------------------------------------------------------------------------- -// Parallelism inside one constant -// --------------------------------------------------------------------------- - -fn par(widths: usize, components: usize, materialize: usize) -> Parallelism { - Parallelism { widths, components, materialize } -} - -const PAR_BUDGETS: [(usize, usize, usize); 6] = - [(3, 1, 1), (1, 4, 1), (1, 1, 4), (3, 4, 4), (2, 16, 3), (3, 64, 64)]; - -fn par_cases() -> Vec { - let t2 = chain(2); - let t16 = chain(16); - let (nine, _) = nine_ref_fixture(); - let mut cases = vec![ - axiom(Expr::all(t2.clone(), t2), 1), - axiom(Expr::all(t16.clone(), t16), 1), - nine, - ]; - let mut rng = Rng(83); - // Enough cases for more than 20 multi-component runs. - for i in 0..180u64 { - let c = match i % 3 { - 0 => { - let roots = gen_heavy_parent(&mut rng); - wrap(&mut rng, roots) - }, - 1 => { - let roots = gen_search_roots(&mut rng); - wrap(&mut rng, roots) - }, - _ => gen_constant(i + 97_000, 6, 30, 5), - }; - cases.push(c); - } - cases -} - -/// Every parallel budget gives the sequential bytes and the same result, -/// counters included, on the section-2 fixtures and generated families, -/// with the one-pass phase 3 and with the per-prefix phase 3 -/// (`phase3_per_prefix`), whose tasks the `materialize` budget splits. -#[test] -fn parallel_tiered_matches_sequential() { - let (mut compared, mut multi_comp) = (0, 0); - for (i, c) in par_cases().iter().enumerate() { - for per_prefix in [false, true] { - let l = ShareLayout::TagN; - let lim = - ExactSharingLimits { phase3_per_prefix: per_prefix, ..limits() }; - let (n, r) = normalize_constant_sharing_tiered(l, c, &lim).unwrap(); - if !per_prefix && r.phase1.components.len() > 1 { - multi_comp += 1; - } - for (w, k, m) in PAR_BUDGETS { - let (pn, pr) = - normalize_constant_sharing_tiered_par(l, c, &lim, par(w, k, m)) - .unwrap(); - let at = format!("case {i} per prefix {per_prefix} budget {w},{k},{m}"); - assert_eq!(put(&pn), put(&n), "{at}"); - assert_eq!(pr, r, "{at}"); - compared += 1; - } - } - } - eprintln!( - "parallel vs sequential: {compared} runs identical ({multi_comp} sequential runs with more than one component)" - ); - assert!(multi_comp > 10); -} - -/// Limits still fail closed under parallelism. With sequential components -/// the error is the sequential one; with parallel components the call fails -/// exactly when the sequential call fails (the reported resource may differ, -/// see `Parallelism`). Every other case runs the per-prefix phase 3 -/// (`phase3_per_prefix`), whose work the `materialize` budget splits. -#[test] -fn parallel_tiered_limits_fail_closed() { - let (mut both_fail, mut both_ok) = (0, 0); - let tight_limits = - [(3, u64::MAX), (40, u64::MAX), (u64::MAX, 2_000), (60, 20_000)]; - for (i, c) in par_cases().iter().enumerate() { - for (max_states, max_work) in tight_limits { - let tight = ExactSharingLimits { - max_states, - max_work, - phase3_per_prefix: i % 2 == 1, - ..limits() - }; - let seq = normalize_constant_sharing_tiered(ShareLayout::TagN, c, &tight); - for (w, k, m) in PAR_BUDGETS { - let res = normalize_constant_sharing_tiered_par( - ShareLayout::TagN, - c, - &tight, - par(w, k, m), - ); - match (&seq, &res) { - (Ok((a, _)), Ok((b, _))) => { - assert_eq!(put(a), put(b), "case {i}"); - both_ok += 1; - }, - (Err(e), Err(f)) => { - if k <= 1 { - assert_eq!(e, f, "case {i} budget {w},{k},{m}"); - } - assert!( - matches!(f, SharingError::ResourceExhausted(_)), - "case {i}: {f:?}" - ); - both_fail += 1; - }, - _ => panic!( - "case {i} budget {w},{k},{m} states {max_states} work {max_work}: sequential ok {} vs parallel ok {}", - seq.is_ok(), - res.is_ok() - ), - } - } - } - } - eprintln!("parallel limits: {both_ok} both succeed, {both_fail} both fail"); - assert!(both_fail > 50 && both_ok > 50); -} - -/// The Kahn priority order of phase 2 against a direct reference: the -/// available term of largest weight, ties by the smaller ID, on random DAGs -/// (dependencies outside `rest` are ignored). Half of the cases use term IDs -/// on both sides of the old 32-bit ID limit. -#[test] -fn kahn_order_matches_reference() { - use super::tiered::kahn_order; - let mut rng = Rng(89); - for case in 0..300 { - let m = 1 + rng.below(30) as usize; - let base: TermId = if case % 2 == 1 { (1 << 32) - 45 } else { 0 }; - // Distinct IDs in a random topological sequence; a term may depend on - // earlier terms of the sequence (some twice) and on IDs outside `rest`. - let mut ids: Vec = (0..m).map(|i| base + i * 3 + (i % 2)).collect(); - for i in (1..m).rev() { - ids.swap(i, rng.below(i as u64 + 1) as usize); - } - let mut weight: FxHashMap = FxHashMap::default(); - let mut deps: FxHashMap> = FxHashMap::default(); - for (i, &t) in ids.iter().enumerate() { - weight.insert(t, rng.below(5)); - let mut ds = Vec::new(); - for &u in &ids[..i] { - if rng.below(4) == 0 { - ds.push(u); - } - } - if rng.below(3) == 0 { - ds.push(base + 10_000 + rng.below(5) as usize); - } - // A repeated dependency does not change the order. - if !ds.is_empty() && rng.below(4) == 0 { - ds.push(ds[rng.below(ds.len() as u64) as usize]); - } - deps.insert(t, ds); - } - let mut rest = ids.clone(); - rest.sort_unstable(); - let in_rest: FxHashMap = - rest.iter().map(|&t| (t, ())).collect(); - let w = |t: TermId| weight[&t]; - let mut placed: Vec = Vec::new(); - while placed.len() < m { - let next = rest - .iter() - .copied() - .filter(|t| !placed.contains(t)) - .filter(|t| { - deps[t].iter().all(|d| !in_rest.contains_key(d) || placed.contains(d)) - }) - .min_by(|&a, &b| w(b).cmp(&w(a)).then(a.cmp(&b))) - .unwrap(); - placed.push(next); - } - assert_eq!(kahn_order(&weight, &deps, &rest), placed, "case {case}"); - } -} - -/// The MSS reference encoding reproduces the `docs/sharing-minimum.md` §2 -/// numbers (T2 17 bytes, T16 46) and encodes the same constant. -#[test] -fn mss_reference_encoding() { - let t2 = chain(2); - let w2 = axiom(Expr::all(t2.clone(), t2), 1); - let t16 = chain(16); - let w16 = axiom(Expr::all(t16.clone(), t16), 1); - assert_eq!(roundtrip(&mss::mss_constant(&w2, &limits()).unwrap()).len(), 17); - assert_eq!(roundtrip(&mss::mss_constant(&w16, &limits()).unwrap()).len(), 46); - let mut rng = Rng(91); - for i in 0..60u64 { - let c = if i % 2 == 0 { - let roots = gen_heavy_parent(&mut rng); - wrap(&mut rng, roots) - } else { - gen_constant(i + 99_000, 6, 30, 5) - }; - let n = mss::mss_constant(&c, &limits()).unwrap(); - assert_eq!(put(&unshared(&n)), put(&unshared(&c)), "case {i}"); - } -} - -// --------------------------------------------------------------------------- -// Limits: defaults and overrides -// --------------------------------------------------------------------------- - -/// The production defaults are the safety-net values. -#[test] -fn limit_defaults_are_the_safety_net() { - let d = ExactSharingLimits::default(); - assert_eq!( - ( - d.max_input_nodes, - d.max_distinct_nodes, - d.max_height, - d.max_candidates, - d.max_states, - d.max_layer_states, - d.max_transitions, - d.max_work, - d.max_output_bytes, - d.max_knapsack_cells - ), - ( - 1 << 40, - 1 << 32, - 1 << 32, - 1 << 16, - 1 << 40, - 1 << 40, - 1 << 40, - 1 << 56, - 1 << 40, - 1 << 28 - ) - ); -} - -/// `with_overrides`: values, `unbounded`, Lean-only keys and errors. -#[test] -fn limit_overrides_parse() { - let d = ExactSharingLimits::default(); - let l = d - .clone() - .with_overrides(" states = 2^10 ,work=12345, output_bytes=max,,") - .unwrap(); - assert_eq!( - (l.max_states, l.max_work, l.max_output_bytes), - (1024, 12345, u64::MAX) - ); - assert_eq!(l.max_height, d.max_height); - // Lean-only keys are accepted and ignored. - assert_eq!( - d.clone() - .with_overrides("depth=5,cost_evals=2^3,materialize_work=9") - .unwrap(), - d - ); - // `unbounded`, then a later item still applies. - let u = d.clone().with_overrides("unbounded,states=2").unwrap(); - assert_eq!( - (u.max_work, u.max_height, u.max_candidates, u.max_states), - (u64::MAX, u64::MAX, u64::MAX, 2) - ); - assert_eq!(u.lower_bound_pruning, d.lower_bound_pruning); - for bad in - ["bogus=1", "states", "states=2^64", "states=-1", "states=1e3", "=3"] - { - assert!(d.clone().with_overrides(bad).is_err(), "accepted {bad}"); - } - assert!(d.clone().with_overrides("states=18446744073709551616").is_err()); - assert_eq!( - d.clone().with_overrides("states=18446744073709551615").unwrap().max_states, - u64::MAX - ); -} - -/// Override specs and the `states` limit each sets (`None`: rejected). This is -/// the grammar of Lean `Ix.Sharing.Exact.Limits.withOverrides`; the Lean -/// test `limitSpecParity` (`Tests/Ix/SharingExact.lean`) pins the same list. -/// Values are ASCII digits, `2^k` or `max` (no sign, no `_` separator, no -/// other spelling); items and keys are trimmed of space, tab, line feed and -/// carriage return only. -const LIMIT_SPEC_PARITY: &[(&str, Option)] = &[ - ("states=5", Some(5)), - ("states=007", Some(7)), - (" states = 2^10 ", Some(1 << 10)), - ("states=2^0", Some(1)), - ("states=2^63", Some(1 << 63)), - ("states=max", Some(u64::MAX)), - ("states=18446744073709551615", Some(u64::MAX)), - ("unbounded", Some(u64::MAX)), - ("", Some(1 << 40)), - (",,states=5,,", Some(5)), - ("\tstates=5\n", Some(5)), - ("states=5\r", Some(5)), - ("states= 5", Some(5)), - ("states =5", Some(5)), - ("states=1_000", None), - ("states=2^1_0", None), - ("states=+5", None), - ("states=2^+5", None), - ("states=-1", None), - ("states=1e3", None), - ("states=0x10", None), - ("states=2^64", None), - ("states=18446744073709551616", None), - ("states=", None), - ("states=2^", None), - ("=5", None), - ("states", None), - ("a=b=c", None), - ("bogus=1", None), - ("STATES=5", None), - ("states=MAX", None), - ("states=\u{a0}5", None), - ("states=\u{ff15}", None), - ("states=\u{c}5", None), - ("states=\u{b}5", None), -]; - -#[test] -fn limit_overrides_shared_spec_list() { - let d = ExactSharingLimits::default(); - for &(spec, states) in LIMIT_SPEC_PARITY { - match (d.clone().with_overrides(spec), states) { - (Ok(l), Some(s)) => assert_eq!(l.max_states, s, "{spec:?}"), - (Err(_), None) => {}, - (got, want) => panic!("{spec:?}: got {got:?}, want states {want:?}"), - } - } -} - -/// The error of an exhausted limit names the override key. -#[test] -fn exhausted_limit_names_its_key() { - let c = axiom(Expr::all(chain(2), chain(2)), 1); - let tiny = ExactSharingLimits { max_distinct_nodes: 2, ..limits() }; - let err = normalize_constant_sharing_tiered(ShareLayout::TagN, &c, &tiny) - .expect_err("two distinct nodes cannot hold this input"); - assert_eq!(err.to_string(), "resource exhausted: distinct_nodes (limit 2)"); - for r in [ - Resource::InputNodes, - Resource::DistinctNodes, - Resource::Height, - Resource::Candidates, - Resource::States, - Resource::LayerStates, - Resource::Transitions, - Resource::Work, - Resource::OutputBytes, - Resource::KnapsackCells, - ] { - let mut l = ExactSharingLimits::default(); - assert!(l.set_limit(r.key(), 3), "{r:?}"); - } -} - -/// The telescope-spine guard of the uniform optimizer (Lean's -/// `teleSubaddEnd` check): the bound, the predicate at the bound, and the -/// Share-flag width's subadditivity just below it at the rung ends. -#[test] -fn telescope_spine_guard() { - use super::uniform::{TELE_SUBADD_END, spines_within_bound}; - assert_eq!(TELE_SUBADD_END, 4_311_811_080); - assert_eq!(TELE_SUBADD_END, TagN::end5(4)); - assert!(spines_within_bound(&[])); - assert!(spines_within_bound(&[0, 1, TELE_SUBADD_END - 1])); - assert!(!spines_within_bound(&[0, TELE_SUBADD_END])); - assert!(!spines_within_bound(&[u64::MAX])); - let ends = [ - TAGN_RUNG1_END, - TAGN_RUNG2_END, - TAGN_RUNG3_END, - TAGN_RUNG4_END, - TAGN_RUNG5_END, - ]; - let mut xs: Vec = vec![0, 1, 2]; - for e in ends { - xs.extend([e - 2, e - 1, e, e + 1]); - } - for &a in &xs { - for &b in &xs { - if a.checked_add(b).is_some_and(|s| s < TELE_SUBADD_END) { - assert!(tag4_len(a + b) <= tag4_len(a) + tag4_len(b), "{a} {b}"); - } - } - } -} - -/// The table-count knapsack has its own limit (`max_knapsack_cells`, -/// resource `KnapsackCells`), separate from `max_states`: the bracket input -/// of `uniform_branch_and_bound_across_a_bracket` runs the knapsack, fails -/// closed under a one-cell limit, and certifies at the defaults even with a -/// `max_states` below the knapsack's cell count. -#[test] -fn knapsack_cells_limit() { - let mut roots = Vec::new(); - for i in 0..120 { - let a = Expr::reference(i, vec![0]); - roots.push(a.clone()); - roots.push(a); - } - roots.extend(gen_prefix_chain(&mut Rng(5))); - let mut knapsack_failures = 0; - for w in 1..=3 { - let ok = optimize_sharing_uniform(w, &roots, &limits()).unwrap(); - let tight = ExactSharingLimits { max_knapsack_cells: 1, ..limits() }; - match optimize_sharing_uniform(w, &roots, &tight) { - Ok(r) => { - assert_eq!((&r.stored, r.model_len), (&ok.stored, ok.model_len)); - }, - Err(SharingError::ResourceExhausted(e)) => { - assert_eq!((e.resource, e.limit), (Resource::KnapsackCells, 1)); - knapsack_failures += 1; - }, - Err(e) => panic!("w={w}: {e:?}"), - } - let few_states = - ExactSharingLimits { max_states: ok.states_visited.max(1), ..limits() }; - let r = optimize_sharing_uniform(w, &roots, &few_states).unwrap(); - assert_eq!((r.stored, r.model_len), (ok.stored, ok.model_len), "w={w}"); - } - assert!(knapsack_failures > 0); -} - -/// `max_candidates` bounds only the width-state search. The canonical -/// construction and its uniform phase record their candidate count without -/// limiting it, as Lean does (its limits have no candidate count): 70,000 -/// distinct references, each used twice, are 70,000 candidates, above the -/// default of 2^16, and the compiler's construction shares them all. -#[test] -fn candidate_count_is_not_a_canonical_limit() { - let roots: Vec = (0..70_000u64) - .flat_map(|i| { - let r = Expr::reference(i, vec![0; 6]); - [r.clone(), r] - }) - .collect(); - assert!(70_000 > limits().max_candidates); - let r = canonical_sharing_tiered(ShareLayout::TagN, &roots, &limits()) - .expect("the candidate count does not limit the canonical construction"); - assert_eq!((r.sharing.len(), r.stats.w), (70_000, 1)); - assert_eq!(r.phase1.stats.candidates, 70_000); - let u = optimize_sharing_uniform(2, &roots, &limits()).unwrap(); - assert_eq!((u.stored.len(), u.stats.candidates), (70_000, 70_000)); - // The width-state search still enforces it. - match optimize_sharing(&roots, &limits()) { - Err(SharingError::ResourceExhausted(e)) => { - assert_eq!((e.resource, e.limit), (Resource::Candidates, 1 << 16)); - }, - other => panic!("width-state search: {other:?}"), - } -} - -/// The incremental evaluation of a growing dictionary equals a full -/// evaluation (`all_costs`) after every addition, costs and work count, for -/// additions in any order; the sparse materializer writes the same -/// expressions with the same work as `materialize`. -#[test] -fn incremental_costs_and_sparse_materialize_match_full_evaluation() { - use super::dict::{ - Evaluation, IncrementalCosts, LazyCosts, Materializer, ReadEdges, - all_costs, materialize, - }; - let mut rng = Rng(97); - let mut cases: Vec> = - par_cases().iter().map(|c| constant_info_root_exprs(&c.info)).collect(); - for _ in 0..60 { - cases.push(gen_search_roots(&mut rng)); - cases.push(gen_spines(&mut rng)); - } - let (mut steps, mut materialized, mut lazy_steps, mut late_steps) = - (0u64, 0u64, 0u64, 0u64); - for (i, roots) in cases.iter().enumerate() { - if roots.is_empty() { - continue; - } - let dag = SharingDag::from_expanded_roots(roots, &limits()).unwrap(); - let nodes = dag.nodes(); - let n = nodes.len(); - let own: Vec = nodes.iter().map(Node::own_len).collect(); - // A random sequence of distinct terms, in any order, with indices that - // cross the TagN width boundaries. - let mut terms: Vec = (0..n).collect(); - for k in (1..terms.len()).rev() { - terms.swap(k, rng.below(k as u64 + 1) as usize); - } - terms.truncate(1 + rng.below(n as u64) as usize); - let mut dict = FixedDictionary::new(); - let edges = ReadEdges::new(nodes); - let mut eval = IncrementalCosts::new( - nodes, - &own, - &edges, - Evaluation::new(nodes, &own, &dict), - ); - let base = Evaluation::new(nodes, &own, &dict); - let lazy_applies = LazyCosts::applies(&base); - let mut lazy = LazyCosts::new(nodes, &own, &edges, base, &dict); - // A second lazy evaluation started halfway, from the evaluation of the - // non-empty dictionary at that point (as the parallel per-prefix phase 3 - // starts every range but the first). - let mut late: Option> = None; - let all: Vec = (0..n).collect(); - let mut mat = Materializer::new(n); - let mut index = rng.below(20); - for (step, &t) in terms.iter().enumerate() { - let gap = if rng.pct(10) { 200 } else { 3 }; - index += 1 + rng.below(gap); - dict.insert(t, index); - eval.add(t, &dict); - let mut w = 0u64; - let full = all_costs(nodes, &own, &dict, &mut w); - assert_eq!(eval.costs(), &full[..], "case {i} step {step}"); - assert_eq!(eval.work(), w, "case {i} step {step}"); - if lazy_applies { - lazy.add(t, &dict); - assert_eq!(lazy.work(), w, "lazy case {i} step {step}"); - // Some steps prepare one target, others every term. - let targets: &[TermId] = - if step % 4 == 3 { &all } else { std::slice::from_ref(&t) }; - lazy.prepare(targets, &dict); - for &x in targets { - assert_eq!(lazy.costs()[x], full[x], "lazy case {i} step {step}"); - } - assert_eq!(lazy.work(), w, "lazy case {i} step {step}"); - if let Some(late) = late.as_mut() { - late.add(t, &dict); - late.prepare(targets, &dict); - for &x in targets { - assert_eq!(late.costs()[x], full[x]); - } - assert_eq!(late.work(), w, "late lazy case {i} step {step}"); - late_steps += 1; - } else if step >= terms.len() / 2 { - let start = Evaluation::new(nodes, &own, &dict); - late = Some(LazyCosts::new(nodes, &own, &edges, start, &dict)); - } - lazy_steps += 1; - } - steps += 1; - if step % 3 == 0 || step + 1 == terms.len() { - let mut targets = - vec![t, terms[rng.below(terms.len() as u64) as usize]]; - targets.push(rng.below(n as u64) as TermId); - targets.extend_from_slice(dag.roots()); - let (mut wa, mut wb) = (w, w); - let a = - materialize(nodes, &own, &dict, &full, &targets, &mut wa).unwrap(); - let b = mat.run(nodes, &own, &dict, &full, &targets, &mut wb).unwrap(); - assert_eq!(wa, wb, "case {i} step {step}"); - let bytes = |es: &[E]| { - let mut out = Vec::new(); - for e in es { - put_expr(e, &mut out); - } - out - }; - assert_eq!(bytes(&a), bytes(&b), "case {i} step {step}"); - materialized += 1; - } - } - } - eprintln!( - "incremental evaluation: {steps} steps, {materialized} materializations" - ); - assert!(steps > 1000 && materialized > 300 && lazy_steps > 1000); - assert!(late_steps > 300, "{late_steps}"); -} - -/// Replace one random leaf of `e` (a Share index or a variable) by a -/// different one, returning whether something changed. -fn mutate_leaf(e: &E, rng: &mut Rng, budget: &mut u64) -> E { - match e.as_ref() { - Expr::Share(k) if *budget == 0 => { - *budget = u64::MAX; - Arc::new(Expr::Share(k ^ (1 + rng.below(3)))) - }, - Expr::Var(n) if *budget == 0 => { - *budget = u64::MAX; - Arc::new(Expr::Var(n + 1)) - }, - Expr::Share(_) | Expr::Var(_) => { - *budget = budget.saturating_sub(1); - e.clone() - }, - _ => { - let kids: Vec = - e.children().into_iter().map(|c| mutate_leaf(c, rng, budget)).collect(); - Arc::new(e.with_children(&kids).unwrap()) - }, - } -} - -/// The re-expansion check without interning (`check_encoding`) accepts -/// exactly the encodings that `build_full` expands back to the DAG with the -/// table terms, and counts the input nodes `build_full` charges. -#[test] -fn encoding_check_matches_reexpansion() { - let mut rng = Rng(101); - let (mut valid, mut rejected) = (0, 0); - let unbounded = ExactSharingLimits::unbounded(); - let reference = |dag: &SharingDag, order: &[TermId], es: &[E], rs: &[E]| { - let mut m = Meter::new(&unbounded); - match SharingDag::build_full(rs, Some(es), &mut m) { - Ok((d, ids)) => (d == *dag - && ids - .iter() - .map(|x| x.unwrap_or(TermId::MAX)) - .eq(order.iter().copied())) - .then_some(m.stats.input_nodes), - Err(_) => None, - } - }; - for c in par_cases() { - let roots = constant_info_root_exprs(&c.info); - if roots.is_empty() { - continue; - } - let (_, t) = - normalize_constant_sharing_tiered(ShareLayout::TagN, &c, &limits()) - .unwrap(); - let u = - optimize_sharing_uniform(1 + rng.below(3), &roots, &limits()).unwrap(); - let dag = SharingDag::from_expanded_roots(&roots, &limits()).unwrap(); - for (order, es, rs) in [ - (&t.table_terms, &t.sharing, &t.roots), - (&u.table_terms, &u.sharing, &u.roots), - ] { - let fast = dag.check_encoding(order, es, rs); - assert!(fast.is_some()); - let len = - cost::exprs_len_with(es.len() as u64, es.iter().chain(rs), &tag4_len); - assert_eq!( - dag.check_and_measure(order, es, rs, &tag4_len), - fast.zip(len) - ); - assert_eq!(fast, reference(&dag, order, es, rs)); - valid += 1; - for _ in 0..4 { - let (mut es2, mut rs2) = (es.clone(), rs.clone()); - let pick = rng.below((es2.len() + rs2.len()) as u64) as usize; - let mut budget = rng.below(6); - let target = if pick < es2.len() { - &mut es2[pick] - } else { - &mut rs2[pick - es.len()] - }; - *target = mutate_leaf(target, &mut rng, &mut budget); - if rng.below(4) == 0 && es2.len() > 1 { - let i = rng.below(es2.len() as u64 - 1) as usize; - es2.swap(i, i + 1); - } - let fast = dag.check_encoding(order, &es2, &rs2); - let slow = reference(&dag, order, &es2, &rs2); - let fused = dag.check_and_measure(order, &es2, &rs2, &tag4_len); - let len = cost::exprs_len_with( - es2.len() as u64, - es2.iter().chain(&rs2), - &tag4_len, - ); - assert_eq!(fused, fast.zip(len)); - if fast.is_some() { - assert_eq!(fast, slow); - } else { - rejected += 1; - assert_eq!(slow, None); - } - } - } - } - eprintln!( - "encoding check: {valid} valid encodings, {rejected} mutations rejected" - ); - assert!(valid > 300 && rejected > 300); -} - -/// Phase 3 in one pass (orders closed under stored descendants) gives the -/// bytes, tables and lengths of the per-prefix phase 3, which the test -/// forces with `phase3_per_prefix`, sequentially and under parallel budgets -/// (the parallel per-prefix path starts a range from the evaluation of a -/// non-empty prefix), on the parallel cases and generated families. -#[test] -fn one_pass_phase3_matches_per_prefix() { - let mut rng = Rng(103); - let mut cases = par_cases(); - for _ in 0..80 { - let roots = gen_search_roots(&mut rng); - cases.push(wrap(&mut rng, roots)); - } - let per_prefix = ExactSharingLimits { phase3_per_prefix: true, ..limits() }; - let mut compared = 0; - for (i, c) in cases.iter().enumerate() { - let one = - normalize_constant_sharing_tiered(ShareLayout::TagN, c, &limits()); - let per = - normalize_constant_sharing_tiered(ShareLayout::TagN, c, &per_prefix); - match (one, per) { - (Ok((a, ra)), Ok((b, rb))) => { - assert_eq!(put(&a), put(&b), "case {i}"); - assert_eq!(ra.table_terms, rb.table_terms, "case {i}"); - assert_eq!(ra.stats.candidate_lengths, rb.stats.candidate_lengths); - assert_eq!(ra.stats.w, rb.stats.w, "case {i}"); - assert_eq!(rb.stats.per_prefix_candidates, 3, "case {i}"); - // Every budget splits the per-prefix tasks into ranges. - if i % 4 == 0 { - for (w, k, m) in PAR_BUDGETS { - let (pc, pr) = normalize_constant_sharing_tiered_par( - ShareLayout::TagN, - c, - &per_prefix, - par(w, k, m), - ) - .unwrap(); - assert_eq!(put(&pc), put(&b), "case {i} budget {w},{k},{m}"); - assert_eq!(pr, rb, "case {i} budget {w},{k},{m}"); - } - } - compared += 1; - }, - (a, b) => panic!( - "case {i}: one-pass ok {} per-prefix ok {}", - a.is_ok(), - b.is_ok() - ), - } - } - assert!(compared > 200); -} - -/// The checked mode (`full_check`) gives the default mode's bytes and -/// result, and the default mode's outcome under tight limits (the same -/// error, or the same success), on the parallel cases and generated -/// families, sequentially and under parallel budgets. -#[test] -fn full_check_matches_the_default_mode() { - let default = ExactSharingLimits::default(); - let checked = limits(); - assert!(!default.full_check && checked.full_check); - let mut rng = Rng(109); - let mut cases = par_cases(); - for _ in 0..60 { - let roots = gen_search_roots(&mut rng); - cases.push(wrap(&mut rng, roots)); - } - let tight_limits = - [(3, u64::MAX), (40, u64::MAX), (u64::MAX, 2_000), (60, 20_000)]; - let (mut same, mut both_fail) = (0, 0); - for (i, c) in cases.iter().enumerate() { - let a = normalize_constant_sharing_tiered(ShareLayout::TagN, c, &default); - let b = normalize_constant_sharing_tiered(ShareLayout::TagN, c, &checked); - let ((na, ra), (nb, rb)) = (a.unwrap(), b.unwrap()); - assert_eq!(put(&na), put(&nb), "case {i}"); - assert_eq!(ra, rb, "case {i}"); - same += 1; - let budget = PAR_BUDGETS[i % PAR_BUDGETS.len()]; - let pb = par(budget.0, budget.1, budget.2); - let (np, rp) = - normalize_constant_sharing_tiered_par(ShareLayout::TagN, c, &checked, pb) - .unwrap(); - assert_eq!((put(&np), &rp), (put(&na), &ra), "case {i} budget {budget:?}"); - for (max_states, max_work) in tight_limits { - let tight = |full_check| ExactSharingLimits { - max_states, - max_work, - full_check, - ..ExactSharingLimits::default() - }; - let a = - normalize_constant_sharing_tiered(ShareLayout::TagN, c, &tight(false)); - let b = - normalize_constant_sharing_tiered(ShareLayout::TagN, c, &tight(true)); - match (a, b) { - (Ok((na, ra)), Ok((nb, rb))) => { - assert_eq!((put(&na), ra), (put(&nb), rb), "case {i}"); - }, - (Err(e), Err(f)) => { - assert_eq!(e, f, "case {i}"); - both_fail += 1; - }, - (a, b) => panic!( - "case {i} states {max_states} work {max_work}: default ok {} vs \ - checked ok {}", - a.is_ok(), - b.is_ok() - ), - } - } - } - assert!(same > 200 && both_fail > 100, "{same} {both_fail}"); -} - -/// The checked mode fails on a discrepancy: a phase-1 dry run that differs -/// from the built expressions, and a one-pass phase-3 length that differs -/// from the per-prefix evaluation, are internal errors. -#[test] -fn full_check_detects_discrepancies() { - use super::tiered::{check_phase1, per_prefix_reference, per_prefix_tasks}; - use super::uniform::{DagPrep, optimize_uniform_with}; - let (nine, _) = nine_ref_fixture(); - let roots = constant_info_root_exprs(&nine.info); - let dag = SharingDag::from_expanded_roots(&roots, &limits()).unwrap(); - let prep = DagPrep::new(&dag); - let internal = |r: Result<(), SharingError>| matches!(r, Err(SharingError::Internal(m)) if m.starts_with("full check")); - let layout = ShareLayout::TagN; - for w in 1..=3 { - let (mut u, plan) = - optimize_uniform_with(w, &dag, &prep, &mut Meter::new(&limits())) - .unwrap(); - let (shares, real) = plan.shares(&dag, &u.table_terms, &limits()).unwrap(); - u.variable_len = real; - assert!(check_phase1(layout, &dag, &u, &plan, &shares).is_ok()); - let mut bad = shares.clone(); - bad.refs[0] += 1; - assert!(internal(check_phase1(layout, &dag, &u, &plan, &bad))); - let mut bad = shares.clone(); - bad.deps[0].push(u.table_terms[0]); - assert!(internal(check_phase1(layout, &dag, &u, &plan, &bad))); - let mut bad = shares.clone(); - bad.nodes += 1; - assert!(internal(check_phase1(layout, &dag, &u, &plan, &bad))); - let mut off = u.clone(); - off.variable_len += 1; - assert!(internal(check_phase1(layout, &dag, &off, &plan, &shares))); - // Phase 3: the per-prefix lengths of the phase-1 order, then one of - // them changed. - let order = &u.table_terms; - let lengths: Vec = per_prefix_tasks(layout, &dag, &prep, order, 1) - .into_iter() - .map(|t| t.unwrap().1) - .collect(); - let reference = - per_prefix_reference(layout, &dag, &prep, order, 2, &lengths); - assert!(reference.is_ok()); - let mut wrong = lengths.clone(); - wrong[0] = wrong[0].plus_u64(1); - let reference = per_prefix_reference(layout, &dag, &prep, order, 3, &wrong); - assert!(internal(reference.map(|_| ()))); - } -} - -/// The phase-1 dry run reports what the materialized phase-1 expressions -/// contain: the Shares of each entry in all expression trees, the entries -/// each entry shares in pre-order of first occurrence, the number of -/// expression nodes (the re-expansion check's input nodes) and the real -/// length; everything else in the result is the same. -#[test] -fn phase1_dry_run_matches_materialized_expressions() { - use super::tiered::share_indices; - use super::uniform::{DagPrep, optimize_uniform, optimize_uniform_with}; - let mut rng = Rng(107); - let mut cases: Vec> = - par_cases().iter().map(|c| constant_info_root_exprs(&c.info)).collect(); - for _ in 0..80 { - cases.push(gen_search_roots(&mut rng)); - cases.push(gen_spines(&mut rng)); - } - let mut compared = 0; - for (i, roots) in cases.iter().enumerate() { - if roots.is_empty() { - continue; - } - let dag = SharingDag::from_expanded_roots(roots, &limits()).unwrap(); - let prep = DagPrep::new(&dag); - for w in 1..=3 { - let full = optimize_uniform(w, &dag, &mut Meter::new(&limits())).unwrap(); - let (mut dry, plan) = - optimize_uniform_with(w, &dag, &prep, &mut Meter::new(&limits())) - .unwrap(); - let (shares, real) = - plan.shares(&dag, &dry.table_terms, &limits()).unwrap(); - dry.variable_len = real; - let order = &full.table_terms; - let mut refs = vec![0u64; order.len()]; - let mut deps: Vec> = Vec::new(); - let mut idx = Vec::new(); - for e in full.sharing.iter().chain(&full.roots) { - idx.clear(); - share_indices(e, &mut idx); - for &j in &idx { - refs[j as usize] += 1; - } - } - for e in &full.sharing { - idx.clear(); - share_indices(e, &mut idx); - let mut ds: Vec = Vec::new(); - for &j in &idx { - let d = order[j as usize]; - if !ds.contains(&d) { - ds.push(d); - } - } - deps.push(ds); - } - let visited = dag.check_encoding(order, &full.sharing, &full.roots); - assert_eq!(shares.refs, refs, "case {i} w {w}"); - assert_eq!(shares.deps, deps, "case {i} w {w}"); - assert_eq!(Some(shares.nodes), visited, "case {i} w {w}"); - assert_eq!(dry.variable_len, full.variable_len, "case {i} w {w}"); - assert_eq!( - (&dry.table_terms, dry.model_len, &dry.stored, &dry.stats), - (&full.table_terms, full.model_len, &full.stored, &full.stats), - "case {i} w {w}" - ); - assert!(dry.sharing.is_empty() && dry.roots.is_empty()); - compared += 1; - } - } - assert!(compared > 900); -} - -/// The per-prefix witness: an App spine `t` of `2 + extra` nodes over -/// `u = App(Var 0, Var 1)` (100 roots), `u` itself (20 roots) and eight -/// atoms used 500 times each. -fn per_prefix_witness(extra: u64) -> Vec { - let u = Expr::app(Expr::var(0), Expr::var(1)); - let mut t = u.clone(); - for i in 0..extra { - t = Expr::app(t, Expr::var(2 + i)); - } - let mut roots = vec![t; 100]; - roots.extend(vec![u; 20]); - for h in 0..8 { - roots.extend(vec![Expr::reference(100 + h, vec![0, 0]); 500]); - } - roots -} - -/// The per-prefix phase 3 is reachable. In the witness of 1,288 spine nodes -/// phase 1 at w = 2 stores `u`, but `t`'s phase-1 body is the whole -/// telescope: the cut at `u` (j = 1287, header `7c ff 00`) and the full -/// telescope (j = 1288, header `7c 00 01`) have the same length and the -/// byte-least header wins, so `t` does not reference `u`, and the Kahn order -/// places `t` before its stored descendant `u`. That candidate is not closed -/// under stored descendants (one pass would evaluate `t`'s entry with `u` -/// available at its later index) and runs per prefix. One spine node fewer -/// and the tie does not arise. The result is the bytes of the Lean reference -/// construction (`canonicalSharingTiered`), in both modes and under every -/// parallel budget; every width alone is a valid encoding. -#[test] -fn per_prefix_phase3_witness() { - use super::tiered::descendant_closed; - let l = ShareLayout::TagN; - let c = wrap(&mut Rng(113), per_prefix_witness(1287)); - let (n, r) = normalize_constant_sharing_tiered(l, &c, &limits()).unwrap(); - assert_eq!(r.stats.per_prefix_candidates, 1); - assert_eq!(r.stats.candidate_lengths, vec![(1, 7106), (2, 7106), (3, 7123)]); - assert_eq!((r.stats.w, r.sharing.len()), (1, 10)); - // The table entries, then the roots, as `put_expr` (Lean `serExpr`) - // writes them: the blake3 of Lean's `canonicalSharingTiered` output. - let mut bytes = Vec::new(); - for e in r.sharing.iter().chain(&r.roots) { - put_expr(e, &mut bytes); - } - assert_eq!( - (bytes.len(), blake3::hash(&bytes).to_hex().to_string()), - ( - 7105, - "3024b70893d7121520cac690c305129190affc0aa5bbc1cd4186123d9543147f".into() - ) - ); - // Each width alone: a valid encoding of the same constant; at w = 2 the - // order is not closed and phase 3 runs per prefix. - let dag = SharingDag::from_constant(&c, &limits()).unwrap(); - for (w, len) in [(1, 7106), (2, 7106), (3, 7123)] { - let (m, s) = - normalize_constant_sharing_tiered_at_width(l, &c, &limits(), w).unwrap(); - assert_eq!(s.stats.candidate_lengths, vec![(w, len)]); - assert_eq!(s.stats.per_prefix_candidates, u64::from(w == 2), "w={w}"); - assert_eq!(descendant_closed(dag.nodes(), &s.table_terms), w != 2); - assert_eq!(put(&unshared(&m)), put(&unshared(&c)), "w={w}"); - } - // The default mode and every parallel budget give the same bytes and - // result (the per-prefix candidate's tasks split by `materialize`). - let default = ExactSharingLimits::default(); - let (d, rd) = normalize_constant_sharing_tiered(l, &c, &default).unwrap(); - assert_eq!((put(&d), &rd), (put(&n), &r)); - for (w, k, m) in PAR_BUDGETS { - for lim in [&default, &limits()] { - let (p, rp) = - normalize_constant_sharing_tiered_par(l, &c, lim, par(w, k, m)) - .unwrap(); - assert_eq!((put(&p), &rp), (put(&n), &r), "budget {w},{k},{m}"); - } - } - // Control: one spine node fewer. - let r0 = - canonical_sharing_tiered(l, &per_prefix_witness(1286), &limits()).unwrap(); - assert_eq!(r0.stats.per_prefix_candidates, 0); -} - -/// `descendant_closed` against a direct check (every stored descendant of -/// every stored term comes before it), on random stored sets of generated -/// DAGs in random orders and in increasing-ID orders with a few swaps. -#[test] -fn descendant_closed_matches_brute_force() { - use super::tiered::descendant_closed; - let mut rng = Rng(127); - let (mut closed, mut open) = (0, 0); - for case in 0..600 { - let roots = gen_search_roots(&mut rng); - if roots.is_empty() { - continue; - } - let dag = SharingDag::from_expanded_roots(&roots, &limits()).unwrap(); - let nodes = dag.nodes(); - let n = nodes.len(); - let mut order: Vec = (0..n).filter(|_| rng.pct(40)).collect(); - if rng.pct(50) { - for k in (1..order.len()).rev() { - order.swap(k, rng.below(k as u64 + 1) as usize); - } - } else { - for _ in 0..rng.below(3) { - if order.len() > 1 { - let k = rng.below(order.len() as u64 - 1) as usize; - order.swap(k, k + 1); - } - } - } - let pos: HashMap = - order.iter().enumerate().map(|(i, &t)| (t, i)).collect(); - let brute = order.iter().enumerate().all(|(i, &t)| { - let mut stack: Vec = nodes[t].children().as_slice().to_vec(); - let mut seen = vec![false; n]; - while let Some(x) = stack.pop() { - if std::mem::replace(&mut seen[x], true) { - continue; - } - if pos.get(&x).is_some_and(|&p| p > i) { - return false; - } - stack.extend_from_slice(nodes[x].children().as_slice()); - } - true - }); - assert_eq!(descendant_closed(nodes, &order), brute, "case {case}"); - if brute { - closed += 1; - } else { - open += 1; - } - } - assert!(closed > 100 && open > 100, "{closed} closed, {open} open"); -} diff --git a/crates/ixon/src/sharing_exact/tests/family.rs b/crates/ixon/src/sharing_exact/tests/family.rs deleted file mode 100644 index 7fbc92dab..000000000 --- a/crates/ixon/src/sharing_exact/tests/family.rs +++ /dev/null @@ -1,278 +0,0 @@ -//! Independent product-family controls. The oracle evaluates full-DAG -//! uniform lengths and orders boolean membership vectors; it does not use -//! production component deltas, family_table, trim, knapsack or set_prec. - -use super::*; -use crate::sharing_exact::uniform::{DagPrep, optimize_family_at_test}; - -fn run(w: u64, dag: &SharingDag, k: usize) -> UniformSharingResult { - let lim = limits(); - let (mut out, plan) = optimize_family_at_test( - w, - dag, - &DagPrep::new(dag), - &mut Meter::new(&lim), - k, - ) - .unwrap(); - let built = plan.build(dag, &out.table_terms, &lim).unwrap(); - out.roots = built.roots; - out.sharing = built.entries; - out.variable_len = built.measured; - out -} - -fn flags(n: usize, set: &[TermId]) -> Vec { - (0..n).map(|t| set.contains(&t)).collect() -} - -fn merged(a: &[TermId], b: &[TermId]) -> Vec { - let mut out = [a, b].concat(); - out.sort_unstable(); - out -} - -fn body(dag: &SharingDag, w: u64, set: &[TermId]) -> i128 { - i128::from(uniform_set_len(dag, w, set)) - - i128::from(tag0_len(set.len() as u64)) -} - -fn oracle_family( - dag: &SharingDag, - w: u64, - cs: &[TermId], - members: &[TermId], - k: usize, -) -> Vec> { - if members.len() <= k { - assert!(members.len() <= 12); - return (0..1u64 << members.len()) - .map(|mask| { - members - .iter() - .enumerate() - .filter_map(|(j, &t)| (mask >> j & 1 != 0).then_some(t)) - .collect() - }) - .collect(); - } - let mut chain: Vec> = vec![Vec::new()]; - for q in 1..=members.len() { - let previous = chain.last().unwrap(); - let extensions: Vec<_> = members - .iter() - .filter(|&&t| !previous.contains(&t)) - .map(|t| merged(previous, &[*t])) - .collect(); - let next = extensions - .iter() - .filter(|s| s.len() == q && previous.iter().all(|t| s.contains(t))) - .min_by_key(|s| (body(dag, w, &merged(cs, s)), flags(dag.len(), s))) - .unwrap(); - chain.push(next.clone()); - } - chain -} - -/// Exhaust the actual succinct product on small cases, evaluating every -/// complete set with the full dictionary oracle, including the count header. -fn check_product( - dag: &SharingDag, - w: u64, - k: usize, - actual: &UniformSharingResult, -) -> (usize, usize) { - let cs = &actual.certain_stored; - let mut product = vec![cs.clone()]; - let mut negative = 0; - let mut tied = 0; - let slack = i128::from( - tag0_len((cs.len() + actual.uncertain.len()) as u64) - - tag0_len(cs.len() as u64), - ); - for (members, retained) in - actual.components.iter().zip(&actual.component_tables) - { - let family = oracle_family(dag, w, cs, members, k); - // Independent complete per-count rows, then the declared slack rule. - let mut rows = Vec::new(); - for q in 0..=members.len() { - let selected = family - .iter() - .filter(|s| s.len() == q) - .min_by_key(|s| (body(dag, w, &merged(cs, s)), flags(dag.len(), s))) - .unwrap(); - let delta = body(dag, w, &merged(cs, selected)) - body(dag, w, cs); - negative += usize::from(delta < 0); - tied += usize::from( - family - .iter() - .filter(|s| { - s.len() == q - && body(dag, w, &merged(cs, s)) - body(dag, w, cs) == delta - }) - .count() - > 1, - ); - rows.push((delta, selected.clone())); - } - let best = rows.iter().map(|r| r.0).min().unwrap(); - let expected: Vec<_> = rows - .into_iter() - .map(|r| { - (r.0 <= best + slack).then(|| (num_bigint::BigInt::from(r.0), r.1)) - }) - .collect(); - assert_eq!(retained, &expected, "retained per-count rows w={w} k={k}"); - product = product - .iter() - .flat_map(|p| family.iter().map(|s| merged(p, s))) - .collect(); - } - let chosen = product - .iter() - .min_by_key(|s| (uniform_set_len(dag, w, s), flags(dag.len(), s))) - .unwrap(); - tied += usize::from( - product - .iter() - .filter(|s| uniform_set_len(dag, w, s) == uniform_set_len(dag, w, chosen)) - .count() - > 1, - ); - assert_eq!( - (&actual.stored, actual.model_len), - (chosen, uniform_set_len(dag, w, chosen)), - "complete family product w={w} k={k}" - ); - (negative, tied) -} - -#[test] -fn mixed_products_match_full_dictionary_oracle() { - let mut rng = Rng(9137); - let (mut checked, mut mixed, mut negative, mut tied, mut bracket, mut lower) = - (0, 0, 0, 0, 0, 0); - for attempt in 0..4000 { - let mut roots = gen_search_roots(&mut rng); - let w = [1, 2, 3, 5][rng.below(4) as usize]; - if attempt % 2 == 0 { - // Large guaranteed-positive atoms supply a fixed CS prefix close to - // the 128-entry count boundary without enlarging the product family. - for j in 0..(120 + rng.below(8)) { - let atom = Expr::reference(10_000 + j, vec![0; 5]); - roots.extend([atom.clone(), atom.clone(), atom]); - } - } - // One independent uncertain singleton guarantees an exact factor at k=1. - let atom = Expr::reference(113, vec![0; (2 * w - 2) as usize]); - roots.extend([atom.clone(), atom]); - let dag = SharingDag::from_expanded_roots(&roots, &limits()).unwrap(); - let k = if attempt % 3 == 0 { 2 } else { 1 }; - let actual = run(w, &dag, k); - if actual.uncertain.len() > 10 || actual.components.is_empty() { - continue; - } - let is_mixed = actual.components.iter().any(|c| c.len() <= k) - && actual.components.iter().any(|c| c.len() > k); - if !is_mixed { - continue; - } - let (n, t) = check_product(&dag, w, k, &actual); - negative += n; - tied += t; - mixed += 1; - bracket += usize::from( - tag0_len(actual.certain_stored.len() as u64) - != tag0_len( - (actual.certain_stored.len() + actual.uncertain.len()) as u64, - ), - ); - lower += usize::from(actual.lower_bracket); - checked += 1; - if checked >= 120 { - break; - } - } - eprintln!( - "mixed product oracle: checked={checked} mixed={mixed} negative_rows={negative} tied_choices={tied} header_crossings={bracket} lower_bracket_winners={lower}" - ); - assert_eq!(checked, 120); - assert!( - mixed >= 20 && negative > 0 && tied > 0 && bracket >= 20 && lower > 0 - ); -} - -#[test] -fn actual_k16_mixed_product_matches_full_dictionary_oracle() { - let mut checked = 0; - for n in [17, 18, 24, 32] { - for w in [3, 5] { - let mut roots = Vec::new(); - let mut prefix = Expr::var(0); - for j in 0..n { - prefix = Expr::app(prefix, Expr::var(j % 5)); - roots.push(Expr::app(prefix.clone(), Expr::sort(1))); - } - roots.push(prefix); - let atom = Expr::reference(113, vec![0; (2 * w - 2) as usize]); - roots.extend([atom.clone(), atom]); - let dag = SharingDag::from_expanded_roots(&roots, &limits()).unwrap(); - let out = run(w, &dag, 16); - let sizes: Vec<_> = out.components.iter().map(Vec::len).collect(); - eprintln!("K16 mixed n={n} w={w}: components={sizes:?}"); - if !sizes.iter().any(|&m| m > 16) || !sizes.iter().any(|&m| m <= 16) { - continue; - } - let families: usize = sizes - .iter() - .map(|&m| if m <= 16 { 1usize << m } else { m + 1 }) - .product(); - assert!(families <= 4096 && sizes.iter().all(|&m| m > 16 || m <= 12)); - check_product(&dag, w, 16, &out); - checked += 1; - } - } - assert!(checked > 0, "fixture must cross the actual K16 gate"); - eprintln!("actual K16 mixed product oracle: {checked} complete products"); -} - -/// The historical fixed-seed 400 + 600 cases now also exercise the actual -/// production family entry. Stats intentionally differ under R3 precharges. -pub(super) fn compare_production( - w: u64, - roots: &[E], - old: &UniformSharingResult, -) { - let dag = SharingDag::from_expanded_roots(roots, &limits()).unwrap(); - let actual = run(w, &dag, 16); - assert_eq!(actual.components, old.components); - assert_eq!(actual.certain_stored, old.certain_stored); - let fallback = actual.components.iter().any(|c| c.len() > 16); - let semantic = |r: &UniformSharingResult| { - ( - r.stored.clone(), - r.model_len, - r.variable_len, - r.table_terms.clone(), - r.sharing.iter().map(|e| bytes_of(e)).collect::>(), - r.roots.iter().map(|e| bytes_of(e)).collect::>(), - ) - }; - let equal = semantic(&actual) == semantic(old); - assert!(equal || fallback, "all-exact production bytes/model/set differ"); - let old_rows = retained_component_counts(old); - let new_rows = retained_component_counts(&actual); - for (j, c) in actual.components.iter().enumerate() { - if c.len() <= 16 { - assert_eq!(old_rows[j], new_rows[j]); - } - } - eprintln!( - "K16 G2 width={w} fallback={fallback} equal={equal} old_states={} new_states={} old_work={} new_work={}", - old.stats.states_created, - actual.stats.states_created, - old.stats.work, - actual.stats.work - ); -} diff --git a/crates/ixon/src/sharing_exact/tests/wide.rs b/crates/ixon/src/sharing_exact/tests/wide.rs deleted file mode 100644 index d9716866c..000000000 --- a/crates/ixon/src/sharing_exact/tests/wide.rs +++ /dev/null @@ -1,114 +0,0 @@ -//! Phase-1 arithmetic past `u128`. The exact representation agrees with -//! the fast one, counters included, wherever the fast one succeeds, and -//! decides the inputs on which the fast one leaves its range: doubling DAGs -//! whose unshared costs exceed `2^127` while their sharing encodings are a -//! few hundred bytes. Lean computes these lengths with `Nat`; the byte -//! comparison with Lean is in the Lean/Rust sharing suite. - -use super::*; -use crate::sharing_exact::uniform::{ - DagPrep, Exact, Fast, optimize_family_at_test, optimize_family_in_test, -}; - -/// `x_(i+1) = step(x_i)` from `x_0 = Var(0)`, `depth` times. -fn tower(depth: usize, step: impl Fn(E) -> E) -> E { - (0..depth).fold(Expr::var(0), |x, _| step(x)) -} - -/// Fresh meters for one width, under the checked-mode limits. -fn phase1( - w: u64, - dag: &SharingDag, - prep: &DagPrep, -) -> Result { - let lim = limits(); - optimize_family_in_test::(w, dag, prep, &mut Meter::new(&lim), 16) - .map(|r| r.0) -} - -#[test] -fn exact_arithmetic_matches_fast_where_fast_succeeds() { - let mut rng = Rng(0x5EED_0B1C); - let mut checked = 0; - for seed in 0..240u64 { - let roots = if seed % 2 == 0 { - gen_search_roots(&mut rng) - } else { - constant_info_root_exprs(&gen_constant(seed, 2, 40, 5).info) - }; - let Ok(dag) = SharingDag::from_expanded_roots(&roots, &limits()) else { - continue; - }; - let prep = DagPrep::new(&dag); - for w in 1..=3 { - let fast = phase1::(w, &dag, &prep); - let exact = phase1::(w, &dag, &prep); - assert_eq!(fast, exact, "seed {seed} w={w}"); - checked += usize::from(fast.is_ok()); - } - } - assert!(checked > 600, "{checked} successful comparisons"); -} - -#[test] -fn exact_arithmetic_decides_doubling_dags_past_u128() { - let towers = [ - ("App(x, x) depth 127", tower(127, |x| Expr::app(x.clone(), x))), - ("App(x, x) depth 130", tower(130, |x| Expr::app(x.clone(), x))), - ("Lam(x, x) depth 130", tower(130, |x| Expr::lam(x.clone(), x))), - ("All(x, x) depth 130", tower(130, |x| Expr::all(x.clone(), x))), - ( - "two App towers depth 128", - Expr::app( - tower(128, |x| Expr::app(x.clone(), x)), - tower(128, |x| Expr::app(x.clone(), Expr::app(Expr::var(1), x))), - ), - ), - ]; - for (name, root) in towers { - let c = axiom(root, 1); - let dag = SharingDag::from_constant(&c, &limits()).unwrap(); - let prep = DagPrep::new(&dag); - let mut refused = 0; - for w in 1..=3 { - let fast = phase1::(w, &dag, &prep); - let exact = phase1::(w, &dag, &prep).unwrap(); - match fast { - Ok(fast) => assert_eq!(fast, exact, "{name} w={w}"), - Err(e) => { - assert_eq!( - e, - SharingError::FormatBound(FormatBound::LengthOverflow), - "{name} w={w}" - ); - refused += 1; - }, - } - // The production entry retries from the meter it was given: its - // result and counters are those of the exact run alone. - let lim = limits(); - let mut meter = Meter::new(&lim); - let (retried, _) = - optimize_family_at_test(w, &dag, &prep, &mut meter, 16).unwrap(); - assert_eq!(retried, exact, "{name} w={w}"); - assert_eq!(meter.stats, exact.stats, "{name} w={w}"); - assert_eq!(exact.unshared_len, None, "{name} w={w}"); - } - assert!(refused > 0, "{name}: the fast representation must overflow"); - // The whole construction succeeds, checked (`full_check`), and is - // idempotent. - let (out, _) = - normalize_constant_sharing_tiered(ShareLayout::TagN, &c, &limits()) - .unwrap(); - let bytes = roundtrip(&out); - let (again, _) = - normalize_constant_sharing_tiered(ShareLayout::TagN, &out, &limits()) - .unwrap(); - assert_eq!(roundtrip(&again), bytes, "{name}: not a fixed point"); - eprintln!( - "{name}: {} bytes, {} entries, fast refused at {refused} widths", - bytes.len(), - out.sharing.len() - ); - } -} diff --git a/crates/ixon/src/sharing_exact/tiered.rs b/crates/ixon/src/sharing_exact/tiered.rs deleted file mode 100644 index e97a75069..000000000 --- a/crates/ixon/src/sharing_exact/tiered.rs +++ /dev/null @@ -1,1408 +0,0 @@ -//! Tiered canonical sharing: uniform-width selection, slot allocation and -//! re-materialization under a Share layout. -//! -//! A rule-for-rule port of the Lean `Ix/Sharing/Exact/Tiered.lean`. -//! -//! The one layout, [`ShareLayout::TagN`], prices a Share at table index `i` by -//! the wire width of the TagN Share code ([`tagn_width`]: 1, 2, 3, 4, 5 or 9 -//! bytes, the width of `TagN::put(4, 0xB, i)`). The output is written with -//! that code, so its price is its serialized length, which is checked. -//! -//! **Width selection.** Phase 1 runs at each uniform width `w` in 1, 2, 3, -//! each result is carried through phases 2 and 3, and the construction -//! returns the candidate with the fewest final layout bytes; ties go to the -//! lower `w`, then `set_prec` on the stored set. Every limit and every -//! length check of phases 1-3 applies to every candidate, and an error at -//! any width fails the call (with the lowest failing width's error); the -//! self-checks of the built expressions apply to the returned candidate -//! (see "Checks"). Each candidate is exact per phase as described below; -//! the final choice is the real-byte minimum over the three candidates, not -//! a global optimum. One candidate is `tiered_at` (Lean `tieredAtWidth`). -//! -//! **Phase 1 (selection).** The stored set is the least uniform-`w` minimum -//! over the fixed K16 initial-component families. Phase 2 reads only -//! what the phase-1 expressions contain, so they are not built: the dry run -//! (`UniformPlan::shares`) reports it from the same decisions. -//! -//! **Phase 2 (slot allocation).** From the phase-1 output, `ref(t)` counts -//! the `Share(t)` in its entries and roots; `t` depends on `u` when `t`'s -//! phase-1 body contains `Share(u)`. The first tier is a maximum-`ref` set -//! of at most 8 stored terms closed under dependencies (ties: order stored -//! terms by `ref` descending then ID ascending, and prefer the set that -//! contains the first term where two sets differ), found by branch and -//! bound. The table is the first tier in pinned priority order, then the -//! rest in the Kahn priority order: repeatedly the available entry (every -//! entry its phase-1 body references already placed) with the largest -//! `ref`, ties by the smaller ID. If that order's reference cost -//! `sum ref(t) * width_at(index t)` exceeds the phase-1 order's, the phase-1 -//! order is kept. The final order is checked to respect the dependencies. -//! -//! **Phase 3 (re-materialization).** Every entry is re-encoded with `C_M` -//! under the entries before it, priced by `width_at(index)`, and the roots -//! under all entries; byte-least ties. The result is checked to be no -//! longer (in layout bytes) than phase 1. The selection needs only the -//! lengths, so only the returned candidate's expressions are built -//! ([`finish`]). -//! -//! When the order is closed under stored descendants (every stored -//! descendant of an entry precedes it; `descendant_closed` decides it in one -//! pass), the cost and choice of every term below entry `j` are the same -//! under the prefix `order[..j]` as under the whole table (Lean -//! `gCost_local`), so one evaluation of the whole table gives every task: -//! entry `j` with its own Share hidden, the roots as they are (the one-pass -//! phase 3). Every order of Init and Mathlib is closed. An order that is not -//! is evaluated per prefix (`per_prefix_tasks`): one evaluation of each -//! prefix, maintained from one prefix to the next. Such an order needs an -//! entry whose phase-1 body does not reference a stored descendant, which -//! takes a tie between a telescope cut at that descendant and a longer -//! telescope, so a telescope spine of at least 1,032 nodes (the 3-byte -//! header rung). -//! -//! # Checks -//! -//! In the default mode (the compiler's), every candidate (`tiered_at`) is -//! checked, in this order, for: -//! -//! 1. phase 1: the telescope-spine bound, the length domain, `states`, -//! `work`, `knapsack_cells`, `output_bytes` (on the model length) and the -//! model length against the full evaluation; -//! 2. the phase-1 dry run: the consistency of the decisions, `input_nodes` -//! on the expression nodes the phase-1 build would allocate (what the -//! re-expansion check of the built expressions charges) and the domain of -//! the real length; -//! 3. phase 2: `states` of the first-tier search and the dependency order; -//! 4. phase 3: `work` of the evaluations (and, per prefix, of the -//! decisions), the domain of the length, the length against phase 1, -//! `input_nodes` on the expression nodes the build allocates (exact per -//! prefix; in one pass bounded by twice the length and counted exactly -//! only when that bound exceeds the limit) and `output_bytes`. -//! -//! The returned candidate alone ([`finish`]) is then checked for: -//! -//! 5. the one-pass decisions and their `work`, added to the candidate's; -//! 6. the build of its expressions; -//! 7. their layout price against the evaluated length; -//! 8. their re-expansion to the DAG and the stored terms, with as many -//! expression nodes as counted; -//! 9. their serialized length (`put_expr`) against the evaluated length. -//! -//! The phase-1 expressions are never built, and checks 5-9 do not run for -//! the two other candidates; their limits are checked with the same counts -//! as the returned candidate's, except the work of check 5. A wrong length -//! of another candidate would change the selection without an error; the -//! checked mode exists to show that this does not happen. -//! -//! With [`ExactSharingLimits::full_check`] (the checked mode) every -//! candidate also builds its phase-1 expressions from the same decisions, -//! checks them as `optimize_uniform` does (re-expansion, real length) and -//! compares them with the dry run (the Shares of each entry, the entries -//! each entry shares, the expression nodes, the real length and the layout -//! price); a one-pass candidate is also evaluated per prefix and its task -//! lengths and decisions are compared; and checks 5-9 run for every -//! candidate, the returned one first (the work of check 5 is charged to the -//! returned candidate only, as in the default mode, so every limit has the -//! outcome it has there). The output and the result equal the default -//! mode's; any discrepancy is an internal error. - -use std::sync::Arc; - -use rustc_hash::FxHashMap; - -use super::cost::{Len, exprs_len_with, tag0_len}; -use super::dag::{Node, SharingDag, TermId, ix}; -use super::dict::{ - Evaluation, Hide, IncrementalCosts, Indices, LazyCosts, Materializer, Plan, - PlanShares, Widths, eval_node, -}; -use super::prof::{self, Phase}; -use super::uniform::{ - DagPrep, UniformPlan, UniformSharingResult, optimize_family_with, - pinned_order, set_prec, -}; -use super::{ - ExactSharingLimits, FormatBound, Meter, NormalizeBytesError, Parallelism, - Resource, ResourceExhausted, SharingError, constant_fixed_len, - constant_info_root_exprs, rebuild_constant_info, -}; -use crate::constant::Constant; -use crate::expr::Expr; -use crate::serialize::put_expr; -use crate::tag::TagN; - -fn internal(msg: impl Into) -> SharingError { - SharingError::Internal(msg.into()) -} - -fn overflow() -> SharingError { - SharingError::FormatBound(FormatBound::LengthOverflow) -} - -fn len64(n: usize) -> u64 { - u64::try_from(n).unwrap_or(u64::MAX) -} - -/// End (exclusive) of the 1-byte TagN Share rung (the Share flag is 4 bits, -/// so the payload has 4 bits; `TagN::end1(4)`). -pub const TAGN_RUNG1_END: u64 = TagN::end1(4); -/// End of the 2-byte rung (2 + 8 value bits; `TagN::end2(4)`). -pub const TAGN_RUNG2_END: u64 = TagN::end2(4); -/// End of the 3-byte rung (2 following bytes; `TagN::end3(4)`). -pub const TAGN_RUNG3_END: u64 = TagN::end3(4); -/// End of the 4-byte rung (3 following bytes; `TagN::end4(4)`). -pub const TAGN_RUNG4_END: u64 = TagN::end4(4); -/// End of the 5-byte rung (4 following bytes; `TagN::end5(4)`); every larger -/// `u64` index is in the 9-byte rung. -pub const TAGN_RUNG5_END: u64 = TagN::end5(4); - -/// Byte width of the TagN Share at index `i` (`TagN::byte_width(4, i)`: -/// 1, 2, 3, 4, 5 or 9), as Lean's `tagNWidth`. -pub fn tagn_width(i: u64) -> u64 { - len64(TagN::byte_width(4, i)) -} - -/// The Share width layout: the TagN Share code, the only wire code. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum ShareLayout { - /// The TagN Share code ([`tagn_width`]). - TagN, -} - -impl ShareLayout { - /// Width of the Share at table index `i`. - pub fn width_at(self, i: u64) -> u64 { - match self { - ShareLayout::TagN => tagn_width(i), - } - } - - /// The layout of the wire Share code (TagN). - pub fn wire() -> ShareLayout { - ShareLayout::TagN - } -} - -/// Variable length of an encoding with every Share priced by `layout`. -pub fn layout_bytes( - layout: ShareLayout, - sharing: &[Arc], - roots: &[Arc], -) -> Option { - let price = |i: u64| layout.width_at(i); - exprs_len_with(len64(sharing.len()), sharing.iter().chain(roots), &price) -} - -/// All `Share` indices of an expression, with multiplicity, in left-to-right -/// pre-order. -pub(crate) fn share_indices(e: &Expr, out: &mut Vec) { - let mut stack: Vec<&Expr> = vec![e]; - while let Some(x) = stack.pop() { - if let Expr::Share(i) = x { - out.push(*i); - } - for c in super::cost::expr_children(x).into_iter().rev().flatten() { - stack.push(c.as_ref()); - } - } -} - -/// Real table indices priced by a layout. -struct LayoutIndex { - index: Vec>, - layout: ShareLayout, -} - -impl Widths for LayoutIndex { - fn width(&self, t: TermId) -> Option { - self.index[ix(t)].map(|i| self.layout.width_at(i)) - } -} - -impl Indices for LayoutIndex { - fn index(&self, t: TermId) -> Option { - self.index[ix(t)] - } -} - -// --------------------------------------------------------------------------- -// First-tier allocation -// --------------------------------------------------------------------------- - -/// Whether every term of `order` comes after all its dependencies (Lean -/// `respectsDeps`). -fn respects_deps( - order: &[TermId], - deps: &FxHashMap>, -) -> bool { - let pos: FxHashMap = - order.iter().enumerate().map(|(i, &t)| (t, i)).collect(); - order.iter().enumerate().all(|(i, t)| { - deps - .get(t) - .is_none_or(|ds| ds.iter().all(|d| pos.get(d).is_some_and(|&p| p < i))) - }) -} - -/// The Kahn priority order of `rest` (Lean `kahnOrder`): repeatedly place -/// the available term (every dependency of it in `rest` already placed) of -/// the largest weight, ties by the smaller ID. Terms left over, which -/// acyclic dependencies never leave, follow in priority order. -pub(crate) fn kahn_order( - weight: &FxHashMap, - deps: &FxHashMap>, - rest: &[TermId], -) -> Vec { - let w = |t: TermId| weight.get(&t).copied().unwrap_or(0); - let mut sorted = rest.to_vec(); - sorted.sort_by(|&a, &b| w(b).cmp(&w(a)).then(a.cmp(&b))); - let m = sorted.len(); - let rank: FxHashMap = - sorted.iter().enumerate().map(|(i, &t)| (t, i)).collect(); - // A repeated dependency is counted once per repetition in `pend` and - // listed as often in `users`, so it is released as often as it is counted: - // repetitions do not change the order, and no dedup (quadratic in an - // entry's dependencies) is needed. - let mut pend = vec![0usize; m]; - let mut users: Vec> = vec![Vec::new(); m]; - for (i, t) in sorted.iter().enumerate() { - for d in deps.get(t).into_iter().flatten() { - if let Some(&r) = rank.get(d) { - pend[i] += 1; - users[r].push(i); - } - } - } - let mut ready: std::collections::BTreeSet = - (0..m).filter(|&i| pend[i] == 0).collect(); - let mut placed = vec![false; m]; - let mut out = Vec::with_capacity(m); - while let Some(r) = ready.pop_first() { - placed[r] = true; - out.push(sorted[r]); - for &u in &users[r] { - pend[u] -= 1; - if pend[u] == 0 { - ready.insert(u); - } - } - } - out.extend((0..m).filter(|&i| !placed[i]).map(|i| sorted[i])); - out -} - -/// Maximum-weight dependency-closed set of at most `cap` stored terms, with -/// the tie order of the module docs. `deps[t]` are the terms `t` -/// references. Returns the set (ascending) and the states visited. -pub(crate) fn first_tier( - stored: &[TermId], - weight: &FxHashMap, - deps: &FxHashMap>, - cap: usize, - limits: &ExactSharingLimits, -) -> Result<(Vec, u64), SharingError> { - let w = |t: TermId| weight.get(&t).copied().unwrap_or(0); - let mut items: Vec = stored.to_vec(); - items.sort_by(|&a, &b| w(b).cmp(&w(a)).then(a.cmp(&b))); - // Closures with early cutoff above `cap`, with the same pop budget as - // the reference (`4 * |stored| + 4` pops). - let mut closure: FxHashMap> = FxHashMap::default(); - let fuel = stored.len().saturating_mul(4).saturating_add(4); - // The visited set never exceeds `cap + 1` terms (the search stops there), - // so it is a short vector. - let mut seen: Vec = Vec::new(); - let mut stack: Vec = Vec::new(); - for &t in stored { - seen.clear(); - stack.clear(); - stack.push(t); - let mut ok = true; - for _ in 0..fuel { - let Some(u) = stack.pop() else { break }; - if seen.contains(&u) { - continue; - } - seen.push(u); - if seen.len() > cap { - ok = false; - break; - } - if let Some(ds) = deps.get(&u) { - stack.extend_from_slice(ds); - } - } - if ok { - let mut cl: Vec = seen.clone(); - cl.sort_unstable(); - closure.insert(t, cl); - } - } - // Depth-first branch and bound, "include" before "exclude"; the first - // maximum found wins ties, so later subtrees are pruned when their bound - // does not exceed the best weight. - // `excluded` holds the item positions decided "exclude" on the path, as a - // bit set (a term is excluded exactly when its item position is). - struct Frame { - pos: usize, - cur: u64, - in_f: Vec, - excluded: Vec, - } - let item_pos: FxHashMap = - items.iter().enumerate().map(|(i, &t)| (t, i)).collect(); - let is_excluded = |excluded: &[u64], u: &TermId| { - item_pos.get(u).is_some_and(|&i| { - excluded.get(i / 64).is_some_and(|w| w >> (i % 64) & 1 == 1) - }) - }; - let mut best: Option<(u64, Vec)> = None; - let mut states: u64 = 0; - let mut stack = - vec![Frame { pos: 0, cur: 0, in_f: Vec::new(), excluded: Vec::new() }]; - while let Some(Frame { pos, cur, in_f, excluded }) = stack.pop() { - states += 1; - if states > limits.max_states { - return Err(SharingError::ResourceExhausted(ResourceExhausted { - resource: Resource::States, - limit: limits.max_states, - })); - } - let room = cap.saturating_sub(in_f.len()); - let mut bound = cur; - let mut taken = 0usize; - for &t in &items[pos.min(items.len())..] { - if taken >= room { - break; - } - if in_f.contains(&t) { - continue; - } - bound = bound.saturating_add(w(t)); - taken += 1; - } - if let Some((b, _)) = &best - && bound <= *b - { - continue; - } - if pos >= items.len() { - if best.as_ref().is_none_or(|(b, _)| cur > *b) { - best = Some((cur, in_f)); - } - continue; - } - let t = items[pos]; - if in_f.contains(&t) { - stack.push(Frame { pos: pos + 1, cur, in_f, excluded }); - continue; - } - let mut exclude = excluded.clone(); - if exclude.len() <= pos / 64 { - exclude.resize(pos / 64 + 1, 0); - } - exclude[pos / 64] |= 1 << (pos % 64); - let mut include: Option = None; - if let Some(cl) = closure.get(&t) { - let new: Vec = - cl.iter().copied().filter(|u| !in_f.contains(u)).collect(); - if !cl.iter().any(|u| is_excluded(&excluded, u)) - && in_f.len() + new.len() <= cap - { - let add = new.iter().fold(0u64, |acc, &u| acc.saturating_add(w(u))); - let mut in2 = in_f.clone(); - in2.extend_from_slice(&new); - include = Some(Frame { - pos: pos + 1, - cur: cur.saturating_add(add), - in_f: in2, - excluded, - }); - } - } - stack.push(Frame { pos: pos + 1, cur, in_f, excluded: exclude }); - if let Some(f) = include { - stack.push(f); - } - } - match best { - Some((_, mut s)) => { - s.sort_unstable(); - Ok((s, states)) - }, - None => Ok((Vec::new(), states)), - } -} - -/// Whether every stored term of `order` comes after all its stored -/// descendants (the order is closed under stored descendants). One pass in -/// increasing ID: the frontier of a term is the largest position among its -/// nearest stored descendants-or-self, and every stored term's children's -/// frontiers must be before it (then, by induction, every stored descendant -/// is). -pub(crate) fn descendant_closed(nodes: &[Node], order: &[TermId]) -> bool { - let n = nodes.len(); - let mut pos: Vec = vec![u64::MAX; n]; - for (i, &t) in order.iter().enumerate() { - pos[ix(t)] = len64(i); - } - // `frontier[x] = 0` for none, else the position plus one. - let mut frontier: Vec = vec![0; n]; - for x in 0..n { - let below = nodes[x] - .children() - .as_slice() - .iter() - .map(|&c| frontier[ix(c)]) - .max() - .unwrap_or(0); - if pos[x] == u64::MAX { - frontier[x] = below; - } else { - if below > pos[x] { - return false; - } - frontier[x] = pos[x] + 1; - } - } - true -} - -/// The phase-3 decisions of every task of a candidate whose order is closed -/// under stored descendants, from the evaluation `costs` of the full -/// dictionary `dict`: entry `j` under `dict` with its own Share hidden, the -/// roots under `dict`. They equal the per-prefix decisions: the choice and -/// cost of a term read only the dictionary at and below it, and below entry -/// `j` the prefix `order[..j]` and the full dictionary agree (every stored -/// descendant is in the prefix, at the same index; the entry itself is -/// absent from one and hidden in the other). -#[allow(clippy::too_many_arguments)] -fn one_pass_plans( - dag: &SharingDag, - own: &[Len], - order: &[TermId], - dict: &LayoutIndex, - costs: &[Len], - mat: &mut Materializer, - work: &mut u64, -) -> Result, SharingError> { - let nodes = dag.nodes(); - let mut plans = Vec::with_capacity(order.len() + 1); - for t in order { - let hide = Hide { inner: dict, hidden: *t }; - plans.push(mat.decide( - nodes, - own, - &hide, - costs, - std::slice::from_ref(t), - work, - )?); - } - plans.push(mat.decide(nodes, own, dict, costs, dag.roots(), work)?); - Ok(plans) -} - -/// The phase-3 evaluation of the growing dictionary: [`LazyCosts`] when its -/// work argument applies, otherwise [`IncrementalCosts`]. Both give the -/// costs of the descendants of prepared targets and the work count of a -/// full evaluation of the current dictionary. -enum Phase3Eval<'a> { - Lazy(LazyCosts<'a>), - Eager(IncrementalCosts<'a>), -} - -impl Phase3Eval<'_> { - fn add(&mut self, t: TermId, widths: &W) { - match self { - Phase3Eval::Lazy(e) => e.add(t, widths), - Phase3Eval::Eager(e) => e.add(t, widths), - } - } - - fn prepare(&mut self, targets: &[TermId], widths: &W) { - if let Phase3Eval::Lazy(e) = self { - e.prepare(targets, widths); - } - } - - fn costs(&self) -> &[Len] { - match self { - Phase3Eval::Lazy(e) => e.costs(), - Phase3Eval::Eager(e) => e.costs(), - } - } - - fn work(&self) -> u64 { - match self { - Phase3Eval::Lazy(e) => e.work(), - Phase3Eval::Eager(e) => e.work(), - } - } -} - -/// One task of the per-prefix phase 3: its decisions, its length (entry -/// `j`'s inline cost under the entries before it, or the roots' cost under -/// all entries) and its work (a full evaluation of its prefix plus its -/// decisions). -pub(crate) type Task = Result<(Plan, Len, u64), SharingError>; - -/// The per-prefix phase 3 of `order` (module docs). Task `j < k` is entry -/// `j`, task `k` the roots; each depends only on the prefix `order[..j]`, so -/// the tasks run in up to `budget` independent ranges (see -/// `Parallelism::materialize`) and come back in task order. Each range -/// starts from the evaluation of its first prefix (`C_0` for the empty one) -/// and maintains it from one prefix to the next (`LazyCosts`, or -/// `IncrementalCosts` when its work argument does not apply), which yields -/// the same costs for the materialized terms and the same work count as -/// evaluating every prefix from scratch; the decisions -/// (`Materializer::decide`) are those of `materialize`, with its work. -pub(crate) fn per_prefix_tasks( - layout: ShareLayout, - dag: &SharingDag, - prep: &DagPrep, - order: &[TermId], - budget: usize, -) -> Vec { - let nodes = dag.nodes(); - let n = nodes.len(); - let own = &prep.own; - let tasks = |range: std::ops::Range| { - let p = prof::scope(Phase::RematerializeCosts); - let mut dict = LayoutIndex { index: vec![None; n], layout }; - for (i, &t) in order[..range.start].iter().enumerate() { - dict.index[ix(t)] = Some(len64(i)); - } - // The evaluation of the empty prefix is `C_0`, the same for every - // candidate. - let start = if range.start == 0 { - prep.base.clone() - } else { - Evaluation::new(nodes, own, &dict) - }; - let mut eval = if LazyCosts::applies(&prep.base) { - Phase3Eval::Lazy(LazyCosts::new(nodes, own, &prep.edges, start, &dict)) - } else { - Phase3Eval::Eager(IncrementalCosts::new(nodes, own, &prep.edges, start)) - }; - let mut mat = Materializer::new(n); - drop(p); - let mut out: Vec = Vec::with_capacity(range.len()); - for j in range.clone() { - if j > range.start { - let p = prof::scope(Phase::RematerializeCosts); - let t = order[j - 1]; - dict.index[ix(t)] = Some(len64(j - 1)); - eval.add(t, &dict); - drop(p); - } - let target = order.get(j).map(std::slice::from_ref); - let p = prof::scope(Phase::RematerializeCosts); - eval.prepare(target.unwrap_or(dag.roots()), &dict); - drop(p); - let _p = prof::scope(Phase::RematerializeDecide); - let mut w = eval.work(); - let costs = eval.costs(); - if let Some(&t) = order.get(j) { - let r = mat.decide(nodes, own, &dict, costs, &[t], &mut w); - out.push(r.map(|plan| (plan, costs[ix(t)], w))); - } else { - let mut c = Len::ZERO; - for &r in dag.roots() { - c = c.plus(costs[ix(r)]); - } - let r = mat.decide(nodes, own, &dict, costs, dag.roots(), &mut w); - out.push(r.map(|plan| (plan, c, w))); - } - } - out - }; - super::par::map_ranges(order.len() + 1, budget, tasks) -} - -// --------------------------------------------------------------------------- -// Result and driver -// --------------------------------------------------------------------------- - -/// Statistics of the tiered construction. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct TieredStats { - pub layout: ShareLayout, - /// Terms with in-degree >= 2 and unshared length >= 2. - pub candidate_count: u64, - /// Phase-1 uniform width of the returned candidate (the winning width). - pub w: u64, - /// Final layout length of every candidate run, as `(w, bytes)` in - /// increasing `w` (one entry for a single candidate, `tiered_at`). - pub candidate_lengths: Vec<(u64, u64)>, - /// Per candidate run, in increasing `w`: `(w, phase-1 uniform-model - /// length, states_created, work)`, the last two the totals charged to that - /// candidate (for the returned candidate including the work of its - /// one-pass decisions, which only it makes). Diagnostic: the counts that - /// the limits act on. - pub candidate_meters: Vec<(u64, u64, u64, u64)>, - /// Candidates whose phase 3 ran per prefix (their order is not closed - /// under stored descendants, or `phase3_per_prefix`); the others ran in - /// one pass. - pub per_prefix_candidates: u64, - /// Phase-1 uniform-model length. - pub phase1_model_bytes: u64, - /// Phase-1 output priced by the layout (its own order). - pub phase1_layout_bytes: u64, - /// First-tier search states. - pub slot_states: u64, - pub first_tier: Vec, - /// The guard kept the phase-1 order. - pub kept_phase1_order: bool, - /// Reference cost `sum ref * width_at` of the phase-1 and final orders. - pub phase1_ref_cost: u128, - pub final_ref_cost: u128, - /// Final length priced by the layout. - pub phase3_layout_bytes: u64, - /// `phase1_layout_bytes - phase3_layout_bytes`. - pub savings: u64, -} - -/// Tiered result. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct TieredSharingResult { - pub roots: Vec>, - pub sharing: Vec>, - pub table_terms: Vec, - /// Variable length priced by the layout. - pub model_len: u64, - /// Real variable length: the table count and the entries and roots as - /// the wire codec (`put_expr`) writes them, which the construction - /// serializes and compares with `model_len`. - pub variable_len: u64, - pub unshared_len: Option, - /// The phase-1 result of the returned candidate's width. Its expressions - /// are not built: `roots` and `sharing` are empty, and `variable_len` is - /// the real length the dry run computed (the model with every Share priced - /// at its TagN width). - pub phase1: UniformSharingResult, - pub stats: TieredStats, -} - -/// The tiered canonical construction on a DAG (Lean -/// `canonicalTieredCore`): phases 1-3 at each phase-1 width 1, 2 and 3, -/// returning the candidate with the fewest final layout bytes; ties go to -/// the lower width, then `set_prec` on the stored set. Each candidate runs -/// under its own meter with the same limits, and an error at any width -/// fails the whole call; the returned candidate (with -/// [`ExactSharingLimits::full_check`], every candidate) is built and checked -/// (module docs, "Checks"). `par` sets the thread budgets ([`Parallelism`]). -pub(crate) fn tiered( - layout: ShareLayout, - dag: &SharingDag, - limits: &ExactSharingLimits, - par: Parallelism, -) -> Result { - // The width-independent tables, shared by the three candidates. - let prep = DagPrep::new(dag); - let run = |w: u64| { - let mut meter = Meter::with_parallelism(limits, par); - tiered_at(layout, dag, &prep, &mut meter, w) - }; - let candidates: Vec> = if par.widths <= 1 { - // The sequential reference stops at the first failing width. - let mut v = Vec::with_capacity(3); - for w in 1..=3 { - v.push(Ok(run(w)?)); - } - v - } else { - super::par::map_ranges(3, par.widths, |r| { - r.map(|i| run(len64(i) + 1)).collect() - }) - }; - finish_candidates(layout, dag, &prep, limits, candidates) -} - -pub(super) fn finish_candidates( - layout: ShareLayout, - dag: &SharingDag, - prep: &DagPrep, - limits: &ExactSharingLimits, - candidates: Vec>, -) -> Result { - let mut built: Vec = Vec::with_capacity(3); - let mut best: Option = None; - let mut lengths = Vec::with_capacity(3); - let mut meters = Vec::with_capacity(3); - let mut per_prefix = 0; - for (w, c) in (1..=3).zip(candidates) { - // In width order: the error of the lowest failing width, as above. - let c = c?; - per_prefix += u64::from(matches!(c.phase3, Phase3::Plans(_))); - lengths.push((w, c.result.stats.phase3_layout_bytes)); - meters.extend_from_slice(&c.result.stats.candidate_meters); - if best.is_none_or(|b| tiered_better(&c.result, &built[b].result)) { - best = Some(built.len()); - } - built.push(c); - } - let best_ix = best.ok_or_else(|| internal("no tiered candidate"))?; - // The returned candidate first, so that its errors are the default - // mode's; then, in the checked mode, the others. - let mut rest: Vec = Vec::with_capacity(2); - let mut chosen: Option = None; - for (i, c) in built.into_iter().enumerate() { - if i == best_ix { - chosen = Some(c); - } else { - rest.push(c); - } - } - let chosen = chosen.ok_or_else(|| internal("no tiered candidate"))?; - let mut best = finish(layout, dag, prep, limits, chosen, true)?; - if limits.full_check { - for c in rest { - finish(layout, dag, prep, limits, c, false)?; - } - } - // The returned candidate's meter includes the work charged in `finish`. - if let (Some(m), Some(&f)) = - (meters.get_mut(best_ix), best.stats.candidate_meters.first()) - { - *m = f; - } - best.stats.candidate_lengths = lengths; - best.stats.candidate_meters = meters; - best.stats.per_prefix_candidates = per_prefix; - Ok(best) -} - -/// A candidate before its expressions are built: the result with empty -/// `roots` and `sharing`, and how its phase-3 decisions are obtained. -/// -/// Only the returned candidate is built ([`finish`]; with -/// [`ExactSharingLimits::full_check`], every candidate): the selection reads -/// the final length, the width and the stored set, which phase 3 computes -/// from the costs. Every limit of a candidate is checked in [`tiered_at`], -/// with the counts of the build where it allocates (the input nodes of the -/// re-expansion check are the expression nodes the build allocates) and the -/// output length that the checks of [`finish`] prove equal to the evaluated -/// one; only the work of the one-pass decisions, which only [`finish`] -/// makes, is charged to the returned candidate alone (module docs, -/// "Checks"). -pub(crate) struct Candidate { - result: TieredSharingResult, - phase3: Phase3, - /// With [`ExactSharingLimits::full_check`], for a one-pass candidate: the - /// decisions of the per-prefix phase 3, which [`finish`] compares with - /// the one-pass decisions. - reference: Option>, -} - -/// How a candidate's phase-3 decisions are obtained. -enum Phase3 { - /// The order is closed under stored descendants: every task is decided - /// under the full dictionary (with the entry's own Share hidden), whose - /// evaluation `costs` is kept. - OnePass { costs: Vec }, - /// The per-prefix decisions of every task. - Plans(Vec), -} - -/// Build the expressions of a candidate and check them: the layout price -/// against the evaluation, the re-expansion (same DAG, entries expanding to -/// the stored terms, as many expression nodes as counted) and the -/// serialized length. With `charge` the work of the one-pass decisions is -/// added to the candidate's and checked against `max_work` (the returned -/// candidate); without (another candidate, checked mode) it is not charged. -fn finish( - layout: ShareLayout, - dag: &SharingDag, - prep: &DagPrep, - limits: &ExactSharingLimits, - cand: Candidate, - charge: bool, -) -> Result { - let Candidate { mut result, phase3, reference } = cand; - let nodes = dag.nodes(); - let p = prof::scope(Phase::RematerializeDecide); - let order = &result.table_terms; - let k_entries = order.len(); - // Every Share of a task names an entry before it, whose index is its - // position in the final order. - let mut dict = LayoutIndex { index: vec![None; nodes.len()], layout }; - for (i, &t) in order.iter().enumerate() { - dict.index[ix(t)] = Some(len64(i)); - } - let mut mat = Materializer::new(nodes.len()); - let plans = match phase3 { - Phase3::Plans(plans) => plans, - Phase3::OnePass { costs } => { - // The decisions of every task under the full dictionary (see - // `one_pass_plans`), and their work. - let mut work = 0u64; - let plans = one_pass_plans( - dag, &prep.own, order, &dict, &costs, &mut mat, &mut work, - )?; - if charge { - let total = result - .stats - .candidate_meters - .first() - .map_or(0, |m| m.3) - .saturating_add(work); - if total > limits.max_work { - return Err(SharingError::ResourceExhausted(ResourceExhausted { - resource: Resource::Work, - limit: limits.max_work, - })); - } - if let Some(m) = result.stats.candidate_meters.first_mut() { - m.3 = total; - } - } - plans - }, - }; - if let Some(reference) = reference - && plans != reference - { - let j = plans.iter().zip(&reference).position(|(a, b)| a != b); - return Err(internal(format!( - "full check: the one-pass phase-3 decisions of task {j:?} differ from \ - the per-prefix decisions" - ))); - } - drop(p); - let p = prof::scope(Phase::RematerializeBuild); - let mut entries = Vec::with_capacity(k_entries); - let mut roots = Vec::new(); - let mut allocated = 0u64; - for (j, plan) in plans.iter().enumerate() { - let mut es = mat.build(nodes, &dict, plan)?; - allocated = allocated.saturating_add(plan.nodes); - if j < k_entries { - entries.push(es.pop().ok_or_else(|| internal("missing entry"))?); - } else { - roots = es; - } - } - drop(p); - let _p = prof::scope(Phase::RematerializeCheck); - let predicted = result.model_len; - // The layout price and the re-expansion check in one walk; when it - // reports a mismatch or an overflow, the separate checks run. - let price = |i: u64| layout.width_at(i); - let fused = dag.check_and_measure(order, &entries, &roots, &price); - let priced = match fused { - Some((_, len)) => len, - None => layout_bytes(layout, &entries, &roots).ok_or_else(overflow)?, - }; - if priced != predicted { - return Err(internal(format!( - "layout length {priced} differs from the evaluation {predicted}" - ))); - } - match fused { - Some((visited, _)) => { - if visited != allocated { - return Err(internal(format!( - "the re-materialized expressions have {visited} nodes, {allocated} counted" - ))); - } - }, - None => dag.check_reexpansion( - order, - &entries, - &roots, - &ExactSharingLimits::unbounded(), - "re-materialized encoding changes the expanded AST", - "re-materialized entries do not expand to the stored terms", - )?, - } - // The input-node limit was checked in `tiered_at` on an exact count, or, - // for a one-pass candidate, on the bound `2 * predicted`; check the bound. - if limits.full_check && allocated > predicted.saturating_mul(2) { - return Err(internal(format!( - "full check: {allocated} expression nodes exceed twice the length \ - {predicted}" - ))); - } - // The real length: the table count and every entry and root as the wire - // codec writes them (`put_sharing`, `put_expr`), one expression at a time - // into a reused buffer; linear in the output. - let mut buf = Vec::new(); - TagN::put(0, 0, len64(entries.len()), &mut buf); - let mut measured = len64(buf.len()); - for e in entries.iter().chain(&roots) { - buf.clear(); - put_expr(e, &mut buf); - measured = measured.saturating_add(len64(buf.len())); - } - if measured != predicted { - return Err(internal(format!( - "serialized length {measured} differs from the evaluation {predicted}" - ))); - } - result.roots = roots; - result.sharing = entries; - result.variable_len = measured; - Ok(result) -} - -/// Whether candidate `a` beats `b` (Lean `tieredBetter`): fewer final layout -/// bytes, then the lower width, then `set_prec` on the stored set. -fn tiered_better(a: &TieredSharingResult, b: &TieredSharingResult) -> bool { - let (x, y) = (&a.stats, &b.stats); - x.phase3_layout_bytes < y.phase3_layout_bytes - || (x.phase3_layout_bytes == y.phase3_layout_bytes - && (x.w < y.w - || (x.w == y.w && set_prec(&a.phase1.stored, &b.phase1.stored)))) -} - -/// The checked mode's phase-1 check of one candidate: build the phase-1 -/// expressions from the decisions of `plan`, check them as -/// `optimize_uniform` does (re-expansion, real length), and compare them -/// with the dry run `shares`: the Shares of each entry in all the -/// expressions and the entries each entry shares (the phase-2 weights and -/// dependencies, computed from the expressions as before the dry run), the -/// expression nodes (the dry run's input-node count), the real length and -/// the layout price of phase 1 (`u.variable_len`). -pub(crate) fn check_phase1( - layout: ShareLayout, - dag: &SharingDag, - u: &UniformSharingResult, - plan: &UniformPlan, - shares: &PlanShares, -) -> Result<(), SharingError> { - let order1 = &u.table_terms; - let built = plan.build(dag, order1, &ExactSharingLimits::unbounded())?; - let fail = |what: &str| { - Err(internal(format!( - "full check: the phase-1 dry run differs from the built expressions \ - ({what})" - ))) - }; - let mut refs = vec![0u64; order1.len()]; - let mut idx = Vec::new(); - for e in built.entries.iter().chain(&built.roots) { - idx.clear(); - share_indices(e, &mut idx); - for &i in &idx { - if let Some(r) = usize::try_from(i).ok().and_then(|i| refs.get_mut(i)) { - *r += 1; - } - } - } - let mut deps: Vec> = Vec::with_capacity(order1.len()); - for e in &built.entries { - idx.clear(); - share_indices(e, &mut idx); - let mut ds: Vec = Vec::new(); - for &j in &idx { - if let Some(&d) = usize::try_from(j).ok().and_then(|j| order1.get(j)) - && !ds.contains(&d) - { - ds.push(d); - } - } - deps.push(ds); - } - if refs != shares.refs { - return fail("Shares per entry"); - } - if deps != shares.deps { - return fail("dependencies"); - } - if built.visited != Some(shares.nodes) { - return fail("expression nodes"); - } - if built.measured != u.variable_len { - return fail("real length"); - } - if layout_bytes(layout, &built.entries, &built.roots) != Some(u.variable_len) - { - return fail("layout price"); - } - Ok(()) -} - -/// The checked mode's reference for a one-pass candidate: its phase 3 per -/// prefix, whose task lengths must equal the one-pass ones (`lengths`, in -/// task order); returns the per-prefix decisions, which [`finish`] compares -/// with the one-pass decisions. Nothing is charged: the work differs by -/// path by design. -pub(crate) fn per_prefix_reference( - layout: ShareLayout, - dag: &SharingDag, - prep: &DagPrep, - order: &[TermId], - budget: usize, - lengths: &[Len], -) -> Result, SharingError> { - let tasks = per_prefix_tasks(layout, dag, prep, order, budget); - if tasks.len() != lengths.len() { - return Err(internal("full check: per-prefix task count")); - } - let mut plans = Vec::with_capacity(tasks.len()); - for (j, (task, &len)) in tasks.into_iter().zip(lengths).enumerate() { - let (plan, cost, _) = task?; - if cost != len { - return Err(internal(format!( - "full check: the one-pass phase-3 length of task {j} ({len:?}) \ - differs from the per-prefix length ({cost:?})" - ))); - } - plans.push(plan); - } - Ok(plans) -} - -/// One candidate of the tiered construction (Lean `tieredAtWidth`): phases -/// 1-3 with phase-1 uniform width `w`. -fn tiered_at( - layout: ShareLayout, - dag: &SharingDag, - prep: &DagPrep, - meter: &mut Meter<'_>, - w: u64, -) -> Result { - tiered_at_using(layout, dag, prep, meter, w, |meter| { - optimize_family_with(w, dag, prep, meter) - }) -} - -pub(super) fn tiered_at_using( - layout: ShareLayout, - dag: &SharingDag, - prep: &DagPrep, - meter: &mut Meter<'_>, - w: u64, - phase1: impl FnOnce( - &mut Meter<'_>, - ) - -> Result<(UniformSharingResult, UniformPlan), SharingError>, -) -> Result { - let nodes = dag.nodes(); - let n = nodes.len(); - let limits = meter.limits(); - let p = prof::scope(Phase::Prep); - let own = &prep.own; - // The work of the evaluation `C_0` (`prep.base`), charged with phase 3. - let work = prep.base.work(); - let facts = &prep.facts; - let k = len64( - (0..n) - .filter(|&t| facts.deg[t] >= 2 && prep.base.costs[t] >= Len::new(2)) - .count(), - ); - drop(p); - // Phase 1, without building its expressions: phase 2 reads only what they - // contain, which the dry run reports from the same decisions - // (`PlanShares`), with the real length. - let (mut u, plan1) = phase1(meter)?; - let (shares, real1) = plan1.shares(dag, &u.table_terms, limits)?; - u.variable_len = real1; - if limits.full_check { - check_phase1(layout, dag, &u, &plan1, &shares)?; - } - drop(plan1); - let p = prof::scope(Phase::Allocate); - let order1 = u.table_terms.clone(); - // The phase-1 output priced by the layout. The TagN layout prices a Share - // at its wire width, so this is the real length of phase 1. - let phase1_layout = match layout { - ShareLayout::TagN => u.variable_len, - }; - // Phase 2: reference counts (the Shares of each entry in all the phase-1 - // expression trees) and dependencies (the entries an entry's phase-1 - // expression shares, in pre-order of first occurrence). - let mut weight: FxHashMap = FxHashMap::default(); - let mut deps: FxHashMap> = FxHashMap::default(); - let PlanShares { refs, deps: entry_deps, .. } = shares; - for ((&t, &r), ds) in order1.iter().zip(&refs).zip(entry_deps) { - weight.insert(t, r); - deps.insert(t, ds); - } - let mut stored = order1.clone(); - stored.sort_unstable(); - let cap = stored.len().min(8); - let (tier, slot_states) = first_tier(&stored, &weight, &deps, cap, limits)?; - let rest: Vec = - stored.iter().copied().filter(|t| !tier.contains(t)).collect(); - let mut order2 = pinned_order(dag, &facts.deg, &tier); - order2.extend(kahn_order(&weight, &deps, &rest)); - let ref_cost = |ord: &[TermId]| -> u128 { - ord.iter().enumerate().fold(0u128, |acc, (i, t)| { - let wt = u128::from(weight.get(t).copied().unwrap_or(0)); - acc.saturating_add(wt * u128::from(layout.width_at(len64(i)))) - }) - }; - let kept = ref_cost(&order2) > ref_cost(&order1); - let order = if kept { order1.clone() } else { order2 }; - if !respects_deps(&order, &deps) { - return Err(internal( - "the allocated order places a body reference after its user", - )); - } - drop(p); - // Phase 3: each entry under the entries before it, priced by the layout, - // then the roots under all entries (module docs). - // - // One pass (the order is closed under stored descendants): one - // evaluation of the full dictionary gives every task's length, entry `j` - // with its own Share hidden and the roots as they are; the decisions are - // made for the returned candidate only (`finish`). The work charged is the - // work done: the evaluation and the hidden-top evaluations. - // - // Per prefix (otherwise): `per_prefix_tasks`, each task charged the work - // of a full evaluation of its prefix plus its decisions, combined in task - // order exactly as the sequential loop proceeds. - let k_entries = order.len(); - let one_pass = !limits.phase3_per_prefix && descendant_closed(nodes, &order); - // With `full_check`, the one-pass length of every task. - let mut task_lengths: Vec = Vec::new(); - let (phase3, predicted, allocated) = if one_pass { - let p = prof::scope(Phase::RematerializeCosts); - let mut dict = LayoutIndex { index: vec![None; n], layout }; - for (i, &t) in order.iter().enumerate() { - dict.index[ix(t)] = Some(len64(i)); - } - let eval = Evaluation::new(nodes, own, &dict); - let mut w = eval.work(); - let mut predicted = Len::new(tag0_len(len64(k_entries))); - for &t in &order { - let hide = Hide { inner: &dict, hidden: t }; - let costs = &eval.costs; - let (c, _) = - eval_node(nodes, own, t, &hide, &|x| costs[ix(x)], false, &mut w); - predicted = predicted.plus(c); - if limits.full_check { - task_lengths.push(c); - } - } - let mut roots = Len::ZERO; - for &r in dag.roots() { - roots = roots.plus(eval.costs[ix(r)]); - } - predicted = predicted.plus(roots); - if limits.full_check { - task_lengths.push(roots); - } - // The work counted before phase 3 (`C_0`) is charged with it. - meter.work(work.saturating_add(w))?; - drop(p); - (Phase3::OnePass { costs: eval.costs }, predicted, None) - } else { - let tasks = - per_prefix_tasks(layout, dag, prep, &order, meter.parallel.materialize); - let mut plans = Vec::with_capacity(k_entries + 1); - let mut predicted = Len::new(tag0_len(len64(k_entries))); - // The work counted before phase 3 is charged with the first task, as the - // sequential loop does. - let mut carried = work; - for task in tasks { - let (plan, cost, w) = task?; - plans.push(plan); - predicted = predicted.plus(cost); - meter.work(carried.saturating_add(w))?; - carried = 0; - } - let allocated = - plans.iter().fold(0u64, |acc, plan| acc.saturating_add(plan.nodes)); - (Phase3::Plans(plans), predicted, Some(allocated)) - }; - let p = prof::scope(Phase::RematerializeCheck); - let predicted = predicted.exact().ok_or_else(overflow)?; - if predicted > phase1_layout { - return Err(internal(format!( - "re-materialization {predicted} is longer than phase 1 {phase1_layout}" - ))); - } - // The input nodes the re-expansion check of the built expressions - // charges: one per expression node, all of them pointer-distinct (every - // task builds fresh nodes). A serialized tree has at most two nodes per - // byte (every node but an App writes at least one byte of its own, and an - // App's argument starts with a byte no other App claims), so the count is - // at most twice the length; only when that bound exceeds the limit are - // the one-pass decisions made here to count exactly. - let allocated = match allocated { - Some(a) => Some(a), - None if predicted.saturating_mul(2) > limits.max_input_nodes => { - let Phase3::OnePass { costs } = &phase3 else { - return Err(internal("one-pass phase 3 without its evaluation")); - }; - let mut dict = LayoutIndex { index: vec![None; n], layout }; - for (i, &t) in order.iter().enumerate() { - dict.index[ix(t)] = Some(len64(i)); - } - let mut scratch = 0u64; - let plans = one_pass_plans( - dag, - own, - &order, - &dict, - costs, - &mut Materializer::new(n), - &mut scratch, - )?; - Some(plans.iter().fold(0u64, |acc, plan| acc.saturating_add(plan.nodes))) - }, - None => None, - }; - if let Some(a) = allocated { - dag.check_input_nodes(a, limits)?; - } - // The real length equals the evaluated one (checked when the candidate - // is built). - meter.output(predicted)?; - drop(p); - // With `full_check`, the per-prefix reference of a one-pass candidate, - // after every check of the default mode (so that its errors come first). - let reference = if limits.full_check && one_pass { - Some(per_prefix_reference( - layout, - dag, - prep, - &order, - meter.parallel.materialize, - &task_lengths, - )?) - } else { - None - }; - let stats = TieredStats { - layout, - candidate_count: k, - w, - candidate_lengths: vec![(w, predicted)], - candidate_meters: vec![( - w, - u.model_len, - meter.stats.states_created, - meter.stats.work, - )], - per_prefix_candidates: u64::from(matches!(phase3, Phase3::Plans(_))), - phase1_model_bytes: u.model_len, - phase1_layout_bytes: phase1_layout, - slot_states, - first_tier: tier, - kept_phase1_order: kept, - phase1_ref_cost: ref_cost(&order1), - final_ref_cost: ref_cost(&order), - phase3_layout_bytes: predicted, - savings: phase1_layout - predicted, - }; - let unshared_len = u.unshared_len; - let result = TieredSharingResult { - roots: Vec::new(), - sharing: Vec::new(), - table_terms: order, - model_len: predicted, - variable_len: predicted, - unshared_len, - phase1: u, - stats, - }; - Ok(Candidate { result, phase3, reference }) -} - -/// Tiered canonical sharing of fully expanded roots under a layout. -pub fn canonical_sharing_tiered( - layout: ShareLayout, - roots: &[Arc], - limits: &ExactSharingLimits, -) -> Result { - let mut meter = Meter::new(limits); - let p = prof::scope(Phase::Dag); - let dag = SharingDag::build(roots, None, &mut meter)?; - drop(p); - let _p = prof::scope(Phase::Total); - tiered(layout, &dag, limits, Parallelism::SEQUENTIAL) -} - -/// Expand `c`'s table and re-share it with the tiered canonical -/// construction (the best of the phase-1 widths 1, 2 and 3). -pub fn normalize_constant_sharing_tiered( - layout: ShareLayout, - c: &Constant, - limits: &ExactSharingLimits, -) -> Result<(Constant, TieredSharingResult), SharingError> { - normalize_tiered_with(c, limits, |dag| { - tiered(layout, dag, limits, Parallelism::SEQUENTIAL) - }) -} - -/// [`normalize_constant_sharing_tiered`] with the thread budgets `par` -/// ([`Parallelism`]): the same bytes and result for every budget. -pub fn normalize_constant_sharing_tiered_par( - layout: ShareLayout, - c: &Constant, - limits: &ExactSharingLimits, - par: Parallelism, -) -> Result<(Constant, TieredSharingResult), SharingError> { - normalize_tiered_with(c, limits, |dag| tiered(layout, dag, limits, par)) -} - -/// The single candidate with phase-1 width `w` ([`tiered_at`], Lean -/// `tieredAtWidth`) on `c`, for the tests of the width selection: the -/// canonical construction returns one of the candidates at `w = 1, 2, 3`. -#[cfg(test)] -pub(crate) fn normalize_constant_sharing_tiered_at_width( - layout: ShareLayout, - c: &Constant, - limits: &ExactSharingLimits, - w: u64, -) -> Result<(Constant, TieredSharingResult), SharingError> { - normalize_tiered_with(c, limits, |dag| { - let prep = DagPrep::new(dag); - let c = tiered_at(layout, dag, &prep, &mut Meter::new(limits), w)?; - finish(layout, dag, &prep, limits, c, true) - }) -} - -/// Expand `c`'s table, run `run` on its DAG, and reassemble the Constant, -/// checking its serialized length against the result's accounting. -pub(super) fn normalize_tiered_with( - c: &Constant, - limits: &ExactSharingLimits, - run: impl FnOnce(&SharingDag) -> Result, -) -> Result<(Constant, TieredSharingResult), SharingError> { - let mut meter = Meter::new(limits); - let p = prof::scope(Phase::Dag); - let roots = constant_info_root_exprs(&c.info); - let dag = SharingDag::build(&roots, Some(&c.sharing), &mut meter)?; - drop(p); - let fixed = constant_fixed_len(c).ok_or_else(overflow)?; - let p = prof::scope(Phase::Total); - let result = run(&dag)?; - drop(p); - let _p = prof::scope(Phase::Output); - let info = rebuild_constant_info(&c.info, &result.roots)?; - let out = Constant { - info, - sharing: result.sharing.clone(), - refs: c.refs.clone(), - univs: c.univs.clone(), - }; - let mut bytes = Vec::new(); - out.put(&mut bytes); - if u64::try_from(bytes.len()).ok() != fixed.checked_add(result.variable_len) { - return Err(internal("tiered output length differs from its accounting")); - } - Ok((out, result)) -} - -/// Byte-level tiered normalization of exactly one serialized Constant. -pub fn normalize_constant_bytes_tiered( - layout: ShareLayout, - bytes: &[u8], - limits: &ExactSharingLimits, -) -> Result, NormalizeBytesError> { - let mut input = bytes; - let c = Constant::get(&mut input).map_err(NormalizeBytesError::Decode)?; - if !input.is_empty() { - return Err(NormalizeBytesError::Decode(format!( - "{} trailing bytes after the Constant", - input.len() - ))); - } - let (out, _) = normalize_constant_sharing_tiered(layout, &c, limits) - .map_err(NormalizeBytesError::Sharing)?; - let mut buf = Vec::new(); - out.put(&mut buf); - Ok(buf) -} diff --git a/crates/ixon/src/sharing_exact/uniform.rs b/crates/ixon/src/sharing_exact/uniform.rs deleted file mode 100644 index a2752c2ba..000000000 --- a/crates/ixon/src/sharing_exact/uniform.rs +++ /dev/null @@ -1,1902 +0,0 @@ -//! Canonical family sharing under a uniform Share width. -//! -//! Production uses the fixed K16 family in `family`. The old unbounded -//! uniform optimizer and its controls live in `reference`, available only -//! to unit tests or the explicit test-FFI reference feature. -//! -//! Cost model: every `Share` costs exactly `w >= 1` bytes regardless of its -//! index; the table count is an exact TagN; telescopes and every other byte -//! are as written by `put_expr`. The model length of a stored set `S` is -//! `tag0(|S|) + sum_{t in S} inl_S(t) + sum_roots C_S(r)`, where `inl_S(t)` -//! is `t`'s cost with an inline top. Order does not affect it: in any order -//! with stored descendants first each entry sees every stored term that can -//! occur inside it, and a Share's price does not depend on its index. Only -//! the set is chosen; the table order is then pinned (stored descendants -//! first, then larger in-degree, then smaller structural ID). -//! -//! # Canonical class and classification -//! -//! * `deg(t)`: compact in-degree, with edge multiplicity and root -//! occurrences; head positions are the root occurrences and the edges -//! that do not continue a telescope (an App as the function of -//! an App, a Lam as the body of a Lam, an All as the body of an All); -//! `occ(t)`: expanded occurrences; `size(t)`: unshared standalone length. -//! * The production result minimizes over the fixed initial component -//! families, with certain-stored entries held fixed. The reference oracle -//! minimizes over all tables whose entries have in-degree >= 2. -//! In-degree-1 terms are left out by an exchange that never -//! increases the length (store the single parent instead, or turn extra -//! writes of a stored parent into Shares), so this class contains a -//! global minimum; ties can also store an in-degree-1 term, which this -//! definition excludes. -//! * CERTAIN-EXCLUDED when `(occ-1)*size < occ*w` (in no minimum). -//! * Candidates: in-degree >= 2 and not certain-excluded. -//! * CERTAIN-STORED (in every minimum of the restricted class) when the gain -//! bound `g >= theta`, with `d`, `h` the visible counts and lower bounds -//! computed bottom-up pricing every candidate child at `min(w, .)`, every -//! other child at its inline bound and a header of at least 1 byte: -//! non-telescope `g = (d-1)*inl - d*w`; telescope with `h >= 1` -//! `g = (d-1)*b + (h-1) - d*w`; telescope with `h = 0` -//! `g = (d-1)*b - tag4(spine length) - d*w`. -//! Visible counts `(d, h)` for a maybe-stored set `M` (the candidates): -//! one per root occurrence plus, per edge from `z` (head edges only for -//! `h`), 1 if `z` is in `M`, else `min(d(z), 2^20)`. -//! Threshold: with `theta_max = 1 + tag0_step_bound(#candidates)` (2 below -//! 4311826560 candidates), `theta = 1` when `tag0(#candidates) = tag0(#terms -//! with g >= theta_max)`, else `theta = theta_max`. -//! * UNCERTAIN: the other candidates; LOW-DEGREE: the other terms. -//! -//! Certain-stored terms cost exactly `w` wherever they occur (`g >= 1` -//! forces their merged payload bound `>= w`), so costs above them are -//! evaluated with them as `w`-byte leaves that end telescopes. -//! -//! # Search -//! -//! Uncertain terms joined by a DAG path avoiding certain-stored terms form -//! a component; costs are sums of per-component functions. Each component -//! search returns, per chosen count, its best change within `slack = -//! tag0(|certain-stored| + |uncertain|) - tag0(|certain-stored|)` of its -//! optimum. The table-count prefix is the only coupling: when the combined -//! choice reaches a TagN bracket of 128 or more entries, a knapsack over -//! components checks the lower brackets. -//! -//! Production enumerates every subset up to sixteen initial members and -//! otherwise keeps every count of the setPrec-least greedy chain. The old -//! reference component search is the reclassifying branch and bound of Lean -//! implementation. At each node it (1) recomputes the bounds and visible -//! counts over the component's area with the members decided "not stored" -//! removed from `M`, and forces -//! "stored" every undecided member with `g >= theta`; (2) bounds the node by -//! the cost with every undecided member available and its entry free, -//! pruning only when strictly above the best by more than `slack`; (3) -//! splits the undecided members into groups joined by DAG paths through -//! non-opaque terms (a decided-stored member is opaque when its `inl` (or, -//! for a telescope, merged) bound is at least `w`; groups below a stored -//! non-opaque member are joined), solving groups by memoized sub-searches -//! keyed by the group and the decisions that reach it; (4) branches on the -//! undecided member with the largest `|g|` (then the smaller ID), "stored" -//! first when `g > 0`. Per-count tables of groups combine by convolution. -//! The plain subset enumeration is kept as a test oracle -//! (`ExactSharingLimits::uniform_subset_search`); it is not on the compiler -//! path, and the Lean optimality theorems assume it is off. -//! -//! # Tie-break -//! -//! Among minimum-length sets the least in the order "the smaller structural -//! ID of the symmetric difference is not stored". It decomposes over -//! components and through the knapsack. -//! -//! # Numeric domain -//! -//! Lean uses unbounded `Nat` and `Int`, and so does this optimizer: costs, -//! bounds, gains, component deltas and knapsack sums are computed in `u128` -//! and `i128` first and, if a value leaves that range, again in `BigUint` -//! and `BigInt` (`arith`), with the same result wherever the first run -//! succeeds. The exclusion test is evaluated exactly even when an -//! occurrence count saturates, and visible counts are `u128`, below `2^84` -//! for any DAG held in memory. Only the model length of the chosen set must -//! be a `u64`: it is the length of an output. - -use std::sync::Arc; - -use rustc_hash::FxHashMap; - -use super::cost::{Len, exprs_len_with, tag0_len, tag4_len}; -use super::dag::{Node, SharingDag, TermId, ix}; -use super::dict::{ - DependentPlan, Evaluation, PlanShares, ReadEdges, UniformIndex, all_costs, - build_dependent, decide_dependent, -}; -use super::prof::{self, Phase}; -use super::{ - ExactSharingLimits, ExactSharingStats, FormatBound, Meter, SharingError, -}; -#[cfg(any(test, feature = "sharing-reference"))] -use super::{ - constant_fixed_len, constant_info_root_exprs, rebuild_constant_info, -}; -#[cfg(any(test, feature = "sharing-reference"))] -use crate::constant::Constant; -use crate::expr::Expr; -use crate::tag::TagN; - -mod arith; -pub(crate) use arith::{Arith, Delta, Exact, Fast}; -mod family; -#[cfg(test)] -pub(crate) fn optimize_family_at_test( - w: u64, - dag: &SharingDag, - prep: &DagPrep, - meter: &mut Meter<'_>, - k: usize, -) -> Result<(UniformSharingResult, UniformPlan), SharingError> { - family::optimize_at(w, dag, prep, meter, k) -} -/// [`optimize_family_at_test`] in the representation `A` only, without the -/// exact retry. -#[cfg(test)] -pub(crate) fn optimize_family_in_test( - w: u64, - dag: &SharingDag, - prep: &DagPrep, - meter: &mut Meter<'_>, - k: usize, -) -> Result<(UniformSharingResult, UniformPlan), SharingError> { - family::optimize_in::(w, dag, prep, meter, k) -} -pub(crate) use family::optimize_family_with; - -#[cfg(any(test, feature = "sharing-reference"))] -mod reference; -#[cfg(any(test, feature = "sharing-reference"))] -pub(crate) use reference::optimize_uniform_with; - -#[cfg(test)] -pub(crate) mod family_trace; - -fn internal(msg: impl Into) -> SharingError { - SharingError::Internal(msg.into()) -} - -fn overflow() -> SharingError { - SharingError::FormatBound(FormatBound::LengthOverflow) -} - -fn len64(n: usize) -> u64 { - u64::try_from(n).unwrap_or(u64::MAX) -} - -fn u(x: u64) -> u128 { - u128::from(x) -} - -/// Classification of a term for the uniform-width search. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum UniformClass { - /// Not certain-excluded, but in-degree below 2: never searched. - LowDegree, - CertainExcluded, - CertainStored, - Uncertain, -} - -/// Uniform-width result: the encoding, its model and real lengths, and the -/// classification and search statistics. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct UniformSharingResult { - /// Rewritten roots, in input order. - pub roots: Vec>, - /// The table, in the pinned order. - pub sharing: Vec>, - /// Term ID of each table entry, in the pinned order. - pub table_terms: Vec, - /// Exact uniform-model variable length (count, entries, roots). - pub model_len: u64, - /// Real serialized variable length of the same output. - pub variable_len: u64, - /// Unshared variable length (`None` if it exceeds `u64`). - pub unshared_len: Option, - /// The stored set, ascending. - pub stored: Vec, - pub certain_stored: Vec, - pub certain_excluded: Vec, - pub uncertain: Vec, - pub low_degree: Vec, - /// Components of the uncertain terms (each ascending, by first member). - pub components: Vec>, - /// Reference cost calls precharged for the component families (search - /// states on the old test-only exact path). - pub states_visited: u64, - /// Whether the table-count knapsack chose a lower TagN bracket. - pub lower_bracket: bool, - pub stats: ExactSharingStats, - /// Test-only view of retained per-count optima, including canonical ties, - /// with exact deltas. - #[cfg(test)] - pub(crate) component_tables: Vec>, -} - -// --------------------------------------------------------------------------- -// Graph facts -// --------------------------------------------------------------------------- - -fn spine_next(node: &Node) -> TermId { - match node { - Node::App(f, _) => *f, - Node::Lam(_, _, b) | Node::All(_, _, _, b) => *b, - _ => 0, - } -} - -fn side_child(node: &Node) -> TermId { - match node { - Node::App(_, a) => *a, - Node::Lam(_, ty, _) | Node::All(_, _, ty, _) => *ty, - _ => 0, - } -} - -fn side_extra(node: &Node) -> u64 { - match node { - Node::App(..) => 0, - _ => 1, - } -} - -/// Bytes a non-telescope node writes itself, excluding its children. -fn own_bytes(node: &Node) -> Result { - match node.family() { - Some(_) => Ok(0), - None => node.own_len().exact().ok_or_else(overflow), - } -} - -// This cap is used ONLY for empty costs consumed by initial classification. -// It covers 2*w for every public u64 width, not merely tiered widths 1..=3. -// Proofs: IxSharingVerify.UniformCappedSize. General UPrep/phi/gains use -// exact arithmetic (`Arith`); a capped value must never enter those paths. -const EMPTY_SIZE_CAP: u128 = (1u128 << 65) - 2; - -#[cfg(test)] -mod capped_size_tests; - -fn empty_cap_add(a: u128, b: u128) -> u128 { - // Both operands become <=2^65-2, so this addition cannot overflow u128. - (a.min(EMPTY_SIZE_CAP) + b.min(EMPTY_SIZE_CAP)).min(EMPTY_SIZE_CAP) -} - -/// Exact own-payload cost, capped independently of Len's u64 sentinel. -/// In particular, all Ref/Rec universe arguments contribute their bytes. -fn empty_own_size(node: &Node) -> Result { - Ok(match node { - Node::Sort(n) | Node::Var(n) | Node::Str(n) | Node::Nat(n) => { - u(tag4_len(*n)) - }, - Node::Ref(n, us) | Node::Rec(n, us) => { - let count = u64::try_from(us.len()).map_err(|_e| overflow())?; - us.iter().fold( - empty_cap_add(u(tag4_len(count)), u(tag0_len(*n))), - |size, level| empty_cap_add(size, u(tag0_len(*level))), - ) - }, - Node::Prj(t, f, _) => empty_cap_add(u(tag4_len(*f)), u(tag0_len(*t))), - Node::Let(c, ..) => empty_cap_add(u(tag4_len(c.flags())), 1), - Node::App(..) | Node::Lam(..) | Node::All(..) => 0, - }) -} - -/// min(C_0, EMPTY_SIZE_CAP) in child-before-parent order. Empty evaluation -/// has no opaque/available terms and no cut or subtraction. Its only -/// recurrences are nonnegative sums of payloads, sides and natural tails; -/// distributing min(cap, _) over those sums is exact. -fn empty_sizes_capped( - nodes: &[Node], - spine_len: &[u64], - spine_tail: &[TermId], -) -> Result, SharingError> { - let mut cost = vec![0; nodes.len()]; - let mut sides = vec![0; nodes.len()]; - for (t, node) in nodes.iter().enumerate() { - if node.family().is_none() { - cost[t] = node - .children() - .as_slice() - .iter() - .fold(empty_own_size(node)?, |size, child| { - empty_cap_add(size, cost[ix(*child)]) - }); - } else { - let next = spine_next(node); - let continued = nodes[ix(next)].family() == node.family(); - sides[t] = empty_cap_add( - empty_cap_add(u(side_extra(node)), cost[ix(side_child(node))]), - if continued { sides[ix(next)] } else { 0 }, - ); - cost[t] = empty_cap_add( - empty_cap_add(u(tag4_len(spine_len[t])), sides[t]), - cost[ix(spine_tail[t])], - ); - } - } - Ok(cost) -} - -/// Whether the edge from `parent` to its `i`-th child continues the -/// parent's telescope. -fn continuation_edge(parent: &Node, i: usize, child: &Node) -> bool { - match (parent, child) { - (Node::App(..), Node::App(..)) => i == 0, - (Node::Lam(..), Node::Lam(..)) | (Node::All(..), Node::All(..)) => i == 1, - _ => false, - } -} - -pub(crate) struct Facts { - pub(crate) deg: Vec, - occ: Vec, - /// Distinct parents of each term. - pub(crate) parents: Vec>, -} - -pub(crate) fn graph_facts(dag: &SharingDag) -> Facts { - let nodes = dag.nodes(); - let n = nodes.len(); - let mut deg = vec![0u64; n]; - let mut parents: Vec> = vec![Vec::new(); n]; - for &r in dag.roots() { - deg[ix(r)] += 1; - } - for (t, node) in nodes.iter().enumerate() { - let kids = node.children(); - for &c in kids.as_slice() { - deg[ix(c)] += 1; - // Parents are visited in increasing ID, so `t` can only repeat as the - // last entry: a constant-time check keeps this linear in the edges. - if parents[ix(c)].last() != Some(&t) { - parents[ix(c)].push(t); - } - } - } - let mut occ = vec![0u128; n]; - for &r in dag.roots() { - occ[ix(r)] = occ[ix(r)].saturating_add(1); - } - for t in (0..n).rev() { - let o = occ[t]; - if o == 0 { - continue; - } - for &c in nodes[t].children().as_slice() { - occ[ix(c)] = occ[ix(c)].saturating_add(o); - } - } - Facts { deg, occ, parents } -} - -/// Spine lengths and natural tails, with spines ending at `stop` terms. -fn spine_tables(nodes: &[Node], stop: &[bool]) -> (Vec, Vec) { - let n = nodes.len(); - let mut len = vec![0u64; n]; - let mut tail: Vec = vec![0; n]; - for (t, node) in nodes.iter().enumerate() { - if node.family().is_some() { - let nxt = spine_next(node); - if nodes[ix(nxt)].family() == node.family() && !stop[ix(nxt)] { - len[t] = len[ix(nxt)] + 1; - tail[t] = tail[ix(nxt)]; - } else { - len[t] = 1; - tail[t] = nxt; - } - } - } - (len, tail) -} - -// --------------------------------------------------------------------------- -// Truncated evaluation -// --------------------------------------------------------------------------- - -/// One term's values under [`UPrep`]: its cost where it occurs, its cost -/// with an inline top, the side bytes of its spine and the nearest -/// available spine descendant. -#[derive(Debug)] -struct UVal { - cost: A::U, - inl: A::U, - sides: A::U, - below: Option, -} - -impl Clone for UVal { - fn clone(&self) -> Self { - UVal { - cost: self.cost.clone(), - inl: self.inl.clone(), - sides: self.sides.clone(), - below: self.below, - } - } -} - -impl Default for UVal { - fn default() -> Self { - UVal { - cost: A::U::default(), - inl: A::U::default(), - sides: A::U::default(), - below: None, - } - } -} - -/// Evaluation tables with `opaq` terms as `w`-byte leaves ending spines. -struct UPrep<'d, A: Arith = Fast> { - nodes: &'d [Node], - w: A::U, - opaq: Vec, - t_len: Vec, - t_tail: Vec, - base: Vec>, -} - -impl<'d, A: Arith> UPrep<'d, A> { - fn new( - nodes: &'d [Node], - w: A::U, - opaq: Vec, - ) -> Result { - let (t_len, t_tail) = spine_tables(nodes, &opaq); - let mut up = UPrep { nodes, w, opaq, t_len, t_tail, base: Vec::new() }; - let mut base: Vec> = Vec::with_capacity(nodes.len()); - for t in 0..nodes.len() { - let (v, _) = up.node(&|c| &base[ix(c)], &|_| false, t)?; - base.push(v); - } - up.base = base; - Ok(up) - } - - /// One term's value given its children's values; also the number of - /// available spine descendants visited. - fn node<'v>( - &self, - get: &dyn Fn(TermId) -> &'v UVal, - avail: &dyn Fn(TermId) -> bool, - t: TermId, - ) -> Result<(UVal, u64), SharingError> { - let node = &self.nodes[ix(t)]; - let mut steps = 0u64; - let mut v = UVal::::default(); - if node.family().is_none() { - let mut inl = A::u(own_bytes(node)?); - for &c in node.children().as_slice() { - inl = A::add(&inl, &get(c).cost); - } - v.inl = inl; - } else { - let nxt = spine_next(node); - let same = - self.nodes[ix(nxt)].family() == node.family() && !self.opaq[ix(nxt)]; - let mut s = A::add(&A::u(side_extra(node)), &get(side_child(node)).cost); - if same { - s = A::add(&s, &get(nxt).sides); - } - let bl = if same { - if avail(nxt) { Some(nxt) } else { get(nxt).below } - } else { - None - }; - let l = self.t_len[ix(t)]; - let mut best = - A::add(&A::add(&A::u(tag4_len(l)), &s), &get(self.t_tail[ix(t)]).cost); - let mut cur = bl; - for _ in 0..l { - let Some(x) = cur else { break }; - steps += 1; - let gx = get(x); - let cand = A::add( - &A::add( - &A::u(tag4_len(l - self.t_len[ix(x)])), - &A::sub(&s, &gx.sides), - ), - &self.w, - ); - if cand < best { - best = cand; - } - cur = gx.below; - } - v.inl = best; - v.sides = s; - v.below = bl; - } - v.cost = if self.opaq[ix(t)] || (avail(t) && self.w < v.inl) { - self.w.clone() - } else { - v.inl.clone() - }; - if A::saturated(&v.inl) || A::saturated(&v.sides) || A::saturated(&v.cost) { - return Err(overflow()); - } - Ok((v, steps)) - } -} - -// --------------------------------------------------------------------------- -// Classification -// --------------------------------------------------------------------------- - -/// `(occ-1)*size < occ*w`, with Nat subtraction, including occ=0/1 and -/// w=0. For size>w and occ>=1 this is occ*(size-w)w). -fn certainly_excluded(occ: u128, size: u128, w: u128) -> bool { - if occ == 0 || w == 0 { - return false; - } - if size <= w { - return true; - } - occ <= (size - 1) / (size - w) -} - -/// Lower bounds of the certain-stored test (Lean `UBounds`): `inl`, bytes -/// of a term written inline at a head position; `merged`, bytes of a -/// telescope node inside a telescope running through it (no header); -/// `head_lb`/`cont_lb`, the same at a head/continuation position when the -/// term may be a Share. -pub(crate) struct Bounds { - inl: Vec, - merged: Vec, - head_lb: Vec, - cont_lb: Vec, -} - -impl Clone for Bounds { - fn clone(&self) -> Self { - Bounds { - inl: self.inl.clone(), - merged: self.merged.clone(), - head_lb: self.head_lb.clone(), - cont_lb: self.cont_lb.clone(), - } - } -} - -/// The lesser of `w` and `x`. -fn at_most(w: &A::U, x: &A::U) -> A::U { - if w < x { w.clone() } else { x.clone() } -} - -/// One step of the bounds (Lean `boundsStep`): `[inl, merged, head_lb, -/// cont_lb]` of `t` from its children's head/continuation bounds. -fn bound_step<'v, A: Arith>( - nodes: &[Node], - t: TermId, - w: &A::U, - maybe: bool, - head: &dyn Fn(TermId) -> &'v A::U, - cont: &dyn Fn(TermId) -> &'v A::U, -) -> Result<[A::U; 4], SharingError> { - let node = &nodes[ix(t)]; - let (i, m) = if node.family().is_none() { - let mut i = A::u(own_bytes(node)?); - for &c in node.children().as_slice() { - i = A::add(&i, head(c)); - } - (i.clone(), i) - } else { - let nxt = spine_next(node); - let rest = if nodes[ix(nxt)].family() == node.family() { - cont(nxt) - } else { - head(nxt) - }; - let m = - A::add(&A::add(&A::u(side_extra(node)), head(side_child(node))), rest); - (A::add(&m, &A::u(1)), m) - }; - if A::saturated(&i) || A::saturated(&m) { - return Err(overflow()); - } - let (hl, cl) = if maybe { - (at_most::(w, &i), at_most::(w, &m)) - } else { - (i.clone(), m.clone()) - }; - Ok([i, m, hl, cl]) -} - -fn uniform_bounds( - nodes: &[Node], - w: &A::U, - maybe: &[bool], -) -> Result, SharingError> { - let n = nodes.len(); - let mut b = Bounds:: { - inl: vec![A::U::default(); n], - merged: vec![A::U::default(); n], - head_lb: vec![A::U::default(); n], - cont_lb: vec![A::U::default(); n], - }; - for (t, &mb) in maybe[..n].iter().enumerate() { - let [i, m, hl, cl] = { - let (hs, cs) = (&b.head_lb, &b.cont_lb); - bound_step::(nodes, t, w, mb, &|c| &hs[ix(c)], &|c| &cs[ix(c)])? - }; - b.inl[t] = i; - b.merged[t] = m; - b.head_lb[t] = hl; - b.cont_lb[t] = cl; - } - Ok(b) -} - -/// Cap of the visible counts where they are passed on. -const VISIBLE_CAP: u128 = 1 << 20; - -/// Lean `propagateCounts`: per term, root occurrences plus, over the edges -/// from each parent `y` (descending IDs), `weight(y, count of y)`; the second -/// array counts only head (non-continuation) edges. With weights at most -/// `VISIBLE_CAP`, a count is below `2^20` times the edges and roots of the -/// DAG, so below `2^84`: the additions are exact. -fn propagate_counts( - dag: &SharingDag, - weight: &dyn Fn(usize, u128) -> u128, -) -> (Vec, Vec) { - let nodes = dag.nodes(); - let n = nodes.len(); - let mut all = vec![0u128; n]; - let mut head = vec![0u128; n]; - for &r in dag.roots() { - all[ix(r)] += 1; - head[ix(r)] += 1; - } - for y in (0..n).rev() { - let wy = weight(y, all[y]); - let node = &nodes[y]; - for (i, &c) in node.children().as_slice().iter().enumerate() { - all[ix(c)] += wy; - if !continuation_edge(node, i, &nodes[ix(c)]) { - head[ix(c)] += wy; - } - } - } - (all, head) -} - -/// Visible counts: lower bounds `(d, h)` on the inline occurrences (all / -/// at head positions) of each term in every encoding of a set inside -/// `maybe` that does not store it. -fn visible_counts(dag: &SharingDag, maybe: &[bool]) -> (Vec, Vec) { - propagate_counts(dag, &|y, c| if maybe[y] { 1 } else { c.min(VISIBLE_CAP) }) -} - -/// The certain-stored gain bound (Lean `storedGainC`) with `d` inline -/// occurrences in all, `h` of them at head positions, and the term's bounds -/// `inl`/`merged`. -fn stored_gain( - node: &Node, - d: u128, - h: u128, - spine_len: u64, - inl: &A::U, - merged: &A::U, - w: &A::U, -) -> Result { - let d = A::signed(&A::count(d))?; - let d1 = d.minus(&A::S::of(1))?; - let dw = d.times(&A::signed(w)?)?; - let g = if node.family().is_none() { - d1.times(&A::signed(inl)?)? - } else if h >= 1 { - let h1 = A::signed(&A::count(h - 1))?; - d1.times(&A::signed(merged)?)?.plus(&h1)? - } else { - let header = A::S::of(i128::from(tag4_len(spine_len))); - d1.times(&A::signed(merged)?)?.minus(&header)? - }; - g.minus(&dw) -} - -// --------------------------------------------------------------------------- -// Components -// --------------------------------------------------------------------------- - -fn uf_find(parent: &[usize], x: usize) -> usize { - let mut cur = x; - while parent[cur] != cur { - cur = parent[cur]; - } - cur -} - -/// Components of the uncertain terms, joined by DAG paths that avoid -/// certain-stored terms; each sorted, sorted by their smallest term. -fn uncertain_components( - dag: &SharingDag, - cls: &[UniformClass], -) -> Vec> { - let nodes = dag.nodes(); - let n = nodes.len(); - let mut uf: Vec = (0..n).collect(); - let mut reps: Vec> = vec![Vec::new(); n]; - for t in 0..n { - if cls[t] == UniformClass::CertainStored { - continue; - } - let mut rs: Vec = Vec::new(); - for &c in nodes[t].children().as_slice() { - for &r in &reps[ix(c)] { - let fr = uf_find(&uf, r); - if !rs.contains(&fr) { - rs.push(fr); - } - } - } - if cls[t] == UniformClass::Uncertain { - for &r in &rs { - let a = uf_find(&uf, t); - let b = uf_find(&uf, r); - if a != b { - if a < b { - uf[b] = a; - } else { - uf[a] = b; - } - } - } - reps[t] = vec![t]; - } else { - reps[t] = rs; - } - } - let mut groups: std::collections::BTreeMap> = - std::collections::BTreeMap::new(); - for (t, c) in cls.iter().enumerate() { - if *c == UniformClass::Uncertain { - groups.entry(uf_find(&uf, t)).or_default().push(t); - } - } - let mut comps: Vec> = groups.into_values().collect(); - comps.sort_by_key(|c| c[0]); - comps -} - -/// Members and their ancestors, not continuing above an opaque term, using -/// caller-owned `stamp`/`epoch` and `stack` buffers so no hash set is -/// allocated per component. -fn component_area_with( - up: &UPrep<'_, A>, - facts: &Facts, - members: &[TermId], - stamp: &mut [u32], - epoch: &mut u32, - stack: &mut Vec, -) -> Vec { - if *epoch == u32::MAX { - stamp.fill(0); - *epoch = 0; - } - *epoch += 1; - let mark = *epoch; - stack.clear(); - let mut out: Vec = Vec::with_capacity(members.len()); - for &t in members { - if stamp[ix(t)] != mark { - stamp[ix(t)] = mark; - out.push(t); - stack.push(t); - } - } - while let Some(t) = stack.pop() { - if up.opaq[ix(t)] { - continue; - } - for &q in &facts.parents[ix(t)] { - if stamp[ix(q)] != mark { - stamp[ix(q)] = mark; - out.push(q); - stack.push(q); - } - } - } - out.sort_unstable(); - out -} - -/// Members and their ancestors, not continuing above an opaque term, using -/// the caller's `pos` table (`no_pos` outside the area) both as the visited -/// set during traversal and as the final position map of the sorted area. -#[cfg(any(test, feature = "sharing-reference"))] -fn component_area_pos( - up: &UPrep<'_, A>, - facts: &Facts, - members: &[TermId], - pos: &mut [usize], - no_pos: usize, - stack: &mut Vec, -) -> Vec { - stack.clear(); - let mut out: Vec = Vec::with_capacity(members.len()); - for &t in members { - if pos[ix(t)] == no_pos { - pos[ix(t)] = 0; - out.push(t); - stack.push(t); - } - } - while let Some(t) = stack.pop() { - if up.opaq[ix(t)] { - continue; - } - for &q in &facts.parents[ix(t)] { - if pos[ix(q)] == no_pos { - pos[ix(q)] = 0; - out.push(q); - stack.push(q); - } - } - } - out.sort_unstable(); - for (j, &t) in out.iter().enumerate() { - pos[ix(t)] = j; - } - out -} - -/// `a` precedes `b` (both ascending) when the smallest term of their -/// symmetric difference is in `b`, i.e. `a` leaves it out. -pub(crate) fn set_prec(a: &[TermId], b: &[TermId]) -> bool { - let (mut x, mut y) = (0, 0); - loop { - match (a.get(x), b.get(y)) { - (Some(p), Some(q)) => { - if p == q { - x += 1; - y += 1; - } else { - return q < p; - } - }, - (Some(_) | None, None) => return false, - (None, Some(_)) => return true, - } - } -} - -fn merge_sorted(a: &[TermId], b: &[TermId]) -> Vec { - let mut out: Vec = a.iter().chain(b).copied().collect(); - out.sort_unstable(); - out -} - -/// A component choice: its delta and its set of stored members. -type Choice = (D, Vec); - -fn better(delta: &D, set: &[TermId], cur: Option<&Choice>) -> bool { - match cur { - None => true, - Some((d, s)) => delta < d || (delta == d && set_prec(set, s)), - } -} - -struct CompCtx<'a, 'd, A: Arith = Fast> { - up: &'a UPrep<'d, A>, - #[cfg(any(test, feature = "sharing-reference"))] - members: Vec, - area: Vec, - root_mult: Vec<(TermId, A::U)>, - stored_in: Vec, - #[cfg(any(test, feature = "sharing-reference"))] - phi0: A::U, - #[cfg(any(test, feature = "sharing-reference"))] - slack: A::S, - /// Scratch: values of area terms, valid where `stamp == epoch`. - vals: Vec>, - stamp: Vec, - epoch: u32, - avail: Vec, -} - -impl CompCtx<'_, '_, A> { - /// Cost of the component's parts with `avail` and `chosen` (entries of - /// undecided members omitted), and the evaluation work. - fn phi(&mut self, chosen: &[TermId]) -> Result<(A::U, u64), SharingError> { - if self.epoch == u32::MAX { - self.stamp.fill(0); - self.epoch = 0; - } - self.epoch += 1; - let ep = self.epoch; - let mut work = 0u64; - for k in 0..self.area.len() { - let t = self.area[k]; - let (v, s) = { - let vals = &self.vals; - let stamp = &self.stamp; - let base = &self.up.base; - let avail = &self.avail; - let get = |c: TermId| { - if stamp[ix(c)] == ep { &vals[ix(c)] } else { &base[ix(c)] } - }; - let av = |c: TermId| avail[ix(c)]; - self.up.node(&get, &av, t)? - }; - self.vals[ix(t)] = v; - self.stamp[ix(t)] = ep; - work = work.saturating_add(1 + s); - } - let get = |c: TermId| { - if self.stamp[ix(c)] == ep { - &self.vals[ix(c)] - } else { - &self.up.base[ix(c)] - } - }; - let mut total = A::U::default(); - for (r, m) in &self.root_mult { - total = A::add(&total, &A::mul(m, &get(*r).cost)); - } - for &c in &self.stored_in { - total = A::add(&total, &get(c).inl); - } - for &x in chosen { - total = A::add(&total, &get(x).inl); - } - if A::saturated(&total) { - return Err(overflow()); - } - Ok((total, work)) - } -} - -// --------------------------------------------------------------------------- -// Reclassifying branch and bound with splitting (Lean `SCtx`) -// --------------------------------------------------------------------------- - -/// Best `(delta, set)` per chosen count (index = set size). -type CTable = Vec>>; - -#[cfg(any(test, feature = "sharing-reference"))] -fn table_best(tb: &CTable) -> Option { - tb.iter().flatten().map(|(d, _)| *d).min() -} - -#[cfg(test)] -fn table_add(tb: &mut CTable, e: Choice) { - let k = e.1.len(); - if tb.len() <= k { - tb.resize(k + 1, None); - } - if better(&e.0, &e.1, tb[k].as_ref()) { - tb[k] = Some(e); - } -} - -#[cfg(test)] -fn table_trim(tb: CTable, slack: i128) -> CTable { - match table_best(&tb) { - None => tb, - Some(b) => { - tb.into_iter().map(|o| o.filter(|(d, _)| *d <= b + slack)).collect() - }, - } -} - -// --------------------------------------------------------------------------- -// Table-count knapsack -// --------------------------------------------------------------------------- - -/// "No element": the first difference of two equal sets (no term has this -/// ID). -const NO_DIFF: TermId = TermId::MAX; - -/// The least element of the symmetric difference of two ascending sets -/// (`NO_DIFF` if they are equal): at the first position where the lists -/// differ, the smaller element is in one set only. -fn first_diff(a: &[TermId], b: &[TermId]) -> TermId { - let (mut i, mut j) = (0, 0); - loop { - match (a.get(i), b.get(j)) { - (Some(x), Some(y)) if x == y => { - i += 1; - j += 1; - }, - (Some(x), Some(y)) => return (*x).min(*y), - (Some(x), None) => return *x, - (None, Some(y)) => return *y, - (None, None) => return NO_DIFF, - } - } -} - -/// One layer of [`Knapsack`]: per cell (total count) the least delta, the -/// rank of the cell's set among the layer's sets (`set_prec` order), and a -/// sparse table over the first differences of rank-adjacent sets. -struct KnapsackLayer { - delta: Vec>, - rank: Vec, - /// `sparse[i][r]`: the least first difference among the rank-adjacent - /// pairs `r .. r + 2^i`. - sparse: Vec>, -} - -impl KnapsackLayer { - /// The least element of the symmetric difference of the sets of two - /// distinct cells. For sets in ascending `set_prec` order (lexicographic - /// on their indicator vectors), it is the least first difference of the - /// rank-adjacent pairs between them, as for sorted strings. - fn first_diff(&self, a: usize, b: usize) -> TermId { - let (ra, rb) = (self.rank[a], self.rank[b]); - let (lo, hi) = if ra < rb { (ra, rb) } else { (rb, ra) }; - let level = (hi - lo).ilog2() as usize; - self.sparse[level][lo].min(self.sparse[level][hi - (1 << level)]) - } -} - -/// The shared phase-1 table-count knapsack for total counts -/// `<= cap`: after the components `0..j`, every cell `c` holds the least -/// `(delta, set)` under [`better`] (delta, then `set_prec`) over the choices -/// of one entry per component table with counts summing to `c`. The sets of -/// distinct choices differ (the components are disjoint), so `better` is a -/// strict total order on them, and adding the same entry of a later -/// component preserves it; each cell is therefore the least combination of -/// the cells of the previous layer with the entries of the next table. -/// -/// The sets themselves are not built. Comparing `S_a + X` with `S_b + Y` -/// (`S` from earlier components, `X`, `Y` entries of the next one) needs the -/// least element of the symmetric difference: the smaller of that of `S_a, -/// S_b` (from the previous layer's ranks) and that of `X, Y`; the set -/// without it precedes. `back` keeps the entry each cell took, to rebuild -/// the chosen set. -struct Knapsack { - last: KnapsackLayer, - /// Per component, per cell: the count of the entry taken (`usize::MAX` - /// where the cell is empty). - back: Vec>, -} - -impl Knapsack { - fn run(tables: &[&CTable], cap: usize) -> Result { - let mut layer = KnapsackLayer { - delta: vec![None; cap + 1], - rank: vec![0; cap + 1], - sparse: Vec::new(), - }; - layer.delta[0] = Some(D::of(0)); - let mut back: Vec> = Vec::with_capacity(tables.len()); - for tab in tables { - let prev = &layer; - // Whether choice `(a, ka)` precedes `(b, kb)` (distinct choices). - let prec = |(a, ka): (usize, usize), (b, kb): (usize, usize)| { - let old = if a == b { NO_DIFF } else { prev.first_diff(a, b) }; - let (xa, xb) = (&tab[ka], &tab[kb]); - let new = match (xa, xb) { - (Some((_, xa)), Some((_, xb))) if ka != kb => first_diff(xa, xb), - _ => NO_DIFF, - }; - if old < new { - prev.rank[a] < prev.rank[b] - } else { - xa.as_ref().is_none_or(|(_, xa)| xa.binary_search(&new).is_err()) - } - }; - let mut delta: Vec> = vec![None; cap + 1]; - let mut choice: Vec<(usize, usize)> = vec![(0, 0); cap + 1]; - let mut took: Vec = vec![usize::MAX; cap + 1]; - for c in 0..=cap { - let mut best: Option<(D, usize, usize)> = None; - for (k, entry) in tab.iter().enumerate().take(c + 1) { - let Some((dk, _)) = entry else { continue }; - let a = c - k; - let Some(da) = &prev.delta[a] else { continue }; - let d = da.plus(dk)?; - if best.as_ref().is_none_or(|(bd, ba, bk)| { - d < *bd || (d == *bd && prec((a, k), (*ba, *bk))) - }) { - best = Some((d, a, k)); - } - } - if let Some((d, a, k)) = best { - delta[c] = Some(d); - choice[c] = (a, k); - took[c] = k; - } - } - // Rank the new sets and record the first differences of neighbours. - let mut order: Vec = - (0..=cap).filter(|&c| delta[c].is_some()).collect(); - order.sort_by(|&u, &v| { - if u == v { - std::cmp::Ordering::Equal - } else if prec(choice[u], choice[v]) { - std::cmp::Ordering::Less - } else { - std::cmp::Ordering::Greater - } - }); - let mut rank = vec![0usize; cap + 1]; - for (r, &c) in order.iter().enumerate() { - rank[c] = r; - } - let adjacent: Vec = order - .windows(2) - .map(|w| { - let ((a, ka), (b, kb)) = (choice[w[0]], choice[w[1]]); - let old = if a == b { NO_DIFF } else { prev.first_diff(a, b) }; - let new = match (&tab[ka], &tab[kb]) { - (Some((_, xa)), Some((_, xb))) if ka != kb => first_diff(xa, xb), - _ => NO_DIFF, - }; - old.min(new) - }) - .collect(); - let mut sparse = vec![adjacent]; - let mut width = 1; - loop { - let below = &sparse[sparse.len() - 1]; - if below.len() <= width { - break; - } - let next: Vec = (0..below.len() - width) - .map(|r| below[r].min(below[r + width])) - .collect(); - sparse.push(next); - width *= 2; - } - layer = KnapsackLayer { delta, rank, sparse }; - back.push(took); - } - Ok(Knapsack { last: layer, back }) - } - - /// The set of cell `c` of the last layer, ascending. - fn set(&self, tables: &[&CTable], mut c: usize) -> Vec { - let mut out: Vec = Vec::new(); - for (tab, took) in tables.iter().zip(&self.back).rev() { - let k = took[c]; - if let Some(Some((_, s))) = tab.get(k) { - out.extend_from_slice(s); - } - c -= k; - } - out.sort_unstable(); - out - } -} - -/// The knapsack as a plain dynamic program over explicit sets (the -/// reference of [`Knapsack`]). -#[cfg(test)] -fn knapsack_reference(tables: &[&CTable], cap: usize) -> Vec> { - let mut dp: Vec> = vec![None; cap + 1]; - dp[0] = Some((0, Vec::new())); - for by_size in tables { - let mut ndp: Vec> = vec![None; cap + 1]; - for c in 0..dp.len() { - let Some((d, s)) = &dp[c] else { continue }; - for (k, opt) in by_size.iter().enumerate() { - let Some((dk, sk)) = opt else { continue }; - if c + k > cap { - continue; - } - let cand = (d + dk, merge_sorted(s, sk)); - if better(&cand.0, &cand.1, ndp[c + k].as_ref()) { - ndp[c + k] = Some(cand); - } - } - } - dp = ndp; - } - dp -} - -// --------------------------------------------------------------------------- -// Order and driver -// --------------------------------------------------------------------------- - -/// Bound on telescope spine lengths (Lean `teleSubaddEnd = Ixon.tagNEnd5 4`, -/// 4,311,811,080): below it the TagN Share-flag width is subadditive -/// (`tag4_len(a + b) <= tag4_len(a) + tag4_len(b)`), which the certain-excluded -/// class relies on when two telescopes merge. Only the reference route's -/// optimality argument needs it; the family route checks no spine bound. -#[cfg(any(test, feature = "sharing-reference"))] -pub(crate) const TELE_SUBADD_END: u64 = TagN::end5(4); - -/// Whether every telescope spine length is below [`TELE_SUBADD_END`]. -#[cfg(any(test, feature = "sharing-reference"))] -pub(crate) fn spines_within_bound(spine_len: &[u64]) -> bool { - spine_len.iter().all(|&l| l < TELE_SUBADD_END) -} - -/// First count with the same TagN (f = 0) width as `k` (`tag0_len`): the -/// start of `k`'s rung. Mirrors Lean `tag0BracketStart`. -fn tag0_bracket_start(k: u64) -> u64 { - if k < TagN::end1(0) { - 0 - } else if k < TagN::end2(0) { - TagN::end1(0) - } else if k < TagN::end3(0) { - TagN::end2(0) - } else if k < TagN::end4(0) { - TagN::end3(0) - } else if k < TagN::end5(0) { - TagN::end4(0) - } else { - TagN::end5(0) - } -} - -/// An upper bound on `tag0_len(k + 1) - tag0_len(k)` for every `k < n`: the -/// TagN (f = 0) width grows by one byte at 128, 16512, 82048 and 16859264, -/// and by four at 4311826560. Mirrors Lean `tag0StepBound`. -fn tag0_step_bound(n: u64) -> u64 { - if n < TagN::end5(0) { 1 } else { 4 } -} - -/// Pinned table order of a stored set: stored descendants first; among -/// ready terms the larger in-degree first, then the smaller ID. -pub(crate) fn pinned_order( - dag: &SharingDag, - deg: &[u64], - stored: &[TermId], -) -> Vec { - let nodes = dag.nodes(); - let mut is_stored = vec![false; nodes.len()]; - for &t in stored { - is_stored[ix(t)] = true; - } - // Nearest stored descendants of each stored term (`seen[x] == k + 1`: - // visited from the `k`-th stored term). - let mut pending: Vec = Vec::with_capacity(stored.len()); - let mut dependents: FxHashMap> = FxHashMap::default(); - let mut seen: Vec = vec![0; nodes.len()]; - let mut stack: Vec = Vec::new(); - for (k, &t) in stored.iter().enumerate() { - stack.clear(); - stack.extend_from_slice(nodes[ix(t)].children().as_slice()); - let mut deps = 0usize; - while let Some(x) = stack.pop() { - if seen[ix(x)] == k + 1 { - continue; - } - seen[ix(x)] = k + 1; - if is_stored[ix(x)] { - deps += 1; - dependents.entry(x).or_default().push(t); - } else { - stack.extend_from_slice(nodes[ix(x)].children().as_slice()); - } - } - pending.push(deps); - } - let pos: FxHashMap = - stored.iter().enumerate().map(|(k, &t)| (t, k)).collect(); - let mut heap: std::collections::BinaryHeap<(u64, std::cmp::Reverse)> = - std::collections::BinaryHeap::new(); - for (k, &t) in stored.iter().enumerate() { - if pending[k] == 0 { - heap.push((deg[ix(t)], std::cmp::Reverse(t))); - } - } - let mut order = Vec::with_capacity(stored.len()); - while let Some((_, std::cmp::Reverse(t))) = heap.pop() { - order.push(t); - if let Some(ds) = dependents.get(&t) { - for &d in ds { - let k = pos[&d]; - pending[k] -= 1; - if pending[k] == 0 { - heap.push((deg[ix(d)], std::cmp::Reverse(d))); - } - } - } - } - order -} - -/// Exact uniform-width optimization of a DAG. -#[cfg(any(test, feature = "sharing-reference"))] -pub(crate) fn optimize_uniform( - w: u64, - dag: &SharingDag, - meter: &mut Meter<'_>, -) -> Result { - if w == 0 { - return Err(SharingError::FormatBound(FormatBound::UniformWidth { w })); - } - let (mut result, plan) = - optimize_uniform_with(w, dag, &DagPrep::new(dag), meter)?; - let built = plan.build(dag, &result.table_terms, meter.limits())?; - result.roots = built.roots; - result.sharing = built.entries; - result.variable_len = built.measured; - Ok(result) -} - -/// The tables of one DAG that do not depend on the uniform width, computed -/// once and shared by the candidates of the tiered construction. Each is -/// exactly what `optimize_uniform` and `tiered_at` computed for themselves. -pub(crate) struct DagPrep { - /// `Node::own_len` of every term. - pub(crate) own: Vec, - /// `C_0` (`all_costs` with no dictionary), with each term's work. - pub(crate) base: Evaluation, - /// The parent edges and what each parent reads (phase 3). - pub(crate) edges: ReadEdges, - pub(crate) facts: Facts, - /// Spine lengths with no stop terms. - spine_len: Vec, - /// Empty sizes capped at EMPTY_SIZE_CAP, ONLY for certain-excluded tests. - /// Errors concern unencodable own-payload counts, not large unfolded costs. - size: Result, SharingError>, -} - -impl DagPrep { - pub(crate) fn new(dag: &SharingDag) -> Self { - let nodes = dag.nodes(); - let n = nodes.len(); - let own: Vec = nodes.iter().map(Node::own_len).collect(); - let base = Evaluation::new(nodes, &own, &super::dict::NoWidths); - let edges = ReadEdges::new(nodes); - let facts = graph_facts(dag); - let (spine_len, spine_tail) = spine_tables(nodes, &vec![false; n]); - let size = empty_sizes_capped(nodes, &spine_len, &spine_tail); - DagPrep { own, base, edges, facts, spine_len, size } - } -} - -/// Initial classification shared by the exact path and the component families. -struct Initial<'d, A: Arith = Fast> { - up: UPrep<'d, A>, - #[cfg(any(test, feature = "sharing-reference"))] - cand: Vec, - #[cfg(any(test, feature = "sharing-reference"))] - bounds: Bounds, - #[cfg(any(test, feature = "sharing-reference"))] - vis0: (Vec, Vec), - #[cfg(any(test, feature = "sharing-reference"))] - theta: A::S, - cs: Vec, - ce_terms: Vec, - unc: Vec, - low: Vec, - comps: Vec>, - slack: A::S, -} - -/// The initial canonical classification, before any recursive reclassification. -fn initial<'d, A: Arith>( - w: u64, - dag: &'d SharingDag, - prep: &DagPrep, - meter: &mut Meter<'_>, -) -> Result, SharingError> { - if w == 0 { - return Err(SharingError::FormatBound(FormatBound::UniformWidth { w })); - } - let wu = A::u(w); - let nodes = dag.nodes(); - let n = nodes.len(); - let facts = &prep.facts; - let spine_len = &prep.spine_len; - // Predicate-sufficient min(C_0, cap); never reused as a general model cost. - let size: &[u128] = prep.size.as_ref().map_err(Clone::clone)?; - // Classes: certain-excluded, low degree, and the gain test on visible - // counts with threshold theta. - let ce: Vec = - (0..n).map(|t| certainly_excluded(facts.occ[t], size[t], u(w))).collect(); - let cand: Vec = (0..n).map(|t| !ce[t] && facts.deg[t] >= 2).collect(); - let bounds = uniform_bounds::(nodes, &wu, &cand)?; - let vis0 = visible_counts(dag, &cand); - let classify = |theta: &A::S| -> Result, SharingError> { - let mut cls = Vec::with_capacity(n); - for t in 0..n { - cls.push(if ce[t] { - UniformClass::CertainExcluded - } else if facts.deg[t] < 2 { - UniformClass::LowDegree - } else if stored_gain::( - &nodes[t], - vis0.0[t], - vis0.1[t], - spine_len[t], - &bounds.inl[t], - &bounds.merged[t], - &wu, - )? >= *theta - { - UniformClass::CertainStored - } else { - UniformClass::Uncertain - }); - } - Ok(cls) - }; - // theta = 1 + tag0_step_bound(#candidates) always holds (one more entry - // grows the table count by at most that many bytes; 2 below 4311826560 - // candidates); 1 when every minimum lies in one count bracket. - let n_cand = len64(cand.iter().filter(|&&c| c).count()); - let theta_max = A::S::of(i128::from(tag0_step_bound(n_cand)) + 1); - let cls_max = classify(&theta_max)?; - let n_cs_max = len64( - cls_max.iter().filter(|&&c| c == UniformClass::CertainStored).count(), - ); - let one_bracket = tag0_len(n_cand) == tag0_len(n_cs_max); - let theta = if one_bracket { A::S::of(1) } else { theta_max }; - let cls = if one_bracket { classify(&theta)? } else { cls_max }; - let pick = |c: UniformClass| -> Vec { - (0..n).filter(|&t| cls[t] == c).collect() - }; - let cs = pick(UniformClass::CertainStored); - let ce_terms = pick(UniformClass::CertainExcluded); - let unc = pick(UniformClass::Uncertain); - let low = pick(UniformClass::LowDegree); - // The candidate count is a statistic here (Lean `Stats.candidates`), not - // a limit: `max_candidates` bounds only the width-state search (`search`), - // and Lean's uniform optimizer has no such limit. - meter.stats.candidates = len64(cs.len() + unc.len()); - let mut opaq = vec![false; n]; - for &t in &cs { - opaq[ix(t)] = true; - } - let up = UPrep::::new(nodes, wu, opaq)?; - let comps = uncertain_components(dag, &cls); - let k_cs = u64::try_from(cs.len()).unwrap_or(u64::MAX); - let k_unc = u64::try_from(unc.len()).unwrap_or(u64::MAX); - // Largest table-count difference between two candidate-only sets that - // contain the certain-stored terms. - let slack = A::S::of( - i128::from(tag0_len(k_cs.saturating_add(k_unc))) - - i128::from(tag0_len(k_cs)), - ); - #[cfg(not(any(test, feature = "sharing-reference")))] - let _ = theta; - Ok(Initial { - up, - #[cfg(any(test, feature = "sharing-reference"))] - cand, - #[cfg(any(test, feature = "sharing-reference"))] - bounds, - #[cfg(any(test, feature = "sharing-reference"))] - vis0, - #[cfg(any(test, feature = "sharing-reference"))] - theta, - cs, - ce_terms, - unc, - low, - comps, - slack, - }) -} - -fn combine_uniform( - w: u64, - dag: &SharingDag, - prep: &DagPrep, - meter: &mut Meter<'_>, - stage: Initial<'_, A>, - results: Vec<(Choice, CTable)>, -) -> Result<(UniformSharingResult, UniformPlan), SharingError> { - let Initial { up, cs, ce_terms, unc, low, comps, .. } = stage; - let nodes = dag.nodes(); - let n = nodes.len(); - let facts = &prep.facts; - let k_cs = len64(cs.len()); - let p = prof::scope(Phase::Knapsack); - let states_visited = meter.stats.states_created; - // Combine: per-component optima, unless a lower count bracket is shorter. - // The components are disjoint, so the union of their sets is their - // concatenation, sorted once. - let mut chosen_x: Vec = Vec::new(); - let mut chosen_delta = A::S::of(0); - for ((d, s), _) in &results { - chosen_x.extend_from_slice(s); - chosen_delta = chosen_delta.plus(d)?; - } - chosen_x.sort_unstable(); - let len_u64 = |v: usize| u64::try_from(v).unwrap_or(u64::MAX); - let count_len = |k: u64| A::S::of(i128::from(tag0_len(k))); - let k0 = k_cs.saturating_add(len_u64(chosen_x.len())); - let start = tag0_bracket_start(k0); - let mut lower_bracket = false; - if start > k_cs { - let cap = usize::try_from(start - 1 - k_cs).map_err(|_e| overflow())?; - let cells = (results.len() + 1).saturating_mul(cap + 1); - if u64::try_from(cells).unwrap_or(u64::MAX) - > meter.limits().max_knapsack_cells - { - return Err(SharingError::ResourceExhausted(super::ResourceExhausted { - resource: super::Resource::KnapsackCells, - limit: meter.limits().max_knapsack_cells, - })); - } - let tables: Vec<&CTable> = results.iter().map(|(_, t)| t).collect(); - let ks = Knapsack::run(&tables, cap)?; - // The per-component optimum is replaced by the least cell under - // (length, set_prec) when that cell precedes it: the least of the - // optimum and every cell, as a scan of the cells in count order with - // `set_prec` on ties keeps. The cells' ranks are their `set_prec` order. - let l0 = chosen_delta.plus(&count_len(k_cs + len_u64(chosen_x.len())))?; - let mut best: Option<(A::S, usize, usize, &A::S)> = None; - for c in 0..=cap { - let Some(d) = &ks.last.delta[c] else { continue }; - let l = d.plus(&count_len(k_cs + len_u64(c)))?; - let r = ks.last.rank[c]; - if best - .as_ref() - .is_none_or(|(bl, br, _, _)| l < *bl || (l == *bl && r < *br)) - { - best = Some((l, r, c, d)); - } - } - if let Some((l, _, c, d)) = best { - let s = ks.set(&tables, c); - if l < l0 || (l == l0 && set_prec(&s, &chosen_x)) { - chosen_x = s; - chosen_delta = d.clone(); - lower_bracket = true; - } - } - } - drop(p); - let p = prof::scope(Phase::UniformMaterialize); - // Model length from the truncated evaluation. - let mut base_total = A::U::default(); - for &r in dag.roots() { - base_total = A::add(&base_total, &up.base[ix(r)].cost); - } - for &c in &cs { - base_total = A::add(&base_total, &up.base[ix(c)].inl); - } - if A::saturated(&base_total) { - return Err(overflow()); - } - let k = k_cs + len_u64(chosen_x.len()); - // The model is the length of an output: it must be a `u64`. - let model = A::signed(&base_total)? - .plus(&chosen_delta)? - .plus(&count_len(k))? - .to_u64() - .filter(|&m| m < u64::MAX) - .ok_or_else(overflow)?; - meter.output(model)?; - // Materialize in the pinned order with the full evaluation and check. - let stored = merge_sorted(&cs, &chosen_x); - let order = pinned_order(dag, &facts.deg, &stored); - if order.len() != stored.len() { - return Err(internal("pinned order dropped a stored term")); - } - let mut index: Vec> = vec![None; n]; - for (pos, &t) in order.iter().enumerate() { - index[ix(t)] = Some(len_u64(pos)); - } - let dict = UniformIndex { index, width: w }; - let own = &prep.own; - let mut work = 0u64; - let costs = all_costs(nodes, own, &dict, &mut work); - let plan = decide_dependent( - nodes, - own, - &dict, - &costs, - &order, - dag.roots(), - &mut work, - )?; - meter.work(work)?; - let predicted = plan.predicted.plus_u64(tag0_len(k)); - if predicted != Len::new(model) { - return Err(internal(format!( - "uniform model length {model} differs from the full evaluation {predicted:?}" - ))); - } - let mut unshared = Len::new(tag0_len(0)); - for &r in dag.roots() { - unshared = unshared.plus(prep.base.costs[ix(r)]); - } - let result = UniformSharingResult { - roots: Vec::new(), - sharing: Vec::new(), - table_terms: order, - model_len: model, - variable_len: 0, - unshared_len: unshared.exact(), - stored, - certain_stored: cs, - certain_excluded: ce_terms, - uncertain: unc, - low_degree: low, - components: comps, - states_visited, - lower_bracket, - stats: meter.stats.clone(), - #[cfg(test)] - component_tables: results - .into_iter() - .map(|(_, table)| { - table.into_iter().map(|row| row.map(|(d, s)| (d.to_big(), s))).collect() - }) - .collect(), - }; - #[cfg(not(test))] - drop(results); - drop(p); - Ok((result, UniformPlan { dict, plan, model, k })) -} - -/// The decisions of one uniform-width family optimum: -/// the table indices of the stored terms and the materialization choice of -/// every entry top and needed term. The expressions are either built and -/// checked ([`UniformPlan::build`]) or only described -/// ([`UniformPlan::shares`]); both read the same decisions. -pub(crate) struct UniformPlan { - dict: UniformIndex, - plan: DependentPlan, - /// The uniform-model variable length. - model: u64, - /// The table length. - k: u64, -} - -/// The phase-1 expressions of a [`UniformPlan`], checked by re-expansion. -pub(crate) struct UniformExprs { - pub(crate) entries: Vec>, - pub(crate) roots: Vec>, - /// Real variable length (Shares at their TagN width, as `put_expr` - /// writes them). - pub(crate) measured: u64, - /// Pointer-distinct expression nodes, as the re-expansion check counted - /// them (`None` when the check without interning reported a mismatch and - /// the interning build accepted the encoding, which it never should). - pub(crate) visited: Option, -} - -impl UniformPlan { - /// What the expressions contain, from the decisions alone (the phase-1 - /// dry run of the tiered construction): [`PlanShares`], and the real - /// variable length, which is the model with every Share priced at its - /// TagN width instead of `w`, Share by Share. Charges `max_input_nodes` - /// with the expression nodes the build allocates, which are the input - /// nodes the re-expansion check of the built expressions charges. - pub(crate) fn shares( - &self, - dag: &SharingDag, - table: &[TermId], - limits: &ExactSharingLimits, - ) -> Result<(PlanShares, u64), SharingError> { - let _p = prof::scope(Phase::UniformCheck); - let position = - |t: TermId| self.dict.index[ix(t)].and_then(|i| usize::try_from(i).ok()); - let shares = - self.plan.shares(dag.nodes(), table, dag.roots(), &position)?; - dag.check_input_nodes(shares.nodes, limits)?; - let mut real = i128::from(self.model); - for (i, &r) in shares.refs.iter().enumerate() { - let delta = i128::from(tag4_len(len64(i))) - i128::from(self.dict.width); - real = real - .checked_add(i128::from(r).checked_mul(delta).ok_or_else(overflow)?) - .ok_or_else(overflow)?; - } - let real = u64::try_from(real) - .ok() - .filter(|&l| l < u64::MAX) - .ok_or_else(overflow)?; - Ok((shares, real)) - } - - /// Build the table `table` and the roots, check them by re-expansion - /// (charging `max_input_nodes` with their nodes) and measure their real - /// length. - pub(crate) fn build( - &self, - dag: &SharingDag, - table: &[TermId], - limits: &ExactSharingLimits, - ) -> Result { - let p = prof::scope(Phase::UniformMaterialize); - let (entries, roots) = - build_dependent(dag.nodes(), &self.dict, &self.plan, table, dag.roots())?; - drop(p); - let _p = prof::scope(Phase::UniformCheck); - // The re-expansion check, then the real length (Shares at their TagN - // width, `expr_len`); both in one walk when the check passes. - let (visited, measured) = - match dag.check_and_measure(table, &entries, &roots, &tag4_len) { - Some((visited, len)) => { - dag.check_input_nodes(visited, limits)?; - (Some(visited), len) - }, - None => { - dag.check_reexpansion( - table, - &entries, - &roots, - limits, - "materialized encoding changes the expanded AST", - "materialized entries do not expand to the stored terms", - )?; - let len = - exprs_len_with(self.k, entries.iter().chain(&roots), &tag4_len) - .ok_or_else(overflow)?; - (None, len) - }, - }; - Ok(UniformExprs { entries, roots, measured, visited }) - } -} - -/// Exact uniform-width sharing of ordered, fully expanded roots. -#[cfg(any(test, feature = "sharing-reference"))] -pub fn optimize_sharing_uniform( - w: u64, - roots: &[Arc], - limits: &ExactSharingLimits, -) -> Result { - let mut meter = Meter::new(limits); - let dag = SharingDag::build(roots, None, &mut meter)?; - optimize_uniform(w, &dag, &mut meter) -} - -/// Expand `c`'s table and re-share it with the uniform-width optimum. -#[cfg(any(test, feature = "sharing-reference"))] -pub fn normalize_constant_sharing_uniform( - w: u64, - c: &Constant, - limits: &ExactSharingLimits, -) -> Result<(Constant, UniformSharingResult), SharingError> { - let mut meter = Meter::new(limits); - let roots = constant_info_root_exprs(&c.info); - let dag = SharingDag::build(&roots, Some(&c.sharing), &mut meter)?; - let fixed = constant_fixed_len(c).ok_or_else(overflow)?; - let result = optimize_uniform(w, &dag, &mut meter)?; - let info = rebuild_constant_info(&c.info, &result.roots)?; - let out = Constant { - info, - sharing: result.sharing.clone(), - refs: c.refs.clone(), - univs: c.univs.clone(), - }; - let mut bytes = Vec::new(); - out.put(&mut bytes); - if u64::try_from(bytes.len()).ok() != fixed.checked_add(result.variable_len) { - return Err(internal("uniform output length differs from its accounting")); - } - Ok((out, result)) -} - -#[cfg(test)] -mod count_bracket_tests { - use super::{tag0_bracket_start, tag0_len, tag0_step_bound}; - use crate::tag::TagN; - - fn counts() -> Vec { - let mut v = vec![0u64, 1, 2, 255, 256, u64::MAX - 1, u64::MAX]; - for e in [ - TagN::end1(0), - TagN::end2(0), - TagN::end3(0), - TagN::end4(0), - TagN::end5(0), - ] { - v.extend([e - 1, e, e + 1]); - } - v - } - - /// Same checks as the Lean `widthTests` for `tag0BracketStart` and - /// `tag0StepBound`. - #[test] - fn bracket_start_and_step_bound() { - for k in counts() { - let s = tag0_bracket_start(k); - assert_eq!(tag0_len(s), tag0_len(k), "bracket start of {k}"); - assert!(s == 0 || tag0_len(s - 1) < tag0_len(k), "least start of {k}"); - if k > 0 { - assert!(tag0_len(k) - tag0_len(k - 1) <= tag0_step_bound(k), "{k}"); - } - } - assert_eq!(tag0_step_bound(82048), 1); - assert_eq!(tag0_step_bound(TagN::end5(0) - 1), 1); - assert_eq!(tag0_step_bound(TagN::end5(0)), 4); - } -} - -#[cfg(test)] -#[allow(clippy::cast_possible_truncation, clippy::needless_range_loop)] -mod knapsack_tests { - use super::{CTable, Knapsack, TermId, knapsack_reference, set_prec}; - - struct Rng(u64); - - impl Rng { - fn below(&mut self, n: u64) -> u64 { - self.0 ^= self.0 << 13; - self.0 ^= self.0 >> 7; - self.0 ^= self.0 << 17; - self.0 % n.max(1) - } - } - - /// The rank-based knapsack returns the reference's least `(delta, set)` - /// in every cell, and its ranks order the cells' sets by `set_prec`; half - /// of the cases use term IDs on both sides of the old 32-bit ID limit. - #[test] - fn knapsack_matches_explicit_sets() { - let mut rng = Rng(0x9E37_79B9_7F4A_7C15); - let mut cells_checked = 0usize; - for case in 0..400 { - // Disjoint components over a shuffled domain of term IDs. - let base: TermId = if case % 2 == 1 { (1 << 32) - 150 } else { 0 }; - let mut domain: Vec = (base..base + 300).collect(); - for i in (1..domain.len()).rev() { - let j = rng.below(i as u64 + 1) as usize; - domain.swap(i, j); - } - let comps = rng.below(30) as usize; - let mut next = 0usize; - let mut tables: Vec = Vec::new(); - for _ in 0..comps { - let size = 1 + rng.below(6) as usize; - let mut members: Vec = domain[next..next + size].to_vec(); - next += size; - members.sort_unstable(); - let mut tb: CTable = Vec::new(); - for k in 0..=size { - if rng.below(10) < 7 || k == 0 { - // A random `k`-subset; small deltas make ties common. - let mut pool = members.clone(); - let mut set = Vec::new(); - for _ in 0..k { - let i = rng.below(pool.len() as u64) as usize; - set.push(pool.swap_remove(i)); - } - set.sort_unstable(); - let d = i128::from(rng.below(4) as u8) - 1; - tb.push(Some((d, set))); - } else { - tb.push(None); - } - } - tables.push(tb); - } - let total: usize = tables.iter().map(|t| t.len() - 1).sum(); - let cap = rng.below(total as u64 + 4) as usize; - let refs: Vec<&CTable> = tables.iter().collect(); - let want = knapsack_reference(&refs, cap); - let got = Knapsack::run(&refs, cap).unwrap(); - let mut some: Vec = Vec::new(); - for c in 0..=cap { - assert_eq!( - got.last.delta[c], - want[c].as_ref().map(|x| x.0), - "case {case} cell {c}" - ); - if let Some((_, s)) = &want[c] { - assert_eq!(&got.set(&refs, c), s, "case {case} cell {c}"); - some.push(c); - cells_checked += 1; - } - } - for &a in &some { - for &b in &some { - if a != b { - let (sa, sb) = - (&want[a].as_ref().unwrap().1, &want[b].as_ref().unwrap().1); - assert_eq!( - got.last.rank[a] < got.last.rank[b], - set_prec(sa, sb), - "case {case} cells {a} {b}" - ); - } - } - } - } - assert!(cells_checked > 2000); - } -} diff --git a/crates/ixon/src/sharing_exact/uniform/arith.rs b/crates/ixon/src/sharing_exact/uniform/arith.rs deleted file mode 100644 index 10d1bc1a2..000000000 --- a/crates/ixon/src/sharing_exact/uniform/arith.rs +++ /dev/null @@ -1,437 +0,0 @@ -//! The integers of the phase-1 optimizer, in two representations. -//! -//! Lean computes phase 1 with `Nat` and `Int`. Its values are lengths, -//! lower bounds and occurrence-weighted sums, which are nonnegative, and -//! differences of them. Intermediate values can be exponential in the size -//! of the input while the chosen encoding stays small: with no uncertain -//! term stored, `x_(i+1) = App(x_i, x_i)` unfolds to about `2^i` bytes. -//! -//! [`Fast`] computes in `u128` and `i128`. Nonnegative sums saturate at -//! `u128::MAX`; saturation is monotone, so a saturated summand can lose a -//! comparison but a value that is not `u128::MAX` is exact, and the -//! optimizer reports [`FormatBound::LengthOverflow`] as soon as a value it -//! keeps is `u128::MAX`. Signed operations and conversions are checked and -//! report the same error. [`Exact`] computes the same values as [`Nat`] and -//! [`Int`], unbounded, and never overflows. -//! -//! The optimizer runs with [`Fast`] and, if that run reports -//! `LengthOverflow`, runs again from the same meter with [`Exact`] -//! (`family::optimize_at`). A [`Fast`] run that succeeds computed every value -//! exactly, so its result is the [`Exact`] one: the representation changes -//! the time, never a successful result. - -use std::borrow::Cow; -use std::cmp::Ordering; -use std::fmt; - -use num_bigint::{BigInt, BigUint, Sign}; - -use super::{FormatBound, SharingError}; - -fn overflow() -> SharingError { - SharingError::FormatBound(FormatBound::LengthOverflow) -} - -/// A natural number: inline below `2^128`, a `BigUint` from there. -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum Nat { - Small(u128), - /// At least `2^128`. - Big(Box), -} - -impl Nat { - fn of_big(x: BigUint) -> Nat { - match u128::try_from(&x) { - Ok(v) => Nat::Small(v), - Err(_) => Nat::Big(Box::new(x)), - } - } - - fn as_big(&self) -> Cow<'_, BigUint> { - match self { - Nat::Small(v) => Cow::Owned(BigUint::from(*v)), - Nat::Big(b) => Cow::Borrowed(b), - } - } -} - -impl Default for Nat { - fn default() -> Self { - Nat::Small(0) - } -} - -impl Ord for Nat { - fn cmp(&self, other: &Self) -> Ordering { - match (self, other) { - (Nat::Small(a), Nat::Small(b)) => a.cmp(b), - (Nat::Small(_), Nat::Big(_)) => Ordering::Less, - (Nat::Big(_), Nat::Small(_)) => Ordering::Greater, - (Nat::Big(a), Nat::Big(b)) => a.cmp(b), - } - } -} - -impl PartialOrd for Nat { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -/// An integer: inline inside `i128`, a `BigInt` outside it. -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum Int { - Small(i128), - /// Outside `i128`. - Big(Box), -} - -impl Int { - fn of_big(x: BigInt) -> Int { - match i128::try_from(&x) { - Ok(v) => Int::Small(v), - Err(_) => Int::Big(Box::new(x)), - } - } - - fn as_big(&self) -> Cow<'_, BigInt> { - match self { - Int::Small(v) => Cow::Owned(BigInt::from(*v)), - Int::Big(b) => Cow::Borrowed(b), - } - } - - /// `op` on two inline values when it does not overflow, else `big`. - fn combine( - &self, - b: &Int, - op: fn(i128, i128) -> Option, - big: fn(&BigInt, &BigInt) -> BigInt, - ) -> Int { - if let (Int::Small(x), Int::Small(y)) = (self, b) - && let Some(v) = op(*x, *y) - { - return Int::Small(v); - } - Int::of_big(big(&self.as_big(), &b.as_big())) - } -} - -impl Default for Int { - fn default() -> Self { - Int::Small(0) - } -} - -impl Ord for Int { - fn cmp(&self, other: &Self) -> Ordering { - let big_side = |b: &BigInt| { - if b.sign() == Sign::Minus { Ordering::Less } else { Ordering::Greater } - }; - match (self, other) { - (Int::Small(a), Int::Small(b)) => a.cmp(b), - (Int::Small(_), Int::Big(b)) => big_side(b).reverse(), - (Int::Big(a), Int::Small(_)) => big_side(a), - (Int::Big(a), Int::Big(b)) => a.cmp(b), - } - } -} - -impl PartialOrd for Int { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -/// Signed differences of lengths. -pub(crate) trait Delta: - Clone + Ord + Default + fmt::Debug + 'static -{ - fn of(x: i128) -> Self; - /// `self + b`; `LengthOverflow` outside the representation. - fn plus(&self, b: &Self) -> Result; - /// `self - b`; `LengthOverflow` outside the representation. - fn minus(&self, b: &Self) -> Result; - /// `self * b`; `LengthOverflow` outside the representation. - fn times(&self, b: &Self) -> Result; - /// The value, if it is a `u64`. - fn to_u64(&self) -> Option; - /// The exact value. - #[cfg(test)] - fn to_big(&self) -> BigInt; -} - -impl Delta for i128 { - #[inline] - fn of(x: i128) -> Self { - x - } - - #[inline] - fn plus(&self, b: &Self) -> Result { - self.checked_add(*b).ok_or_else(overflow) - } - - #[inline] - fn minus(&self, b: &Self) -> Result { - self.checked_sub(*b).ok_or_else(overflow) - } - - #[inline] - fn times(&self, b: &Self) -> Result { - self.checked_mul(*b).ok_or_else(overflow) - } - - #[inline] - fn to_u64(&self) -> Option { - u64::try_from(*self).ok() - } - - #[cfg(test)] - fn to_big(&self) -> BigInt { - BigInt::from(*self) - } -} - -impl Delta for Int { - fn of(x: i128) -> Self { - Int::Small(x) - } - - fn plus(&self, b: &Self) -> Result { - Ok(self.combine(b, i128::checked_add, |x, y| x + y)) - } - - fn minus(&self, b: &Self) -> Result { - Ok(self.combine(b, i128::checked_sub, |x, y| x - y)) - } - - fn times(&self, b: &Self) -> Result { - Ok(self.combine(b, i128::checked_mul, |x, y| x * y)) - } - - fn to_u64(&self) -> Option { - match self { - Int::Small(v) => u64::try_from(*v).ok(), - Int::Big(_) => None, - } - } - - #[cfg(test)] - fn to_big(&self) -> BigInt { - self.as_big().into_owned() - } -} - -/// A representation of the phase-1 integers: nonnegative values `U` and -/// their differences `S`. -pub(crate) trait Arith: 'static { - type U: Clone + Ord + Default + fmt::Debug + 'static; - type S: Delta; - fn u(x: u64) -> Self::U; - fn count(x: u128) -> Self::U; - /// `a + b`; [`Fast`] saturates. - fn add(a: &Self::U, b: &Self::U) -> Self::U; - /// `a * b`; [`Fast`] saturates. - fn mul(a: &Self::U, b: &Self::U) -> Self::U; - /// `a - b`, for `b <= a`. - fn sub(a: &Self::U, b: &Self::U) -> Self::U; - /// Whether `a` may have saturated; never for [`Exact`]. - fn saturated(a: &Self::U) -> bool; - /// `a` as a difference; `LengthOverflow` outside the representation. - fn signed(a: &Self::U) -> Result; -} - -/// `u128` and `i128`, saturating or checked. -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct Fast; - -/// [`Nat`] and [`Int`]. -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct Exact; - -impl Arith for Fast { - type U = u128; - type S = i128; - - #[inline] - fn u(x: u64) -> u128 { - u128::from(x) - } - - #[inline] - fn count(x: u128) -> u128 { - x - } - - #[inline] - fn add(a: &u128, b: &u128) -> u128 { - a.saturating_add(*b) - } - - #[inline] - fn mul(a: &u128, b: &u128) -> u128 { - a.saturating_mul(*b) - } - - #[inline] - fn sub(a: &u128, b: &u128) -> u128 { - if Self::saturated(a) || Self::saturated(b) { u128::MAX } else { a - b } - } - - #[inline] - fn saturated(a: &u128) -> bool { - *a == u128::MAX - } - - #[inline] - fn signed(a: &u128) -> Result { - i128::try_from(*a).map_err(|_e| overflow()) - } -} - -impl Arith for Exact { - type U = Nat; - type S = Int; - - fn u(x: u64) -> Nat { - Nat::Small(u128::from(x)) - } - - fn count(x: u128) -> Nat { - Nat::Small(x) - } - - fn add(a: &Nat, b: &Nat) -> Nat { - if let (Nat::Small(x), Nat::Small(y)) = (a, b) - && let Some(v) = x.checked_add(*y) - { - return Nat::Small(v); - } - Nat::of_big(&*a.as_big() + &*b.as_big()) - } - - fn mul(a: &Nat, b: &Nat) -> Nat { - if let (Nat::Small(x), Nat::Small(y)) = (a, b) - && let Some(v) = x.checked_mul(*y) - { - return Nat::Small(v); - } - Nat::of_big(&*a.as_big() * &*b.as_big()) - } - - fn sub(a: &Nat, b: &Nat) -> Nat { - if let (Nat::Small(x), Nat::Small(y)) = (a, b) { - return Nat::Small(x - y); - } - Nat::of_big(&*a.as_big() - &*b.as_big()) - } - - fn saturated(_a: &Nat) -> bool { - false - } - - fn signed(a: &Nat) -> Result { - Ok(match a { - Nat::Small(v) => i128::try_from(*v) - .map_or_else(|_e| Int::Big(Box::new(BigInt::from(*v))), Int::Small), - Nat::Big(b) => Int::Big(Box::new(BigInt::from((**b).clone()))), - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn nat(x: &BigUint) -> Nat { - Nat::of_big(x.clone()) - } - - /// The two representations agree wherever the fast one succeeds, the - /// fast one refuses (never wraps) where its range ends, and the exact - /// one is `BigUint`/`BigInt` arithmetic across the inline boundary. - #[test] - fn fast_matches_exact_and_exact_matches_big_integers() { - let mut us: Vec = - [0u64, 1, 2, 7, 255, 1 << 32, u64::MAX - 1, u64::MAX] - .iter() - .map(|&x| BigUint::from(x)) - .collect(); - let edge = BigUint::from(u128::MAX); - us.extend([&edge - 1u32, edge.clone(), &edge + 1u32, &edge * &edge]); - for a in &us { - for b in &us { - let (ea, eb) = (nat(a), nat(b)); - assert_eq!(ea.cmp(&eb), a.cmp(b)); - assert_eq!(Exact::add(&ea, &eb), nat(&(a + b))); - assert_eq!(Exact::mul(&ea, &eb), nat(&(a * b))); - if b <= a { - assert_eq!(Exact::sub(&ea, &eb), nat(&(a - b))); - } - if let (Ok(x), Ok(y)) = (u128::try_from(a), u128::try_from(b)) { - let sum = Fast::add(&x, &y); - assert_eq!(Fast::saturated(&sum), a + b >= edge); - if !Fast::saturated(&sum) { - assert_eq!(BigUint::from(sum), a + b); - } - if b <= a { - let diff = Fast::sub(&x, &y); - assert_eq!( - Fast::saturated(&diff), - Fast::saturated(&x) || Fast::saturated(&y) - ); - if !Fast::saturated(&diff) { - assert_eq!(BigUint::from(diff), a - b); - } - } - } - } - let signed = Exact::signed(&nat(a)).unwrap(); - assert_eq!(signed.to_big(), BigInt::from(a.clone())); - if let Ok(x) = u128::try_from(a) { - if i128::try_from(x).is_ok() { - assert_eq!(Fast::signed(&x).unwrap().to_big(), signed.to_big()); - } else { - assert_eq!(Fast::signed(&x), Err(overflow())); - } - } - } - let mut ss: Vec = [i128::MIN, -3, -1, 0, 1, 5, i128::MAX] - .iter() - .map(|&x| BigInt::from(x)) - .collect(); - ss.extend([ - BigInt::from(i128::MIN) - 1, - BigInt::from(i128::MAX) + 1, - BigInt::from(i128::MAX) * BigInt::from(i128::MAX), - -(BigInt::from(i128::MAX) * BigInt::from(7)), - ]); - for a in &ss { - for b in &ss { - let (ia, ib) = (Int::of_big(a.clone()), Int::of_big(b.clone())); - assert_eq!(ia.cmp(&ib), a.cmp(b), "{a} {b}"); - let fit = |r: Result, e: &BigInt| match r { - Ok(v) => assert_eq!(&BigInt::from(v), e), - Err(err) => { - assert_eq!(err, overflow()); - assert!(i128::try_from(e).is_err()); - }, - }; - assert_eq!(ia.plus(&ib).unwrap().to_big(), a + b); - assert_eq!(ia.minus(&ib).unwrap().to_big(), a - b); - assert_eq!(ia.times(&ib).unwrap().to_big(), a * b); - if let (Ok(x), Ok(y)) = (i128::try_from(a), i128::try_from(b)) { - fit(x.plus(&y), &(a + b)); - fit(x.minus(&y), &(a - b)); - fit(x.times(&y), &(a * b)); - } - } - } - assert_eq!(Int::of(-1).to_u64(), None); - assert_eq!(Int::of(i128::from(u64::MAX)).to_u64(), Some(u64::MAX)); - assert_eq!(Int::of_big(BigInt::from(i128::MAX) + 1).to_u64(), None); - assert_eq!((i128::from(u64::MAX) + 1).to_u64(), None); - } -} diff --git a/crates/ixon/src/sharing_exact/uniform/capped_size_tests.rs b/crates/ixon/src/sharing_exact/uniform/capped_size_tests.rs deleted file mode 100644 index a55bbdc9f..000000000 --- a/crates/ixon/src/sharing_exact/uniform/capped_size_tests.rs +++ /dev/null @@ -1,151 +0,0 @@ -use super::*; -use crate::contract::{BinderContract, LetContract, ValueContract}; -use crate::serialize::put_expr; -use num_bigint::BigUint; - -fn exact_excluded(occ: &BigUint, size: &BigUint, width: u64) -> bool { - if *occ == BigUint::from(0u8) { - return false; - } - (occ - 1u8) * size < occ * BigUint::from(width) -} - -#[test] -fn predicate_caps_match_biguint_including_zero_and_max_width() { - let cap = BigUint::from(EMPTY_SIZE_CAP); - let max = BigUint::from(u128::MAX); - let occs = [0u128, 1, 2, 3, u128::MAX] - .map(BigUint::from) - .into_iter() - .chain([&max + 1u8, BigUint::from(1u8) << 200]); - let sizes = [ - 0u128, - 1, - 2, - 3, - 6, - u128::from(u64::MAX), - EMPTY_SIZE_CAP - 1, - EMPTY_SIZE_CAP, - EMPTY_SIZE_CAP + 1, - u128::MAX, - ] - .map(BigUint::from) - .into_iter() - .chain([BigUint::from(1u8) << 200]) - .collect::>(); - for occ in occs { - for size in &sizes { - for width in [0, 1, 2, 3, u64::MAX - 1, u64::MAX] { - let o = u128::try_from(occ.clone().min(max.clone())).unwrap(); - let s = u128::try_from(size.clone().min(cap.clone())).unwrap(); - assert_eq!( - certainly_excluded(o, s, u128::from(width)), - exact_excluded(&occ, size, width), - "occ={occ} size={size} w={width}" - ); - } - } - } - for a in [0, EMPTY_SIZE_CAP - 1, EMPTY_SIZE_CAP, u128::MAX] { - for b in [0, 1, EMPTY_SIZE_CAP, u128::MAX] { - assert_eq!( - BigUint::from(empty_cap_add(a, b)), - (BigUint::from(a) + BigUint::from(b)).min(cap.clone()) - ); - } - } -} - -// Independent unbounded reference. Own bytes come from the serializer of -// one node with one-byte dummy children, not empty_own_size/Node::own_len. -fn reference(nodes: &[Node]) -> Vec { - let mut cost: Vec = Vec::new(); - let mut payload: Vec = Vec::new(); - let mut lengths = Vec::new(); - for node in nodes { - let mut bytes = Vec::new(); - put_expr(&node.to_expr(|_| Expr::var(0)), &mut bytes); - let mut own = bytes.len() - node.children().as_slice().len(); - let mut body = BigUint::from(0u8); - let mut length = 0; - let full = if node.family().is_some() { - own -= 1; // the single-node telescope header - let next = ix(spine_next(node)); - let continued = nodes[next].family() == node.family(); - length = if continued { lengths[next] + 1 } else { 1 }; - body = BigUint::from(own) - + &cost[ix(side_child(node))] - + if continued { &payload[next] } else { &cost[next] }; - let mut header = Vec::new(); - TagN::put(4, 0, length, &mut header); - BigUint::from(header.len()) + &body - } else { - node - .children() - .as_slice() - .iter() - .fold(BigUint::from(own), |sum, child| sum + &cost[ix(*child)]) - }; - cost.push(full); - payload.push(body); - lengths.push(length); - } - cost -} - -fn check(nodes: &[Node]) { - let (len, tail) = spine_tables(nodes, &vec![false; nodes.len()]); - let actual = empty_sizes_capped(nodes, &len, &tail).unwrap(); - let exact = reference(nodes); - assert_eq!(actual.len(), exact.len()); - for (actual, expected) in actual.into_iter().zip(exact) { - assert_eq!( - BigUint::from(actual), - expected.min(BigUint::from(EMPTY_SIZE_CAP)) - ); - } -} - -#[test] -fn empty_recurrence_matches_biguint_on_payloads_spines_and_random_dags() { - let mut nodes = vec![ - Node::Var(0), - Node::Sort(u64::MAX), - Node::Str(u64::MAX), - Node::Nat(u64::MAX), - Node::Ref(u64::MAX, vec![u64::MAX; 1100]), - Node::Rec(u64::MAX, vec![0, u64::MAX]), - Node::Prj(u64::MAX, u64::MAX, 0), - Node::Let(LetContract::plain(false), 1, 2, 3), - ]; - for _ in 0..300 { - let t = nodes.len() - 1; - nodes.push(Node::App(t, t)); - } - check(&nodes); // exact costs exceed 2^300; never expand an occurrence tree - let mut spine = vec![Node::Var(0)]; - for _ in 0..1100 { - spine.push(Node::App(spine.len() - 1, 0)); - } - check(&spine); // includes TagN length-rung changes - for seed in 0..1000u64 { - let mut rng = seed + 1; - let mut ns = vec![Node::Var(0), Node::Sort(0)]; - for _ in 0..80 { - rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1); - let a = usize::try_from(rng % len64(ns.len())).unwrap(); - let b = usize::try_from((rng >> 16) % len64(ns.len())).unwrap(); - ns.push(match rng % 5 { - 0 => Node::App(a, b), - 1 => Node::Lam(BinderContract::default(), a, b), - 2 => { - Node::All(BinderContract::default(), ValueContract::default(), a, b) - }, - 3 => Node::Let(LetContract::plain(true), a, b, a), - _ => Node::Prj(rng, rng >> 32, a), - }); - } - check(&ns); - } -} diff --git a/crates/ixon/src/sharing_exact/uniform/family.rs b/crates/ixon/src/sharing_exact/uniform/family.rs deleted file mode 100644 index 284af9d06..000000000 --- a/crates/ixon/src/sharing_exact/uniform/family.rs +++ /dev/null @@ -1,392 +0,0 @@ -//! Production phase-1 family: all subsets for at most sixteen initial -//! members, otherwise the complete setPrec-least greedy chain. -//! Only the fixed entry is reachable from production tiered normalization; -//! configurable gates remain private for reference tests. No trace work. - -use super::*; -use crate::sharing_exact::Resource; - -const EXACT_COMPONENT_LIMIT: usize = 16; - -// The Lean familyCalls/familyStoredSum model, not measured native work. -// Greedy extensions reuse their winning cost here, but are charged as the -// reference recipe: all extensions followed by every emitted chain row. -fn family_calls(m: usize, k: usize) -> Option { - let n = m as u128; - if m <= k { - 1u128.checked_shl(u32::try_from(m).ok()?) - } else { - n.checked_mul(n.checked_add(1)?)? - .checked_div(2)? - .checked_add(n)? - .checked_add(1) - } -} - -fn family_work( - m: usize, - k: usize, - closure: usize, - calls: u128, -) -> Option { - let n = m as u128; - let stored = if m <= k { - n.checked_mul(1u128.checked_shl(u32::try_from(m.saturating_sub(1)).ok()?)?)? - } else { - let pairs = n.checked_mul(n.checked_add(1)?)?; - pairs - .checked_mul(n.checked_add(2)?)? - .checked_div(6)? - .checked_add(pairs / 2)? - }; - calls.checked_add(1)?.checked_mul(closure as u128)?.checked_add(stored) -} - -/// Check the mathematical precharge before narrowing it to the public u64 -/// counter. Neither a charge nor an accumulated total may saturate into a -/// success at u64::MAX. Overflow here is the corresponding budget refusal. -fn charge( - total: &mut u64, - amount: Option, - limit: u64, - resource: Resource, -) -> Result<(), SharingError> { - let next = amount.and_then(|n| n.checked_add(u128::from(*total))); - let Some(next) = next.filter(|&n| n <= u128::from(limit)) else { - return Err(Meter::exhausted(resource, limit)); - }; - *total = u64::try_from(next).expect("precharge checked against a u64 limit"); - Ok(()) -} - -/// Count members plus every ancestor, including ancestors above opaque -/// nodes. This is Lean SCtx.closure, not the smaller stopped evaluation area. -/// Width-local buffers are reused; each node/edge is visited at most once -/// per closure, with a complete stamp reset only when the epoch wraps. -struct ClosureCount { - seen: Vec, - epoch: u32, - pending: Vec, -} - -impl ClosureCount { - fn new(n: usize) -> Self { - Self { seen: vec![0; n], epoch: 0, pending: Vec::new() } - } - - fn count(&mut self, parents: &[Vec], members: &[TermId]) -> usize { - self.epoch = self.epoch.wrapping_add(1); - if self.epoch == 0 { - self.seen.fill(0); - self.epoch = 1; - } - self.pending.clear(); - self.pending.extend_from_slice(members); - let mut count = 0; - while let Some(t) = self.pending.pop() { - if self.seen[ix(t)] == self.epoch { - continue; - } - self.seen[ix(t)] = self.epoch; - count += 1; - self.pending.extend_from_slice(&parents[ix(t)]); - } - count - } -} - -pub(crate) fn optimize_family_with( - w: u64, - dag: &SharingDag, - prep: &DagPrep, - meter: &mut Meter<'_>, -) -> Result<(UniformSharingResult, UniformPlan), SharingError> { - optimize_at(w, dag, prep, meter, EXACT_COMPONENT_LIMIT) -} - -/// Explicit mask enumeration is the bounded exact evaluator: exactly 2^m -/// complete subsets, no recursive search, pruning or partial-state evaluation. -/// The greedy path evaluates each remaining one-member extension. Every count -/// has a feasible row before trimming, even when its delta is negative or tied. -pub(super) fn family_table( - members: &[TermId], - k: usize, - mut cost: impl FnMut(&[TermId]) -> Result, -) -> Result, SharingError> { - if k > 24 { - return Err(internal("unsupported K > 24")); - } - if !members.windows(2).all(|p| p[0] < p[1]) { - return Err(internal("noncanonical component members")); - } - let mut rows: CTable = vec![None; members.len() + 1]; - if members.len() <= k { - for mask in 0..(1u64 << members.len()) { - let chosen: Vec<_> = members - .iter() - .enumerate() - .filter_map(|(j, &t)| ((mask >> j) & 1 != 0).then_some(t)) - .collect(); - let d = cost(&chosen)?; - let row = &mut rows[chosen.len()]; - if better(&d, &chosen, row.as_ref()) { - *row = Some((d, chosen)); - } - } - } else { - let mut chosen = Vec::new(); - rows[0] = Some((cost(&chosen)?, chosen.clone())); - for row in rows.iter_mut().skip(1) { - let mut best = None; - for &t in members { - let Err(at) = chosen.binary_search(&t) else { continue }; - let mut next = chosen.clone(); - next.insert(at, t); - let d = cost(&next)?; - if better(&d, &next, best.as_ref()) { - best = Some((d, next)); - } - } - let next = best.ok_or_else(|| internal("invalid component members"))?; - chosen = next.1.clone(); - *row = Some(next); - } - } - Ok(rows) -} - -/// The fixed-family optimum at width `w` with family bound `k`: computed in -/// `u128`/`i128` and, if a value leaves that range (`LengthOverflow`), again -/// in exact integers from the meter as it was before the first run, so the -/// counters and the result are those of one exact run (`arith`). -pub(super) fn optimize_at( - w: u64, - dag: &SharingDag, - prep: &DagPrep, - meter: &mut Meter<'_>, - k: usize, -) -> Result<(UniformSharingResult, UniformPlan), SharingError> { - let before = meter.stats.clone(); - match optimize_in::(w, dag, prep, meter, k) { - Err(SharingError::FormatBound(FormatBound::LengthOverflow)) => { - meter.stats = before; - optimize_in::(w, dag, prep, meter, k) - }, - result => result, - } -} - -/// [`optimize_at`] in the representation `A`. -pub(super) fn optimize_in( - w: u64, - dag: &SharingDag, - prep: &DagPrep, - meter: &mut Meter<'_>, - k: usize, -) -> Result<(UniformSharingResult, UniformPlan), SharingError> { - if k > 24 { - return Err(internal("unsupported K > 24")); - } - let stage = initial::(w, dag, prep, meter)?; - let n = dag.len(); - let mut closure = ClosureCount::new(n); - // Reuse the full-DAG stamp/value buffers across components. These are - // scratch only; neither available bits nor chosen effects survive a row. - let mut cx = CompCtx:: { - up: &stage.up, - #[cfg(any(test, feature = "sharing-reference"))] - members: Vec::new(), - area: Vec::new(), - root_mult: Vec::new(), - stored_in: Vec::new(), - #[cfg(any(test, feature = "sharing-reference"))] - phi0: A::U::default(), - #[cfg(any(test, feature = "sharing-reference"))] - slack: stage.slack.clone(), - vals: vec![UVal::default(); n], - stamp: vec![0; n], - epoch: 0, - avail: vec![false; n], - }; - let mut results = Vec::with_capacity(stage.comps.len()); - for members in &stage.comps { - let calls = family_calls(members.len(), k); - charge( - &mut meter.stats.states_created, - calls, - meter.limits.max_states, - Resource::States, - )?; - let closure_size = closure.count(&prep.facts.parents, members); - charge( - &mut meter.stats.work, - calls.and_then(|n| family_work(members.len(), k, closure_size, n)), - meter.limits.max_work, - Resource::Work, - )?; - cx.area = component_area_with( - &stage.up, - &prep.facts, - members, - &mut cx.stamp, - &mut cx.epoch, - &mut closure.pending, - ); - let mut roots = std::collections::BTreeMap::::new(); - for &r in dag.roots() { - if cx.area.binary_search(&r).is_ok() { - *roots.entry(r).or_default() += 1; - } - } - cx.root_mult = roots.into_iter().map(|(r, m)| (r, A::u(m))).collect(); - cx.stored_in = - cx.area.iter().copied().filter(|&t| stage.up.opaq[ix(t)]).collect(); - let (base, _) = cx.phi(&[])?; - let base = A::signed(&base)?; - let rows = family_table(members, k, |chosen| { - for &t in chosen { - cx.avail[ix(t)] = true; - } - let evaluated = cx.phi(chosen); - for &t in chosen { - cx.avail[ix(t)] = false; - } - let (value, _) = evaluated?; - A::signed(&value)?.minus(&base) - })?; - let mut best: Option> = None; - for row in &rows { - let (delta, selected) = - row.as_ref().ok_or_else(|| internal("missing count"))?; - if better(delta, selected, best.as_ref()) { - best = Some((delta.clone(), selected.clone())); - } - } - let best = best.ok_or_else(|| internal("empty table"))?; - let bound = best.0.plus(&stage.slack)?; - let trimmed: CTable = - rows.into_iter().map(|r| r.filter(|(d, _)| *d <= bound)).collect(); - results.push((best, trimmed)); - } - drop(cx); - combine_uniform(w, dag, prep, meter, stage, results) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn logical_charges_count_the_reference_recipe() { - for k in [0, 1, 16] { - for m in 0..=18usize { - let sizes: Vec = if m <= k { - (0..1u64 << m).map(|s| s.count_ones() as usize).collect() - } else { - (1..=m) - .flat_map(|q| std::iter::repeat_n(q, m - q + 1)) - .chain(0..=m) - .collect() - }; - let calls = sizes.len() as u128; - assert_eq!(family_calls(m, k), Some(calls)); - for closure in [0, 1, 19, 203] { - let work = closure + sizes.iter().map(|q| closure + q).sum::(); - assert_eq!(family_work(m, k, closure, calls), Some(work as u128)); - } - } - } - } - - #[test] - fn closure_charge_reuses_buffers_without_losing_ancestors() { - let parents = - vec![vec![2, 3], vec![3], vec![4], vec![4, 4], vec![5], vec![]]; - let mut counter = ClosureCount::new(parents.len()); - for mask in (0..64).chain((0..64).rev()) { - let members: Vec<_> = (0..6).filter(|t| mask >> t & 1 != 0).collect(); - let mut expected = std::collections::BTreeSet::new(); - let mut pending = members.clone(); - while let Some(t) = pending.pop() { - if expected.insert(t) { - pending.extend_from_slice(&parents[ix(t)]); - } - } - assert_eq!(counter.count(&parents, &members), expected.len()); - counter.epoch = u32::MAX; // same closure after the wrap/reset path - assert_eq!(counter.count(&parents, &members), expected.len()); - } - assert_eq!(counter.count(&parents, &[0]), 5); - } - - #[test] - fn precharges_never_saturate_into_success() { - let mut total = 0; - charge(&mut total, Some(u128::from(u64::MAX)), u64::MAX, Resource::States) - .unwrap(); - assert_eq!(total, u64::MAX); - for amount in [Some(1), Some(u128::MAX), None] { - assert_eq!( - charge(&mut total, amount, u64::MAX, Resource::States), - Err(Meter::exhausted(Resource::States, u64::MAX)) - ); - } - let calls = family_calls(usize::MAX, 0); - assert!(charge(&mut 0, calls, u64::MAX, Resource::States).is_err()); - assert!( - charge( - &mut 0, - calls.and_then(|n| family_work(usize::MAX, 0, 1, n)), - u64::MAX, - Resource::Work - ) - .is_err() - ); - } - - #[test] - fn family_refusals_follow_state_then_closure_work_precharges() { - let t2 = Expr::all(Expr::sort(0), Expr::all(Expr::sort(0), Expr::sort(0))); - let roots = [Expr::all(t2.clone(), t2)]; - let limits = ExactSharingLimits::default(); - let dag = SharingDag::from_expanded_roots(&roots, &limits).unwrap(); - let prep = DagPrep::new(&dag); - let stage = - initial::(3, &dag, &prep, &mut Meter::new(&limits)).unwrap(); - assert_eq!(stage.comps.len(), 1); - assert_eq!(stage.comps[0].len(), 1); - let closure = - ClosureCount::new(dag.len()).count(&prep.facts.parents, &stage.comps[0]); - assert_eq!(closure, 2); - let run = |k, states, work| { - let limits = ExactSharingLimits { - max_states: states, - max_work: work, - ..limits.clone() - }; - optimize_at(3, &dag, &prep, &mut Meter::new(&limits), k).map(|r| r.0) - }; - for (states, work, resource) in [ - (0, 0, Resource::States), - (1, 0, Resource::States), - (2, 6, Resource::Work), - ] { - assert_eq!( - run(16, states, work).unwrap_err(), - Meter::exhausted( - resource, - if resource == Resource::States { states } else { work } - ) - ); - } - let exact = run(16, 2, limits.max_work).unwrap(); - let greedy = run(0, 3, limits.max_work).unwrap(); - assert_eq!( - (&exact.stored, exact.model_len, &exact.component_tables), - (&greedy.stored, greedy.model_len, &greedy.component_tables) - ); - assert_eq!(exact.stats.states_created, 2); - assert_eq!(greedy.stats.states_created, 3); - assert_eq!(greedy.stats.work - exact.stats.work, 3); - } -} diff --git a/crates/ixon/src/sharing_exact/uniform/family_trace.rs b/crates/ixon/src/sharing_exact/uniform/family_trace.rs deleted file mode 100644 index d071781b7..000000000 --- a/crates/ixon/src/sharing_exact/uniform/family_trace.rs +++ /dev/null @@ -1,384 +0,0 @@ -//! Family normalization at a configurable exact-subset limit K, with a -//! component and row trace. It is a unit-test control, not the canonical rule -//! (which fixes K = 16 in `family.rs`) and not a totality claim. Initial -//! components alone decide the family. All subsets are evaluated for size <= K; -//! otherwise all counts of one greedy chain are retained. Arithmetic, -//! input/output and phase-2/3 limits still fail closed. No work/time limit -//! changes the family. The greedy tie is the existing `set_prec`. - -use super::super::tiered::{ - ShareLayout, TieredSharingResult, finish_candidates, normalize_tiered_with, - tiered_at_using, -}; -use super::*; -use rustc_hash::FxHashSet; - -/// Trace schema v1; streamed before slack filtering. Model work is not native -/// time or a proved operational cost. `closure` is the full upward closure. -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) enum Event { - /// Cooperative observation/abort point, not part of the family trace. - Progress, - Input { - input_visits: u64, - interned_nodes: u64, - dag_nodes: usize, - edges: usize, - roots: usize, - table_entries: usize, - refs: usize, - univs: usize, - level_args: usize, - scalar_bits: u128, - max_scalar_bits: u32, - }, - Width { - width: u64, - certain: usize, - uncertain: usize, - components: usize, - slack: i128, - }, - Component { - width: u64, - index: usize, - mode: &'static str, - members: Vec, - area: usize, - closure: usize, - }, - Row { - width: u64, - index: usize, - mode: &'static str, - count: usize, - delta: i128, - selected: Vec, - }, -} - -type Observe<'a> = dyn FnMut(&Event) -> Result<(), SharingError> + 'a; - -fn input_event(c: &Constant, dag: &SharingDag, m: &Meter<'_>) -> Event { - let mut bits = 0u128; - let mut max_bits = 0; - let mut level_args = 0; - for node in dag.nodes() { - if let Node::Ref(_, us) | Node::Rec(_, us) = node { - level_args += us.len(); - } - for s in node.scalars() { - let b = (u64::BITS - s.leading_zeros()).max(1); - bits += u128::from(b); - max_bits = max_bits.max(b); - } - } - Event::Input { - input_visits: m.stats.input_nodes, - interned_nodes: m.stats.distinct_nodes, - dag_nodes: dag.len(), - edges: dag.nodes().iter().map(|n| n.children().as_slice().len()).sum(), - roots: dag.roots().len(), - table_entries: c.sharing.len(), - refs: c.refs.len(), - univs: c.univs.len(), - level_args, - scalar_bits: bits, - max_scalar_bits: max_bits, - } -} - -fn closure_size(facts: &Facts, members: &[TermId]) -> usize { - let mut seen: FxHashSet = members.iter().copied().collect(); - let mut todo = members.to_vec(); - while let Some(t) = todo.pop() { - for &p in &facts.parents[ix(t)] { - if seen.insert(p) { - todo.push(p); - } - } - } - seen.len() -} - -fn component_event( - w: u64, - j: usize, - mode: &'static str, - members: &[TermId], - area: usize, - facts: &Facts, -) -> Event { - Event::Component { - width: w, - index: j, - mode, - members: members.to_vec(), - area, - closure: closure_size(facts, members), - } -} - -fn width_event(w: u64, s: &Initial<'_>) -> Event { - Event::Width { - width: w, - certain: s.cs.len(), - uncertain: s.unc.len(), - components: s.comps.len(), - slack: s.slack, - } -} - -/// Classification and source metrics only: never calls component search, -/// convolution, knapsack, phase 2, or materialization. Serialized input bytes -/// (including unreachable entries and universe payloads) are supplied by the -/// runner, separately from these reachable-DAG/payload metrics. -pub(crate) fn classify( - c: &Constant, - limits: &ExactSharingLimits, - observe: &mut Observe<'_>, -) -> Result<(), SharingError> { - let mut m = Meter::new(limits); - let dag = SharingDag::build( - &constant_info_root_exprs(&c.info), - Some(&c.sharing), - &mut m, - )?; - observe(&input_event(c, &dag, &m))?; - let prep = DagPrep::new(&dag); - let n = dag.len(); - let mut stamp = vec![0u32; n]; - let mut epoch = 0u32; - let mut stack = Vec::new(); - for w in 1..=3 { - let stage = initial::(w, &dag, &prep, &mut m)?; - observe(&width_event(w, &stage))?; - for (j, members) in stage.comps.iter().enumerate() { - let area = component_area_with( - &stage.up, - &prep.facts, - members, - &mut stamp, - &mut epoch, - &mut stack, - ); - observe(&component_event( - w, - j, - "classify", - members, - area.len(), - &prep.facts, - ))?; - } - } - Ok(()) -} - -use super::family::family_table; - -fn uniform_family( - w: u64, - dag: &SharingDag, - prep: &DagPrep, - meter: &mut Meter<'_>, - k: usize, - observe: &mut Observe<'_>, -) -> Result<(UniformSharingResult, UniformPlan), SharingError> { - let stage = initial::(w, dag, prep, meter)?; - observe(&width_event(w, &stage))?; - let n = dag.len(); - // Reuse the full-DAG stamp/value buffers across components. These are - // scratch only; neither available bits nor chosen effects survive a row. - let mut cx = CompCtx:: { - up: &stage.up, - members: Vec::new(), - area: Vec::new(), - root_mult: Vec::new(), - stored_in: Vec::new(), - phi0: 0, - slack: stage.slack, - vals: vec![UVal::default(); n], - stamp: vec![0; n], - epoch: 0, - avail: vec![false; n], - }; - let mut stack = Vec::new(); - let mut results = Vec::with_capacity(stage.comps.len()); - for (j, members) in stage.comps.iter().enumerate() { - let mode = if members.len() <= k { "exact" } else { "greedy" }; - cx.area = component_area_with( - &stage.up, - &prep.facts, - members, - &mut cx.stamp, - &mut cx.epoch, - &mut stack, - ); - observe(&component_event(w, j, mode, members, cx.area.len(), &prep.facts))?; - let mut roots = std::collections::BTreeMap::::new(); - for &r in dag.roots() { - if cx.area.binary_search(&r).is_ok() { - *roots.entry(r).or_default() += 1; - } - } - cx.root_mult = roots.into_iter().collect(); - cx.stored_in = - cx.area.iter().copied().filter(|&t| stage.up.opaq[ix(t)]).collect(); - let (base, work) = cx.phi(&[])?; - meter.work(work)?; - let base = Fast::signed(&base)?; - let mut evaluated_rows = 0u64; - let rows = family_table(members, k, |chosen| { - meter.state()?; - if evaluated_rows.is_multiple_of(4096) { - observe(&Event::Progress)?; - } - evaluated_rows += 1; - for &t in chosen { - cx.avail[ix(t)] = true; - } - let evaluated = cx.phi(chosen); - for &t in chosen { - cx.avail[ix(t)] = false; - } - let (value, work) = evaluated?; - meter.work(work)?; - Fast::signed(&value)?.minus(&base) - })?; - let mut best: Option = None; - for (count, row) in rows.iter().enumerate() { - let (delta, selected) = - row.as_ref().ok_or_else(|| internal("family trace: missing count"))?; - observe(&Event::Row { - width: w, - index: j, - mode, - count, - delta: *delta, - selected: selected.clone(), - })?; - if better(delta, selected, best.as_ref()) { - best = Some((*delta, selected.clone())); - } - } - let best = best.ok_or_else(|| internal("family trace: empty table"))?; - let bound = best.0.plus(&stage.slack)?; - let trimmed: CTable = - rows.into_iter().map(|r| r.filter(|(d, _)| *d <= bound)).collect(); - results.push((best, trimmed)); - } - drop(cx); - combine_uniform(w, dag, prep, meter, stage, results) -} - -/// Test output only. Production phase 2/3 and all validation remain; -/// their resource/format errors are reported, not converted to a success. -/// No admitted family changes when a budget is exhausted. K is test -/// data (0..=24), NOT a format identifier or default compiler option. -pub(crate) fn normalize( - c: &Constant, - limits: &ExactSharingLimits, - k: usize, - observe: &mut Observe<'_>, -) -> Result<(Constant, TieredSharingResult), SharingError> { - if k > 24 { - return Err(internal("family trace: unsupported K > 24")); - } - normalize_tiered_with(c, limits, |dag| { - let prep = DagPrep::new(dag); - let mut candidates = Vec::with_capacity(3); - for w in 1..=3 { - let mut meter = Meter::new(limits); - let out = tiered_at_using( - ShareLayout::TagN, - dag, - &prep, - &mut meter, - w, - |meter| uniform_family(w, dag, &prep, meter, k, observe), - )?; - candidates.push(Ok(out)); - } - finish_candidates(ShareLayout::TagN, dag, &prep, limits, candidates) - }) -} - -#[cfg(test)] -mod tests { - use super::*; - // Independent oracle: complete boolean-vector domain, lexicographically - // ordered (false before true), then integer cost. No set_prec or family - // helper participates in choosing a reference row. Odd seeds place the - // members on both sides of the old 32-bit term-ID limit. - #[test] - fn family_trace_all_counts_oracle() { - for n in 0..=7 { - for seed in 0..20u64 { - let base: TermId = if seed % 2 == 1 { (1 << 32) - 10 } else { 0 }; - let members: Vec = (0..n).map(|j| base + 3 * j + 2).collect(); - let mut choices = Vec::new(); - for mask in 0..(1u64 << n) { - let flags: Vec = (0..n).map(|j| (mask >> j) & 1 != 0).collect(); - let delta = - i128::from((mask.wrapping_mul(17 + seed) ^ (mask >> 2)) % 11) - 7; - choices.push((delta, flags)); - } - choices.sort(); - let set = |flags: &[bool]| { - members - .iter() - .zip(flags) - .filter_map(|(&t, &b)| b.then_some(t)) - .collect::>() - }; - for k in 0..=n { - let actual = family_table(&members, k, |s| { - Ok(choices.iter().find(|(_, flags)| set(flags) == s).unwrap().0) - }) - .unwrap(); - let mut previous = vec![false; n]; - for (count, row) in actual.iter().enumerate() { - let expected = choices - .iter() - .find(|(_, flags)| { - flags.iter().filter(|&&b| b).count() == count - && (n <= k - || previous.iter().zip(flags).all(|(&a, &b)| !a || b)) - }) - .unwrap(); - assert_eq!( - row, - &Some((expected.0, set(&expected.1))), - "n={n} k={k} seed={seed}" - ); - previous.clone_from(&expected.1); - } - } - } - } - } - - #[test] - fn family_trace_threshold_ties_and_refusals() { - for n in [0usize, 1, 2, 12, 13, 16, 17] { - let members: Vec<_> = (0..n).collect(); - for k in [0usize, 12, 16] { - let mut calls = 0; - let rows = family_table(&members, k, |_| { - calls += 1; - Ok(-3i128) - }) - .unwrap(); - assert_eq!(calls, if n <= k { 1 << n } else { 1 + n * (n + 1) / 2 }); - for (q, row) in rows.iter().enumerate() { - assert_eq!(row, &Some((-3, members[members.len() - q..].to_vec()))); - } - } - } - assert!(family_table(&[2, 2], 2, |_| Ok(0i128)).is_err()); - assert!(family_table(&[2, 1], 2, |_| Ok(0i128)).is_err()); - assert!(family_table(&[], 25, |_| Ok(0i128)).is_err()); - assert!(family_table(&[1, 2], 0, |_| Err::(overflow())).is_err()); - } -} diff --git a/crates/ixon/src/sharing_exact/uniform/reference.rs b/crates/ixon/src/sharing_exact/uniform/reference.rs deleted file mode 100644 index 4debd31c6..000000000 --- a/crates/ixon/src/sharing_exact/uniform/reference.rs +++ /dev/null @@ -1,1786 +0,0 @@ -//! Old unbounded uniform solver: test/reference feature only. -//! Algorithm and controls moved from uniform.rs without rule changes. - -use super::*; - -/// `x` as a delta; the reference refuses outside `i128`. -fn to_i(x: u128) -> Result { - Fast::signed(&x) -} - -impl CompCtx<'_, '_> { - /// Depth-first branch and bound over the members (ascending), "not - /// stored" first. Returns the best choice and the best per set size. - fn search( - &mut self, - meter: &mut Meter<'_>, - ) -> Result<(Choice, Vec>), SharingError> { - let mut best: Option = None; - let mut by_size: Vec> = Vec::new(); - // Frames: (next member index, chosen so far). - let mut stack: Vec<(usize, Vec)> = vec![(0, Vec::new())]; - while let Some((i, chosen)) = stack.pop() { - meter.state()?; - for (k, &m) in self.members.iter().enumerate() { - self.avail[ix(m)] = k >= i || chosen.contains(&m); - } - let (phi, work) = self.phi(&chosen)?; - meter.work(work)?; - let delta = to_i(phi)? - to_i(self.phi0)?; - if i >= self.members.len() { - if better(&delta, &chosen, best.as_ref()) { - best = Some((delta, chosen.clone())); - } - let k = chosen.len(); - if by_size.len() <= k { - by_size.resize(k + 1, None); - } - if better(&delta, &chosen, by_size[k].as_ref()) { - by_size[k] = Some((delta, chosen)); - } - continue; - } - if let Some((b, _)) = &best - && delta > b + self.slack - { - continue; - } - let t = self.members[i]; - let mut with = chosen.clone(); - with.push(t); - stack.push((i + 1, with)); - stack.push((i + 1, chosen)); - } - for &m in &self.members { - self.avail[ix(m)] = false; - } - let best = - best.ok_or_else(|| internal("component search found no choice"))?; - Ok((best, by_size)) - } -} - -/// The sorted concatenation of two ascending slices, without allocation. -/// Equal elements are emitted twice, just as `merge_sorted` preserves them. -struct Merged<'a> { - a: &'a [TermId], - b: &'a [TermId], -} - -impl Iterator for Merged<'_> { - type Item = TermId; - - fn next(&mut self) -> Option { - match (self.a.split_first(), self.b.split_first()) { - (Some((&x, a)), Some((&y, b))) => { - if x <= y { - self.a = a; - Some(x) - } else { - self.b = b; - Some(y) - } - }, - (Some((&x, a)), None) => { - self.a = a; - Some(x) - }, - (None, Some((&y, b))) => { - self.b = b; - Some(y) - }, - (None, None) => None, - } - } - - fn size_hint(&self) -> (usize, Option) { - let n = self.a.len() + self.b.len(); - (n, Some(n)) - } -} - -impl ExactSizeIterator for Merged<'_> {} - -/// Exactly `set_prec` on the two materialized merges, including prefix and -/// equal-sequence behavior. No disjointness or duplicate removal is assumed. -fn merged_prec(mut a: Merged<'_>, mut b: Merged<'_>) -> bool { - loop { - match (a.next(), b.next()) { - (Some(x), Some(y)) if x == y => {}, - (Some(x), Some(y)) => return y < x, - (None, Some(_)) => return true, - _ => return false, - } - } -} - -#[cfg(test)] -type DelayedChoice<'a> = (i128, &'a [TermId], &'a [TermId]); - -/// Convolve tables of ascending choices in the original pair order, keeping -/// the same winner at every actual merged count. Only final winners allocate. -#[cfg(test)] -fn table_conv(a: &CTable, b: &CTable) -> CTable { - let mut out: Vec>> = Vec::new(); - for (da, sa) in a.iter().flatten() { - for (db, sb) in b.iter().flatten() { - let delta = da + db; - let k = sa.len() + sb.len(); - if out.len() <= k { - out.resize(k + 1, None); - } - let wins = match out[k] { - None => true, - Some((d, a, b)) => { - delta < d - || (delta == d - && merged_prec(Merged { a: sa, b: sb }, Merged { a, b })) - }, - }; - if wins { - out[k] = Some((delta, sa, sb)); - } - } - } - out - .into_iter() - .map(|e| e.map(|(d, a, b)| (d, Merged { a, b }.collect()))) - .collect() -} - -// SCtx choices use a bounded component-local encoding. Global tables and -// the count knapsack still receive the original sorted ID vectors. -const PACKED_MEMBERS: usize = 512; - -#[derive(Clone, Debug, PartialEq, Eq)] -enum LocalSet { - Listed(Vec), - Packed(Box<[u64]>), -} - -impl LocalSet { - fn len(&self) -> usize { - match self { - Self::Listed(ids) => ids.len(), - Self::Packed(words) => { - words.iter().map(|w| w.count_ones() as usize).sum() - }, - } - } -} - -type LocalTable = Vec>; - -enum LocalIter<'a> { - Listed(std::slice::Iter<'a, TermId>), - Packed { members: &'a [TermId], words: &'a [u64], at: usize, bits: u64 }, -} - -impl Iterator for LocalIter<'_> { - type Item = TermId; - fn next(&mut self) -> Option { - match self { - Self::Listed(ids) => ids.next().copied(), - Self::Packed { members, words, at, bits } => loop { - if *bits != 0 { - let bit = bits.trailing_zeros() as usize; - *bits &= *bits - 1; - return Some(members[*at * 64 + bit]); - } - *at += 1; - *bits = *words.get(*at)?; - }, - } - } -} - -#[derive(Clone, Copy)] -struct LocalPair<'a> { - a: &'a LocalSet, - b: &'a LocalSet, - packed: bool, -} - -#[derive(Clone, Copy)] -struct ComponentSets<'a> { - members: &'a [TermId], - words: Option, -} - -impl<'a> ComponentSets<'a> { - fn new(members: &'a [TermId], enabled: bool) -> Self { - let packed = enabled - && members.len() <= PACKED_MEMBERS - && members.windows(2).all(|w| w[0] < w[1]); - Self { members, words: packed.then(|| members.len().div_ceil(64)) } - } - - fn pack(&self, ids: Vec) -> LocalSet { - let Some(n) = self.words else { - return LocalSet::Listed(ids); - }; - if !ids.windows(2).all(|w| w[0] < w[1]) { - return LocalSet::Listed(ids); - } - let mut words = vec![0u64; n]; - for &t in &ids { - let Ok(i) = self.members.binary_search(&t) else { - return LocalSet::Listed(ids); - }; - words[i / 64] |= 1 << (i % 64); - } - LocalSet::Packed(words.into_boxed_slice()) - } - - fn iter<'b>(&'b self, set: &'b LocalSet) -> LocalIter<'b> { - match set { - LocalSet::Listed(ids) => LocalIter::Listed(ids.iter()), - LocalSet::Packed(words) => LocalIter::Packed { - members: self.members, - words, - at: 0, - bits: words.first().copied().unwrap_or(0), - }, - } - } - - fn iter_prec( - mut a: impl Iterator, - mut b: impl Iterator, - ) -> bool { - loop { - match (a.next(), b.next()) { - (Some(x), Some(y)) if x == y => {}, - (Some(x), Some(y)) => return y < x, - (None, Some(_)) => return true, - _ => return false, - } - } - } - - fn prec(&self, a: &LocalSet, b: &LocalSet) -> bool { - match (a, b) { - (LocalSet::Listed(a), LocalSet::Listed(b)) => set_prec(a, b), - (LocalSet::Packed(a), LocalSet::Packed(b)) => { - for (&a, &b) in a.iter().zip(b) { - let diff = a ^ b; - if diff != 0 { - return a & (1 << diff.trailing_zeros()) == 0; - } - } - false - }, - _ => Self::iter_prec(self.iter(a), self.iter(b)), - } - } - - fn pair<'b>(&self, a: &'b LocalSet, b: &'b LocalSet) -> LocalPair<'b> { - let packed = match (self.words, a, b) { - (Some(n), LocalSet::Packed(a), LocalSet::Packed(b)) => { - a.len() == n && b.len() == n && a.iter().zip(b).all(|(a, b)| a & b == 0) - }, - _ => false, - }; - LocalPair { a, b, packed } - } - - fn union<'b>( - &'b self, - pair: LocalPair<'b>, - ) -> impl Iterator + 'b { - let mut a = self.iter(pair.a).peekable(); - let mut b = self.iter(pair.b).peekable(); - std::iter::from_fn(move || match (a.peek(), b.peek()) { - (Some(x), Some(y)) if x <= y => a.next(), - (_, Some(_)) => b.next(), - (Some(_), None) => a.next(), - (None, None) => None, - }) - } - - fn pair_prec(&self, a: LocalPair<'_>, b: LocalPair<'_>) -> bool { - if a.packed - && b.packed - && let ( - LocalSet::Packed(a1), - LocalSet::Packed(a2), - LocalSet::Packed(b1), - LocalSet::Packed(b2), - ) = (a.a, a.b, b.a, b.b) - { - for ((&a1, &a2), (&b1, &b2)) in a1.iter().zip(a2).zip(b1.iter().zip(b2)) { - let a = a1 | a2; - let diff = a ^ (b1 | b2); - if diff != 0 { - return a & (1 << diff.trailing_zeros()) == 0; - } - } - return false; - } - if let ( - LocalSet::Listed(a1), - LocalSet::Listed(a2), - LocalSet::Listed(b1), - LocalSet::Listed(b2), - ) = (a.a, a.b, b.a, b.b) - { - return merged_prec(Merged { a: a1, b: a2 }, Merged { a: b1, b: b2 }); - } - Self::iter_prec(self.union(a), self.union(b)) - } - - fn materialize(&self, pair: LocalPair<'_>) -> LocalSet { - if pair.packed - && let (LocalSet::Packed(a), LocalSet::Packed(b)) = (pair.a, pair.b) - { - return LocalSet::Packed(a.iter().zip(b).map(|(a, b)| a | b).collect()); - } - let ids = match (pair.a, pair.b) { - (LocalSet::Listed(a), LocalSet::Listed(b)) => Merged { a, b }.collect(), - _ => self.union(pair).collect(), - }; - // Duplicate/overlapping or out-of-vocabulary choices remain listed. - self.pack(ids) - } - - fn add(&self, tb: &mut LocalTable, e: (i128, LocalSet)) { - let k = e.1.len(); - if tb.len() <= k { - tb.resize(k + 1, None); - } - if tb[k] - .as_ref() - .is_none_or(|(d, s)| e.0 < *d || (e.0 == *d && self.prec(&e.1, s))) - { - tb[k] = Some(e); - } - } - - fn best(tb: &LocalTable) -> Option { - tb.iter().flatten().map(|(d, _)| *d).min() - } - - fn trim(tb: LocalTable, slack: i128) -> LocalTable { - match Self::best(&tb) { - None => tb, - Some(b) => { - tb.into_iter().map(|e| e.filter(|(d, _)| *d <= b + slack)).collect() - }, - } - } - - fn conv(&self, a: &LocalTable, b: &LocalTable) -> LocalTable { - let mut out: Vec)>> = Vec::new(); - for (da, sa) in a.iter().flatten() { - for (db, sb) in b.iter().flatten() { - let delta = da + db; - let k = sa.len() + sb.len(); - if out.len() <= k { - out.resize(k + 1, None); - } - let pair = self.pair(sa, sb); - if out[k].is_none_or(|(d, old)| { - delta < d || (delta == d && self.pair_prec(pair, old)) - }) { - out[k] = Some((delta, pair)); - } - } - } - out.into_iter().map(|e| e.map(|(d, p)| (d, self.materialize(p)))).collect() - } - - fn decode(&self, tb: LocalTable) -> CTable { - tb.into_iter() - .map(|e| { - e.map(|(d, set)| { - let ids = match set { - LocalSet::Listed(ids) => ids, - set => self.iter(&set).collect(), - }; - (d, ids) - }) - }) - .collect() - } -} - -/// The undecided member to branch on: the largest `|gain|`, then the -/// smaller ID. -fn pick_branch(gains: &[(TermId, i128)]) -> Option<(TermId, i128)> { - let mut acc: Option<(TermId, i128)> = None; - for &(t, g) in gains { - acc = match acc { - None => Some((t, g)), - Some((a, ga)) => { - if g.unsigned_abs() > ga.unsigned_abs() - || (g.unsigned_abs() == ga.unsigned_abs() && t < a) - { - Some((t, g)) - } else { - acc - } - }, - }; - } - acc -} - -/// Area position of no term (`SCtx::pos`). -const NO_POS: usize = usize::MAX; - -/// Bounds of the area under a maybe-stored predicate, over the root-level -/// bounds (Lean `SCtx.rebound`): `vals[j]` for the area term at position -/// `j`, the root-level bounds elsewhere. -struct Overlay<'b> { - base: &'b Bounds, - pos: &'b [usize], - vals: Vec<[u128; 4]>, -} - -impl Overlay<'_> { - fn get(&self, t: TermId) -> [u128; 4] { - match self.pos[ix(t)] { - NO_POS => { - let k = ix(t); - [ - self.base.inl[k], - self.base.merged[k], - self.base.head_lb[k], - self.base.cont_lb[k], - ] - }, - j => self.vals[j], - } - } - - /// The `i`-th bound of `t`, as [`Overlay::get`]. - fn bound(&self, t: TermId, i: usize) -> &u128 { - match self.pos[ix(t)] { - NO_POS => { - let k = ix(t); - match i { - 0 => &self.base.inl[k], - 1 => &self.base.merged[k], - 2 => &self.base.head_lb[k], - _ => &self.base.cont_lb[k], - } - }, - j => &self.vals[j][i], - } - } -} - -/// The search of one component (Lean `SCtx`). Sets of decided members are -/// passed as sorted term lists and, inside one call, as flags over the area -/// positions (every member is in the area; a term outside the area is in -/// none of them). -struct SCtx<'a, 'd> { - up: &'a UPrep<'d>, - facts: &'a Facts, - cand: &'a [bool], - b0: &'a Bounds, - vis0: &'a (Vec, Vec), - spine_len: &'a [u64], - dag_size: usize, - members: Vec, - /// Member index of each area position (`NO_POS` for other terms). - member_of: Vec, - area: Vec, - /// Area position of every term of the DAG (`NO_POS` outside the area). - pos: &'a [usize], - /// Per area node: `(parent, its area position, edges, head edges)` per - /// distinct parent. - in_edges: Vec>, - root_occ: Vec, - root_mult: Vec<(TermId, u128)>, - stored_in: Vec, - slack: i128, - theta: i128, - /// At most eight words per packed choice; None retains listed choices. - set_words: Option, - // Completed group tables are immutable; memo hits share their per-count - // choices instead of cloning every selected-term vector. - memo: FxHashMap, Arc>, - memo_hits: u64, - /// Scratch of `phi`: the value of each area term. - vals: Vec, -} - -impl SCtx<'_, '_> { - fn sets(&self) -> ComponentSets<'_> { - ComponentSets { members: &self.members, words: self.set_words } - } - - fn at(&self, t: TermId) -> Option { - match self.pos.get(ix(t)).copied()? { - NO_POS => None, - j => Some(j), - } - } - - /// Flags over the area positions of the terms `ts`. - fn flags(&self, ts: &[TermId]) -> Vec { - let mut f = vec![false; self.area.len()]; - for &t in ts { - if let Some(j) = self.at(t) { - f[j] = true; - } - } - f - } - - /// The component cost with the area terms flagged in `avail` available - /// and the entries of `stored`, and the evaluation work (Lean - /// `SCtx.phi`). Every area term is evaluated, children first; other terms - /// keep their root-level values. - fn phi( - &mut self, - avail: &[bool], - stored: &[TermId], - ) -> Result<(u128, u64), SharingError> { - let mut work = 0u64; - for k in 0..self.area.len() { - let t = self.area[k]; - let (v, s) = { - let (vals, base, pos) = (&self.vals, &self.up.base, self.pos); - let get = |c: TermId| match pos[ix(c)] { - NO_POS => &base[ix(c)], - j => &vals[j], - }; - let av = |c: TermId| match pos[ix(c)] { - NO_POS => false, - j => avail[j], - }; - self.up.node(&get, &av, t)? - }; - self.vals[k] = v; - work = work.saturating_add(1 + s); - } - let get = |c: TermId| match self.pos[ix(c)] { - NO_POS => &self.up.base[ix(c)], - j => &self.vals[j], - }; - let mut total = 0u128; - for &(r, m) in &self.root_mult { - total = total.saturating_add(m.saturating_mul(get(r).cost)); - } - for &c in self.stored_in.iter().chain(stored) { - total = total.saturating_add(get(c).inl); - } - if total == u128::MAX { - return Err(overflow()); - } - Ok((total, work)) - } - - fn charge(meter: &mut Meter<'_>, work: u64) -> Result<(), SharingError> { - meter.state()?; - meter.work(work) - } - - /// Whether `t` may be stored: a candidate not decided "not stored". - fn maybe(&self, out: &[bool], t: TermId) -> bool { - self.cand[ix(t)] && self.at(t).is_none_or(|j| !out[j]) - } - - fn rebound(&self, out: &[bool]) -> Result, SharingError> { - let mut ov = Overlay { - base: self.b0, - pos: self.pos, - vals: Vec::with_capacity(self.area.len()), - }; - for (j, &t) in self.area.iter().enumerate() { - let v = { - let ovr = &ov; - bound_step::( - self.up.nodes, - t, - &self.up.w, - self.cand[ix(t)] && !out[j], - &|c| ovr.bound(c, 2), - &|c| ovr.bound(c, 3), - )? - }; - ov.vals.push(v); - } - Ok(ov) - } - - /// Visible counts of the area terms (by area position). - fn revisible(&self, out: &[bool]) -> Vec<(u128, u128)> { - let mut vis: Vec<(u128, u128)> = vec![(0, 0); self.area.len()]; - for j in (0..self.area.len()).rev() { - let r = u128::from(self.root_occ[j]); - let (mut d, mut h) = (r, r); - for &(q, qj, ma, mh) in &self.in_edges[j] { - let wq = if self.maybe(out, q) { - 1 - } else { - match qj { - NO_POS => self.vis0.0[ix(q)], - qj => vis[qj].0, - } - .min(VISIBLE_CAP) - }; - d = d.saturating_add(u128::from(ma).saturating_mul(wq)); - h = h.saturating_add(u128::from(mh).saturating_mul(wq)); - } - vis[j] = (d, h); - } - vis - } - - fn opaque_under(&self, b: &Overlay<'_>, t: TermId) -> bool { - let v = b.get(t); - if self.up.nodes[ix(t)].family().is_none() { - v[0] >= self.up.w - } else { - v[1] >= self.up.w - } - } - - /// Groups of the undecided members (Lean `SCtx.groups`); `opq` and - /// `avail_fixed` are flags over the area positions. - fn groups( - &self, - opq: &[bool], - avail_fixed: &[bool], - und: &[TermId], - ) -> Vec> { - let und_f = self.flags(und); - let mut uf: Vec = (0..self.members.len()).collect(); - let mut reps: Vec> = vec![Vec::new(); self.area.len()]; - let union = |uf: &mut Vec, x: usize, y: usize| { - let a = uf_find(uf, x); - let b = uf_find(uf, y); - if a != b { - if a < b { - uf[b] = a; - } else { - uf[a] = b; - } - } - }; - let member = |j: usize| match self.member_of[j] { - NO_POS => 0, - m => m, - }; - for j in 0..self.area.len() { - let y = self.area[j]; - if opq[j] { - continue; - } - let mut rs: Vec = Vec::new(); - for &c in self.up.nodes[ix(y)].children().as_slice() { - if let Some(jc) = self.at(c) { - for &r in &reps[jc] { - let fr = uf_find(&uf, r); - if !rs.contains(&fr) { - rs.push(fr); - } - } - } - } - if und_f[j] { - let iy = member(j); - for &r in &rs { - union(&mut uf, iy, r); - } - reps[j] = vec![uf_find(&uf, iy)]; - } else if avail_fixed[j] && rs.len() > 1 { - let r0 = rs[0]; - for &r in &rs { - union(&mut uf, r0, r); - } - reps[j] = vec![uf_find(&uf, r0)]; - } else { - reps[j] = rs; - } - } - let mut groups: FxHashMap> = FxHashMap::default(); - for &t in und { - let r = uf_find(&uf, self.at(t).map_or(0, member)); - groups.entry(r).or_default().push(t); - } - let mut gs: Vec> = groups - .into_values() - .map(|mut g| { - g.sort_unstable(); - g - }) - .collect(); - gs.sort_by_key(|g| g[0]); - gs - } - - /// Memo key of a group and the decided members that reach it (Lean - /// `SCtx.memoKey`), and those members as flags over the area positions. - /// `opq`, `in_f` and `out` are flags over the area positions. - fn memo_key( - &self, - opq: &[bool], - in_f: &[bool], - out: &[bool], - g: &[TermId], - ) -> (Vec, Vec) { - let code = |q: TermId, j: usize| q * 3 + if opq[j] { 2 } else { 1 }; - let mut entries: Vec = Vec::new(); - let mut rel = vec![false; self.area.len()]; - let mut down_start: Vec = g.to_vec(); - let mut seen = self.flags(g); - let mut stack: Vec = g.to_vec(); - while let Some(y) = stack.pop() { - for &q in &self.facts.parents[ix(y)] { - let Some(j) = self.at(q) else { continue }; - if seen[j] { - continue; - } - seen[j] = true; - if out[j] { - entries.push(q * 3); - rel[j] = true; - } else if in_f[j] { - entries.push(code(q, j)); - rel[j] = true; - if !opq[j] { - down_start.push(q); - } - } - if !opq[j] { - stack.push(q); - } - } - } - let mut seen = self.flags(&down_start); - let mut stack = down_start; - while let Some(y) = stack.pop() { - for &c in self.up.nodes[ix(y)].children().as_slice() { - let Some(j) = self.at(c) else { continue }; - if seen[j] { - continue; - } - seen[j] = true; - if !rel[j] { - if out[j] { - entries.push(c * 3); - rel[j] = true; - } else if in_f[j] { - entries.push(code(c, j)); - rel[j] = true; - } - } - if !opq[j] { - stack.push(c); - } - } - } - entries.sort_unstable(); - let mut key: Vec = g.to_vec(); - key.push(3 * self.dag_size + 3); - key.extend(entries); - (key, rel) - } - - /// Lean `keyContext` and the checks before `solveP`'s memo lookup. Only - /// the key's context suffix is decoded; the group and separator are not. - fn key_context( - &self, - g: &[TermId], - entries: &[usize], - in_f: &[bool], - out: &[bool], - ) -> Result<(Vec, Vec), SharingError> { - let err = - || internal("memo key context is not part of the decided context"); - let (mut in_red, mut out_red) = (Vec::new(), Vec::new()); - for &e in entries { - let t: TermId = e / 3; - if e % 3 == 0 { - out_red.push(t); - } else { - in_red.push(t); - } - } - let ordered_members = |ts: &[TermId]| { - ts.windows(2).all(|w| w[0] < w[1]) - && ts - .iter() - .all(|&t| self.at(t).is_some_and(|j| self.member_of[j] != NO_POS)) - }; - let has = |set: &[bool], t| self.at(t).is_some_and(|j| set[j]); - if !ordered_members(g) - || !ordered_members(&in_red) - || !ordered_members(&out_red) - || g.iter().any(|&t| has(in_f, t) || has(out, t)) - || in_red.iter().any(|&t| !has(in_f, t) || has(out, t)) - || out_red.iter().any(|&t| !has(out, t) || has(in_f, t)) - { - return Err(err()); - } - Ok((in_red, out_red)) - } - - /// Lean `sepCheckA`: a non-out, non-opaque member must not reach both - /// the group and other members. Recompute opacity under the reduced - /// context; a cut inferred from the full context is not sufficient. - fn sep_check( - &self, - g: &[TermId], - in_red: &[TermId], - out_red: &[TermId], - ) -> Result { - let in_f = self.flags(in_red); - let out = self.flags(out_red); - let b = self.rebound(&out)?; - let opq: Vec = self - .area - .iter() - .enumerate() - .map(|(j, &t)| { - self.up.opaq[ix(t)] || (in_f[j] && self.opaque_under(&b, t)) - }) - .collect(); - // Bit 1: group; bit 2: other member; zero: unlabeled. OR is the - // reach-label join, and 3 is Lean's mixed label `some none`. - let mut label = vec![0u8; self.area.len()]; - for (ts, bit) in [(&self.members[..], 2), (g, 1), (in_red, 0), (out_red, 0)] - { - for &t in ts { - let Some(j) = self.at(t) else { - return Ok(false); - }; - label[j] = bit; - } - } - let mut reach = vec![0u8; self.area.len()]; - for (j, &t) in self.area.iter().enumerate() { - let mut r = label[j]; - for &c in self.up.nodes[ix(t)].children().as_slice() { - if let Some(k) = self.at(c) { - r |= if opq[k] { label[k] } else { reach[k] }; - } - } - reach[j] = r; - } - Ok( - g.iter().all(|&t| { - self.at(t).is_some_and(|j| self.member_of[j] != NO_POS && label[j] == 1) - }) && self.members.iter().all(|&t| { - self.at(t).is_some_and(|j| out[j] || opq[j] || reach[j] != 3) - }), - ) - } - - /// Exact table of a group under the decided members outside it (Lean - /// `SCtx.solve`). - fn solve( - &mut self, - meter: &mut Meter<'_>, - g: &[TermId], - in_all: &[TermId], - out_all: &[TermId], - ) -> Result, SharingError> { - let out = self.flags(out_all); - let in_f = self.flags(in_all); - let key = { - let b = self.rebound(&out)?; - let opq: Vec = self - .area - .iter() - .enumerate() - .map(|(j, &t)| { - self.up.opaq[ix(t)] || (in_f[j] && self.opaque_under(&b, t)) - }) - .collect(); - self.memo_key(&opq, &in_f, &out, g).0 - }; - let (in_red, out_red) = - self.key_context(g, &key[g.len() + 1..], &in_f, &out)?; - if let Some(tb) = self.memo.get(&key) { - self.memo_hits += 1; - return Ok(Arc::clone(tb)); - } - if !self.sep_check(g, &in_red, &out_red)? { - return Err(internal("search group is not separated")); - } - let (phi0, work) = self.phi(&self.flags(&in_red), &in_red)?; - Self::charge(meter, work.saturating_add(len64(self.area.len())))?; - let tb = self.node( - meter, - phi0, - &in_red, - out_red.clone(), - out_red.len(), - Vec::new(), - g, - Vec::new(), - )?; - let tb = Arc::new(ComponentSets::trim(tb, self.slack)); - self.memo.insert(key, Arc::clone(&tb)); - Ok(tb) - } - - /// Branch-and-bound node of a group (Lean `SCtx.node`). - #[allow(clippy::too_many_arguments)] - fn node( - &mut self, - meter: &mut Meter<'_>, - phi0: u128, - in_ctx: &[TermId], - out_all: Vec, - n_out_ctx: usize, - local_in: Vec, - und: &[TermId], - mut tb: LocalTable, - ) -> Result { - let out = self.flags(&out_all); - // Reclassify. - let (gains, opaque_in) = { - let b = self.rebound(&out)?; - let vis = self.revisible(&out); - let mut gains: Vec<(TermId, i128)> = Vec::with_capacity(und.len()); - for &t in und { - let (d, h) = self - .at(t) - .map_or((self.vis0.0[ix(t)], self.vis0.1[ix(t)]), |j| vis[j]); - let v = b.get(t); - let gt = stored_gain::( - &self.up.nodes[ix(t)], - d, - h, - self.spine_len[ix(t)], - &v[0], - &v[1], - &self.up.w, - )?; - gains.push((t, gt)); - } - // Opacity of every decided-stored member under these bounds (the - // forced members included), for the split below. - let mut opaque_in = vec![false; self.area.len()]; - for &t in in_ctx - .iter() - .chain(&local_in) - .chain(gains.iter().filter(|(_, g)| *g >= self.theta).map(|(t, _)| t)) - { - if self.opaque_under(&b, t) - && let Some(j) = self.at(t) - { - opaque_in[j] = true; - } - } - (gains, opaque_in) - }; - let forced: Vec = - gains.iter().filter(|(_, g)| *g >= self.theta).map(|(t, _)| *t).collect(); - let local_in = if forced.is_empty() { - local_in - } else { - merge_sorted(&local_in, &forced) - }; - let open: Vec<(TermId, i128)> = - gains.into_iter().filter(|(_, g)| *g < self.theta).collect(); - let und: Vec = open.iter().map(|(t, _)| *t).collect(); - let in_all: Vec = in_ctx.iter().chain(&local_in).copied().collect(); - let in_f = self.flags(&in_all); - // Lower bound: every undecided member available, its entry free. - let mut in_und = self.flags(&und); - for (a, &b) in in_und.iter_mut().zip(&in_f) { - *a |= b; - } - let (phi, work) = self.phi(&in_und, &in_all)?; - Self::charge(meter, work.saturating_add(2 * len64(self.area.len())))?; - let delta = to_i(phi)? - to_i(phi0)?; - if und.is_empty() { - let sets = self.sets(); - sets.add(&mut tb, (delta, sets.pack(local_in))); - return Ok(tb); - } - if let Some(bd) = ComponentSets::best(&tb) - && delta > bd + self.slack - { - return Ok(tb); - } - // Split into independent groups. - let opq: Vec = self - .area - .iter() - .enumerate() - .map(|(j, &t)| self.up.opaq[ix(t)] || (in_f[j] && opaque_in[j])) - .collect(); - let avail_fixed: Vec = - in_f.iter().zip(&opq).map(|(&i, &o)| i && !o).collect(); - let groups = self.groups(&opq, &avail_fixed, &und); - let route = if groups.len() > 1 { - true - } else if groups.len() == 1 { - let (_, rel) = self.memo_key(&opq, &in_f, &out, &groups[0]); - let related = |t: &TermId| self.at(*t).is_some_and(|j| rel[j]); - local_in.iter().any(|t| !related(t)) - || out_all[n_out_ctx..].iter().any(|t| !related(t)) - } else { - false - }; - if route { - let (phi_none, work) = self.phi(&in_f, &in_all)?; - Self::charge(meter, work)?; - let base = to_i(phi_none)? - to_i(phi0)?; - let mut comb: LocalTable = - vec![Some((base, self.sets().pack(local_in.clone())))]; - for grp in &groups { - let sub = self.solve(meter, grp, &in_all, &out_all)?; - comb = ComponentSets::trim(self.sets().conv(&comb, &sub), self.slack); - } - for e in comb.into_iter().flatten() { - self.sets().add(&mut tb, e); - } - return Ok(tb); - } - // Branch on the largest |gain|, "stored" first when it is positive. - let Some((t, gt)) = pick_branch(&open) else { - return Ok(tb); - }; - let mut und2 = und.clone(); - if let Some(p) = und2.iter().position(|&x| x == t) { - und2.remove(p); - } - let with_t = merge_sorted(&local_in, &[t]); - let mut out_t = out_all.clone(); - out_t.push(t); - if gt > 0 { - let tb = self - .node(meter, phi0, in_ctx, out_all, n_out_ctx, with_t, &und2, tb)?; - self.node(meter, phi0, in_ctx, out_t, n_out_ctx, local_in, &und2, tb) - } else { - let tb = self.node( - meter, - phi0, - in_ctx, - out_t, - n_out_ctx, - local_in.clone(), - &und2, - tb, - )?; - self.node(meter, phi0, in_ctx, out_all, n_out_ctx, with_t, &und2, tb) - } - } -} - -/// Exact optimization up to decisions; the returned plan builds expressions -/// or describes their shares for the tiered construction's dry run. -pub(crate) fn optimize_uniform_with( - w: u64, - dag: &SharingDag, - prep: &DagPrep, - meter: &mut Meter<'_>, -) -> Result<(UniformSharingResult, UniformPlan), SharingError> { - let p = prof::scope(Phase::Classify); - // Fail closed on a telescope spine of `TELE_SUBADD_END` or more steps, - // before the search (Lean's `optimizeUniformExpanded`): the reference - // optimality argument needs subadditive telescope headers. A zero width - // is refused first, by `initial`. - if w != 0 && !spines_within_bound(&prep.spine_len) { - return Err(SharingError::FormatBound(FormatBound::TelescopeSpine { - bound: TELE_SUBADD_END, - })); - } - let Initial { - up, - cand, - bounds, - vis0, - theta, - cs, - ce_terms, - unc, - low, - comps, - slack, - } = initial::(w, dag, prep, meter)?; - let nodes = dag.nodes(); - let n = nodes.len(); - let facts = &prep.facts; - let spine_len = &prep.spine_len; - // Search each component (independent searches; see `Parallelism`). - let search_area = |members: &Vec, - area: Vec, - meter: &mut Meter<'_>, - pos: &[usize]| - -> Result<(Choice, CTable), SharingError> { - let mut mult: std::collections::BTreeMap = - std::collections::BTreeMap::new(); - for &r in dag.roots() { - if pos[ix(r)] != NO_POS { - *mult.entry(r).or_default() += 1; - } - } - let root_mult: Vec<(TermId, u128)> = mult.into_iter().collect(); - let stored_in: Vec = - area.iter().copied().filter(|&t| up.opaq[ix(t)]).collect(); - if meter.limits().uniform_subset_search { - // The reference: plain subset enumeration. - let mut cx = CompCtx { - up: &up, - members: members.clone(), - area, - root_mult, - stored_in, - phi0: 0, - slack, - vals: vec![UVal::default(); n], - stamp: vec![0; n], - epoch: 0, - avail: vec![false; n], - }; - let (phi0, _) = cx.phi(&[])?; - cx.phi0 = phi0; - return cx.search(meter); - } - let mut member_of: Vec = vec![NO_POS; area.len()]; - for (m, &t) in members.iter().enumerate() { - let j = pos[ix(t)]; - if j < area.len() { - member_of[j] = m; - } - } - let in_edges: Vec> = area - .iter() - .map(|&y| { - facts.parents[ix(y)] - .iter() - .map(|&q| { - let node = &nodes[ix(q)]; - let (mut ma, mut mh) = (0u64, 0u64); - for (i, &c) in node.children().as_slice().iter().enumerate() { - if c == y { - ma += 1; - if !continuation_edge(node, i, &nodes[ix(y)]) { - mh += 1; - } - } - } - (q, pos[ix(q)], ma, mh) - }) - .collect() - }) - .collect(); - let root_occ: Vec = area - .iter() - .map(|y| { - root_mult - .iter() - .find(|(r, _)| r == y) - .map_or(0, |(_, m)| u64::try_from(*m).unwrap_or(u64::MAX)) - }) - .collect(); - let area_len = area.len(); - let mut cx = SCtx { - up: &up, - facts, - cand: &cand, - b0: &bounds, - vis0: &vis0, - spine_len, - dag_size: n, - members: members.clone(), - member_of, - area, - pos, - in_edges, - root_occ, - root_mult, - stored_in, - slack, - theta, - set_words: ComponentSets::new(members, true).words, - memo: FxHashMap::default(), - memo_hits: 0, - vals: vec![UVal::default(); area_len], - }; - let tb = cx.solve(meter, members, &[], &[])?; - // Only the final component table escapes. Release its memo owners - // before recovering the owned table expected by the existing API. - drop(cx); - let tb = ComponentSets::new(members, true).decode(Arc::unwrap_or_clone(tb)); - let bd = table_best(&tb) - .ok_or_else(|| internal("component search found no choice"))?; - let mut best_set: Option<&Vec> = None; - for (d, s) in tb.iter().flatten() { - if *d == bd && best_set.is_none_or(|a| set_prec(s, a)) { - best_set = Some(s); - } - } - let bs = best_set - .cloned() - .ok_or_else(|| internal("component search found no choice"))?; - Ok(((bd, bs), tb)) - }; - // `pos` maps every term to its position in the component's area - // (`NO_POS` outside it); it is set for the area of each component and - // cleared afterwards, so one buffer serves every component. - let search_one = |members: &Vec, - meter: &mut Meter<'_>, - pos: &mut [usize], - stack: &mut Vec| - -> Result<(Choice, CTable), SharingError> { - let area = component_area_pos(&up, facts, members, pos, NO_POS, stack); - let res = search_area(members, area.clone(), meter, pos); - for &t in &area { - pos[ix(t)] = NO_POS; - } - res - }; - drop(p); - let p = prof::scope(Phase::Search); - let budget = meter.parallel.components; - let mut results: Vec<(Choice, CTable)> = Vec::with_capacity(comps.len()); - if budget <= 1 { - let mut pos = vec![NO_POS; n]; - let mut stack = Vec::new(); - for members in &comps { - results.push(search_one(members, meter, &mut pos, &mut stack)?); - } - } else { - // Each component on its own meter; the counts are added in component - // order afterwards (`Meter::absorb`), then the results are used in the - // same order as sequentially. - let limits = meter.limits(); - let outs = super::super::par::map_ranges(comps.len(), budget, |r| { - let mut pos = vec![NO_POS; n]; - let mut stack = Vec::new(); - r.map(|j| { - let mut m = Meter::new(limits); - let res = search_one(&comps[j], &mut m, &mut pos, &mut stack); - (res, m.stats) - }) - .collect() - }); - for (res, stats) in outs { - meter.absorb(&stats)?; - results.push(res?); - } - } - drop(p); - combine_uniform( - w, - dag, - prep, - meter, - Initial { - up, - cand, - bounds, - vis0, - theta, - cs, - ce_terms, - unc, - low, - comps, - slack, - }, - results, - ) -} - -#[cfg(test)] -mod convolution_tests { - use super::*; - - // The previous eager implementation is the complete-table oracle. - fn eager(a: &CTable, b: &CTable) -> CTable { - let mut out = CTable::new(); - for (da, sa) in a.iter().flatten() { - for (db, sb) in b.iter().flatten() { - table_add(&mut out, (da + db, merge_sorted(sa, sb))); - } - } - out - } - - #[test] - fn merged_streams_preserve_duplicates_prefixes_and_exact_ties() { - let mut lists = vec![Vec::new()]; - for len in 1..=3u32 { - for mut code in 0..3usize.pow(len) { - let mut xs = Vec::new(); - for _ in 0..len { - xs.push(code % 3); - code /= 3; - } - xs.sort_unstable(); - if !lists.contains(&xs) { - lists.push(xs); - } - } - } - assert_eq!(lists.len(), 20); - let pairs: Vec<_> = lists - .iter() - .flat_map(|a| { - lists - .iter() - .map(move |b| (a.as_slice(), b.as_slice(), merge_sorted(a, b))) - }) - .collect(); - for (a, b, expected) in &pairs { - let mut merged = Merged { a, b }; - for (i, &t) in expected.iter().enumerate() { - assert_eq!(merged.len(), expected.len() - i); - assert_eq!(merged.next(), Some(t)); - } - assert_eq!((merged.len(), merged.next()), (0, None)); - for (c, d, other) in &pairs { - assert_eq!( - merged_prec(Merged { a, b }, Merged { a: c, b: d }), - set_prec(expected, other), - "{a:?}+{b:?} vs {c:?}+{d:?}" - ); - } - } - // Equal choices must not replace the first winner, even when split differently. - assert!(!merged_prec( - Merged { a: &[1], b: &[2] }, - Merged { a: &[], b: &[1, 2] } - )); - } - - #[test] - fn convolution_preserves_complete_sparse_tables_and_actual_counts() { - let tables = [ - vec![], - vec![None, None], - vec![Some((0, vec![])), None, None], - vec![None, Some((0, vec![1, 1])), None, Some((0, vec![]))], - vec![ - Some((-1, vec![0, 4])), - Some((0, vec![1])), - None, - Some((-1, vec![4])), - ], - vec![Some((0, vec![2])), Some((0, vec![1])), Some((0, vec![1]))], - ]; - for a in &tables { - for b in &tables { - assert_eq!(table_conv(a, b), eager(a, b)); - } - } - // Deliberately unrelated row indices and overlapping input sets: count - // the actual merged multiset, preserve every duplicate and every hole. - let got = table_conv( - &vec![Some((0, vec![1, 1]))], - &vec![Some((0, vec![1, 2])), None], - ); - assert_eq!(got, vec![None, None, None, None, Some((0, vec![1, 1, 1, 2]))]); - } - - fn next(seed: &mut u64, n: u64) -> u64 { - *seed ^= *seed << 13; - *seed ^= *seed >> 7; - *seed ^= *seed << 17; - *seed % n - } - - fn random_table(seed: &mut u64, parity: Option) -> CTable { - (0..next(seed, 10)) - .map(|_| { - if next(seed, 4) == 0 { - return None; - } - let mut xs: Vec<_> = (0..next(seed, 9)) - .map(|_| { - let t = TermId::try_from(next(seed, 12)).unwrap(); - parity.map_or(t, |p| 2 * t + p) - }) - .collect(); - xs.sort_unstable(); - if parity.is_some() { - xs.dedup(); - } - Some((i128::from(next(seed, 5)) - 2, xs)) - }) - .collect() - } - - #[test] - fn convolution_matches_eager_random_tables() { - let mut seed = 0x9E37_79B9_7F4A_7C15; - for i in 0..1000 { - // Alternate arbitrary overlapping multisets and disjoint group sets. - let disjoint = i % 2 == 0; - let a = random_table(&mut seed, disjoint.then_some(0)); - let b = random_table(&mut seed, disjoint.then_some(1)); - assert_eq!(table_conv(&a, &b), eager(&a, &b), "case {i}: {a:?} {b:?}"); - } - } -} - -#[cfg(test)] -mod compact_choice_tests { - use super::*; - - fn encode(codec: ComponentSets<'_>, tb: &CTable, mixed: bool) -> LocalTable { - tb.iter() - .enumerate() - .map(|(i, e)| { - e.as_ref().map(|(d, ids)| { - let set = if mixed && i % 2 == 1 { - LocalSet::Listed(ids.clone()) - } else { - codec.pack(ids.clone()) - }; - (*d, set) - }) - }) - .collect() - } - - #[test] - fn compact_roundtrip_and_ties_cover_word_and_fallback_boundaries() { - for n in [0, 1, 63, 64, 65, 127, 128, 511, 512, 513] { - let members: Vec<_> = (0..n).map(|i| 10 + 3 * i).collect(); - let codec = ComponentSets::new(&members, true); - assert_eq!(codec.words.is_some(), n <= PACKED_MEMBERS); - let mut cases = vec![ - vec![], - members.clone(), - members.iter().step_by(2).copied().collect(), - members.iter().take(3).copied().collect(), - members.iter().skip(n.saturating_sub(3)).copied().collect(), - vec![0], - ]; - if n != 0 { - cases.push(vec![members[0], members[0]]); - } - for a in &cases { - let packed = codec.pack(a.clone()); - assert_eq!(packed.len(), a.len()); - assert_eq!(codec.iter(&packed).collect::>(), *a); - let eligible = n <= PACKED_MEMBERS - && a.windows(2).all(|w| w[0] < w[1]) - && a.iter().all(|t| members.binary_search(t).is_ok()); - assert_eq!(matches!(packed, LocalSet::Packed(_)), eligible); - for b in &cases { - let bp = codec.pack(b.clone()); - let al = LocalSet::Listed(a.clone()); - let bl = LocalSet::Listed(b.clone()); - for (x, y) in [(&packed, &bp), (&packed, &bl), (&al, &bp), (&al, &bl)] - { - assert_eq!(codec.prec(x, y), set_prec(a, b)); - let union = codec.materialize(codec.pair(x, y)); - assert_eq!(union.len(), a.len() + b.len()); - assert_eq!( - codec.iter(&union).collect::>(), - merge_sorted(a, b) - ); - } - } - } - if n != 0 && n <= PACKED_MEMBERS { - let one = codec.pack(vec![members[0]]); - let pair = codec.pair(&one, &one); - assert!(!pair.packed, "overlap must preserve multiplicity"); - assert_eq!( - codec.materialize(pair), - LocalSet::Listed(vec![members[0]; 2]) - ); - } - } - for members in [vec![2, 1], vec![1, 1]] { - let codec = ComponentSets::new(&members, true); - assert!(codec.words.is_none()); - assert_eq!(codec.pack(vec![1, 2]), LocalSet::Listed(vec![1, 2])); - } - } - - #[test] - fn compact_pairs_match_all_stream_ties_and_equal_choices() { - let members = [3, 6, 9, 12, 15, 18, 21, 24, 27]; - let codec = ComponentSets::new(&members, true); - let cases = [ - vec![], - vec![3], - vec![6], - vec![3, 6], - vec![6, 9], - vec![3, 3], - vec![0, 6], - ]; - let sets: Vec<_> = cases - .iter() - .flat_map(|s| [codec.pack(s.clone()), LocalSet::Listed(s.clone())]) - .collect(); - for a in &sets { - for b in &sets { - for c in &sets { - for d in &sets { - let p = codec.pair(a, b); - let q = codec.pair(c, d); - let expected_p = codec.union(p).collect::>(); - let expected_q = codec.union(q).collect::>(); - assert_eq!( - codec.pair_prec(p, q), - set_prec(&expected_p, &expected_q) - ); - } - } - } - } - } - - fn next(seed: &mut u64, n: usize) -> usize { - *seed ^= *seed << 13; - *seed ^= *seed >> 7; - *seed ^= *seed << 17; - usize::try_from(*seed % len64(n)).unwrap() - } - - fn table(seed: &mut u64, members: &[TermId], unique: bool) -> CTable { - (0..next(seed, 9)) - .map(|_| { - if next(seed, 4) == 0 { - return None; - } - let mut ids: Vec<_> = (0..next(seed, 12)) - .map(|_| { - let i = next(seed, members.len() + 2); - members.get(i).copied().unwrap_or(1_000_000 + i) - }) - .collect(); - ids.sort_unstable(); - if unique { - ids.dedup(); - } - Some((i128::try_from(next(seed, 5)).unwrap() - 2, ids)) - }) - .collect() - } - - #[test] - fn compact_tables_match_complete_listed_results() { - let mut seed = 0x9E37_79B9_7F4A_7C15; - for n in [0, 1, 63, 64, 65, 511, 512, 513] { - let members: Vec<_> = (0..n).map(|i| 17 + 5 * i).collect(); - for i in 0..100 { - let a = table(&mut seed, &members, i % 2 == 0); - let b = table(&mut seed, &members, i % 2 == 0); - let expected = table_conv(&a, &b); - for enabled in [false, true] { - let codec = ComponentSets::new(&members, enabled); - for mixed in [false, true] { - let local = - codec.conv(&encode(codec, &a, mixed), &encode(codec, &b, mixed)); - assert_eq!(codec.decode(local.clone()), expected, "n={n} case={i}"); - for slack in [0, 1, 2] { - assert_eq!( - codec.decode(ComponentSets::trim(local.clone(), slack)), - table_trim(expected.clone(), slack) - ); - } - } - } - } - } - } - - #[test] - fn compact_and_forced_listed_search_have_identical_tables_and_counters() { - let mut results = Vec::new(); - for packed in [false, true] { - context_tests::with_context(1, |cx, parent| { - if !packed { - cx.set_words = None; - } - let limits = ExactSharingLimits::default(); - let mut meter = Meter::new(&limits); - let table = cx.solve(&mut meter, &[0, 1], &[2, parent], &[3]).unwrap(); - results.push((cx.sets().decode(table.as_ref().clone()), meter.stats)); - }); - } - assert_eq!(results[0], results[1]); - eprintln!( - "choice metadata: listed cell={}B local cell={}B; target packed payload=5*8B; vector capacity/hash/allocator overhead is additional", - size_of::>(), - size_of::>() - ); - } -} - -#[cfg(test)] -mod context_tests { - use super::*; - - // Four candidate leaves under one candidate App ancestor. The two - // intermediate App prefixes have compact in-degree one, hence are not - // candidates. Storing the ancestor separates the leaves. - pub(super) fn with_context( - w: u128, - test: impl FnOnce(&mut SCtx<'_, '_>, TermId), - ) { - let leaves: Vec<_> = (0..4).map(|i| Expr::reference(i, vec![0])).collect(); - let parent = leaves[1..] - .iter() - .fold(leaves[0].clone(), |f, x| Expr::app(f, x.clone())); - let mut roots = vec![parent.clone(), parent]; - roots.extend(leaves); - let dag = - SharingDag::from_expanded_roots(&roots, &ExactSharingLimits::default()) - .unwrap(); - let nodes = dag.nodes(); - let n = nodes.len(); - let facts = graph_facts(&dag); - let cand: Vec = facts.deg.iter().map(|&d| d >= 2).collect(); - let members: Vec<_> = (0..n).filter(|&t| cand[t]).collect(); - let up = UPrep::::new(nodes, w, vec![false; n]).unwrap(); - let b0 = uniform_bounds::(nodes, &w, &cand).unwrap(); - let vis0 = visible_counts(&dag, &cand); - let (spine_len, _) = spine_tables(nodes, &vec![false; n]); - let pos: Vec<_> = (0..n).collect(); - let mut member_of = vec![NO_POS; n]; - for (i, &t) in members.iter().enumerate() { - member_of[ix(t)] = i; - } - let in_edges = (0..n) - .map(|y| { - facts.parents[y] - .iter() - .map(|&q| { - let p = &nodes[ix(q)]; - let edge = - p.children().as_slice().iter().position(|&c| ix(c) == y).unwrap(); - (q, q, 1, u64::from(!continuation_edge(p, edge, &nodes[y]))) - }) - .collect() - }) - .collect(); - let mut root_occ = vec![0; n]; - for &r in dag.roots() { - root_occ[ix(r)] += 1; - } - let root_mult = root_occ - .iter() - .enumerate() - .filter(|(_, m)| **m != 0) - .map(|(t, &m)| (t, u128::from(m))) - .collect(); - let set_words = ComponentSets::new(&members, true).words; - let mut cx = SCtx { - up: &up, - facts: &facts, - cand: &cand, - b0: &b0, - vis0: &vis0, - spine_len: &spine_len, - dag_size: n, - members, - member_of, - area: pos.clone(), - pos: &pos, - in_edges, - root_occ, - root_mult, - stored_in: vec![], - slack: 1, - theta: 2, - set_words, - memo: FxHashMap::default(), - memo_hits: 0, - vals: vec![UVal::default(); n], - }; - test(&mut cx, dag.roots()[0]); - } - - #[test] - fn reduced_context_keeps_all_counts_ties_and_memo_replay() { - with_context(1, |cx, parent| { - let group = [0, 1]; - let in_all = [2, parent]; - let out_all = [3]; - let in_f = cx.flags(&in_all); - let out = cx.flags(&out_all); - let b = cx.rebound(&out).unwrap(); - let opq: Vec<_> = cx - .area - .iter() - .enumerate() - .map(|(j, &t)| in_f[j] && cx.opaque_under(&b, t)) - .collect(); - let key = cx.memo_key(&opq, &in_f, &out, &group).0; - let (in_red, out_red) = - cx.key_context(&group, &key[group.len() + 1..], &in_f, &out).unwrap(); - assert_eq!((&in_red, &out_red), (&vec![parent], &vec![])); - assert!(in_red.len() < in_all.len() && out_red.len() < out_all.len()); - let base_full = cx.phi(&in_f, &in_all).unwrap().0; - let base_red = cx.phi(&cx.flags(&in_red), &in_red).unwrap().0; - let mut reference = CTable::new(); - for mask in 0..4 { - let chosen: Vec<_> = group - .iter() - .enumerate() - .filter(|(i, _)| mask & (1 << i) != 0) - .map(|(_, &t)| t) - .collect(); - let full = merge_sorted(&in_all, &chosen); - let red = merge_sorted(&in_red, &chosen); - let df = to_i(cx.phi(&cx.flags(&full), &full).unwrap().0).unwrap() - - to_i(base_full).unwrap(); - let dr = to_i(cx.phi(&cx.flags(&red), &red).unwrap().0).unwrap() - - to_i(base_red).unwrap(); - assert_eq!(df, dr, "subset {chosen:?}"); - table_add(&mut reference, (dr, chosen)); - } - let reference = table_trim(reference, cx.slack); - assert!(reference.iter().flatten().count() >= 2); - assert_eq!( - reference[1].as_ref().unwrap().1, - vec![1], - "canonical one-member tie" - ); - let limits = ExactSharingLimits::default(); - let mut meter = Meter::new(&limits); - let got = cx.solve(&mut meter, &group, &in_all, &out_all).unwrap(); - let decoded = cx.sets().decode(got.as_ref().clone()); - assert_eq!( - decoded.iter().flatten().collect::>(), - reference.iter().flatten().collect::>() - ); - let (hits, states) = (cx.memo_hits, meter.stats.states_created); - let replay = cx.solve(&mut meter, &group, &[parent], &[2, 3]).unwrap(); - assert_eq!(got, replay); - assert_eq!( - (cx.memo_hits, meter.stats.states_created), - (hits + 1, states) - ); - }); - } - - #[test] - fn reduced_context_rejects_mixed_reach_and_bad_keys() { - with_context(1, |cx, parent| { - assert!(cx.sep_check(&[0, 1], &[parent], &[]).unwrap()); - assert!(cx.sep_check(&[0, 1], &[], &[parent]).unwrap()); - assert!(!cx.sep_check(&[0, 1], &[], &[]).unwrap()); - assert!(!cx.sep_check(&[0, 1], &[0, parent], &[]).unwrap()); - let inside = cx.flags(&[parent]); - let outside = cx.flags(&[]); - for (group, entries) in [ - (vec![0, 0], vec![]), - (vec![0], vec![3 * parent + 2; 2]), - (vec![0], vec![3 * parent]), - (vec![0], vec![3 * (TermId::MAX / 3 - 1) + 2]), - (vec![parent], vec![]), - ] { - assert!(cx.key_context(&group, &entries, &inside, &outside).is_err()); - } - let limits = ExactSharingLimits::default(); - assert!(matches!(cx.solve(&mut Meter::new(&limits), &[0, 1], &[], &[]), - Err(SharingError::Internal(msg)) if msg == "search group is not separated")); - }); - with_context(100, |cx, parent| { - // A stored term is not necessarily opaque: recheck under the actual - // reduced bounds instead of trusting an encoded opaque tag. - assert!(!cx.sep_check(&[0, 1], &[parent], &[]).unwrap()); - }); - } -} diff --git a/crates/kernel/tests/fixtures/input_contracts/.gitignore b/crates/ixon/tests/fixtures/input_contracts/.gitignore similarity index 90% rename from crates/kernel/tests/fixtures/input_contracts/.gitignore rename to crates/ixon/tests/fixtures/input_contracts/.gitignore index c4db93d79..6eab3230f 100644 --- a/crates/kernel/tests/fixtures/input_contracts/.gitignore +++ b/crates/ixon/tests/fixtures/input_contracts/.gitignore @@ -1,3 +1,2 @@ # These are committed canonical regression inputs, not generated workspaces. -!*.const !*.ixe diff --git a/crates/kernel/tests/fixtures/input_contracts/README.txt b/crates/ixon/tests/fixtures/input_contracts/README.txt similarity index 61% rename from crates/kernel/tests/fixtures/input_contracts/README.txt rename to crates/ixon/tests/fixtures/input_contracts/README.txt index 94dcd73af..33e1eb643 100644 --- a/crates/kernel/tests/fixtures/input_contracts/README.txt +++ b/crates/ixon/tests/fixtures/input_contracts/README.txt @@ -1,10 +1,10 @@ Canonical C+ input-contract regression fixtures These six reference-free definitions were constructed directly as Ixon data, -serialized and fully decoded, then checked by published 137 -8fb4049f9f0833d1e583d5a19b049c7e9a7169c0. Each .const is exactly 15 bytes and -each complete .ixe envelope 122 bytes. input_contracts.rs checks the exact -addresses, scalar fields, structure and byte roundtrip before check_const. +serialized and fully decoded, then checked against published 137 +8fb4049f9f0833d1e583d5a19b049c7e9a7169c0. Each complete .ixe envelope is 122 +bytes. The fixtures record the exact addresses, scalar fields, structure and +serialized bytes for these six cases. The type is Prop -> Prop and the body is the identity lambda over Prop. No external constant, primitive role, assumption or resource validation is @@ -14,3 +14,6 @@ inputs and the result-only unique variant must be accepted. Published 137 accepted all six, demonstrating the intended negative regression. The bytes and expected behavior are fixed independently of compiler output. + +Today only `crates/ixon/tests/role_binding.rs` reads them, as inputs for the +role-binding transport tests, which claim nothing about admission. diff --git a/crates/kernel/tests/fixtures/input_contracts/linear-match.ixe b/crates/ixon/tests/fixtures/input_contracts/linear-match.ixe similarity index 100% rename from crates/kernel/tests/fixtures/input_contracts/linear-match.ixe rename to crates/ixon/tests/fixtures/input_contracts/linear-match.ixe diff --git a/crates/kernel/tests/fixtures/input_contracts/locality-mismatch.ixe b/crates/ixon/tests/fixtures/input_contracts/locality-mismatch.ixe similarity index 100% rename from crates/kernel/tests/fixtures/input_contracts/locality-mismatch.ixe rename to crates/ixon/tests/fixtures/input_contracts/locality-mismatch.ixe diff --git a/crates/kernel/tests/fixtures/input_contracts/many-match.ixe b/crates/ixon/tests/fixtures/input_contracts/many-match.ixe similarity index 100% rename from crates/kernel/tests/fixtures/input_contracts/many-match.ixe rename to crates/ixon/tests/fixtures/input_contracts/many-match.ixe diff --git a/crates/kernel/tests/fixtures/input_contracts/ownership-mismatch.ixe b/crates/ixon/tests/fixtures/input_contracts/ownership-mismatch.ixe similarity index 100% rename from crates/kernel/tests/fixtures/input_contracts/ownership-mismatch.ixe rename to crates/ixon/tests/fixtures/input_contracts/ownership-mismatch.ixe diff --git a/crates/kernel/tests/fixtures/input_contracts/result-only-unique.ixe b/crates/ixon/tests/fixtures/input_contracts/result-only-unique.ixe similarity index 100% rename from crates/kernel/tests/fixtures/input_contracts/result-only-unique.ixe rename to crates/ixon/tests/fixtures/input_contracts/result-only-unique.ixe diff --git a/crates/kernel/tests/fixtures/input_contracts/uses-mismatch.ixe b/crates/ixon/tests/fixtures/input_contracts/uses-mismatch.ixe similarity index 100% rename from crates/kernel/tests/fixtures/input_contracts/uses-mismatch.ixe rename to crates/ixon/tests/fixtures/input_contracts/uses-mismatch.ixe diff --git a/crates/ixon/tests/reject_parity.rs b/crates/ixon/tests/reject_parity.rs index e98bad211..e8e4f3436 100644 --- a/crates/ixon/tests/reject_parity.rs +++ b/crates/ixon/tests/reject_parity.rs @@ -1,8 +1,8 @@ //! Malformed inputs every Rust reader of their kind refuses: the Rust side of //! the shared fixture `Tests/Fixtures/ixon-v4/reject-parity.txt`, which the Lean suite //! `ixon-reject-parity` (`Tests/Ix/IxonRejectParity.lean`) reads too, so -//! that both sides refuse the same byte strings. The `.ixes` cases are the -//! kernel crate's (`crates/kernel/tests/reject_parity.rs`). +//! that both sides refuse the same byte strings. Only the Lean suite reads +//! the `.ixes` cases. use ixon::assumption_tree::AssumptionTree; use ixon::constant::Constant; diff --git a/crates/ixon/tests/role_binding.rs b/crates/ixon/tests/role_binding.rs index 526c50910..5a5e0d991 100644 --- a/crates/ixon/tests/role_binding.rs +++ b/crates/ixon/tests/role_binding.rs @@ -12,22 +12,12 @@ use ixon::tag::TagN; use rustc_hash::FxHashSet; const INPUTS: [&[u8]; 6] = [ - include_bytes!("../../kernel/tests/fixtures/input_contracts/many-match.ixe"), - include_bytes!( - "../../kernel/tests/fixtures/input_contracts/linear-match.ixe" - ), - include_bytes!( - "../../kernel/tests/fixtures/input_contracts/uses-mismatch.ixe" - ), - include_bytes!( - "../../kernel/tests/fixtures/input_contracts/ownership-mismatch.ixe" - ), - include_bytes!( - "../../kernel/tests/fixtures/input_contracts/locality-mismatch.ixe" - ), - include_bytes!( - "../../kernel/tests/fixtures/input_contracts/result-only-unique.ixe" - ), + include_bytes!("fixtures/input_contracts/many-match.ixe"), + include_bytes!("fixtures/input_contracts/linear-match.ixe"), + include_bytes!("fixtures/input_contracts/uses-mismatch.ixe"), + include_bytes!("fixtures/input_contracts/ownership-mismatch.ixe"), + include_bytes!("fixtures/input_contracts/locality-mismatch.ixe"), + include_bytes!("fixtures/input_contracts/result-only-unique.ixe"), ]; fn binding(bytes: &[u8]) -> RoleBinding { diff --git a/crates/ixvm-codegen/Cargo.toml b/crates/ixvm-codegen/Cargo.toml index 831916731..42e1e5322 100644 --- a/crates/ixvm-codegen/Cargo.toml +++ b/crates/ixvm-codegen/Cargo.toml @@ -8,9 +8,7 @@ license.workspace = true aiur = { workspace = true } ix-common = { workspace = true } ixon = { workspace = true } -dashmap = { workspace = true, features = ["rayon"] } multi-stark = { workspace = true } -num-bigint = { workspace = true } rayon = { workspace = true } rustc-hash = { workspace = true } stacker = "0.1" diff --git a/crates/ixvm-codegen/src/aiur_ix_aggr.rs b/crates/ixvm-codegen/src/aiur_ix_aggr.rs index b216fc955..0e7feceb4 100644 --- a/crates/ixvm-codegen/src/aiur_ix_aggr.rs +++ b/crates/ixvm-codegen/src/aiur_ix_aggr.rs @@ -352,7 +352,7 @@ fn aiur_fn_84(inp: [G; IN_84], input_hash: u64, recor const INPUT_SIZE_85: usize = 6; const IN_85: usize = 6; const OUT_85: usize = 1; -fn aiur_fn_85(inp: [G; IN_85], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_85], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = G::from_u64(1); let __v_7: G = G::from_u64(2); let __v_8: G = G::from_u64(8); let __r_arr: [G; OUT_82] = { let __args: [G; IN_82] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(82, &__args[..])?) { [G::ZERO; OUT_82] } else { let (__hash, __hit) = record.function_queries[82].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[82].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[82].bump_multiplicity(__i); } let __ret: [G; OUT_82] = unsafe { *(record.function_queries[82].output_at(__i).as_ptr() as *const [G; OUT_82]) }; __ret }, _ => { aiur_fn_82::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = __r_arr[1]; let __v_11: G = __r_arr[2]; let __v_12: G = __r_arr[3]; let __v_13: G = __r_arr[4]; let __v_14: G = __r_arr[5]; let __v_15: G = __r_arr[6]; let __v_16: G = __r_arr[7]; let __v_17: G = __r_arr[8]; let __v_18: G = __r_arr[9]; let __v_19: G = __r_arr[10]; let __v_20: G = __r_arr[11]; let __v_21: G = __r_arr[12]; let __v_22: G = __r_arr[13]; let __v_23: G = __r_arr[14]; let __v_24: G = __r_arr[15]; let __v_25: G = __r_arr[16]; let __v_26: G = __r_arr[17]; let __v_27: G = __r_arr[18]; let __v_28: G = __r_arr[19]; let __v_29: G = __r_arr[20]; let __v_30: G = __r_arr[21]; let __v_31: G = __r_arr[22]; let __v_32: G = __r_arr[23]; let __v_33: G = __r_arr[24]; let __v_34: G = __r_arr[25]; let __v_35: G = __r_arr[26]; let __v_36: G = __r_arr[27]; let __v_37: G = __r_arr[28]; let __v_38: G = __r_arr[29]; let __v_39: G = __r_arr[30]; let __v_40: G = __r_arr[31]; let __v_41: G = __r_arr[32]; let __v_42: G = __r_arr[33]; let __v_43: G = __r_arr[34]; let __v_44: G = __r_arr[35]; let __v_45: G = __r_arr[36]; let __v_46: G = __r_arr[37]; let __v_47: G = __r_arr[38]; let __v_48: G = __r_arr[39]; let __v_49: G = __r_arr[40]; let __v_50: G = __r_arr[41]; let __v_51: G = __r_arr[42]; let __v_52: G = __r_arr[43]; let __v_53: G = __r_arr[44]; let __v_54: G = __r_arr[45]; let __v_55: G = __r_arr[46]; let __v_56: G = __r_arr[47]; let __v_57: G = __r_arr[48]; let __v_58: G = __r_arr[49]; let __v_59: G = __r_arr[50]; let __v_60: G = __r_arr[51]; let __v_61: G = __r_arr[52]; let __v_62: G = __r_arr[53]; let __v_63: G = __r_arr[54]; let __v_64: G = __r_arr[55]; let __v_65: G = __r_arr[56]; let __v_66: G = __r_arr[57]; let __v_67: G = __r_arr[58]; let __v_68: G = __r_arr[59]; let __v_69: G = __r_arr[60]; let __v_70: G = __r_arr[61]; let __v_71: G = __r_arr[62]; let __v_72: G = __r_arr[63]; match __v_2.as_canonical_u64() { 1023u64 => { let __r_arr: [G; OUT_320] = { let __args: [G; IN_320] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(320, &__args[..])?) { [G::ZERO; OUT_320] } else { let (__hash, __hit) = record.function_queries[320].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[320].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[320].bump_multiplicity(__i); } let __ret: [G; OUT_320] = unsafe { *(record.function_queries[320].output_at(__i).as_ptr() as *const [G; OUT_320]) }; __ret }, _ => { aiur_fn_320::(__args, __hash, record, io_buffer)? }, } } }; let __v_73: G = __r_arr[0]; let __v_74: G = __v_8 * __v_73; let __loaded: [G; 8] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let __ptr_u64 = __v_3.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 8 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 8] = __args[..8].try_into().unwrap(); __arr }; let __v_75: G = __loaded[0]; let __v_76: G = __loaded[1]; let __v_77: G = __loaded[2]; let __v_78: G = __loaded[3]; let __v_79: G = __loaded[4]; let __v_80: G = __loaded[5]; let __v_81: G = __loaded[6]; let __v_82: G = __loaded[7]; let __r_arr: [G; OUT_4] = { let __args: [G; IN_4] = [__v_75, __v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_82]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(4, &__args[..])?) { [G::ZERO; OUT_4] } else { let (__hash, __hit) = record.function_queries[4].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[4].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[4].bump_multiplicity(__i); } let __ret: [G; OUT_4] = unsafe { *(record.function_queries[4].output_at(__i).as_ptr() as *const [G; OUT_4]) }; __ret }, _ => { aiur_fn_4::(__args, __hash, record, io_buffer)? }, } } }; let __v_83: G = __r_arr[0]; let __v_84: G = __v_74 * __v_83; let __v_85: G = __v_84 + __v_7; let __v_86: G = G::from_u64(103); let __v_87: G = G::from_u64(230); let __v_88: G = G::from_u64(9); let __v_89: G = G::from_u64(106); let __v_90: G = G::from_u64(133); let __v_91: G = G::from_u64(174); let __v_92: G = G::from_u64(187); let __v_93: G = G::from_u64(114); let __v_94: G = G::from_u64(243); let __v_95: G = G::from_u64(110); let __v_96: G = G::from_u64(60); let __v_97: G = G::from_u64(58); let __v_98: G = G::from_u64(245); let __v_99: G = G::from_u64(79); let __v_100: G = G::from_u64(165); let __v_101: G = G::from_u64(127); let __v_102: G = G::from_u64(82); let __v_103: G = G::from_u64(14); let __v_104: G = G::from_u64(81); let __v_105: G = G::from_u64(140); let __v_106: G = G::from_u64(104); let __v_107: G = G::from_u64(5); let __v_108: G = G::from_u64(155); let __v_109: G = G::from_u64(171); let __v_110: G = G::from_u64(217); let __v_111: G = G::from_u64(131); let __v_112: G = G::from_u64(31); let __v_113: G = G::from_u64(25); let __v_114: G = G::from_u64(205); let __v_115: G = G::from_u64(224); let __v_116: G = G::from_u64(91); let __loaded: [G; 32] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 32 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 32] = __args[..32].try_into().unwrap(); __arr }; let __v_117: G = __loaded[0]; let __v_118: G = __loaded[1]; let __v_119: G = __loaded[2]; let __v_120: G = __loaded[3]; let __v_121: G = __loaded[4]; let __v_122: G = __loaded[5]; let __v_123: G = __loaded[6]; let __v_124: G = __loaded[7]; let __v_125: G = __loaded[8]; let __v_126: G = __loaded[9]; let __v_127: G = __loaded[10]; let __v_128: G = __loaded[11]; let __v_129: G = __loaded[12]; let __v_130: G = __loaded[13]; let __v_131: G = __loaded[14]; let __v_132: G = __loaded[15]; let __v_133: G = __loaded[16]; let __v_134: G = __loaded[17]; let __v_135: G = __loaded[18]; let __v_136: G = __loaded[19]; let __v_137: G = __loaded[20]; let __v_138: G = __loaded[21]; let __v_139: G = __loaded[22]; let __v_140: G = __loaded[23]; let __v_141: G = __loaded[24]; let __v_142: G = __loaded[25]; let __v_143: G = __loaded[26]; let __v_144: G = __loaded[27]; let __v_145: G = __loaded[28]; let __v_146: G = __loaded[29]; let __v_147: G = __loaded[30]; let __v_148: G = __loaded[31]; let __v_149: G = G::from_u64(64); let __v_150: G = G::from_u64(0); let __r_arr: [G; OUT_89] = { let __args: [G; IN_89] = [__v_150, __v_117, __v_118, __v_119, __v_120, __v_121, __v_122, __v_123, __v_124, __v_125, __v_126, __v_127, __v_128, __v_129, __v_130, __v_131, __v_132, __v_133, __v_134, __v_135, __v_136, __v_137, __v_138, __v_139, __v_140, __v_141, __v_142, __v_143, __v_144, __v_145, __v_146, __v_147, __v_148, __v_86, __v_87, __v_88, __v_89, __v_90, __v_91, __v_86, __v_92, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_99, __v_100, __v_75, __v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_82, __v_149, __v_150, __v_150, __v_150, __v_85, __v_150, __v_150, __v_150, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53, __v_54, __v_55, __v_56, __v_57, __v_58, __v_59, __v_60, __v_61, __v_62, __v_63, __v_64, __v_65, __v_66, __v_67, __v_68, __v_69, __v_70, __v_71, __v_72]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(89, &__args[..])?) { [G::ZERO; OUT_89] } else { let (__hash, __hit) = record.function_queries[89].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[89].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[89].bump_multiplicity(__i); } let __ret: [G; OUT_89] = unsafe { *(record.function_queries[89].output_at(__i).as_ptr() as *const [G; OUT_89]) }; __ret }, _ => { aiur_fn_89::(__args, __hash, record, io_buffer)? }, } } }; let __v_151: G = __r_arr[0]; let __v_152: G = __r_arr[1]; let __v_153: G = __r_arr[2]; let __v_154: G = __r_arr[3]; let __v_155: G = __r_arr[4]; let __v_156: G = __r_arr[5]; let __v_157: G = __r_arr[6]; let __v_158: G = __r_arr[7]; let __v_159: G = __r_arr[8]; let __v_160: G = __r_arr[9]; let __v_161: G = __r_arr[10]; let __v_162: G = __r_arr[11]; let __v_163: G = __r_arr[12]; let __v_164: G = __r_arr[13]; let __v_165: G = __r_arr[14]; let __v_166: G = __r_arr[15]; let __v_167: G = __r_arr[16]; let __v_168: G = __r_arr[17]; let __v_169: G = __r_arr[18]; let __v_170: G = __r_arr[19]; let __v_171: G = __r_arr[20]; let __v_172: G = __r_arr[21]; let __v_173: G = __r_arr[22]; let __v_174: G = __r_arr[23]; let __v_175: G = __r_arr[24]; let __v_176: G = __r_arr[25]; let __v_177: G = __r_arr[26]; let __v_178: G = __r_arr[27]; let __v_179: G = __r_arr[28]; let __v_180: G = __r_arr[29]; let __v_181: G = __r_arr[30]; let __v_182: G = __r_arr[31]; let __v_183: G = { let __values: [G; 34] = [__v_150, __v_5, __v_151, __v_152, __v_153, __v_154, __v_155, __v_156, __v_157, __v_158, __v_159, __v_160, __v_161, __v_162, __v_163, __v_164, __v_165, __v_166, __v_167, __v_168, __v_169, __v_170, __v_171, __v_172, __v_173, __v_174, __v_175, __v_176, __v_177, __v_178, __v_179, __v_180, __v_181, __v_182]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_184: G = G::from_u64(1); let __v_185: G = { let __values: [G; 3] = [__v_184, __v_184, __v_184]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_13] = { let __args: [G; IN_13] = [__v_75, __v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_82]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(13, &__args[..])?) { [G::ZERO; OUT_13] } else { let (__hash, __hit) = record.function_queries[13].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[13].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[13].bump_multiplicity(__i); } let __ret: [G; OUT_13] = unsafe { *(record.function_queries[13].output_at(__i).as_ptr() as *const [G; OUT_13]) }; __ret }, _ => { aiur_fn_13::(__args, __hash, record, io_buffer)? }, } } }; let __v_186: G = __r_arr[0]; let __v_187: G = __r_arr[1]; let __v_188: G = __r_arr[2]; let __v_189: G = __r_arr[3]; let __v_190: G = __r_arr[4]; let __v_191: G = __r_arr[5]; let __v_192: G = __r_arr[6]; let __v_193: G = __r_arr[7]; let __v_194: G = { let __values: [G; 8] = [__v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_195: G = { let __values: [G; 32] = [__v_86, __v_87, __v_88, __v_89, __v_90, __v_91, __v_86, __v_92, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_99, __v_100, __v_101, __v_102, __v_103, __v_104, __v_105, __v_106, __v_107, __v_108, __v_109, __v_110, __v_111, __v_112, __v_113, __v_114, __v_115, __v_116]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_0, __v_185, __v_150, __v_150, __v_194, __v_195, __v_183]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_196: G = __r_arr[0]; let __ret: [G; OUT_85] = [__v_196]; record.function_queries[85].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_320] = { let __args: [G; IN_320] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(320, &__args[..])?) { [G::ZERO; OUT_320] } else { let (__hash, __hit) = record.function_queries[320].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[320].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[320].bump_multiplicity(__i); } let __ret: [G; OUT_320] = unsafe { *(record.function_queries[320].output_at(__i).as_ptr() as *const [G; OUT_320]) }; __ret }, _ => { aiur_fn_320::(__args, __hash, record, io_buffer)? }, } } }; let __v_73: G = __r_arr[0]; let __v_74: G = __v_73 * __v_7; let __r_arr: [G; OUT_320] = { let __args: [G; IN_320] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(320, &__args[..])?) { [G::ZERO; OUT_320] } else { let (__hash, __hit) = record.function_queries[320].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[320].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[320].bump_multiplicity(__i); } let __ret: [G; OUT_320] = unsafe { *(record.function_queries[320].output_at(__i).as_ptr() as *const [G; OUT_320]) }; __ret }, _ => { aiur_fn_320::(__args, __hash, record, io_buffer)? }, } } }; let __v_75: G = __r_arr[0]; let __loaded: [G; 8] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let __ptr_u64 = __v_3.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 8 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 8] = __args[..8].try_into().unwrap(); __arr }; let __v_76: G = __loaded[0]; let __v_77: G = __loaded[1]; let __v_78: G = __loaded[2]; let __v_79: G = __loaded[3]; let __v_80: G = __loaded[4]; let __v_81: G = __loaded[5]; let __v_82: G = __loaded[6]; let __v_83: G = __loaded[7]; let __r_arr: [G; OUT_4] = { let __args: [G; IN_4] = [__v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_82, __v_83]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(4, &__args[..])?) { [G::ZERO; OUT_4] } else { let (__hash, __hit) = record.function_queries[4].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[4].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[4].bump_multiplicity(__i); } let __ret: [G; OUT_4] = unsafe { *(record.function_queries[4].output_at(__i).as_ptr() as *const [G; OUT_4]) }; __ret }, _ => { aiur_fn_4::(__args, __hash, record, io_buffer)? }, } } }; let __v_84: G = __r_arr[0]; let __v_85: G = __v_75 * __v_84; let __v_86: G = __v_85 * __v_8; let __v_87: G = G::from_u64(63); let __v_88: G = __v_2 - __v_87; let __v_89: G = G::from_bool(__v_88 == G::ZERO); let __v_90: G = __v_89 * __v_6; let __v_91: G = __v_74 + __v_86; let __v_92: G = __v_91 + __v_90; let __loaded: [G; 32] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 32 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 32] = __args[..32].try_into().unwrap(); __arr }; let __v_93: G = __loaded[0]; let __v_94: G = __loaded[1]; let __v_95: G = __loaded[2]; let __v_96: G = __loaded[3]; let __v_97: G = __loaded[4]; let __v_98: G = __loaded[5]; let __v_99: G = __loaded[6]; let __v_100: G = __loaded[7]; let __v_101: G = __loaded[8]; let __v_102: G = __loaded[9]; let __v_103: G = __loaded[10]; let __v_104: G = __loaded[11]; let __v_105: G = __loaded[12]; let __v_106: G = __loaded[13]; let __v_107: G = __loaded[14]; let __v_108: G = __loaded[15]; let __v_109: G = __loaded[16]; let __v_110: G = __loaded[17]; let __v_111: G = __loaded[18]; let __v_112: G = __loaded[19]; let __v_113: G = __loaded[20]; let __v_114: G = __loaded[21]; let __v_115: G = __loaded[22]; let __v_116: G = __loaded[23]; let __v_117: G = __loaded[24]; let __v_118: G = __loaded[25]; let __v_119: G = __loaded[26]; let __v_120: G = __loaded[27]; let __v_121: G = __loaded[28]; let __v_122: G = __loaded[29]; let __v_123: G = __loaded[30]; let __v_124: G = __loaded[31]; let __v_125: G = G::from_u64(64); let __v_126: G = G::from_u64(103); let __v_127: G = G::from_u64(230); let __v_128: G = G::from_u64(9); let __v_129: G = G::from_u64(106); let __v_130: G = G::from_u64(133); let __v_131: G = G::from_u64(174); let __v_132: G = G::from_u64(187); let __v_133: G = G::from_u64(114); let __v_134: G = G::from_u64(243); let __v_135: G = G::from_u64(110); let __v_136: G = G::from_u64(60); let __v_137: G = G::from_u64(58); let __v_138: G = G::from_u64(245); let __v_139: G = G::from_u64(79); let __v_140: G = G::from_u64(165); let __v_141: G = G::from_u64(0); let __r_arr: [G; OUT_89] = { let __args: [G; IN_89] = [__v_141, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_99, __v_100, __v_101, __v_102, __v_103, __v_104, __v_105, __v_106, __v_107, __v_108, __v_109, __v_110, __v_111, __v_112, __v_113, __v_114, __v_115, __v_116, __v_117, __v_118, __v_119, __v_120, __v_121, __v_122, __v_123, __v_124, __v_126, __v_127, __v_128, __v_129, __v_130, __v_131, __v_126, __v_132, __v_133, __v_134, __v_135, __v_136, __v_137, __v_138, __v_139, __v_140, __v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_82, __v_83, __v_125, __v_141, __v_141, __v_141, __v_92, __v_141, __v_141, __v_141, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53, __v_54, __v_55, __v_56, __v_57, __v_58, __v_59, __v_60, __v_61, __v_62, __v_63, __v_64, __v_65, __v_66, __v_67, __v_68, __v_69, __v_70, __v_71, __v_72]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(89, &__args[..])?) { [G::ZERO; OUT_89] } else { let (__hash, __hit) = record.function_queries[89].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[89].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[89].bump_multiplicity(__i); } let __ret: [G; OUT_89] = unsafe { *(record.function_queries[89].output_at(__i).as_ptr() as *const [G; OUT_89]) }; __ret }, _ => { aiur_fn_89::(__args, __hash, record, io_buffer)? }, } } }; let __v_142: G = __r_arr[0]; let __v_143: G = __r_arr[1]; let __v_144: G = __r_arr[2]; let __v_145: G = __r_arr[3]; let __v_146: G = __r_arr[4]; let __v_147: G = __r_arr[5]; let __v_148: G = __r_arr[6]; let __v_149: G = __r_arr[7]; let __v_150: G = __r_arr[8]; let __v_151: G = __r_arr[9]; let __v_152: G = __r_arr[10]; let __v_153: G = __r_arr[11]; let __v_154: G = __r_arr[12]; let __v_155: G = __r_arr[13]; let __v_156: G = __r_arr[14]; let __v_157: G = __r_arr[15]; let __v_158: G = __r_arr[16]; let __v_159: G = __r_arr[17]; let __v_160: G = __r_arr[18]; let __v_161: G = __r_arr[19]; let __v_162: G = __r_arr[20]; let __v_163: G = __r_arr[21]; let __v_164: G = __r_arr[22]; let __v_165: G = __r_arr[23]; let __v_166: G = __r_arr[24]; let __v_167: G = __r_arr[25]; let __v_168: G = __r_arr[26]; let __v_169: G = __r_arr[27]; let __v_170: G = __r_arr[28]; let __v_171: G = __r_arr[29]; let __v_172: G = __r_arr[30]; let __v_173: G = __r_arr[31]; let __v_174: G = G::from_u64(1); let __v_175: G = { let __values: [G; 3] = [__v_174, __v_174, __v_174]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_176: G = __v_2 + __v_174; let __v_177: G = { let __values: [G; 32] = [__v_142, __v_143, __v_144, __v_145, __v_146, __v_147, __v_148, __v_149, __v_150, __v_151, __v_152, __v_153, __v_154, __v_155, __v_156, __v_157, __v_158, __v_159, __v_160, __v_161, __v_162, __v_163, __v_164, __v_165, __v_166, __v_167, __v_168, __v_169, __v_170, __v_171, __v_172, __v_173]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_0, __v_175, __v_141, __v_176, __v_3, __v_177, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_178: G = __r_arr[0]; let __ret: [G; OUT_85] = [__v_178]; record.function_queries[85].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } +fn aiur_fn_85(inp: [G; IN_85], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_85], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = G::from_u64(1); let __v_7: G = G::from_u64(2); let __v_8: G = G::from_u64(8); let __r_arr: [G; OUT_82] = { let __args: [G; IN_82] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(82, &__args[..])?) { [G::ZERO; OUT_82] } else { let (__hash, __hit) = record.function_queries[82].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[82].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[82].bump_multiplicity(__i); } let __ret: [G; OUT_82] = unsafe { *(record.function_queries[82].output_at(__i).as_ptr() as *const [G; OUT_82]) }; __ret }, _ => { aiur_fn_82::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = __r_arr[1]; let __v_11: G = __r_arr[2]; let __v_12: G = __r_arr[3]; let __v_13: G = __r_arr[4]; let __v_14: G = __r_arr[5]; let __v_15: G = __r_arr[6]; let __v_16: G = __r_arr[7]; let __v_17: G = __r_arr[8]; let __v_18: G = __r_arr[9]; let __v_19: G = __r_arr[10]; let __v_20: G = __r_arr[11]; let __v_21: G = __r_arr[12]; let __v_22: G = __r_arr[13]; let __v_23: G = __r_arr[14]; let __v_24: G = __r_arr[15]; let __v_25: G = __r_arr[16]; let __v_26: G = __r_arr[17]; let __v_27: G = __r_arr[18]; let __v_28: G = __r_arr[19]; let __v_29: G = __r_arr[20]; let __v_30: G = __r_arr[21]; let __v_31: G = __r_arr[22]; let __v_32: G = __r_arr[23]; let __v_33: G = __r_arr[24]; let __v_34: G = __r_arr[25]; let __v_35: G = __r_arr[26]; let __v_36: G = __r_arr[27]; let __v_37: G = __r_arr[28]; let __v_38: G = __r_arr[29]; let __v_39: G = __r_arr[30]; let __v_40: G = __r_arr[31]; let __v_41: G = __r_arr[32]; let __v_42: G = __r_arr[33]; let __v_43: G = __r_arr[34]; let __v_44: G = __r_arr[35]; let __v_45: G = __r_arr[36]; let __v_46: G = __r_arr[37]; let __v_47: G = __r_arr[38]; let __v_48: G = __r_arr[39]; let __v_49: G = __r_arr[40]; let __v_50: G = __r_arr[41]; let __v_51: G = __r_arr[42]; let __v_52: G = __r_arr[43]; let __v_53: G = __r_arr[44]; let __v_54: G = __r_arr[45]; let __v_55: G = __r_arr[46]; let __v_56: G = __r_arr[47]; let __v_57: G = __r_arr[48]; let __v_58: G = __r_arr[49]; let __v_59: G = __r_arr[50]; let __v_60: G = __r_arr[51]; let __v_61: G = __r_arr[52]; let __v_62: G = __r_arr[53]; let __v_63: G = __r_arr[54]; let __v_64: G = __r_arr[55]; let __v_65: G = __r_arr[56]; let __v_66: G = __r_arr[57]; let __v_67: G = __r_arr[58]; let __v_68: G = __r_arr[59]; let __v_69: G = __r_arr[60]; let __v_70: G = __r_arr[61]; let __v_71: G = __r_arr[62]; let __v_72: G = __r_arr[63]; match __v_2.as_canonical_u64() { 1023u64 => { let __r_arr: [G; OUT_322] = { let __args: [G; IN_322] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(322, &__args[..])?) { [G::ZERO; OUT_322] } else { let (__hash, __hit) = record.function_queries[322].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[322].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[322].bump_multiplicity(__i); } let __ret: [G; OUT_322] = unsafe { *(record.function_queries[322].output_at(__i).as_ptr() as *const [G; OUT_322]) }; __ret }, _ => { aiur_fn_322::(__args, __hash, record, io_buffer)? }, } } }; let __v_73: G = __r_arr[0]; let __v_74: G = __v_8 * __v_73; let __loaded: [G; 8] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let __ptr_u64 = __v_3.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 8 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 8] = __args[..8].try_into().unwrap(); __arr }; let __v_75: G = __loaded[0]; let __v_76: G = __loaded[1]; let __v_77: G = __loaded[2]; let __v_78: G = __loaded[3]; let __v_79: G = __loaded[4]; let __v_80: G = __loaded[5]; let __v_81: G = __loaded[6]; let __v_82: G = __loaded[7]; let __r_arr: [G; OUT_4] = { let __args: [G; IN_4] = [__v_75, __v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_82]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(4, &__args[..])?) { [G::ZERO; OUT_4] } else { let (__hash, __hit) = record.function_queries[4].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[4].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[4].bump_multiplicity(__i); } let __ret: [G; OUT_4] = unsafe { *(record.function_queries[4].output_at(__i).as_ptr() as *const [G; OUT_4]) }; __ret }, _ => { aiur_fn_4::(__args, __hash, record, io_buffer)? }, } } }; let __v_83: G = __r_arr[0]; let __v_84: G = __v_74 * __v_83; let __v_85: G = __v_84 + __v_7; let __v_86: G = G::from_u64(103); let __v_87: G = G::from_u64(230); let __v_88: G = G::from_u64(9); let __v_89: G = G::from_u64(106); let __v_90: G = G::from_u64(133); let __v_91: G = G::from_u64(174); let __v_92: G = G::from_u64(187); let __v_93: G = G::from_u64(114); let __v_94: G = G::from_u64(243); let __v_95: G = G::from_u64(110); let __v_96: G = G::from_u64(60); let __v_97: G = G::from_u64(58); let __v_98: G = G::from_u64(245); let __v_99: G = G::from_u64(79); let __v_100: G = G::from_u64(165); let __v_101: G = G::from_u64(127); let __v_102: G = G::from_u64(82); let __v_103: G = G::from_u64(14); let __v_104: G = G::from_u64(81); let __v_105: G = G::from_u64(140); let __v_106: G = G::from_u64(104); let __v_107: G = G::from_u64(5); let __v_108: G = G::from_u64(155); let __v_109: G = G::from_u64(171); let __v_110: G = G::from_u64(217); let __v_111: G = G::from_u64(131); let __v_112: G = G::from_u64(31); let __v_113: G = G::from_u64(25); let __v_114: G = G::from_u64(205); let __v_115: G = G::from_u64(224); let __v_116: G = G::from_u64(91); let __loaded: [G; 32] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 32 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 32] = __args[..32].try_into().unwrap(); __arr }; let __v_117: G = __loaded[0]; let __v_118: G = __loaded[1]; let __v_119: G = __loaded[2]; let __v_120: G = __loaded[3]; let __v_121: G = __loaded[4]; let __v_122: G = __loaded[5]; let __v_123: G = __loaded[6]; let __v_124: G = __loaded[7]; let __v_125: G = __loaded[8]; let __v_126: G = __loaded[9]; let __v_127: G = __loaded[10]; let __v_128: G = __loaded[11]; let __v_129: G = __loaded[12]; let __v_130: G = __loaded[13]; let __v_131: G = __loaded[14]; let __v_132: G = __loaded[15]; let __v_133: G = __loaded[16]; let __v_134: G = __loaded[17]; let __v_135: G = __loaded[18]; let __v_136: G = __loaded[19]; let __v_137: G = __loaded[20]; let __v_138: G = __loaded[21]; let __v_139: G = __loaded[22]; let __v_140: G = __loaded[23]; let __v_141: G = __loaded[24]; let __v_142: G = __loaded[25]; let __v_143: G = __loaded[26]; let __v_144: G = __loaded[27]; let __v_145: G = __loaded[28]; let __v_146: G = __loaded[29]; let __v_147: G = __loaded[30]; let __v_148: G = __loaded[31]; let __v_149: G = G::from_u64(64); let __v_150: G = G::from_u64(0); let __r_arr: [G; OUT_89] = { let __args: [G; IN_89] = [__v_150, __v_117, __v_118, __v_119, __v_120, __v_121, __v_122, __v_123, __v_124, __v_125, __v_126, __v_127, __v_128, __v_129, __v_130, __v_131, __v_132, __v_133, __v_134, __v_135, __v_136, __v_137, __v_138, __v_139, __v_140, __v_141, __v_142, __v_143, __v_144, __v_145, __v_146, __v_147, __v_148, __v_86, __v_87, __v_88, __v_89, __v_90, __v_91, __v_86, __v_92, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_99, __v_100, __v_75, __v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_82, __v_149, __v_150, __v_150, __v_150, __v_85, __v_150, __v_150, __v_150, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53, __v_54, __v_55, __v_56, __v_57, __v_58, __v_59, __v_60, __v_61, __v_62, __v_63, __v_64, __v_65, __v_66, __v_67, __v_68, __v_69, __v_70, __v_71, __v_72]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(89, &__args[..])?) { [G::ZERO; OUT_89] } else { let (__hash, __hit) = record.function_queries[89].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[89].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[89].bump_multiplicity(__i); } let __ret: [G; OUT_89] = unsafe { *(record.function_queries[89].output_at(__i).as_ptr() as *const [G; OUT_89]) }; __ret }, _ => { aiur_fn_89::(__args, __hash, record, io_buffer)? }, } } }; let __v_151: G = __r_arr[0]; let __v_152: G = __r_arr[1]; let __v_153: G = __r_arr[2]; let __v_154: G = __r_arr[3]; let __v_155: G = __r_arr[4]; let __v_156: G = __r_arr[5]; let __v_157: G = __r_arr[6]; let __v_158: G = __r_arr[7]; let __v_159: G = __r_arr[8]; let __v_160: G = __r_arr[9]; let __v_161: G = __r_arr[10]; let __v_162: G = __r_arr[11]; let __v_163: G = __r_arr[12]; let __v_164: G = __r_arr[13]; let __v_165: G = __r_arr[14]; let __v_166: G = __r_arr[15]; let __v_167: G = __r_arr[16]; let __v_168: G = __r_arr[17]; let __v_169: G = __r_arr[18]; let __v_170: G = __r_arr[19]; let __v_171: G = __r_arr[20]; let __v_172: G = __r_arr[21]; let __v_173: G = __r_arr[22]; let __v_174: G = __r_arr[23]; let __v_175: G = __r_arr[24]; let __v_176: G = __r_arr[25]; let __v_177: G = __r_arr[26]; let __v_178: G = __r_arr[27]; let __v_179: G = __r_arr[28]; let __v_180: G = __r_arr[29]; let __v_181: G = __r_arr[30]; let __v_182: G = __r_arr[31]; let __v_183: G = { let __values: [G; 34] = [__v_150, __v_5, __v_151, __v_152, __v_153, __v_154, __v_155, __v_156, __v_157, __v_158, __v_159, __v_160, __v_161, __v_162, __v_163, __v_164, __v_165, __v_166, __v_167, __v_168, __v_169, __v_170, __v_171, __v_172, __v_173, __v_174, __v_175, __v_176, __v_177, __v_178, __v_179, __v_180, __v_181, __v_182]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_184: G = G::from_u64(1); let __v_185: G = { let __values: [G; 3] = [__v_184, __v_184, __v_184]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_13] = { let __args: [G; IN_13] = [__v_75, __v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_82]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(13, &__args[..])?) { [G::ZERO; OUT_13] } else { let (__hash, __hit) = record.function_queries[13].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[13].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[13].bump_multiplicity(__i); } let __ret: [G; OUT_13] = unsafe { *(record.function_queries[13].output_at(__i).as_ptr() as *const [G; OUT_13]) }; __ret }, _ => { aiur_fn_13::(__args, __hash, record, io_buffer)? }, } } }; let __v_186: G = __r_arr[0]; let __v_187: G = __r_arr[1]; let __v_188: G = __r_arr[2]; let __v_189: G = __r_arr[3]; let __v_190: G = __r_arr[4]; let __v_191: G = __r_arr[5]; let __v_192: G = __r_arr[6]; let __v_193: G = __r_arr[7]; let __v_194: G = { let __values: [G; 8] = [__v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_195: G = { let __values: [G; 32] = [__v_86, __v_87, __v_88, __v_89, __v_90, __v_91, __v_86, __v_92, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_99, __v_100, __v_101, __v_102, __v_103, __v_104, __v_105, __v_106, __v_107, __v_108, __v_109, __v_110, __v_111, __v_112, __v_113, __v_114, __v_115, __v_116]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_0, __v_185, __v_150, __v_150, __v_194, __v_195, __v_183]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_196: G = __r_arr[0]; let __ret: [G; OUT_85] = [__v_196]; record.function_queries[85].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_322] = { let __args: [G; IN_322] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(322, &__args[..])?) { [G::ZERO; OUT_322] } else { let (__hash, __hit) = record.function_queries[322].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[322].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[322].bump_multiplicity(__i); } let __ret: [G; OUT_322] = unsafe { *(record.function_queries[322].output_at(__i).as_ptr() as *const [G; OUT_322]) }; __ret }, _ => { aiur_fn_322::(__args, __hash, record, io_buffer)? }, } } }; let __v_73: G = __r_arr[0]; let __v_74: G = __v_73 * __v_7; let __r_arr: [G; OUT_322] = { let __args: [G; IN_322] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(322, &__args[..])?) { [G::ZERO; OUT_322] } else { let (__hash, __hit) = record.function_queries[322].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[322].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[322].bump_multiplicity(__i); } let __ret: [G; OUT_322] = unsafe { *(record.function_queries[322].output_at(__i).as_ptr() as *const [G; OUT_322]) }; __ret }, _ => { aiur_fn_322::(__args, __hash, record, io_buffer)? }, } } }; let __v_75: G = __r_arr[0]; let __loaded: [G; 8] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let __ptr_u64 = __v_3.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 8 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 8] = __args[..8].try_into().unwrap(); __arr }; let __v_76: G = __loaded[0]; let __v_77: G = __loaded[1]; let __v_78: G = __loaded[2]; let __v_79: G = __loaded[3]; let __v_80: G = __loaded[4]; let __v_81: G = __loaded[5]; let __v_82: G = __loaded[6]; let __v_83: G = __loaded[7]; let __r_arr: [G; OUT_4] = { let __args: [G; IN_4] = [__v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_82, __v_83]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(4, &__args[..])?) { [G::ZERO; OUT_4] } else { let (__hash, __hit) = record.function_queries[4].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[4].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[4].bump_multiplicity(__i); } let __ret: [G; OUT_4] = unsafe { *(record.function_queries[4].output_at(__i).as_ptr() as *const [G; OUT_4]) }; __ret }, _ => { aiur_fn_4::(__args, __hash, record, io_buffer)? }, } } }; let __v_84: G = __r_arr[0]; let __v_85: G = __v_75 * __v_84; let __v_86: G = __v_85 * __v_8; let __v_87: G = G::from_u64(63); let __v_88: G = __v_2 - __v_87; let __v_89: G = G::from_bool(__v_88 == G::ZERO); let __v_90: G = __v_89 * __v_6; let __v_91: G = __v_74 + __v_86; let __v_92: G = __v_91 + __v_90; let __loaded: [G; 32] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 32 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 32] = __args[..32].try_into().unwrap(); __arr }; let __v_93: G = __loaded[0]; let __v_94: G = __loaded[1]; let __v_95: G = __loaded[2]; let __v_96: G = __loaded[3]; let __v_97: G = __loaded[4]; let __v_98: G = __loaded[5]; let __v_99: G = __loaded[6]; let __v_100: G = __loaded[7]; let __v_101: G = __loaded[8]; let __v_102: G = __loaded[9]; let __v_103: G = __loaded[10]; let __v_104: G = __loaded[11]; let __v_105: G = __loaded[12]; let __v_106: G = __loaded[13]; let __v_107: G = __loaded[14]; let __v_108: G = __loaded[15]; let __v_109: G = __loaded[16]; let __v_110: G = __loaded[17]; let __v_111: G = __loaded[18]; let __v_112: G = __loaded[19]; let __v_113: G = __loaded[20]; let __v_114: G = __loaded[21]; let __v_115: G = __loaded[22]; let __v_116: G = __loaded[23]; let __v_117: G = __loaded[24]; let __v_118: G = __loaded[25]; let __v_119: G = __loaded[26]; let __v_120: G = __loaded[27]; let __v_121: G = __loaded[28]; let __v_122: G = __loaded[29]; let __v_123: G = __loaded[30]; let __v_124: G = __loaded[31]; let __v_125: G = G::from_u64(64); let __v_126: G = G::from_u64(103); let __v_127: G = G::from_u64(230); let __v_128: G = G::from_u64(9); let __v_129: G = G::from_u64(106); let __v_130: G = G::from_u64(133); let __v_131: G = G::from_u64(174); let __v_132: G = G::from_u64(187); let __v_133: G = G::from_u64(114); let __v_134: G = G::from_u64(243); let __v_135: G = G::from_u64(110); let __v_136: G = G::from_u64(60); let __v_137: G = G::from_u64(58); let __v_138: G = G::from_u64(245); let __v_139: G = G::from_u64(79); let __v_140: G = G::from_u64(165); let __v_141: G = G::from_u64(0); let __r_arr: [G; OUT_89] = { let __args: [G; IN_89] = [__v_141, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_99, __v_100, __v_101, __v_102, __v_103, __v_104, __v_105, __v_106, __v_107, __v_108, __v_109, __v_110, __v_111, __v_112, __v_113, __v_114, __v_115, __v_116, __v_117, __v_118, __v_119, __v_120, __v_121, __v_122, __v_123, __v_124, __v_126, __v_127, __v_128, __v_129, __v_130, __v_131, __v_126, __v_132, __v_133, __v_134, __v_135, __v_136, __v_137, __v_138, __v_139, __v_140, __v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_82, __v_83, __v_125, __v_141, __v_141, __v_141, __v_92, __v_141, __v_141, __v_141, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53, __v_54, __v_55, __v_56, __v_57, __v_58, __v_59, __v_60, __v_61, __v_62, __v_63, __v_64, __v_65, __v_66, __v_67, __v_68, __v_69, __v_70, __v_71, __v_72]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(89, &__args[..])?) { [G::ZERO; OUT_89] } else { let (__hash, __hit) = record.function_queries[89].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[89].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[89].bump_multiplicity(__i); } let __ret: [G; OUT_89] = unsafe { *(record.function_queries[89].output_at(__i).as_ptr() as *const [G; OUT_89]) }; __ret }, _ => { aiur_fn_89::(__args, __hash, record, io_buffer)? }, } } }; let __v_142: G = __r_arr[0]; let __v_143: G = __r_arr[1]; let __v_144: G = __r_arr[2]; let __v_145: G = __r_arr[3]; let __v_146: G = __r_arr[4]; let __v_147: G = __r_arr[5]; let __v_148: G = __r_arr[6]; let __v_149: G = __r_arr[7]; let __v_150: G = __r_arr[8]; let __v_151: G = __r_arr[9]; let __v_152: G = __r_arr[10]; let __v_153: G = __r_arr[11]; let __v_154: G = __r_arr[12]; let __v_155: G = __r_arr[13]; let __v_156: G = __r_arr[14]; let __v_157: G = __r_arr[15]; let __v_158: G = __r_arr[16]; let __v_159: G = __r_arr[17]; let __v_160: G = __r_arr[18]; let __v_161: G = __r_arr[19]; let __v_162: G = __r_arr[20]; let __v_163: G = __r_arr[21]; let __v_164: G = __r_arr[22]; let __v_165: G = __r_arr[23]; let __v_166: G = __r_arr[24]; let __v_167: G = __r_arr[25]; let __v_168: G = __r_arr[26]; let __v_169: G = __r_arr[27]; let __v_170: G = __r_arr[28]; let __v_171: G = __r_arr[29]; let __v_172: G = __r_arr[30]; let __v_173: G = __r_arr[31]; let __v_174: G = G::from_u64(1); let __v_175: G = { let __values: [G; 3] = [__v_174, __v_174, __v_174]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_176: G = __v_2 + __v_174; let __v_177: G = { let __values: [G; 32] = [__v_142, __v_143, __v_144, __v_145, __v_146, __v_147, __v_148, __v_149, __v_150, __v_151, __v_152, __v_153, __v_154, __v_155, __v_156, __v_157, __v_158, __v_159, __v_160, __v_161, __v_162, __v_163, __v_164, __v_165, __v_166, __v_167, __v_168, __v_169, __v_170, __v_171, __v_172, __v_173]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_0, __v_175, __v_141, __v_176, __v_3, __v_177, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_178: G = __r_arr[0]; let __ret: [G; OUT_85] = [__v_178]; record.function_queries[85].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } const INPUT_SIZE_86: usize = 24; const IN_86: usize = 24; const OUT_86: usize = 16; @@ -452,7 +452,7 @@ fn aiur_fn_109(inp: [G; IN_109], input_hash: u64, rec const INPUT_SIZE_110: usize = 1; const IN_110: usize = 1; const OUT_110: usize = 13; -fn aiur_fn_110(inp: [G; IN_110], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_110], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __v_3: G = __r_arr[2]; let __v_4: G = __r_arr[3]; let __v_5: G = __r_arr[4]; let __v_6: G = __r_arr[5]; let __v_7: G = __r_arr[6]; let __v_8: G = __r_arr[7]; let __v_9: G = __r_arr[8]; let __v_10: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = __r_arr[1]; let __v_13: G = __r_arr[2]; let __v_14: G = __r_arr[3]; let __v_15: G = __r_arr[4]; let __v_16: G = __r_arr[5]; let __v_17: G = __r_arr[6]; let __v_18: G = __r_arr[7]; let __v_19: G = __r_arr[8]; let __v_20: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __v_23: G = __r_arr[2]; let __v_24: G = __r_arr[3]; let __v_25: G = __r_arr[4]; let __v_26: G = __r_arr[5]; let __v_27: G = __r_arr[6]; let __v_28: G = __r_arr[7]; let __v_29: G = __r_arr[8]; let __v_30: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_30]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_31: G = __r_arr[0]; let __v_32: G = __r_arr[1]; let __v_33: G = __r_arr[2]; let __v_34: G = __r_arr[3]; let __v_35: G = __r_arr[4]; let __v_36: G = __r_arr[5]; let __v_37: G = __r_arr[6]; let __v_38: G = __r_arr[7]; let __v_39: G = __r_arr[8]; let __v_40: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_41: G = __r_arr[0]; let __v_42: G = __r_arr[1]; let __v_43: G = __r_arr[2]; let __v_44: G = __r_arr[3]; let __v_45: G = __r_arr[4]; let __v_46: G = __r_arr[5]; let __v_47: G = __r_arr[6]; let __v_48: G = __r_arr[7]; let __v_49: G = __r_arr[8]; let __v_50: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_50]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_51: G = __r_arr[0]; let __v_52: G = __r_arr[1]; let __v_53: G = __r_arr[2]; let __v_54: G = __r_arr[3]; let __v_55: G = __r_arr[4]; let __v_56: G = __r_arr[5]; let __v_57: G = __r_arr[6]; let __v_58: G = __r_arr[7]; let __v_59: G = __r_arr[8]; let __v_60: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_60]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_61: G = __r_arr[0]; let __v_62: G = __r_arr[1]; let __v_63: G = __r_arr[2]; let __v_64: G = __r_arr[3]; let __v_65: G = __r_arr[4]; let __v_66: G = __r_arr[5]; let __v_67: G = __r_arr[6]; let __v_68: G = __r_arr[7]; let __v_69: G = __r_arr[8]; let __v_70: G = __r_arr[9]; let __v_71: G = G::from_u64(0); let __v_72: G = G::from_u64(1); let __v_73: G = { let __values: [G; 10] = [__v_72, __v_72, __v_72, __v_72, __v_72, __v_72, __v_72, __v_72, __v_72, __v_72]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_74: G = { let __values: [G; 10] = [__v_71, __v_62, __v_63, __v_64, __v_65, __v_66, __v_67, __v_68, __v_69, __v_73]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_75: G = { let __values: [G; 10] = [__v_71, __v_52, __v_53, __v_54, __v_55, __v_56, __v_57, __v_58, __v_59, __v_74]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_76: G = { let __values: [G; 10] = [__v_71, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_75]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_77: G = { let __values: [G; 10] = [__v_71, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_76]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_78: G = { let __values: [G; 10] = [__v_71, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_77]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_79: G = { let __values: [G; 10] = [__v_71, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_78]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_80: G = { let __values: [G; 10] = [__v_71, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_79]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_70]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_81: G = __r_arr[0]; let __v_82: G = __r_arr[1]; let __v_83: G = __r_arr[2]; let __v_84: G = __r_arr[3]; let __v_85: G = __r_arr[4]; let __v_86: G = __r_arr[5]; let __v_87: G = __r_arr[6]; let __v_88: G = __r_arr[7]; let __v_89: G = __r_arr[8]; let __v_90: G = __r_arr[9]; let __r_arr: [G; OUT_106] = { let __args: [G; IN_106] = [__v_90, __v_81]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(106, &__args[..])?) { [G::ZERO; OUT_106] } else { let (__hash, __hit) = record.function_queries[106].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[106].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[106].bump_multiplicity(__i); } let __ret: [G; OUT_106] = unsafe { *(record.function_queries[106].output_at(__i).as_ptr() as *const [G; OUT_106]) }; __ret }, _ => { aiur_fn_106::(__args, __hash, record, io_buffer)? }, } } }; let __v_91: G = __r_arr[0]; let __v_92: G = __r_arr[1]; let __v_93: G = __r_arr[2]; let __r_arr: [G; OUT_107] = { let __args: [G; IN_107] = [__v_93]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(107, &__args[..])?) { [G::ZERO; OUT_107] } else { let (__hash, __hit) = record.function_queries[107].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[107].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[107].bump_multiplicity(__i); } let __ret: [G; OUT_107] = unsafe { *(record.function_queries[107].output_at(__i).as_ptr() as *const [G; OUT_107]) }; __ret }, _ => { aiur_fn_107::(__args, __hash, record, io_buffer)? }, } } }; let __v_94: G = __r_arr[0]; let __v_95: G = __r_arr[1]; let __v_96: G = __r_arr[2]; let __r_arr: [G; OUT_108] = { let __args: [G; IN_108] = [__v_96, __v_81]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(108, &__args[..])?) { [G::ZERO; OUT_108] } else { let (__hash, __hit) = record.function_queries[108].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[108].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[108].bump_multiplicity(__i); } let __ret: [G; OUT_108] = unsafe { *(record.function_queries[108].output_at(__i).as_ptr() as *const [G; OUT_108]) }; __ret }, _ => { aiur_fn_108::(__args, __hash, record, io_buffer)? }, } } }; let __v_97: G = __r_arr[0]; let __v_98: G = __r_arr[1]; let __v_99: G = { let __values: [G; 10] = [__v_71, __v_82, __v_83, __v_84, __v_85, __v_86, __v_87, __v_88, __v_89, __v_92]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_322] = { let __args: [G; IN_322] = [__v_80, __v_99]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(322, &__args[..])?) { [G::ZERO; OUT_322] } else { let (__hash, __hit) = record.function_queries[322].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[322].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[322].bump_multiplicity(__i); } let __ret: [G; OUT_322] = unsafe { *(record.function_queries[322].output_at(__i).as_ptr() as *const [G; OUT_322]) }; __ret }, _ => { aiur_fn_322::(__args, __hash, record, io_buffer)? }, } } }; let __v_100: G = __r_arr[0]; let __ret: [G; OUT_110] = [__v_1, __v_11, __v_21, __v_31, __v_41, __v_51, __v_61, __v_100, __v_91, __v_94, __v_95, __v_97, __v_98]; record.function_queries[110].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +fn aiur_fn_110(inp: [G; IN_110], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_110], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __v_3: G = __r_arr[2]; let __v_4: G = __r_arr[3]; let __v_5: G = __r_arr[4]; let __v_6: G = __r_arr[5]; let __v_7: G = __r_arr[6]; let __v_8: G = __r_arr[7]; let __v_9: G = __r_arr[8]; let __v_10: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = __r_arr[1]; let __v_13: G = __r_arr[2]; let __v_14: G = __r_arr[3]; let __v_15: G = __r_arr[4]; let __v_16: G = __r_arr[5]; let __v_17: G = __r_arr[6]; let __v_18: G = __r_arr[7]; let __v_19: G = __r_arr[8]; let __v_20: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __v_23: G = __r_arr[2]; let __v_24: G = __r_arr[3]; let __v_25: G = __r_arr[4]; let __v_26: G = __r_arr[5]; let __v_27: G = __r_arr[6]; let __v_28: G = __r_arr[7]; let __v_29: G = __r_arr[8]; let __v_30: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_30]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_31: G = __r_arr[0]; let __v_32: G = __r_arr[1]; let __v_33: G = __r_arr[2]; let __v_34: G = __r_arr[3]; let __v_35: G = __r_arr[4]; let __v_36: G = __r_arr[5]; let __v_37: G = __r_arr[6]; let __v_38: G = __r_arr[7]; let __v_39: G = __r_arr[8]; let __v_40: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_41: G = __r_arr[0]; let __v_42: G = __r_arr[1]; let __v_43: G = __r_arr[2]; let __v_44: G = __r_arr[3]; let __v_45: G = __r_arr[4]; let __v_46: G = __r_arr[5]; let __v_47: G = __r_arr[6]; let __v_48: G = __r_arr[7]; let __v_49: G = __r_arr[8]; let __v_50: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_50]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_51: G = __r_arr[0]; let __v_52: G = __r_arr[1]; let __v_53: G = __r_arr[2]; let __v_54: G = __r_arr[3]; let __v_55: G = __r_arr[4]; let __v_56: G = __r_arr[5]; let __v_57: G = __r_arr[6]; let __v_58: G = __r_arr[7]; let __v_59: G = __r_arr[8]; let __v_60: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_60]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_61: G = __r_arr[0]; let __v_62: G = __r_arr[1]; let __v_63: G = __r_arr[2]; let __v_64: G = __r_arr[3]; let __v_65: G = __r_arr[4]; let __v_66: G = __r_arr[5]; let __v_67: G = __r_arr[6]; let __v_68: G = __r_arr[7]; let __v_69: G = __r_arr[8]; let __v_70: G = __r_arr[9]; let __v_71: G = G::from_u64(0); let __v_72: G = G::from_u64(1); let __v_73: G = { let __values: [G; 10] = [__v_72, __v_72, __v_72, __v_72, __v_72, __v_72, __v_72, __v_72, __v_72, __v_72]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_74: G = { let __values: [G; 10] = [__v_71, __v_62, __v_63, __v_64, __v_65, __v_66, __v_67, __v_68, __v_69, __v_73]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_75: G = { let __values: [G; 10] = [__v_71, __v_52, __v_53, __v_54, __v_55, __v_56, __v_57, __v_58, __v_59, __v_74]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_76: G = { let __values: [G; 10] = [__v_71, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_75]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_77: G = { let __values: [G; 10] = [__v_71, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_76]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_78: G = { let __values: [G; 10] = [__v_71, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_77]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_79: G = { let __values: [G; 10] = [__v_71, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_78]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_80: G = { let __values: [G; 10] = [__v_71, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_79]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_70]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_81: G = __r_arr[0]; let __v_82: G = __r_arr[1]; let __v_83: G = __r_arr[2]; let __v_84: G = __r_arr[3]; let __v_85: G = __r_arr[4]; let __v_86: G = __r_arr[5]; let __v_87: G = __r_arr[6]; let __v_88: G = __r_arr[7]; let __v_89: G = __r_arr[8]; let __v_90: G = __r_arr[9]; let __r_arr: [G; OUT_106] = { let __args: [G; IN_106] = [__v_90, __v_81]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(106, &__args[..])?) { [G::ZERO; OUT_106] } else { let (__hash, __hit) = record.function_queries[106].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[106].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[106].bump_multiplicity(__i); } let __ret: [G; OUT_106] = unsafe { *(record.function_queries[106].output_at(__i).as_ptr() as *const [G; OUT_106]) }; __ret }, _ => { aiur_fn_106::(__args, __hash, record, io_buffer)? }, } } }; let __v_91: G = __r_arr[0]; let __v_92: G = __r_arr[1]; let __v_93: G = __r_arr[2]; let __r_arr: [G; OUT_107] = { let __args: [G; IN_107] = [__v_93]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(107, &__args[..])?) { [G::ZERO; OUT_107] } else { let (__hash, __hit) = record.function_queries[107].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[107].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[107].bump_multiplicity(__i); } let __ret: [G; OUT_107] = unsafe { *(record.function_queries[107].output_at(__i).as_ptr() as *const [G; OUT_107]) }; __ret }, _ => { aiur_fn_107::(__args, __hash, record, io_buffer)? }, } } }; let __v_94: G = __r_arr[0]; let __v_95: G = __r_arr[1]; let __v_96: G = __r_arr[2]; let __r_arr: [G; OUT_108] = { let __args: [G; IN_108] = [__v_96, __v_81]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(108, &__args[..])?) { [G::ZERO; OUT_108] } else { let (__hash, __hit) = record.function_queries[108].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[108].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[108].bump_multiplicity(__i); } let __ret: [G; OUT_108] = unsafe { *(record.function_queries[108].output_at(__i).as_ptr() as *const [G; OUT_108]) }; __ret }, _ => { aiur_fn_108::(__args, __hash, record, io_buffer)? }, } } }; let __v_97: G = __r_arr[0]; let __v_98: G = __r_arr[1]; let __v_99: G = { let __values: [G; 10] = [__v_71, __v_82, __v_83, __v_84, __v_85, __v_86, __v_87, __v_88, __v_89, __v_92]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_324] = { let __args: [G; IN_324] = [__v_80, __v_99]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(324, &__args[..])?) { [G::ZERO; OUT_324] } else { let (__hash, __hit) = record.function_queries[324].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[324].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[324].bump_multiplicity(__i); } let __ret: [G; OUT_324] = unsafe { *(record.function_queries[324].output_at(__i).as_ptr() as *const [G; OUT_324]) }; __ret }, _ => { aiur_fn_324::(__args, __hash, record, io_buffer)? }, } } }; let __v_100: G = __r_arr[0]; let __ret: [G; OUT_110] = [__v_1, __v_11, __v_21, __v_31, __v_41, __v_51, __v_61, __v_100, __v_91, __v_94, __v_95, __v_97, __v_98]; record.function_queries[110].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } const INPUT_SIZE_111: usize = 5; const IN_111: usize = 5; const OUT_111: usize = 1; @@ -524,7 +524,7 @@ fn aiur_fn_127(inp: [G; IN_127], input_hash: u64, rec const INPUT_SIZE_128: usize = 4; const IN_128: usize = 4; const OUT_128: usize = 1; -fn aiur_fn_128(inp: [G; IN_128], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_128], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_4: G = __loaded[0]; let __v_5: G = __loaded[1]; let __v_6: G = __loaded[2]; match __v_4.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = G::from_u64(0); if (__v_7 != __v_8) { return Err(ExecError::AssertEqMismatch { lhs: __v_7.as_canonical_u64(), rhs: __v_8.as_canonical_u64(), msg: None }); } let __v_9: G = G::from_u64(1); let __v_10: G = { let __values: [G; 6] = [__v_9, __v_9, __v_9, __v_9, __v_9, __v_9]; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 6)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_128] = [__v_10]; record.function_queries[128].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_1.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_7: G = __loaded[0]; let __v_8: G = __loaded[1]; let __v_9: G = __loaded[2]; match __v_7.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_122] = { let __args: [G; IN_122] = [__v_8, __v_2, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(122, &__args[..])?) { [G::ZERO; OUT_122] } else { let (__hash, __hit) = record.function_queries[122].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[122].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[122].bump_multiplicity(__i); } let __ret: [G; OUT_122] = unsafe { *(record.function_queries[122].output_at(__i).as_ptr() as *const [G; OUT_122]) }; __ret }, _ => { aiur_fn_122::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = G::from_u64(0); let __r_arr: [G; OUT_124] = { let __args: [G; IN_124] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(124, &__args[..])?) { [G::ZERO; OUT_124] } else { let (__hash, __hit) = record.function_queries[124].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[124].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[124].bump_multiplicity(__i); } let __ret: [G; OUT_124] = unsafe { *(record.function_queries[124].output_at(__i).as_ptr() as *const [G; OUT_124]) }; __ret }, _ => { aiur_fn_124::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __r_arr: [G; OUT_128] = { let __args: [G; IN_128] = [__v_6, __v_9, __v_2, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(128, &__args[..])?) { [G::ZERO; OUT_128] } else { let (__hash, __hit) = record.function_queries[128].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[128].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[128].bump_multiplicity(__i); } let __ret: [G; OUT_128] = unsafe { *(record.function_queries[128].output_at(__i).as_ptr() as *const [G; OUT_128]) }; __ret }, _ => { aiur_fn_128::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = { let __values: [G; 6] = [__v_11, __v_12, __v_5, __v_8, __v_10, __v_13]; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 6)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_128] = [__v_14]; record.function_queries[128].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_7.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_4.as_canonical_u64())); }, } }) } +fn aiur_fn_128(inp: [G; IN_128], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_128], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_4: G = __loaded[0]; let __v_5: G = __loaded[1]; let __v_6: G = __loaded[2]; match __v_4.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = G::from_u64(0); if (__v_7 != __v_8) { return Err(ExecError::AssertEqMismatch { lhs: __v_7.as_canonical_u64(), rhs: __v_8.as_canonical_u64(), msg: None }); } let __v_9: G = G::from_u64(1); let __v_10: G = { let __values: [G; 6] = [__v_9, __v_9, __v_9, __v_9, __v_9, __v_9]; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 6)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_128] = [__v_10]; record.function_queries[128].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_1.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_7: G = __loaded[0]; let __v_8: G = __loaded[1]; let __v_9: G = __loaded[2]; match __v_7.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_122] = { let __args: [G; IN_122] = [__v_8, __v_2, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(122, &__args[..])?) { [G::ZERO; OUT_122] } else { let (__hash, __hit) = record.function_queries[122].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[122].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[122].bump_multiplicity(__i); } let __ret: [G; OUT_122] = unsafe { *(record.function_queries[122].output_at(__i).as_ptr() as *const [G; OUT_122]) }; __ret }, _ => { aiur_fn_122::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = G::from_u64(0); let __r_arr: [G; OUT_124] = { let __args: [G; IN_124] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(124, &__args[..])?) { [G::ZERO; OUT_124] } else { let (__hash, __hit) = record.function_queries[124].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[124].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[124].bump_multiplicity(__i); } let __ret: [G; OUT_124] = unsafe { *(record.function_queries[124].output_at(__i).as_ptr() as *const [G; OUT_124]) }; __ret }, _ => { aiur_fn_124::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __r_arr: [G; OUT_128] = { let __args: [G; IN_128] = [__v_6, __v_9, __v_2, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(128, &__args[..])?) { [G::ZERO; OUT_128] } else { let (__hash, __hit) = record.function_queries[128].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[128].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[128].bump_multiplicity(__i); } let __ret: [G; OUT_128] = unsafe { *(record.function_queries[128].output_at(__i).as_ptr() as *const [G; OUT_128]) }; __ret }, _ => { aiur_fn_128::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = { let __values: [G; 6] = [__v_11, __v_12, __v_5, __v_8, __v_10, __v_13]; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 6)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_128] = [__v_14]; record.function_queries[128].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_7.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_4.as_canonical_u64())); }, } }) } const INPUT_SIZE_129: usize = 4; const IN_129: usize = 4; const OUT_129: usize = 2; @@ -536,7 +536,7 @@ fn aiur_fn_130(inp: [G; IN_130], input_hash: u64, rec const INPUT_SIZE_131: usize = 6; const IN_131: usize = 6; const OUT_131: usize = 1; -fn aiur_fn_131(inp: [G; IN_131], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_131], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = __v_6 - __v_7; let __v_9: G = G::from_bool(__v_8 == G::ZERO); let __v_10: G = G::from_u64(1); if (__v_9 != __v_10) { return Err(ExecError::AssertEqMismatch { lhs: __v_9.as_canonical_u64(), rhs: __v_10.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_128] = { let __args: [G; IN_128] = [__v_3, __v_1, __v_2, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(128, &__args[..])?) { [G::ZERO; OUT_128] } else { let (__hash, __hit) = record.function_queries[128].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[128].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[128].bump_multiplicity(__i); } let __ret: [G; OUT_128] = unsafe { *(record.function_queries[128].output_at(__i).as_ptr() as *const [G; OUT_128]) }; __ret }, _ => { aiur_fn_128::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __r_arr: [G; OUT_127] = { let __args: [G; IN_127] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(127, &__args[..])?) { [G::ZERO; OUT_127] } else { let (__hash, __hit) = record.function_queries[127].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[127].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[127].bump_multiplicity(__i); } let __ret: [G; OUT_127] = unsafe { *(record.function_queries[127].output_at(__i).as_ptr() as *const [G; OUT_127]) }; __ret }, _ => { aiur_fn_127::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __r_arr: [G; OUT_130] = { let __args: [G; IN_130] = [__v_12, __v_4, __v_2, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(130, &__args[..])?) { [G::ZERO; OUT_130] } else { let (__hash, __hit) = record.function_queries[130].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[130].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[130].bump_multiplicity(__i); } let __ret: [G; OUT_130] = unsafe { *(record.function_queries[130].output_at(__i).as_ptr() as *const [G; OUT_130]) }; __ret }, _ => { aiur_fn_130::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = G::from_u64(0); if (__v_15 != __v_16) { return Err(ExecError::AssertEqMismatch { lhs: __v_15.as_canonical_u64(), rhs: __v_16.as_canonical_u64(), msg: None }); } let __loaded: [G; 6] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let __ptr_u64 = __v_13.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 6 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 6] = __args[..6].try_into().unwrap(); __arr }; let __v_17: G = __loaded[0]; let __v_18: G = __loaded[1]; let __v_19: G = __loaded[2]; let __v_20: G = __loaded[3]; let __v_21: G = __loaded[4]; let __v_22: G = __loaded[5]; match __v_17.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __v_24: G = G::from_u64(0); if (__v_23 != __v_24) { return Err(ExecError::AssertEqMismatch { lhs: __v_23.as_canonical_u64(), rhs: __v_24.as_canonical_u64(), msg: None }); } match __v_18.as_canonical_u64() { _ => { let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_0, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __v_26: G = __v_25 - __v_21; let __v_27: G = G::from_bool(__v_26 == G::ZERO); let __ret: [G; OUT_131] = [__v_27]; record.function_queries[131].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_17.as_canonical_u64())); }, } }) } +fn aiur_fn_131(inp: [G; IN_131], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_131], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = __v_6 - __v_7; let __v_9: G = G::from_bool(__v_8 == G::ZERO); let __v_10: G = G::from_u64(1); if (__v_9 != __v_10) { return Err(ExecError::AssertEqMismatch { lhs: __v_9.as_canonical_u64(), rhs: __v_10.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_128] = { let __args: [G; IN_128] = [__v_3, __v_1, __v_2, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(128, &__args[..])?) { [G::ZERO; OUT_128] } else { let (__hash, __hit) = record.function_queries[128].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[128].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[128].bump_multiplicity(__i); } let __ret: [G; OUT_128] = unsafe { *(record.function_queries[128].output_at(__i).as_ptr() as *const [G; OUT_128]) }; __ret }, _ => { aiur_fn_128::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __r_arr: [G; OUT_127] = { let __args: [G; IN_127] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(127, &__args[..])?) { [G::ZERO; OUT_127] } else { let (__hash, __hit) = record.function_queries[127].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[127].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[127].bump_multiplicity(__i); } let __ret: [G; OUT_127] = unsafe { *(record.function_queries[127].output_at(__i).as_ptr() as *const [G; OUT_127]) }; __ret }, _ => { aiur_fn_127::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __r_arr: [G; OUT_130] = { let __args: [G; IN_130] = [__v_12, __v_4, __v_2, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(130, &__args[..])?) { [G::ZERO; OUT_130] } else { let (__hash, __hit) = record.function_queries[130].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[130].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[130].bump_multiplicity(__i); } let __ret: [G; OUT_130] = unsafe { *(record.function_queries[130].output_at(__i).as_ptr() as *const [G; OUT_130]) }; __ret }, _ => { aiur_fn_130::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = G::from_u64(0); if (__v_15 != __v_16) { return Err(ExecError::AssertEqMismatch { lhs: __v_15.as_canonical_u64(), rhs: __v_16.as_canonical_u64(), msg: None }); } let __loaded: [G; 6] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let __ptr_u64 = __v_13.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 6 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 6] = __args[..6].try_into().unwrap(); __arr }; let __v_17: G = __loaded[0]; let __v_18: G = __loaded[1]; let __v_19: G = __loaded[2]; let __v_20: G = __loaded[3]; let __v_21: G = __loaded[4]; let __v_22: G = __loaded[5]; match __v_17.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_317] = { let __args: [G; IN_317] = [__v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(317, &__args[..])?) { [G::ZERO; OUT_317] } else { let (__hash, __hit) = record.function_queries[317].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[317].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[317].bump_multiplicity(__i); } let __ret: [G; OUT_317] = unsafe { *(record.function_queries[317].output_at(__i).as_ptr() as *const [G; OUT_317]) }; __ret }, _ => { aiur_fn_317::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __v_24: G = G::from_u64(0); if (__v_23 != __v_24) { return Err(ExecError::AssertEqMismatch { lhs: __v_23.as_canonical_u64(), rhs: __v_24.as_canonical_u64(), msg: None }); } match __v_18.as_canonical_u64() { _ => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_0, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __v_26: G = __v_25 - __v_21; let __v_27: G = G::from_bool(__v_26 == G::ZERO); let __ret: [G; OUT_131] = [__v_27]; record.function_queries[131].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_17.as_canonical_u64())); }, } }) } const INPUT_SIZE_132: usize = 2; const IN_132: usize = 2; const OUT_132: usize = 1; @@ -588,7 +588,7 @@ fn aiur_fn_143(inp: [G; IN_143], input_hash: u64, rec const INPUT_SIZE_144: usize = 5; const IN_144: usize = 5; const OUT_144: usize = 1; -fn aiur_fn_144(inp: [G; IN_144], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_144], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; match __v_2.as_canonical_u64() { 0u64 => { let __v_5: G = G::from_u64(0); let __ret: [G; OUT_144] = [__v_5]; record.function_queries[144].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_0, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __v_5 + __v_1; let __v_7: G = __v_6 - __v_4; let __v_8: G = G::from_bool(__v_7 == G::ZERO); match __v_8.as_canonical_u64() { 1u64 => { let __v_9: G = G::from_u64(1); let __ret: [G; OUT_144] = [__v_9]; record.function_queries[144].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_9: G = G::from_u64(1); let __v_10: G = __v_2 - __v_9; let __v_11: G = __v_3 + __v_9; let __r_arr: [G; OUT_144] = { let __args: [G; IN_144] = [__v_0, __v_1, __v_10, __v_11, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(144, &__args[..])?) { [G::ZERO; OUT_144] } else { let (__hash, __hit) = record.function_queries[144].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[144].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[144].bump_multiplicity(__i); } let __ret: [G; OUT_144] = unsafe { *(record.function_queries[144].output_at(__i).as_ptr() as *const [G; OUT_144]) }; __ret }, _ => { aiur_fn_144::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __ret: [G; OUT_144] = [__v_12]; record.function_queries[144].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, } }) } +fn aiur_fn_144(inp: [G; IN_144], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_144], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; match __v_2.as_canonical_u64() { 0u64 => { let __v_5: G = G::from_u64(0); let __ret: [G; OUT_144] = [__v_5]; record.function_queries[144].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_0, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __v_5 + __v_1; let __v_7: G = __v_6 - __v_4; let __v_8: G = G::from_bool(__v_7 == G::ZERO); match __v_8.as_canonical_u64() { 1u64 => { let __v_9: G = G::from_u64(1); let __ret: [G; OUT_144] = [__v_9]; record.function_queries[144].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_9: G = G::from_u64(1); let __v_10: G = __v_2 - __v_9; let __v_11: G = __v_3 + __v_9; let __r_arr: [G; OUT_144] = { let __args: [G; IN_144] = [__v_0, __v_1, __v_10, __v_11, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(144, &__args[..])?) { [G::ZERO; OUT_144] } else { let (__hash, __hit) = record.function_queries[144].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[144].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[144].bump_multiplicity(__i); } let __ret: [G; OUT_144] = unsafe { *(record.function_queries[144].output_at(__i).as_ptr() as *const [G; OUT_144]) }; __ret }, _ => { aiur_fn_144::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __ret: [G; OUT_144] = [__v_12]; record.function_queries[144].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, } }) } const INPUT_SIZE_145: usize = 4; const IN_145: usize = 4; const OUT_145: usize = 1; @@ -596,7 +596,7 @@ fn aiur_fn_145(inp: [G; IN_145], input_hash: u64, rec const INPUT_SIZE_146: usize = 8; const IN_146: usize = 8; const OUT_146: usize = 1; -fn aiur_fn_146(inp: [G; IN_146], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_146], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __loaded: [G; 7] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(4).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(7))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 7 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 7] = __args[..7].try_into().unwrap(); __arr }; let __v_8: G = __loaded[0]; let __v_9: G = __loaded[1]; let __v_10: G = __loaded[2]; let __v_11: G = __loaded[3]; let __v_12: G = __loaded[4]; let __v_13: G = __loaded[5]; let __v_14: G = __loaded[6]; match __v_8.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_317] = { let __args: [G; IN_317] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(317, &__args[..])?) { [G::ZERO; OUT_317] } else { let (__hash, __hit) = record.function_queries[317].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[317].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[317].bump_multiplicity(__i); } let __ret: [G; OUT_317] = unsafe { *(record.function_queries[317].output_at(__i).as_ptr() as *const [G; OUT_317]) }; __ret }, _ => { aiur_fn_317::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = __v_15 - __v_16; let __v_18: G = G::from_bool(__v_17 == G::ZERO); let __v_19: G = G::from_u64(1); if (__v_18 != __v_19) { return Err(ExecError::AssertEqMismatch { lhs: __v_18.as_canonical_u64(), rhs: __v_19.as_canonical_u64(), msg: None }); } let __v_20: G = { let __values: [G; 7] = [__v_19, __v_19, __v_19, __v_19, __v_19, __v_19, __v_19]; let __mq = record.memory_queries.get_index_mut(4).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(7))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 7)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_146] = [__v_20]; record.function_queries[146].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_9.as_canonical_u64() { _ => { let __v_15: G = __v_9 - __v_1; let __v_16: G = G::from_bool(__v_15 == G::ZERO); match __v_16.as_canonical_u64() { 1u64 => { let __v_17: G = G::from_u64(0); let __r_arr: [G; OUT_136] = { let __args: [G; IN_136] = [__v_2, __v_3, __v_6, __v_7, __v_17, __v_17, __v_10, __v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(136, &__args[..])?) { [G::ZERO; OUT_136] } else { let (__hash, __hit) = record.function_queries[136].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[136].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[136].bump_multiplicity(__i); } let __ret: [G; OUT_136] = unsafe { *(record.function_queries[136].output_at(__i).as_ptr() as *const [G; OUT_136]) }; __ret }, _ => { aiur_fn_136::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __v_19: G = __r_arr[1]; let __v_20: G = __r_arr[2]; let __v_21: G = __r_arr[3]; let __v_22: G = __v_4 * __v_18; let __v_23: G = G::from_u64(7); let __v_24: G = __v_5 * __v_19; let __v_25: G = __v_23 * __v_24; let __v_26: G = __v_22 + __v_25; let __v_27: G = __v_4 * __v_19; let __v_28: G = __v_5 * __v_18; let __v_29: G = __v_27 + __v_28; let __v_30: G = __v_12 + __v_26; let __v_31: G = __v_13 + __v_29; let __v_32: G = { let __values: [G; 7] = [__v_17, __v_9, __v_20, __v_21, __v_30, __v_31, __v_14]; let __mq = record.memory_queries.get_index_mut(4).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(7))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 7)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_146] = [__v_32]; record.function_queries[146].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_17: G = G::from_u64(0); let __r_arr: [G; OUT_146] = { let __args: [G; IN_146] = [__v_14, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(146, &__args[..])?) { [G::ZERO; OUT_146] } else { let (__hash, __hit) = record.function_queries[146].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[146].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[146].bump_multiplicity(__i); } let __ret: [G; OUT_146] = unsafe { *(record.function_queries[146].output_at(__i).as_ptr() as *const [G; OUT_146]) }; __ret }, _ => { aiur_fn_146::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __v_19: G = { let __values: [G; 7] = [__v_17, __v_9, __v_10, __v_11, __v_12, __v_13, __v_18]; let __mq = record.memory_queries.get_index_mut(4).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(7))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 7)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_146] = [__v_19]; record.function_queries[146].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, } }, _ => { return Err(ExecError::MatchNoCase(__v_8.as_canonical_u64())); }, } }) } +fn aiur_fn_146(inp: [G; IN_146], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_146], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __loaded: [G; 7] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(4).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(7))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 7 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 7] = __args[..7].try_into().unwrap(); __arr }; let __v_8: G = __loaded[0]; let __v_9: G = __loaded[1]; let __v_10: G = __loaded[2]; let __v_11: G = __loaded[3]; let __v_12: G = __loaded[4]; let __v_13: G = __loaded[5]; let __v_14: G = __loaded[6]; match __v_8.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_319] = { let __args: [G; IN_319] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(319, &__args[..])?) { [G::ZERO; OUT_319] } else { let (__hash, __hit) = record.function_queries[319].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[319].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[319].bump_multiplicity(__i); } let __ret: [G; OUT_319] = unsafe { *(record.function_queries[319].output_at(__i).as_ptr() as *const [G; OUT_319]) }; __ret }, _ => { aiur_fn_319::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = __v_15 - __v_16; let __v_18: G = G::from_bool(__v_17 == G::ZERO); let __v_19: G = G::from_u64(1); if (__v_18 != __v_19) { return Err(ExecError::AssertEqMismatch { lhs: __v_18.as_canonical_u64(), rhs: __v_19.as_canonical_u64(), msg: None }); } let __v_20: G = { let __values: [G; 7] = [__v_19, __v_19, __v_19, __v_19, __v_19, __v_19, __v_19]; let __mq = record.memory_queries.get_index_mut(4).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(7))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 7)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_146] = [__v_20]; record.function_queries[146].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_9.as_canonical_u64() { _ => { let __v_15: G = __v_9 - __v_1; let __v_16: G = G::from_bool(__v_15 == G::ZERO); match __v_16.as_canonical_u64() { 1u64 => { let __v_17: G = G::from_u64(0); let __r_arr: [G; OUT_136] = { let __args: [G; IN_136] = [__v_2, __v_3, __v_6, __v_7, __v_17, __v_17, __v_10, __v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(136, &__args[..])?) { [G::ZERO; OUT_136] } else { let (__hash, __hit) = record.function_queries[136].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[136].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[136].bump_multiplicity(__i); } let __ret: [G; OUT_136] = unsafe { *(record.function_queries[136].output_at(__i).as_ptr() as *const [G; OUT_136]) }; __ret }, _ => { aiur_fn_136::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __v_19: G = __r_arr[1]; let __v_20: G = __r_arr[2]; let __v_21: G = __r_arr[3]; let __v_22: G = __v_4 * __v_18; let __v_23: G = G::from_u64(7); let __v_24: G = __v_5 * __v_19; let __v_25: G = __v_23 * __v_24; let __v_26: G = __v_22 + __v_25; let __v_27: G = __v_4 * __v_19; let __v_28: G = __v_5 * __v_18; let __v_29: G = __v_27 + __v_28; let __v_30: G = __v_12 + __v_26; let __v_31: G = __v_13 + __v_29; let __v_32: G = { let __values: [G; 7] = [__v_17, __v_9, __v_20, __v_21, __v_30, __v_31, __v_14]; let __mq = record.memory_queries.get_index_mut(4).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(7))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 7)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_146] = [__v_32]; record.function_queries[146].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_17: G = G::from_u64(0); let __r_arr: [G; OUT_146] = { let __args: [G; IN_146] = [__v_14, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(146, &__args[..])?) { [G::ZERO; OUT_146] } else { let (__hash, __hit) = record.function_queries[146].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[146].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[146].bump_multiplicity(__i); } let __ret: [G; OUT_146] = unsafe { *(record.function_queries[146].output_at(__i).as_ptr() as *const [G; OUT_146]) }; __ret }, _ => { aiur_fn_146::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __v_19: G = { let __values: [G; 7] = [__v_17, __v_9, __v_10, __v_11, __v_12, __v_13, __v_18]; let __mq = record.memory_queries.get_index_mut(4).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(7))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 7)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_146] = [__v_19]; record.function_queries[146].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, } }, _ => { return Err(ExecError::MatchNoCase(__v_8.as_canonical_u64())); }, } }) } const INPUT_SIZE_147: usize = 2; const IN_147: usize = 2; const OUT_147: usize = 1; @@ -608,31 +608,31 @@ fn aiur_fn_148(inp: [G; IN_148], input_hash: u64, rec const INPUT_SIZE_149: usize = 10; const IN_149: usize = 10; const OUT_149: usize = 1; -fn aiur_fn_149(inp: [G; IN_149], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_149], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = __v_3 - __v_1; let __r_arr: [G; OUT_324] = { let __args: [G; IN_324] = [__v_2, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(324, &__args[..])?) { [G::ZERO; OUT_324] } else { let (__hash, __hit) = record.function_queries[324].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[324].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[324].bump_multiplicity(__i); } let __ret: [G; OUT_324] = unsafe { *(record.function_queries[324].output_at(__i).as_ptr() as *const [G; OUT_324]) }; __ret }, _ => { aiur_fn_324::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_u64(7); let __r_arr: [G; OUT_189] = { let __args: [G; IN_189] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(189, &__args[..])?) { [G::ZERO; OUT_189] } else { let (__hash, __hit) = record.function_queries[189].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[189].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[189].bump_multiplicity(__i); } let __ret: [G; OUT_189] = unsafe { *(record.function_queries[189].output_at(__i).as_ptr() as *const [G; OUT_189]) }; __ret }, _ => { aiur_fn_189::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = G::from_u64(1); let __v_15: G = { let __values: [G; 3] = [__v_14, __v_14, __v_14]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_132] = { let __args: [G; IN_132] = [__v_11, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(132, &__args[..])?) { [G::ZERO; OUT_132] } else { let (__hash, __hit) = record.function_queries[132].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[132].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[132].bump_multiplicity(__i); } let __ret: [G; OUT_132] = unsafe { *(record.function_queries[132].output_at(__i).as_ptr() as *const [G; OUT_132]) }; __ret }, _ => { aiur_fn_132::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __r_arr: [G; OUT_133] = { let __args: [G; IN_133] = [__v_13, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(133, &__args[..])?) { [G::ZERO; OUT_133] } else { let (__hash, __hit) = record.function_queries[133].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[133].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[133].bump_multiplicity(__i); } let __ret: [G; OUT_133] = unsafe { *(record.function_queries[133].output_at(__i).as_ptr() as *const [G; OUT_133]) }; __ret }, _ => { aiur_fn_133::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = __v_12 * __v_17; let __v_19: G = G::from_u64(0); let __v_20: G = __v_4 - __v_18; let __v_21: G = __v_5 - __v_19; let __v_22: G = __v_20 * __v_20; let __v_23: G = __v_21 * __v_21; let __v_24: G = __v_12 * __v_23; let __v_25: G = __v_22 - __v_24; let __v_26: G = aiur::execute::g_inverse_value(__v_25); let __v_27: G = __v_25 * __v_26; let __v_28: G = __v_27 - __v_14; let __v_29: G = __v_25 * __v_28; if (__v_29 != __v_19) { return Err(ExecError::AssertEqMismatch { lhs: __v_29.as_canonical_u64(), rhs: __v_19.as_canonical_u64(), msg: None }); } let __v_30: G = __v_26 * __v_28; if (__v_30 != __v_19) { return Err(ExecError::AssertEqMismatch { lhs: __v_30.as_canonical_u64(), rhs: __v_19.as_canonical_u64(), msg: None }); } let __v_31: G = __v_20 * __v_26; let __v_32: G = __v_19 - __v_21; let __v_33: G = __v_32 * __v_26; let __r_arr: [G; OUT_146] = { let __args: [G; IN_146] = [__v_0, __v_1, __v_6, __v_7, __v_31, __v_33, __v_8, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(146, &__args[..])?) { [G::ZERO; OUT_146] } else { let (__hash, __hit) = record.function_queries[146].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[146].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[146].bump_multiplicity(__i); } let __ret: [G; OUT_146] = unsafe { *(record.function_queries[146].output_at(__i).as_ptr() as *const [G; OUT_146]) }; __ret }, _ => { aiur_fn_146::(__args, __hash, record, io_buffer)? }, } } }; let __v_34: G = __r_arr[0]; let __ret: [G; OUT_149] = [__v_34]; record.function_queries[149].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +fn aiur_fn_149(inp: [G; IN_149], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_149], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = __v_3 - __v_1; let __r_arr: [G; OUT_326] = { let __args: [G; IN_326] = [__v_2, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(326, &__args[..])?) { [G::ZERO; OUT_326] } else { let (__hash, __hit) = record.function_queries[326].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[326].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[326].bump_multiplicity(__i); } let __ret: [G; OUT_326] = unsafe { *(record.function_queries[326].output_at(__i).as_ptr() as *const [G; OUT_326]) }; __ret }, _ => { aiur_fn_326::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_u64(7); let __r_arr: [G; OUT_189] = { let __args: [G; IN_189] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(189, &__args[..])?) { [G::ZERO; OUT_189] } else { let (__hash, __hit) = record.function_queries[189].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[189].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[189].bump_multiplicity(__i); } let __ret: [G; OUT_189] = unsafe { *(record.function_queries[189].output_at(__i).as_ptr() as *const [G; OUT_189]) }; __ret }, _ => { aiur_fn_189::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = G::from_u64(1); let __v_15: G = { let __values: [G; 3] = [__v_14, __v_14, __v_14]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_132] = { let __args: [G; IN_132] = [__v_11, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(132, &__args[..])?) { [G::ZERO; OUT_132] } else { let (__hash, __hit) = record.function_queries[132].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[132].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[132].bump_multiplicity(__i); } let __ret: [G; OUT_132] = unsafe { *(record.function_queries[132].output_at(__i).as_ptr() as *const [G; OUT_132]) }; __ret }, _ => { aiur_fn_132::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __r_arr: [G; OUT_133] = { let __args: [G; IN_133] = [__v_13, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(133, &__args[..])?) { [G::ZERO; OUT_133] } else { let (__hash, __hit) = record.function_queries[133].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[133].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[133].bump_multiplicity(__i); } let __ret: [G; OUT_133] = unsafe { *(record.function_queries[133].output_at(__i).as_ptr() as *const [G; OUT_133]) }; __ret }, _ => { aiur_fn_133::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = __v_12 * __v_17; let __v_19: G = G::from_u64(0); let __v_20: G = __v_4 - __v_18; let __v_21: G = __v_5 - __v_19; let __v_22: G = __v_20 * __v_20; let __v_23: G = __v_21 * __v_21; let __v_24: G = __v_12 * __v_23; let __v_25: G = __v_22 - __v_24; let __v_26: G = aiur::execute::g_inverse_value(__v_25); let __v_27: G = __v_25 * __v_26; let __v_28: G = __v_27 - __v_14; let __v_29: G = __v_25 * __v_28; if (__v_29 != __v_19) { return Err(ExecError::AssertEqMismatch { lhs: __v_29.as_canonical_u64(), rhs: __v_19.as_canonical_u64(), msg: None }); } let __v_30: G = __v_26 * __v_28; if (__v_30 != __v_19) { return Err(ExecError::AssertEqMismatch { lhs: __v_30.as_canonical_u64(), rhs: __v_19.as_canonical_u64(), msg: None }); } let __v_31: G = __v_20 * __v_26; let __v_32: G = __v_19 - __v_21; let __v_33: G = __v_32 * __v_26; let __r_arr: [G; OUT_146] = { let __args: [G; IN_146] = [__v_0, __v_1, __v_6, __v_7, __v_31, __v_33, __v_8, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(146, &__args[..])?) { [G::ZERO; OUT_146] } else { let (__hash, __hit) = record.function_queries[146].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[146].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[146].bump_multiplicity(__i); } let __ret: [G; OUT_146] = unsafe { *(record.function_queries[146].output_at(__i).as_ptr() as *const [G; OUT_146]) }; __ret }, _ => { aiur_fn_146::(__args, __hash, record, io_buffer)? }, } } }; let __v_34: G = __r_arr[0]; let __ret: [G; OUT_149] = [__v_34]; record.function_queries[149].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } const INPUT_SIZE_150: usize = 11; const IN_150: usize = 11; const OUT_150: usize = 1; -fn aiur_fn_150(inp: [G; IN_150], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_150], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = G::from_u64(0); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_8, __v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __v_13: G = G::from_u64(1); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_8, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_14: G = __r_arr[0]; let __r_arr: [G; OUT_189] = { let __args: [G; IN_189] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(189, &__args[..])?) { [G::ZERO; OUT_189] } else { let (__hash, __hit) = record.function_queries[189].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[189].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[189].bump_multiplicity(__i); } let __ret: [G; OUT_189] = unsafe { *(record.function_queries[189].output_at(__i).as_ptr() as *const [G; OUT_189]) }; __ret }, _ => { aiur_fn_189::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __v_5 * __v_15; let __v_17: G = G::from_u64(7); let __v_18: G = __v_6 * __v_11; let __v_19: G = __v_17 * __v_18; let __v_20: G = __v_16 + __v_19; let __v_21: G = __v_5 * __v_11; let __v_22: G = __v_6 * __v_15; let __v_23: G = __v_21 + __v_22; let __r_arr: [G; OUT_149] = { let __args: [G; IN_149] = [__v_0, __v_3, __v_1, __v_2, __v_5, __v_6, __v_7, __v_12, __v_9, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(149, &__args[..])?) { [G::ZERO; OUT_149] } else { let (__hash, __hit) = record.function_queries[149].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[149].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[149].bump_multiplicity(__i); } let __ret: [G; OUT_149] = unsafe { *(record.function_queries[149].output_at(__i).as_ptr() as *const [G; OUT_149]) }; __ret }, _ => { aiur_fn_149::(__args, __hash, record, io_buffer)? }, } } }; let __v_24: G = __r_arr[0]; let __r_arr: [G; OUT_149] = { let __args: [G; IN_149] = [__v_24, __v_3, __v_1, __v_2, __v_20, __v_23, __v_7, __v_14, __v_9, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(149, &__args[..])?) { [G::ZERO; OUT_149] } else { let (__hash, __hit) = record.function_queries[149].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[149].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[149].bump_multiplicity(__i); } let __ret: [G; OUT_149] = unsafe { *(record.function_queries[149].output_at(__i).as_ptr() as *const [G; OUT_149]) }; __ret }, _ => { aiur_fn_149::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __ret: [G; OUT_150] = [__v_25]; record.function_queries[150].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +fn aiur_fn_150(inp: [G; IN_150], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_150], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = G::from_u64(0); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_8, __v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __v_13: G = G::from_u64(1); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_8, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_14: G = __r_arr[0]; let __r_arr: [G; OUT_189] = { let __args: [G; IN_189] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(189, &__args[..])?) { [G::ZERO; OUT_189] } else { let (__hash, __hit) = record.function_queries[189].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[189].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[189].bump_multiplicity(__i); } let __ret: [G; OUT_189] = unsafe { *(record.function_queries[189].output_at(__i).as_ptr() as *const [G; OUT_189]) }; __ret }, _ => { aiur_fn_189::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __v_5 * __v_15; let __v_17: G = G::from_u64(7); let __v_18: G = __v_6 * __v_11; let __v_19: G = __v_17 * __v_18; let __v_20: G = __v_16 + __v_19; let __v_21: G = __v_5 * __v_11; let __v_22: G = __v_6 * __v_15; let __v_23: G = __v_21 + __v_22; let __r_arr: [G; OUT_149] = { let __args: [G; IN_149] = [__v_0, __v_3, __v_1, __v_2, __v_5, __v_6, __v_7, __v_12, __v_9, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(149, &__args[..])?) { [G::ZERO; OUT_149] } else { let (__hash, __hit) = record.function_queries[149].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[149].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[149].bump_multiplicity(__i); } let __ret: [G; OUT_149] = unsafe { *(record.function_queries[149].output_at(__i).as_ptr() as *const [G; OUT_149]) }; __ret }, _ => { aiur_fn_149::(__args, __hash, record, io_buffer)? }, } } }; let __v_24: G = __r_arr[0]; let __r_arr: [G; OUT_149] = { let __args: [G; IN_149] = [__v_24, __v_3, __v_1, __v_2, __v_20, __v_23, __v_7, __v_14, __v_9, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(149, &__args[..])?) { [G::ZERO; OUT_149] } else { let (__hash, __hit) = record.function_queries[149].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[149].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[149].bump_multiplicity(__i); } let __ret: [G; OUT_149] = unsafe { *(record.function_queries[149].output_at(__i).as_ptr() as *const [G; OUT_149]) }; __ret }, _ => { aiur_fn_149::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __ret: [G; OUT_150] = [__v_25]; record.function_queries[150].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } const INPUT_SIZE_151: usize = 13; const IN_151: usize = 13; const OUT_151: usize = 1; -fn aiur_fn_151(inp: [G; IN_151], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_151], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; match __v_5.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_151] = [__v_0]; record.function_queries[151].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_6, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __v_13 + __v_3; let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_9, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_10, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __r_arr: [G; OUT_150] = { let __args: [G; IN_150] = [__v_0, __v_1, __v_2, __v_14, __v_13, __v_7, __v_8, __v_15, __v_16, __v_11, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(150, &__args[..])?) { [G::ZERO; OUT_150] } else { let (__hash, __hit) = record.function_queries[150].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[150].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[150].bump_multiplicity(__i); } let __ret: [G; OUT_150] = unsafe { *(record.function_queries[150].output_at(__i).as_ptr() as *const [G; OUT_150]) }; __ret }, _ => { aiur_fn_150::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = G::from_u64(1); let __v_19: G = __v_4 + __v_18; let __v_20: G = __v_5 - __v_18; let __r_arr: [G; OUT_151] = { let __args: [G; IN_151] = [__v_17, __v_1, __v_2, __v_3, __v_19, __v_20, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(151, &__args[..])?) { [G::ZERO; OUT_151] } else { let (__hash, __hit) = record.function_queries[151].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[151].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[151].bump_multiplicity(__i); } let __ret: [G; OUT_151] = unsafe { *(record.function_queries[151].output_at(__i).as_ptr() as *const [G; OUT_151]) }; __ret }, _ => { aiur_fn_151::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __ret: [G; OUT_151] = [__v_21]; record.function_queries[151].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } +fn aiur_fn_151(inp: [G; IN_151], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_151], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; match __v_5.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_151] = [__v_0]; record.function_queries[151].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_6, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __v_13 + __v_3; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_9, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_10, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __r_arr: [G; OUT_150] = { let __args: [G; IN_150] = [__v_0, __v_1, __v_2, __v_14, __v_13, __v_7, __v_8, __v_15, __v_16, __v_11, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(150, &__args[..])?) { [G::ZERO; OUT_150] } else { let (__hash, __hit) = record.function_queries[150].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[150].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[150].bump_multiplicity(__i); } let __ret: [G; OUT_150] = unsafe { *(record.function_queries[150].output_at(__i).as_ptr() as *const [G; OUT_150]) }; __ret }, _ => { aiur_fn_150::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = G::from_u64(1); let __v_19: G = __v_4 + __v_18; let __v_20: G = __v_5 - __v_18; let __r_arr: [G; OUT_151] = { let __args: [G; IN_151] = [__v_17, __v_1, __v_2, __v_3, __v_19, __v_20, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(151, &__args[..])?) { [G::ZERO; OUT_151] } else { let (__hash, __hit) = record.function_queries[151].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[151].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[151].bump_multiplicity(__i); } let __ret: [G; OUT_151] = unsafe { *(record.function_queries[151].output_at(__i).as_ptr() as *const [G; OUT_151]) }; __ret }, _ => { aiur_fn_151::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __ret: [G; OUT_151] = [__v_21]; record.function_queries[151].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } const INPUT_SIZE_152: usize = 13; const IN_152: usize = 13; const OUT_152: usize = 1; -fn aiur_fn_152(inp: [G; IN_152], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_152], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; match __v_5.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_152] = [__v_0]; record.function_queries[152].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_6, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __v_13 + __v_3; let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_9, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_10, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = G::from_u64(0); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_16, __v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __r_arr: [G; OUT_149] = { let __args: [G; IN_149] = [__v_0, __v_14, __v_1, __v_2, __v_7, __v_8, __v_15, __v_18, __v_11, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(149, &__args[..])?) { [G::ZERO; OUT_149] } else { let (__hash, __hit) = record.function_queries[149].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[149].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[149].bump_multiplicity(__i); } let __ret: [G; OUT_149] = unsafe { *(record.function_queries[149].output_at(__i).as_ptr() as *const [G; OUT_149]) }; __ret }, _ => { aiur_fn_149::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = G::from_u64(1); let __v_21: G = __v_4 + __v_20; let __v_22: G = __v_5 - __v_20; let __r_arr: [G; OUT_152] = { let __args: [G; IN_152] = [__v_19, __v_1, __v_2, __v_3, __v_21, __v_22, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(152, &__args[..])?) { [G::ZERO; OUT_152] } else { let (__hash, __hit) = record.function_queries[152].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[152].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[152].bump_multiplicity(__i); } let __ret: [G; OUT_152] = unsafe { *(record.function_queries[152].output_at(__i).as_ptr() as *const [G; OUT_152]) }; __ret }, _ => { aiur_fn_152::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __ret: [G; OUT_152] = [__v_23]; record.function_queries[152].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } +fn aiur_fn_152(inp: [G; IN_152], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_152], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; match __v_5.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_152] = [__v_0]; record.function_queries[152].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_6, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __v_13 + __v_3; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_9, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_10, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = G::from_u64(0); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_16, __v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __r_arr: [G; OUT_149] = { let __args: [G; IN_149] = [__v_0, __v_14, __v_1, __v_2, __v_7, __v_8, __v_15, __v_18, __v_11, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(149, &__args[..])?) { [G::ZERO; OUT_149] } else { let (__hash, __hit) = record.function_queries[149].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[149].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[149].bump_multiplicity(__i); } let __ret: [G; OUT_149] = unsafe { *(record.function_queries[149].output_at(__i).as_ptr() as *const [G; OUT_149]) }; __ret }, _ => { aiur_fn_149::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = G::from_u64(1); let __v_21: G = __v_4 + __v_20; let __v_22: G = __v_5 - __v_20; let __r_arr: [G; OUT_152] = { let __args: [G; IN_152] = [__v_19, __v_1, __v_2, __v_3, __v_21, __v_22, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(152, &__args[..])?) { [G::ZERO; OUT_152] } else { let (__hash, __hit) = record.function_queries[152].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[152].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[152].bump_multiplicity(__i); } let __ret: [G; OUT_152] = unsafe { *(record.function_queries[152].output_at(__i).as_ptr() as *const [G; OUT_152]) }; __ret }, _ => { aiur_fn_152::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __ret: [G; OUT_152] = [__v_23]; record.function_queries[152].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } const INPUT_SIZE_153: usize = 15; const IN_153: usize = 15; const OUT_153: usize = 1; -fn aiur_fn_153(inp: [G; IN_153], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_153], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; match __v_5.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_153] = [__v_0]; record.function_queries[153].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_8, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __r_arr[1]; match __v_15.as_canonical_u64() { 0u64 => { let __v_17: G = G::from_u64(1); let __v_18: G = __v_4 + __v_17; let __v_19: G = __v_5 - __v_17; let __r_arr: [G; OUT_153] = { let __args: [G; IN_153] = [__v_0, __v_1, __v_2, __v_3, __v_18, __v_19, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(153, &__args[..])?) { [G::ZERO; OUT_153] } else { let (__hash, __hit) = record.function_queries[153].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[153].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[153].bump_multiplicity(__i); } let __ret: [G; OUT_153] = unsafe { *(record.function_queries[153].output_at(__i).as_ptr() as *const [G; OUT_153]) }; __ret }, _ => { aiur_fn_153::(__args, __hash, record, io_buffer)? }, } } }; let __v_20: G = __r_arr[0]; let __ret: [G; OUT_153] = [__v_20]; record.function_queries[153].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_7, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = __v_17 + __v_3; let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_11, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_12, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_20: G = __r_arr[0]; let __r_arr: [G; OUT_150] = { let __args: [G; IN_150] = [__v_0, __v_1, __v_2, __v_18, __v_17, __v_9, __v_10, __v_19, __v_20, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(150, &__args[..])?) { [G::ZERO; OUT_150] } else { let (__hash, __hit) = record.function_queries[150].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[150].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[150].bump_multiplicity(__i); } let __ret: [G; OUT_150] = unsafe { *(record.function_queries[150].output_at(__i).as_ptr() as *const [G; OUT_150]) }; __ret }, _ => { aiur_fn_150::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = G::from_u64(1); let __v_23: G = __v_4 + __v_22; let __v_24: G = __v_5 - __v_22; let __v_25: G = __v_6 + __v_22; let __r_arr: [G; OUT_153] = { let __args: [G; IN_153] = [__v_21, __v_1, __v_2, __v_3, __v_23, __v_24, __v_25, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(153, &__args[..])?) { [G::ZERO; OUT_153] } else { let (__hash, __hit) = record.function_queries[153].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[153].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[153].bump_multiplicity(__i); } let __ret: [G; OUT_153] = unsafe { *(record.function_queries[153].output_at(__i).as_ptr() as *const [G; OUT_153]) }; __ret }, _ => { aiur_fn_153::(__args, __hash, record, io_buffer)? }, } } }; let __v_26: G = __r_arr[0]; let __ret: [G; OUT_153] = [__v_26]; record.function_queries[153].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_15.as_canonical_u64())); }, } }, } }) } +fn aiur_fn_153(inp: [G; IN_153], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_153], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; match __v_5.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_153] = [__v_0]; record.function_queries[153].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_8, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __r_arr[1]; match __v_15.as_canonical_u64() { 0u64 => { let __v_17: G = G::from_u64(1); let __v_18: G = __v_4 + __v_17; let __v_19: G = __v_5 - __v_17; let __r_arr: [G; OUT_153] = { let __args: [G; IN_153] = [__v_0, __v_1, __v_2, __v_3, __v_18, __v_19, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(153, &__args[..])?) { [G::ZERO; OUT_153] } else { let (__hash, __hit) = record.function_queries[153].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[153].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[153].bump_multiplicity(__i); } let __ret: [G; OUT_153] = unsafe { *(record.function_queries[153].output_at(__i).as_ptr() as *const [G; OUT_153]) }; __ret }, _ => { aiur_fn_153::(__args, __hash, record, io_buffer)? }, } } }; let __v_20: G = __r_arr[0]; let __ret: [G; OUT_153] = [__v_20]; record.function_queries[153].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_7, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = __v_17 + __v_3; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_11, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_12, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_20: G = __r_arr[0]; let __r_arr: [G; OUT_150] = { let __args: [G; IN_150] = [__v_0, __v_1, __v_2, __v_18, __v_17, __v_9, __v_10, __v_19, __v_20, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(150, &__args[..])?) { [G::ZERO; OUT_150] } else { let (__hash, __hit) = record.function_queries[150].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[150].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[150].bump_multiplicity(__i); } let __ret: [G; OUT_150] = unsafe { *(record.function_queries[150].output_at(__i).as_ptr() as *const [G; OUT_150]) }; __ret }, _ => { aiur_fn_150::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = G::from_u64(1); let __v_23: G = __v_4 + __v_22; let __v_24: G = __v_5 - __v_22; let __v_25: G = __v_6 + __v_22; let __r_arr: [G; OUT_153] = { let __args: [G; IN_153] = [__v_21, __v_1, __v_2, __v_3, __v_23, __v_24, __v_25, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(153, &__args[..])?) { [G::ZERO; OUT_153] } else { let (__hash, __hit) = record.function_queries[153].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[153].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[153].bump_multiplicity(__i); } let __ret: [G; OUT_153] = unsafe { *(record.function_queries[153].output_at(__i).as_ptr() as *const [G; OUT_153]) }; __ret }, _ => { aiur_fn_153::(__args, __hash, record, io_buffer)? }, } } }; let __v_26: G = __r_arr[0]; let __ret: [G; OUT_153] = [__v_26]; record.function_queries[153].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_15.as_canonical_u64())); }, } }, } }) } const INPUT_SIZE_154: usize = 15; const IN_154: usize = 15; const OUT_154: usize = 1; -fn aiur_fn_154(inp: [G; IN_154], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_154], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; match __v_3.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_154] = [__v_0]; record.function_queries[154].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_15: G = G::from_u64(3); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_1, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; match __v_16.as_canonical_u64() { _ => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __v_19: G = __v_17 - __v_18; let __v_20: G = G::from_bool(__v_19 == G::ZERO); let __v_21: G = G::from_u64(1); if (__v_20 != __v_21) { return Err(ExecError::AssertEqMismatch { lhs: __v_20.as_canonical_u64(), rhs: __v_21.as_canonical_u64(), msg: None }); } let __v_22: G = G::from_u64(0); let __r_arr: [G; OUT_157] = { let __args: [G; IN_157] = [__v_6, __v_10, __v_5, __v_7, __v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(157, &__args[..])?) { [G::ZERO; OUT_157] } else { let (__hash, __hit) = record.function_queries[157].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[157].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[157].bump_multiplicity(__i); } let __ret: [G; OUT_157] = unsafe { *(record.function_queries[157].output_at(__i).as_ptr() as *const [G; OUT_157]) }; __ret }, _ => { aiur_fn_157::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __r_arr: [G; OUT_156] = { let __args: [G; IN_156] = [__v_23]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(156, &__args[..])?) { [G::ZERO; OUT_156] } else { let (__hash, __hit) = record.function_queries[156].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[156].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[156].bump_multiplicity(__i); } let __ret: [G; OUT_156] = unsafe { *(record.function_queries[156].output_at(__i).as_ptr() as *const [G; OUT_156]) }; __ret }, _ => { aiur_fn_156::(__args, __hash, record, io_buffer)? }, } } }; let __v_24: G = __r_arr[0]; let __r_arr: [G; OUT_153] = { let __args: [G; IN_153] = [__v_0, __v_8, __v_9, __v_10, __v_22, __v_7, __v_22, __v_6, __v_5, __v_11, __v_12, __v_16, __v_4, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(153, &__args[..])?) { [G::ZERO; OUT_153] } else { let (__hash, __hit) = record.function_queries[153].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[153].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[153].bump_multiplicity(__i); } let __ret: [G; OUT_153] = unsafe { *(record.function_queries[153].output_at(__i).as_ptr() as *const [G; OUT_153]) }; __ret }, _ => { aiur_fn_153::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __ret: [G; OUT_154] = [__v_25]; record.function_queries[154].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_3.as_canonical_u64())); }, } }) } +fn aiur_fn_154(inp: [G; IN_154], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_154], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; match __v_3.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_154] = [__v_0]; record.function_queries[154].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_15: G = G::from_u64(3); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_1, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; match __v_16.as_canonical_u64() { _ => { let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __v_19: G = __v_17 - __v_18; let __v_20: G = G::from_bool(__v_19 == G::ZERO); let __v_21: G = G::from_u64(1); if (__v_20 != __v_21) { return Err(ExecError::AssertEqMismatch { lhs: __v_20.as_canonical_u64(), rhs: __v_21.as_canonical_u64(), msg: None }); } let __v_22: G = G::from_u64(0); let __r_arr: [G; OUT_157] = { let __args: [G; IN_157] = [__v_6, __v_10, __v_5, __v_7, __v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(157, &__args[..])?) { [G::ZERO; OUT_157] } else { let (__hash, __hit) = record.function_queries[157].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[157].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[157].bump_multiplicity(__i); } let __ret: [G; OUT_157] = unsafe { *(record.function_queries[157].output_at(__i).as_ptr() as *const [G; OUT_157]) }; __ret }, _ => { aiur_fn_157::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __r_arr: [G; OUT_156] = { let __args: [G; IN_156] = [__v_23]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(156, &__args[..])?) { [G::ZERO; OUT_156] } else { let (__hash, __hit) = record.function_queries[156].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[156].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[156].bump_multiplicity(__i); } let __ret: [G; OUT_156] = unsafe { *(record.function_queries[156].output_at(__i).as_ptr() as *const [G; OUT_156]) }; __ret }, _ => { aiur_fn_156::(__args, __hash, record, io_buffer)? }, } } }; let __v_24: G = __r_arr[0]; let __r_arr: [G; OUT_153] = { let __args: [G; IN_153] = [__v_0, __v_8, __v_9, __v_10, __v_22, __v_7, __v_22, __v_6, __v_5, __v_11, __v_12, __v_16, __v_4, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(153, &__args[..])?) { [G::ZERO; OUT_153] } else { let (__hash, __hit) = record.function_queries[153].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[153].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[153].bump_multiplicity(__i); } let __ret: [G; OUT_153] = unsafe { *(record.function_queries[153].output_at(__i).as_ptr() as *const [G; OUT_153]) }; __ret }, _ => { aiur_fn_153::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __ret: [G; OUT_154] = [__v_25]; record.function_queries[154].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_3.as_canonical_u64())); }, } }) } const INPUT_SIZE_155: usize = 4; const IN_155: usize = 4; const OUT_155: usize = 1; -fn aiur_fn_155(inp: [G; IN_155], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_155], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; match __v_2.as_canonical_u64() { 0u64 => { let __v_4: G = G::from_u64(1); let __v_5: G = { let __values: [G; 3] = [__v_4, __v_4, __v_4]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_155] = [__v_5]; record.function_queries[155].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_4: G = G::from_u64(0); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_0, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __v_5 + __v_1; let __v_7: G = G::from_u64(1); let __v_8: G = __v_2 - __v_7; let __v_9: G = __v_3 + __v_7; let __r_arr: [G; OUT_155] = { let __args: [G; IN_155] = [__v_0, __v_1, __v_8, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(155, &__args[..])?) { [G::ZERO; OUT_155] } else { let (__hash, __hit) = record.function_queries[155].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[155].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[155].bump_multiplicity(__i); } let __ret: [G; OUT_155] = unsafe { *(record.function_queries[155].output_at(__i).as_ptr() as *const [G; OUT_155]) }; __ret }, _ => { aiur_fn_155::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = { let __values: [G; 3] = [__v_4, __v_6, __v_10]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_155] = [__v_11]; record.function_queries[155].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } +fn aiur_fn_155(inp: [G; IN_155], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_155], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; match __v_2.as_canonical_u64() { 0u64 => { let __v_4: G = G::from_u64(1); let __v_5: G = { let __values: [G; 3] = [__v_4, __v_4, __v_4]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_155] = [__v_5]; record.function_queries[155].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_4: G = G::from_u64(0); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_0, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __v_5 + __v_1; let __v_7: G = G::from_u64(1); let __v_8: G = __v_2 - __v_7; let __v_9: G = __v_3 + __v_7; let __r_arr: [G; OUT_155] = { let __args: [G; IN_155] = [__v_0, __v_1, __v_8, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(155, &__args[..])?) { [G::ZERO; OUT_155] } else { let (__hash, __hit) = record.function_queries[155].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[155].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[155].bump_multiplicity(__i); } let __ret: [G; OUT_155] = unsafe { *(record.function_queries[155].output_at(__i).as_ptr() as *const [G; OUT_155]) }; __ret }, _ => { aiur_fn_155::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = { let __values: [G; 3] = [__v_4, __v_6, __v_10]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_155] = [__v_11]; record.function_queries[155].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } const INPUT_SIZE_156: usize = 1; const IN_156: usize = 1; const OUT_156: usize = 1; @@ -640,7 +640,7 @@ fn aiur_fn_156(inp: [G; IN_156], input_hash: u64, rec const INPUT_SIZE_157: usize = 5; const IN_157: usize = 5; const OUT_157: usize = 1; -fn aiur_fn_157(inp: [G; IN_157], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_157], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; match __v_3.as_canonical_u64() { 0u64 => { let __v_5: G = G::from_u64(1); let __v_6: G = { let __values: [G; 3] = [__v_5, __v_5, __v_5]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_157] = [__v_6]; record.function_queries[157].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_2, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __r_arr[1]; match __v_5.as_canonical_u64() { 0u64 => { let __v_7: G = G::from_u64(1); let __v_8: G = __v_3 - __v_7; let __v_9: G = __v_4 + __v_7; let __r_arr: [G; OUT_157] = { let __args: [G; IN_157] = [__v_0, __v_1, __v_2, __v_8, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(157, &__args[..])?) { [G::ZERO; OUT_157] } else { let (__hash, __hit) = record.function_queries[157].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[157].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[157].bump_multiplicity(__i); } let __ret: [G; OUT_157] = unsafe { *(record.function_queries[157].output_at(__i).as_ptr() as *const [G; OUT_157]) }; __ret }, _ => { aiur_fn_157::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __ret: [G; OUT_157] = [__v_10]; record.function_queries[157].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_7: G = G::from_u64(0); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_0, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __v_9: G = __v_8 + __v_1; let __v_10: G = G::from_u64(1); let __v_11: G = __v_3 - __v_10; let __v_12: G = __v_4 + __v_10; let __r_arr: [G; OUT_157] = { let __args: [G; IN_157] = [__v_0, __v_1, __v_2, __v_11, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(157, &__args[..])?) { [G::ZERO; OUT_157] } else { let (__hash, __hit) = record.function_queries[157].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[157].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[157].bump_multiplicity(__i); } let __ret: [G; OUT_157] = unsafe { *(record.function_queries[157].output_at(__i).as_ptr() as *const [G; OUT_157]) }; __ret }, _ => { aiur_fn_157::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = { let __values: [G; 3] = [__v_7, __v_9, __v_13]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_157] = [__v_14]; record.function_queries[157].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, } }) } +fn aiur_fn_157(inp: [G; IN_157], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_157], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; match __v_3.as_canonical_u64() { 0u64 => { let __v_5: G = G::from_u64(1); let __v_6: G = { let __values: [G; 3] = [__v_5, __v_5, __v_5]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_157] = [__v_6]; record.function_queries[157].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_2, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __r_arr[1]; match __v_5.as_canonical_u64() { 0u64 => { let __v_7: G = G::from_u64(1); let __v_8: G = __v_3 - __v_7; let __v_9: G = __v_4 + __v_7; let __r_arr: [G; OUT_157] = { let __args: [G; IN_157] = [__v_0, __v_1, __v_2, __v_8, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(157, &__args[..])?) { [G::ZERO; OUT_157] } else { let (__hash, __hit) = record.function_queries[157].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[157].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[157].bump_multiplicity(__i); } let __ret: [G; OUT_157] = unsafe { *(record.function_queries[157].output_at(__i).as_ptr() as *const [G; OUT_157]) }; __ret }, _ => { aiur_fn_157::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __ret: [G; OUT_157] = [__v_10]; record.function_queries[157].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_7: G = G::from_u64(0); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_0, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __v_9: G = __v_8 + __v_1; let __v_10: G = G::from_u64(1); let __v_11: G = __v_3 - __v_10; let __v_12: G = __v_4 + __v_10; let __r_arr: [G; OUT_157] = { let __args: [G; IN_157] = [__v_0, __v_1, __v_2, __v_11, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(157, &__args[..])?) { [G::ZERO; OUT_157] } else { let (__hash, __hit) = record.function_queries[157].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[157].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[157].bump_multiplicity(__i); } let __ret: [G; OUT_157] = unsafe { *(record.function_queries[157].output_at(__i).as_ptr() as *const [G; OUT_157]) }; __ret }, _ => { aiur_fn_157::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = { let __values: [G; 3] = [__v_7, __v_9, __v_13]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_157] = [__v_14]; record.function_queries[157].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, } }) } const INPUT_SIZE_158: usize = 5; const IN_158: usize = 5; const OUT_158: usize = 4; @@ -656,11 +656,11 @@ fn aiur_fn_160(inp: [G; IN_160], input_hash: u64, rec const INPUT_SIZE_161: usize = 9; const IN_161: usize = 9; const OUT_161: usize = 3; -fn aiur_fn_161(inp: [G; IN_161], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_161], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_9: G = __loaded[0]; let __v_10: G = __loaded[1]; let __v_11: G = __loaded[2]; let __v_12: G = __loaded[3]; match __v_9.as_canonical_u64() { 1u64 => { let __v_13: G = __v_6 - __v_8; let __v_14: G = G::from_bool(__v_13 == G::ZERO); let __v_15: G = G::from_u64(1); if (__v_14 != __v_15) { return Err(ExecError::AssertEqMismatch { lhs: __v_14.as_canonical_u64(), rhs: __v_15.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_316] = { let __args: [G; IN_316] = [__v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(316, &__args[..])?) { [G::ZERO; OUT_316] } else { let (__hash, __hit) = record.function_queries[316].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[316].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[316].bump_multiplicity(__i); } let __ret: [G; OUT_316] = unsafe { *(record.function_queries[316].output_at(__i).as_ptr() as *const [G; OUT_316]) }; __ret }, _ => { aiur_fn_316::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = G::from_u64(0); if (__v_16 != __v_17) { return Err(ExecError::AssertEqMismatch { lhs: __v_16.as_canonical_u64(), rhs: __v_17.as_canonical_u64(), msg: None }); } let __v_18: G = { let __values: [G; 3] = [__v_15, __v_15, __v_15]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_161] = [__v_0, __v_1, __v_18]; record.function_queries[161].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_2.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_13: G = __loaded[0]; let __v_14: G = __loaded[1]; let __v_15: G = __loaded[2]; let __v_16: G = __loaded[3]; match __v_13.as_canonical_u64() { 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_3.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_17: G = __loaded[0]; let __v_18: G = __loaded[1]; let __v_19: G = __loaded[2]; match __v_17.as_canonical_u64() { 0u64 => { match __v_10.as_canonical_u64() { _ => { let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_20: G = __r_arr[0]; let __v_21: G = G::from_u64(1); if (__v_20 != __v_21) { return Err(ExecError::AssertEqMismatch { lhs: __v_20.as_canonical_u64(), rhs: __v_21.as_canonical_u64(), msg: None }); } let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_5.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_22: G = __loaded[0]; let __v_23: G = __loaded[1]; let __v_24: G = __loaded[2]; match __v_22.as_canonical_u64() { 0u64 => { let __v_25: G = G::from_u64(1); let __v_26: G = __v_6 - __v_25; let __v_27: G = G::from_u64(0); let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_11, __v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_28: G = __r_arr[0]; let __v_29: G = __r_arr[1]; let __mc_out___mc_0: [G; 4] = '__mc_0: { match __v_23.as_canonical_u64() { 0u64 => { break '__mc_0 [__v_0, __v_1, __v_28, __v_29]; }, _ => { break '__mc_0 [__v_28, __v_29, __v_0, __v_1]; }, } }; let __v_30: G = __mc_out___mc_0[0]; let __v_31: G = __mc_out___mc_0[1]; let __v_32: G = __mc_out___mc_0[2]; let __v_33: G = __mc_out___mc_0[3]; let __r_arr: [G; OUT_134] = { let __args: [G; IN_134] = [__v_24, __v_26, __v_14, __v_15, __v_30, __v_31, __v_32, __v_33]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(134, &__args[..])?) { [G::ZERO; OUT_134] } else { let (__hash, __hit) = record.function_queries[134].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[134].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[134].bump_multiplicity(__i); } let __ret: [G; OUT_134] = unsafe { *(record.function_queries[134].output_at(__i).as_ptr() as *const [G; OUT_134]) }; __ret }, _ => { aiur_fn_134::(__args, __hash, record, io_buffer)? }, } } }; let __v_34: G = __r_arr[0]; let __v_35: G = __r_arr[1]; let __r_arr: [G; OUT_160] = { let __args: [G; IN_160] = [__v_34, __v_35, __v_26, __v_14, __v_15, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(160, &__args[..])?) { [G::ZERO; OUT_160] } else { let (__hash, __hit) = record.function_queries[160].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[160].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[160].bump_multiplicity(__i); } let __ret: [G; OUT_160] = unsafe { *(record.function_queries[160].output_at(__i).as_ptr() as *const [G; OUT_160]) }; __ret }, _ => { aiur_fn_160::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __v_38: G = __r_arr[2]; let __r_arr: [G; OUT_161] = { let __args: [G; IN_161] = [__v_36, __v_37, __v_16, __v_19, __v_12, __v_24, __v_26, __v_38, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(161, &__args[..])?) { [G::ZERO; OUT_161] } else { let (__hash, __hit) = record.function_queries[161].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[161].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[161].bump_multiplicity(__i); } let __ret: [G; OUT_161] = unsafe { *(record.function_queries[161].output_at(__i).as_ptr() as *const [G; OUT_161]) }; __ret }, _ => { aiur_fn_161::(__args, __hash, record, io_buffer)? }, } } }; let __v_39: G = __r_arr[0]; let __v_40: G = __r_arr[1]; let __v_41: G = __r_arr[2]; let __gb: [u8; 8] = __v_30.as_canonical_u64().to_le_bytes(); let __v_42: G = G::from_u8(__gb[0]); let __v_43: G = G::from_u8(__gb[1]); let __v_44: G = G::from_u8(__gb[2]); let __v_45: G = G::from_u8(__gb[3]); let __v_46: G = G::from_u8(__gb[4]); let __v_47: G = G::from_u8(__gb[5]); let __v_48: G = G::from_u8(__gb[6]); let __v_49: G = G::from_u8(__gb[7]); if (!unconstrained) { let __vi = __v_42; let __vj = __v_43; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_44; let __vj = __v_45; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_46; let __vj = __v_47; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_48; let __vj = __v_49; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } let __v_50: G = G::from_u64(256); let __v_51: G = __v_50 * __v_43; let __v_52: G = __v_42 + __v_51; let __v_53: G = G::from_u64(65536); let __v_54: G = __v_53 * __v_44; let __v_55: G = __v_52 + __v_54; let __v_56: G = G::from_u64(16777216); let __v_57: G = __v_56 * __v_45; let __v_58: G = __v_55 + __v_57; let __v_59: G = G::from_u64(4294967296); let __v_60: G = __v_59 * __v_46; let __v_61: G = __v_58 + __v_60; let __v_62: G = G::from_u64(1099511627776); let __v_63: G = __v_62 * __v_47; let __v_64: G = __v_61 + __v_63; let __v_65: G = G::from_u64(281474976710656); let __v_66: G = __v_65 * __v_48; let __v_67: G = __v_64 + __v_66; let __v_68: G = G::from_u64(72057594037927936); let __v_69: G = __v_68 * __v_49; let __v_70: G = __v_67 + __v_69; if (__v_70 != __v_30) { return Err(ExecError::AssertEqMismatch { lhs: __v_70.as_canonical_u64(), rhs: __v_30.as_canonical_u64(), msg: None }); } let __v_71: G = __v_46 + __v_47; let __v_72: G = __v_71 + __v_48; let __v_73: G = __v_72 + __v_49; let __v_74: G = G::from_u64(1020); let __v_75: G = __v_73 - __v_74; let __v_76: G = G::from_bool(__v_75 == G::ZERO); let __v_77: G = __v_42 + __v_43; let __v_78: G = __v_77 + __v_44; let __v_79: G = __v_78 + __v_45; let __v_80: G = G::from_bool(__v_79 == G::ZERO); let __v_81: G = G::from_u64(1); let __v_82: G = __v_81 - __v_80; let __v_83: G = __v_76 * __v_82; let __v_84: G = __v_81 - __v_83; if (__v_84 != __v_81) { return Err(ExecError::AssertEqMismatch { lhs: __v_84.as_canonical_u64(), rhs: __v_81.as_canonical_u64(), msg: None }); } let __gb: [u8; 8] = __v_31.as_canonical_u64().to_le_bytes(); let __v_85: G = G::from_u8(__gb[0]); let __v_86: G = G::from_u8(__gb[1]); let __v_87: G = G::from_u8(__gb[2]); let __v_88: G = G::from_u8(__gb[3]); let __v_89: G = G::from_u8(__gb[4]); let __v_90: G = G::from_u8(__gb[5]); let __v_91: G = G::from_u8(__gb[6]); let __v_92: G = G::from_u8(__gb[7]); if (!unconstrained) { let __vi = __v_85; let __vj = __v_86; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_87; let __vj = __v_88; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_89; let __vj = __v_90; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_91; let __vj = __v_92; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } let __v_93: G = __v_50 * __v_86; let __v_94: G = __v_85 + __v_93; let __v_95: G = __v_53 * __v_87; let __v_96: G = __v_94 + __v_95; let __v_97: G = __v_56 * __v_88; let __v_98: G = __v_96 + __v_97; let __v_99: G = __v_59 * __v_89; let __v_100: G = __v_98 + __v_99; let __v_101: G = __v_62 * __v_90; let __v_102: G = __v_100 + __v_101; let __v_103: G = __v_65 * __v_91; let __v_104: G = __v_102 + __v_103; let __v_105: G = __v_68 * __v_92; let __v_106: G = __v_104 + __v_105; if (__v_106 != __v_31) { return Err(ExecError::AssertEqMismatch { lhs: __v_106.as_canonical_u64(), rhs: __v_31.as_canonical_u64(), msg: None }); } let __v_107: G = __v_89 + __v_90; let __v_108: G = __v_107 + __v_91; let __v_109: G = __v_108 + __v_92; let __v_110: G = __v_109 - __v_74; let __v_111: G = G::from_bool(__v_110 == G::ZERO); let __v_112: G = __v_85 + __v_86; let __v_113: G = __v_112 + __v_87; let __v_114: G = __v_113 + __v_88; let __v_115: G = G::from_bool(__v_114 == G::ZERO); let __v_116: G = __v_81 - __v_115; let __v_117: G = __v_111 * __v_116; let __v_118: G = __v_81 - __v_117; if (__v_118 != __v_81) { return Err(ExecError::AssertEqMismatch { lhs: __v_118.as_canonical_u64(), rhs: __v_81.as_canonical_u64(), msg: None }); } let __gb: [u8; 8] = __v_32.as_canonical_u64().to_le_bytes(); let __v_119: G = G::from_u8(__gb[0]); let __v_120: G = G::from_u8(__gb[1]); let __v_121: G = G::from_u8(__gb[2]); let __v_122: G = G::from_u8(__gb[3]); let __v_123: G = G::from_u8(__gb[4]); let __v_124: G = G::from_u8(__gb[5]); let __v_125: G = G::from_u8(__gb[6]); let __v_126: G = G::from_u8(__gb[7]); if (!unconstrained) { let __vi = __v_119; let __vj = __v_120; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_121; let __vj = __v_122; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_123; let __vj = __v_124; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_125; let __vj = __v_126; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } let __v_127: G = __v_50 * __v_120; let __v_128: G = __v_119 + __v_127; let __v_129: G = __v_53 * __v_121; let __v_130: G = __v_128 + __v_129; let __v_131: G = __v_56 * __v_122; let __v_132: G = __v_130 + __v_131; let __v_133: G = __v_59 * __v_123; let __v_134: G = __v_132 + __v_133; let __v_135: G = __v_62 * __v_124; let __v_136: G = __v_134 + __v_135; let __v_137: G = __v_65 * __v_125; let __v_138: G = __v_136 + __v_137; let __v_139: G = __v_68 * __v_126; let __v_140: G = __v_138 + __v_139; if (__v_140 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_140.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: None }); } let __v_141: G = __v_123 + __v_124; let __v_142: G = __v_141 + __v_125; let __v_143: G = __v_142 + __v_126; let __v_144: G = __v_143 - __v_74; let __v_145: G = G::from_bool(__v_144 == G::ZERO); let __v_146: G = __v_119 + __v_120; let __v_147: G = __v_146 + __v_121; let __v_148: G = __v_147 + __v_122; let __v_149: G = G::from_bool(__v_148 == G::ZERO); let __v_150: G = __v_81 - __v_149; let __v_151: G = __v_145 * __v_150; let __v_152: G = __v_81 - __v_151; if (__v_152 != __v_81) { return Err(ExecError::AssertEqMismatch { lhs: __v_152.as_canonical_u64(), rhs: __v_81.as_canonical_u64(), msg: None }); } let __gb: [u8; 8] = __v_33.as_canonical_u64().to_le_bytes(); let __v_153: G = G::from_u8(__gb[0]); let __v_154: G = G::from_u8(__gb[1]); let __v_155: G = G::from_u8(__gb[2]); let __v_156: G = G::from_u8(__gb[3]); let __v_157: G = G::from_u8(__gb[4]); let __v_158: G = G::from_u8(__gb[5]); let __v_159: G = G::from_u8(__gb[6]); let __v_160: G = G::from_u8(__gb[7]); if (!unconstrained) { let __vi = __v_153; let __vj = __v_154; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_155; let __vj = __v_156; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_157; let __vj = __v_158; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_159; let __vj = __v_160; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } let __v_161: G = __v_50 * __v_154; let __v_162: G = __v_153 + __v_161; let __v_163: G = __v_53 * __v_155; let __v_164: G = __v_162 + __v_163; let __v_165: G = __v_56 * __v_156; let __v_166: G = __v_164 + __v_165; let __v_167: G = __v_59 * __v_157; let __v_168: G = __v_166 + __v_167; let __v_169: G = __v_62 * __v_158; let __v_170: G = __v_168 + __v_169; let __v_171: G = __v_65 * __v_159; let __v_172: G = __v_170 + __v_171; let __v_173: G = __v_68 * __v_160; let __v_174: G = __v_172 + __v_173; if (__v_174 != __v_33) { return Err(ExecError::AssertEqMismatch { lhs: __v_174.as_canonical_u64(), rhs: __v_33.as_canonical_u64(), msg: None }); } let __v_175: G = __v_157 + __v_158; let __v_176: G = __v_175 + __v_159; let __v_177: G = __v_176 + __v_160; let __v_178: G = __v_177 - __v_74; let __v_179: G = G::from_bool(__v_178 == G::ZERO); let __v_180: G = __v_153 + __v_154; let __v_181: G = __v_180 + __v_155; let __v_182: G = __v_181 + __v_156; let __v_183: G = G::from_bool(__v_182 == G::ZERO); let __v_184: G = __v_81 - __v_183; let __v_185: G = __v_179 * __v_184; let __v_186: G = __v_81 - __v_185; if (__v_186 != __v_81) { return Err(ExecError::AssertEqMismatch { lhs: __v_186.as_canonical_u64(), rhs: __v_81.as_canonical_u64(), msg: None }); } let __v_187: G = G::from_u64(0); let __v_188: G = { let __values: [G; 10] = [__v_81, __v_81, __v_81, __v_81, __v_81, __v_81, __v_81, __v_81, __v_81, __v_81]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_189: G = { let __values: [G; 10] = [__v_187, __v_153, __v_154, __v_155, __v_156, __v_157, __v_158, __v_159, __v_160, __v_188]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_190: G = { let __values: [G; 10] = [__v_187, __v_119, __v_120, __v_121, __v_122, __v_123, __v_124, __v_125, __v_126, __v_189]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_191: G = { let __values: [G; 10] = [__v_187, __v_85, __v_86, __v_87, __v_88, __v_89, __v_90, __v_91, __v_92, __v_190]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_192: G = { let __values: [G; 10] = [__v_187, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_191]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_193: G = { let __values: [G; 3] = [__v_187, __v_192, __v_41]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_161] = [__v_39, __v_40, __v_193]; record.function_queries[161].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_22.as_canonical_u64())); }, } }, } }, _ => { return Err(ExecError::MatchNoCase(__v_17.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_13.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_9.as_canonical_u64())); }, } }) } +fn aiur_fn_161(inp: [G; IN_161], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_161], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_9: G = __loaded[0]; let __v_10: G = __loaded[1]; let __v_11: G = __loaded[2]; let __v_12: G = __loaded[3]; match __v_9.as_canonical_u64() { 1u64 => { let __v_13: G = __v_6 - __v_8; let __v_14: G = G::from_bool(__v_13 == G::ZERO); let __v_15: G = G::from_u64(1); if (__v_14 != __v_15) { return Err(ExecError::AssertEqMismatch { lhs: __v_14.as_canonical_u64(), rhs: __v_15.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_318] = { let __args: [G; IN_318] = [__v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(318, &__args[..])?) { [G::ZERO; OUT_318] } else { let (__hash, __hit) = record.function_queries[318].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[318].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[318].bump_multiplicity(__i); } let __ret: [G; OUT_318] = unsafe { *(record.function_queries[318].output_at(__i).as_ptr() as *const [G; OUT_318]) }; __ret }, _ => { aiur_fn_318::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = G::from_u64(0); if (__v_16 != __v_17) { return Err(ExecError::AssertEqMismatch { lhs: __v_16.as_canonical_u64(), rhs: __v_17.as_canonical_u64(), msg: None }); } let __v_18: G = { let __values: [G; 3] = [__v_15, __v_15, __v_15]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_161] = [__v_0, __v_1, __v_18]; record.function_queries[161].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_2.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_13: G = __loaded[0]; let __v_14: G = __loaded[1]; let __v_15: G = __loaded[2]; let __v_16: G = __loaded[3]; match __v_13.as_canonical_u64() { 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_3.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_17: G = __loaded[0]; let __v_18: G = __loaded[1]; let __v_19: G = __loaded[2]; match __v_17.as_canonical_u64() { 0u64 => { match __v_10.as_canonical_u64() { _ => { let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_20: G = __r_arr[0]; let __v_21: G = G::from_u64(1); if (__v_20 != __v_21) { return Err(ExecError::AssertEqMismatch { lhs: __v_20.as_canonical_u64(), rhs: __v_21.as_canonical_u64(), msg: None }); } let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_5.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_22: G = __loaded[0]; let __v_23: G = __loaded[1]; let __v_24: G = __loaded[2]; match __v_22.as_canonical_u64() { 0u64 => { let __v_25: G = G::from_u64(1); let __v_26: G = __v_6 - __v_25; let __v_27: G = G::from_u64(0); let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_11, __v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_28: G = __r_arr[0]; let __v_29: G = __r_arr[1]; let __mc_out___mc_0: [G; 4] = '__mc_0: { match __v_23.as_canonical_u64() { 0u64 => { break '__mc_0 [__v_0, __v_1, __v_28, __v_29]; }, _ => { break '__mc_0 [__v_28, __v_29, __v_0, __v_1]; }, } }; let __v_30: G = __mc_out___mc_0[0]; let __v_31: G = __mc_out___mc_0[1]; let __v_32: G = __mc_out___mc_0[2]; let __v_33: G = __mc_out___mc_0[3]; let __r_arr: [G; OUT_134] = { let __args: [G; IN_134] = [__v_24, __v_26, __v_14, __v_15, __v_30, __v_31, __v_32, __v_33]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(134, &__args[..])?) { [G::ZERO; OUT_134] } else { let (__hash, __hit) = record.function_queries[134].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[134].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[134].bump_multiplicity(__i); } let __ret: [G; OUT_134] = unsafe { *(record.function_queries[134].output_at(__i).as_ptr() as *const [G; OUT_134]) }; __ret }, _ => { aiur_fn_134::(__args, __hash, record, io_buffer)? }, } } }; let __v_34: G = __r_arr[0]; let __v_35: G = __r_arr[1]; let __r_arr: [G; OUT_160] = { let __args: [G; IN_160] = [__v_34, __v_35, __v_26, __v_14, __v_15, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(160, &__args[..])?) { [G::ZERO; OUT_160] } else { let (__hash, __hit) = record.function_queries[160].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[160].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[160].bump_multiplicity(__i); } let __ret: [G; OUT_160] = unsafe { *(record.function_queries[160].output_at(__i).as_ptr() as *const [G; OUT_160]) }; __ret }, _ => { aiur_fn_160::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __v_38: G = __r_arr[2]; let __r_arr: [G; OUT_161] = { let __args: [G; IN_161] = [__v_36, __v_37, __v_16, __v_19, __v_12, __v_24, __v_26, __v_38, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(161, &__args[..])?) { [G::ZERO; OUT_161] } else { let (__hash, __hit) = record.function_queries[161].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[161].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[161].bump_multiplicity(__i); } let __ret: [G; OUT_161] = unsafe { *(record.function_queries[161].output_at(__i).as_ptr() as *const [G; OUT_161]) }; __ret }, _ => { aiur_fn_161::(__args, __hash, record, io_buffer)? }, } } }; let __v_39: G = __r_arr[0]; let __v_40: G = __r_arr[1]; let __v_41: G = __r_arr[2]; let __gb: [u8; 8] = __v_30.as_canonical_u64().to_le_bytes(); let __v_42: G = G::from_u8(__gb[0]); let __v_43: G = G::from_u8(__gb[1]); let __v_44: G = G::from_u8(__gb[2]); let __v_45: G = G::from_u8(__gb[3]); let __v_46: G = G::from_u8(__gb[4]); let __v_47: G = G::from_u8(__gb[5]); let __v_48: G = G::from_u8(__gb[6]); let __v_49: G = G::from_u8(__gb[7]); if (!unconstrained) { let __vi = __v_42; let __vj = __v_43; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_44; let __vj = __v_45; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_46; let __vj = __v_47; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_48; let __vj = __v_49; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } let __v_50: G = G::from_u64(256); let __v_51: G = __v_50 * __v_43; let __v_52: G = __v_42 + __v_51; let __v_53: G = G::from_u64(65536); let __v_54: G = __v_53 * __v_44; let __v_55: G = __v_52 + __v_54; let __v_56: G = G::from_u64(16777216); let __v_57: G = __v_56 * __v_45; let __v_58: G = __v_55 + __v_57; let __v_59: G = G::from_u64(4294967296); let __v_60: G = __v_59 * __v_46; let __v_61: G = __v_58 + __v_60; let __v_62: G = G::from_u64(1099511627776); let __v_63: G = __v_62 * __v_47; let __v_64: G = __v_61 + __v_63; let __v_65: G = G::from_u64(281474976710656); let __v_66: G = __v_65 * __v_48; let __v_67: G = __v_64 + __v_66; let __v_68: G = G::from_u64(72057594037927936); let __v_69: G = __v_68 * __v_49; let __v_70: G = __v_67 + __v_69; if (__v_70 != __v_30) { return Err(ExecError::AssertEqMismatch { lhs: __v_70.as_canonical_u64(), rhs: __v_30.as_canonical_u64(), msg: None }); } let __v_71: G = __v_46 + __v_47; let __v_72: G = __v_71 + __v_48; let __v_73: G = __v_72 + __v_49; let __v_74: G = G::from_u64(1020); let __v_75: G = __v_73 - __v_74; let __v_76: G = G::from_bool(__v_75 == G::ZERO); let __v_77: G = __v_42 + __v_43; let __v_78: G = __v_77 + __v_44; let __v_79: G = __v_78 + __v_45; let __v_80: G = G::from_bool(__v_79 == G::ZERO); let __v_81: G = G::from_u64(1); let __v_82: G = __v_81 - __v_80; let __v_83: G = __v_76 * __v_82; let __v_84: G = __v_81 - __v_83; if (__v_84 != __v_81) { return Err(ExecError::AssertEqMismatch { lhs: __v_84.as_canonical_u64(), rhs: __v_81.as_canonical_u64(), msg: None }); } let __gb: [u8; 8] = __v_31.as_canonical_u64().to_le_bytes(); let __v_85: G = G::from_u8(__gb[0]); let __v_86: G = G::from_u8(__gb[1]); let __v_87: G = G::from_u8(__gb[2]); let __v_88: G = G::from_u8(__gb[3]); let __v_89: G = G::from_u8(__gb[4]); let __v_90: G = G::from_u8(__gb[5]); let __v_91: G = G::from_u8(__gb[6]); let __v_92: G = G::from_u8(__gb[7]); if (!unconstrained) { let __vi = __v_85; let __vj = __v_86; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_87; let __vj = __v_88; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_89; let __vj = __v_90; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_91; let __vj = __v_92; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } let __v_93: G = __v_50 * __v_86; let __v_94: G = __v_85 + __v_93; let __v_95: G = __v_53 * __v_87; let __v_96: G = __v_94 + __v_95; let __v_97: G = __v_56 * __v_88; let __v_98: G = __v_96 + __v_97; let __v_99: G = __v_59 * __v_89; let __v_100: G = __v_98 + __v_99; let __v_101: G = __v_62 * __v_90; let __v_102: G = __v_100 + __v_101; let __v_103: G = __v_65 * __v_91; let __v_104: G = __v_102 + __v_103; let __v_105: G = __v_68 * __v_92; let __v_106: G = __v_104 + __v_105; if (__v_106 != __v_31) { return Err(ExecError::AssertEqMismatch { lhs: __v_106.as_canonical_u64(), rhs: __v_31.as_canonical_u64(), msg: None }); } let __v_107: G = __v_89 + __v_90; let __v_108: G = __v_107 + __v_91; let __v_109: G = __v_108 + __v_92; let __v_110: G = __v_109 - __v_74; let __v_111: G = G::from_bool(__v_110 == G::ZERO); let __v_112: G = __v_85 + __v_86; let __v_113: G = __v_112 + __v_87; let __v_114: G = __v_113 + __v_88; let __v_115: G = G::from_bool(__v_114 == G::ZERO); let __v_116: G = __v_81 - __v_115; let __v_117: G = __v_111 * __v_116; let __v_118: G = __v_81 - __v_117; if (__v_118 != __v_81) { return Err(ExecError::AssertEqMismatch { lhs: __v_118.as_canonical_u64(), rhs: __v_81.as_canonical_u64(), msg: None }); } let __gb: [u8; 8] = __v_32.as_canonical_u64().to_le_bytes(); let __v_119: G = G::from_u8(__gb[0]); let __v_120: G = G::from_u8(__gb[1]); let __v_121: G = G::from_u8(__gb[2]); let __v_122: G = G::from_u8(__gb[3]); let __v_123: G = G::from_u8(__gb[4]); let __v_124: G = G::from_u8(__gb[5]); let __v_125: G = G::from_u8(__gb[6]); let __v_126: G = G::from_u8(__gb[7]); if (!unconstrained) { let __vi = __v_119; let __vj = __v_120; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_121; let __vj = __v_122; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_123; let __vj = __v_124; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_125; let __vj = __v_126; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } let __v_127: G = __v_50 * __v_120; let __v_128: G = __v_119 + __v_127; let __v_129: G = __v_53 * __v_121; let __v_130: G = __v_128 + __v_129; let __v_131: G = __v_56 * __v_122; let __v_132: G = __v_130 + __v_131; let __v_133: G = __v_59 * __v_123; let __v_134: G = __v_132 + __v_133; let __v_135: G = __v_62 * __v_124; let __v_136: G = __v_134 + __v_135; let __v_137: G = __v_65 * __v_125; let __v_138: G = __v_136 + __v_137; let __v_139: G = __v_68 * __v_126; let __v_140: G = __v_138 + __v_139; if (__v_140 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_140.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: None }); } let __v_141: G = __v_123 + __v_124; let __v_142: G = __v_141 + __v_125; let __v_143: G = __v_142 + __v_126; let __v_144: G = __v_143 - __v_74; let __v_145: G = G::from_bool(__v_144 == G::ZERO); let __v_146: G = __v_119 + __v_120; let __v_147: G = __v_146 + __v_121; let __v_148: G = __v_147 + __v_122; let __v_149: G = G::from_bool(__v_148 == G::ZERO); let __v_150: G = __v_81 - __v_149; let __v_151: G = __v_145 * __v_150; let __v_152: G = __v_81 - __v_151; if (__v_152 != __v_81) { return Err(ExecError::AssertEqMismatch { lhs: __v_152.as_canonical_u64(), rhs: __v_81.as_canonical_u64(), msg: None }); } let __gb: [u8; 8] = __v_33.as_canonical_u64().to_le_bytes(); let __v_153: G = G::from_u8(__gb[0]); let __v_154: G = G::from_u8(__gb[1]); let __v_155: G = G::from_u8(__gb[2]); let __v_156: G = G::from_u8(__gb[3]); let __v_157: G = G::from_u8(__gb[4]); let __v_158: G = G::from_u8(__gb[5]); let __v_159: G = G::from_u8(__gb[6]); let __v_160: G = G::from_u8(__gb[7]); if (!unconstrained) { let __vi = __v_153; let __vj = __v_154; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_155; let __vj = __v_156; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_157; let __vj = __v_158; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_159; let __vj = __v_160; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } let __v_161: G = __v_50 * __v_154; let __v_162: G = __v_153 + __v_161; let __v_163: G = __v_53 * __v_155; let __v_164: G = __v_162 + __v_163; let __v_165: G = __v_56 * __v_156; let __v_166: G = __v_164 + __v_165; let __v_167: G = __v_59 * __v_157; let __v_168: G = __v_166 + __v_167; let __v_169: G = __v_62 * __v_158; let __v_170: G = __v_168 + __v_169; let __v_171: G = __v_65 * __v_159; let __v_172: G = __v_170 + __v_171; let __v_173: G = __v_68 * __v_160; let __v_174: G = __v_172 + __v_173; if (__v_174 != __v_33) { return Err(ExecError::AssertEqMismatch { lhs: __v_174.as_canonical_u64(), rhs: __v_33.as_canonical_u64(), msg: None }); } let __v_175: G = __v_157 + __v_158; let __v_176: G = __v_175 + __v_159; let __v_177: G = __v_176 + __v_160; let __v_178: G = __v_177 - __v_74; let __v_179: G = G::from_bool(__v_178 == G::ZERO); let __v_180: G = __v_153 + __v_154; let __v_181: G = __v_180 + __v_155; let __v_182: G = __v_181 + __v_156; let __v_183: G = G::from_bool(__v_182 == G::ZERO); let __v_184: G = __v_81 - __v_183; let __v_185: G = __v_179 * __v_184; let __v_186: G = __v_81 - __v_185; if (__v_186 != __v_81) { return Err(ExecError::AssertEqMismatch { lhs: __v_186.as_canonical_u64(), rhs: __v_81.as_canonical_u64(), msg: None }); } let __v_187: G = G::from_u64(0); let __v_188: G = { let __values: [G; 10] = [__v_81, __v_81, __v_81, __v_81, __v_81, __v_81, __v_81, __v_81, __v_81, __v_81]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_189: G = { let __values: [G; 10] = [__v_187, __v_153, __v_154, __v_155, __v_156, __v_157, __v_158, __v_159, __v_160, __v_188]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_190: G = { let __values: [G; 10] = [__v_187, __v_119, __v_120, __v_121, __v_122, __v_123, __v_124, __v_125, __v_126, __v_189]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_191: G = { let __values: [G; 10] = [__v_187, __v_85, __v_86, __v_87, __v_88, __v_89, __v_90, __v_91, __v_92, __v_190]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_192: G = { let __values: [G; 10] = [__v_187, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_191]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_193: G = { let __values: [G; 3] = [__v_187, __v_192, __v_41]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_161] = [__v_39, __v_40, __v_193]; record.function_queries[161].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_22.as_canonical_u64())); }, } }, } }, _ => { return Err(ExecError::MatchNoCase(__v_17.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_13.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_9.as_canonical_u64())); }, } }) } const INPUT_SIZE_162: usize = 25; const IN_162: usize = 25; const OUT_162: usize = 1; -fn aiur_fn_162(inp: [G; IN_162], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_162], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; match __v_1.as_canonical_u64() { _ => { let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __v_26: G = __v_25 - __v_24; let __v_27: G = G::from_bool(__v_26 == G::ZERO); let __v_28: G = G::from_u64(1); if (__v_27 != __v_28) { return Err(ExecError::AssertEqMismatch { lhs: __v_27.as_canonical_u64(), rhs: __v_28.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_29: G = __r_arr[0]; let __v_30: G = G::from_u64(3); let __r_arr: [G; OUT_148] = { let __args: [G; IN_148] = [__v_8, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(148, &__args[..])?) { [G::ZERO; OUT_148] } else { let (__hash, __hit) = record.function_queries[148].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[148].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[148].bump_multiplicity(__i); } let __ret: [G; OUT_148] = unsafe { *(record.function_queries[148].output_at(__i).as_ptr() as *const [G; OUT_148]) }; __ret }, _ => { aiur_fn_148::(__args, __hash, record, io_buffer)? }, } } }; let __v_31: G = __r_arr[0]; let __v_32: G = __v_30 + __v_31; let __v_33: G = __v_29 - __v_32; let __v_34: G = G::from_bool(__v_33 == G::ZERO); if (__v_34 != __v_28) { return Err(ExecError::AssertEqMismatch { lhs: __v_34.as_canonical_u64(), rhs: __v_28.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_145] = { let __args: [G; IN_145] = [__v_15, __v_19, __v_18, __v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(145, &__args[..])?) { [G::ZERO; OUT_145] } else { let (__hash, __hit) = record.function_queries[145].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[145].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[145].bump_multiplicity(__i); } let __ret: [G; OUT_145] = unsafe { *(record.function_queries[145].output_at(__i).as_ptr() as *const [G; OUT_145]) }; __ret }, _ => { aiur_fn_145::(__args, __hash, record, io_buffer)? }, } } }; let __v_35: G = __r_arr[0]; let __v_36: G = G::from_u64(0); let __r_arr: [G; OUT_155] = { let __args: [G; IN_155] = [__v_15, __v_19, __v_18, __v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(155, &__args[..])?) { [G::ZERO; OUT_155] } else { let (__hash, __hit) = record.function_queries[155].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[155].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[155].bump_multiplicity(__i); } let __ret: [G; OUT_155] = unsafe { *(record.function_queries[155].output_at(__i).as_ptr() as *const [G; OUT_155]) }; __ret }, _ => { aiur_fn_155::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_1, __v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_38: G = __r_arr[0]; match __v_38.as_canonical_u64() { _ => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_38]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_39: G = __r_arr[0]; let __v_40: G = __v_39 - __v_18; let __v_41: G = G::from_bool(__v_40 == G::ZERO); let __v_42: G = G::from_u64(1); if (__v_41 != __v_42) { return Err(ExecError::AssertEqMismatch { lhs: __v_41.as_canonical_u64(), rhs: __v_42.as_canonical_u64(), msg: None }); } let __v_43: G = G::from_u64(0); let __r_arr: [G; OUT_151] = { let __args: [G; IN_151] = [__v_35, __v_0, __v_20, __v_19, __v_43, __v_18, __v_15, __v_16, __v_17, __v_38, __v_5, __v_3, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(151, &__args[..])?) { [G::ZERO; OUT_151] } else { let (__hash, __hit) = record.function_queries[151].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[151].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[151].bump_multiplicity(__i); } let __ret: [G; OUT_151] = unsafe { *(record.function_queries[151].output_at(__i).as_ptr() as *const [G; OUT_151]) }; __ret }, _ => { aiur_fn_151::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_1, __v_42]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_45: G = __r_arr[0]; match __v_45.as_canonical_u64() { _ => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_45]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_46: G = __r_arr[0]; let __v_47: G = __v_46 - __v_18; let __v_48: G = G::from_bool(__v_47 == G::ZERO); let __v_49: G = G::from_u64(1); if (__v_48 != __v_49) { return Err(ExecError::AssertEqMismatch { lhs: __v_48.as_canonical_u64(), rhs: __v_49.as_canonical_u64(), msg: None }); } let __v_50: G = G::from_u64(0); let __r_arr: [G; OUT_151] = { let __args: [G; IN_151] = [__v_44, __v_0, __v_20, __v_19, __v_50, __v_18, __v_15, __v_16, __v_17, __v_45, __v_6, __v_3, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(151, &__args[..])?) { [G::ZERO; OUT_151] } else { let (__hash, __hit) = record.function_queries[151].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[151].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[151].bump_multiplicity(__i); } let __ret: [G; OUT_151] = unsafe { *(record.function_queries[151].output_at(__i).as_ptr() as *const [G; OUT_151]) }; __ret }, _ => { aiur_fn_151::(__args, __hash, record, io_buffer)? }, } } }; let __v_51: G = __r_arr[0]; let __v_52: G = G::from_u64(2); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_1, __v_52]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; match __v_53.as_canonical_u64() { _ => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_53]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __v_55: G = __v_54 - __v_18; let __v_56: G = G::from_bool(__v_55 == G::ZERO); let __v_57: G = G::from_u64(1); if (__v_56 != __v_57) { return Err(ExecError::AssertEqMismatch { lhs: __v_56.as_canonical_u64(), rhs: __v_57.as_canonical_u64(), msg: None }); } let __v_58: G = G::from_u64(0); let __r_arr: [G; OUT_152] = { let __args: [G; IN_152] = [__v_51, __v_0, __v_20, __v_19, __v_58, __v_18, __v_15, __v_16, __v_17, __v_53, __v_7, __v_3, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(152, &__args[..])?) { [G::ZERO; OUT_152] } else { let (__hash, __hit) = record.function_queries[152].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[152].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[152].bump_multiplicity(__i); } let __ret: [G; OUT_152] = unsafe { *(record.function_queries[152].output_at(__i).as_ptr() as *const [G; OUT_152]) }; __ret }, _ => { aiur_fn_152::(__args, __hash, record, io_buffer)? }, } } }; let __v_59: G = __r_arr[0]; let __r_arr: [G; OUT_154] = { let __args: [G; IN_154] = [__v_59, __v_1, __v_13, __v_8, __v_9, __v_14, __v_15, __v_18, __v_0, __v_20, __v_19, __v_16, __v_17, __v_3, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(154, &__args[..])?) { [G::ZERO; OUT_154] } else { let (__hash, __hit) = record.function_queries[154].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[154].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[154].bump_multiplicity(__i); } let __ret: [G; OUT_154] = unsafe { *(record.function_queries[154].output_at(__i).as_ptr() as *const [G; OUT_154]) }; __ret }, _ => { aiur_fn_154::(__args, __hash, record, io_buffer)? }, } } }; let __v_60: G = __r_arr[0]; let __r_arr: [G; OUT_147] = { let __args: [G; IN_147] = [__v_60, __v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(147, &__args[..])?) { [G::ZERO; OUT_147] } else { let (__hash, __hit) = record.function_queries[147].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[147].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[147].bump_multiplicity(__i); } let __ret: [G; OUT_147] = unsafe { *(record.function_queries[147].output_at(__i).as_ptr() as *const [G; OUT_147]) }; __ret }, _ => { aiur_fn_147::(__args, __hash, record, io_buffer)? }, } } }; let __v_61: G = __r_arr[0]; let __loaded: [G; 7] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(4).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(7))?; let __ptr_u64 = __v_60.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 7 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 7] = __args[..7].try_into().unwrap(); __arr }; let __v_62: G = __loaded[0]; let __v_63: G = __loaded[1]; let __v_64: G = __loaded[2]; let __v_65: G = __loaded[3]; let __v_66: G = __loaded[4]; let __v_67: G = __loaded[5]; let __v_68: G = __loaded[6]; match __v_62.as_canonical_u64() { 0u64 => { match __v_63.as_canonical_u64() { _ => { let __v_69: G = __v_63 - __v_20; let __v_70: G = G::from_bool(__v_69 == G::ZERO); let __v_71: G = G::from_u64(1); if (__v_70 != __v_71) { return Err(ExecError::AssertEqMismatch { lhs: __v_70.as_canonical_u64(), rhs: __v_71.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_161] = { let __args: [G; IN_161] = [__v_66, __v_67, __v_21, __v_22, __v_2, __v_0, __v_20, __v_68, __v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(161, &__args[..])?) { [G::ZERO; OUT_161] } else { let (__hash, __hit) = record.function_queries[161].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[161].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[161].bump_multiplicity(__i); } let __ret: [G; OUT_161] = unsafe { *(record.function_queries[161].output_at(__i).as_ptr() as *const [G; OUT_161]) }; __ret }, _ => { aiur_fn_161::(__args, __hash, record, io_buffer)? }, } } }; let __v_72: G = __r_arr[0]; let __v_73: G = __r_arr[1]; let __v_74: G = __r_arr[2]; let __v_75: G = G::from_u64(0); let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_23, __v_75]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_76: G = __r_arr[0]; let __v_77: G = __r_arr[1]; let __v_78: G = __v_76 - __v_72; let __v_79: G = G::from_bool(__v_78 == G::ZERO); let __v_80: G = __v_77 - __v_73; let __v_81: G = G::from_bool(__v_80 == G::ZERO); let __v_82: G = __v_79 * __v_81; if (__v_82 != __v_71) { return Err(ExecError::AssertEqMismatch { lhs: __v_82.as_canonical_u64(), rhs: __v_71.as_canonical_u64(), msg: None }); } let __ret: [G; OUT_162] = [__v_74]; record.function_queries[162].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_62.as_canonical_u64())); }, } }, } }, } }, } }, } }) } +fn aiur_fn_162(inp: [G; IN_162], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_162], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; match __v_1.as_canonical_u64() { _ => { let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __v_26: G = __v_25 - __v_24; let __v_27: G = G::from_bool(__v_26 == G::ZERO); let __v_28: G = G::from_u64(1); if (__v_27 != __v_28) { return Err(ExecError::AssertEqMismatch { lhs: __v_27.as_canonical_u64(), rhs: __v_28.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_29: G = __r_arr[0]; let __v_30: G = G::from_u64(3); let __r_arr: [G; OUT_148] = { let __args: [G; IN_148] = [__v_8, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(148, &__args[..])?) { [G::ZERO; OUT_148] } else { let (__hash, __hit) = record.function_queries[148].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[148].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[148].bump_multiplicity(__i); } let __ret: [G; OUT_148] = unsafe { *(record.function_queries[148].output_at(__i).as_ptr() as *const [G; OUT_148]) }; __ret }, _ => { aiur_fn_148::(__args, __hash, record, io_buffer)? }, } } }; let __v_31: G = __r_arr[0]; let __v_32: G = __v_30 + __v_31; let __v_33: G = __v_29 - __v_32; let __v_34: G = G::from_bool(__v_33 == G::ZERO); if (__v_34 != __v_28) { return Err(ExecError::AssertEqMismatch { lhs: __v_34.as_canonical_u64(), rhs: __v_28.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_145] = { let __args: [G; IN_145] = [__v_15, __v_19, __v_18, __v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(145, &__args[..])?) { [G::ZERO; OUT_145] } else { let (__hash, __hit) = record.function_queries[145].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[145].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[145].bump_multiplicity(__i); } let __ret: [G; OUT_145] = unsafe { *(record.function_queries[145].output_at(__i).as_ptr() as *const [G; OUT_145]) }; __ret }, _ => { aiur_fn_145::(__args, __hash, record, io_buffer)? }, } } }; let __v_35: G = __r_arr[0]; let __v_36: G = G::from_u64(0); let __r_arr: [G; OUT_155] = { let __args: [G; IN_155] = [__v_15, __v_19, __v_18, __v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(155, &__args[..])?) { [G::ZERO; OUT_155] } else { let (__hash, __hit) = record.function_queries[155].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[155].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[155].bump_multiplicity(__i); } let __ret: [G; OUT_155] = unsafe { *(record.function_queries[155].output_at(__i).as_ptr() as *const [G; OUT_155]) }; __ret }, _ => { aiur_fn_155::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_1, __v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_38: G = __r_arr[0]; match __v_38.as_canonical_u64() { _ => { let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_38]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_39: G = __r_arr[0]; let __v_40: G = __v_39 - __v_18; let __v_41: G = G::from_bool(__v_40 == G::ZERO); let __v_42: G = G::from_u64(1); if (__v_41 != __v_42) { return Err(ExecError::AssertEqMismatch { lhs: __v_41.as_canonical_u64(), rhs: __v_42.as_canonical_u64(), msg: None }); } let __v_43: G = G::from_u64(0); let __r_arr: [G; OUT_151] = { let __args: [G; IN_151] = [__v_35, __v_0, __v_20, __v_19, __v_43, __v_18, __v_15, __v_16, __v_17, __v_38, __v_5, __v_3, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(151, &__args[..])?) { [G::ZERO; OUT_151] } else { let (__hash, __hit) = record.function_queries[151].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[151].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[151].bump_multiplicity(__i); } let __ret: [G; OUT_151] = unsafe { *(record.function_queries[151].output_at(__i).as_ptr() as *const [G; OUT_151]) }; __ret }, _ => { aiur_fn_151::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_1, __v_42]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_45: G = __r_arr[0]; match __v_45.as_canonical_u64() { _ => { let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_45]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_46: G = __r_arr[0]; let __v_47: G = __v_46 - __v_18; let __v_48: G = G::from_bool(__v_47 == G::ZERO); let __v_49: G = G::from_u64(1); if (__v_48 != __v_49) { return Err(ExecError::AssertEqMismatch { lhs: __v_48.as_canonical_u64(), rhs: __v_49.as_canonical_u64(), msg: None }); } let __v_50: G = G::from_u64(0); let __r_arr: [G; OUT_151] = { let __args: [G; IN_151] = [__v_44, __v_0, __v_20, __v_19, __v_50, __v_18, __v_15, __v_16, __v_17, __v_45, __v_6, __v_3, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(151, &__args[..])?) { [G::ZERO; OUT_151] } else { let (__hash, __hit) = record.function_queries[151].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[151].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[151].bump_multiplicity(__i); } let __ret: [G; OUT_151] = unsafe { *(record.function_queries[151].output_at(__i).as_ptr() as *const [G; OUT_151]) }; __ret }, _ => { aiur_fn_151::(__args, __hash, record, io_buffer)? }, } } }; let __v_51: G = __r_arr[0]; let __v_52: G = G::from_u64(2); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_1, __v_52]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; match __v_53.as_canonical_u64() { _ => { let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_53]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __v_55: G = __v_54 - __v_18; let __v_56: G = G::from_bool(__v_55 == G::ZERO); let __v_57: G = G::from_u64(1); if (__v_56 != __v_57) { return Err(ExecError::AssertEqMismatch { lhs: __v_56.as_canonical_u64(), rhs: __v_57.as_canonical_u64(), msg: None }); } let __v_58: G = G::from_u64(0); let __r_arr: [G; OUT_152] = { let __args: [G; IN_152] = [__v_51, __v_0, __v_20, __v_19, __v_58, __v_18, __v_15, __v_16, __v_17, __v_53, __v_7, __v_3, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(152, &__args[..])?) { [G::ZERO; OUT_152] } else { let (__hash, __hit) = record.function_queries[152].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[152].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[152].bump_multiplicity(__i); } let __ret: [G; OUT_152] = unsafe { *(record.function_queries[152].output_at(__i).as_ptr() as *const [G; OUT_152]) }; __ret }, _ => { aiur_fn_152::(__args, __hash, record, io_buffer)? }, } } }; let __v_59: G = __r_arr[0]; let __r_arr: [G; OUT_154] = { let __args: [G; IN_154] = [__v_59, __v_1, __v_13, __v_8, __v_9, __v_14, __v_15, __v_18, __v_0, __v_20, __v_19, __v_16, __v_17, __v_3, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(154, &__args[..])?) { [G::ZERO; OUT_154] } else { let (__hash, __hit) = record.function_queries[154].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[154].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[154].bump_multiplicity(__i); } let __ret: [G; OUT_154] = unsafe { *(record.function_queries[154].output_at(__i).as_ptr() as *const [G; OUT_154]) }; __ret }, _ => { aiur_fn_154::(__args, __hash, record, io_buffer)? }, } } }; let __v_60: G = __r_arr[0]; let __r_arr: [G; OUT_147] = { let __args: [G; IN_147] = [__v_60, __v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(147, &__args[..])?) { [G::ZERO; OUT_147] } else { let (__hash, __hit) = record.function_queries[147].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[147].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[147].bump_multiplicity(__i); } let __ret: [G; OUT_147] = unsafe { *(record.function_queries[147].output_at(__i).as_ptr() as *const [G; OUT_147]) }; __ret }, _ => { aiur_fn_147::(__args, __hash, record, io_buffer)? }, } } }; let __v_61: G = __r_arr[0]; let __loaded: [G; 7] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(4).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(7))?; let __ptr_u64 = __v_60.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 7 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 7] = __args[..7].try_into().unwrap(); __arr }; let __v_62: G = __loaded[0]; let __v_63: G = __loaded[1]; let __v_64: G = __loaded[2]; let __v_65: G = __loaded[3]; let __v_66: G = __loaded[4]; let __v_67: G = __loaded[5]; let __v_68: G = __loaded[6]; match __v_62.as_canonical_u64() { 0u64 => { match __v_63.as_canonical_u64() { _ => { let __v_69: G = __v_63 - __v_20; let __v_70: G = G::from_bool(__v_69 == G::ZERO); let __v_71: G = G::from_u64(1); if (__v_70 != __v_71) { return Err(ExecError::AssertEqMismatch { lhs: __v_70.as_canonical_u64(), rhs: __v_71.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_161] = { let __args: [G; IN_161] = [__v_66, __v_67, __v_21, __v_22, __v_2, __v_0, __v_20, __v_68, __v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(161, &__args[..])?) { [G::ZERO; OUT_161] } else { let (__hash, __hit) = record.function_queries[161].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[161].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[161].bump_multiplicity(__i); } let __ret: [G; OUT_161] = unsafe { *(record.function_queries[161].output_at(__i).as_ptr() as *const [G; OUT_161]) }; __ret }, _ => { aiur_fn_161::(__args, __hash, record, io_buffer)? }, } } }; let __v_72: G = __r_arr[0]; let __v_73: G = __r_arr[1]; let __v_74: G = __r_arr[2]; let __v_75: G = G::from_u64(0); let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_23, __v_75]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_76: G = __r_arr[0]; let __v_77: G = __r_arr[1]; let __v_78: G = __v_76 - __v_72; let __v_79: G = G::from_bool(__v_78 == G::ZERO); let __v_80: G = __v_77 - __v_73; let __v_81: G = G::from_bool(__v_80 == G::ZERO); let __v_82: G = __v_79 * __v_81; if (__v_82 != __v_71) { return Err(ExecError::AssertEqMismatch { lhs: __v_82.as_canonical_u64(), rhs: __v_71.as_canonical_u64(), msg: None }); } let __ret: [G; OUT_162] = [__v_74]; record.function_queries[162].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_62.as_canonical_u64())); }, } }, } }, } }, } }, } }) } const INPUT_SIZE_163: usize = 25; const IN_163: usize = 25; const OUT_163: usize = 1; @@ -672,11 +672,11 @@ fn aiur_fn_164(inp: [G; IN_164], input_hash: u64, rec const INPUT_SIZE_165: usize = 2; const IN_165: usize = 2; const OUT_165: usize = 1; -fn aiur_fn_165(inp: [G; IN_165], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_165], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; let __v_5: G = __loaded[3]; match __v_2.as_canonical_u64() { 1u64 => { let __v_6: G = G::from_u64(1); let __v_7: G = { let __values: [G; 3] = [__v_6, __v_6, __v_6]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_165] = [__v_7]; record.function_queries[165].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_3.as_canonical_u64() { _ => { let __v_6: G = G::from_u64(0); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_3, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __r_arr: [G; OUT_165] = { let __args: [G; IN_165] = [__v_5, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(165, &__args[..])?) { [G::ZERO; OUT_165] } else { let (__hash, __hit) = record.function_queries[165].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[165].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[165].bump_multiplicity(__i); } let __ret: [G; OUT_165] = unsafe { *(record.function_queries[165].output_at(__i).as_ptr() as *const [G; OUT_165]) }; __ret }, _ => { aiur_fn_165::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __v_9: G = { let __values: [G; 3] = [__v_6, __v_7, __v_8]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_165] = [__v_9]; record.function_queries[165].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } +fn aiur_fn_165(inp: [G; IN_165], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_165], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; let __v_5: G = __loaded[3]; match __v_2.as_canonical_u64() { 1u64 => { let __v_6: G = G::from_u64(1); let __v_7: G = { let __values: [G; 3] = [__v_6, __v_6, __v_6]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_165] = [__v_7]; record.function_queries[165].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_3.as_canonical_u64() { _ => { let __v_6: G = G::from_u64(0); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_3, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __r_arr: [G; OUT_165] = { let __args: [G; IN_165] = [__v_5, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(165, &__args[..])?) { [G::ZERO; OUT_165] } else { let (__hash, __hit) = record.function_queries[165].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[165].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[165].bump_multiplicity(__i); } let __ret: [G; OUT_165] = unsafe { *(record.function_queries[165].output_at(__i).as_ptr() as *const [G; OUT_165]) }; __ret }, _ => { aiur_fn_165::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __v_9: G = { let __values: [G; 3] = [__v_6, __v_7, __v_8]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_165] = [__v_9]; record.function_queries[165].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } const INPUT_SIZE_166: usize = 2; const IN_166: usize = 2; const OUT_166: usize = 1; -fn aiur_fn_166(inp: [G; IN_166], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_166], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 5] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(2).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(5))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 5 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 5] = __args[..5].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; let __v_5: G = __loaded[3]; let __v_6: G = __loaded[4]; match __v_2.as_canonical_u64() { 1u64 => { let __v_7: G = G::from_u64(1); let __v_8: G = { let __values: [G; 4] = [__v_7, __v_7, __v_7, __v_7]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_166] = [__v_8]; record.function_queries[166].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_3.as_canonical_u64() { _ => { let __v_7: G = G::from_u64(0); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_4, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __r_arr: [G; OUT_166] = { let __args: [G; IN_166] = [__v_6, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(166, &__args[..])?) { [G::ZERO; OUT_166] } else { let (__hash, __hit) = record.function_queries[166].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[166].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[166].bump_multiplicity(__i); } let __ret: [G; OUT_166] = unsafe { *(record.function_queries[166].output_at(__i).as_ptr() as *const [G; OUT_166]) }; __ret }, _ => { aiur_fn_166::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = { let __values: [G; 4] = [__v_7, __v_3, __v_8, __v_9]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_166] = [__v_10]; record.function_queries[166].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } +fn aiur_fn_166(inp: [G; IN_166], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_166], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 5] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(2).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(5))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 5 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 5] = __args[..5].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; let __v_5: G = __loaded[3]; let __v_6: G = __loaded[4]; match __v_2.as_canonical_u64() { 1u64 => { let __v_7: G = G::from_u64(1); let __v_8: G = { let __values: [G; 4] = [__v_7, __v_7, __v_7, __v_7]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_166] = [__v_8]; record.function_queries[166].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_3.as_canonical_u64() { _ => { let __v_7: G = G::from_u64(0); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_4, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __r_arr: [G; OUT_166] = { let __args: [G; IN_166] = [__v_6, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(166, &__args[..])?) { [G::ZERO; OUT_166] } else { let (__hash, __hit) = record.function_queries[166].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[166].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[166].bump_multiplicity(__i); } let __ret: [G; OUT_166] = unsafe { *(record.function_queries[166].output_at(__i).as_ptr() as *const [G; OUT_166]) }; __ret }, _ => { aiur_fn_166::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = { let __values: [G; 4] = [__v_7, __v_3, __v_8, __v_9]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_166] = [__v_10]; record.function_queries[166].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } const INPUT_SIZE_167: usize = 4; const IN_167: usize = 4; const OUT_167: usize = 1; @@ -684,23 +684,23 @@ fn aiur_fn_167(inp: [G; IN_167], input_hash: u64, rec const INPUT_SIZE_168: usize = 13; const IN_168: usize = 13; const OUT_168: usize = 1; -fn aiur_fn_168(inp: [G; IN_168], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_168], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = G::from_u64(0); let __r_arr: [G; OUT_155] = { let __args: [G; IN_155] = [__v_9, __v_11, __v_10, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(155, &__args[..])?) { [G::ZERO; OUT_155] } else { let (__hash, __hit) = record.function_queries[155].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[155].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[155].bump_multiplicity(__i); } let __ret: [G; OUT_155] = unsafe { *(record.function_queries[155].output_at(__i).as_ptr() as *const [G; OUT_155]) }; __ret }, _ => { aiur_fn_155::(__args, __hash, record, io_buffer)? }, } } }; let __v_14: G = __r_arr[0]; let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_0, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __r_arr[1]; match __v_15.as_canonical_u64() { _ => { let __v_17: G = G::from_u64(1); let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_0, __v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __v_19: G = __r_arr[1]; match __v_18.as_canonical_u64() { _ => { let __v_20: G = G::from_u64(2); let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_0, __v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; match __v_21.as_canonical_u64() { _ => { let __r_arr: [G; OUT_131] = { let __args: [G; IN_131] = [__v_2, __v_15, __v_14, __v_1, __v_16, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(131, &__args[..])?) { [G::ZERO; OUT_131] } else { let (__hash, __hit) = record.function_queries[131].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[131].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[131].bump_multiplicity(__i); } let __ret: [G; OUT_131] = unsafe { *(record.function_queries[131].output_at(__i).as_ptr() as *const [G; OUT_131]) }; __ret }, _ => { aiur_fn_131::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __v_24: G = G::from_u64(1); if (__v_23 != __v_24) { return Err(ExecError::AssertEqMismatch { lhs: __v_23.as_canonical_u64(), rhs: __v_24.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_131] = { let __args: [G; IN_131] = [__v_3, __v_18, __v_14, __v_1, __v_19, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(131, &__args[..])?) { [G::ZERO; OUT_131] } else { let (__hash, __hit) = record.function_queries[131].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[131].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[131].bump_multiplicity(__i); } let __ret: [G; OUT_131] = unsafe { *(record.function_queries[131].output_at(__i).as_ptr() as *const [G; OUT_131]) }; __ret }, _ => { aiur_fn_131::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; if (__v_25 != __v_24) { return Err(ExecError::AssertEqMismatch { lhs: __v_25.as_canonical_u64(), rhs: __v_24.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_131] = { let __args: [G; IN_131] = [__v_4, __v_21, __v_14, __v_1, __v_22, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(131, &__args[..])?) { [G::ZERO; OUT_131] } else { let (__hash, __hit) = record.function_queries[131].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[131].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[131].bump_multiplicity(__i); } let __ret: [G; OUT_131] = unsafe { *(record.function_queries[131].output_at(__i).as_ptr() as *const [G; OUT_131]) }; __ret }, _ => { aiur_fn_131::(__args, __hash, record, io_buffer)? }, } } }; let __v_26: G = __r_arr[0]; if (__v_26 != __v_24) { return Err(ExecError::AssertEqMismatch { lhs: __v_26.as_canonical_u64(), rhs: __v_24.as_canonical_u64(), msg: None }); } match __v_6.as_canonical_u64() { 0u64 => { let __v_27: G = G::from_u64(1); let __ret: [G; OUT_168] = [__v_27]; record.function_queries[168].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_27: G = G::from_u64(3); let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_0, __v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_28: G = __r_arr[0]; let __v_29: G = __r_arr[1]; match __v_28.as_canonical_u64() { _ => { let __v_30: G = G::from_u64(0); let __r_arr: [G; OUT_157] = { let __args: [G; IN_157] = [__v_9, __v_11, __v_8, __v_10, __v_30]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(157, &__args[..])?) { [G::ZERO; OUT_157] } else { let (__hash, __hit) = record.function_queries[157].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[157].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[157].bump_multiplicity(__i); } let __ret: [G; OUT_157] = unsafe { *(record.function_queries[157].output_at(__i).as_ptr() as *const [G; OUT_157]) }; __ret }, _ => { aiur_fn_157::(__args, __hash, record, io_buffer)? }, } } }; let __v_31: G = __r_arr[0]; let __r_arr: [G; OUT_156] = { let __args: [G; IN_156] = [__v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(156, &__args[..])?) { [G::ZERO; OUT_156] } else { let (__hash, __hit) = record.function_queries[156].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[156].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[156].bump_multiplicity(__i); } let __ret: [G; OUT_156] = unsafe { *(record.function_queries[156].output_at(__i).as_ptr() as *const [G; OUT_156]) }; __ret }, _ => { aiur_fn_156::(__args, __hash, record, io_buffer)? }, } } }; let __v_32: G = __r_arr[0]; let __v_33: G = __v_12 - __v_32; let __r_arr: [G; OUT_169] = { let __args: [G; IN_169] = [__v_1, __v_33]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(169, &__args[..])?) { [G::ZERO; OUT_169] } else { let (__hash, __hit) = record.function_queries[169].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[169].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[169].bump_multiplicity(__i); } let __ret: [G; OUT_169] = unsafe { *(record.function_queries[169].output_at(__i).as_ptr() as *const [G; OUT_169]) }; __ret }, _ => { aiur_fn_169::(__args, __hash, record, io_buffer)? }, } } }; let __v_34: G = __r_arr[0]; let __r_arr: [G; OUT_131] = { let __args: [G; IN_131] = [__v_5, __v_28, __v_31, __v_34, __v_29, __v_32]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(131, &__args[..])?) { [G::ZERO; OUT_131] } else { let (__hash, __hit) = record.function_queries[131].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[131].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[131].bump_multiplicity(__i); } let __ret: [G; OUT_131] = unsafe { *(record.function_queries[131].output_at(__i).as_ptr() as *const [G; OUT_131]) }; __ret }, _ => { aiur_fn_131::(__args, __hash, record, io_buffer)? }, } } }; let __v_35: G = __r_arr[0]; let __ret: [G; OUT_168] = [__v_35]; record.function_queries[168].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_6.as_canonical_u64())); }, } }, } }, } }, } }) } +fn aiur_fn_168(inp: [G; IN_168], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_168], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = G::from_u64(0); let __r_arr: [G; OUT_155] = { let __args: [G; IN_155] = [__v_9, __v_11, __v_10, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(155, &__args[..])?) { [G::ZERO; OUT_155] } else { let (__hash, __hit) = record.function_queries[155].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[155].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[155].bump_multiplicity(__i); } let __ret: [G; OUT_155] = unsafe { *(record.function_queries[155].output_at(__i).as_ptr() as *const [G; OUT_155]) }; __ret }, _ => { aiur_fn_155::(__args, __hash, record, io_buffer)? }, } } }; let __v_14: G = __r_arr[0]; let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_0, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __r_arr[1]; match __v_15.as_canonical_u64() { _ => { let __v_17: G = G::from_u64(1); let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_0, __v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __v_19: G = __r_arr[1]; match __v_18.as_canonical_u64() { _ => { let __v_20: G = G::from_u64(2); let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_0, __v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; match __v_21.as_canonical_u64() { _ => { let __r_arr: [G; OUT_131] = { let __args: [G; IN_131] = [__v_2, __v_15, __v_14, __v_1, __v_16, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(131, &__args[..])?) { [G::ZERO; OUT_131] } else { let (__hash, __hit) = record.function_queries[131].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[131].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[131].bump_multiplicity(__i); } let __ret: [G; OUT_131] = unsafe { *(record.function_queries[131].output_at(__i).as_ptr() as *const [G; OUT_131]) }; __ret }, _ => { aiur_fn_131::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __v_24: G = G::from_u64(1); if (__v_23 != __v_24) { return Err(ExecError::AssertEqMismatch { lhs: __v_23.as_canonical_u64(), rhs: __v_24.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_131] = { let __args: [G; IN_131] = [__v_3, __v_18, __v_14, __v_1, __v_19, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(131, &__args[..])?) { [G::ZERO; OUT_131] } else { let (__hash, __hit) = record.function_queries[131].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[131].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[131].bump_multiplicity(__i); } let __ret: [G; OUT_131] = unsafe { *(record.function_queries[131].output_at(__i).as_ptr() as *const [G; OUT_131]) }; __ret }, _ => { aiur_fn_131::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; if (__v_25 != __v_24) { return Err(ExecError::AssertEqMismatch { lhs: __v_25.as_canonical_u64(), rhs: __v_24.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_131] = { let __args: [G; IN_131] = [__v_4, __v_21, __v_14, __v_1, __v_22, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(131, &__args[..])?) { [G::ZERO; OUT_131] } else { let (__hash, __hit) = record.function_queries[131].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[131].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[131].bump_multiplicity(__i); } let __ret: [G; OUT_131] = unsafe { *(record.function_queries[131].output_at(__i).as_ptr() as *const [G; OUT_131]) }; __ret }, _ => { aiur_fn_131::(__args, __hash, record, io_buffer)? }, } } }; let __v_26: G = __r_arr[0]; if (__v_26 != __v_24) { return Err(ExecError::AssertEqMismatch { lhs: __v_26.as_canonical_u64(), rhs: __v_24.as_canonical_u64(), msg: None }); } match __v_6.as_canonical_u64() { 0u64 => { let __v_27: G = G::from_u64(1); let __ret: [G; OUT_168] = [__v_27]; record.function_queries[168].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_27: G = G::from_u64(3); let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_0, __v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_28: G = __r_arr[0]; let __v_29: G = __r_arr[1]; match __v_28.as_canonical_u64() { _ => { let __v_30: G = G::from_u64(0); let __r_arr: [G; OUT_157] = { let __args: [G; IN_157] = [__v_9, __v_11, __v_8, __v_10, __v_30]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(157, &__args[..])?) { [G::ZERO; OUT_157] } else { let (__hash, __hit) = record.function_queries[157].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[157].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[157].bump_multiplicity(__i); } let __ret: [G; OUT_157] = unsafe { *(record.function_queries[157].output_at(__i).as_ptr() as *const [G; OUT_157]) }; __ret }, _ => { aiur_fn_157::(__args, __hash, record, io_buffer)? }, } } }; let __v_31: G = __r_arr[0]; let __r_arr: [G; OUT_156] = { let __args: [G; IN_156] = [__v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(156, &__args[..])?) { [G::ZERO; OUT_156] } else { let (__hash, __hit) = record.function_queries[156].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[156].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[156].bump_multiplicity(__i); } let __ret: [G; OUT_156] = unsafe { *(record.function_queries[156].output_at(__i).as_ptr() as *const [G; OUT_156]) }; __ret }, _ => { aiur_fn_156::(__args, __hash, record, io_buffer)? }, } } }; let __v_32: G = __r_arr[0]; let __v_33: G = __v_12 - __v_32; let __r_arr: [G; OUT_169] = { let __args: [G; IN_169] = [__v_1, __v_33]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(169, &__args[..])?) { [G::ZERO; OUT_169] } else { let (__hash, __hit) = record.function_queries[169].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[169].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[169].bump_multiplicity(__i); } let __ret: [G; OUT_169] = unsafe { *(record.function_queries[169].output_at(__i).as_ptr() as *const [G; OUT_169]) }; __ret }, _ => { aiur_fn_169::(__args, __hash, record, io_buffer)? }, } } }; let __v_34: G = __r_arr[0]; let __r_arr: [G; OUT_131] = { let __args: [G; IN_131] = [__v_5, __v_28, __v_31, __v_34, __v_29, __v_32]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(131, &__args[..])?) { [G::ZERO; OUT_131] } else { let (__hash, __hit) = record.function_queries[131].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[131].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[131].bump_multiplicity(__i); } let __ret: [G; OUT_131] = unsafe { *(record.function_queries[131].output_at(__i).as_ptr() as *const [G; OUT_131]) }; __ret }, _ => { aiur_fn_131::(__args, __hash, record, io_buffer)? }, } } }; let __v_35: G = __r_arr[0]; let __ret: [G; OUT_168] = [__v_35]; record.function_queries[168].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_6.as_canonical_u64())); }, } }, } }, } }, } }) } const INPUT_SIZE_169: usize = 2; const IN_169: usize = 2; const OUT_169: usize = 1; -fn aiur_fn_169(inp: [G; IN_169], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_169], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; match __v_2.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_169] = [__v_0]; record.function_queries[169].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_5: G = G::from_u64(0); let __r_arr: [G; OUT_324] = { let __args: [G; IN_324] = [__v_3, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(324, &__args[..])?) { [G::ZERO; OUT_324] } else { let (__hash, __hit) = record.function_queries[324].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[324].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[324].bump_multiplicity(__i); } let __ret: [G; OUT_324] = unsafe { *(record.function_queries[324].output_at(__i).as_ptr() as *const [G; OUT_324]) }; __ret }, _ => { aiur_fn_324::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __r_arr: [G; OUT_169] = { let __args: [G; IN_169] = [__v_4, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(169, &__args[..])?) { [G::ZERO; OUT_169] } else { let (__hash, __hit) = record.function_queries[169].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[169].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[169].bump_multiplicity(__i); } let __ret: [G; OUT_169] = unsafe { *(record.function_queries[169].output_at(__i).as_ptr() as *const [G; OUT_169]) }; __ret }, _ => { aiur_fn_169::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = { let __values: [G; 3] = [__v_5, __v_6, __v_7]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_169] = [__v_8]; record.function_queries[169].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } +fn aiur_fn_169(inp: [G; IN_169], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_169], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; match __v_2.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_169] = [__v_0]; record.function_queries[169].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_5: G = G::from_u64(0); let __r_arr: [G; OUT_326] = { let __args: [G; IN_326] = [__v_3, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(326, &__args[..])?) { [G::ZERO; OUT_326] } else { let (__hash, __hit) = record.function_queries[326].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[326].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[326].bump_multiplicity(__i); } let __ret: [G; OUT_326] = unsafe { *(record.function_queries[326].output_at(__i).as_ptr() as *const [G; OUT_326]) }; __ret }, _ => { aiur_fn_326::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __r_arr: [G; OUT_169] = { let __args: [G; IN_169] = [__v_4, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(169, &__args[..])?) { [G::ZERO; OUT_169] } else { let (__hash, __hit) = record.function_queries[169].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[169].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[169].bump_multiplicity(__i); } let __ret: [G; OUT_169] = unsafe { *(record.function_queries[169].output_at(__i).as_ptr() as *const [G; OUT_169]) }; __ret }, _ => { aiur_fn_169::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = { let __values: [G; 3] = [__v_5, __v_6, __v_7]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_169] = [__v_8]; record.function_queries[169].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } const INPUT_SIZE_170: usize = 2; const IN_170: usize = 2; const OUT_170: usize = 1; -fn aiur_fn_170(inp: [G; IN_170], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_170], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; match __v_2.as_canonical_u64() { 1u64 => { let __v_5: G = G::from_u64(1); let __v_6: G = { let __values: [G; 3] = [__v_5, __v_5, __v_5]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_170] = [__v_6]; record.function_queries[170].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_5: G = G::from_u64(0); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_3, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = G::from_u64(1); let __v_8: G = { let __values: [G; 3] = [__v_7, __v_7, __v_7]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_9: G = { let __values: [G; 3] = [__v_5, __v_6, __v_8]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_170] = { let __args: [G; IN_170] = [__v_4, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(170, &__args[..])?) { [G::ZERO; OUT_170] } else { let (__hash, __hit) = record.function_queries[170].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[170].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[170].bump_multiplicity(__i); } let __ret: [G; OUT_170] = unsafe { *(record.function_queries[170].output_at(__i).as_ptr() as *const [G; OUT_170]) }; __ret }, _ => { aiur_fn_170::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = { let __values: [G; 3] = [__v_5, __v_9, __v_10]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_170] = [__v_11]; record.function_queries[170].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } +fn aiur_fn_170(inp: [G; IN_170], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_170], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; match __v_2.as_canonical_u64() { 1u64 => { let __v_5: G = G::from_u64(1); let __v_6: G = { let __values: [G; 3] = [__v_5, __v_5, __v_5]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_170] = [__v_6]; record.function_queries[170].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_5: G = G::from_u64(0); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_3, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = G::from_u64(1); let __v_8: G = { let __values: [G; 3] = [__v_7, __v_7, __v_7]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_9: G = { let __values: [G; 3] = [__v_5, __v_6, __v_8]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_170] = { let __args: [G; IN_170] = [__v_4, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(170, &__args[..])?) { [G::ZERO; OUT_170] } else { let (__hash, __hit) = record.function_queries[170].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[170].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[170].bump_multiplicity(__i); } let __ret: [G; OUT_170] = unsafe { *(record.function_queries[170].output_at(__i).as_ptr() as *const [G; OUT_170]) }; __ret }, _ => { aiur_fn_170::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = { let __values: [G; 3] = [__v_5, __v_9, __v_10]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_170] = [__v_11]; record.function_queries[170].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } const INPUT_SIZE_171: usize = 6; const IN_171: usize = 6; const OUT_171: usize = 1; -fn aiur_fn_171(inp: [G; IN_171], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_171], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __loaded: [G; 5] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(2).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(5))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 5 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 5] = __args[..5].try_into().unwrap(); __arr }; let __v_6: G = __loaded[0]; let __v_7: G = __loaded[1]; let __v_8: G = __loaded[2]; let __v_9: G = __loaded[3]; let __v_10: G = __loaded[4]; match __v_6.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_bool(__v_11 == G::ZERO); let __ret: [G; OUT_171] = [__v_12]; record.function_queries[171].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_1.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_11: G = __loaded[0]; let __v_12: G = __loaded[1]; let __v_13: G = __loaded[2]; match __v_11.as_canonical_u64() { 0u64 => { match __v_7.as_canonical_u64() { _ => { let __v_14: G = G::from_u64(1); if (__v_7 != __v_14) { return Err(ExecError::AssertEqMismatch { lhs: __v_7.as_canonical_u64(), rhs: __v_14.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; if (__v_15 != __v_16) { return Err(ExecError::AssertEqMismatch { lhs: __v_15.as_canonical_u64(), rhs: __v_16.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_169] = { let __args: [G; IN_169] = [__v_3, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(169, &__args[..])?) { [G::ZERO; OUT_169] } else { let (__hash, __hit) = record.function_queries[169].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[169].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[169].bump_multiplicity(__i); } let __ret: [G; OUT_169] = unsafe { *(record.function_queries[169].output_at(__i).as_ptr() as *const [G; OUT_169]) }; __ret }, _ => { aiur_fn_169::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = __v_5 - __v_14; let __r_arr: [G; OUT_170] = { let __args: [G; IN_170] = [__v_2, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(170, &__args[..])?) { [G::ZERO; OUT_170] } else { let (__hash, __hit) = record.function_queries[170].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[170].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[170].bump_multiplicity(__i); } let __ret: [G; OUT_170] = unsafe { *(record.function_queries[170].output_at(__i).as_ptr() as *const [G; OUT_170]) }; __ret }, _ => { aiur_fn_170::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = G::from_u64(0); let __v_21: G = { let __values: [G; 3] = [__v_14, __v_14, __v_14]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_22: G = { let __values: [G; 3] = [__v_20, __v_18, __v_21]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_131] = { let __args: [G; IN_131] = [__v_12, __v_19, __v_22, __v_17, __v_9, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(131, &__args[..])?) { [G::ZERO; OUT_131] } else { let (__hash, __hit) = record.function_queries[131].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[131].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[131].bump_multiplicity(__i); } let __ret: [G; OUT_131] = unsafe { *(record.function_queries[131].output_at(__i).as_ptr() as *const [G; OUT_131]) }; __ret }, _ => { aiur_fn_131::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; if (__v_23 != __v_14) { return Err(ExecError::AssertEqMismatch { lhs: __v_23.as_canonical_u64(), rhs: __v_14.as_canonical_u64(), msg: None }); } let __v_24: G = __v_4 + __v_14; let __r_arr: [G; OUT_171] = { let __args: [G; IN_171] = [__v_10, __v_13, __v_2, __v_17, __v_24, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(171, &__args[..])?) { [G::ZERO; OUT_171] } else { let (__hash, __hit) = record.function_queries[171].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[171].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[171].bump_multiplicity(__i); } let __ret: [G; OUT_171] = unsafe { *(record.function_queries[171].output_at(__i).as_ptr() as *const [G; OUT_171]) }; __ret }, _ => { aiur_fn_171::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __ret: [G; OUT_171] = [__v_25]; record.function_queries[171].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_11.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_6.as_canonical_u64())); }, } }) } +fn aiur_fn_171(inp: [G; IN_171], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_171], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __loaded: [G; 5] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(2).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(5))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 5 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 5] = __args[..5].try_into().unwrap(); __arr }; let __v_6: G = __loaded[0]; let __v_7: G = __loaded[1]; let __v_8: G = __loaded[2]; let __v_9: G = __loaded[3]; let __v_10: G = __loaded[4]; match __v_6.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_bool(__v_11 == G::ZERO); let __ret: [G; OUT_171] = [__v_12]; record.function_queries[171].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_1.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_11: G = __loaded[0]; let __v_12: G = __loaded[1]; let __v_13: G = __loaded[2]; match __v_11.as_canonical_u64() { 0u64 => { match __v_7.as_canonical_u64() { _ => { let __v_14: G = G::from_u64(1); if (__v_7 != __v_14) { return Err(ExecError::AssertEqMismatch { lhs: __v_7.as_canonical_u64(), rhs: __v_14.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; if (__v_15 != __v_16) { return Err(ExecError::AssertEqMismatch { lhs: __v_15.as_canonical_u64(), rhs: __v_16.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_169] = { let __args: [G; IN_169] = [__v_3, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(169, &__args[..])?) { [G::ZERO; OUT_169] } else { let (__hash, __hit) = record.function_queries[169].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[169].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[169].bump_multiplicity(__i); } let __ret: [G; OUT_169] = unsafe { *(record.function_queries[169].output_at(__i).as_ptr() as *const [G; OUT_169]) }; __ret }, _ => { aiur_fn_169::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = __v_5 - __v_14; let __r_arr: [G; OUT_170] = { let __args: [G; IN_170] = [__v_2, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(170, &__args[..])?) { [G::ZERO; OUT_170] } else { let (__hash, __hit) = record.function_queries[170].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[170].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[170].bump_multiplicity(__i); } let __ret: [G; OUT_170] = unsafe { *(record.function_queries[170].output_at(__i).as_ptr() as *const [G; OUT_170]) }; __ret }, _ => { aiur_fn_170::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = G::from_u64(0); let __v_21: G = { let __values: [G; 3] = [__v_14, __v_14, __v_14]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_22: G = { let __values: [G; 3] = [__v_20, __v_18, __v_21]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_131] = { let __args: [G; IN_131] = [__v_12, __v_19, __v_22, __v_17, __v_9, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(131, &__args[..])?) { [G::ZERO; OUT_131] } else { let (__hash, __hit) = record.function_queries[131].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[131].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[131].bump_multiplicity(__i); } let __ret: [G; OUT_131] = unsafe { *(record.function_queries[131].output_at(__i).as_ptr() as *const [G; OUT_131]) }; __ret }, _ => { aiur_fn_131::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; if (__v_23 != __v_14) { return Err(ExecError::AssertEqMismatch { lhs: __v_23.as_canonical_u64(), rhs: __v_14.as_canonical_u64(), msg: None }); } let __v_24: G = __v_4 + __v_14; let __r_arr: [G; OUT_171] = { let __args: [G; IN_171] = [__v_10, __v_13, __v_2, __v_17, __v_24, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(171, &__args[..])?) { [G::ZERO; OUT_171] } else { let (__hash, __hit) = record.function_queries[171].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[171].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[171].bump_multiplicity(__i); } let __ret: [G; OUT_171] = unsafe { *(record.function_queries[171].output_at(__i).as_ptr() as *const [G; OUT_171]) }; __ret }, _ => { aiur_fn_171::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __ret: [G; OUT_171] = [__v_25]; record.function_queries[171].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_11.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_6.as_canonical_u64())); }, } }) } const INPUT_SIZE_172: usize = 32; const IN_172: usize = 32; const OUT_172: usize = 1; -fn aiur_fn_172(inp: [G; IN_172], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_172], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; match __v_6.as_canonical_u64() { _ => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_32: G = __r_arr[0]; let __r_arr: [G; OUT_317] = { let __args: [G; IN_317] = [__v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(317, &__args[..])?) { [G::ZERO; OUT_317] } else { let (__hash, __hit) = record.function_queries[317].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[317].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[317].bump_multiplicity(__i); } let __ret: [G; OUT_317] = unsafe { *(record.function_queries[317].output_at(__i).as_ptr() as *const [G; OUT_317]) }; __ret }, _ => { aiur_fn_317::(__args, __hash, record, io_buffer)? }, } } }; let __v_33: G = __r_arr[0]; let __v_34: G = __v_33 - __v_32; let __v_35: G = G::from_bool(__v_34 == G::ZERO); let __v_36: G = G::from_u64(1); if (__v_35 != __v_36) { return Err(ExecError::AssertEqMismatch { lhs: __v_35.as_canonical_u64(), rhs: __v_36.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = G::from_u64(3); let __r_arr: [G; OUT_148] = { let __args: [G; IN_148] = [__v_4, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(148, &__args[..])?) { [G::ZERO; OUT_148] } else { let (__hash, __hit) = record.function_queries[148].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[148].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[148].bump_multiplicity(__i); } let __ret: [G; OUT_148] = unsafe { *(record.function_queries[148].output_at(__i).as_ptr() as *const [G; OUT_148]) }; __ret }, _ => { aiur_fn_148::(__args, __hash, record, io_buffer)? }, } } }; let __v_39: G = __r_arr[0]; let __v_40: G = __v_38 + __v_39; if (__v_37 != __v_40) { return Err(ExecError::AssertEqMismatch { lhs: __v_37.as_canonical_u64(), rhs: __v_40.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_41: G = __r_arr[0]; let __v_42: G = __v_41 - __v_32; let __v_43: G = G::from_bool(__v_42 == G::ZERO); if (__v_43 != __v_36) { return Err(ExecError::AssertEqMismatch { lhs: __v_43.as_canonical_u64(), rhs: __v_36.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; if (__v_44 != __v_36) { return Err(ExecError::AssertEqMismatch { lhs: __v_44.as_canonical_u64(), rhs: __v_36.as_canonical_u64(), msg: None }); } let __v_45: G = { let __values: [G; 3] = [__v_36, __v_36, __v_36]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __mc_out___mc_0: [G; 1] = '__mc_0: { match __v_4.as_canonical_u64() { 0u64 => { break '__mc_0 [__v_45]; }, 1u64 => { let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_5, __v_45]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_46: G = __r_arr[0]; break '__mc_0 [__v_46]; }, _ => { return Err(ExecError::MatchNoCase(__v_4.as_canonical_u64())); }, } }; let __v_46: G = __mc_out___mc_0[0]; let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_3, __v_46]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_47: G = __r_arr[0]; let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_2, __v_47]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_48: G = __r_arr[0]; let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_1, __v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_0, __v_49]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_50: G = __r_arr[0]; let __v_51: G = G::from_u64(1); let __v_52: G = { let __values: [G; 3] = [__v_51, __v_51, __v_51]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_180] = { let __args: [G; IN_180] = [__v_50, __v_52]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(180, &__args[..])?) { [G::ZERO; OUT_180] } else { let (__hash, __hit) = record.function_queries[180].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[180].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[180].bump_multiplicity(__i); } let __ret: [G; OUT_180] = unsafe { *(record.function_queries[180].output_at(__i).as_ptr() as *const [G; OUT_180]) }; __ret }, _ => { aiur_fn_180::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; let __v_54: G = __r_arr[1]; let __v_55: G = __r_arr[2]; let __v_56: G = __r_arr[3]; let __v_57: G = __r_arr[4]; let __v_58: G = __r_arr[5]; let __v_59: G = __r_arr[6]; let __v_60: G = __r_arr[7]; let __v_61: G = __r_arr[8]; let __v_62: G = __r_arr[9]; let __v_63: G = __r_arr[10]; let __v_64: G = __r_arr[11]; let __v_65: G = __r_arr[12]; let __v_66: G = __r_arr[13]; let __v_67: G = __r_arr[14]; let __v_68: G = __r_arr[15]; let __v_69: G = __r_arr[16]; let __v_70: G = __r_arr[17]; let __v_71: G = G::from_u64(256); let __v_72: G = __v_71 * __v_54; let __v_73: G = __v_53 + __v_72; let __v_74: G = G::from_u64(65536); let __v_75: G = __v_74 * __v_55; let __v_76: G = __v_73 + __v_75; let __v_77: G = G::from_u64(16777216); let __v_78: G = __v_77 * __v_56; let __v_79: G = __v_76 + __v_78; let __v_80: G = G::from_u64(4294967296); let __v_81: G = __v_80 * __v_57; let __v_82: G = __v_79 + __v_81; let __v_83: G = G::from_u64(1099511627776); let __v_84: G = __v_83 * __v_58; let __v_85: G = __v_82 + __v_84; let __v_86: G = G::from_u64(281474976710656); let __v_87: G = __v_86 * __v_59; let __v_88: G = __v_85 + __v_87; let __v_89: G = G::from_u64(72057594037927936); let __v_90: G = __v_89 * __v_60; let __v_91: G = __v_88 + __v_90; let __v_92: G = __v_71 * __v_62; let __v_93: G = __v_61 + __v_92; let __v_94: G = __v_74 * __v_63; let __v_95: G = __v_93 + __v_94; let __v_96: G = __v_77 * __v_64; let __v_97: G = __v_95 + __v_96; let __v_98: G = __v_80 * __v_65; let __v_99: G = __v_97 + __v_98; let __v_100: G = __v_83 * __v_66; let __v_101: G = __v_99 + __v_100; let __v_102: G = __v_86 * __v_67; let __v_103: G = __v_101 + __v_102; let __v_104: G = __v_89 * __v_68; let __v_105: G = __v_103 + __v_104; let __r_arr: [G; OUT_143] = { let __args: [G; IN_143] = [__v_69, __v_6, __v_7, __v_30]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(143, &__args[..])?) { [G::ZERO; OUT_143] } else { let (__hash, __hit) = record.function_queries[143].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[143].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[143].bump_multiplicity(__i); } let __ret: [G; OUT_143] = unsafe { *(record.function_queries[143].output_at(__i).as_ptr() as *const [G; OUT_143]) }; __ret }, _ => { aiur_fn_143::(__args, __hash, record, io_buffer)? }, } } }; let __v_106: G = __r_arr[0]; let __v_107: G = __r_arr[1]; let __r_arr: [G; OUT_137] = { let __args: [G; IN_137] = [__v_10, __v_52]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(137, &__args[..])?) { [G::ZERO; OUT_137] } else { let (__hash, __hit) = record.function_queries[137].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[137].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[137].bump_multiplicity(__i); } let __ret: [G; OUT_137] = unsafe { *(record.function_queries[137].output_at(__i).as_ptr() as *const [G; OUT_137]) }; __ret }, _ => { aiur_fn_137::(__args, __hash, record, io_buffer)? }, } } }; let __v_108: G = __r_arr[0]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_107, __v_108]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_109: G = __r_arr[0]; let __r_arr: [G; OUT_141] = { let __args: [G; IN_141] = [__v_109, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(141, &__args[..])?) { [G::ZERO; OUT_141] } else { let (__hash, __hit) = record.function_queries[141].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[141].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[141].bump_multiplicity(__i); } let __ret: [G; OUT_141] = unsafe { *(record.function_queries[141].output_at(__i).as_ptr() as *const [G; OUT_141]) }; __ret }, _ => { aiur_fn_141::(__args, __hash, record, io_buffer)? }, } } }; let __v_110: G = __r_arr[0]; let __r_arr: [G; OUT_142] = { let __args: [G; IN_142] = [__v_110, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(142, &__args[..])?) { [G::ZERO; OUT_142] } else { let (__hash, __hit) = record.function_queries[142].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[142].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[142].bump_multiplicity(__i); } let __ret: [G; OUT_142] = unsafe { *(record.function_queries[142].output_at(__i).as_ptr() as *const [G; OUT_142]) }; __ret }, _ => { aiur_fn_142::(__args, __hash, record, io_buffer)? }, } } }; let __v_111: G = __r_arr[0]; let __v_112: G = __r_arr[1]; let __v_113: G = __v_32 + __v_28; let __v_114: G = G::from_u64(33); let __v_115: G = { let __a_val = __v_113.as_canonical_u64(); let __b_val = __v_114.as_canonical_u64(); let __a_u32 = u32::try_from(__a_val).ok().ok_or(ExecError::U32OutOfRange(__a_val))?; let __b_u32 = u32::try_from(__b_val).ok().ok_or(ExecError::U32OutOfRange(__b_val))?; let __result = G::from_bool(__a_u32 < __b_u32); if (!unconstrained) { let __c_u32 = __a_u32.wrapping_sub(__b_u32); for __word in [__a_u32, __c_u32, __b_u32] { record.bytes2_queries.bump_u16_range_check((__word & 65535) as u16); record.bytes2_queries.bump_u16_range_check((__word >> 16) as u16); } } __result }; if (__v_115 != __v_51) { return Err(ExecError::AssertEqMismatch { lhs: __v_115.as_canonical_u64(), rhs: __v_51.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_164] = { let __args: [G; IN_164] = [__v_111, __v_112, __v_29, __v_113]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(164, &__args[..])?) { [G::ZERO; OUT_164] } else { let (__hash, __hit) = record.function_queries[164].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[164].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[164].bump_multiplicity(__i); } let __ret: [G; OUT_164] = unsafe { *(record.function_queries[164].output_at(__i).as_ptr() as *const [G; OUT_164]) }; __ret }, _ => { aiur_fn_164::(__args, __hash, record, io_buffer)? }, } } }; let __v_116: G = __r_arr[0]; let __r_arr: [G; OUT_168] = { let __args: [G; IN_168] = [__v_8, __v_116, __v_19, __v_20, __v_21, __v_22, __v_4, __v_5, __v_23, __v_24, __v_27, __v_28, __v_113]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(168, &__args[..])?) { [G::ZERO; OUT_168] } else { let (__hash, __hit) = record.function_queries[168].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[168].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[168].bump_multiplicity(__i); } let __ret: [G; OUT_168] = unsafe { *(record.function_queries[168].output_at(__i).as_ptr() as *const [G; OUT_168]) }; __ret }, _ => { aiur_fn_168::(__args, __hash, record, io_buffer)? }, } } }; let __v_117: G = __r_arr[0]; if (__v_117 != __v_51) { return Err(ExecError::AssertEqMismatch { lhs: __v_117.as_canonical_u64(), rhs: __v_51.as_canonical_u64(), msg: None }); } let __v_118: G = G::from_u64(0); let __r_arr: [G; OUT_167] = { let __args: [G; IN_167] = [__v_8, __v_9, __v_118, __v_29]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(167, &__args[..])?) { [G::ZERO; OUT_167] } else { let (__hash, __hit) = record.function_queries[167].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[167].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[167].bump_multiplicity(__i); } let __ret: [G; OUT_167] = unsafe { *(record.function_queries[167].output_at(__i).as_ptr() as *const [G; OUT_167]) }; __ret }, _ => { aiur_fn_167::(__args, __hash, record, io_buffer)? }, } } }; let __v_119: G = __r_arr[0]; let __r_arr: [G; OUT_163] = { let __args: [G; IN_163] = [__v_111, __v_112, __v_119, __v_91, __v_105, __v_1, __v_2, __v_3, __v_4, __v_5, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_113, __v_106, __v_6, __v_10, __v_32]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(163, &__args[..])?) { [G::ZERO; OUT_163] } else { let (__hash, __hit) = record.function_queries[163].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[163].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[163].bump_multiplicity(__i); } let __ret: [G; OUT_163] = unsafe { *(record.function_queries[163].output_at(__i).as_ptr() as *const [G; OUT_163]) }; __ret }, _ => { aiur_fn_163::(__args, __hash, record, io_buffer)? }, } } }; let __v_120: G = __r_arr[0]; let __r_arr: [G; OUT_171] = { let __args: [G; IN_171] = [__v_9, __v_6, __v_120, __v_116, __v_118, __v_113]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(171, &__args[..])?) { [G::ZERO; OUT_171] } else { let (__hash, __hit) = record.function_queries[171].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[171].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[171].bump_multiplicity(__i); } let __ret: [G; OUT_171] = unsafe { *(record.function_queries[171].output_at(__i).as_ptr() as *const [G; OUT_171]) }; __ret }, _ => { aiur_fn_171::(__args, __hash, record, io_buffer)? }, } } }; let __v_121: G = __r_arr[0]; let __ret: [G; OUT_172] = [__v_121]; record.function_queries[172].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } +fn aiur_fn_172(inp: [G; IN_172], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_172], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; match __v_6.as_canonical_u64() { _ => { let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_32: G = __r_arr[0]; let __r_arr: [G; OUT_319] = { let __args: [G; IN_319] = [__v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(319, &__args[..])?) { [G::ZERO; OUT_319] } else { let (__hash, __hit) = record.function_queries[319].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[319].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[319].bump_multiplicity(__i); } let __ret: [G; OUT_319] = unsafe { *(record.function_queries[319].output_at(__i).as_ptr() as *const [G; OUT_319]) }; __ret }, _ => { aiur_fn_319::(__args, __hash, record, io_buffer)? }, } } }; let __v_33: G = __r_arr[0]; let __v_34: G = __v_33 - __v_32; let __v_35: G = G::from_bool(__v_34 == G::ZERO); let __v_36: G = G::from_u64(1); if (__v_35 != __v_36) { return Err(ExecError::AssertEqMismatch { lhs: __v_35.as_canonical_u64(), rhs: __v_36.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = G::from_u64(3); let __r_arr: [G; OUT_148] = { let __args: [G; IN_148] = [__v_4, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(148, &__args[..])?) { [G::ZERO; OUT_148] } else { let (__hash, __hit) = record.function_queries[148].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[148].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[148].bump_multiplicity(__i); } let __ret: [G; OUT_148] = unsafe { *(record.function_queries[148].output_at(__i).as_ptr() as *const [G; OUT_148]) }; __ret }, _ => { aiur_fn_148::(__args, __hash, record, io_buffer)? }, } } }; let __v_39: G = __r_arr[0]; let __v_40: G = __v_38 + __v_39; if (__v_37 != __v_40) { return Err(ExecError::AssertEqMismatch { lhs: __v_37.as_canonical_u64(), rhs: __v_40.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_325] = { let __args: [G; IN_325] = [__v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(325, &__args[..])?) { [G::ZERO; OUT_325] } else { let (__hash, __hit) = record.function_queries[325].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[325].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[325].bump_multiplicity(__i); } let __ret: [G; OUT_325] = unsafe { *(record.function_queries[325].output_at(__i).as_ptr() as *const [G; OUT_325]) }; __ret }, _ => { aiur_fn_325::(__args, __hash, record, io_buffer)? }, } } }; let __v_41: G = __r_arr[0]; let __v_42: G = __v_41 - __v_32; let __v_43: G = G::from_bool(__v_42 == G::ZERO); if (__v_43 != __v_36) { return Err(ExecError::AssertEqMismatch { lhs: __v_43.as_canonical_u64(), rhs: __v_36.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; if (__v_44 != __v_36) { return Err(ExecError::AssertEqMismatch { lhs: __v_44.as_canonical_u64(), rhs: __v_36.as_canonical_u64(), msg: None }); } let __v_45: G = { let __values: [G; 3] = [__v_36, __v_36, __v_36]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __mc_out___mc_0: [G; 1] = '__mc_0: { match __v_4.as_canonical_u64() { 0u64 => { break '__mc_0 [__v_45]; }, 1u64 => { let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_5, __v_45]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_46: G = __r_arr[0]; break '__mc_0 [__v_46]; }, _ => { return Err(ExecError::MatchNoCase(__v_4.as_canonical_u64())); }, } }; let __v_46: G = __mc_out___mc_0[0]; let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_3, __v_46]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_47: G = __r_arr[0]; let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_2, __v_47]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_48: G = __r_arr[0]; let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_1, __v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __r_arr: [G; OUT_316] = { let __args: [G; IN_316] = [__v_0, __v_49]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(316, &__args[..])?) { [G::ZERO; OUT_316] } else { let (__hash, __hit) = record.function_queries[316].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[316].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[316].bump_multiplicity(__i); } let __ret: [G; OUT_316] = unsafe { *(record.function_queries[316].output_at(__i).as_ptr() as *const [G; OUT_316]) }; __ret }, _ => { aiur_fn_316::(__args, __hash, record, io_buffer)? }, } } }; let __v_50: G = __r_arr[0]; let __v_51: G = G::from_u64(1); let __v_52: G = { let __values: [G; 3] = [__v_51, __v_51, __v_51]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_180] = { let __args: [G; IN_180] = [__v_50, __v_52]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(180, &__args[..])?) { [G::ZERO; OUT_180] } else { let (__hash, __hit) = record.function_queries[180].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[180].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[180].bump_multiplicity(__i); } let __ret: [G; OUT_180] = unsafe { *(record.function_queries[180].output_at(__i).as_ptr() as *const [G; OUT_180]) }; __ret }, _ => { aiur_fn_180::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; let __v_54: G = __r_arr[1]; let __v_55: G = __r_arr[2]; let __v_56: G = __r_arr[3]; let __v_57: G = __r_arr[4]; let __v_58: G = __r_arr[5]; let __v_59: G = __r_arr[6]; let __v_60: G = __r_arr[7]; let __v_61: G = __r_arr[8]; let __v_62: G = __r_arr[9]; let __v_63: G = __r_arr[10]; let __v_64: G = __r_arr[11]; let __v_65: G = __r_arr[12]; let __v_66: G = __r_arr[13]; let __v_67: G = __r_arr[14]; let __v_68: G = __r_arr[15]; let __v_69: G = __r_arr[16]; let __v_70: G = __r_arr[17]; let __v_71: G = G::from_u64(256); let __v_72: G = __v_71 * __v_54; let __v_73: G = __v_53 + __v_72; let __v_74: G = G::from_u64(65536); let __v_75: G = __v_74 * __v_55; let __v_76: G = __v_73 + __v_75; let __v_77: G = G::from_u64(16777216); let __v_78: G = __v_77 * __v_56; let __v_79: G = __v_76 + __v_78; let __v_80: G = G::from_u64(4294967296); let __v_81: G = __v_80 * __v_57; let __v_82: G = __v_79 + __v_81; let __v_83: G = G::from_u64(1099511627776); let __v_84: G = __v_83 * __v_58; let __v_85: G = __v_82 + __v_84; let __v_86: G = G::from_u64(281474976710656); let __v_87: G = __v_86 * __v_59; let __v_88: G = __v_85 + __v_87; let __v_89: G = G::from_u64(72057594037927936); let __v_90: G = __v_89 * __v_60; let __v_91: G = __v_88 + __v_90; let __v_92: G = __v_71 * __v_62; let __v_93: G = __v_61 + __v_92; let __v_94: G = __v_74 * __v_63; let __v_95: G = __v_93 + __v_94; let __v_96: G = __v_77 * __v_64; let __v_97: G = __v_95 + __v_96; let __v_98: G = __v_80 * __v_65; let __v_99: G = __v_97 + __v_98; let __v_100: G = __v_83 * __v_66; let __v_101: G = __v_99 + __v_100; let __v_102: G = __v_86 * __v_67; let __v_103: G = __v_101 + __v_102; let __v_104: G = __v_89 * __v_68; let __v_105: G = __v_103 + __v_104; let __r_arr: [G; OUT_143] = { let __args: [G; IN_143] = [__v_69, __v_6, __v_7, __v_30]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(143, &__args[..])?) { [G::ZERO; OUT_143] } else { let (__hash, __hit) = record.function_queries[143].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[143].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[143].bump_multiplicity(__i); } let __ret: [G; OUT_143] = unsafe { *(record.function_queries[143].output_at(__i).as_ptr() as *const [G; OUT_143]) }; __ret }, _ => { aiur_fn_143::(__args, __hash, record, io_buffer)? }, } } }; let __v_106: G = __r_arr[0]; let __v_107: G = __r_arr[1]; let __r_arr: [G; OUT_137] = { let __args: [G; IN_137] = [__v_10, __v_52]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(137, &__args[..])?) { [G::ZERO; OUT_137] } else { let (__hash, __hit) = record.function_queries[137].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[137].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[137].bump_multiplicity(__i); } let __ret: [G; OUT_137] = unsafe { *(record.function_queries[137].output_at(__i).as_ptr() as *const [G; OUT_137]) }; __ret }, _ => { aiur_fn_137::(__args, __hash, record, io_buffer)? }, } } }; let __v_108: G = __r_arr[0]; let __r_arr: [G; OUT_316] = { let __args: [G; IN_316] = [__v_107, __v_108]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(316, &__args[..])?) { [G::ZERO; OUT_316] } else { let (__hash, __hit) = record.function_queries[316].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[316].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[316].bump_multiplicity(__i); } let __ret: [G; OUT_316] = unsafe { *(record.function_queries[316].output_at(__i).as_ptr() as *const [G; OUT_316]) }; __ret }, _ => { aiur_fn_316::(__args, __hash, record, io_buffer)? }, } } }; let __v_109: G = __r_arr[0]; let __r_arr: [G; OUT_141] = { let __args: [G; IN_141] = [__v_109, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(141, &__args[..])?) { [G::ZERO; OUT_141] } else { let (__hash, __hit) = record.function_queries[141].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[141].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[141].bump_multiplicity(__i); } let __ret: [G; OUT_141] = unsafe { *(record.function_queries[141].output_at(__i).as_ptr() as *const [G; OUT_141]) }; __ret }, _ => { aiur_fn_141::(__args, __hash, record, io_buffer)? }, } } }; let __v_110: G = __r_arr[0]; let __r_arr: [G; OUT_142] = { let __args: [G; IN_142] = [__v_110, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(142, &__args[..])?) { [G::ZERO; OUT_142] } else { let (__hash, __hit) = record.function_queries[142].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[142].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[142].bump_multiplicity(__i); } let __ret: [G; OUT_142] = unsafe { *(record.function_queries[142].output_at(__i).as_ptr() as *const [G; OUT_142]) }; __ret }, _ => { aiur_fn_142::(__args, __hash, record, io_buffer)? }, } } }; let __v_111: G = __r_arr[0]; let __v_112: G = __r_arr[1]; let __v_113: G = __v_32 + __v_28; let __v_114: G = G::from_u64(33); let __v_115: G = { let __a_val = __v_113.as_canonical_u64(); let __b_val = __v_114.as_canonical_u64(); let __a_u32 = u32::try_from(__a_val).ok().ok_or(ExecError::U32OutOfRange(__a_val))?; let __b_u32 = u32::try_from(__b_val).ok().ok_or(ExecError::U32OutOfRange(__b_val))?; let __result = G::from_bool(__a_u32 < __b_u32); if (!unconstrained) { let __c_u32 = __a_u32.wrapping_sub(__b_u32); for __word in [__a_u32, __c_u32, __b_u32] { record.bytes2_queries.bump_u16_range_check((__word & 65535) as u16); record.bytes2_queries.bump_u16_range_check((__word >> 16) as u16); } } __result }; if (__v_115 != __v_51) { return Err(ExecError::AssertEqMismatch { lhs: __v_115.as_canonical_u64(), rhs: __v_51.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_164] = { let __args: [G; IN_164] = [__v_111, __v_112, __v_29, __v_113]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(164, &__args[..])?) { [G::ZERO; OUT_164] } else { let (__hash, __hit) = record.function_queries[164].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[164].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[164].bump_multiplicity(__i); } let __ret: [G; OUT_164] = unsafe { *(record.function_queries[164].output_at(__i).as_ptr() as *const [G; OUT_164]) }; __ret }, _ => { aiur_fn_164::(__args, __hash, record, io_buffer)? }, } } }; let __v_116: G = __r_arr[0]; let __r_arr: [G; OUT_168] = { let __args: [G; IN_168] = [__v_8, __v_116, __v_19, __v_20, __v_21, __v_22, __v_4, __v_5, __v_23, __v_24, __v_27, __v_28, __v_113]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(168, &__args[..])?) { [G::ZERO; OUT_168] } else { let (__hash, __hit) = record.function_queries[168].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[168].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[168].bump_multiplicity(__i); } let __ret: [G; OUT_168] = unsafe { *(record.function_queries[168].output_at(__i).as_ptr() as *const [G; OUT_168]) }; __ret }, _ => { aiur_fn_168::(__args, __hash, record, io_buffer)? }, } } }; let __v_117: G = __r_arr[0]; if (__v_117 != __v_51) { return Err(ExecError::AssertEqMismatch { lhs: __v_117.as_canonical_u64(), rhs: __v_51.as_canonical_u64(), msg: None }); } let __v_118: G = G::from_u64(0); let __r_arr: [G; OUT_167] = { let __args: [G; IN_167] = [__v_8, __v_9, __v_118, __v_29]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(167, &__args[..])?) { [G::ZERO; OUT_167] } else { let (__hash, __hit) = record.function_queries[167].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[167].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[167].bump_multiplicity(__i); } let __ret: [G; OUT_167] = unsafe { *(record.function_queries[167].output_at(__i).as_ptr() as *const [G; OUT_167]) }; __ret }, _ => { aiur_fn_167::(__args, __hash, record, io_buffer)? }, } } }; let __v_119: G = __r_arr[0]; let __r_arr: [G; OUT_163] = { let __args: [G; IN_163] = [__v_111, __v_112, __v_119, __v_91, __v_105, __v_1, __v_2, __v_3, __v_4, __v_5, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_113, __v_106, __v_6, __v_10, __v_32]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(163, &__args[..])?) { [G::ZERO; OUT_163] } else { let (__hash, __hit) = record.function_queries[163].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[163].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[163].bump_multiplicity(__i); } let __ret: [G; OUT_163] = unsafe { *(record.function_queries[163].output_at(__i).as_ptr() as *const [G; OUT_163]) }; __ret }, _ => { aiur_fn_163::(__args, __hash, record, io_buffer)? }, } } }; let __v_120: G = __r_arr[0]; let __r_arr: [G; OUT_171] = { let __args: [G; IN_171] = [__v_9, __v_6, __v_120, __v_116, __v_118, __v_113]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(171, &__args[..])?) { [G::ZERO; OUT_171] } else { let (__hash, __hit) = record.function_queries[171].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[171].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[171].bump_multiplicity(__i); } let __ret: [G; OUT_171] = unsafe { *(record.function_queries[171].output_at(__i).as_ptr() as *const [G; OUT_171]) }; __ret }, _ => { aiur_fn_171::(__args, __hash, record, io_buffer)? }, } } }; let __v_121: G = __r_arr[0]; let __ret: [G; OUT_172] = [__v_121]; record.function_queries[172].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } const INPUT_SIZE_173: usize = 2; const IN_173: usize = 2; const OUT_173: usize = 1; @@ -744,11 +744,11 @@ fn aiur_fn_182(inp: [G; IN_182], input_hash: u64, rec const INPUT_SIZE_183: usize = 9; const IN_183: usize = 9; const OUT_183: usize = 1; -fn aiur_fn_183(inp: [G; IN_183], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_183], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = G::from_u64(1); let __v_10: G = { let __values: [G; 3] = [__v_9, __v_9, __v_9]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_174] = { let __args: [G; IN_174] = [__v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(174, &__args[..])?) { [G::ZERO; OUT_174] } else { let (__hash, __hit) = record.function_queries[174].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[174].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[174].bump_multiplicity(__i); } let __ret: [G; OUT_174] = unsafe { *(record.function_queries[174].output_at(__i).as_ptr() as *const [G; OUT_174]) }; __ret }, _ => { aiur_fn_174::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_0, __v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __ret: [G; OUT_183] = [__v_12]; record.function_queries[183].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +fn aiur_fn_183(inp: [G; IN_183], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_183], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = G::from_u64(1); let __v_10: G = { let __values: [G; 3] = [__v_9, __v_9, __v_9]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_174] = { let __args: [G; IN_174] = [__v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(174, &__args[..])?) { [G::ZERO; OUT_174] } else { let (__hash, __hit) = record.function_queries[174].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[174].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[174].bump_multiplicity(__i); } let __ret: [G; OUT_174] = unsafe { *(record.function_queries[174].output_at(__i).as_ptr() as *const [G; OUT_174]) }; __ret }, _ => { aiur_fn_174::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __r_arr: [G; OUT_316] = { let __args: [G; IN_316] = [__v_0, __v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(316, &__args[..])?) { [G::ZERO; OUT_316] } else { let (__hash, __hit) = record.function_queries[316].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[316].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[316].bump_multiplicity(__i); } let __ret: [G; OUT_316] = unsafe { *(record.function_queries[316].output_at(__i).as_ptr() as *const [G; OUT_316]) }; __ret }, _ => { aiur_fn_316::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __ret: [G; OUT_183] = [__v_12]; record.function_queries[183].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } const INPUT_SIZE_184: usize = 2; const IN_184: usize = 2; const OUT_184: usize = 1; -fn aiur_fn_184(inp: [G; IN_184], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_184], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = G::from_u64(1); let __v_3: G = { let __values: [G; 3] = [__v_2, __v_2, __v_2]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_176] = { let __args: [G; IN_176] = [__v_1, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(176, &__args[..])?) { [G::ZERO; OUT_176] } else { let (__hash, __hit) = record.function_queries[176].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[176].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[176].bump_multiplicity(__i); } let __ret: [G; OUT_176] = unsafe { *(record.function_queries[176].output_at(__i).as_ptr() as *const [G; OUT_176]) }; __ret }, _ => { aiur_fn_176::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_0, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __ret: [G; OUT_184] = [__v_5]; record.function_queries[184].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +fn aiur_fn_184(inp: [G; IN_184], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_184], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = G::from_u64(1); let __v_3: G = { let __values: [G; 3] = [__v_2, __v_2, __v_2]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_176] = { let __args: [G; IN_176] = [__v_1, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(176, &__args[..])?) { [G::ZERO; OUT_176] } else { let (__hash, __hit) = record.function_queries[176].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[176].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[176].bump_multiplicity(__i); } let __ret: [G; OUT_176] = unsafe { *(record.function_queries[176].output_at(__i).as_ptr() as *const [G; OUT_176]) }; __ret }, _ => { aiur_fn_176::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __r_arr: [G; OUT_316] = { let __args: [G; IN_316] = [__v_0, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(316, &__args[..])?) { [G::ZERO; OUT_316] } else { let (__hash, __hit) = record.function_queries[316].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[316].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[316].bump_multiplicity(__i); } let __ret: [G; OUT_316] = unsafe { *(record.function_queries[316].output_at(__i).as_ptr() as *const [G; OUT_316]) }; __ret }, _ => { aiur_fn_316::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __ret: [G; OUT_184] = [__v_5]; record.function_queries[184].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } const INPUT_SIZE_185: usize = 1; const IN_185: usize = 1; const OUT_185: usize = 1; @@ -796,7 +796,7 @@ fn aiur_fn_195(inp: [G; IN_195], input_hash: u64, rec const INPUT_SIZE_196: usize = 15; const IN_196: usize = 15; const OUT_196: usize = 2; -fn aiur_fn_196(inp: [G; IN_196], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_196], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __r_arr: [G; OUT_319] = { let __args: [G; IN_319] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(319, &__args[..])?) { [G::ZERO; OUT_319] } else { let (__hash, __hit) = record.function_queries[319].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[319].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[319].bump_multiplicity(__i); } let __ret: [G; OUT_319] = unsafe { *(record.function_queries[319].output_at(__i).as_ptr() as *const [G; OUT_319]) }; __ret }, _ => { aiur_fn_319::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __r_arr[1]; let __v_17: G = __r_arr[2]; let __v_18: G = __r_arr[3]; match __v_15.as_canonical_u64() { 0u64 => { let __v_19: G = G::from_u64(0); let __ret: [G; OUT_196] = [__v_16, __v_19]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_19: G = __v_16 + __v_16; let __v_20: G = __v_19 + __v_17; match __v_20.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_4, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __ret: [G; OUT_196] = [__v_21, __v_22]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_5, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __ret: [G; OUT_196] = [__v_21, __v_22]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 2u64 => { let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_2, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __ret: [G; OUT_196] = [__v_21, __v_22]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 3u64 => { let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_3, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __ret: [G; OUT_196] = [__v_21, __v_22]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 4u64 => { let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_6, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __ret: [G; OUT_196] = [__v_21, __v_22]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_7, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __ret: [G; OUT_196] = [__v_21, __v_22]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, 2u64 => { let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_8, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __ret: [G; OUT_196] = [__v_19, __v_20]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 3u64 => { let __ret: [G; OUT_196] = [__v_9, __v_10]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 4u64 => { let __ret: [G; OUT_196] = [__v_11, __v_12]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 5u64 => { let __ret: [G; OUT_196] = [__v_13, __v_14]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 6u64 => { let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_0, __v_16, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_0, __v_17, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __v_23: G = __v_19 + __v_21; let __v_24: G = __v_20 + __v_22; let __ret: [G; OUT_196] = [__v_23, __v_24]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 7u64 => { let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_0, __v_16, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_0, __v_17, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __v_23: G = __v_19 - __v_21; let __v_24: G = __v_20 - __v_22; let __ret: [G; OUT_196] = [__v_23, __v_24]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 8u64 => { let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_0, __v_16, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_0, __v_17, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __v_23: G = __v_19 * __v_21; let __v_24: G = G::from_u64(7); let __v_25: G = __v_20 * __v_22; let __v_26: G = __v_24 * __v_25; let __v_27: G = __v_23 + __v_26; let __v_28: G = __v_19 * __v_22; let __v_29: G = __v_20 * __v_21; let __v_30: G = __v_28 + __v_29; let __ret: [G; OUT_196] = [__v_27, __v_30]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 9u64 => { let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_0, __v_16, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __v_21: G = G::from_u64(0); let __v_22: G = __v_21 - __v_19; let __v_23: G = __v_21 - __v_20; let __ret: [G; OUT_196] = [__v_22, __v_23]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_15.as_canonical_u64())); }, } }) } +fn aiur_fn_196(inp: [G; IN_196], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_196], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __r_arr[1]; let __v_17: G = __r_arr[2]; let __v_18: G = __r_arr[3]; match __v_15.as_canonical_u64() { 0u64 => { let __v_19: G = G::from_u64(0); let __ret: [G; OUT_196] = [__v_16, __v_19]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_19: G = __v_16 + __v_16; let __v_20: G = __v_19 + __v_17; match __v_20.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_4, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __ret: [G; OUT_196] = [__v_21, __v_22]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_5, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __ret: [G; OUT_196] = [__v_21, __v_22]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 2u64 => { let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_2, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __ret: [G; OUT_196] = [__v_21, __v_22]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 3u64 => { let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_3, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __ret: [G; OUT_196] = [__v_21, __v_22]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 4u64 => { let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_6, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __ret: [G; OUT_196] = [__v_21, __v_22]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_7, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __ret: [G; OUT_196] = [__v_21, __v_22]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, 2u64 => { let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_8, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __ret: [G; OUT_196] = [__v_19, __v_20]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 3u64 => { let __ret: [G; OUT_196] = [__v_9, __v_10]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 4u64 => { let __ret: [G; OUT_196] = [__v_11, __v_12]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 5u64 => { let __ret: [G; OUT_196] = [__v_13, __v_14]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 6u64 => { let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_0, __v_16, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_0, __v_17, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __v_23: G = __v_19 + __v_21; let __v_24: G = __v_20 + __v_22; let __ret: [G; OUT_196] = [__v_23, __v_24]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 7u64 => { let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_0, __v_16, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_0, __v_17, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __v_23: G = __v_19 - __v_21; let __v_24: G = __v_20 - __v_22; let __ret: [G; OUT_196] = [__v_23, __v_24]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 8u64 => { let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_0, __v_16, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_0, __v_17, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __v_23: G = __v_19 * __v_21; let __v_24: G = G::from_u64(7); let __v_25: G = __v_20 * __v_22; let __v_26: G = __v_24 * __v_25; let __v_27: G = __v_23 + __v_26; let __v_28: G = __v_19 * __v_22; let __v_29: G = __v_20 * __v_21; let __v_30: G = __v_28 + __v_29; let __ret: [G; OUT_196] = [__v_27, __v_30]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 9u64 => { let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_0, __v_16, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __v_21: G = G::from_u64(0); let __v_22: G = __v_21 - __v_19; let __v_23: G = __v_21 - __v_20; let __ret: [G; OUT_196] = [__v_22, __v_23]; record.function_queries[196].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_15.as_canonical_u64())); }, } }) } const INPUT_SIZE_197: usize = 19; const IN_197: usize = 19; const OUT_197: usize = 2; @@ -812,11 +812,11 @@ fn aiur_fn_199(inp: [G; IN_199], input_hash: u64, rec const INPUT_SIZE_200: usize = 44; const IN_200: usize = 44; const OUT_200: usize = 2; -fn aiur_fn_200(inp: [G; IN_200], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_200], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = inp[34]; let __v_35: G = inp[35]; let __v_36: G = inp[36]; let __v_37: G = inp[37]; let __v_38: G = inp[38]; let __v_39: G = inp[39]; let __v_40: G = inp[40]; let __v_41: G = inp[41]; let __v_42: G = inp[42]; let __v_43: G = inp[43]; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_44: G = __loaded[0]; let __v_45: G = __loaded[1]; let __v_46: G = __loaded[2]; let __v_47: G = __loaded[3]; match __v_44.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_200] = [__v_0, __v_1]; record.function_queries[200].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_45.as_canonical_u64() { _ => { let __r_arr: [G; OUT_199] = { let __args: [G; IN_199] = [__v_46, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(199, &__args[..])?) { [G::ZERO; OUT_199] } else { let (__hash, __hit) = record.function_queries[199].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[199].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[199].bump_multiplicity(__i); } let __ret: [G; OUT_199] = unsafe { *(record.function_queries[199].output_at(__i).as_ptr() as *const [G; OUT_199]) }; __ret }, _ => { aiur_fn_199::(__args, __hash, record, io_buffer)? }, } } }; let __v_48: G = __r_arr[0]; let __v_49: G = __r_arr[1]; let __v_50: G = __r_arr[2]; let __v_51: G = __r_arr[3]; let __v_52: G = __v_48 + __v_22; let __v_53: G = __v_49 + __v_23; let __v_54: G = __v_50 + __v_24; let __v_55: G = __v_51 + __v_25; let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_30, __v_45, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_56: G = __r_arr[0]; let __v_57: G = __r_arr[1]; let __r_arr: [G; OUT_198] = { let __args: [G; IN_198] = [__v_14, __v_15, __v_16, __v_17, __v_52, __v_53, __v_54, __v_55]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(198, &__args[..])?) { [G::ZERO; OUT_198] } else { let (__hash, __hit) = record.function_queries[198].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[198].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[198].bump_multiplicity(__i); } let __ret: [G; OUT_198] = unsafe { *(record.function_queries[198].output_at(__i).as_ptr() as *const [G; OUT_198]) }; __ret }, _ => { aiur_fn_198::(__args, __hash, record, io_buffer)? }, } } }; let __v_58: G = __r_arr[0]; let __v_59: G = __r_arr[1]; let __v_60: G = __r_arr[2]; let __v_61: G = __r_arr[3]; let __v_62: G = __v_56 * __v_10; let __v_63: G = G::from_u64(7); let __v_64: G = __v_57 * __v_11; let __v_65: G = __v_63 * __v_64; let __v_66: G = __v_62 + __v_65; let __v_67: G = __v_56 * __v_11; let __v_68: G = __v_57 * __v_10; let __v_69: G = __v_67 + __v_68; let __v_70: G = __v_58 + __v_66; let __v_71: G = __v_59 + __v_69; let __v_72: G = __v_56 * __v_12; let __v_73: G = __v_57 * __v_13; let __v_74: G = __v_63 * __v_73; let __v_75: G = __v_72 + __v_74; let __v_76: G = __v_56 * __v_13; let __v_77: G = __v_57 * __v_12; let __v_78: G = __v_76 + __v_77; let __v_79: G = __v_60 + __v_75; let __v_80: G = __v_61 + __v_78; let __r_arr: [G; OUT_198] = { let __args: [G; IN_198] = [__v_10, __v_11, __v_12, __v_13, __v_52, __v_53, __v_54, __v_55]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(198, &__args[..])?) { [G::ZERO; OUT_198] } else { let (__hash, __hit) = record.function_queries[198].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[198].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[198].bump_multiplicity(__i); } let __ret: [G; OUT_198] = unsafe { *(record.function_queries[198].output_at(__i).as_ptr() as *const [G; OUT_198]) }; __ret }, _ => { aiur_fn_198::(__args, __hash, record, io_buffer)? }, } } }; let __v_81: G = __r_arr[0]; let __v_82: G = __r_arr[1]; let __v_83: G = __r_arr[2]; let __v_84: G = __r_arr[3]; let __v_85: G = G::from_u64(1); let __v_86: G = __v_5 + __v_85; let __v_87: G = __v_86 - __v_6; match __v_87.as_canonical_u64() { 0u64 => { let __v_88: G = __v_7 + __v_7; let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_35, __v_88]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_89: G = __r_arr[0]; let __v_90: G = __r_arr[1]; let __v_91: G = G::from_u64(1); let __v_92: G = __v_88 + __v_91; let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_35, __v_92]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_93: G = __r_arr[0]; let __v_94: G = __r_arr[1]; let __v_95: G = G::from_u64(0); let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_36, __v_95]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_96: G = __r_arr[0]; let __v_97: G = __r_arr[1]; let __v_98: G = __v_96 + __v_18; let __v_99: G = __v_97 + __v_19; let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_36, __v_91]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_100: G = __r_arr[0]; let __v_101: G = __r_arr[1]; let __v_102: G = __v_100 + __v_20; let __v_103: G = __v_101 + __v_21; let __v_104: G = __v_98 - __v_89; let __v_105: G = __v_99 - __v_90; let __v_106: G = __v_102 - __v_93; let __v_107: G = __v_103 - __v_94; let __r_arr: [G; OUT_198] = { let __args: [G; IN_198] = [__v_81, __v_82, __v_83, __v_84, __v_104, __v_105, __v_106, __v_107]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(198, &__args[..])?) { [G::ZERO; OUT_198] } else { let (__hash, __hit) = record.function_queries[198].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[198].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[198].bump_multiplicity(__i); } let __ret: [G; OUT_198] = unsafe { *(record.function_queries[198].output_at(__i).as_ptr() as *const [G; OUT_198]) }; __ret }, _ => { aiur_fn_198::(__args, __hash, record, io_buffer)? }, } } }; let __v_108: G = __r_arr[0]; let __v_109: G = __r_arr[1]; let __v_110: G = __r_arr[2]; let __v_111: G = __r_arr[3]; let __v_112: G = __v_108 - __v_70; let __v_113: G = __v_109 - __v_71; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_0, __v_1, __v_2, __v_3, __v_112, __v_113]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_114: G = __r_arr[0]; let __v_115: G = __r_arr[1]; let __v_116: G = __v_110 - __v_79; let __v_117: G = __v_111 - __v_80; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_114, __v_115, __v_2, __v_3, __v_116, __v_117]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_118: G = __r_arr[0]; let __v_119: G = __r_arr[1]; let __ret: [G; OUT_200] = [__v_118, __v_119]; record.function_queries[200].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_88: G = G::from_u64(1); let __v_89: G = __v_8 - __v_88; match __v_89.as_canonical_u64() { 0u64 => { let __v_90: G = __v_7 + __v_7; let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_35, __v_90]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_91: G = __r_arr[0]; let __v_92: G = __r_arr[1]; let __v_93: G = G::from_u64(1); let __v_94: G = __v_90 + __v_93; let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_35, __v_94]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_95: G = __r_arr[0]; let __v_96: G = __r_arr[1]; let __v_97: G = G::from_u64(2); let __v_98: G = __v_90 + __v_97; let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_35, __v_98]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_99: G = __r_arr[0]; let __v_100: G = __r_arr[1]; let __v_101: G = G::from_u64(3); let __v_102: G = __v_90 + __v_101; let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_35, __v_102]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_103: G = __r_arr[0]; let __v_104: G = __r_arr[1]; let __v_105: G = __v_99 - __v_91; let __v_106: G = __v_100 - __v_92; let __v_107: G = __v_103 - __v_95; let __v_108: G = __v_104 - __v_96; let __r_arr: [G; OUT_198] = { let __args: [G; IN_198] = [__v_81, __v_82, __v_83, __v_84, __v_105, __v_106, __v_107, __v_108]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(198, &__args[..])?) { [G::ZERO; OUT_198] } else { let (__hash, __hit) = record.function_queries[198].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[198].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[198].bump_multiplicity(__i); } let __ret: [G; OUT_198] = unsafe { *(record.function_queries[198].output_at(__i).as_ptr() as *const [G; OUT_198]) }; __ret }, _ => { aiur_fn_198::(__args, __hash, record, io_buffer)? }, } } }; let __v_109: G = __r_arr[0]; let __v_110: G = __r_arr[1]; let __v_111: G = __r_arr[2]; let __v_112: G = __r_arr[3]; let __v_113: G = __v_109 - __v_70; let __v_114: G = __v_110 - __v_71; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_0, __v_1, __v_2, __v_3, __v_113, __v_114]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_115: G = __r_arr[0]; let __v_116: G = __r_arr[1]; let __v_117: G = __v_111 - __v_79; let __v_118: G = __v_112 - __v_80; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_115, __v_116, __v_2, __v_3, __v_117, __v_118]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_119: G = __r_arr[0]; let __v_120: G = __r_arr[1]; let __v_121: G = __v_5 + __v_93; let __v_122: G = __v_7 + __v_93; let __v_123: G = G::from_u64(0); let __r_arr: [G; OUT_200] = { let __args: [G; IN_200] = [__v_119, __v_120, __v_2, __v_3, __v_47, __v_121, __v_6, __v_122, __v_9, __v_9, __v_93, __v_123, __v_123, __v_123, __v_123, __v_123, __v_123, __v_123, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(200, &__args[..])?) { [G::ZERO; OUT_200] } else { let (__hash, __hit) = record.function_queries[200].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[200].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[200].bump_multiplicity(__i); } let __ret: [G; OUT_200] = unsafe { *(record.function_queries[200].output_at(__i).as_ptr() as *const [G; OUT_200]) }; __ret }, _ => { aiur_fn_200::(__args, __hash, record, io_buffer)? }, } } }; let __v_124: G = __r_arr[0]; let __v_125: G = __r_arr[1]; let __ret: [G; OUT_200] = [__v_124, __v_125]; record.function_queries[200].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_90: G = G::from_u64(1); let __v_91: G = __v_5 + __v_90; let __v_92: G = __v_8 - __v_90; let __r_arr: [G; OUT_200] = { let __args: [G; IN_200] = [__v_0, __v_1, __v_2, __v_3, __v_47, __v_91, __v_6, __v_7, __v_92, __v_9, __v_81, __v_82, __v_83, __v_84, __v_70, __v_71, __v_79, __v_80, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(200, &__args[..])?) { [G::ZERO; OUT_200] } else { let (__hash, __hit) = record.function_queries[200].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[200].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[200].bump_multiplicity(__i); } let __ret: [G; OUT_200] = unsafe { *(record.function_queries[200].output_at(__i).as_ptr() as *const [G; OUT_200]) }; __ret }, _ => { aiur_fn_200::(__args, __hash, record, io_buffer)? }, } } }; let __v_93: G = __r_arr[0]; let __v_94: G = __r_arr[1]; let __ret: [G; OUT_200] = [__v_93, __v_94]; record.function_queries[200].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, } }, } }, _ => { return Err(ExecError::MatchNoCase(__v_44.as_canonical_u64())); }, } }) } +fn aiur_fn_200(inp: [G; IN_200], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_200], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = inp[34]; let __v_35: G = inp[35]; let __v_36: G = inp[36]; let __v_37: G = inp[37]; let __v_38: G = inp[38]; let __v_39: G = inp[39]; let __v_40: G = inp[40]; let __v_41: G = inp[41]; let __v_42: G = inp[42]; let __v_43: G = inp[43]; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_44: G = __loaded[0]; let __v_45: G = __loaded[1]; let __v_46: G = __loaded[2]; let __v_47: G = __loaded[3]; match __v_44.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_200] = [__v_0, __v_1]; record.function_queries[200].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_45.as_canonical_u64() { _ => { let __r_arr: [G; OUT_199] = { let __args: [G; IN_199] = [__v_46, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(199, &__args[..])?) { [G::ZERO; OUT_199] } else { let (__hash, __hit) = record.function_queries[199].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[199].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[199].bump_multiplicity(__i); } let __ret: [G; OUT_199] = unsafe { *(record.function_queries[199].output_at(__i).as_ptr() as *const [G; OUT_199]) }; __ret }, _ => { aiur_fn_199::(__args, __hash, record, io_buffer)? }, } } }; let __v_48: G = __r_arr[0]; let __v_49: G = __r_arr[1]; let __v_50: G = __r_arr[2]; let __v_51: G = __r_arr[3]; let __v_52: G = __v_48 + __v_22; let __v_53: G = __v_49 + __v_23; let __v_54: G = __v_50 + __v_24; let __v_55: G = __v_51 + __v_25; let __r_arr: [G; OUT_196] = { let __args: [G; IN_196] = [__v_30, __v_45, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(196, &__args[..])?) { [G::ZERO; OUT_196] } else { let (__hash, __hit) = record.function_queries[196].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[196].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[196].bump_multiplicity(__i); } let __ret: [G; OUT_196] = unsafe { *(record.function_queries[196].output_at(__i).as_ptr() as *const [G; OUT_196]) }; __ret }, _ => { aiur_fn_196::(__args, __hash, record, io_buffer)? }, } } }; let __v_56: G = __r_arr[0]; let __v_57: G = __r_arr[1]; let __r_arr: [G; OUT_198] = { let __args: [G; IN_198] = [__v_14, __v_15, __v_16, __v_17, __v_52, __v_53, __v_54, __v_55]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(198, &__args[..])?) { [G::ZERO; OUT_198] } else { let (__hash, __hit) = record.function_queries[198].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[198].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[198].bump_multiplicity(__i); } let __ret: [G; OUT_198] = unsafe { *(record.function_queries[198].output_at(__i).as_ptr() as *const [G; OUT_198]) }; __ret }, _ => { aiur_fn_198::(__args, __hash, record, io_buffer)? }, } } }; let __v_58: G = __r_arr[0]; let __v_59: G = __r_arr[1]; let __v_60: G = __r_arr[2]; let __v_61: G = __r_arr[3]; let __v_62: G = __v_56 * __v_10; let __v_63: G = G::from_u64(7); let __v_64: G = __v_57 * __v_11; let __v_65: G = __v_63 * __v_64; let __v_66: G = __v_62 + __v_65; let __v_67: G = __v_56 * __v_11; let __v_68: G = __v_57 * __v_10; let __v_69: G = __v_67 + __v_68; let __v_70: G = __v_58 + __v_66; let __v_71: G = __v_59 + __v_69; let __v_72: G = __v_56 * __v_12; let __v_73: G = __v_57 * __v_13; let __v_74: G = __v_63 * __v_73; let __v_75: G = __v_72 + __v_74; let __v_76: G = __v_56 * __v_13; let __v_77: G = __v_57 * __v_12; let __v_78: G = __v_76 + __v_77; let __v_79: G = __v_60 + __v_75; let __v_80: G = __v_61 + __v_78; let __r_arr: [G; OUT_198] = { let __args: [G; IN_198] = [__v_10, __v_11, __v_12, __v_13, __v_52, __v_53, __v_54, __v_55]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(198, &__args[..])?) { [G::ZERO; OUT_198] } else { let (__hash, __hit) = record.function_queries[198].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[198].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[198].bump_multiplicity(__i); } let __ret: [G; OUT_198] = unsafe { *(record.function_queries[198].output_at(__i).as_ptr() as *const [G; OUT_198]) }; __ret }, _ => { aiur_fn_198::(__args, __hash, record, io_buffer)? }, } } }; let __v_81: G = __r_arr[0]; let __v_82: G = __r_arr[1]; let __v_83: G = __r_arr[2]; let __v_84: G = __r_arr[3]; let __v_85: G = G::from_u64(1); let __v_86: G = __v_5 + __v_85; let __v_87: G = __v_86 - __v_6; match __v_87.as_canonical_u64() { 0u64 => { let __v_88: G = __v_7 + __v_7; let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_35, __v_88]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_89: G = __r_arr[0]; let __v_90: G = __r_arr[1]; let __v_91: G = G::from_u64(1); let __v_92: G = __v_88 + __v_91; let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_35, __v_92]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_93: G = __r_arr[0]; let __v_94: G = __r_arr[1]; let __v_95: G = G::from_u64(0); let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_36, __v_95]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_96: G = __r_arr[0]; let __v_97: G = __r_arr[1]; let __v_98: G = __v_96 + __v_18; let __v_99: G = __v_97 + __v_19; let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_36, __v_91]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_100: G = __r_arr[0]; let __v_101: G = __r_arr[1]; let __v_102: G = __v_100 + __v_20; let __v_103: G = __v_101 + __v_21; let __v_104: G = __v_98 - __v_89; let __v_105: G = __v_99 - __v_90; let __v_106: G = __v_102 - __v_93; let __v_107: G = __v_103 - __v_94; let __r_arr: [G; OUT_198] = { let __args: [G; IN_198] = [__v_81, __v_82, __v_83, __v_84, __v_104, __v_105, __v_106, __v_107]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(198, &__args[..])?) { [G::ZERO; OUT_198] } else { let (__hash, __hit) = record.function_queries[198].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[198].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[198].bump_multiplicity(__i); } let __ret: [G; OUT_198] = unsafe { *(record.function_queries[198].output_at(__i).as_ptr() as *const [G; OUT_198]) }; __ret }, _ => { aiur_fn_198::(__args, __hash, record, io_buffer)? }, } } }; let __v_108: G = __r_arr[0]; let __v_109: G = __r_arr[1]; let __v_110: G = __r_arr[2]; let __v_111: G = __r_arr[3]; let __v_112: G = __v_108 - __v_70; let __v_113: G = __v_109 - __v_71; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_0, __v_1, __v_2, __v_3, __v_112, __v_113]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_114: G = __r_arr[0]; let __v_115: G = __r_arr[1]; let __v_116: G = __v_110 - __v_79; let __v_117: G = __v_111 - __v_80; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_114, __v_115, __v_2, __v_3, __v_116, __v_117]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_118: G = __r_arr[0]; let __v_119: G = __r_arr[1]; let __ret: [G; OUT_200] = [__v_118, __v_119]; record.function_queries[200].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_88: G = G::from_u64(1); let __v_89: G = __v_8 - __v_88; match __v_89.as_canonical_u64() { 0u64 => { let __v_90: G = __v_7 + __v_7; let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_35, __v_90]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_91: G = __r_arr[0]; let __v_92: G = __r_arr[1]; let __v_93: G = G::from_u64(1); let __v_94: G = __v_90 + __v_93; let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_35, __v_94]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_95: G = __r_arr[0]; let __v_96: G = __r_arr[1]; let __v_97: G = G::from_u64(2); let __v_98: G = __v_90 + __v_97; let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_35, __v_98]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_99: G = __r_arr[0]; let __v_100: G = __r_arr[1]; let __v_101: G = G::from_u64(3); let __v_102: G = __v_90 + __v_101; let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_35, __v_102]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_103: G = __r_arr[0]; let __v_104: G = __r_arr[1]; let __v_105: G = __v_99 - __v_91; let __v_106: G = __v_100 - __v_92; let __v_107: G = __v_103 - __v_95; let __v_108: G = __v_104 - __v_96; let __r_arr: [G; OUT_198] = { let __args: [G; IN_198] = [__v_81, __v_82, __v_83, __v_84, __v_105, __v_106, __v_107, __v_108]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(198, &__args[..])?) { [G::ZERO; OUT_198] } else { let (__hash, __hit) = record.function_queries[198].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[198].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[198].bump_multiplicity(__i); } let __ret: [G; OUT_198] = unsafe { *(record.function_queries[198].output_at(__i).as_ptr() as *const [G; OUT_198]) }; __ret }, _ => { aiur_fn_198::(__args, __hash, record, io_buffer)? }, } } }; let __v_109: G = __r_arr[0]; let __v_110: G = __r_arr[1]; let __v_111: G = __r_arr[2]; let __v_112: G = __r_arr[3]; let __v_113: G = __v_109 - __v_70; let __v_114: G = __v_110 - __v_71; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_0, __v_1, __v_2, __v_3, __v_113, __v_114]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_115: G = __r_arr[0]; let __v_116: G = __r_arr[1]; let __v_117: G = __v_111 - __v_79; let __v_118: G = __v_112 - __v_80; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_115, __v_116, __v_2, __v_3, __v_117, __v_118]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_119: G = __r_arr[0]; let __v_120: G = __r_arr[1]; let __v_121: G = __v_5 + __v_93; let __v_122: G = __v_7 + __v_93; let __v_123: G = G::from_u64(0); let __r_arr: [G; OUT_200] = { let __args: [G; IN_200] = [__v_119, __v_120, __v_2, __v_3, __v_47, __v_121, __v_6, __v_122, __v_9, __v_9, __v_93, __v_123, __v_123, __v_123, __v_123, __v_123, __v_123, __v_123, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(200, &__args[..])?) { [G::ZERO; OUT_200] } else { let (__hash, __hit) = record.function_queries[200].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[200].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[200].bump_multiplicity(__i); } let __ret: [G; OUT_200] = unsafe { *(record.function_queries[200].output_at(__i).as_ptr() as *const [G; OUT_200]) }; __ret }, _ => { aiur_fn_200::(__args, __hash, record, io_buffer)? }, } } }; let __v_124: G = __r_arr[0]; let __v_125: G = __r_arr[1]; let __ret: [G; OUT_200] = [__v_124, __v_125]; record.function_queries[200].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_90: G = G::from_u64(1); let __v_91: G = __v_5 + __v_90; let __v_92: G = __v_8 - __v_90; let __r_arr: [G; OUT_200] = { let __args: [G; IN_200] = [__v_0, __v_1, __v_2, __v_3, __v_47, __v_91, __v_6, __v_7, __v_92, __v_9, __v_81, __v_82, __v_83, __v_84, __v_70, __v_71, __v_79, __v_80, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(200, &__args[..])?) { [G::ZERO; OUT_200] } else { let (__hash, __hit) = record.function_queries[200].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[200].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[200].bump_multiplicity(__i); } let __ret: [G; OUT_200] = unsafe { *(record.function_queries[200].output_at(__i).as_ptr() as *const [G; OUT_200]) }; __ret }, _ => { aiur_fn_200::(__args, __hash, record, io_buffer)? }, } } }; let __v_93: G = __r_arr[0]; let __v_94: G = __r_arr[1]; let __ret: [G; OUT_200] = [__v_93, __v_94]; record.function_queries[200].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, } }, } }, _ => { return Err(ExecError::MatchNoCase(__v_44.as_canonical_u64())); }, } }) } const INPUT_SIZE_201: usize = 28; const IN_201: usize = 28; const OUT_201: usize = 2; -fn aiur_fn_201(inp: [G; IN_201], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_201], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = G::from_u64(0); let __r_arr: [G; OUT_197] = { let __args: [G; IN_197] = [__v_28, __v_28, __v_25, __v_26, __v_1, __v_0, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(197, &__args[..])?) { [G::ZERO; OUT_197] } else { let (__hash, __hit) = record.function_queries[197].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[197].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[197].bump_multiplicity(__i); } let __ret: [G; OUT_197] = unsafe { *(record.function_queries[197].output_at(__i).as_ptr() as *const [G; OUT_197]) }; __ret }, _ => { aiur_fn_197::(__args, __hash, record, io_buffer)? }, } } }; let __v_29: G = __r_arr[0]; let __v_30: G = __r_arr[1]; let __v_31: G = __v_17 - __v_15; let __v_32: G = __v_28 - __v_28; let __v_33: G = __v_31 * __v_27; let __v_34: G = G::from_u64(7); let __v_35: G = __v_32 * __v_28; let __v_36: G = __v_34 * __v_35; let __v_37: G = __v_33 + __v_36; let __v_38: G = __v_31 * __v_28; let __v_39: G = __v_32 * __v_27; let __v_40: G = __v_38 + __v_39; let __v_41: G = __v_21 * __v_37; let __v_42: G = __v_22 * __v_40; let __v_43: G = __v_34 * __v_42; let __v_44: G = __v_41 + __v_43; let __v_45: G = __v_21 * __v_40; let __v_46: G = __v_22 * __v_37; let __v_47: G = __v_45 + __v_46; let __v_48: G = __v_18 - __v_16; let __v_49: G = __v_48 * __v_27; let __v_50: G = __v_49 + __v_36; let __v_51: G = __v_48 * __v_28; let __v_52: G = __v_51 + __v_39; let __v_53: G = __v_21 * __v_50; let __v_54: G = __v_22 * __v_52; let __v_55: G = __v_34 * __v_54; let __v_56: G = __v_53 + __v_55; let __v_57: G = __v_21 * __v_52; let __v_58: G = __v_22 * __v_50; let __v_59: G = __v_57 + __v_58; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_2.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_60: G = __loaded[0]; let __v_61: G = __loaded[1]; let __v_62: G = __loaded[2]; let __v_63: G = __loaded[3]; match __v_60.as_canonical_u64() { 1u64 => { let __v_64: G = G::from_u64(0); let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_9, __v_64]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_65: G = __r_arr[0]; let __v_66: G = __r_arr[1]; let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_8, __v_64]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_67: G = __r_arr[0]; let __v_68: G = __r_arr[1]; let __v_69: G = __v_65 - __v_67; let __v_70: G = __v_66 - __v_68; let __v_71: G = __v_69 + __v_44; let __v_72: G = __v_70 + __v_47; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_29, __v_30, __v_25, __v_26, __v_71, __v_72]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_73: G = __r_arr[0]; let __v_74: G = __r_arr[1]; let __v_75: G = G::from_u64(1); let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_9, __v_75]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_76: G = __r_arr[0]; let __v_77: G = __r_arr[1]; let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_8, __v_75]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_78: G = __r_arr[0]; let __v_79: G = __r_arr[1]; let __v_80: G = __v_76 - __v_78; let __v_81: G = __v_77 - __v_79; let __v_82: G = __v_80 + __v_56; let __v_83: G = __v_81 + __v_59; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_73, __v_74, __v_25, __v_26, __v_82, __v_83]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_84: G = __r_arr[0]; let __v_85: G = __r_arr[1]; let __ret: [G; OUT_201] = [__v_84, __v_85]; record.function_queries[201].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_64: G = G::from_u64(0); let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_65: G = __r_arr[0]; let __v_66: G = G::from_u64(1); let __r_arr: [G; OUT_200] = { let __args: [G; IN_200] = [__v_29, __v_30, __v_25, __v_26, __v_2, __v_64, __v_65, __v_64, __v_3, __v_3, __v_66, __v_64, __v_64, __v_64, __v_64, __v_64, __v_64, __v_64, __v_44, __v_47, __v_56, __v_59, __v_11, __v_28, __v_12, __v_28, __v_13, __v_28, __v_14, __v_28, __v_0, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(200, &__args[..])?) { [G::ZERO; OUT_200] } else { let (__hash, __hit) = record.function_queries[200].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[200].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[200].bump_multiplicity(__i); } let __ret: [G; OUT_200] = unsafe { *(record.function_queries[200].output_at(__i).as_ptr() as *const [G; OUT_200]) }; __ret }, _ => { aiur_fn_200::(__args, __hash, record, io_buffer)? }, } } }; let __v_67: G = __r_arr[0]; let __v_68: G = __r_arr[1]; let __ret: [G; OUT_201] = [__v_67, __v_68]; record.function_queries[201].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_60.as_canonical_u64())); }, } }) } +fn aiur_fn_201(inp: [G; IN_201], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_201], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = G::from_u64(0); let __r_arr: [G; OUT_197] = { let __args: [G; IN_197] = [__v_28, __v_28, __v_25, __v_26, __v_1, __v_0, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(197, &__args[..])?) { [G::ZERO; OUT_197] } else { let (__hash, __hit) = record.function_queries[197].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[197].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[197].bump_multiplicity(__i); } let __ret: [G; OUT_197] = unsafe { *(record.function_queries[197].output_at(__i).as_ptr() as *const [G; OUT_197]) }; __ret }, _ => { aiur_fn_197::(__args, __hash, record, io_buffer)? }, } } }; let __v_29: G = __r_arr[0]; let __v_30: G = __r_arr[1]; let __v_31: G = __v_17 - __v_15; let __v_32: G = __v_28 - __v_28; let __v_33: G = __v_31 * __v_27; let __v_34: G = G::from_u64(7); let __v_35: G = __v_32 * __v_28; let __v_36: G = __v_34 * __v_35; let __v_37: G = __v_33 + __v_36; let __v_38: G = __v_31 * __v_28; let __v_39: G = __v_32 * __v_27; let __v_40: G = __v_38 + __v_39; let __v_41: G = __v_21 * __v_37; let __v_42: G = __v_22 * __v_40; let __v_43: G = __v_34 * __v_42; let __v_44: G = __v_41 + __v_43; let __v_45: G = __v_21 * __v_40; let __v_46: G = __v_22 * __v_37; let __v_47: G = __v_45 + __v_46; let __v_48: G = __v_18 - __v_16; let __v_49: G = __v_48 * __v_27; let __v_50: G = __v_49 + __v_36; let __v_51: G = __v_48 * __v_28; let __v_52: G = __v_51 + __v_39; let __v_53: G = __v_21 * __v_50; let __v_54: G = __v_22 * __v_52; let __v_55: G = __v_34 * __v_54; let __v_56: G = __v_53 + __v_55; let __v_57: G = __v_21 * __v_52; let __v_58: G = __v_22 * __v_50; let __v_59: G = __v_57 + __v_58; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_2.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_60: G = __loaded[0]; let __v_61: G = __loaded[1]; let __v_62: G = __loaded[2]; let __v_63: G = __loaded[3]; match __v_60.as_canonical_u64() { 1u64 => { let __v_64: G = G::from_u64(0); let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_9, __v_64]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_65: G = __r_arr[0]; let __v_66: G = __r_arr[1]; let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_8, __v_64]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_67: G = __r_arr[0]; let __v_68: G = __r_arr[1]; let __v_69: G = __v_65 - __v_67; let __v_70: G = __v_66 - __v_68; let __v_71: G = __v_69 + __v_44; let __v_72: G = __v_70 + __v_47; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_29, __v_30, __v_25, __v_26, __v_71, __v_72]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_73: G = __r_arr[0]; let __v_74: G = __r_arr[1]; let __v_75: G = G::from_u64(1); let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_9, __v_75]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_76: G = __r_arr[0]; let __v_77: G = __r_arr[1]; let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_8, __v_75]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_78: G = __r_arr[0]; let __v_79: G = __r_arr[1]; let __v_80: G = __v_76 - __v_78; let __v_81: G = __v_77 - __v_79; let __v_82: G = __v_80 + __v_56; let __v_83: G = __v_81 + __v_59; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_73, __v_74, __v_25, __v_26, __v_82, __v_83]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_84: G = __r_arr[0]; let __v_85: G = __r_arr[1]; let __ret: [G; OUT_201] = [__v_84, __v_85]; record.function_queries[201].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_64: G = G::from_u64(0); let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_65: G = __r_arr[0]; let __v_66: G = G::from_u64(1); let __r_arr: [G; OUT_200] = { let __args: [G; IN_200] = [__v_29, __v_30, __v_25, __v_26, __v_2, __v_64, __v_65, __v_64, __v_3, __v_3, __v_66, __v_64, __v_64, __v_64, __v_64, __v_64, __v_64, __v_64, __v_44, __v_47, __v_56, __v_59, __v_11, __v_28, __v_12, __v_28, __v_13, __v_28, __v_14, __v_28, __v_0, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(200, &__args[..])?) { [G::ZERO; OUT_200] } else { let (__hash, __hit) = record.function_queries[200].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[200].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[200].bump_multiplicity(__i); } let __ret: [G; OUT_200] = unsafe { *(record.function_queries[200].output_at(__i).as_ptr() as *const [G; OUT_200]) }; __ret }, _ => { aiur_fn_200::(__args, __hash, record, io_buffer)? }, } } }; let __v_67: G = __r_arr[0]; let __v_68: G = __r_arr[1]; let __ret: [G; OUT_201] = [__v_67, __v_68]; record.function_queries[201].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_60.as_canonical_u64())); }, } }) } const INPUT_SIZE_202: usize = 8; const IN_202: usize = 8; const OUT_202: usize = 1; @@ -832,11 +832,11 @@ fn aiur_fn_204(inp: [G; IN_204], input_hash: u64, rec const INPUT_SIZE_205: usize = 4; const IN_205: usize = 4; const OUT_205: usize = 2; -fn aiur_fn_205(inp: [G; IN_205], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_205], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; match __v_2.as_canonical_u64() { 0u64 => { let __v_4: G = G::from_u64(1); let __v_5: G = { let __values: [G; 4] = [__v_4, __v_4, __v_4, __v_4]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_205] = [__v_5, __v_5]; record.function_queries[205].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { match __v_0.as_canonical_u64() { 0u64 => { let __v_4: G = G::from_u64(1); let __v_5: G = { let __values: [G; 4] = [__v_4, __v_4, __v_4, __v_4]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_205] = [__v_5, __v_5]; record.function_queries[205].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_1, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = G::from_u64(0); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_4, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = G::from_u64(1); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_4, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __ret: [G; OUT_205] = [__v_6, __v_8]; record.function_queries[205].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_0.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } +fn aiur_fn_205(inp: [G; IN_205], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_205], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; match __v_2.as_canonical_u64() { 0u64 => { let __v_4: G = G::from_u64(1); let __v_5: G = { let __values: [G; 4] = [__v_4, __v_4, __v_4, __v_4]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_205] = [__v_5, __v_5]; record.function_queries[205].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { match __v_0.as_canonical_u64() { 0u64 => { let __v_4: G = G::from_u64(1); let __v_5: G = { let __values: [G; 4] = [__v_4, __v_4, __v_4, __v_4]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_205] = [__v_5, __v_5]; record.function_queries[205].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_1, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = G::from_u64(0); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_4, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = G::from_u64(1); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_4, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __ret: [G; OUT_205] = [__v_6, __v_8]; record.function_queries[205].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_0.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } const INPUT_SIZE_206: usize = 20; const IN_206: usize = 20; const OUT_206: usize = 1; -fn aiur_fn_206(inp: [G; IN_206], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_206], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __loaded: [G; 11] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(7).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(11))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 11 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 11] = __args[..11].try_into().unwrap(); __arr }; let __v_20: G = __loaded[0]; let __v_21: G = __loaded[1]; let __v_22: G = __loaded[2]; let __v_23: G = __loaded[3]; let __v_24: G = __loaded[4]; let __v_25: G = __loaded[5]; let __v_26: G = __loaded[6]; let __v_27: G = __loaded[7]; let __v_28: G = __loaded[8]; let __v_29: G = __loaded[9]; let __v_30: G = __loaded[10]; match __v_20.as_canonical_u64() { 1u64 => { let __v_31: G = G::from_u64(1); let __ret: [G; OUT_206] = [__v_31]; record.function_queries[206].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_21.as_canonical_u64() { _ => { let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_2, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_31: G = __r_arr[0]; let __v_32: G = G::from_u64(32); let __v_33: G = if unconstrained { aiur::execute::CodegenBytes2::less_than(&__v_31, &__v_32) } else { aiur::execute::bytes2_less_than_value(__v_31, __v_32, record) }; let __v_34: G = G::from_u64(1); if (__v_33 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_33.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_204] = { let __args: [G; IN_204] = [__v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(204, &__args[..])?) { [G::ZERO; OUT_204] } else { let (__hash, __hit) = record.function_queries[204].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[204].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[204].bump_multiplicity(__i); } let __ret: [G; OUT_204] = unsafe { *(record.function_queries[204].output_at(__i).as_ptr() as *const [G; OUT_204]) }; __ret }, _ => { aiur_fn_204::(__args, __hash, record, io_buffer)? }, } } }; let __v_35: G = __r_arr[0]; let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_3, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_4, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_38: G = __r_arr[0]; let __v_39: G = G::from_u64(0); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_38, __v_39]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_40: G = __r_arr[0]; let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_38, __v_34]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_41: G = __r_arr[0]; let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_5, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_42: G = __r_arr[0]; let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_42, __v_39]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_43: G = __r_arr[0]; let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_42, __v_34]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_1, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_45: G = __r_arr[0]; let __v_46: G = __r_arr[1]; let __mc_out___mc_0: [G; 2] = '__mc_0: { match __v_45.as_canonical_u64() { 0u64 => { let __v_47: G = G::from_u64(1); let __v_48: G = { let __values: [G; 4] = [__v_47, __v_47, __v_47, __v_47]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; break '__mc_0 [__v_48, __v_48]; }, 1u64 => { match __v_6.as_canonical_u64() { 0u64 => { let __v_47: G = G::from_u64(1); let __v_48: G = { let __values: [G; 4] = [__v_47, __v_47, __v_47, __v_47]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; break '__mc_0 [__v_48, __v_48]; }, 1u64 => { let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_7, __v_46]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_47: G = __r_arr[0]; let __v_48: G = G::from_u64(0); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_47, __v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __v_50: G = G::from_u64(1); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_47, __v_50]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_51: G = __r_arr[0]; break '__mc_0 [__v_49, __v_51]; }, _ => { return Err(ExecError::MatchNoCase(__v_6.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_45.as_canonical_u64())); }, } }; let __v_47: G = __mc_out___mc_0[0]; let __v_48: G = __mc_out___mc_0[1]; let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __v_50: G = __v_49 - __v_27; let __v_51: G = G::from_bool(__v_50 == G::ZERO); let __v_52: G = G::from_u64(1); if (__v_51 != __v_52) { return Err(ExecError::AssertEqMismatch { lhs: __v_51.as_canonical_u64(), rhs: __v_52.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_41]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; let __v_54: G = __v_53 - __v_27; let __v_55: G = G::from_bool(__v_54 == G::ZERO); if (__v_55 != __v_52) { return Err(ExecError::AssertEqMismatch { lhs: __v_55.as_canonical_u64(), rhs: __v_52.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_56: G = __r_arr[0]; let __v_57: G = __v_56 - __v_29; let __v_58: G = G::from_bool(__v_57 == G::ZERO); if (__v_58 != __v_52) { return Err(ExecError::AssertEqMismatch { lhs: __v_58.as_canonical_u64(), rhs: __v_52.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_44]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_59: G = __r_arr[0]; let __v_60: G = __v_59 - __v_29; let __v_61: G = G::from_bool(__v_60 == G::ZERO); if (__v_61 != __v_52) { return Err(ExecError::AssertEqMismatch { lhs: __v_61.as_canonical_u64(), rhs: __v_52.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_47]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_62: G = __r_arr[0]; let __v_63: G = __v_62 - __v_28; let __v_64: G = G::from_bool(__v_63 == G::ZERO); if (__v_64 != __v_52) { return Err(ExecError::AssertEqMismatch { lhs: __v_64.as_canonical_u64(), rhs: __v_52.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_65: G = __r_arr[0]; let __v_66: G = __v_65 - __v_28; let __v_67: G = G::from_bool(__v_66 == G::ZERO); if (__v_67 != __v_52) { return Err(ExecError::AssertEqMismatch { lhs: __v_67.as_canonical_u64(), rhs: __v_52.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_188] = { let __args: [G; IN_188] = [__v_18, __v_19, __v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(188, &__args[..])?) { [G::ZERO; OUT_188] } else { let (__hash, __hit) = record.function_queries[188].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[188].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[188].bump_multiplicity(__i); } let __ret: [G; OUT_188] = unsafe { *(record.function_queries[188].output_at(__i).as_ptr() as *const [G; OUT_188]) }; __ret }, _ => { aiur_fn_188::(__args, __hash, record, io_buffer)? }, } } }; let __v_68: G = __r_arr[0]; let __v_69: G = __r_arr[1]; let __v_70: G = G::from_u64(0); let __v_71: G = __v_68 - __v_52; let __v_72: G = __v_69 - __v_70; let __r_arr: [G; OUT_189] = { let __args: [G; IN_189] = [__v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(189, &__args[..])?) { [G::ZERO; OUT_189] } else { let (__hash, __hit) = record.function_queries[189].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[189].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[189].bump_multiplicity(__i); } let __ret: [G; OUT_189] = unsafe { *(record.function_queries[189].output_at(__i).as_ptr() as *const [G; OUT_189]) }; __ret }, _ => { aiur_fn_189::(__args, __hash, record, io_buffer)? }, } } }; let __v_73: G = __r_arr[0]; let __v_74: G = aiur::execute::g_inverse_value(__v_73); let __v_75: G = __v_73 * __v_74; let __v_76: G = __v_75 - __v_52; let __v_77: G = __v_73 * __v_76; if (__v_77 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_77.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_78: G = __v_74 * __v_76; if (__v_78 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_78.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_79: G = __v_18 - __v_52; let __v_80: G = __v_19 - __v_70; let __v_81: G = __v_79 * __v_79; let __v_82: G = G::from_u64(7); let __v_83: G = __v_80 * __v_80; let __v_84: G = __v_82 * __v_83; let __v_85: G = __v_81 - __v_84; let __v_86: G = aiur::execute::g_inverse_value(__v_85); let __v_87: G = __v_85 * __v_86; let __v_88: G = __v_87 - __v_52; let __v_89: G = __v_85 * __v_88; if (__v_89 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_89.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_90: G = __v_86 * __v_88; if (__v_90 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_90.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_91: G = __v_79 * __v_86; let __v_92: G = __v_70 - __v_80; let __v_93: G = __v_92 * __v_86; let __v_94: G = __v_71 * __v_91; let __v_95: G = __v_72 * __v_93; let __v_96: G = __v_82 * __v_95; let __v_97: G = __v_94 + __v_96; let __v_98: G = __v_71 * __v_93; let __v_99: G = __v_72 * __v_91; let __v_100: G = __v_98 + __v_99; let __v_101: G = __v_18 - __v_74; let __v_102: G = __v_101 * __v_101; let __v_103: G = __v_102 - __v_84; let __v_104: G = aiur::execute::g_inverse_value(__v_103); let __v_105: G = __v_103 * __v_104; let __v_106: G = __v_105 - __v_52; let __v_107: G = __v_103 * __v_106; if (__v_107 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_107.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_108: G = __v_104 * __v_106; if (__v_108 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_108.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_109: G = __v_101 * __v_104; let __v_110: G = __v_92 * __v_104; let __v_111: G = __v_71 * __v_109; let __v_112: G = __v_72 * __v_110; let __v_113: G = __v_82 * __v_112; let __v_114: G = __v_111 + __v_113; let __v_115: G = __v_71 * __v_110; let __v_116: G = __v_72 * __v_109; let __v_117: G = __v_115 + __v_116; let __v_118: G = __v_71 * __v_71; let __v_119: G = __v_72 * __v_72; let __v_120: G = __v_82 * __v_119; let __v_121: G = __v_118 - __v_120; let __v_122: G = aiur::execute::g_inverse_value(__v_121); let __v_123: G = __v_121 * __v_122; let __v_124: G = __v_123 - __v_52; let __v_125: G = __v_121 * __v_124; if (__v_125 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_125.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_126: G = __v_122 * __v_124; if (__v_126 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_126.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_127: G = __v_71 * __v_122; let __v_128: G = __v_70 - __v_72; let __v_129: G = __v_128 * __v_122; let __v_130: G = { let __values: [G; 4] = [__v_52, __v_52, __v_52, __v_52]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_131: G = { let __values: [G; 4] = [__v_70, __v_37, __v_70, __v_130]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_132: G = { let __values: [G; 4] = [__v_70, __v_36, __v_70, __v_131]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_133: G = { let __values: [G; 4] = [__v_70, __v_11, __v_70, __v_132]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_134: G = { let __values: [G; 4] = [__v_70, __v_10, __v_70, __v_133]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_135: G = { let __values: [G; 4] = [__v_70, __v_15, __v_70, __v_134]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_136: G = { let __values: [G; 4] = [__v_70, __v_14, __v_70, __v_135]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_137: G = { let __values: [G; 4] = [__v_70, __v_13, __v_70, __v_136]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_138: G = { let __values: [G; 4] = [__v_70, __v_12, __v_70, __v_137]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_191] = { let __args: [G; IN_191] = [__v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(191, &__args[..])?) { [G::ZERO; OUT_191] } else { let (__hash, __hit) = record.function_queries[191].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[191].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[191].bump_multiplicity(__i); } let __ret: [G; OUT_191] = unsafe { *(record.function_queries[191].output_at(__i).as_ptr() as *const [G; OUT_191]) }; __ret }, _ => { aiur_fn_191::(__args, __hash, record, io_buffer)? }, } } }; let __v_139: G = __r_arr[0]; let __r_arr: [G; OUT_189] = { let __args: [G; IN_189] = [__v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(189, &__args[..])?) { [G::ZERO; OUT_189] } else { let (__hash, __hit) = record.function_queries[189].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[189].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[189].bump_multiplicity(__i); } let __ret: [G; OUT_189] = unsafe { *(record.function_queries[189].output_at(__i).as_ptr() as *const [G; OUT_189]) }; __ret }, _ => { aiur_fn_189::(__args, __hash, record, io_buffer)? }, } } }; let __v_140: G = __r_arr[0]; let __v_141: G = __v_139 * __v_140; let __v_142: G = aiur::execute::g_inverse_value(__v_141); let __v_143: G = __v_141 * __v_142; let __v_144: G = __v_143 - __v_52; let __v_145: G = __v_141 * __v_144; if (__v_145 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_145.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_146: G = __v_142 * __v_144; if (__v_146 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_146.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_197] = { let __args: [G; IN_197] = [__v_70, __v_70, __v_16, __v_17, __v_23, __v_21, __v_40, __v_41, __v_47, __v_48, __v_43, __v_44, __v_138, __v_97, __v_100, __v_114, __v_117, __v_101, __v_80]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(197, &__args[..])?) { [G::ZERO; OUT_197] } else { let (__hash, __hit) = record.function_queries[197].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[197].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[197].bump_multiplicity(__i); } let __ret: [G; OUT_197] = unsafe { *(record.function_queries[197].output_at(__i).as_ptr() as *const [G; OUT_197]) }; __ret }, _ => { aiur_fn_197::(__args, __hash, record, io_buffer)? }, } } }; let __v_147: G = __r_arr[0]; let __v_148: G = __r_arr[1]; let __v_149: G = __v_36 - __v_10; let __v_150: G = __v_70 - __v_70; let __v_151: G = __v_149 * __v_142; let __v_152: G = __v_150 * __v_70; let __v_153: G = __v_82 * __v_152; let __v_154: G = __v_151 + __v_153; let __v_155: G = __v_149 * __v_70; let __v_156: G = __v_150 * __v_142; let __v_157: G = __v_155 + __v_156; let __v_158: G = __v_114 * __v_154; let __v_159: G = __v_117 * __v_157; let __v_160: G = __v_82 * __v_159; let __v_161: G = __v_158 + __v_160; let __v_162: G = __v_114 * __v_157; let __v_163: G = __v_117 * __v_154; let __v_164: G = __v_162 + __v_163; let __v_165: G = __v_37 - __v_11; let __v_166: G = __v_165 * __v_142; let __v_167: G = __v_166 + __v_153; let __v_168: G = __v_165 * __v_70; let __v_169: G = __v_168 + __v_156; let __v_170: G = __v_114 * __v_167; let __v_171: G = __v_117 * __v_169; let __v_172: G = __v_82 * __v_171; let __v_173: G = __v_170 + __v_172; let __v_174: G = __v_114 * __v_169; let __v_175: G = __v_117 * __v_167; let __v_176: G = __v_174 + __v_175; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_25.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_177: G = __loaded[0]; let __v_178: G = __loaded[1]; let __v_179: G = __loaded[2]; let __v_180: G = __loaded[3]; let __mc_out___mc_1: [G; 2] = '__mc_1: { match __v_177.as_canonical_u64() { 1u64 => { let __v_181: G = G::from_u64(0); let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_44, __v_181]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_182: G = __r_arr[0]; let __v_183: G = __r_arr[1]; let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_43, __v_181]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_184: G = __r_arr[0]; let __v_185: G = __r_arr[1]; let __v_186: G = __v_182 - __v_184; let __v_187: G = __v_183 - __v_185; let __v_188: G = __v_186 + __v_161; let __v_189: G = __v_187 + __v_164; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_147, __v_148, __v_16, __v_17, __v_188, __v_189]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_190: G = __r_arr[0]; let __v_191: G = __r_arr[1]; let __v_192: G = G::from_u64(1); let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_44, __v_192]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_193: G = __r_arr[0]; let __v_194: G = __r_arr[1]; let __r_arr: [G; OUT_321] = { let __args: [G; IN_321] = [__v_43, __v_192]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(321, &__args[..])?) { [G::ZERO; OUT_321] } else { let (__hash, __hit) = record.function_queries[321].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[321].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[321].bump_multiplicity(__i); } let __ret: [G; OUT_321] = unsafe { *(record.function_queries[321].output_at(__i).as_ptr() as *const [G; OUT_321]) }; __ret }, _ => { aiur_fn_321::(__args, __hash, record, io_buffer)? }, } } }; let __v_195: G = __r_arr[0]; let __v_196: G = __r_arr[1]; let __v_197: G = __v_193 - __v_195; let __v_198: G = __v_194 - __v_196; let __v_199: G = __v_197 + __v_173; let __v_200: G = __v_198 + __v_176; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_190, __v_191, __v_16, __v_17, __v_199, __v_200]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_201: G = __r_arr[0]; let __v_202: G = __r_arr[1]; break '__mc_1 [__v_201, __v_202]; }, 0u64 => { let __v_181: G = G::from_u64(0); let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_182: G = __r_arr[0]; let __v_183: G = G::from_u64(1); let __r_arr: [G; OUT_200] = { let __args: [G; IN_200] = [__v_147, __v_148, __v_16, __v_17, __v_25, __v_181, __v_182, __v_181, __v_26, __v_26, __v_183, __v_181, __v_181, __v_181, __v_181, __v_181, __v_181, __v_181, __v_161, __v_164, __v_173, __v_176, __v_12, __v_70, __v_13, __v_70, __v_14, __v_70, __v_15, __v_70, __v_21, __v_40, __v_41, __v_47, __v_48, __v_43, __v_44, __v_138, __v_97, __v_100, __v_114, __v_117, __v_101, __v_80]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(200, &__args[..])?) { [G::ZERO; OUT_200] } else { let (__hash, __hit) = record.function_queries[200].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[200].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[200].bump_multiplicity(__i); } let __ret: [G; OUT_200] = unsafe { *(record.function_queries[200].output_at(__i).as_ptr() as *const [G; OUT_200]) }; __ret }, _ => { aiur_fn_200::(__args, __hash, record, io_buffer)? }, } } }; let __v_184: G = __r_arr[0]; let __v_185: G = __r_arr[1]; break '__mc_1 [__v_184, __v_185]; }, _ => { return Err(ExecError::MatchNoCase(__v_177.as_canonical_u64())); }, } }; let __v_181: G = __mc_out___mc_1[0]; let __v_182: G = __mc_out___mc_1[1]; let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_8, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_183: G = __r_arr[0]; let __v_184: G = G::from_u64(0); let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_183, __v_184]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __v_185: G = __r_arr[0]; let __r_arr: [G; OUT_195] = { let __args: [G; IN_195] = [__v_185]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(195, &__args[..])?) { [G::ZERO; OUT_195] } else { let (__hash, __hit) = record.function_queries[195].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[195].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[195].bump_multiplicity(__i); } let __ret: [G; OUT_195] = unsafe { *(record.function_queries[195].output_at(__i).as_ptr() as *const [G; OUT_195]) }; __ret }, _ => { aiur_fn_195::(__args, __hash, record, io_buffer)? }, } } }; let __v_186: G = __r_arr[0]; let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_186]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_187: G = __r_arr[0]; let __v_188: G = __v_187 - __v_35; let __v_189: G = G::from_bool(__v_188 == G::ZERO); let __v_190: G = G::from_u64(1); if (__v_189 != __v_190) { return Err(ExecError::AssertEqMismatch { lhs: __v_189.as_canonical_u64(), rhs: __v_190.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_188] = { let __args: [G; IN_188] = [__v_18, __v_19, __v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(188, &__args[..])?) { [G::ZERO; OUT_188] } else { let (__hash, __hit) = record.function_queries[188].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[188].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[188].bump_multiplicity(__i); } let __ret: [G; OUT_188] = unsafe { *(record.function_queries[188].output_at(__i).as_ptr() as *const [G; OUT_188]) }; __ret }, _ => { aiur_fn_188::(__args, __hash, record, io_buffer)? }, } } }; let __v_191: G = __r_arr[0]; let __v_192: G = __r_arr[1]; let __r_arr: [G; OUT_203] = { let __args: [G; IN_203] = [__v_186, __v_191, __v_192, __v_190, __v_184]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(203, &__args[..])?) { [G::ZERO; OUT_203] } else { let (__hash, __hit) = record.function_queries[203].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[203].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[203].bump_multiplicity(__i); } let __ret: [G; OUT_203] = unsafe { *(record.function_queries[203].output_at(__i).as_ptr() as *const [G; OUT_203]) }; __ret }, _ => { aiur_fn_203::(__args, __hash, record, io_buffer)? }, } } }; let __v_193: G = __r_arr[0]; let __v_194: G = __r_arr[1]; let __v_195: G = __v_181 * __v_127; let __v_196: G = G::from_u64(7); let __v_197: G = __v_182 * __v_129; let __v_198: G = __v_196 * __v_197; let __v_199: G = __v_195 + __v_198; let __v_200: G = __v_181 * __v_129; let __v_201: G = __v_182 * __v_127; let __v_202: G = __v_200 + __v_201; let __v_203: G = __v_199 - __v_193; let __v_204: G = G::from_bool(__v_203 == G::ZERO); let __v_205: G = __v_202 - __v_194; let __v_206: G = G::from_bool(__v_205 == G::ZERO); let __v_207: G = __v_204 * __v_206; if (__v_207 != __v_190) { return Err(ExecError::AssertEqMismatch { lhs: __v_207.as_canonical_u64(), rhs: __v_190.as_canonical_u64(), msg: None }); } let __v_208: G = __v_9 + __v_190; let __r_arr: [G; OUT_206] = { let __args: [G; IN_206] = [__v_30, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_208, __v_36, __v_37, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(206, &__args[..])?) { [G::ZERO; OUT_206] } else { let (__hash, __hit) = record.function_queries[206].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[206].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[206].bump_multiplicity(__i); } let __ret: [G; OUT_206] = unsafe { *(record.function_queries[206].output_at(__i).as_ptr() as *const [G; OUT_206]) }; __ret }, _ => { aiur_fn_206::(__args, __hash, record, io_buffer)? }, } } }; let __v_209: G = __r_arr[0]; let __ret: [G; OUT_206] = [__v_209]; record.function_queries[206].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_20.as_canonical_u64())); }, } }) } +fn aiur_fn_206(inp: [G; IN_206], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_206], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __loaded: [G; 11] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(7).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(11))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 11 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 11] = __args[..11].try_into().unwrap(); __arr }; let __v_20: G = __loaded[0]; let __v_21: G = __loaded[1]; let __v_22: G = __loaded[2]; let __v_23: G = __loaded[3]; let __v_24: G = __loaded[4]; let __v_25: G = __loaded[5]; let __v_26: G = __loaded[6]; let __v_27: G = __loaded[7]; let __v_28: G = __loaded[8]; let __v_29: G = __loaded[9]; let __v_30: G = __loaded[10]; match __v_20.as_canonical_u64() { 1u64 => { let __v_31: G = G::from_u64(1); let __ret: [G; OUT_206] = [__v_31]; record.function_queries[206].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_21.as_canonical_u64() { _ => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_2, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_31: G = __r_arr[0]; let __v_32: G = G::from_u64(32); let __v_33: G = if unconstrained { aiur::execute::CodegenBytes2::less_than(&__v_31, &__v_32) } else { aiur::execute::bytes2_less_than_value(__v_31, __v_32, record) }; let __v_34: G = G::from_u64(1); if (__v_33 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_33.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_204] = { let __args: [G; IN_204] = [__v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(204, &__args[..])?) { [G::ZERO; OUT_204] } else { let (__hash, __hit) = record.function_queries[204].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[204].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[204].bump_multiplicity(__i); } let __ret: [G; OUT_204] = unsafe { *(record.function_queries[204].output_at(__i).as_ptr() as *const [G; OUT_204]) }; __ret }, _ => { aiur_fn_204::(__args, __hash, record, io_buffer)? }, } } }; let __v_35: G = __r_arr[0]; let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_3, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_4, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_38: G = __r_arr[0]; let __v_39: G = G::from_u64(0); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_38, __v_39]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_40: G = __r_arr[0]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_38, __v_34]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_41: G = __r_arr[0]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_5, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_42: G = __r_arr[0]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_42, __v_39]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_43: G = __r_arr[0]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_42, __v_34]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_1, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_45: G = __r_arr[0]; let __v_46: G = __r_arr[1]; let __mc_out___mc_0: [G; 2] = '__mc_0: { match __v_45.as_canonical_u64() { 0u64 => { let __v_47: G = G::from_u64(1); let __v_48: G = { let __values: [G; 4] = [__v_47, __v_47, __v_47, __v_47]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; break '__mc_0 [__v_48, __v_48]; }, 1u64 => { match __v_6.as_canonical_u64() { 0u64 => { let __v_47: G = G::from_u64(1); let __v_48: G = { let __values: [G; 4] = [__v_47, __v_47, __v_47, __v_47]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; break '__mc_0 [__v_48, __v_48]; }, 1u64 => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_7, __v_46]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_47: G = __r_arr[0]; let __v_48: G = G::from_u64(0); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_47, __v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __v_50: G = G::from_u64(1); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_47, __v_50]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_51: G = __r_arr[0]; break '__mc_0 [__v_49, __v_51]; }, _ => { return Err(ExecError::MatchNoCase(__v_6.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_45.as_canonical_u64())); }, } }; let __v_47: G = __mc_out___mc_0[0]; let __v_48: G = __mc_out___mc_0[1]; let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __v_50: G = __v_49 - __v_27; let __v_51: G = G::from_bool(__v_50 == G::ZERO); let __v_52: G = G::from_u64(1); if (__v_51 != __v_52) { return Err(ExecError::AssertEqMismatch { lhs: __v_51.as_canonical_u64(), rhs: __v_52.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_41]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; let __v_54: G = __v_53 - __v_27; let __v_55: G = G::from_bool(__v_54 == G::ZERO); if (__v_55 != __v_52) { return Err(ExecError::AssertEqMismatch { lhs: __v_55.as_canonical_u64(), rhs: __v_52.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_56: G = __r_arr[0]; let __v_57: G = __v_56 - __v_29; let __v_58: G = G::from_bool(__v_57 == G::ZERO); if (__v_58 != __v_52) { return Err(ExecError::AssertEqMismatch { lhs: __v_58.as_canonical_u64(), rhs: __v_52.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_44]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_59: G = __r_arr[0]; let __v_60: G = __v_59 - __v_29; let __v_61: G = G::from_bool(__v_60 == G::ZERO); if (__v_61 != __v_52) { return Err(ExecError::AssertEqMismatch { lhs: __v_61.as_canonical_u64(), rhs: __v_52.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_47]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_62: G = __r_arr[0]; let __v_63: G = __v_62 - __v_28; let __v_64: G = G::from_bool(__v_63 == G::ZERO); if (__v_64 != __v_52) { return Err(ExecError::AssertEqMismatch { lhs: __v_64.as_canonical_u64(), rhs: __v_52.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_65: G = __r_arr[0]; let __v_66: G = __v_65 - __v_28; let __v_67: G = G::from_bool(__v_66 == G::ZERO); if (__v_67 != __v_52) { return Err(ExecError::AssertEqMismatch { lhs: __v_67.as_canonical_u64(), rhs: __v_52.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_188] = { let __args: [G; IN_188] = [__v_18, __v_19, __v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(188, &__args[..])?) { [G::ZERO; OUT_188] } else { let (__hash, __hit) = record.function_queries[188].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[188].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[188].bump_multiplicity(__i); } let __ret: [G; OUT_188] = unsafe { *(record.function_queries[188].output_at(__i).as_ptr() as *const [G; OUT_188]) }; __ret }, _ => { aiur_fn_188::(__args, __hash, record, io_buffer)? }, } } }; let __v_68: G = __r_arr[0]; let __v_69: G = __r_arr[1]; let __v_70: G = G::from_u64(0); let __v_71: G = __v_68 - __v_52; let __v_72: G = __v_69 - __v_70; let __r_arr: [G; OUT_189] = { let __args: [G; IN_189] = [__v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(189, &__args[..])?) { [G::ZERO; OUT_189] } else { let (__hash, __hit) = record.function_queries[189].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[189].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[189].bump_multiplicity(__i); } let __ret: [G; OUT_189] = unsafe { *(record.function_queries[189].output_at(__i).as_ptr() as *const [G; OUT_189]) }; __ret }, _ => { aiur_fn_189::(__args, __hash, record, io_buffer)? }, } } }; let __v_73: G = __r_arr[0]; let __v_74: G = aiur::execute::g_inverse_value(__v_73); let __v_75: G = __v_73 * __v_74; let __v_76: G = __v_75 - __v_52; let __v_77: G = __v_73 * __v_76; if (__v_77 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_77.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_78: G = __v_74 * __v_76; if (__v_78 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_78.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_79: G = __v_18 - __v_52; let __v_80: G = __v_19 - __v_70; let __v_81: G = __v_79 * __v_79; let __v_82: G = G::from_u64(7); let __v_83: G = __v_80 * __v_80; let __v_84: G = __v_82 * __v_83; let __v_85: G = __v_81 - __v_84; let __v_86: G = aiur::execute::g_inverse_value(__v_85); let __v_87: G = __v_85 * __v_86; let __v_88: G = __v_87 - __v_52; let __v_89: G = __v_85 * __v_88; if (__v_89 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_89.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_90: G = __v_86 * __v_88; if (__v_90 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_90.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_91: G = __v_79 * __v_86; let __v_92: G = __v_70 - __v_80; let __v_93: G = __v_92 * __v_86; let __v_94: G = __v_71 * __v_91; let __v_95: G = __v_72 * __v_93; let __v_96: G = __v_82 * __v_95; let __v_97: G = __v_94 + __v_96; let __v_98: G = __v_71 * __v_93; let __v_99: G = __v_72 * __v_91; let __v_100: G = __v_98 + __v_99; let __v_101: G = __v_18 - __v_74; let __v_102: G = __v_101 * __v_101; let __v_103: G = __v_102 - __v_84; let __v_104: G = aiur::execute::g_inverse_value(__v_103); let __v_105: G = __v_103 * __v_104; let __v_106: G = __v_105 - __v_52; let __v_107: G = __v_103 * __v_106; if (__v_107 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_107.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_108: G = __v_104 * __v_106; if (__v_108 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_108.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_109: G = __v_101 * __v_104; let __v_110: G = __v_92 * __v_104; let __v_111: G = __v_71 * __v_109; let __v_112: G = __v_72 * __v_110; let __v_113: G = __v_82 * __v_112; let __v_114: G = __v_111 + __v_113; let __v_115: G = __v_71 * __v_110; let __v_116: G = __v_72 * __v_109; let __v_117: G = __v_115 + __v_116; let __v_118: G = __v_71 * __v_71; let __v_119: G = __v_72 * __v_72; let __v_120: G = __v_82 * __v_119; let __v_121: G = __v_118 - __v_120; let __v_122: G = aiur::execute::g_inverse_value(__v_121); let __v_123: G = __v_121 * __v_122; let __v_124: G = __v_123 - __v_52; let __v_125: G = __v_121 * __v_124; if (__v_125 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_125.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_126: G = __v_122 * __v_124; if (__v_126 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_126.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_127: G = __v_71 * __v_122; let __v_128: G = __v_70 - __v_72; let __v_129: G = __v_128 * __v_122; let __v_130: G = { let __values: [G; 4] = [__v_52, __v_52, __v_52, __v_52]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_131: G = { let __values: [G; 4] = [__v_70, __v_37, __v_70, __v_130]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_132: G = { let __values: [G; 4] = [__v_70, __v_36, __v_70, __v_131]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_133: G = { let __values: [G; 4] = [__v_70, __v_11, __v_70, __v_132]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_134: G = { let __values: [G; 4] = [__v_70, __v_10, __v_70, __v_133]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_135: G = { let __values: [G; 4] = [__v_70, __v_15, __v_70, __v_134]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_136: G = { let __values: [G; 4] = [__v_70, __v_14, __v_70, __v_135]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_137: G = { let __values: [G; 4] = [__v_70, __v_13, __v_70, __v_136]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_138: G = { let __values: [G; 4] = [__v_70, __v_12, __v_70, __v_137]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 4)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_191] = { let __args: [G; IN_191] = [__v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(191, &__args[..])?) { [G::ZERO; OUT_191] } else { let (__hash, __hit) = record.function_queries[191].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[191].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[191].bump_multiplicity(__i); } let __ret: [G; OUT_191] = unsafe { *(record.function_queries[191].output_at(__i).as_ptr() as *const [G; OUT_191]) }; __ret }, _ => { aiur_fn_191::(__args, __hash, record, io_buffer)? }, } } }; let __v_139: G = __r_arr[0]; let __r_arr: [G; OUT_189] = { let __args: [G; IN_189] = [__v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(189, &__args[..])?) { [G::ZERO; OUT_189] } else { let (__hash, __hit) = record.function_queries[189].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[189].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[189].bump_multiplicity(__i); } let __ret: [G; OUT_189] = unsafe { *(record.function_queries[189].output_at(__i).as_ptr() as *const [G; OUT_189]) }; __ret }, _ => { aiur_fn_189::(__args, __hash, record, io_buffer)? }, } } }; let __v_140: G = __r_arr[0]; let __v_141: G = __v_139 * __v_140; let __v_142: G = aiur::execute::g_inverse_value(__v_141); let __v_143: G = __v_141 * __v_142; let __v_144: G = __v_143 - __v_52; let __v_145: G = __v_141 * __v_144; if (__v_145 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_145.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __v_146: G = __v_142 * __v_144; if (__v_146 != __v_70) { return Err(ExecError::AssertEqMismatch { lhs: __v_146.as_canonical_u64(), rhs: __v_70.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_197] = { let __args: [G; IN_197] = [__v_70, __v_70, __v_16, __v_17, __v_23, __v_21, __v_40, __v_41, __v_47, __v_48, __v_43, __v_44, __v_138, __v_97, __v_100, __v_114, __v_117, __v_101, __v_80]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(197, &__args[..])?) { [G::ZERO; OUT_197] } else { let (__hash, __hit) = record.function_queries[197].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[197].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[197].bump_multiplicity(__i); } let __ret: [G; OUT_197] = unsafe { *(record.function_queries[197].output_at(__i).as_ptr() as *const [G; OUT_197]) }; __ret }, _ => { aiur_fn_197::(__args, __hash, record, io_buffer)? }, } } }; let __v_147: G = __r_arr[0]; let __v_148: G = __r_arr[1]; let __v_149: G = __v_36 - __v_10; let __v_150: G = __v_70 - __v_70; let __v_151: G = __v_149 * __v_142; let __v_152: G = __v_150 * __v_70; let __v_153: G = __v_82 * __v_152; let __v_154: G = __v_151 + __v_153; let __v_155: G = __v_149 * __v_70; let __v_156: G = __v_150 * __v_142; let __v_157: G = __v_155 + __v_156; let __v_158: G = __v_114 * __v_154; let __v_159: G = __v_117 * __v_157; let __v_160: G = __v_82 * __v_159; let __v_161: G = __v_158 + __v_160; let __v_162: G = __v_114 * __v_157; let __v_163: G = __v_117 * __v_154; let __v_164: G = __v_162 + __v_163; let __v_165: G = __v_37 - __v_11; let __v_166: G = __v_165 * __v_142; let __v_167: G = __v_166 + __v_153; let __v_168: G = __v_165 * __v_70; let __v_169: G = __v_168 + __v_156; let __v_170: G = __v_114 * __v_167; let __v_171: G = __v_117 * __v_169; let __v_172: G = __v_82 * __v_171; let __v_173: G = __v_170 + __v_172; let __v_174: G = __v_114 * __v_169; let __v_175: G = __v_117 * __v_167; let __v_176: G = __v_174 + __v_175; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_25.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_177: G = __loaded[0]; let __v_178: G = __loaded[1]; let __v_179: G = __loaded[2]; let __v_180: G = __loaded[3]; let __mc_out___mc_1: [G; 2] = '__mc_1: { match __v_177.as_canonical_u64() { 1u64 => { let __v_181: G = G::from_u64(0); let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_44, __v_181]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_182: G = __r_arr[0]; let __v_183: G = __r_arr[1]; let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_43, __v_181]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_184: G = __r_arr[0]; let __v_185: G = __r_arr[1]; let __v_186: G = __v_182 - __v_184; let __v_187: G = __v_183 - __v_185; let __v_188: G = __v_186 + __v_161; let __v_189: G = __v_187 + __v_164; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_147, __v_148, __v_16, __v_17, __v_188, __v_189]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_190: G = __r_arr[0]; let __v_191: G = __r_arr[1]; let __v_192: G = G::from_u64(1); let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_44, __v_192]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_193: G = __r_arr[0]; let __v_194: G = __r_arr[1]; let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_43, __v_192]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_195: G = __r_arr[0]; let __v_196: G = __r_arr[1]; let __v_197: G = __v_193 - __v_195; let __v_198: G = __v_194 - __v_196; let __v_199: G = __v_197 + __v_173; let __v_200: G = __v_198 + __v_176; let __r_arr: [G; OUT_193] = { let __args: [G; IN_193] = [__v_190, __v_191, __v_16, __v_17, __v_199, __v_200]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(193, &__args[..])?) { [G::ZERO; OUT_193] } else { let (__hash, __hit) = record.function_queries[193].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[193].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[193].bump_multiplicity(__i); } let __ret: [G; OUT_193] = unsafe { *(record.function_queries[193].output_at(__i).as_ptr() as *const [G; OUT_193]) }; __ret }, _ => { aiur_fn_193::(__args, __hash, record, io_buffer)? }, } } }; let __v_201: G = __r_arr[0]; let __v_202: G = __r_arr[1]; break '__mc_1 [__v_201, __v_202]; }, 0u64 => { let __v_181: G = G::from_u64(0); let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_182: G = __r_arr[0]; let __v_183: G = G::from_u64(1); let __r_arr: [G; OUT_200] = { let __args: [G; IN_200] = [__v_147, __v_148, __v_16, __v_17, __v_25, __v_181, __v_182, __v_181, __v_26, __v_26, __v_183, __v_181, __v_181, __v_181, __v_181, __v_181, __v_181, __v_181, __v_161, __v_164, __v_173, __v_176, __v_12, __v_70, __v_13, __v_70, __v_14, __v_70, __v_15, __v_70, __v_21, __v_40, __v_41, __v_47, __v_48, __v_43, __v_44, __v_138, __v_97, __v_100, __v_114, __v_117, __v_101, __v_80]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(200, &__args[..])?) { [G::ZERO; OUT_200] } else { let (__hash, __hit) = record.function_queries[200].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[200].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[200].bump_multiplicity(__i); } let __ret: [G; OUT_200] = unsafe { *(record.function_queries[200].output_at(__i).as_ptr() as *const [G; OUT_200]) }; __ret }, _ => { aiur_fn_200::(__args, __hash, record, io_buffer)? }, } } }; let __v_184: G = __r_arr[0]; let __v_185: G = __r_arr[1]; break '__mc_1 [__v_184, __v_185]; }, _ => { return Err(ExecError::MatchNoCase(__v_177.as_canonical_u64())); }, } }; let __v_181: G = __mc_out___mc_1[0]; let __v_182: G = __mc_out___mc_1[1]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_8, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_183: G = __r_arr[0]; let __v_184: G = G::from_u64(0); let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_183, __v_184]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_185: G = __r_arr[0]; let __r_arr: [G; OUT_195] = { let __args: [G; IN_195] = [__v_185]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(195, &__args[..])?) { [G::ZERO; OUT_195] } else { let (__hash, __hit) = record.function_queries[195].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[195].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[195].bump_multiplicity(__i); } let __ret: [G; OUT_195] = unsafe { *(record.function_queries[195].output_at(__i).as_ptr() as *const [G; OUT_195]) }; __ret }, _ => { aiur_fn_195::(__args, __hash, record, io_buffer)? }, } } }; let __v_186: G = __r_arr[0]; let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_186]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_187: G = __r_arr[0]; let __v_188: G = __v_187 - __v_35; let __v_189: G = G::from_bool(__v_188 == G::ZERO); let __v_190: G = G::from_u64(1); if (__v_189 != __v_190) { return Err(ExecError::AssertEqMismatch { lhs: __v_189.as_canonical_u64(), rhs: __v_190.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_188] = { let __args: [G; IN_188] = [__v_18, __v_19, __v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(188, &__args[..])?) { [G::ZERO; OUT_188] } else { let (__hash, __hit) = record.function_queries[188].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[188].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[188].bump_multiplicity(__i); } let __ret: [G; OUT_188] = unsafe { *(record.function_queries[188].output_at(__i).as_ptr() as *const [G; OUT_188]) }; __ret }, _ => { aiur_fn_188::(__args, __hash, record, io_buffer)? }, } } }; let __v_191: G = __r_arr[0]; let __v_192: G = __r_arr[1]; let __r_arr: [G; OUT_203] = { let __args: [G; IN_203] = [__v_186, __v_191, __v_192, __v_190, __v_184]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(203, &__args[..])?) { [G::ZERO; OUT_203] } else { let (__hash, __hit) = record.function_queries[203].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[203].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[203].bump_multiplicity(__i); } let __ret: [G; OUT_203] = unsafe { *(record.function_queries[203].output_at(__i).as_ptr() as *const [G; OUT_203]) }; __ret }, _ => { aiur_fn_203::(__args, __hash, record, io_buffer)? }, } } }; let __v_193: G = __r_arr[0]; let __v_194: G = __r_arr[1]; let __v_195: G = __v_181 * __v_127; let __v_196: G = G::from_u64(7); let __v_197: G = __v_182 * __v_129; let __v_198: G = __v_196 * __v_197; let __v_199: G = __v_195 + __v_198; let __v_200: G = __v_181 * __v_129; let __v_201: G = __v_182 * __v_127; let __v_202: G = __v_200 + __v_201; let __v_203: G = __v_199 - __v_193; let __v_204: G = G::from_bool(__v_203 == G::ZERO); let __v_205: G = __v_202 - __v_194; let __v_206: G = G::from_bool(__v_205 == G::ZERO); let __v_207: G = __v_204 * __v_206; if (__v_207 != __v_190) { return Err(ExecError::AssertEqMismatch { lhs: __v_207.as_canonical_u64(), rhs: __v_190.as_canonical_u64(), msg: None }); } let __v_208: G = __v_9 + __v_190; let __r_arr: [G; OUT_206] = { let __args: [G; IN_206] = [__v_30, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_208, __v_36, __v_37, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(206, &__args[..])?) { [G::ZERO; OUT_206] } else { let (__hash, __hit) = record.function_queries[206].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[206].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[206].bump_multiplicity(__i); } let __ret: [G; OUT_206] = unsafe { *(record.function_queries[206].output_at(__i).as_ptr() as *const [G; OUT_206]) }; __ret }, _ => { aiur_fn_206::(__args, __hash, record, io_buffer)? }, } } }; let __v_209: G = __r_arr[0]; let __ret: [G; OUT_206] = [__v_209]; record.function_queries[206].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_20.as_canonical_u64())); }, } }) } const INPUT_SIZE_207: usize = 3; const IN_207: usize = 3; const OUT_207: usize = 2; @@ -864,15 +864,15 @@ fn aiur_fn_212(inp: [G; IN_212], input_hash: u64, rec const INPUT_SIZE_213: usize = 2; const IN_213: usize = 2; const OUT_213: usize = 1; -fn aiur_fn_213(inp: [G; IN_213], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_213], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __r_arr: [G; OUT_214] = { let __args: [G; IN_214] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(214, &__args[..])?) { [G::ZERO; OUT_214] } else { let (__hash, __hit) = record.function_queries[214].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[214].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[214].bump_multiplicity(__i); } let __ret: [G; OUT_214] = unsafe { *(record.function_queries[214].output_at(__i).as_ptr() as *const [G; OUT_214]) }; __ret }, _ => { aiur_fn_214::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __r_arr: [G; OUT_212] = { let __args: [G; IN_212] = [__v_2, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(212, &__args[..])?) { [G::ZERO; OUT_212] } else { let (__hash, __hit) = record.function_queries[212].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[212].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[212].bump_multiplicity(__i); } let __ret: [G; OUT_212] = unsafe { *(record.function_queries[212].output_at(__i).as_ptr() as *const [G; OUT_212]) }; __ret }, _ => { aiur_fn_212::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __ret: [G; OUT_213] = [__v_4]; record.function_queries[213].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +fn aiur_fn_213(inp: [G; IN_213], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_213], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __r_arr: [G; OUT_214] = { let __args: [G; IN_214] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(214, &__args[..])?) { [G::ZERO; OUT_214] } else { let (__hash, __hit) = record.function_queries[214].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[214].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[214].bump_multiplicity(__i); } let __ret: [G; OUT_214] = unsafe { *(record.function_queries[214].output_at(__i).as_ptr() as *const [G; OUT_214]) }; __ret }, _ => { aiur_fn_214::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __r_arr: [G; OUT_212] = { let __args: [G; IN_212] = [__v_2, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(212, &__args[..])?) { [G::ZERO; OUT_212] } else { let (__hash, __hit) = record.function_queries[212].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[212].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[212].bump_multiplicity(__i); } let __ret: [G; OUT_212] = unsafe { *(record.function_queries[212].output_at(__i).as_ptr() as *const [G; OUT_212]) }; __ret }, _ => { aiur_fn_212::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __ret: [G; OUT_213] = [__v_4]; record.function_queries[213].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } const INPUT_SIZE_214: usize = 2; const IN_214: usize = 2; const OUT_214: usize = 1; -fn aiur_fn_214(inp: [G; IN_214], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_214], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; match __v_2.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_214] = [__v_1]; record.function_queries[214].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_317] = { let __args: [G; IN_317] = [__v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(317, &__args[..])?) { [G::ZERO; OUT_317] } else { let (__hash, __hit) = record.function_queries[317].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[317].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[317].bump_multiplicity(__i); } let __ret: [G; OUT_317] = unsafe { *(record.function_queries[317].output_at(__i).as_ptr() as *const [G; OUT_317]) }; __ret }, _ => { aiur_fn_317::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __r_arr: [G; OUT_214] = { let __args: [G; IN_214] = [__v_4, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(214, &__args[..])?) { [G::ZERO; OUT_214] } else { let (__hash, __hit) = record.function_queries[214].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[214].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[214].bump_multiplicity(__i); } let __ret: [G; OUT_214] = unsafe { *(record.function_queries[214].output_at(__i).as_ptr() as *const [G; OUT_214]) }; __ret }, _ => { aiur_fn_214::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __r_arr: [G; OUT_186] = { let __args: [G; IN_186] = [__v_3, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(186, &__args[..])?) { [G::ZERO; OUT_186] } else { let (__hash, __hit) = record.function_queries[186].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[186].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[186].bump_multiplicity(__i); } let __ret: [G; OUT_186] = unsafe { *(record.function_queries[186].output_at(__i).as_ptr() as *const [G; OUT_186]) }; __ret }, _ => { aiur_fn_186::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __r_arr: [G; OUT_212] = { let __args: [G; IN_212] = [__v_5, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(212, &__args[..])?) { [G::ZERO; OUT_212] } else { let (__hash, __hit) = record.function_queries[212].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[212].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[212].bump_multiplicity(__i); } let __ret: [G; OUT_212] = unsafe { *(record.function_queries[212].output_at(__i).as_ptr() as *const [G; OUT_212]) }; __ret }, _ => { aiur_fn_212::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __ret: [G; OUT_214] = [__v_8]; record.function_queries[214].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } +fn aiur_fn_214(inp: [G; IN_214], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_214], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; match __v_2.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_214] = [__v_1]; record.function_queries[214].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_319] = { let __args: [G; IN_319] = [__v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(319, &__args[..])?) { [G::ZERO; OUT_319] } else { let (__hash, __hit) = record.function_queries[319].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[319].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[319].bump_multiplicity(__i); } let __ret: [G; OUT_319] = unsafe { *(record.function_queries[319].output_at(__i).as_ptr() as *const [G; OUT_319]) }; __ret }, _ => { aiur_fn_319::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __r_arr: [G; OUT_214] = { let __args: [G; IN_214] = [__v_4, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(214, &__args[..])?) { [G::ZERO; OUT_214] } else { let (__hash, __hit) = record.function_queries[214].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[214].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[214].bump_multiplicity(__i); } let __ret: [G; OUT_214] = unsafe { *(record.function_queries[214].output_at(__i).as_ptr() as *const [G; OUT_214]) }; __ret }, _ => { aiur_fn_214::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __r_arr: [G; OUT_186] = { let __args: [G; IN_186] = [__v_3, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(186, &__args[..])?) { [G::ZERO; OUT_186] } else { let (__hash, __hit) = record.function_queries[186].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[186].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[186].bump_multiplicity(__i); } let __ret: [G; OUT_186] = unsafe { *(record.function_queries[186].output_at(__i).as_ptr() as *const [G; OUT_186]) }; __ret }, _ => { aiur_fn_186::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __r_arr: [G; OUT_212] = { let __args: [G; IN_212] = [__v_5, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(212, &__args[..])?) { [G::ZERO; OUT_212] } else { let (__hash, __hit) = record.function_queries[212].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[212].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[212].bump_multiplicity(__i); } let __ret: [G; OUT_212] = unsafe { *(record.function_queries[212].output_at(__i).as_ptr() as *const [G; OUT_212]) }; __ret }, _ => { aiur_fn_212::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __ret: [G; OUT_214] = [__v_8]; record.function_queries[214].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } const INPUT_SIZE_215: usize = 5; const IN_215: usize = 5; const OUT_215: usize = 1; -fn aiur_fn_215(inp: [G; IN_215], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_215], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; match __v_0.as_canonical_u64() { _ => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __r_arr: [G; OUT_213] = { let __args: [G; IN_213] = [__v_3, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(213, &__args[..])?) { [G::ZERO; OUT_213] } else { let (__hash, __hit) = record.function_queries[213].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[213].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[213].bump_multiplicity(__i); } let __ret: [G; OUT_213] = unsafe { *(record.function_queries[213].output_at(__i).as_ptr() as *const [G; OUT_213]) }; __ret }, _ => { aiur_fn_213::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __r_arr: [G; OUT_177] = { let __args: [G; IN_177] = [__v_2, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(177, &__args[..])?) { [G::ZERO; OUT_177] } else { let (__hash, __hit) = record.function_queries[177].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[177].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[177].bump_multiplicity(__i); } let __ret: [G; OUT_177] = unsafe { *(record.function_queries[177].output_at(__i).as_ptr() as *const [G; OUT_177]) }; __ret }, _ => { aiur_fn_177::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __r_arr: [G; OUT_212] = { let __args: [G; IN_212] = [__v_5, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(212, &__args[..])?) { [G::ZERO; OUT_212] } else { let (__hash, __hit) = record.function_queries[212].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[212].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[212].bump_multiplicity(__i); } let __ret: [G; OUT_212] = unsafe { *(record.function_queries[212].output_at(__i).as_ptr() as *const [G; OUT_212]) }; __ret }, _ => { aiur_fn_212::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __r_arr: [G; OUT_176] = { let __args: [G; IN_176] = [__v_1, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(176, &__args[..])?) { [G::ZERO; OUT_176] } else { let (__hash, __hit) = record.function_queries[176].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[176].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[176].bump_multiplicity(__i); } let __ret: [G; OUT_176] = unsafe { *(record.function_queries[176].output_at(__i).as_ptr() as *const [G; OUT_176]) }; __ret }, _ => { aiur_fn_176::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __r_arr: [G; OUT_177] = { let __args: [G; IN_177] = [__v_0, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(177, &__args[..])?) { [G::ZERO; OUT_177] } else { let (__hash, __hit) = record.function_queries[177].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[177].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[177].bump_multiplicity(__i); } let __ret: [G; OUT_177] = unsafe { *(record.function_queries[177].output_at(__i).as_ptr() as *const [G; OUT_177]) }; __ret }, _ => { aiur_fn_177::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __ret: [G; OUT_215] = [__v_10]; record.function_queries[215].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } +fn aiur_fn_215(inp: [G; IN_215], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_215], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; match __v_0.as_canonical_u64() { _ => { let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __r_arr: [G; OUT_213] = { let __args: [G; IN_213] = [__v_3, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(213, &__args[..])?) { [G::ZERO; OUT_213] } else { let (__hash, __hit) = record.function_queries[213].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[213].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[213].bump_multiplicity(__i); } let __ret: [G; OUT_213] = unsafe { *(record.function_queries[213].output_at(__i).as_ptr() as *const [G; OUT_213]) }; __ret }, _ => { aiur_fn_213::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __r_arr: [G; OUT_177] = { let __args: [G; IN_177] = [__v_2, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(177, &__args[..])?) { [G::ZERO; OUT_177] } else { let (__hash, __hit) = record.function_queries[177].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[177].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[177].bump_multiplicity(__i); } let __ret: [G; OUT_177] = unsafe { *(record.function_queries[177].output_at(__i).as_ptr() as *const [G; OUT_177]) }; __ret }, _ => { aiur_fn_177::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __r_arr: [G; OUT_212] = { let __args: [G; IN_212] = [__v_5, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(212, &__args[..])?) { [G::ZERO; OUT_212] } else { let (__hash, __hit) = record.function_queries[212].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[212].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[212].bump_multiplicity(__i); } let __ret: [G; OUT_212] = unsafe { *(record.function_queries[212].output_at(__i).as_ptr() as *const [G; OUT_212]) }; __ret }, _ => { aiur_fn_212::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __r_arr: [G; OUT_176] = { let __args: [G; IN_176] = [__v_1, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(176, &__args[..])?) { [G::ZERO; OUT_176] } else { let (__hash, __hit) = record.function_queries[176].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[176].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[176].bump_multiplicity(__i); } let __ret: [G; OUT_176] = unsafe { *(record.function_queries[176].output_at(__i).as_ptr() as *const [G; OUT_176]) }; __ret }, _ => { aiur_fn_176::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __r_arr: [G; OUT_177] = { let __args: [G; IN_177] = [__v_0, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(177, &__args[..])?) { [G::ZERO; OUT_177] } else { let (__hash, __hit) = record.function_queries[177].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[177].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[177].bump_multiplicity(__i); } let __ret: [G; OUT_177] = unsafe { *(record.function_queries[177].output_at(__i).as_ptr() as *const [G; OUT_177]) }; __ret }, _ => { aiur_fn_177::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __ret: [G; OUT_215] = [__v_10]; record.function_queries[215].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } const INPUT_SIZE_216: usize = 2; const IN_216: usize = 2; const OUT_216: usize = 1; @@ -880,19 +880,19 @@ fn aiur_fn_216(inp: [G; IN_216], input_hash: u64, rec const INPUT_SIZE_217: usize = 2; const IN_217: usize = 2; const OUT_217: usize = 1; -fn aiur_fn_217(inp: [G; IN_217], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_217], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_318] = { let __args: [G; IN_318] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(318, &__args[..])?) { [G::ZERO; OUT_318] } else { let (__hash, __hit) = record.function_queries[318].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[318].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[318].bump_multiplicity(__i); } let __ret: [G; OUT_318] = unsafe { *(record.function_queries[318].output_at(__i).as_ptr() as *const [G; OUT_318]) }; __ret }, _ => { aiur_fn_318::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __r_arr: [G; OUT_218] = { let __args: [G; IN_218] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(218, &__args[..])?) { [G::ZERO; OUT_218] } else { let (__hash, __hit) = record.function_queries[218].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[218].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[218].bump_multiplicity(__i); } let __ret: [G; OUT_218] = unsafe { *(record.function_queries[218].output_at(__i).as_ptr() as *const [G; OUT_218]) }; __ret }, _ => { aiur_fn_218::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __r_arr: [G; OUT_212] = { let __args: [G; IN_212] = [__v_2, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(212, &__args[..])?) { [G::ZERO; OUT_212] } else { let (__hash, __hit) = record.function_queries[212].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[212].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[212].bump_multiplicity(__i); } let __ret: [G; OUT_212] = unsafe { *(record.function_queries[212].output_at(__i).as_ptr() as *const [G; OUT_212]) }; __ret }, _ => { aiur_fn_212::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __ret: [G; OUT_217] = [__v_4]; record.function_queries[217].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +fn aiur_fn_217(inp: [G; IN_217], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_217], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_320] = { let __args: [G; IN_320] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(320, &__args[..])?) { [G::ZERO; OUT_320] } else { let (__hash, __hit) = record.function_queries[320].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[320].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[320].bump_multiplicity(__i); } let __ret: [G; OUT_320] = unsafe { *(record.function_queries[320].output_at(__i).as_ptr() as *const [G; OUT_320]) }; __ret }, _ => { aiur_fn_320::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __r_arr: [G; OUT_218] = { let __args: [G; IN_218] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(218, &__args[..])?) { [G::ZERO; OUT_218] } else { let (__hash, __hit) = record.function_queries[218].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[218].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[218].bump_multiplicity(__i); } let __ret: [G; OUT_218] = unsafe { *(record.function_queries[218].output_at(__i).as_ptr() as *const [G; OUT_218]) }; __ret }, _ => { aiur_fn_218::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __r_arr: [G; OUT_212] = { let __args: [G; IN_212] = [__v_2, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(212, &__args[..])?) { [G::ZERO; OUT_212] } else { let (__hash, __hit) = record.function_queries[212].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[212].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[212].bump_multiplicity(__i); } let __ret: [G; OUT_212] = unsafe { *(record.function_queries[212].output_at(__i).as_ptr() as *const [G; OUT_212]) }; __ret }, _ => { aiur_fn_212::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __ret: [G; OUT_217] = [__v_4]; record.function_queries[217].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } const INPUT_SIZE_218: usize = 2; const IN_218: usize = 2; const OUT_218: usize = 1; -fn aiur_fn_218(inp: [G; IN_218], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_218], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 11] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(7).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(11))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 11 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 11] = __args[..11].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; let __v_5: G = __loaded[3]; let __v_6: G = __loaded[4]; let __v_7: G = __loaded[5]; let __v_8: G = __loaded[6]; let __v_9: G = __loaded[7]; let __v_10: G = __loaded[8]; let __v_11: G = __loaded[9]; let __v_12: G = __loaded[10]; match __v_2.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_218] = [__v_1]; record.function_queries[218].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_3.as_canonical_u64() { _ => { let __r_arr: [G; OUT_317] = { let __args: [G; IN_317] = [__v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(317, &__args[..])?) { [G::ZERO; OUT_317] } else { let (__hash, __hit) = record.function_queries[317].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[317].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[317].bump_multiplicity(__i); } let __ret: [G; OUT_317] = unsafe { *(record.function_queries[317].output_at(__i).as_ptr() as *const [G; OUT_317]) }; __ret }, _ => { aiur_fn_317::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __r_arr: [G; OUT_218] = { let __args: [G; IN_218] = [__v_12, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(218, &__args[..])?) { [G::ZERO; OUT_218] } else { let (__hash, __hit) = record.function_queries[218].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[218].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[218].bump_multiplicity(__i); } let __ret: [G; OUT_218] = unsafe { *(record.function_queries[218].output_at(__i).as_ptr() as *const [G; OUT_218]) }; __ret }, _ => { aiur_fn_218::(__args, __hash, record, io_buffer)? }, } } }; let __v_14: G = __r_arr[0]; let __r_arr: [G; OUT_174] = { let __args: [G; IN_174] = [__v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(174, &__args[..])?) { [G::ZERO; OUT_174] } else { let (__hash, __hit) = record.function_queries[174].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[174].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[174].bump_multiplicity(__i); } let __ret: [G; OUT_174] = unsafe { *(record.function_queries[174].output_at(__i).as_ptr() as *const [G; OUT_174]) }; __ret }, _ => { aiur_fn_174::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __r_arr: [G; OUT_186] = { let __args: [G; IN_186] = [__v_3, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(186, &__args[..])?) { [G::ZERO; OUT_186] } else { let (__hash, __hit) = record.function_queries[186].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[186].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[186].bump_multiplicity(__i); } let __ret: [G; OUT_186] = unsafe { *(record.function_queries[186].output_at(__i).as_ptr() as *const [G; OUT_186]) }; __ret }, _ => { aiur_fn_186::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __r_arr: [G; OUT_212] = { let __args: [G; IN_212] = [__v_13, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(212, &__args[..])?) { [G::ZERO; OUT_212] } else { let (__hash, __hit) = record.function_queries[212].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[212].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[212].bump_multiplicity(__i); } let __ret: [G; OUT_212] = unsafe { *(record.function_queries[212].output_at(__i).as_ptr() as *const [G; OUT_212]) }; __ret }, _ => { aiur_fn_212::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __ret: [G; OUT_218] = [__v_17]; record.function_queries[218].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } +fn aiur_fn_218(inp: [G; IN_218], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_218], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 11] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(7).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(11))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 11 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 11] = __args[..11].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; let __v_5: G = __loaded[3]; let __v_6: G = __loaded[4]; let __v_7: G = __loaded[5]; let __v_8: G = __loaded[6]; let __v_9: G = __loaded[7]; let __v_10: G = __loaded[8]; let __v_11: G = __loaded[9]; let __v_12: G = __loaded[10]; match __v_2.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_218] = [__v_1]; record.function_queries[218].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_3.as_canonical_u64() { _ => { let __r_arr: [G; OUT_319] = { let __args: [G; IN_319] = [__v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(319, &__args[..])?) { [G::ZERO; OUT_319] } else { let (__hash, __hit) = record.function_queries[319].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[319].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[319].bump_multiplicity(__i); } let __ret: [G; OUT_319] = unsafe { *(record.function_queries[319].output_at(__i).as_ptr() as *const [G; OUT_319]) }; __ret }, _ => { aiur_fn_319::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __r_arr: [G; OUT_218] = { let __args: [G; IN_218] = [__v_12, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(218, &__args[..])?) { [G::ZERO; OUT_218] } else { let (__hash, __hit) = record.function_queries[218].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[218].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[218].bump_multiplicity(__i); } let __ret: [G; OUT_218] = unsafe { *(record.function_queries[218].output_at(__i).as_ptr() as *const [G; OUT_218]) }; __ret }, _ => { aiur_fn_218::(__args, __hash, record, io_buffer)? }, } } }; let __v_14: G = __r_arr[0]; let __r_arr: [G; OUT_174] = { let __args: [G; IN_174] = [__v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(174, &__args[..])?) { [G::ZERO; OUT_174] } else { let (__hash, __hit) = record.function_queries[174].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[174].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[174].bump_multiplicity(__i); } let __ret: [G; OUT_174] = unsafe { *(record.function_queries[174].output_at(__i).as_ptr() as *const [G; OUT_174]) }; __ret }, _ => { aiur_fn_174::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __r_arr: [G; OUT_186] = { let __args: [G; IN_186] = [__v_3, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(186, &__args[..])?) { [G::ZERO; OUT_186] } else { let (__hash, __hit) = record.function_queries[186].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[186].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[186].bump_multiplicity(__i); } let __ret: [G; OUT_186] = unsafe { *(record.function_queries[186].output_at(__i).as_ptr() as *const [G; OUT_186]) }; __ret }, _ => { aiur_fn_186::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __r_arr: [G; OUT_212] = { let __args: [G; IN_212] = [__v_13, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(212, &__args[..])?) { [G::ZERO; OUT_212] } else { let (__hash, __hit) = record.function_queries[212].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[212].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[212].bump_multiplicity(__i); } let __ret: [G; OUT_212] = unsafe { *(record.function_queries[212].output_at(__i).as_ptr() as *const [G; OUT_212]) }; __ret }, _ => { aiur_fn_212::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __ret: [G; OUT_218] = [__v_17]; record.function_queries[218].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } const INPUT_SIZE_219: usize = 4; const IN_219: usize = 4; const OUT_219: usize = 5; -fn aiur_fn_219(inp: [G; IN_219], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_219], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = G::from_u64(1); let __v_5: G = { let __values: [G; 3] = [__v_4, __v_4, __v_4]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_217] = { let __args: [G; IN_217] = [__v_3, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(217, &__args[..])?) { [G::ZERO; OUT_217] } else { let (__hash, __hit) = record.function_queries[217].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[217].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[217].bump_multiplicity(__i); } let __ret: [G; OUT_217] = unsafe { *(record.function_queries[217].output_at(__i).as_ptr() as *const [G; OUT_217]) }; __ret }, _ => { aiur_fn_217::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __r_arr: [G; OUT_216] = { let __args: [G; IN_216] = [__v_2, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(216, &__args[..])?) { [G::ZERO; OUT_216] } else { let (__hash, __hit) = record.function_queries[216].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[216].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[216].bump_multiplicity(__i); } let __ret: [G; OUT_216] = unsafe { *(record.function_queries[216].output_at(__i).as_ptr() as *const [G; OUT_216]) }; __ret }, _ => { aiur_fn_216::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __r_arr: [G; OUT_212] = { let __args: [G; IN_212] = [__v_8, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(212, &__args[..])?) { [G::ZERO; OUT_212] } else { let (__hash, __hit) = record.function_queries[212].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[212].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[212].bump_multiplicity(__i); } let __ret: [G; OUT_212] = unsafe { *(record.function_queries[212].output_at(__i).as_ptr() as *const [G; OUT_212]) }; __ret }, _ => { aiur_fn_212::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __r_arr: [G; OUT_176] = { let __args: [G; IN_176] = [__v_1, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(176, &__args[..])?) { [G::ZERO; OUT_176] } else { let (__hash, __hit) = record.function_queries[176].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[176].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[176].bump_multiplicity(__i); } let __ret: [G; OUT_176] = unsafe { *(record.function_queries[176].output_at(__i).as_ptr() as *const [G; OUT_176]) }; __ret }, _ => { aiur_fn_176::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __r_arr: [G; OUT_186] = { let __args: [G; IN_186] = [__v_0, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(186, &__args[..])?) { [G::ZERO; OUT_186] } else { let (__hash, __hit) = record.function_queries[186].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[186].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[186].bump_multiplicity(__i); } let __ret: [G; OUT_186] = unsafe { *(record.function_queries[186].output_at(__i).as_ptr() as *const [G; OUT_186]) }; __ret }, _ => { aiur_fn_186::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_u64(0); let __v_13: G = G::from_u64(109); let __v_14: G = G::from_u64(117); let __v_15: G = G::from_u64(108); let __v_16: G = G::from_u64(116); let __v_17: G = G::from_u64(105); let __v_18: G = G::from_u64(45); let __v_19: G = G::from_u64(115); let __v_20: G = G::from_u64(97); let __v_21: G = G::from_u64(114); let __v_22: G = G::from_u64(107); let __v_23: G = G::from_u64(47); let __v_24: G = G::from_u64(118); let __v_25: G = G::from_u64(48); let __v_26: G = { let __values: [G; 3] = [__v_12, __v_25, __v_11]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_27: G = { let __values: [G; 3] = [__v_12, __v_24, __v_26]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_28: G = { let __values: [G; 3] = [__v_12, __v_23, __v_27]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_29: G = { let __values: [G; 3] = [__v_12, __v_22, __v_28]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_30: G = { let __values: [G; 3] = [__v_12, __v_21, __v_29]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_31: G = { let __values: [G; 3] = [__v_12, __v_20, __v_30]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_32: G = { let __values: [G; 3] = [__v_12, __v_16, __v_31]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_33: G = { let __values: [G; 3] = [__v_12, __v_19, __v_32]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_34: G = { let __values: [G; 3] = [__v_12, __v_18, __v_33]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_35: G = { let __values: [G; 3] = [__v_12, __v_17, __v_34]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_36: G = { let __values: [G; 3] = [__v_12, __v_16, __v_35]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_37: G = { let __values: [G; 3] = [__v_12, __v_15, __v_36]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_38: G = { let __values: [G; 3] = [__v_12, __v_14, __v_37]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_39: G = { let __values: [G; 3] = [__v_12, __v_13, __v_38]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_180] = { let __args: [G; IN_180] = [__v_39, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(180, &__args[..])?) { [G::ZERO; OUT_180] } else { let (__hash, __hit) = record.function_queries[180].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[180].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[180].bump_multiplicity(__i); } let __ret: [G; OUT_180] = unsafe { *(record.function_queries[180].output_at(__i).as_ptr() as *const [G; OUT_180]) }; __ret }, _ => { aiur_fn_180::(__args, __hash, record, io_buffer)? }, } } }; let __v_40: G = __r_arr[0]; let __v_41: G = __r_arr[1]; let __v_42: G = __r_arr[2]; let __v_43: G = __r_arr[3]; let __v_44: G = __r_arr[4]; let __v_45: G = __r_arr[5]; let __v_46: G = __r_arr[6]; let __v_47: G = __r_arr[7]; let __v_48: G = __r_arr[8]; let __v_49: G = __r_arr[9]; let __v_50: G = __r_arr[10]; let __v_51: G = __r_arr[11]; let __v_52: G = __r_arr[12]; let __v_53: G = __r_arr[13]; let __v_54: G = __r_arr[14]; let __v_55: G = __r_arr[15]; let __v_56: G = __r_arr[16]; let __v_57: G = __r_arr[17]; let __r_arr: [G; OUT_174] = { let __args: [G; IN_174] = [__v_48, __v_49, __v_50, __v_51, __v_52, __v_53, __v_54, __v_55, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(174, &__args[..])?) { [G::ZERO; OUT_174] } else { let (__hash, __hit) = record.function_queries[174].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[174].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[174].bump_multiplicity(__i); } let __ret: [G; OUT_174] = unsafe { *(record.function_queries[174].output_at(__i).as_ptr() as *const [G; OUT_174]) }; __ret }, _ => { aiur_fn_174::(__args, __hash, record, io_buffer)? }, } } }; let __v_58: G = __r_arr[0]; let __r_arr: [G; OUT_174] = { let __args: [G; IN_174] = [__v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_58]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(174, &__args[..])?) { [G::ZERO; OUT_174] } else { let (__hash, __hit) = record.function_queries[174].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[174].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[174].bump_multiplicity(__i); } let __ret: [G; OUT_174] = unsafe { *(record.function_queries[174].output_at(__i).as_ptr() as *const [G; OUT_174]) }; __ret }, _ => { aiur_fn_174::(__args, __hash, record, io_buffer)? }, } } }; let __v_59: G = __r_arr[0]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_56, __v_59]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_60: G = __r_arr[0]; let __r_arr: [G; OUT_180] = { let __args: [G; IN_180] = [__v_60, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(180, &__args[..])?) { [G::ZERO; OUT_180] } else { let (__hash, __hit) = record.function_queries[180].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[180].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[180].bump_multiplicity(__i); } let __ret: [G; OUT_180] = unsafe { *(record.function_queries[180].output_at(__i).as_ptr() as *const [G; OUT_180]) }; __ret }, _ => { aiur_fn_180::(__args, __hash, record, io_buffer)? }, } } }; let __v_61: G = __r_arr[0]; let __v_62: G = __r_arr[1]; let __v_63: G = __r_arr[2]; let __v_64: G = __r_arr[3]; let __v_65: G = __r_arr[4]; let __v_66: G = __r_arr[5]; let __v_67: G = __r_arr[6]; let __v_68: G = __r_arr[7]; let __v_69: G = __r_arr[8]; let __v_70: G = __r_arr[9]; let __v_71: G = __r_arr[10]; let __v_72: G = __r_arr[11]; let __v_73: G = __r_arr[12]; let __v_74: G = __r_arr[13]; let __v_75: G = __r_arr[14]; let __v_76: G = __r_arr[15]; let __v_77: G = __r_arr[16]; let __v_78: G = __r_arr[17]; let __r_arr: [G; OUT_174] = { let __args: [G; IN_174] = [__v_69, __v_70, __v_71, __v_72, __v_73, __v_74, __v_75, __v_76, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(174, &__args[..])?) { [G::ZERO; OUT_174] } else { let (__hash, __hit) = record.function_queries[174].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[174].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[174].bump_multiplicity(__i); } let __ret: [G; OUT_174] = unsafe { *(record.function_queries[174].output_at(__i).as_ptr() as *const [G; OUT_174]) }; __ret }, _ => { aiur_fn_174::(__args, __hash, record, io_buffer)? }, } } }; let __v_79: G = __r_arr[0]; let __r_arr: [G; OUT_174] = { let __args: [G; IN_174] = [__v_61, __v_62, __v_63, __v_64, __v_65, __v_66, __v_67, __v_68, __v_79]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(174, &__args[..])?) { [G::ZERO; OUT_174] } else { let (__hash, __hit) = record.function_queries[174].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[174].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[174].bump_multiplicity(__i); } let __ret: [G; OUT_174] = unsafe { *(record.function_queries[174].output_at(__i).as_ptr() as *const [G; OUT_174]) }; __ret }, _ => { aiur_fn_174::(__args, __hash, record, io_buffer)? }, } } }; let __v_80: G = __r_arr[0]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_77, __v_80]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_81: G = __r_arr[0]; let __v_82: G = G::from_u64(256); let __v_83: G = __v_82 * __v_41; let __v_84: G = __v_40 + __v_83; let __v_85: G = G::from_u64(65536); let __v_86: G = __v_85 * __v_42; let __v_87: G = __v_84 + __v_86; let __v_88: G = G::from_u64(16777216); let __v_89: G = __v_88 * __v_43; let __v_90: G = __v_87 + __v_89; let __v_91: G = G::from_u64(4294967296); let __v_92: G = __v_91 * __v_44; let __v_93: G = __v_90 + __v_92; let __v_94: G = G::from_u64(1099511627776); let __v_95: G = __v_94 * __v_45; let __v_96: G = __v_93 + __v_95; let __v_97: G = G::from_u64(281474976710656); let __v_98: G = __v_97 * __v_46; let __v_99: G = __v_96 + __v_98; let __v_100: G = G::from_u64(72057594037927936); let __v_101: G = __v_100 * __v_47; let __v_102: G = __v_99 + __v_101; let __v_103: G = __v_82 * __v_49; let __v_104: G = __v_48 + __v_103; let __v_105: G = __v_85 * __v_50; let __v_106: G = __v_104 + __v_105; let __v_107: G = __v_88 * __v_51; let __v_108: G = __v_106 + __v_107; let __v_109: G = __v_91 * __v_52; let __v_110: G = __v_108 + __v_109; let __v_111: G = __v_94 * __v_53; let __v_112: G = __v_110 + __v_111; let __v_113: G = __v_97 * __v_54; let __v_114: G = __v_112 + __v_113; let __v_115: G = __v_100 * __v_55; let __v_116: G = __v_114 + __v_115; let __v_117: G = __v_82 * __v_62; let __v_118: G = __v_61 + __v_117; let __v_119: G = __v_85 * __v_63; let __v_120: G = __v_118 + __v_119; let __v_121: G = __v_88 * __v_64; let __v_122: G = __v_120 + __v_121; let __v_123: G = __v_91 * __v_65; let __v_124: G = __v_122 + __v_123; let __v_125: G = __v_94 * __v_66; let __v_126: G = __v_124 + __v_125; let __v_127: G = __v_97 * __v_67; let __v_128: G = __v_126 + __v_127; let __v_129: G = __v_100 * __v_68; let __v_130: G = __v_128 + __v_129; let __v_131: G = __v_82 * __v_70; let __v_132: G = __v_69 + __v_131; let __v_133: G = __v_85 * __v_71; let __v_134: G = __v_132 + __v_133; let __v_135: G = __v_88 * __v_72; let __v_136: G = __v_134 + __v_135; let __v_137: G = __v_91 * __v_73; let __v_138: G = __v_136 + __v_137; let __v_139: G = __v_94 * __v_74; let __v_140: G = __v_138 + __v_139; let __v_141: G = __v_97 * __v_75; let __v_142: G = __v_140 + __v_141; let __v_143: G = __v_100 * __v_76; let __v_144: G = __v_142 + __v_143; let __ret: [G; OUT_219] = [__v_102, __v_116, __v_130, __v_144, __v_81]; record.function_queries[219].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +fn aiur_fn_219(inp: [G; IN_219], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_219], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = G::from_u64(1); let __v_5: G = { let __values: [G; 3] = [__v_4, __v_4, __v_4]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_217] = { let __args: [G; IN_217] = [__v_3, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(217, &__args[..])?) { [G::ZERO; OUT_217] } else { let (__hash, __hit) = record.function_queries[217].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[217].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[217].bump_multiplicity(__i); } let __ret: [G; OUT_217] = unsafe { *(record.function_queries[217].output_at(__i).as_ptr() as *const [G; OUT_217]) }; __ret }, _ => { aiur_fn_217::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __r_arr: [G; OUT_216] = { let __args: [G; IN_216] = [__v_2, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(216, &__args[..])?) { [G::ZERO; OUT_216] } else { let (__hash, __hit) = record.function_queries[216].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[216].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[216].bump_multiplicity(__i); } let __ret: [G; OUT_216] = unsafe { *(record.function_queries[216].output_at(__i).as_ptr() as *const [G; OUT_216]) }; __ret }, _ => { aiur_fn_216::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __r_arr: [G; OUT_317] = { let __args: [G; IN_317] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(317, &__args[..])?) { [G::ZERO; OUT_317] } else { let (__hash, __hit) = record.function_queries[317].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[317].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[317].bump_multiplicity(__i); } let __ret: [G; OUT_317] = unsafe { *(record.function_queries[317].output_at(__i).as_ptr() as *const [G; OUT_317]) }; __ret }, _ => { aiur_fn_317::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __r_arr: [G; OUT_212] = { let __args: [G; IN_212] = [__v_8, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(212, &__args[..])?) { [G::ZERO; OUT_212] } else { let (__hash, __hit) = record.function_queries[212].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[212].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[212].bump_multiplicity(__i); } let __ret: [G; OUT_212] = unsafe { *(record.function_queries[212].output_at(__i).as_ptr() as *const [G; OUT_212]) }; __ret }, _ => { aiur_fn_212::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __r_arr: [G; OUT_176] = { let __args: [G; IN_176] = [__v_1, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(176, &__args[..])?) { [G::ZERO; OUT_176] } else { let (__hash, __hit) = record.function_queries[176].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[176].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[176].bump_multiplicity(__i); } let __ret: [G; OUT_176] = unsafe { *(record.function_queries[176].output_at(__i).as_ptr() as *const [G; OUT_176]) }; __ret }, _ => { aiur_fn_176::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __r_arr: [G; OUT_186] = { let __args: [G; IN_186] = [__v_0, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(186, &__args[..])?) { [G::ZERO; OUT_186] } else { let (__hash, __hit) = record.function_queries[186].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[186].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[186].bump_multiplicity(__i); } let __ret: [G; OUT_186] = unsafe { *(record.function_queries[186].output_at(__i).as_ptr() as *const [G; OUT_186]) }; __ret }, _ => { aiur_fn_186::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_u64(0); let __v_13: G = G::from_u64(109); let __v_14: G = G::from_u64(117); let __v_15: G = G::from_u64(108); let __v_16: G = G::from_u64(116); let __v_17: G = G::from_u64(105); let __v_18: G = G::from_u64(45); let __v_19: G = G::from_u64(115); let __v_20: G = G::from_u64(97); let __v_21: G = G::from_u64(114); let __v_22: G = G::from_u64(107); let __v_23: G = G::from_u64(47); let __v_24: G = G::from_u64(118); let __v_25: G = G::from_u64(48); let __v_26: G = { let __values: [G; 3] = [__v_12, __v_25, __v_11]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_27: G = { let __values: [G; 3] = [__v_12, __v_24, __v_26]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_28: G = { let __values: [G; 3] = [__v_12, __v_23, __v_27]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_29: G = { let __values: [G; 3] = [__v_12, __v_22, __v_28]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_30: G = { let __values: [G; 3] = [__v_12, __v_21, __v_29]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_31: G = { let __values: [G; 3] = [__v_12, __v_20, __v_30]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_32: G = { let __values: [G; 3] = [__v_12, __v_16, __v_31]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_33: G = { let __values: [G; 3] = [__v_12, __v_19, __v_32]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_34: G = { let __values: [G; 3] = [__v_12, __v_18, __v_33]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_35: G = { let __values: [G; 3] = [__v_12, __v_17, __v_34]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_36: G = { let __values: [G; 3] = [__v_12, __v_16, __v_35]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_37: G = { let __values: [G; 3] = [__v_12, __v_15, __v_36]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_38: G = { let __values: [G; 3] = [__v_12, __v_14, __v_37]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_39: G = { let __values: [G; 3] = [__v_12, __v_13, __v_38]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_180] = { let __args: [G; IN_180] = [__v_39, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(180, &__args[..])?) { [G::ZERO; OUT_180] } else { let (__hash, __hit) = record.function_queries[180].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[180].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[180].bump_multiplicity(__i); } let __ret: [G; OUT_180] = unsafe { *(record.function_queries[180].output_at(__i).as_ptr() as *const [G; OUT_180]) }; __ret }, _ => { aiur_fn_180::(__args, __hash, record, io_buffer)? }, } } }; let __v_40: G = __r_arr[0]; let __v_41: G = __r_arr[1]; let __v_42: G = __r_arr[2]; let __v_43: G = __r_arr[3]; let __v_44: G = __r_arr[4]; let __v_45: G = __r_arr[5]; let __v_46: G = __r_arr[6]; let __v_47: G = __r_arr[7]; let __v_48: G = __r_arr[8]; let __v_49: G = __r_arr[9]; let __v_50: G = __r_arr[10]; let __v_51: G = __r_arr[11]; let __v_52: G = __r_arr[12]; let __v_53: G = __r_arr[13]; let __v_54: G = __r_arr[14]; let __v_55: G = __r_arr[15]; let __v_56: G = __r_arr[16]; let __v_57: G = __r_arr[17]; let __r_arr: [G; OUT_174] = { let __args: [G; IN_174] = [__v_48, __v_49, __v_50, __v_51, __v_52, __v_53, __v_54, __v_55, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(174, &__args[..])?) { [G::ZERO; OUT_174] } else { let (__hash, __hit) = record.function_queries[174].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[174].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[174].bump_multiplicity(__i); } let __ret: [G; OUT_174] = unsafe { *(record.function_queries[174].output_at(__i).as_ptr() as *const [G; OUT_174]) }; __ret }, _ => { aiur_fn_174::(__args, __hash, record, io_buffer)? }, } } }; let __v_58: G = __r_arr[0]; let __r_arr: [G; OUT_174] = { let __args: [G; IN_174] = [__v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_58]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(174, &__args[..])?) { [G::ZERO; OUT_174] } else { let (__hash, __hit) = record.function_queries[174].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[174].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[174].bump_multiplicity(__i); } let __ret: [G; OUT_174] = unsafe { *(record.function_queries[174].output_at(__i).as_ptr() as *const [G; OUT_174]) }; __ret }, _ => { aiur_fn_174::(__args, __hash, record, io_buffer)? }, } } }; let __v_59: G = __r_arr[0]; let __r_arr: [G; OUT_316] = { let __args: [G; IN_316] = [__v_56, __v_59]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(316, &__args[..])?) { [G::ZERO; OUT_316] } else { let (__hash, __hit) = record.function_queries[316].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[316].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[316].bump_multiplicity(__i); } let __ret: [G; OUT_316] = unsafe { *(record.function_queries[316].output_at(__i).as_ptr() as *const [G; OUT_316]) }; __ret }, _ => { aiur_fn_316::(__args, __hash, record, io_buffer)? }, } } }; let __v_60: G = __r_arr[0]; let __r_arr: [G; OUT_180] = { let __args: [G; IN_180] = [__v_60, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(180, &__args[..])?) { [G::ZERO; OUT_180] } else { let (__hash, __hit) = record.function_queries[180].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[180].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[180].bump_multiplicity(__i); } let __ret: [G; OUT_180] = unsafe { *(record.function_queries[180].output_at(__i).as_ptr() as *const [G; OUT_180]) }; __ret }, _ => { aiur_fn_180::(__args, __hash, record, io_buffer)? }, } } }; let __v_61: G = __r_arr[0]; let __v_62: G = __r_arr[1]; let __v_63: G = __r_arr[2]; let __v_64: G = __r_arr[3]; let __v_65: G = __r_arr[4]; let __v_66: G = __r_arr[5]; let __v_67: G = __r_arr[6]; let __v_68: G = __r_arr[7]; let __v_69: G = __r_arr[8]; let __v_70: G = __r_arr[9]; let __v_71: G = __r_arr[10]; let __v_72: G = __r_arr[11]; let __v_73: G = __r_arr[12]; let __v_74: G = __r_arr[13]; let __v_75: G = __r_arr[14]; let __v_76: G = __r_arr[15]; let __v_77: G = __r_arr[16]; let __v_78: G = __r_arr[17]; let __r_arr: [G; OUT_174] = { let __args: [G; IN_174] = [__v_69, __v_70, __v_71, __v_72, __v_73, __v_74, __v_75, __v_76, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(174, &__args[..])?) { [G::ZERO; OUT_174] } else { let (__hash, __hit) = record.function_queries[174].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[174].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[174].bump_multiplicity(__i); } let __ret: [G; OUT_174] = unsafe { *(record.function_queries[174].output_at(__i).as_ptr() as *const [G; OUT_174]) }; __ret }, _ => { aiur_fn_174::(__args, __hash, record, io_buffer)? }, } } }; let __v_79: G = __r_arr[0]; let __r_arr: [G; OUT_174] = { let __args: [G; IN_174] = [__v_61, __v_62, __v_63, __v_64, __v_65, __v_66, __v_67, __v_68, __v_79]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(174, &__args[..])?) { [G::ZERO; OUT_174] } else { let (__hash, __hit) = record.function_queries[174].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[174].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[174].bump_multiplicity(__i); } let __ret: [G; OUT_174] = unsafe { *(record.function_queries[174].output_at(__i).as_ptr() as *const [G; OUT_174]) }; __ret }, _ => { aiur_fn_174::(__args, __hash, record, io_buffer)? }, } } }; let __v_80: G = __r_arr[0]; let __r_arr: [G; OUT_316] = { let __args: [G; IN_316] = [__v_77, __v_80]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(316, &__args[..])?) { [G::ZERO; OUT_316] } else { let (__hash, __hit) = record.function_queries[316].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[316].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[316].bump_multiplicity(__i); } let __ret: [G; OUT_316] = unsafe { *(record.function_queries[316].output_at(__i).as_ptr() as *const [G; OUT_316]) }; __ret }, _ => { aiur_fn_316::(__args, __hash, record, io_buffer)? }, } } }; let __v_81: G = __r_arr[0]; let __v_82: G = G::from_u64(256); let __v_83: G = __v_82 * __v_41; let __v_84: G = __v_40 + __v_83; let __v_85: G = G::from_u64(65536); let __v_86: G = __v_85 * __v_42; let __v_87: G = __v_84 + __v_86; let __v_88: G = G::from_u64(16777216); let __v_89: G = __v_88 * __v_43; let __v_90: G = __v_87 + __v_89; let __v_91: G = G::from_u64(4294967296); let __v_92: G = __v_91 * __v_44; let __v_93: G = __v_90 + __v_92; let __v_94: G = G::from_u64(1099511627776); let __v_95: G = __v_94 * __v_45; let __v_96: G = __v_93 + __v_95; let __v_97: G = G::from_u64(281474976710656); let __v_98: G = __v_97 * __v_46; let __v_99: G = __v_96 + __v_98; let __v_100: G = G::from_u64(72057594037927936); let __v_101: G = __v_100 * __v_47; let __v_102: G = __v_99 + __v_101; let __v_103: G = __v_82 * __v_49; let __v_104: G = __v_48 + __v_103; let __v_105: G = __v_85 * __v_50; let __v_106: G = __v_104 + __v_105; let __v_107: G = __v_88 * __v_51; let __v_108: G = __v_106 + __v_107; let __v_109: G = __v_91 * __v_52; let __v_110: G = __v_108 + __v_109; let __v_111: G = __v_94 * __v_53; let __v_112: G = __v_110 + __v_111; let __v_113: G = __v_97 * __v_54; let __v_114: G = __v_112 + __v_113; let __v_115: G = __v_100 * __v_55; let __v_116: G = __v_114 + __v_115; let __v_117: G = __v_82 * __v_62; let __v_118: G = __v_61 + __v_117; let __v_119: G = __v_85 * __v_63; let __v_120: G = __v_118 + __v_119; let __v_121: G = __v_88 * __v_64; let __v_122: G = __v_120 + __v_121; let __v_123: G = __v_91 * __v_65; let __v_124: G = __v_122 + __v_123; let __v_125: G = __v_94 * __v_66; let __v_126: G = __v_124 + __v_125; let __v_127: G = __v_97 * __v_67; let __v_128: G = __v_126 + __v_127; let __v_129: G = __v_100 * __v_68; let __v_130: G = __v_128 + __v_129; let __v_131: G = __v_82 * __v_70; let __v_132: G = __v_69 + __v_131; let __v_133: G = __v_85 * __v_71; let __v_134: G = __v_132 + __v_133; let __v_135: G = __v_88 * __v_72; let __v_136: G = __v_134 + __v_135; let __v_137: G = __v_91 * __v_73; let __v_138: G = __v_136 + __v_137; let __v_139: G = __v_94 * __v_74; let __v_140: G = __v_138 + __v_139; let __v_141: G = __v_97 * __v_75; let __v_142: G = __v_140 + __v_141; let __v_143: G = __v_100 * __v_76; let __v_144: G = __v_142 + __v_143; let __ret: [G; OUT_219] = [__v_102, __v_116, __v_130, __v_144, __v_81]; record.function_queries[219].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } const INPUT_SIZE_220: usize = 5; const IN_220: usize = 5; const OUT_220: usize = 5; -fn aiur_fn_220(inp: [G; IN_220], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_220], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __gb: [u8; 8] = __v_1.as_canonical_u64().to_le_bytes(); let __v_5: G = G::from_u8(__gb[0]); let __v_6: G = G::from_u8(__gb[1]); let __v_7: G = G::from_u8(__gb[2]); let __v_8: G = G::from_u8(__gb[3]); let __v_9: G = G::from_u8(__gb[4]); let __v_10: G = G::from_u8(__gb[5]); let __v_11: G = G::from_u8(__gb[6]); let __v_12: G = G::from_u8(__gb[7]); if (!unconstrained) { let __vi = __v_5; let __vj = __v_6; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_7; let __vj = __v_8; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_9; let __vj = __v_10; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_11; let __vj = __v_12; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } let __v_13: G = G::from_u64(256); let __v_14: G = __v_13 * __v_6; let __v_15: G = __v_5 + __v_14; let __v_16: G = G::from_u64(65536); let __v_17: G = __v_16 * __v_7; let __v_18: G = __v_15 + __v_17; let __v_19: G = G::from_u64(16777216); let __v_20: G = __v_19 * __v_8; let __v_21: G = __v_18 + __v_20; let __v_22: G = G::from_u64(4294967296); let __v_23: G = __v_22 * __v_9; let __v_24: G = __v_21 + __v_23; let __v_25: G = G::from_u64(1099511627776); let __v_26: G = __v_25 * __v_10; let __v_27: G = __v_24 + __v_26; let __v_28: G = G::from_u64(281474976710656); let __v_29: G = __v_28 * __v_11; let __v_30: G = __v_27 + __v_29; let __v_31: G = G::from_u64(72057594037927936); let __v_32: G = __v_31 * __v_12; let __v_33: G = __v_30 + __v_32; if (__v_33 != __v_1) { return Err(ExecError::AssertEqMismatch { lhs: __v_33.as_canonical_u64(), rhs: __v_1.as_canonical_u64(), msg: None }); } let __v_34: G = __v_9 + __v_10; let __v_35: G = __v_34 + __v_11; let __v_36: G = __v_35 + __v_12; let __v_37: G = G::from_u64(1020); let __v_38: G = __v_36 - __v_37; let __v_39: G = G::from_bool(__v_38 == G::ZERO); let __v_40: G = __v_5 + __v_6; let __v_41: G = __v_40 + __v_7; let __v_42: G = __v_41 + __v_8; let __v_43: G = G::from_bool(__v_42 == G::ZERO); let __v_44: G = G::from_u64(1); let __v_45: G = __v_44 - __v_43; let __v_46: G = __v_39 * __v_45; let __v_47: G = __v_44 - __v_46; if (__v_47 != __v_44) { return Err(ExecError::AssertEqMismatch { lhs: __v_47.as_canonical_u64(), rhs: __v_44.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_183] = { let __args: [G; IN_183] = [__v_0, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(183, &__args[..])?) { [G::ZERO; OUT_183] } else { let (__hash, __hit) = record.function_queries[183].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[183].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[183].bump_multiplicity(__i); } let __ret: [G; OUT_183] = unsafe { *(record.function_queries[183].output_at(__i).as_ptr() as *const [G; OUT_183]) }; __ret }, _ => { aiur_fn_183::(__args, __hash, record, io_buffer)? }, } } }; let __v_48: G = __r_arr[0]; let __r_arr: [G; OUT_184] = { let __args: [G; IN_184] = [__v_48, __v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(184, &__args[..])?) { [G::ZERO; OUT_184] } else { let (__hash, __hit) = record.function_queries[184].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[184].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[184].bump_multiplicity(__i); } let __ret: [G; OUT_184] = unsafe { *(record.function_queries[184].output_at(__i).as_ptr() as *const [G; OUT_184]) }; __ret }, _ => { aiur_fn_184::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __v_50: G = { let __values: [G; 3] = [__v_44, __v_44, __v_44]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_137] = { let __args: [G; IN_137] = [__v_4, __v_50]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(137, &__args[..])?) { [G::ZERO; OUT_137] } else { let (__hash, __hit) = record.function_queries[137].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[137].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[137].bump_multiplicity(__i); } let __ret: [G; OUT_137] = unsafe { *(record.function_queries[137].output_at(__i).as_ptr() as *const [G; OUT_137]) }; __ret }, _ => { aiur_fn_137::(__args, __hash, record, io_buffer)? }, } } }; let __v_51: G = __r_arr[0]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_49, __v_51]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_52: G = __r_arr[0]; let __r_arr: [G; OUT_180] = { let __args: [G; IN_180] = [__v_52, __v_50]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(180, &__args[..])?) { [G::ZERO; OUT_180] } else { let (__hash, __hit) = record.function_queries[180].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[180].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[180].bump_multiplicity(__i); } let __ret: [G; OUT_180] = unsafe { *(record.function_queries[180].output_at(__i).as_ptr() as *const [G; OUT_180]) }; __ret }, _ => { aiur_fn_180::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; let __v_54: G = __r_arr[1]; let __v_55: G = __r_arr[2]; let __v_56: G = __r_arr[3]; let __v_57: G = __r_arr[4]; let __v_58: G = __r_arr[5]; let __v_59: G = __r_arr[6]; let __v_60: G = __r_arr[7]; let __v_61: G = __r_arr[8]; let __v_62: G = __r_arr[9]; let __v_63: G = __r_arr[10]; let __v_64: G = __r_arr[11]; let __v_65: G = __r_arr[12]; let __v_66: G = __r_arr[13]; let __v_67: G = __r_arr[14]; let __v_68: G = __r_arr[15]; let __v_69: G = __r_arr[16]; let __v_70: G = __r_arr[17]; let __r_arr: [G; OUT_184] = { let __args: [G; IN_184] = [__v_69, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(184, &__args[..])?) { [G::ZERO; OUT_184] } else { let (__hash, __hit) = record.function_queries[184].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[184].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[184].bump_multiplicity(__i); } let __ret: [G; OUT_184] = unsafe { *(record.function_queries[184].output_at(__i).as_ptr() as *const [G; OUT_184]) }; __ret }, _ => { aiur_fn_184::(__args, __hash, record, io_buffer)? }, } } }; let __v_71: G = __r_arr[0]; let __r_arr: [G; OUT_180] = { let __args: [G; IN_180] = [__v_71, __v_50]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(180, &__args[..])?) { [G::ZERO; OUT_180] } else { let (__hash, __hit) = record.function_queries[180].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[180].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[180].bump_multiplicity(__i); } let __ret: [G; OUT_180] = unsafe { *(record.function_queries[180].output_at(__i).as_ptr() as *const [G; OUT_180]) }; __ret }, _ => { aiur_fn_180::(__args, __hash, record, io_buffer)? }, } } }; let __v_72: G = __r_arr[0]; let __v_73: G = __r_arr[1]; let __v_74: G = __r_arr[2]; let __v_75: G = __r_arr[3]; let __v_76: G = __r_arr[4]; let __v_77: G = __r_arr[5]; let __v_78: G = __r_arr[6]; let __v_79: G = __r_arr[7]; let __v_80: G = __r_arr[8]; let __v_81: G = __r_arr[9]; let __v_82: G = __r_arr[10]; let __v_83: G = __r_arr[11]; let __v_84: G = __r_arr[12]; let __v_85: G = __r_arr[13]; let __v_86: G = __r_arr[14]; let __v_87: G = __r_arr[15]; let __v_88: G = __r_arr[16]; let __v_89: G = __r_arr[17]; let __v_90: G = __v_13 * __v_54; let __v_91: G = __v_53 + __v_90; let __v_92: G = __v_16 * __v_55; let __v_93: G = __v_91 + __v_92; let __v_94: G = __v_19 * __v_56; let __v_95: G = __v_93 + __v_94; let __v_96: G = __v_22 * __v_57; let __v_97: G = __v_95 + __v_96; let __v_98: G = __v_25 * __v_58; let __v_99: G = __v_97 + __v_98; let __v_100: G = __v_28 * __v_59; let __v_101: G = __v_99 + __v_100; let __v_102: G = __v_31 * __v_60; let __v_103: G = __v_101 + __v_102; let __v_104: G = __v_13 * __v_62; let __v_105: G = __v_61 + __v_104; let __v_106: G = __v_16 * __v_63; let __v_107: G = __v_105 + __v_106; let __v_108: G = __v_19 * __v_64; let __v_109: G = __v_107 + __v_108; let __v_110: G = __v_22 * __v_65; let __v_111: G = __v_109 + __v_110; let __v_112: G = __v_25 * __v_66; let __v_113: G = __v_111 + __v_112; let __v_114: G = __v_28 * __v_67; let __v_115: G = __v_113 + __v_114; let __v_116: G = __v_31 * __v_68; let __v_117: G = __v_115 + __v_116; let __v_118: G = __v_13 * __v_73; let __v_119: G = __v_72 + __v_118; let __v_120: G = __v_16 * __v_74; let __v_121: G = __v_119 + __v_120; let __v_122: G = __v_19 * __v_75; let __v_123: G = __v_121 + __v_122; let __v_124: G = __v_22 * __v_76; let __v_125: G = __v_123 + __v_124; let __v_126: G = __v_25 * __v_77; let __v_127: G = __v_125 + __v_126; let __v_128: G = __v_28 * __v_78; let __v_129: G = __v_127 + __v_128; let __v_130: G = __v_31 * __v_79; let __v_131: G = __v_129 + __v_130; let __v_132: G = __v_13 * __v_81; let __v_133: G = __v_80 + __v_132; let __v_134: G = __v_16 * __v_82; let __v_135: G = __v_133 + __v_134; let __v_136: G = __v_19 * __v_83; let __v_137: G = __v_135 + __v_136; let __v_138: G = __v_22 * __v_84; let __v_139: G = __v_137 + __v_138; let __v_140: G = __v_25 * __v_85; let __v_141: G = __v_139 + __v_140; let __v_142: G = __v_28 * __v_86; let __v_143: G = __v_141 + __v_142; let __v_144: G = __v_31 * __v_87; let __v_145: G = __v_143 + __v_144; let __ret: [G; OUT_220] = [__v_103, __v_117, __v_131, __v_145, __v_88]; record.function_queries[220].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +fn aiur_fn_220(inp: [G; IN_220], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_220], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __gb: [u8; 8] = __v_1.as_canonical_u64().to_le_bytes(); let __v_5: G = G::from_u8(__gb[0]); let __v_6: G = G::from_u8(__gb[1]); let __v_7: G = G::from_u8(__gb[2]); let __v_8: G = G::from_u8(__gb[3]); let __v_9: G = G::from_u8(__gb[4]); let __v_10: G = G::from_u8(__gb[5]); let __v_11: G = G::from_u8(__gb[6]); let __v_12: G = G::from_u8(__gb[7]); if (!unconstrained) { let __vi = __v_5; let __vj = __v_6; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_7; let __vj = __v_8; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_9; let __vj = __v_10; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } if (!unconstrained) { let __vi = __v_11; let __vj = __v_12; let __bi = __vi.as_canonical_u64(); let __bj = __vj.as_canonical_u64(); if (__bi >= 256) { return Err(ExecError::U8RangeCheckFailed(__bi)); } if (__bj >= 256) { return Err(ExecError::U8RangeCheckFailed(__bj)); } record.bytes2_queries.bump_range_check(&__vi, &__vj); } let __v_13: G = G::from_u64(256); let __v_14: G = __v_13 * __v_6; let __v_15: G = __v_5 + __v_14; let __v_16: G = G::from_u64(65536); let __v_17: G = __v_16 * __v_7; let __v_18: G = __v_15 + __v_17; let __v_19: G = G::from_u64(16777216); let __v_20: G = __v_19 * __v_8; let __v_21: G = __v_18 + __v_20; let __v_22: G = G::from_u64(4294967296); let __v_23: G = __v_22 * __v_9; let __v_24: G = __v_21 + __v_23; let __v_25: G = G::from_u64(1099511627776); let __v_26: G = __v_25 * __v_10; let __v_27: G = __v_24 + __v_26; let __v_28: G = G::from_u64(281474976710656); let __v_29: G = __v_28 * __v_11; let __v_30: G = __v_27 + __v_29; let __v_31: G = G::from_u64(72057594037927936); let __v_32: G = __v_31 * __v_12; let __v_33: G = __v_30 + __v_32; if (__v_33 != __v_1) { return Err(ExecError::AssertEqMismatch { lhs: __v_33.as_canonical_u64(), rhs: __v_1.as_canonical_u64(), msg: None }); } let __v_34: G = __v_9 + __v_10; let __v_35: G = __v_34 + __v_11; let __v_36: G = __v_35 + __v_12; let __v_37: G = G::from_u64(1020); let __v_38: G = __v_36 - __v_37; let __v_39: G = G::from_bool(__v_38 == G::ZERO); let __v_40: G = __v_5 + __v_6; let __v_41: G = __v_40 + __v_7; let __v_42: G = __v_41 + __v_8; let __v_43: G = G::from_bool(__v_42 == G::ZERO); let __v_44: G = G::from_u64(1); let __v_45: G = __v_44 - __v_43; let __v_46: G = __v_39 * __v_45; let __v_47: G = __v_44 - __v_46; if (__v_47 != __v_44) { return Err(ExecError::AssertEqMismatch { lhs: __v_47.as_canonical_u64(), rhs: __v_44.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_183] = { let __args: [G; IN_183] = [__v_0, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(183, &__args[..])?) { [G::ZERO; OUT_183] } else { let (__hash, __hit) = record.function_queries[183].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[183].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[183].bump_multiplicity(__i); } let __ret: [G; OUT_183] = unsafe { *(record.function_queries[183].output_at(__i).as_ptr() as *const [G; OUT_183]) }; __ret }, _ => { aiur_fn_183::(__args, __hash, record, io_buffer)? }, } } }; let __v_48: G = __r_arr[0]; let __r_arr: [G; OUT_184] = { let __args: [G; IN_184] = [__v_48, __v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(184, &__args[..])?) { [G::ZERO; OUT_184] } else { let (__hash, __hit) = record.function_queries[184].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[184].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[184].bump_multiplicity(__i); } let __ret: [G; OUT_184] = unsafe { *(record.function_queries[184].output_at(__i).as_ptr() as *const [G; OUT_184]) }; __ret }, _ => { aiur_fn_184::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __v_50: G = { let __values: [G; 3] = [__v_44, __v_44, __v_44]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_137] = { let __args: [G; IN_137] = [__v_4, __v_50]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(137, &__args[..])?) { [G::ZERO; OUT_137] } else { let (__hash, __hit) = record.function_queries[137].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[137].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[137].bump_multiplicity(__i); } let __ret: [G; OUT_137] = unsafe { *(record.function_queries[137].output_at(__i).as_ptr() as *const [G; OUT_137]) }; __ret }, _ => { aiur_fn_137::(__args, __hash, record, io_buffer)? }, } } }; let __v_51: G = __r_arr[0]; let __r_arr: [G; OUT_316] = { let __args: [G; IN_316] = [__v_49, __v_51]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(316, &__args[..])?) { [G::ZERO; OUT_316] } else { let (__hash, __hit) = record.function_queries[316].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[316].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[316].bump_multiplicity(__i); } let __ret: [G; OUT_316] = unsafe { *(record.function_queries[316].output_at(__i).as_ptr() as *const [G; OUT_316]) }; __ret }, _ => { aiur_fn_316::(__args, __hash, record, io_buffer)? }, } } }; let __v_52: G = __r_arr[0]; let __r_arr: [G; OUT_180] = { let __args: [G; IN_180] = [__v_52, __v_50]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(180, &__args[..])?) { [G::ZERO; OUT_180] } else { let (__hash, __hit) = record.function_queries[180].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[180].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[180].bump_multiplicity(__i); } let __ret: [G; OUT_180] = unsafe { *(record.function_queries[180].output_at(__i).as_ptr() as *const [G; OUT_180]) }; __ret }, _ => { aiur_fn_180::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; let __v_54: G = __r_arr[1]; let __v_55: G = __r_arr[2]; let __v_56: G = __r_arr[3]; let __v_57: G = __r_arr[4]; let __v_58: G = __r_arr[5]; let __v_59: G = __r_arr[6]; let __v_60: G = __r_arr[7]; let __v_61: G = __r_arr[8]; let __v_62: G = __r_arr[9]; let __v_63: G = __r_arr[10]; let __v_64: G = __r_arr[11]; let __v_65: G = __r_arr[12]; let __v_66: G = __r_arr[13]; let __v_67: G = __r_arr[14]; let __v_68: G = __r_arr[15]; let __v_69: G = __r_arr[16]; let __v_70: G = __r_arr[17]; let __r_arr: [G; OUT_184] = { let __args: [G; IN_184] = [__v_69, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(184, &__args[..])?) { [G::ZERO; OUT_184] } else { let (__hash, __hit) = record.function_queries[184].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[184].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[184].bump_multiplicity(__i); } let __ret: [G; OUT_184] = unsafe { *(record.function_queries[184].output_at(__i).as_ptr() as *const [G; OUT_184]) }; __ret }, _ => { aiur_fn_184::(__args, __hash, record, io_buffer)? }, } } }; let __v_71: G = __r_arr[0]; let __r_arr: [G; OUT_180] = { let __args: [G; IN_180] = [__v_71, __v_50]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(180, &__args[..])?) { [G::ZERO; OUT_180] } else { let (__hash, __hit) = record.function_queries[180].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[180].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[180].bump_multiplicity(__i); } let __ret: [G; OUT_180] = unsafe { *(record.function_queries[180].output_at(__i).as_ptr() as *const [G; OUT_180]) }; __ret }, _ => { aiur_fn_180::(__args, __hash, record, io_buffer)? }, } } }; let __v_72: G = __r_arr[0]; let __v_73: G = __r_arr[1]; let __v_74: G = __r_arr[2]; let __v_75: G = __r_arr[3]; let __v_76: G = __r_arr[4]; let __v_77: G = __r_arr[5]; let __v_78: G = __r_arr[6]; let __v_79: G = __r_arr[7]; let __v_80: G = __r_arr[8]; let __v_81: G = __r_arr[9]; let __v_82: G = __r_arr[10]; let __v_83: G = __r_arr[11]; let __v_84: G = __r_arr[12]; let __v_85: G = __r_arr[13]; let __v_86: G = __r_arr[14]; let __v_87: G = __r_arr[15]; let __v_88: G = __r_arr[16]; let __v_89: G = __r_arr[17]; let __v_90: G = __v_13 * __v_54; let __v_91: G = __v_53 + __v_90; let __v_92: G = __v_16 * __v_55; let __v_93: G = __v_91 + __v_92; let __v_94: G = __v_19 * __v_56; let __v_95: G = __v_93 + __v_94; let __v_96: G = __v_22 * __v_57; let __v_97: G = __v_95 + __v_96; let __v_98: G = __v_25 * __v_58; let __v_99: G = __v_97 + __v_98; let __v_100: G = __v_28 * __v_59; let __v_101: G = __v_99 + __v_100; let __v_102: G = __v_31 * __v_60; let __v_103: G = __v_101 + __v_102; let __v_104: G = __v_13 * __v_62; let __v_105: G = __v_61 + __v_104; let __v_106: G = __v_16 * __v_63; let __v_107: G = __v_105 + __v_106; let __v_108: G = __v_19 * __v_64; let __v_109: G = __v_107 + __v_108; let __v_110: G = __v_22 * __v_65; let __v_111: G = __v_109 + __v_110; let __v_112: G = __v_25 * __v_66; let __v_113: G = __v_111 + __v_112; let __v_114: G = __v_28 * __v_67; let __v_115: G = __v_113 + __v_114; let __v_116: G = __v_31 * __v_68; let __v_117: G = __v_115 + __v_116; let __v_118: G = __v_13 * __v_73; let __v_119: G = __v_72 + __v_118; let __v_120: G = __v_16 * __v_74; let __v_121: G = __v_119 + __v_120; let __v_122: G = __v_19 * __v_75; let __v_123: G = __v_121 + __v_122; let __v_124: G = __v_22 * __v_76; let __v_125: G = __v_123 + __v_124; let __v_126: G = __v_25 * __v_77; let __v_127: G = __v_125 + __v_126; let __v_128: G = __v_28 * __v_78; let __v_129: G = __v_127 + __v_128; let __v_130: G = __v_31 * __v_79; let __v_131: G = __v_129 + __v_130; let __v_132: G = __v_13 * __v_81; let __v_133: G = __v_80 + __v_132; let __v_134: G = __v_16 * __v_82; let __v_135: G = __v_133 + __v_134; let __v_136: G = __v_19 * __v_83; let __v_137: G = __v_135 + __v_136; let __v_138: G = __v_22 * __v_84; let __v_139: G = __v_137 + __v_138; let __v_140: G = __v_25 * __v_85; let __v_141: G = __v_139 + __v_140; let __v_142: G = __v_28 * __v_86; let __v_143: G = __v_141 + __v_142; let __v_144: G = __v_31 * __v_87; let __v_145: G = __v_143 + __v_144; let __ret: [G; OUT_220] = [__v_103, __v_117, __v_131, __v_145, __v_88]; record.function_queries[220].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } const INPUT_SIZE_221: usize = 2; const IN_221: usize = 2; const OUT_221: usize = 1; @@ -952,7 +952,7 @@ fn aiur_fn_234(inp: [G; IN_234], input_hash: u64, rec const INPUT_SIZE_235: usize = 46; const IN_235: usize = 46; const OUT_235: usize = 2; -fn aiur_fn_235(inp: [G; IN_235], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_235], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = inp[34]; let __v_35: G = inp[35]; let __v_36: G = inp[36]; let __v_37: G = inp[37]; let __v_38: G = inp[38]; let __v_39: G = inp[39]; let __v_40: G = inp[40]; let __v_41: G = inp[41]; let __v_42: G = inp[42]; let __v_43: G = inp[43]; let __v_44: G = inp[44]; let __v_45: G = inp[45]; match __v_0.as_canonical_u64() { _ => { match __v_0.as_canonical_u64() { _ => { let __v_46: G = G::from_u64(0); if (__v_1 != __v_46) { return Err(ExecError::AssertEqMismatch { lhs: __v_1.as_canonical_u64(), rhs: __v_46.as_canonical_u64(), msg: None }); } if (__v_2 != __v_46) { return Err(ExecError::AssertEqMismatch { lhs: __v_2.as_canonical_u64(), rhs: __v_46.as_canonical_u64(), msg: None }); } let __v_47: G = G::from_u64(1); if (__v_3 != __v_47) { return Err(ExecError::AssertEqMismatch { lhs: __v_3.as_canonical_u64(), rhs: __v_47.as_canonical_u64(), msg: None }); } match __v_12.as_canonical_u64() { _ => { match __v_17.as_canonical_u64() { _ => { let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_21]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_48: G = __r_arr[0]; let __v_49: G = G::from_bool(__v_48 == G::ZERO); let __v_50: G = G::from_u64(0); if (__v_49 != __v_50) { return Err(ExecError::AssertEqMismatch { lhs: __v_49.as_canonical_u64(), rhs: __v_50.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_39]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_51: G = __r_arr[0]; if (__v_51 != __v_48) { return Err(ExecError::AssertEqMismatch { lhs: __v_51.as_canonical_u64(), rhs: __v_48.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_52: G = __r_arr[0]; if (__v_52 != __v_48) { return Err(ExecError::AssertEqMismatch { lhs: __v_52.as_canonical_u64(), rhs: __v_48.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; if (__v_53 != __v_48) { return Err(ExecError::AssertEqMismatch { lhs: __v_53.as_canonical_u64(), rhs: __v_48.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_209] = { let __args: [G; IN_209] = [__v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(209, &__args[..])?) { [G::ZERO; OUT_209] } else { let (__hash, __hit) = record.function_queries[209].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[209].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[209].bump_multiplicity(__i); } let __ret: [G; OUT_209] = unsafe { *(record.function_queries[209].output_at(__i).as_ptr() as *const [G; OUT_209]) }; __ret }, _ => { aiur_fn_209::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __v_55: G = G::from_u64(1); if (__v_54 != __v_55) { return Err(ExecError::AssertEqMismatch { lhs: __v_54.as_canonical_u64(), rhs: __v_55.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_56: G = __r_arr[0]; let __r_arr: [G; OUT_318] = { let __args: [G; IN_318] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(318, &__args[..])?) { [G::ZERO; OUT_318] } else { let (__hash, __hit) = record.function_queries[318].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[318].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[318].bump_multiplicity(__i); } let __ret: [G; OUT_318] = unsafe { *(record.function_queries[318].output_at(__i).as_ptr() as *const [G; OUT_318]) }; __ret }, _ => { aiur_fn_318::(__args, __hash, record, io_buffer)? }, } } }; let __v_57: G = __r_arr[0]; let __v_58: G = __v_56 - __v_57; let __v_59: G = G::from_bool(__v_58 == G::ZERO); if (__v_59 != __v_55) { return Err(ExecError::AssertEqMismatch { lhs: __v_59.as_canonical_u64(), rhs: __v_55.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_210] = { let __args: [G; IN_210] = [__v_8, __v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(210, &__args[..])?) { [G::ZERO; OUT_210] } else { let (__hash, __hit) = record.function_queries[210].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[210].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[210].bump_multiplicity(__i); } let __ret: [G; OUT_210] = unsafe { *(record.function_queries[210].output_at(__i).as_ptr() as *const [G; OUT_210]) }; __ret }, _ => { aiur_fn_210::(__args, __hash, record, io_buffer)? }, } } }; let __v_60: G = __r_arr[0]; let __r_arr: [G; OUT_211] = { let __args: [G; IN_211] = [__v_11, __v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(211, &__args[..])?) { [G::ZERO; OUT_211] } else { let (__hash, __hit) = record.function_queries[211].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[211].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[211].bump_multiplicity(__i); } let __ret: [G; OUT_211] = unsafe { *(record.function_queries[211].output_at(__i).as_ptr() as *const [G; OUT_211]) }; __ret }, _ => { aiur_fn_211::(__args, __hash, record, io_buffer)? }, } } }; let __v_61: G = __r_arr[0]; let __r_arr: [G; OUT_318] = { let __args: [G; IN_318] = [__v_60]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(318, &__args[..])?) { [G::ZERO; OUT_318] } else { let (__hash, __hit) = record.function_queries[318].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[318].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[318].bump_multiplicity(__i); } let __ret: [G; OUT_318] = unsafe { *(record.function_queries[318].output_at(__i).as_ptr() as *const [G; OUT_318]) }; __ret }, _ => { aiur_fn_318::(__args, __hash, record, io_buffer)? }, } } }; let __v_62: G = __r_arr[0]; let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_21]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_63: G = __r_arr[0]; let __v_64: G = __v_62 - __v_63; let __v_65: G = G::from_bool(__v_64 == G::ZERO); if (__v_65 != __v_55) { return Err(ExecError::AssertEqMismatch { lhs: __v_65.as_canonical_u64(), rhs: __v_55.as_canonical_u64(), msg: None }); } match __v_18.as_canonical_u64() { _ => { let __r_arr: [G; OUT_221] = { let __args: [G; IN_221] = [__v_12, __v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(221, &__args[..])?) { [G::ZERO; OUT_221] } else { let (__hash, __hit) = record.function_queries[221].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[221].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[221].bump_multiplicity(__i); } let __ret: [G; OUT_221] = unsafe { *(record.function_queries[221].output_at(__i).as_ptr() as *const [G; OUT_221]) }; __ret }, _ => { aiur_fn_221::(__args, __hash, record, io_buffer)? }, } } }; let __v_66: G = __r_arr[0]; let __v_67: G = G::from_u64(1); if (__v_66 != __v_67) { return Err(ExecError::AssertEqMismatch { lhs: __v_66.as_canonical_u64(), rhs: __v_67.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_224] = { let __args: [G; IN_224] = [__v_13, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(224, &__args[..])?) { [G::ZERO; OUT_224] } else { let (__hash, __hit) = record.function_queries[224].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[224].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[224].bump_multiplicity(__i); } let __ret: [G; OUT_224] = unsafe { *(record.function_queries[224].output_at(__i).as_ptr() as *const [G; OUT_224]) }; __ret }, _ => { aiur_fn_224::(__args, __hash, record, io_buffer)? }, } } }; let __v_68: G = __r_arr[0]; if (__v_68 != __v_67) { return Err(ExecError::AssertEqMismatch { lhs: __v_68.as_canonical_u64(), rhs: __v_67.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_221] = { let __args: [G; IN_221] = [__v_14, __v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(221, &__args[..])?) { [G::ZERO; OUT_221] } else { let (__hash, __hit) = record.function_queries[221].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[221].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[221].bump_multiplicity(__i); } let __ret: [G; OUT_221] = unsafe { *(record.function_queries[221].output_at(__i).as_ptr() as *const [G; OUT_221]) }; __ret }, _ => { aiur_fn_221::(__args, __hash, record, io_buffer)? }, } } }; let __v_69: G = __r_arr[0]; if (__v_69 != __v_67) { return Err(ExecError::AssertEqMismatch { lhs: __v_69.as_canonical_u64(), rhs: __v_67.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_228] = { let __args: [G; IN_228] = [__v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(228, &__args[..])?) { [G::ZERO; OUT_228] } else { let (__hash, __hit) = record.function_queries[228].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[228].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[228].bump_multiplicity(__i); } let __ret: [G; OUT_228] = unsafe { *(record.function_queries[228].output_at(__i).as_ptr() as *const [G; OUT_228]) }; __ret }, _ => { aiur_fn_228::(__args, __hash, record, io_buffer)? }, } } }; let __v_70: G = __r_arr[0]; if (__v_70 != __v_67) { return Err(ExecError::AssertEqMismatch { lhs: __v_70.as_canonical_u64(), rhs: __v_67.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_229] = { let __args: [G; IN_229] = [__v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(229, &__args[..])?) { [G::ZERO; OUT_229] } else { let (__hash, __hit) = record.function_queries[229].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[229].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[229].bump_multiplicity(__i); } let __ret: [G; OUT_229] = unsafe { *(record.function_queries[229].output_at(__i).as_ptr() as *const [G; OUT_229]) }; __ret }, _ => { aiur_fn_229::(__args, __hash, record, io_buffer)? }, } } }; let __v_71: G = __r_arr[0]; let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_72: G = __r_arr[0]; if (__v_71 != __v_72) { return Err(ExecError::AssertEqMismatch { lhs: __v_71.as_canonical_u64(), rhs: __v_72.as_canonical_u64(), msg: None }); } let __mc_out___mc_0: [G; 1] = '__mc_0: { match __v_9.as_canonical_u64() { 0u64 => { let __v_73: G = G::from_u64(1); let __v_74: G = { let __values: [G; 3] = [__v_73, __v_73, __v_73]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; break '__mc_0 [__v_74]; }, 1u64 => { break '__mc_0 [__v_10]; }, _ => { return Err(ExecError::MatchNoCase(__v_9.as_canonical_u64())); }, } }; let __v_73: G = __mc_out___mc_0[0]; let __r_arr: [G; OUT_220] = { let __args: [G; IN_220] = [__v_41, __v_16, __v_19, __v_20, __v_21]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(220, &__args[..])?) { [G::ZERO; OUT_220] } else { let (__hash, __hit) = record.function_queries[220].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[220].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[220].bump_multiplicity(__i); } let __ret: [G; OUT_220] = unsafe { *(record.function_queries[220].output_at(__i).as_ptr() as *const [G; OUT_220]) }; __ret }, _ => { aiur_fn_220::(__args, __hash, record, io_buffer)? }, } } }; let __v_74: G = __r_arr[0]; let __v_75: G = __r_arr[1]; let __v_76: G = __r_arr[2]; let __v_77: G = __r_arr[3]; let __v_78: G = __r_arr[4]; let __v_79: G = G::from_u64(0); let __r_arr: [G; OUT_208] = { let __args: [G; IN_208] = [__v_79, __v_79, __v_15, __v_42, __v_43, __v_44, __v_45]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(208, &__args[..])?) { [G::ZERO; OUT_208] } else { let (__hash, __hit) = record.function_queries[208].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[208].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[208].bump_multiplicity(__i); } let __ret: [G; OUT_208] = unsafe { *(record.function_queries[208].output_at(__i).as_ptr() as *const [G; OUT_208]) }; __ret }, _ => { aiur_fn_208::(__args, __hash, record, io_buffer)? }, } } }; let __v_80: G = __r_arr[0]; let __v_81: G = __r_arr[1]; let __r_arr: [G; OUT_206] = { let __args: [G; IN_206] = [__v_60, __v_61, __v_22, __v_21, __v_39, __v_40, __v_37, __v_38, __v_36, __v_79, __v_80, __v_81, __v_42, __v_43, __v_44, __v_45, __v_74, __v_75, __v_76, __v_77]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(206, &__args[..])?) { [G::ZERO; OUT_206] } else { let (__hash, __hit) = record.function_queries[206].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[206].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[206].bump_multiplicity(__i); } let __ret: [G; OUT_206] = unsafe { *(record.function_queries[206].output_at(__i).as_ptr() as *const [G; OUT_206]) }; __ret }, _ => { aiur_fn_206::(__args, __hash, record, io_buffer)? }, } } }; let __v_82: G = __r_arr[0]; let __r_arr: [G; OUT_318] = { let __args: [G; IN_318] = [__v_60]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(318, &__args[..])?) { [G::ZERO; OUT_318] } else { let (__hash, __hit) = record.function_queries[318].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[318].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[318].bump_multiplicity(__i); } let __ret: [G; OUT_318] = unsafe { *(record.function_queries[318].output_at(__i).as_ptr() as *const [G; OUT_318]) }; __ret }, _ => { aiur_fn_318::(__args, __hash, record, io_buffer)? }, } } }; let __v_83: G = __r_arr[0]; match __v_23.as_canonical_u64() { _ => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_23]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_84: G = __r_arr[0]; let __r_arr: [G; OUT_317] = { let __args: [G; IN_317] = [__v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(317, &__args[..])?) { [G::ZERO; OUT_317] } else { let (__hash, __hit) = record.function_queries[317].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[317].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[317].bump_multiplicity(__i); } let __ret: [G; OUT_317] = unsafe { *(record.function_queries[317].output_at(__i).as_ptr() as *const [G; OUT_317]) }; __ret }, _ => { aiur_fn_317::(__args, __hash, record, io_buffer)? }, } } }; let __v_85: G = __r_arr[0]; let __v_86: G = __v_85 - __v_84; let __v_87: G = G::from_bool(__v_86 == G::ZERO); let __v_88: G = G::from_u64(1); if (__v_87 != __v_88) { return Err(ExecError::AssertEqMismatch { lhs: __v_87.as_canonical_u64(), rhs: __v_88.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_89: G = __r_arr[0]; let __v_90: G = G::from_u64(3); let __r_arr: [G; OUT_148] = { let __args: [G; IN_148] = [__v_37, __v_38]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(148, &__args[..])?) { [G::ZERO; OUT_148] } else { let (__hash, __hit) = record.function_queries[148].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[148].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[148].bump_multiplicity(__i); } let __ret: [G; OUT_148] = unsafe { *(record.function_queries[148].output_at(__i).as_ptr() as *const [G; OUT_148]) }; __ret }, _ => { aiur_fn_148::(__args, __hash, record, io_buffer)? }, } } }; let __v_91: G = __r_arr[0]; let __v_92: G = __v_90 + __v_91; if (__v_89 != __v_92) { return Err(ExecError::AssertEqMismatch { lhs: __v_89.as_canonical_u64(), rhs: __v_92.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_93: G = __r_arr[0]; let __v_94: G = __v_93 - __v_84; let __v_95: G = G::from_bool(__v_94 == G::ZERO); if (__v_95 != __v_88) { return Err(ExecError::AssertEqMismatch { lhs: __v_95.as_canonical_u64(), rhs: __v_88.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_96: G = __r_arr[0]; if (__v_96 != __v_88) { return Err(ExecError::AssertEqMismatch { lhs: __v_96.as_canonical_u64(), rhs: __v_88.as_canonical_u64(), msg: None }); } let __v_97: G = { let __values: [G; 3] = [__v_88, __v_88, __v_88]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __mc_out___mc_1: [G; 1] = '__mc_1: { match __v_37.as_canonical_u64() { 0u64 => { break '__mc_1 [__v_97]; }, 1u64 => { let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_38, __v_97]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_98: G = __r_arr[0]; break '__mc_1 [__v_98]; }, _ => { return Err(ExecError::MatchNoCase(__v_37.as_canonical_u64())); }, } }; let __v_98: G = __mc_out___mc_1[0]; let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_36, __v_98]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_99: G = __r_arr[0]; let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_40, __v_99]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_100: G = __r_arr[0]; let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_39, __v_100]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_101: G = __r_arr[0]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_78, __v_101]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_102: G = __r_arr[0]; let __v_103: G = G::from_u64(1); let __v_104: G = { let __values: [G; 3] = [__v_103, __v_103, __v_103]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_180] = { let __args: [G; IN_180] = [__v_102, __v_104]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(180, &__args[..])?) { [G::ZERO; OUT_180] } else { let (__hash, __hit) = record.function_queries[180].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[180].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[180].bump_multiplicity(__i); } let __ret: [G; OUT_180] = unsafe { *(record.function_queries[180].output_at(__i).as_ptr() as *const [G; OUT_180]) }; __ret }, _ => { aiur_fn_180::(__args, __hash, record, io_buffer)? }, } } }; let __v_105: G = __r_arr[0]; let __v_106: G = __r_arr[1]; let __v_107: G = __r_arr[2]; let __v_108: G = __r_arr[3]; let __v_109: G = __r_arr[4]; let __v_110: G = __r_arr[5]; let __v_111: G = __r_arr[6]; let __v_112: G = __r_arr[7]; let __v_113: G = __r_arr[8]; let __v_114: G = __r_arr[9]; let __v_115: G = __r_arr[10]; let __v_116: G = __r_arr[11]; let __v_117: G = __r_arr[12]; let __v_118: G = __r_arr[13]; let __v_119: G = __r_arr[14]; let __v_120: G = __r_arr[15]; let __v_121: G = __r_arr[16]; let __v_122: G = __r_arr[17]; let __v_123: G = G::from_u64(256); let __v_124: G = __v_123 * __v_106; let __v_125: G = __v_105 + __v_124; let __v_126: G = G::from_u64(65536); let __v_127: G = __v_126 * __v_107; let __v_128: G = __v_125 + __v_127; let __v_129: G = G::from_u64(16777216); let __v_130: G = __v_129 * __v_108; let __v_131: G = __v_128 + __v_130; let __v_132: G = G::from_u64(4294967296); let __v_133: G = __v_132 * __v_109; let __v_134: G = __v_131 + __v_133; let __v_135: G = G::from_u64(1099511627776); let __v_136: G = __v_135 * __v_110; let __v_137: G = __v_134 + __v_136; let __v_138: G = G::from_u64(281474976710656); let __v_139: G = __v_138 * __v_111; let __v_140: G = __v_137 + __v_139; let __v_141: G = G::from_u64(72057594037927936); let __v_142: G = __v_141 * __v_112; let __v_143: G = __v_140 + __v_142; let __v_144: G = __v_123 * __v_114; let __v_145: G = __v_113 + __v_144; let __v_146: G = __v_126 * __v_115; let __v_147: G = __v_145 + __v_146; let __v_148: G = __v_129 * __v_116; let __v_149: G = __v_147 + __v_148; let __v_150: G = __v_132 * __v_117; let __v_151: G = __v_149 + __v_150; let __v_152: G = __v_135 * __v_118; let __v_153: G = __v_151 + __v_152; let __v_154: G = __v_138 * __v_119; let __v_155: G = __v_153 + __v_154; let __v_156: G = __v_141 * __v_120; let __v_157: G = __v_155 + __v_156; let __r_arr: [G; OUT_143] = { let __args: [G; IN_143] = [__v_121, __v_23, __v_24, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(143, &__args[..])?) { [G::ZERO; OUT_143] } else { let (__hash, __hit) = record.function_queries[143].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[143].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[143].bump_multiplicity(__i); } let __ret: [G; OUT_143] = unsafe { *(record.function_queries[143].output_at(__i).as_ptr() as *const [G; OUT_143]) }; __ret }, _ => { aiur_fn_143::(__args, __hash, record, io_buffer)? }, } } }; let __v_158: G = __r_arr[0]; let __v_159: G = __r_arr[1]; let __r_arr: [G; OUT_137] = { let __args: [G; IN_137] = [__v_27, __v_104]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(137, &__args[..])?) { [G::ZERO; OUT_137] } else { let (__hash, __hit) = record.function_queries[137].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[137].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[137].bump_multiplicity(__i); } let __ret: [G; OUT_137] = unsafe { *(record.function_queries[137].output_at(__i).as_ptr() as *const [G; OUT_137]) }; __ret }, _ => { aiur_fn_137::(__args, __hash, record, io_buffer)? }, } } }; let __v_160: G = __r_arr[0]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_159, __v_160]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_161: G = __r_arr[0]; let __r_arr: [G; OUT_141] = { let __args: [G; IN_141] = [__v_161, __v_23]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(141, &__args[..])?) { [G::ZERO; OUT_141] } else { let (__hash, __hit) = record.function_queries[141].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[141].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[141].bump_multiplicity(__i); } let __ret: [G; OUT_141] = unsafe { *(record.function_queries[141].output_at(__i).as_ptr() as *const [G; OUT_141]) }; __ret }, _ => { aiur_fn_141::(__args, __hash, record, io_buffer)? }, } } }; let __v_162: G = __r_arr[0]; let __r_arr: [G; OUT_142] = { let __args: [G; IN_142] = [__v_162, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(142, &__args[..])?) { [G::ZERO; OUT_142] } else { let (__hash, __hit) = record.function_queries[142].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[142].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[142].bump_multiplicity(__i); } let __ret: [G; OUT_142] = unsafe { *(record.function_queries[142].output_at(__i).as_ptr() as *const [G; OUT_142]) }; __ret }, _ => { aiur_fn_142::(__args, __hash, record, io_buffer)? }, } } }; let __v_163: G = __r_arr[0]; let __v_164: G = __r_arr[1]; let __v_165: G = __v_84 + __v_0; let __v_166: G = G::from_u64(33); let __v_167: G = { let __a_val = __v_165.as_canonical_u64(); let __b_val = __v_166.as_canonical_u64(); let __a_u32 = u32::try_from(__a_val).ok().ok_or(ExecError::U32OutOfRange(__a_val))?; let __b_u32 = u32::try_from(__b_val).ok().ok_or(ExecError::U32OutOfRange(__b_val))?; let __result = G::from_bool(__a_u32 < __b_u32); if (!unconstrained) { let __c_u32 = __a_u32.wrapping_sub(__b_u32); for __word in [__a_u32, __c_u32, __b_u32] { record.bytes2_queries.bump_u16_range_check((__word & 65535) as u16); record.bytes2_queries.bump_u16_range_check((__word >> 16) as u16); } } __result }; if (__v_167 != __v_103) { return Err(ExecError::AssertEqMismatch { lhs: __v_167.as_canonical_u64(), rhs: __v_103.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_164] = { let __args: [G; IN_164] = [__v_163, __v_164, __v_4, __v_165]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(164, &__args[..])?) { [G::ZERO; OUT_164] } else { let (__hash, __hit) = record.function_queries[164].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[164].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[164].bump_multiplicity(__i); } let __ret: [G; OUT_164] = unsafe { *(record.function_queries[164].output_at(__i).as_ptr() as *const [G; OUT_164]) }; __ret }, _ => { aiur_fn_164::(__args, __hash, record, io_buffer)? }, } } }; let __v_168: G = __r_arr[0]; let __r_arr: [G; OUT_168] = { let __args: [G; IN_168] = [__v_25, __v_168, __v_18, __v_19, __v_20, __v_73, __v_37, __v_38, __v_61, __v_22, __v_83, __v_0, __v_165]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(168, &__args[..])?) { [G::ZERO; OUT_168] } else { let (__hash, __hit) = record.function_queries[168].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[168].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[168].bump_multiplicity(__i); } let __ret: [G; OUT_168] = unsafe { *(record.function_queries[168].output_at(__i).as_ptr() as *const [G; OUT_168]) }; __ret }, _ => { aiur_fn_168::(__args, __hash, record, io_buffer)? }, } } }; let __v_169: G = __r_arr[0]; if (__v_169 != __v_103) { return Err(ExecError::AssertEqMismatch { lhs: __v_169.as_canonical_u64(), rhs: __v_103.as_canonical_u64(), msg: None }); } let __v_170: G = G::from_u64(0); let __r_arr: [G; OUT_167] = { let __args: [G; IN_167] = [__v_25, __v_26, __v_170, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(167, &__args[..])?) { [G::ZERO; OUT_167] } else { let (__hash, __hit) = record.function_queries[167].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[167].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[167].bump_multiplicity(__i); } let __ret: [G; OUT_167] = unsafe { *(record.function_queries[167].output_at(__i).as_ptr() as *const [G; OUT_167]) }; __ret }, _ => { aiur_fn_167::(__args, __hash, record, io_buffer)? }, } } }; let __v_171: G = __r_arr[0]; let __r_arr: [G; OUT_163] = { let __args: [G; IN_163] = [__v_163, __v_164, __v_171, __v_143, __v_157, __v_39, __v_40, __v_36, __v_37, __v_38, __v_18, __v_19, __v_20, __v_73, __v_61, __v_22, __v_76, __v_77, __v_83, __v_0, __v_165, __v_158, __v_23, __v_27, __v_84]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(163, &__args[..])?) { [G::ZERO; OUT_163] } else { let (__hash, __hit) = record.function_queries[163].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[163].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[163].bump_multiplicity(__i); } let __ret: [G; OUT_163] = unsafe { *(record.function_queries[163].output_at(__i).as_ptr() as *const [G; OUT_163]) }; __ret }, _ => { aiur_fn_163::(__args, __hash, record, io_buffer)? }, } } }; let __v_172: G = __r_arr[0]; let __r_arr: [G; OUT_171] = { let __args: [G; IN_171] = [__v_26, __v_23, __v_172, __v_168, __v_170, __v_165]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(171, &__args[..])?) { [G::ZERO; OUT_171] } else { let (__hash, __hit) = record.function_queries[171].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[171].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[171].bump_multiplicity(__i); } let __ret: [G; OUT_171] = unsafe { *(record.function_queries[171].output_at(__i).as_ptr() as *const [G; OUT_171]) }; __ret }, _ => { aiur_fn_171::(__args, __hash, record, io_buffer)? }, } } }; let __v_173: G = __r_arr[0]; if (__v_173 != __v_103) { return Err(ExecError::AssertEqMismatch { lhs: __v_173.as_canonical_u64(), rhs: __v_103.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_225] = { let __args: [G; IN_225] = [__v_21]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(225, &__args[..])?) { [G::ZERO; OUT_225] } else { let (__hash, __hit) = record.function_queries[225].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[225].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[225].bump_multiplicity(__i); } let __ret: [G; OUT_225] = unsafe { *(record.function_queries[225].output_at(__i).as_ptr() as *const [G; OUT_225]) }; __ret }, _ => { aiur_fn_225::(__args, __hash, record, io_buffer)? }, } } }; let __v_174: G = __r_arr[0]; let __v_175: G = __r_arr[1]; let __ret: [G; OUT_235] = [__v_174, __v_175]; record.function_queries[235].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, } }, } }, } }, } }, } }) } +fn aiur_fn_235(inp: [G; IN_235], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_235], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = inp[34]; let __v_35: G = inp[35]; let __v_36: G = inp[36]; let __v_37: G = inp[37]; let __v_38: G = inp[38]; let __v_39: G = inp[39]; let __v_40: G = inp[40]; let __v_41: G = inp[41]; let __v_42: G = inp[42]; let __v_43: G = inp[43]; let __v_44: G = inp[44]; let __v_45: G = inp[45]; match __v_0.as_canonical_u64() { _ => { match __v_0.as_canonical_u64() { _ => { let __v_46: G = G::from_u64(0); if (__v_1 != __v_46) { return Err(ExecError::AssertEqMismatch { lhs: __v_1.as_canonical_u64(), rhs: __v_46.as_canonical_u64(), msg: None }); } if (__v_2 != __v_46) { return Err(ExecError::AssertEqMismatch { lhs: __v_2.as_canonical_u64(), rhs: __v_46.as_canonical_u64(), msg: None }); } let __v_47: G = G::from_u64(1); if (__v_3 != __v_47) { return Err(ExecError::AssertEqMismatch { lhs: __v_3.as_canonical_u64(), rhs: __v_47.as_canonical_u64(), msg: None }); } match __v_12.as_canonical_u64() { _ => { match __v_17.as_canonical_u64() { _ => { let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_21]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_48: G = __r_arr[0]; let __v_49: G = G::from_bool(__v_48 == G::ZERO); let __v_50: G = G::from_u64(0); if (__v_49 != __v_50) { return Err(ExecError::AssertEqMismatch { lhs: __v_49.as_canonical_u64(), rhs: __v_50.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_39]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_51: G = __r_arr[0]; if (__v_51 != __v_48) { return Err(ExecError::AssertEqMismatch { lhs: __v_51.as_canonical_u64(), rhs: __v_48.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_52: G = __r_arr[0]; if (__v_52 != __v_48) { return Err(ExecError::AssertEqMismatch { lhs: __v_52.as_canonical_u64(), rhs: __v_48.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; if (__v_53 != __v_48) { return Err(ExecError::AssertEqMismatch { lhs: __v_53.as_canonical_u64(), rhs: __v_48.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_209] = { let __args: [G; IN_209] = [__v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(209, &__args[..])?) { [G::ZERO; OUT_209] } else { let (__hash, __hit) = record.function_queries[209].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[209].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[209].bump_multiplicity(__i); } let __ret: [G; OUT_209] = unsafe { *(record.function_queries[209].output_at(__i).as_ptr() as *const [G; OUT_209]) }; __ret }, _ => { aiur_fn_209::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __v_55: G = G::from_u64(1); if (__v_54 != __v_55) { return Err(ExecError::AssertEqMismatch { lhs: __v_54.as_canonical_u64(), rhs: __v_55.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_56: G = __r_arr[0]; let __r_arr: [G; OUT_320] = { let __args: [G; IN_320] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(320, &__args[..])?) { [G::ZERO; OUT_320] } else { let (__hash, __hit) = record.function_queries[320].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[320].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[320].bump_multiplicity(__i); } let __ret: [G; OUT_320] = unsafe { *(record.function_queries[320].output_at(__i).as_ptr() as *const [G; OUT_320]) }; __ret }, _ => { aiur_fn_320::(__args, __hash, record, io_buffer)? }, } } }; let __v_57: G = __r_arr[0]; let __v_58: G = __v_56 - __v_57; let __v_59: G = G::from_bool(__v_58 == G::ZERO); if (__v_59 != __v_55) { return Err(ExecError::AssertEqMismatch { lhs: __v_59.as_canonical_u64(), rhs: __v_55.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_210] = { let __args: [G; IN_210] = [__v_8, __v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(210, &__args[..])?) { [G::ZERO; OUT_210] } else { let (__hash, __hit) = record.function_queries[210].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[210].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[210].bump_multiplicity(__i); } let __ret: [G; OUT_210] = unsafe { *(record.function_queries[210].output_at(__i).as_ptr() as *const [G; OUT_210]) }; __ret }, _ => { aiur_fn_210::(__args, __hash, record, io_buffer)? }, } } }; let __v_60: G = __r_arr[0]; let __r_arr: [G; OUT_211] = { let __args: [G; IN_211] = [__v_11, __v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(211, &__args[..])?) { [G::ZERO; OUT_211] } else { let (__hash, __hit) = record.function_queries[211].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[211].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[211].bump_multiplicity(__i); } let __ret: [G; OUT_211] = unsafe { *(record.function_queries[211].output_at(__i).as_ptr() as *const [G; OUT_211]) }; __ret }, _ => { aiur_fn_211::(__args, __hash, record, io_buffer)? }, } } }; let __v_61: G = __r_arr[0]; let __r_arr: [G; OUT_320] = { let __args: [G; IN_320] = [__v_60]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(320, &__args[..])?) { [G::ZERO; OUT_320] } else { let (__hash, __hit) = record.function_queries[320].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[320].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[320].bump_multiplicity(__i); } let __ret: [G; OUT_320] = unsafe { *(record.function_queries[320].output_at(__i).as_ptr() as *const [G; OUT_320]) }; __ret }, _ => { aiur_fn_320::(__args, __hash, record, io_buffer)? }, } } }; let __v_62: G = __r_arr[0]; let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_21]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_63: G = __r_arr[0]; let __v_64: G = __v_62 - __v_63; let __v_65: G = G::from_bool(__v_64 == G::ZERO); if (__v_65 != __v_55) { return Err(ExecError::AssertEqMismatch { lhs: __v_65.as_canonical_u64(), rhs: __v_55.as_canonical_u64(), msg: None }); } match __v_18.as_canonical_u64() { _ => { let __r_arr: [G; OUT_221] = { let __args: [G; IN_221] = [__v_12, __v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(221, &__args[..])?) { [G::ZERO; OUT_221] } else { let (__hash, __hit) = record.function_queries[221].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[221].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[221].bump_multiplicity(__i); } let __ret: [G; OUT_221] = unsafe { *(record.function_queries[221].output_at(__i).as_ptr() as *const [G; OUT_221]) }; __ret }, _ => { aiur_fn_221::(__args, __hash, record, io_buffer)? }, } } }; let __v_66: G = __r_arr[0]; let __v_67: G = G::from_u64(1); if (__v_66 != __v_67) { return Err(ExecError::AssertEqMismatch { lhs: __v_66.as_canonical_u64(), rhs: __v_67.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_224] = { let __args: [G; IN_224] = [__v_13, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(224, &__args[..])?) { [G::ZERO; OUT_224] } else { let (__hash, __hit) = record.function_queries[224].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[224].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[224].bump_multiplicity(__i); } let __ret: [G; OUT_224] = unsafe { *(record.function_queries[224].output_at(__i).as_ptr() as *const [G; OUT_224]) }; __ret }, _ => { aiur_fn_224::(__args, __hash, record, io_buffer)? }, } } }; let __v_68: G = __r_arr[0]; if (__v_68 != __v_67) { return Err(ExecError::AssertEqMismatch { lhs: __v_68.as_canonical_u64(), rhs: __v_67.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_221] = { let __args: [G; IN_221] = [__v_14, __v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(221, &__args[..])?) { [G::ZERO; OUT_221] } else { let (__hash, __hit) = record.function_queries[221].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[221].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[221].bump_multiplicity(__i); } let __ret: [G; OUT_221] = unsafe { *(record.function_queries[221].output_at(__i).as_ptr() as *const [G; OUT_221]) }; __ret }, _ => { aiur_fn_221::(__args, __hash, record, io_buffer)? }, } } }; let __v_69: G = __r_arr[0]; if (__v_69 != __v_67) { return Err(ExecError::AssertEqMismatch { lhs: __v_69.as_canonical_u64(), rhs: __v_67.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_228] = { let __args: [G; IN_228] = [__v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(228, &__args[..])?) { [G::ZERO; OUT_228] } else { let (__hash, __hit) = record.function_queries[228].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[228].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[228].bump_multiplicity(__i); } let __ret: [G; OUT_228] = unsafe { *(record.function_queries[228].output_at(__i).as_ptr() as *const [G; OUT_228]) }; __ret }, _ => { aiur_fn_228::(__args, __hash, record, io_buffer)? }, } } }; let __v_70: G = __r_arr[0]; if (__v_70 != __v_67) { return Err(ExecError::AssertEqMismatch { lhs: __v_70.as_canonical_u64(), rhs: __v_67.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_229] = { let __args: [G; IN_229] = [__v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(229, &__args[..])?) { [G::ZERO; OUT_229] } else { let (__hash, __hit) = record.function_queries[229].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[229].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[229].bump_multiplicity(__i); } let __ret: [G; OUT_229] = unsafe { *(record.function_queries[229].output_at(__i).as_ptr() as *const [G; OUT_229]) }; __ret }, _ => { aiur_fn_229::(__args, __hash, record, io_buffer)? }, } } }; let __v_71: G = __r_arr[0]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_72: G = __r_arr[0]; if (__v_71 != __v_72) { return Err(ExecError::AssertEqMismatch { lhs: __v_71.as_canonical_u64(), rhs: __v_72.as_canonical_u64(), msg: None }); } let __mc_out___mc_0: [G; 1] = '__mc_0: { match __v_9.as_canonical_u64() { 0u64 => { let __v_73: G = G::from_u64(1); let __v_74: G = { let __values: [G; 3] = [__v_73, __v_73, __v_73]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; break '__mc_0 [__v_74]; }, 1u64 => { break '__mc_0 [__v_10]; }, _ => { return Err(ExecError::MatchNoCase(__v_9.as_canonical_u64())); }, } }; let __v_73: G = __mc_out___mc_0[0]; let __r_arr: [G; OUT_220] = { let __args: [G; IN_220] = [__v_41, __v_16, __v_19, __v_20, __v_21]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(220, &__args[..])?) { [G::ZERO; OUT_220] } else { let (__hash, __hit) = record.function_queries[220].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[220].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[220].bump_multiplicity(__i); } let __ret: [G; OUT_220] = unsafe { *(record.function_queries[220].output_at(__i).as_ptr() as *const [G; OUT_220]) }; __ret }, _ => { aiur_fn_220::(__args, __hash, record, io_buffer)? }, } } }; let __v_74: G = __r_arr[0]; let __v_75: G = __r_arr[1]; let __v_76: G = __r_arr[2]; let __v_77: G = __r_arr[3]; let __v_78: G = __r_arr[4]; let __v_79: G = G::from_u64(0); let __r_arr: [G; OUT_208] = { let __args: [G; IN_208] = [__v_79, __v_79, __v_15, __v_42, __v_43, __v_44, __v_45]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(208, &__args[..])?) { [G::ZERO; OUT_208] } else { let (__hash, __hit) = record.function_queries[208].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[208].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[208].bump_multiplicity(__i); } let __ret: [G; OUT_208] = unsafe { *(record.function_queries[208].output_at(__i).as_ptr() as *const [G; OUT_208]) }; __ret }, _ => { aiur_fn_208::(__args, __hash, record, io_buffer)? }, } } }; let __v_80: G = __r_arr[0]; let __v_81: G = __r_arr[1]; let __r_arr: [G; OUT_206] = { let __args: [G; IN_206] = [__v_60, __v_61, __v_22, __v_21, __v_39, __v_40, __v_37, __v_38, __v_36, __v_79, __v_80, __v_81, __v_42, __v_43, __v_44, __v_45, __v_74, __v_75, __v_76, __v_77]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(206, &__args[..])?) { [G::ZERO; OUT_206] } else { let (__hash, __hit) = record.function_queries[206].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[206].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[206].bump_multiplicity(__i); } let __ret: [G; OUT_206] = unsafe { *(record.function_queries[206].output_at(__i).as_ptr() as *const [G; OUT_206]) }; __ret }, _ => { aiur_fn_206::(__args, __hash, record, io_buffer)? }, } } }; let __v_82: G = __r_arr[0]; let __r_arr: [G; OUT_320] = { let __args: [G; IN_320] = [__v_60]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(320, &__args[..])?) { [G::ZERO; OUT_320] } else { let (__hash, __hit) = record.function_queries[320].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[320].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[320].bump_multiplicity(__i); } let __ret: [G; OUT_320] = unsafe { *(record.function_queries[320].output_at(__i).as_ptr() as *const [G; OUT_320]) }; __ret }, _ => { aiur_fn_320::(__args, __hash, record, io_buffer)? }, } } }; let __v_83: G = __r_arr[0]; match __v_23.as_canonical_u64() { _ => { let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_23]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_84: G = __r_arr[0]; let __r_arr: [G; OUT_319] = { let __args: [G; IN_319] = [__v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(319, &__args[..])?) { [G::ZERO; OUT_319] } else { let (__hash, __hit) = record.function_queries[319].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[319].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[319].bump_multiplicity(__i); } let __ret: [G; OUT_319] = unsafe { *(record.function_queries[319].output_at(__i).as_ptr() as *const [G; OUT_319]) }; __ret }, _ => { aiur_fn_319::(__args, __hash, record, io_buffer)? }, } } }; let __v_85: G = __r_arr[0]; let __v_86: G = __v_85 - __v_84; let __v_87: G = G::from_bool(__v_86 == G::ZERO); let __v_88: G = G::from_u64(1); if (__v_87 != __v_88) { return Err(ExecError::AssertEqMismatch { lhs: __v_87.as_canonical_u64(), rhs: __v_88.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_89: G = __r_arr[0]; let __v_90: G = G::from_u64(3); let __r_arr: [G; OUT_148] = { let __args: [G; IN_148] = [__v_37, __v_38]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(148, &__args[..])?) { [G::ZERO; OUT_148] } else { let (__hash, __hit) = record.function_queries[148].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[148].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[148].bump_multiplicity(__i); } let __ret: [G; OUT_148] = unsafe { *(record.function_queries[148].output_at(__i).as_ptr() as *const [G; OUT_148]) }; __ret }, _ => { aiur_fn_148::(__args, __hash, record, io_buffer)? }, } } }; let __v_91: G = __r_arr[0]; let __v_92: G = __v_90 + __v_91; if (__v_89 != __v_92) { return Err(ExecError::AssertEqMismatch { lhs: __v_89.as_canonical_u64(), rhs: __v_92.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_325] = { let __args: [G; IN_325] = [__v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(325, &__args[..])?) { [G::ZERO; OUT_325] } else { let (__hash, __hit) = record.function_queries[325].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[325].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[325].bump_multiplicity(__i); } let __ret: [G; OUT_325] = unsafe { *(record.function_queries[325].output_at(__i).as_ptr() as *const [G; OUT_325]) }; __ret }, _ => { aiur_fn_325::(__args, __hash, record, io_buffer)? }, } } }; let __v_93: G = __r_arr[0]; let __v_94: G = __v_93 - __v_84; let __v_95: G = G::from_bool(__v_94 == G::ZERO); if (__v_95 != __v_88) { return Err(ExecError::AssertEqMismatch { lhs: __v_95.as_canonical_u64(), rhs: __v_88.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_96: G = __r_arr[0]; if (__v_96 != __v_88) { return Err(ExecError::AssertEqMismatch { lhs: __v_96.as_canonical_u64(), rhs: __v_88.as_canonical_u64(), msg: None }); } let __v_97: G = { let __values: [G; 3] = [__v_88, __v_88, __v_88]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __mc_out___mc_1: [G; 1] = '__mc_1: { match __v_37.as_canonical_u64() { 0u64 => { break '__mc_1 [__v_97]; }, 1u64 => { let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_38, __v_97]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_98: G = __r_arr[0]; break '__mc_1 [__v_98]; }, _ => { return Err(ExecError::MatchNoCase(__v_37.as_canonical_u64())); }, } }; let __v_98: G = __mc_out___mc_1[0]; let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_36, __v_98]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_99: G = __r_arr[0]; let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_40, __v_99]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_100: G = __r_arr[0]; let __r_arr: [G; OUT_139] = { let __args: [G; IN_139] = [__v_39, __v_100]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(139, &__args[..])?) { [G::ZERO; OUT_139] } else { let (__hash, __hit) = record.function_queries[139].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[139].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[139].bump_multiplicity(__i); } let __ret: [G; OUT_139] = unsafe { *(record.function_queries[139].output_at(__i).as_ptr() as *const [G; OUT_139]) }; __ret }, _ => { aiur_fn_139::(__args, __hash, record, io_buffer)? }, } } }; let __v_101: G = __r_arr[0]; let __r_arr: [G; OUT_316] = { let __args: [G; IN_316] = [__v_78, __v_101]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(316, &__args[..])?) { [G::ZERO; OUT_316] } else { let (__hash, __hit) = record.function_queries[316].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[316].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[316].bump_multiplicity(__i); } let __ret: [G; OUT_316] = unsafe { *(record.function_queries[316].output_at(__i).as_ptr() as *const [G; OUT_316]) }; __ret }, _ => { aiur_fn_316::(__args, __hash, record, io_buffer)? }, } } }; let __v_102: G = __r_arr[0]; let __v_103: G = G::from_u64(1); let __v_104: G = { let __values: [G; 3] = [__v_103, __v_103, __v_103]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_180] = { let __args: [G; IN_180] = [__v_102, __v_104]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(180, &__args[..])?) { [G::ZERO; OUT_180] } else { let (__hash, __hit) = record.function_queries[180].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[180].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[180].bump_multiplicity(__i); } let __ret: [G; OUT_180] = unsafe { *(record.function_queries[180].output_at(__i).as_ptr() as *const [G; OUT_180]) }; __ret }, _ => { aiur_fn_180::(__args, __hash, record, io_buffer)? }, } } }; let __v_105: G = __r_arr[0]; let __v_106: G = __r_arr[1]; let __v_107: G = __r_arr[2]; let __v_108: G = __r_arr[3]; let __v_109: G = __r_arr[4]; let __v_110: G = __r_arr[5]; let __v_111: G = __r_arr[6]; let __v_112: G = __r_arr[7]; let __v_113: G = __r_arr[8]; let __v_114: G = __r_arr[9]; let __v_115: G = __r_arr[10]; let __v_116: G = __r_arr[11]; let __v_117: G = __r_arr[12]; let __v_118: G = __r_arr[13]; let __v_119: G = __r_arr[14]; let __v_120: G = __r_arr[15]; let __v_121: G = __r_arr[16]; let __v_122: G = __r_arr[17]; let __v_123: G = G::from_u64(256); let __v_124: G = __v_123 * __v_106; let __v_125: G = __v_105 + __v_124; let __v_126: G = G::from_u64(65536); let __v_127: G = __v_126 * __v_107; let __v_128: G = __v_125 + __v_127; let __v_129: G = G::from_u64(16777216); let __v_130: G = __v_129 * __v_108; let __v_131: G = __v_128 + __v_130; let __v_132: G = G::from_u64(4294967296); let __v_133: G = __v_132 * __v_109; let __v_134: G = __v_131 + __v_133; let __v_135: G = G::from_u64(1099511627776); let __v_136: G = __v_135 * __v_110; let __v_137: G = __v_134 + __v_136; let __v_138: G = G::from_u64(281474976710656); let __v_139: G = __v_138 * __v_111; let __v_140: G = __v_137 + __v_139; let __v_141: G = G::from_u64(72057594037927936); let __v_142: G = __v_141 * __v_112; let __v_143: G = __v_140 + __v_142; let __v_144: G = __v_123 * __v_114; let __v_145: G = __v_113 + __v_144; let __v_146: G = __v_126 * __v_115; let __v_147: G = __v_145 + __v_146; let __v_148: G = __v_129 * __v_116; let __v_149: G = __v_147 + __v_148; let __v_150: G = __v_132 * __v_117; let __v_151: G = __v_149 + __v_150; let __v_152: G = __v_135 * __v_118; let __v_153: G = __v_151 + __v_152; let __v_154: G = __v_138 * __v_119; let __v_155: G = __v_153 + __v_154; let __v_156: G = __v_141 * __v_120; let __v_157: G = __v_155 + __v_156; let __r_arr: [G; OUT_143] = { let __args: [G; IN_143] = [__v_121, __v_23, __v_24, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(143, &__args[..])?) { [G::ZERO; OUT_143] } else { let (__hash, __hit) = record.function_queries[143].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[143].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[143].bump_multiplicity(__i); } let __ret: [G; OUT_143] = unsafe { *(record.function_queries[143].output_at(__i).as_ptr() as *const [G; OUT_143]) }; __ret }, _ => { aiur_fn_143::(__args, __hash, record, io_buffer)? }, } } }; let __v_158: G = __r_arr[0]; let __v_159: G = __r_arr[1]; let __r_arr: [G; OUT_137] = { let __args: [G; IN_137] = [__v_27, __v_104]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(137, &__args[..])?) { [G::ZERO; OUT_137] } else { let (__hash, __hit) = record.function_queries[137].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[137].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[137].bump_multiplicity(__i); } let __ret: [G; OUT_137] = unsafe { *(record.function_queries[137].output_at(__i).as_ptr() as *const [G; OUT_137]) }; __ret }, _ => { aiur_fn_137::(__args, __hash, record, io_buffer)? }, } } }; let __v_160: G = __r_arr[0]; let __r_arr: [G; OUT_316] = { let __args: [G; IN_316] = [__v_159, __v_160]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(316, &__args[..])?) { [G::ZERO; OUT_316] } else { let (__hash, __hit) = record.function_queries[316].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[316].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[316].bump_multiplicity(__i); } let __ret: [G; OUT_316] = unsafe { *(record.function_queries[316].output_at(__i).as_ptr() as *const [G; OUT_316]) }; __ret }, _ => { aiur_fn_316::(__args, __hash, record, io_buffer)? }, } } }; let __v_161: G = __r_arr[0]; let __r_arr: [G; OUT_141] = { let __args: [G; IN_141] = [__v_161, __v_23]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(141, &__args[..])?) { [G::ZERO; OUT_141] } else { let (__hash, __hit) = record.function_queries[141].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[141].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[141].bump_multiplicity(__i); } let __ret: [G; OUT_141] = unsafe { *(record.function_queries[141].output_at(__i).as_ptr() as *const [G; OUT_141]) }; __ret }, _ => { aiur_fn_141::(__args, __hash, record, io_buffer)? }, } } }; let __v_162: G = __r_arr[0]; let __r_arr: [G; OUT_142] = { let __args: [G; IN_142] = [__v_162, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(142, &__args[..])?) { [G::ZERO; OUT_142] } else { let (__hash, __hit) = record.function_queries[142].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[142].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[142].bump_multiplicity(__i); } let __ret: [G; OUT_142] = unsafe { *(record.function_queries[142].output_at(__i).as_ptr() as *const [G; OUT_142]) }; __ret }, _ => { aiur_fn_142::(__args, __hash, record, io_buffer)? }, } } }; let __v_163: G = __r_arr[0]; let __v_164: G = __r_arr[1]; let __v_165: G = __v_84 + __v_0; let __v_166: G = G::from_u64(33); let __v_167: G = { let __a_val = __v_165.as_canonical_u64(); let __b_val = __v_166.as_canonical_u64(); let __a_u32 = u32::try_from(__a_val).ok().ok_or(ExecError::U32OutOfRange(__a_val))?; let __b_u32 = u32::try_from(__b_val).ok().ok_or(ExecError::U32OutOfRange(__b_val))?; let __result = G::from_bool(__a_u32 < __b_u32); if (!unconstrained) { let __c_u32 = __a_u32.wrapping_sub(__b_u32); for __word in [__a_u32, __c_u32, __b_u32] { record.bytes2_queries.bump_u16_range_check((__word & 65535) as u16); record.bytes2_queries.bump_u16_range_check((__word >> 16) as u16); } } __result }; if (__v_167 != __v_103) { return Err(ExecError::AssertEqMismatch { lhs: __v_167.as_canonical_u64(), rhs: __v_103.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_164] = { let __args: [G; IN_164] = [__v_163, __v_164, __v_4, __v_165]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(164, &__args[..])?) { [G::ZERO; OUT_164] } else { let (__hash, __hit) = record.function_queries[164].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[164].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[164].bump_multiplicity(__i); } let __ret: [G; OUT_164] = unsafe { *(record.function_queries[164].output_at(__i).as_ptr() as *const [G; OUT_164]) }; __ret }, _ => { aiur_fn_164::(__args, __hash, record, io_buffer)? }, } } }; let __v_168: G = __r_arr[0]; let __r_arr: [G; OUT_168] = { let __args: [G; IN_168] = [__v_25, __v_168, __v_18, __v_19, __v_20, __v_73, __v_37, __v_38, __v_61, __v_22, __v_83, __v_0, __v_165]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(168, &__args[..])?) { [G::ZERO; OUT_168] } else { let (__hash, __hit) = record.function_queries[168].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[168].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[168].bump_multiplicity(__i); } let __ret: [G; OUT_168] = unsafe { *(record.function_queries[168].output_at(__i).as_ptr() as *const [G; OUT_168]) }; __ret }, _ => { aiur_fn_168::(__args, __hash, record, io_buffer)? }, } } }; let __v_169: G = __r_arr[0]; if (__v_169 != __v_103) { return Err(ExecError::AssertEqMismatch { lhs: __v_169.as_canonical_u64(), rhs: __v_103.as_canonical_u64(), msg: None }); } let __v_170: G = G::from_u64(0); let __r_arr: [G; OUT_167] = { let __args: [G; IN_167] = [__v_25, __v_26, __v_170, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(167, &__args[..])?) { [G::ZERO; OUT_167] } else { let (__hash, __hit) = record.function_queries[167].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[167].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[167].bump_multiplicity(__i); } let __ret: [G; OUT_167] = unsafe { *(record.function_queries[167].output_at(__i).as_ptr() as *const [G; OUT_167]) }; __ret }, _ => { aiur_fn_167::(__args, __hash, record, io_buffer)? }, } } }; let __v_171: G = __r_arr[0]; let __r_arr: [G; OUT_163] = { let __args: [G; IN_163] = [__v_163, __v_164, __v_171, __v_143, __v_157, __v_39, __v_40, __v_36, __v_37, __v_38, __v_18, __v_19, __v_20, __v_73, __v_61, __v_22, __v_76, __v_77, __v_83, __v_0, __v_165, __v_158, __v_23, __v_27, __v_84]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(163, &__args[..])?) { [G::ZERO; OUT_163] } else { let (__hash, __hit) = record.function_queries[163].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[163].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[163].bump_multiplicity(__i); } let __ret: [G; OUT_163] = unsafe { *(record.function_queries[163].output_at(__i).as_ptr() as *const [G; OUT_163]) }; __ret }, _ => { aiur_fn_163::(__args, __hash, record, io_buffer)? }, } } }; let __v_172: G = __r_arr[0]; let __r_arr: [G; OUT_171] = { let __args: [G; IN_171] = [__v_26, __v_23, __v_172, __v_168, __v_170, __v_165]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(171, &__args[..])?) { [G::ZERO; OUT_171] } else { let (__hash, __hit) = record.function_queries[171].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[171].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[171].bump_multiplicity(__i); } let __ret: [G; OUT_171] = unsafe { *(record.function_queries[171].output_at(__i).as_ptr() as *const [G; OUT_171]) }; __ret }, _ => { aiur_fn_171::(__args, __hash, record, io_buffer)? }, } } }; let __v_173: G = __r_arr[0]; if (__v_173 != __v_103) { return Err(ExecError::AssertEqMismatch { lhs: __v_173.as_canonical_u64(), rhs: __v_103.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_225] = { let __args: [G; IN_225] = [__v_21]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(225, &__args[..])?) { [G::ZERO; OUT_225] } else { let (__hash, __hit) = record.function_queries[225].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[225].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[225].bump_multiplicity(__i); } let __ret: [G; OUT_225] = unsafe { *(record.function_queries[225].output_at(__i).as_ptr() as *const [G; OUT_225]) }; __ret }, _ => { aiur_fn_225::(__args, __hash, record, io_buffer)? }, } } }; let __v_174: G = __r_arr[0]; let __v_175: G = __r_arr[1]; let __ret: [G; OUT_235] = [__v_174, __v_175]; record.function_queries[235].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, } }, } }, } }, } }, } }) } const INPUT_SIZE_236: usize = 22; const IN_236: usize = 22; const OUT_236: usize = 2; @@ -960,11 +960,11 @@ fn aiur_fn_236(inp: [G; IN_236], input_hash: u64, rec const INPUT_SIZE_237: usize = 1; const IN_237: usize = 1; const OUT_237: usize = 1; -fn aiur_fn_237(inp: [G; IN_237], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_237], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 6] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 6 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 6] = __args[..6].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; let __v_6: G = __loaded[5]; match __v_1.as_canonical_u64() { 1u64 => { let __v_7: G = G::from_u64(1); let __v_8: G = { let __values: [G; 3] = [__v_7, __v_7, __v_7]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_237] = [__v_8]; record.function_queries[237].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_2.as_canonical_u64() { _ => { let __r_arr: [G; OUT_237] = { let __args: [G; IN_237] = [__v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(237, &__args[..])?) { [G::ZERO; OUT_237] } else { let (__hash, __hit) = record.function_queries[237].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[237].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[237].bump_multiplicity(__i); } let __ret: [G; OUT_237] = unsafe { *(record.function_queries[237].output_at(__i).as_ptr() as *const [G; OUT_237]) }; __ret }, _ => { aiur_fn_237::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_5, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __ret: [G; OUT_237] = [__v_8]; record.function_queries[237].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } +fn aiur_fn_237(inp: [G; IN_237], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_237], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 6] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 6 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 6] = __args[..6].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; let __v_6: G = __loaded[5]; match __v_1.as_canonical_u64() { 1u64 => { let __v_7: G = G::from_u64(1); let __v_8: G = { let __values: [G; 3] = [__v_7, __v_7, __v_7]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_237] = [__v_8]; record.function_queries[237].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_2.as_canonical_u64() { _ => { let __r_arr: [G; OUT_237] = { let __args: [G; IN_237] = [__v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(237, &__args[..])?) { [G::ZERO; OUT_237] } else { let (__hash, __hit) = record.function_queries[237].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[237].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[237].bump_multiplicity(__i); } let __ret: [G; OUT_237] = unsafe { *(record.function_queries[237].output_at(__i).as_ptr() as *const [G; OUT_237]) }; __ret }, _ => { aiur_fn_237::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __r_arr: [G; OUT_316] = { let __args: [G; IN_316] = [__v_5, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(316, &__args[..])?) { [G::ZERO; OUT_316] } else { let (__hash, __hit) = record.function_queries[316].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[316].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[316].bump_multiplicity(__i); } let __ret: [G; OUT_316] = unsafe { *(record.function_queries[316].output_at(__i).as_ptr() as *const [G; OUT_316]) }; __ret }, _ => { aiur_fn_316::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __ret: [G; OUT_237] = [__v_8]; record.function_queries[237].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } const INPUT_SIZE_238: usize = 15; const IN_238: usize = 15; const OUT_238: usize = 1; -fn aiur_fn_238(inp: [G; IN_238], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_238], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; match __v_12.as_canonical_u64() { _ => { let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __r_arr: [G; OUT_230] = { let __args: [G; IN_230] = [__v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(230, &__args[..])?) { [G::ZERO; OUT_230] } else { let (__hash, __hit) = record.function_queries[230].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[230].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[230].bump_multiplicity(__i); } let __ret: [G; OUT_230] = unsafe { *(record.function_queries[230].output_at(__i).as_ptr() as *const [G; OUT_230]) }; __ret }, _ => { aiur_fn_230::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = G::from_u64(1); if (__v_16 != __v_17) { return Err(ExecError::AssertEqMismatch { lhs: __v_16.as_canonical_u64(), rhs: __v_17.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_311] = { let __args: [G; IN_311] = [__v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(311, &__args[..])?) { [G::ZERO; OUT_311] } else { let (__hash, __hit) = record.function_queries[311].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[311].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[311].bump_multiplicity(__i); } let __ret: [G; OUT_311] = unsafe { *(record.function_queries[311].output_at(__i).as_ptr() as *const [G; OUT_311]) }; __ret }, _ => { aiur_fn_311::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; if (__v_18 != __v_19) { return Err(ExecError::AssertEqMismatch { lhs: __v_18.as_canonical_u64(), rhs: __v_19.as_canonical_u64(), msg: None }); } let __v_20: G = G::from_u64(0); let __r_arr: [G; OUT_234] = { let __args: [G; IN_234] = [__v_13, __v_20, __v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(234, &__args[..])?) { [G::ZERO; OUT_234] } else { let (__hash, __hit) = record.function_queries[234].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[234].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[234].bump_multiplicity(__i); } let __ret: [G; OUT_234] = unsafe { *(record.function_queries[234].output_at(__i).as_ptr() as *const [G; OUT_234]) }; __ret }, _ => { aiur_fn_234::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; if (__v_21 != __v_17) { return Err(ExecError::AssertEqMismatch { lhs: __v_21.as_canonical_u64(), rhs: __v_17.as_canonical_u64(), msg: None }); } match __v_0.as_canonical_u64() { _ => { let __mc_out___mc_0: [G; 1] = '__mc_0: { match __v_9.as_canonical_u64() { 0u64 => { let __v_22: G = G::from_u64(1); let __v_23: G = { let __values: [G; 3] = [__v_22, __v_22, __v_22]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; break '__mc_0 [__v_23]; }, 1u64 => { break '__mc_0 [__v_10]; }, _ => { return Err(ExecError::MatchNoCase(__v_9.as_canonical_u64())); }, } }; let __v_22: G = __mc_out___mc_0[0]; let __r_arr: [G; OUT_219] = { let __args: [G; IN_219] = [__v_7, __v_22, __v_12, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(219, &__args[..])?) { [G::ZERO; OUT_219] } else { let (__hash, __hit) = record.function_queries[219].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[219].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[219].bump_multiplicity(__i); } let __ret: [G; OUT_219] = unsafe { *(record.function_queries[219].output_at(__i).as_ptr() as *const [G; OUT_219]) }; __ret }, _ => { aiur_fn_219::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __v_24: G = __r_arr[1]; let __v_25: G = __r_arr[2]; let __v_26: G = __r_arr[3]; let __v_27: G = __r_arr[4]; let __v_28: G = G::from_u64(0); let __r_arr: [G; OUT_226] = { let __args: [G; IN_226] = [__v_28, __v_28, __v_13, __v_23, __v_24, __v_25, __v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(226, &__args[..])?) { [G::ZERO; OUT_226] } else { let (__hash, __hit) = record.function_queries[226].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[226].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[226].bump_multiplicity(__i); } let __ret: [G; OUT_226] = unsafe { *(record.function_queries[226].output_at(__i).as_ptr() as *const [G; OUT_226]) }; __ret }, _ => { aiur_fn_226::(__args, __hash, record, io_buffer)? }, } } }; let __v_29: G = __r_arr[0]; let __v_30: G = __r_arr[1]; let __r_arr: [G; OUT_236] = { let __args: [G; IN_236] = [__v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_14, __v_28, __v_27, __v_23, __v_24, __v_25, __v_26, __v_29, __v_30]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(236, &__args[..])?) { [G::ZERO; OUT_236] } else { let (__hash, __hit) = record.function_queries[236].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[236].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[236].bump_multiplicity(__i); } let __ret: [G; OUT_236] = unsafe { *(record.function_queries[236].output_at(__i).as_ptr() as *const [G; OUT_236]) }; __ret }, _ => { aiur_fn_236::(__args, __hash, record, io_buffer)? }, } } }; let __v_31: G = __r_arr[0]; let __v_32: G = __r_arr[1]; let __v_33: G = G::from_bool(__v_31 == G::ZERO); let __v_34: G = G::from_bool(__v_32 == G::ZERO); let __v_35: G = __v_33 * __v_34; let __v_36: G = G::from_u64(1); if (__v_35 != __v_36) { return Err(ExecError::AssertEqMismatch { lhs: __v_35.as_canonical_u64(), rhs: __v_36.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_237] = { let __args: [G; IN_237] = [__v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(237, &__args[..])?) { [G::ZERO; OUT_237] } else { let (__hash, __hit) = record.function_queries[237].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[237].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[237].bump_multiplicity(__i); } let __ret: [G; OUT_237] = unsafe { *(record.function_queries[237].output_at(__i).as_ptr() as *const [G; OUT_237]) }; __ret }, _ => { aiur_fn_237::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __ret: [G; OUT_238] = [__v_37]; record.function_queries[238].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, } }) } +fn aiur_fn_238(inp: [G; IN_238], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_238], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; match __v_12.as_canonical_u64() { _ => { let __r_arr: [G; OUT_317] = { let __args: [G; IN_317] = [__v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(317, &__args[..])?) { [G::ZERO; OUT_317] } else { let (__hash, __hit) = record.function_queries[317].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[317].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[317].bump_multiplicity(__i); } let __ret: [G; OUT_317] = unsafe { *(record.function_queries[317].output_at(__i).as_ptr() as *const [G; OUT_317]) }; __ret }, _ => { aiur_fn_317::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __r_arr: [G; OUT_230] = { let __args: [G; IN_230] = [__v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(230, &__args[..])?) { [G::ZERO; OUT_230] } else { let (__hash, __hit) = record.function_queries[230].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[230].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[230].bump_multiplicity(__i); } let __ret: [G; OUT_230] = unsafe { *(record.function_queries[230].output_at(__i).as_ptr() as *const [G; OUT_230]) }; __ret }, _ => { aiur_fn_230::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = G::from_u64(1); if (__v_16 != __v_17) { return Err(ExecError::AssertEqMismatch { lhs: __v_16.as_canonical_u64(), rhs: __v_17.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __r_arr: [G; OUT_317] = { let __args: [G; IN_317] = [__v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(317, &__args[..])?) { [G::ZERO; OUT_317] } else { let (__hash, __hit) = record.function_queries[317].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[317].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[317].bump_multiplicity(__i); } let __ret: [G; OUT_317] = unsafe { *(record.function_queries[317].output_at(__i).as_ptr() as *const [G; OUT_317]) }; __ret }, _ => { aiur_fn_317::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; if (__v_18 != __v_19) { return Err(ExecError::AssertEqMismatch { lhs: __v_18.as_canonical_u64(), rhs: __v_19.as_canonical_u64(), msg: None }); } let __v_20: G = G::from_u64(0); let __r_arr: [G; OUT_234] = { let __args: [G; IN_234] = [__v_13, __v_20, __v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(234, &__args[..])?) { [G::ZERO; OUT_234] } else { let (__hash, __hit) = record.function_queries[234].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[234].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[234].bump_multiplicity(__i); } let __ret: [G; OUT_234] = unsafe { *(record.function_queries[234].output_at(__i).as_ptr() as *const [G; OUT_234]) }; __ret }, _ => { aiur_fn_234::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; if (__v_21 != __v_17) { return Err(ExecError::AssertEqMismatch { lhs: __v_21.as_canonical_u64(), rhs: __v_17.as_canonical_u64(), msg: None }); } match __v_0.as_canonical_u64() { _ => { let __mc_out___mc_0: [G; 1] = '__mc_0: { match __v_9.as_canonical_u64() { 0u64 => { let __v_22: G = G::from_u64(1); let __v_23: G = { let __values: [G; 3] = [__v_22, __v_22, __v_22]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; break '__mc_0 [__v_23]; }, 1u64 => { break '__mc_0 [__v_10]; }, _ => { return Err(ExecError::MatchNoCase(__v_9.as_canonical_u64())); }, } }; let __v_22: G = __mc_out___mc_0[0]; let __r_arr: [G; OUT_219] = { let __args: [G; IN_219] = [__v_7, __v_22, __v_12, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(219, &__args[..])?) { [G::ZERO; OUT_219] } else { let (__hash, __hit) = record.function_queries[219].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[219].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[219].bump_multiplicity(__i); } let __ret: [G; OUT_219] = unsafe { *(record.function_queries[219].output_at(__i).as_ptr() as *const [G; OUT_219]) }; __ret }, _ => { aiur_fn_219::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __v_24: G = __r_arr[1]; let __v_25: G = __r_arr[2]; let __v_26: G = __r_arr[3]; let __v_27: G = __r_arr[4]; let __v_28: G = G::from_u64(0); let __r_arr: [G; OUT_226] = { let __args: [G; IN_226] = [__v_28, __v_28, __v_13, __v_23, __v_24, __v_25, __v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(226, &__args[..])?) { [G::ZERO; OUT_226] } else { let (__hash, __hit) = record.function_queries[226].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[226].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[226].bump_multiplicity(__i); } let __ret: [G; OUT_226] = unsafe { *(record.function_queries[226].output_at(__i).as_ptr() as *const [G; OUT_226]) }; __ret }, _ => { aiur_fn_226::(__args, __hash, record, io_buffer)? }, } } }; let __v_29: G = __r_arr[0]; let __v_30: G = __r_arr[1]; let __r_arr: [G; OUT_236] = { let __args: [G; IN_236] = [__v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_14, __v_28, __v_27, __v_23, __v_24, __v_25, __v_26, __v_29, __v_30]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(236, &__args[..])?) { [G::ZERO; OUT_236] } else { let (__hash, __hit) = record.function_queries[236].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[236].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[236].bump_multiplicity(__i); } let __ret: [G; OUT_236] = unsafe { *(record.function_queries[236].output_at(__i).as_ptr() as *const [G; OUT_236]) }; __ret }, _ => { aiur_fn_236::(__args, __hash, record, io_buffer)? }, } } }; let __v_31: G = __r_arr[0]; let __v_32: G = __r_arr[1]; let __v_33: G = G::from_bool(__v_31 == G::ZERO); let __v_34: G = G::from_bool(__v_32 == G::ZERO); let __v_35: G = __v_33 * __v_34; let __v_36: G = G::from_u64(1); if (__v_35 != __v_36) { return Err(ExecError::AssertEqMismatch { lhs: __v_35.as_canonical_u64(), rhs: __v_36.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_237] = { let __args: [G; IN_237] = [__v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(237, &__args[..])?) { [G::ZERO; OUT_237] } else { let (__hash, __hit) = record.function_queries[237].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[237].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[237].bump_multiplicity(__i); } let __ret: [G; OUT_237] = unsafe { *(record.function_queries[237].output_at(__i).as_ptr() as *const [G; OUT_237]) }; __ret }, _ => { aiur_fn_237::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __ret: [G; OUT_238] = [__v_37]; record.function_queries[238].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, } }) } const INPUT_SIZE_239: usize = 15; const IN_239: usize = 15; const OUT_239: usize = 1; @@ -1020,7 +1020,7 @@ fn aiur_fn_251(inp: [G; IN_251], input_hash: u64, rec const INPUT_SIZE_252: usize = 1; const IN_252: usize = 1; const OUT_252: usize = 2; -fn aiur_fn_252(inp: [G; IN_252], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_252], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; match __v_1.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __v_4: G = __r_arr[1]; let __v_5: G = G::from_u64(0); let __v_6: G = G::from_u64(1); let __v_7: G = { let __values: [G; 3] = [__v_6, __v_6, __v_6]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_8: G = { let __values: [G; 3] = [__v_5, __v_3, __v_7]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_252] = [__v_8, __v_4]; record.function_queries[252].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_3: G = G::from_u64(1); let __v_4: G = { let __values: [G; 3] = [__v_3, __v_3, __v_3]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_252] = [__v_4, __v_2]; record.function_queries[252].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 2u64 => { let __r_arr: [G; OUT_252] = { let __args: [G; IN_252] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(252, &__args[..])?) { [G::ZERO; OUT_252] } else { let (__hash, __hit) = record.function_queries[252].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[252].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[252].bump_multiplicity(__i); } let __ret: [G; OUT_252] = unsafe { *(record.function_queries[252].output_at(__i).as_ptr() as *const [G; OUT_252]) }; __ret }, _ => { aiur_fn_252::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __v_4: G = __r_arr[1]; let __r_arr: [G; OUT_252] = { let __args: [G; IN_252] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(252, &__args[..])?) { [G::ZERO; OUT_252] } else { let (__hash, __hit) = record.function_queries[252].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[252].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[252].bump_multiplicity(__i); } let __ret: [G; OUT_252] = unsafe { *(record.function_queries[252].output_at(__i).as_ptr() as *const [G; OUT_252]) }; __ret }, _ => { aiur_fn_252::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __r_arr[1]; let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_3, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __ret: [G; OUT_252] = [__v_7, __v_6]; record.function_queries[252].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } +fn aiur_fn_252(inp: [G; IN_252], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_252], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; match __v_1.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __v_4: G = __r_arr[1]; let __v_5: G = G::from_u64(0); let __v_6: G = G::from_u64(1); let __v_7: G = { let __values: [G; 3] = [__v_6, __v_6, __v_6]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_8: G = { let __values: [G; 3] = [__v_5, __v_3, __v_7]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_252] = [__v_8, __v_4]; record.function_queries[252].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_3: G = G::from_u64(1); let __v_4: G = { let __values: [G; 3] = [__v_3, __v_3, __v_3]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_252] = [__v_4, __v_2]; record.function_queries[252].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 2u64 => { let __r_arr: [G; OUT_252] = { let __args: [G; IN_252] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(252, &__args[..])?) { [G::ZERO; OUT_252] } else { let (__hash, __hit) = record.function_queries[252].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[252].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[252].bump_multiplicity(__i); } let __ret: [G; OUT_252] = unsafe { *(record.function_queries[252].output_at(__i).as_ptr() as *const [G; OUT_252]) }; __ret }, _ => { aiur_fn_252::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __v_4: G = __r_arr[1]; let __r_arr: [G; OUT_252] = { let __args: [G; IN_252] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(252, &__args[..])?) { [G::ZERO; OUT_252] } else { let (__hash, __hit) = record.function_queries[252].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[252].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[252].bump_multiplicity(__i); } let __ret: [G; OUT_252] = unsafe { *(record.function_queries[252].output_at(__i).as_ptr() as *const [G; OUT_252]) }; __ret }, _ => { aiur_fn_252::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __r_arr[1]; let __r_arr: [G; OUT_316] = { let __args: [G; IN_316] = [__v_3, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(316, &__args[..])?) { [G::ZERO; OUT_316] } else { let (__hash, __hit) = record.function_queries[316].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[316].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[316].bump_multiplicity(__i); } let __ret: [G; OUT_316] = unsafe { *(record.function_queries[316].output_at(__i).as_ptr() as *const [G; OUT_316]) }; __ret }, _ => { aiur_fn_316::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __ret: [G; OUT_252] = [__v_7, __v_6]; record.function_queries[252].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } const INPUT_SIZE_253: usize = 1; const IN_253: usize = 1; const OUT_253: usize = 1; @@ -1040,7 +1040,7 @@ fn aiur_fn_256(inp: [G; IN_256], input_hash: u64, rec const INPUT_SIZE_257: usize = 1; const IN_257: usize = 1; const OUT_257: usize = 1; -fn aiur_fn_257(inp: [G; IN_257], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_257], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 32] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 32 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 32] = __args[..32].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; let __v_6: G = __loaded[5]; let __v_7: G = __loaded[6]; let __v_8: G = __loaded[7]; let __v_9: G = __loaded[8]; let __v_10: G = __loaded[9]; let __v_11: G = __loaded[10]; let __v_12: G = __loaded[11]; let __v_13: G = __loaded[12]; let __v_14: G = __loaded[13]; let __v_15: G = __loaded[14]; let __v_16: G = __loaded[15]; let __v_17: G = __loaded[16]; let __v_18: G = __loaded[17]; let __v_19: G = __loaded[18]; let __v_20: G = __loaded[19]; let __v_21: G = __loaded[20]; let __v_22: G = __loaded[21]; let __v_23: G = __loaded[22]; let __v_24: G = __loaded[23]; let __v_25: G = __loaded[24]; let __v_26: G = __loaded[25]; let __v_27: G = __loaded[26]; let __v_28: G = __loaded[27]; let __v_29: G = __loaded[28]; let __v_30: G = __loaded[29]; let __v_31: G = __loaded[30]; let __v_32: G = __loaded[31]; let __v_33: G = G::from_u64(5); let __io_pair: (G, G) = { let __key: [G; 32] = [__v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32]; let __info = io_buffer.get_info(__v_33, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_34: G = __io_pair.0; let __v_35: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_33, __v_34, __v_35]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = __r_arr[1]; let __v_39: G = G::from_u64(226); if (__v_37 != __v_39) { return Err(ExecError::AssertEqMismatch { lhs: __v_37.as_canonical_u64(), rhs: __v_39.as_canonical_u64(), msg: Some("aggr: tree has the wrong Ixon tag".to_string()) }); } let __r_arr: [G; OUT_252] = { let __args: [G; IN_252] = [__v_38]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(252, &__args[..])?) { [G::ZERO; OUT_252] } else { let (__hash, __hit) = record.function_queries[252].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[252].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[252].bump_multiplicity(__i); } let __ret: [G; OUT_252] = unsafe { *(record.function_queries[252].output_at(__i).as_ptr() as *const [G; OUT_252]) }; __ret }, _ => { aiur_fn_252::(__args, __hash, record, io_buffer)? }, } } }; let __v_40: G = __r_arr[0]; let __v_41: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_41.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_42: G = __loaded[0]; let __v_43: G = __loaded[1]; let __v_44: G = __loaded[2]; let __v_45: G = G::from_u64(1); if (__v_42 != __v_45) { return Err(ExecError::AssertEqMismatch { lhs: __v_42.as_canonical_u64(), rhs: __v_45.as_canonical_u64(), msg: Some("aggr: trailing bytes after AssumptionTree".to_string()) }); } if (__v_43 != __v_45) { return Err(ExecError::AssertEqMismatch { lhs: __v_43.as_canonical_u64(), rhs: __v_45.as_canonical_u64(), msg: Some("aggr: trailing bytes after AssumptionTree".to_string()) }); } if (__v_44 != __v_45) { return Err(ExecError::AssertEqMismatch { lhs: __v_44.as_canonical_u64(), rhs: __v_45.as_canonical_u64(), msg: Some("aggr: trailing bytes after AssumptionTree".to_string()) }); } let __r_arr: [G; OUT_320] = { let __args: [G; IN_320] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(320, &__args[..])?) { [G::ZERO; OUT_320] } else { let (__hash, __hit) = record.function_queries[320].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[320].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[320].bump_multiplicity(__i); } let __ret: [G; OUT_320] = unsafe { *(record.function_queries[320].output_at(__i).as_ptr() as *const [G; OUT_320]) }; __ret }, _ => { aiur_fn_320::(__args, __hash, record, io_buffer)? }, } } }; let __v_46: G = __r_arr[0]; let __v_47: G = G::from_u64(0); if (__v_46 != __v_47) { return Err(ExecError::AssertEqMismatch { lhs: __v_46.as_canonical_u64(), rhs: __v_47.as_canonical_u64(), msg: Some("aggr: a present tree must contain at least one leaf".to_string()) }); } let __r_arr: [G; OUT_251] = { let __args: [G; IN_251] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(251, &__args[..])?) { [G::ZERO; OUT_251] } else { let (__hash, __hit) = record.function_queries[251].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[251].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[251].bump_multiplicity(__i); } let __ret: [G; OUT_251] = unsafe { *(record.function_queries[251].output_at(__i).as_ptr() as *const [G; OUT_251]) }; __ret }, _ => { aiur_fn_251::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_256] = { let __args: [G; IN_256] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(256, &__args[..])?) { [G::ZERO; OUT_256] } else { let (__hash, __hit) = record.function_queries[256].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[256].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[256].bump_multiplicity(__i); } let __ret: [G; OUT_256] = unsafe { *(record.function_queries[256].output_at(__i).as_ptr() as *const [G; OUT_256]) }; __ret }, _ => { aiur_fn_256::(__args, __hash, record, io_buffer)? }, } } }; let __v_48: G = __r_arr[0]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_48, __v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; if (__v_49 != __v_45) { return Err(ExecError::AssertEqMismatch { lhs: __v_49.as_canonical_u64(), rhs: __v_45.as_canonical_u64(), msg: Some("aggr: tree leaves do not reproduce the canonical root".to_string()) }); } let __ret: [G; OUT_257] = [__v_40]; record.function_queries[257].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +fn aiur_fn_257(inp: [G; IN_257], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_257], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 32] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 32 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 32] = __args[..32].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; let __v_6: G = __loaded[5]; let __v_7: G = __loaded[6]; let __v_8: G = __loaded[7]; let __v_9: G = __loaded[8]; let __v_10: G = __loaded[9]; let __v_11: G = __loaded[10]; let __v_12: G = __loaded[11]; let __v_13: G = __loaded[12]; let __v_14: G = __loaded[13]; let __v_15: G = __loaded[14]; let __v_16: G = __loaded[15]; let __v_17: G = __loaded[16]; let __v_18: G = __loaded[17]; let __v_19: G = __loaded[18]; let __v_20: G = __loaded[19]; let __v_21: G = __loaded[20]; let __v_22: G = __loaded[21]; let __v_23: G = __loaded[22]; let __v_24: G = __loaded[23]; let __v_25: G = __loaded[24]; let __v_26: G = __loaded[25]; let __v_27: G = __loaded[26]; let __v_28: G = __loaded[27]; let __v_29: G = __loaded[28]; let __v_30: G = __loaded[29]; let __v_31: G = __loaded[30]; let __v_32: G = __loaded[31]; let __v_33: G = G::from_u64(5); let __io_pair: (G, G) = { let __key: [G; 32] = [__v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32]; let __info = io_buffer.get_info(__v_33, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_34: G = __io_pair.0; let __v_35: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_33, __v_34, __v_35]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = __r_arr[1]; let __v_39: G = G::from_u64(226); if (__v_37 != __v_39) { return Err(ExecError::AssertEqMismatch { lhs: __v_37.as_canonical_u64(), rhs: __v_39.as_canonical_u64(), msg: Some("aggr: tree has the wrong Ixon tag".to_string()) }); } let __r_arr: [G; OUT_252] = { let __args: [G; IN_252] = [__v_38]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(252, &__args[..])?) { [G::ZERO; OUT_252] } else { let (__hash, __hit) = record.function_queries[252].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[252].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[252].bump_multiplicity(__i); } let __ret: [G; OUT_252] = unsafe { *(record.function_queries[252].output_at(__i).as_ptr() as *const [G; OUT_252]) }; __ret }, _ => { aiur_fn_252::(__args, __hash, record, io_buffer)? }, } } }; let __v_40: G = __r_arr[0]; let __v_41: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_41.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_42: G = __loaded[0]; let __v_43: G = __loaded[1]; let __v_44: G = __loaded[2]; let __v_45: G = G::from_u64(1); if (__v_42 != __v_45) { return Err(ExecError::AssertEqMismatch { lhs: __v_42.as_canonical_u64(), rhs: __v_45.as_canonical_u64(), msg: Some("aggr: trailing bytes after AssumptionTree".to_string()) }); } if (__v_43 != __v_45) { return Err(ExecError::AssertEqMismatch { lhs: __v_43.as_canonical_u64(), rhs: __v_45.as_canonical_u64(), msg: Some("aggr: trailing bytes after AssumptionTree".to_string()) }); } if (__v_44 != __v_45) { return Err(ExecError::AssertEqMismatch { lhs: __v_44.as_canonical_u64(), rhs: __v_45.as_canonical_u64(), msg: Some("aggr: trailing bytes after AssumptionTree".to_string()) }); } let __r_arr: [G; OUT_322] = { let __args: [G; IN_322] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(322, &__args[..])?) { [G::ZERO; OUT_322] } else { let (__hash, __hit) = record.function_queries[322].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[322].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[322].bump_multiplicity(__i); } let __ret: [G; OUT_322] = unsafe { *(record.function_queries[322].output_at(__i).as_ptr() as *const [G; OUT_322]) }; __ret }, _ => { aiur_fn_322::(__args, __hash, record, io_buffer)? }, } } }; let __v_46: G = __r_arr[0]; let __v_47: G = G::from_u64(0); if (__v_46 != __v_47) { return Err(ExecError::AssertEqMismatch { lhs: __v_46.as_canonical_u64(), rhs: __v_47.as_canonical_u64(), msg: Some("aggr: a present tree must contain at least one leaf".to_string()) }); } let __r_arr: [G; OUT_251] = { let __args: [G; IN_251] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(251, &__args[..])?) { [G::ZERO; OUT_251] } else { let (__hash, __hit) = record.function_queries[251].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[251].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[251].bump_multiplicity(__i); } let __ret: [G; OUT_251] = unsafe { *(record.function_queries[251].output_at(__i).as_ptr() as *const [G; OUT_251]) }; __ret }, _ => { aiur_fn_251::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_256] = { let __args: [G; IN_256] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(256, &__args[..])?) { [G::ZERO; OUT_256] } else { let (__hash, __hit) = record.function_queries[256].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[256].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[256].bump_multiplicity(__i); } let __ret: [G; OUT_256] = unsafe { *(record.function_queries[256].output_at(__i).as_ptr() as *const [G; OUT_256]) }; __ret }, _ => { aiur_fn_256::(__args, __hash, record, io_buffer)? }, } } }; let __v_48: G = __r_arr[0]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_48, __v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; if (__v_49 != __v_45) { return Err(ExecError::AssertEqMismatch { lhs: __v_49.as_canonical_u64(), rhs: __v_45.as_canonical_u64(), msg: Some("aggr: tree leaves do not reproduce the canonical root".to_string()) }); } let __ret: [G; OUT_257] = [__v_40]; record.function_queries[257].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } const INPUT_SIZE_258: usize = 2; const IN_258: usize = 2; const OUT_258: usize = 1; @@ -1053,276 +1053,284 @@ const INPUT_SIZE_260: usize = 3; const IN_260: usize = 3; const OUT_260: usize = 0; fn aiur_fn_260(inp: [G; IN_260], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_260], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_3: G = __loaded[0]; let __v_4: G = __loaded[1]; let __v_5: G = __loaded[2]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_1.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_6: G = __loaded[0]; let __v_7: G = __loaded[1]; let __v_8: G = __loaded[2]; match __v_3.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_259] = { let __args: [G; IN_259] = [__v_1, __v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(259, &__args[..])?) { [G::ZERO; OUT_259] } else { let (__hash, __hit) = record.function_queries[259].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[259].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[259].bump_multiplicity(__i); } let __ret: [G; OUT_259] = unsafe { *(record.function_queries[259].output_at(__i).as_ptr() as *const [G; OUT_259]) }; __ret }, _ => { aiur_fn_259::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_260] = []; record.function_queries[260].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_6.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_259] = { let __args: [G; IN_259] = [__v_0, __v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(259, &__args[..])?) { [G::ZERO; OUT_259] } else { let (__hash, __hit) = record.function_queries[259].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[259].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[259].bump_multiplicity(__i); } let __ret: [G; OUT_259] = unsafe { *(record.function_queries[259].output_at(__i).as_ptr() as *const [G; OUT_259]) }; __ret }, _ => { aiur_fn_259::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_260] = []; record.function_queries[260].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_2.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_9: G = __loaded[0]; let __v_10: G = __loaded[1]; let __v_11: G = __loaded[2]; match __v_9.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_250] = { let __args: [G; IN_250] = [__v_4, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(250, &__args[..])?) { [G::ZERO; OUT_250] } else { let (__hash, __hit) = record.function_queries[250].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[250].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[250].bump_multiplicity(__i); } let __ret: [G; OUT_250] = unsafe { *(record.function_queries[250].output_at(__i).as_ptr() as *const [G; OUT_250]) }; __ret }, _ => { aiur_fn_250::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; match __v_12.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_4, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = G::from_u64(1); if (__v_13 != __v_14) { return Err(ExecError::AssertEqMismatch { lhs: __v_13.as_canonical_u64(), rhs: __v_14.as_canonical_u64(), msg: Some("aggr: subject union mismatch".to_string()) }); } let __r_arr: [G; OUT_260] = { let __args: [G; IN_260] = [__v_5, __v_1, __v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(260, &__args[..])?) { [G::ZERO; OUT_260] } else { let (__hash, __hit) = record.function_queries[260].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[260].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[260].bump_multiplicity(__i); } let __ret: [G; OUT_260] = unsafe { *(record.function_queries[260].output_at(__i).as_ptr() as *const [G; OUT_260]) }; __ret }, _ => { aiur_fn_260::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_260] = []; record.function_queries[260].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_4, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = G::from_u64(1); if (__v_13 != __v_14) { return Err(ExecError::AssertEqMismatch { lhs: __v_13.as_canonical_u64(), rhs: __v_14.as_canonical_u64(), msg: Some("aggr: subject union mismatch".to_string()) }); } let __r_arr: [G; OUT_260] = { let __args: [G; IN_260] = [__v_5, __v_8, __v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(260, &__args[..])?) { [G::ZERO; OUT_260] } else { let (__hash, __hit) = record.function_queries[260].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[260].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[260].bump_multiplicity(__i); } let __ret: [G; OUT_260] = unsafe { *(record.function_queries[260].output_at(__i).as_ptr() as *const [G; OUT_260]) }; __ret }, _ => { aiur_fn_260::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_260] = []; record.function_queries[260].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_7, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = G::from_u64(1); if (__v_13 != __v_14) { return Err(ExecError::AssertEqMismatch { lhs: __v_13.as_canonical_u64(), rhs: __v_14.as_canonical_u64(), msg: Some("aggr: subject union mismatch".to_string()) }); } let __r_arr: [G; OUT_260] = { let __args: [G; IN_260] = [__v_0, __v_8, __v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(260, &__args[..])?) { [G::ZERO; OUT_260] } else { let (__hash, __hit) = record.function_queries[260].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[260].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[260].bump_multiplicity(__i); } let __ret: [G; OUT_260] = unsafe { *(record.function_queries[260].output_at(__i).as_ptr() as *const [G; OUT_260]) }; __ret }, _ => { aiur_fn_260::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_260] = []; record.function_queries[260].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_9.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_6.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_3.as_canonical_u64())); }, } }) } -const INPUT_SIZE_261: usize = 2; -const IN_261: usize = 2; -const OUT_261: usize = 4; -fn aiur_fn_261(inp: [G; IN_261], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_261], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_1.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_5: G = __loaded[0]; let __v_6: G = __loaded[1]; let __v_7: G = __loaded[2]; match __v_2.as_canonical_u64() { 1u64 => { match __v_5.as_canonical_u64() { 1u64 => { let __v_8: G = G::from_u64(0); let __ret: [G; OUT_261] = [__v_8, __v_8, __v_8, __v_8]; record.function_queries[261].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_8: G = G::from_u64(1); let __ret: [G; OUT_261] = [__v_8, __v_6, __v_0, __v_7]; record.function_queries[261].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, 0u64 => { match __v_5.as_canonical_u64() { 1u64 => { let __v_8: G = G::from_u64(1); let __ret: [G; OUT_261] = [__v_8, __v_3, __v_4, __v_1]; record.function_queries[261].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_250] = { let __args: [G; IN_250] = [__v_3, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(250, &__args[..])?) { [G::ZERO; OUT_250] } else { let (__hash, __hit) = record.function_queries[250].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[250].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[250].bump_multiplicity(__i); } let __ret: [G; OUT_250] = unsafe { *(record.function_queries[250].output_at(__i).as_ptr() as *const [G; OUT_250]) }; __ret }, _ => { aiur_fn_250::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; match __v_8.as_canonical_u64() { 0u64 => { let __v_9: G = G::from_u64(1); let __ret: [G; OUT_261] = [__v_9, __v_3, __v_4, __v_1]; record.function_queries[261].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_9: G = G::from_u64(1); let __ret: [G; OUT_261] = [__v_9, __v_3, __v_4, __v_7]; record.function_queries[261].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_9: G = G::from_u64(1); let __ret: [G; OUT_261] = [__v_9, __v_6, __v_0, __v_7]; record.function_queries[261].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } +const INPUT_SIZE_261: usize = 3; +const IN_261: usize = 3; +const OUT_261: usize = 0; +fn aiur_fn_261(inp: [G; IN_261], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_261], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_3: G = __loaded[0]; let __v_4: G = __loaded[1]; let __v_5: G = __loaded[2]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_1.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_6: G = __loaded[0]; let __v_7: G = __loaded[1]; let __v_8: G = __loaded[2]; match __v_3.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_259] = { let __args: [G; IN_259] = [__v_1, __v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(259, &__args[..])?) { [G::ZERO; OUT_259] } else { let (__hash, __hit) = record.function_queries[259].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[259].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[259].bump_multiplicity(__i); } let __ret: [G; OUT_259] = unsafe { *(record.function_queries[259].output_at(__i).as_ptr() as *const [G; OUT_259]) }; __ret }, _ => { aiur_fn_259::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_261] = []; record.function_queries[261].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_6.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_259] = { let __args: [G; IN_259] = [__v_0, __v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(259, &__args[..])?) { [G::ZERO; OUT_259] } else { let (__hash, __hit) = record.function_queries[259].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[259].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[259].bump_multiplicity(__i); } let __ret: [G; OUT_259] = unsafe { *(record.function_queries[259].output_at(__i).as_ptr() as *const [G; OUT_259]) }; __ret }, _ => { aiur_fn_259::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_261] = []; record.function_queries[261].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_2.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_9: G = __loaded[0]; let __v_10: G = __loaded[1]; let __v_11: G = __loaded[2]; match __v_9.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_250] = { let __args: [G; IN_250] = [__v_4, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(250, &__args[..])?) { [G::ZERO; OUT_250] } else { let (__hash, __hit) = record.function_queries[250].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[250].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[250].bump_multiplicity(__i); } let __ret: [G; OUT_250] = unsafe { *(record.function_queries[250].output_at(__i).as_ptr() as *const [G; OUT_250]) }; __ret }, _ => { aiur_fn_250::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; match __v_12.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_4, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = G::from_u64(1); if (__v_13 != __v_14) { return Err(ExecError::AssertEqMismatch { lhs: __v_13.as_canonical_u64(), rhs: __v_14.as_canonical_u64(), msg: Some("aggr: subject union mismatch".to_string()) }); } let __r_arr: [G; OUT_261] = { let __args: [G; IN_261] = [__v_5, __v_1, __v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(261, &__args[..])?) { [G::ZERO; OUT_261] } else { let (__hash, __hit) = record.function_queries[261].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[261].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[261].bump_multiplicity(__i); } let __ret: [G; OUT_261] = unsafe { *(record.function_queries[261].output_at(__i).as_ptr() as *const [G; OUT_261]) }; __ret }, _ => { aiur_fn_261::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_261] = []; record.function_queries[261].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_13: G = G::from_u64(0); let __v_14: G = G::from_u64(1); if (__v_13 != __v_14) { return Err(ExecError::AssertEqMismatch { lhs: __v_13.as_canonical_u64(), rhs: __v_14.as_canonical_u64(), msg: Some("aggr: child subject sets overlap".to_string()) }); } let __ret: [G; OUT_261] = []; record.function_queries[261].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_7, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = G::from_u64(1); if (__v_13 != __v_14) { return Err(ExecError::AssertEqMismatch { lhs: __v_13.as_canonical_u64(), rhs: __v_14.as_canonical_u64(), msg: Some("aggr: subject union mismatch".to_string()) }); } let __r_arr: [G; OUT_261] = { let __args: [G; IN_261] = [__v_0, __v_8, __v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(261, &__args[..])?) { [G::ZERO; OUT_261] } else { let (__hash, __hit) = record.function_queries[261].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[261].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[261].bump_multiplicity(__i); } let __ret: [G; OUT_261] = unsafe { *(record.function_queries[261].output_at(__i).as_ptr() as *const [G; OUT_261]) }; __ret }, _ => { aiur_fn_261::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_261] = []; record.function_queries[261].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_9.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_6.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_3.as_canonical_u64())); }, } }) } const INPUT_SIZE_262: usize = 2; const IN_262: usize = 2; -const OUT_262: usize = 2; -fn aiur_fn_262(inp: [G; IN_262], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_262], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_1.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; match __v_2.as_canonical_u64() { 1u64 => { let __v_5: G = G::from_u64(0); let __ret: [G; OUT_262] = [__v_5, __v_1]; record.function_queries[262].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_250] = { let __args: [G; IN_250] = [__v_3, __v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(250, &__args[..])?) { [G::ZERO; OUT_250] } else { let (__hash, __hit) = record.function_queries[250].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[250].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[250].bump_multiplicity(__i); } let __ret: [G; OUT_250] = unsafe { *(record.function_queries[250].output_at(__i).as_ptr() as *const [G; OUT_250]) }; __ret }, _ => { aiur_fn_250::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; match __v_5.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_262] = { let __args: [G; IN_262] = [__v_0, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(262, &__args[..])?) { [G::ZERO; OUT_262] } else { let (__hash, __hit) = record.function_queries[262].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[262].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[262].bump_multiplicity(__i); } let __ret: [G; OUT_262] = unsafe { *(record.function_queries[262].output_at(__i).as_ptr() as *const [G; OUT_262]) }; __ret }, _ => { aiur_fn_262::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = __r_arr[1]; let __ret: [G; OUT_262] = [__v_6, __v_7]; record.function_queries[262].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_6: G = G::from_u64(1); let __ret: [G; OUT_262] = [__v_6, __v_4]; record.function_queries[262].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_6: G = G::from_u64(0); let __ret: [G; OUT_262] = [__v_6, __v_1]; record.function_queries[262].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } -const INPUT_SIZE_263: usize = 4; -const IN_263: usize = 4; -const OUT_263: usize = 0; -fn aiur_fn_263(inp: [G; IN_263], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_263], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __r_arr: [G; OUT_261] = { let __args: [G; IN_261] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(261, &__args[..])?) { [G::ZERO; OUT_261] } else { let (__hash, __hit) = record.function_queries[261].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[261].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[261].bump_multiplicity(__i); } let __ret: [G; OUT_261] = unsafe { *(record.function_queries[261].output_at(__i).as_ptr() as *const [G; OUT_261]) }; __ret }, _ => { aiur_fn_261::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = __r_arr[1]; let __v_6: G = __r_arr[2]; let __v_7: G = __r_arr[3]; match __v_4.as_canonical_u64() { 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_3.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_8: G = __loaded[0]; let __v_9: G = __loaded[1]; let __v_10: G = __loaded[2]; let __v_11: G = G::from_u64(1); if (__v_8 != __v_11) { return Err(ExecError::AssertEqMismatch { lhs: __v_8.as_canonical_u64(), rhs: __v_11.as_canonical_u64(), msg: Some("aggr: output has an extra assumption".to_string()) }); } if (__v_9 != __v_11) { return Err(ExecError::AssertEqMismatch { lhs: __v_9.as_canonical_u64(), rhs: __v_11.as_canonical_u64(), msg: Some("aggr: output has an extra assumption".to_string()) }); } if (__v_10 != __v_11) { return Err(ExecError::AssertEqMismatch { lhs: __v_10.as_canonical_u64(), rhs: __v_11.as_canonical_u64(), msg: Some("aggr: output has an extra assumption".to_string()) }); } let __ret: [G; OUT_263] = []; record.function_queries[263].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __r_arr: [G; OUT_262] = { let __args: [G; IN_262] = [__v_5, __v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(262, &__args[..])?) { [G::ZERO; OUT_262] } else { let (__hash, __hit) = record.function_queries[262].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[262].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[262].bump_multiplicity(__i); } let __ret: [G; OUT_262] = unsafe { *(record.function_queries[262].output_at(__i).as_ptr() as *const [G; OUT_262]) }; __ret }, _ => { aiur_fn_262::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __v_9: G = __r_arr[1]; match __v_8.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_263] = { let __args: [G; IN_263] = [__v_6, __v_7, __v_9, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(263, &__args[..])?) { [G::ZERO; OUT_263] } else { let (__hash, __hit) = record.function_queries[263].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[263].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[263].bump_multiplicity(__i); } let __ret: [G; OUT_263] = unsafe { *(record.function_queries[263].output_at(__i).as_ptr() as *const [G; OUT_263]) }; __ret }, _ => { aiur_fn_263::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_263] = []; record.function_queries[263].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_3.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_10: G = __loaded[0]; let __v_11: G = __loaded[1]; let __v_12: G = __loaded[2]; match __v_10.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_5, __v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = G::from_u64(1); if (__v_13 != __v_14) { return Err(ExecError::AssertEqMismatch { lhs: __v_13.as_canonical_u64(), rhs: __v_14.as_canonical_u64(), msg: Some("aggr: outstanding assumption mismatch".to_string()) }); } let __r_arr: [G; OUT_263] = { let __args: [G; IN_263] = [__v_6, __v_7, __v_9, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(263, &__args[..])?) { [G::ZERO; OUT_263] } else { let (__hash, __hit) = record.function_queries[263].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[263].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[263].bump_multiplicity(__i); } let __ret: [G; OUT_263] = unsafe { *(record.function_queries[263].output_at(__i).as_ptr() as *const [G; OUT_263]) }; __ret }, _ => { aiur_fn_263::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_263] = []; record.function_queries[263].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_10.as_canonical_u64())); }, } }, } }, _ => { return Err(ExecError::MatchNoCase(__v_4.as_canonical_u64())); }, } }) } -const INPUT_SIZE_264: usize = 3; -const IN_264: usize = 3; +const OUT_262: usize = 0; +fn aiur_fn_262(inp: [G; IN_262], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_262], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_1.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_5: G = __loaded[0]; let __v_6: G = __loaded[1]; let __v_7: G = __loaded[2]; match __v_2.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_262] = []; record.function_queries[262].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { match __v_5.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_262] = []; record.function_queries[262].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_250] = { let __args: [G; IN_250] = [__v_3, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(250, &__args[..])?) { [G::ZERO; OUT_250] } else { let (__hash, __hit) = record.function_queries[250].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[250].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[250].bump_multiplicity(__i); } let __ret: [G; OUT_250] = unsafe { *(record.function_queries[250].output_at(__i).as_ptr() as *const [G; OUT_250]) }; __ret }, _ => { aiur_fn_250::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; match __v_8.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_262] = { let __args: [G; IN_262] = [__v_4, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(262, &__args[..])?) { [G::ZERO; OUT_262] } else { let (__hash, __hit) = record.function_queries[262].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[262].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[262].bump_multiplicity(__i); } let __ret: [G; OUT_262] = unsafe { *(record.function_queries[262].output_at(__i).as_ptr() as *const [G; OUT_262]) }; __ret }, _ => { aiur_fn_262::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_262] = []; record.function_queries[262].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_9: G = G::from_u64(0); let __v_10: G = G::from_u64(1); if (__v_9 != __v_10) { return Err(ExecError::AssertEqMismatch { lhs: __v_9.as_canonical_u64(), rhs: __v_10.as_canonical_u64(), msg: Some("aggr: subject set overlaps assumption set".to_string()) }); } let __ret: [G; OUT_262] = []; record.function_queries[262].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_262] = { let __args: [G; IN_262] = [__v_0, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(262, &__args[..])?) { [G::ZERO; OUT_262] } else { let (__hash, __hit) = record.function_queries[262].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[262].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[262].bump_multiplicity(__i); } let __ret: [G; OUT_262] = unsafe { *(record.function_queries[262].output_at(__i).as_ptr() as *const [G; OUT_262]) }; __ret }, _ => { aiur_fn_262::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_262] = []; record.function_queries[262].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } +const INPUT_SIZE_263: usize = 2; +const IN_263: usize = 2; +const OUT_263: usize = 5; +fn aiur_fn_263(inp: [G; IN_263], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_263], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_1.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_5: G = __loaded[0]; let __v_6: G = __loaded[1]; let __v_7: G = __loaded[2]; match __v_2.as_canonical_u64() { 1u64 => { match __v_5.as_canonical_u64() { 1u64 => { let __v_8: G = G::from_u64(0); let __ret: [G; OUT_263] = [__v_8, __v_8, __v_8, __v_8, __v_8]; record.function_queries[263].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_8: G = G::from_u64(1); let __v_9: G = G::from_u64(2); let __ret: [G; OUT_263] = [__v_8, __v_6, __v_9, __v_0, __v_7]; record.function_queries[263].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, 0u64 => { match __v_5.as_canonical_u64() { 1u64 => { let __v_8: G = G::from_u64(1); let __v_9: G = G::from_u64(0); let __ret: [G; OUT_263] = [__v_8, __v_3, __v_9, __v_4, __v_1]; record.function_queries[263].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_250] = { let __args: [G; IN_250] = [__v_3, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(250, &__args[..])?) { [G::ZERO; OUT_250] } else { let (__hash, __hit) = record.function_queries[250].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[250].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[250].bump_multiplicity(__i); } let __ret: [G; OUT_250] = unsafe { *(record.function_queries[250].output_at(__i).as_ptr() as *const [G; OUT_250]) }; __ret }, _ => { aiur_fn_250::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; match __v_8.as_canonical_u64() { 0u64 => { let __v_9: G = G::from_u64(1); let __v_10: G = G::from_u64(0); let __ret: [G; OUT_263] = [__v_9, __v_3, __v_10, __v_4, __v_1]; record.function_queries[263].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_9: G = G::from_u64(1); let __ret: [G; OUT_263] = [__v_9, __v_3, __v_9, __v_4, __v_7]; record.function_queries[263].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_9: G = G::from_u64(1); let __v_10: G = G::from_u64(2); let __ret: [G; OUT_263] = [__v_9, __v_6, __v_10, __v_0, __v_7]; record.function_queries[263].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } +const INPUT_SIZE_264: usize = 2; +const IN_264: usize = 2; const OUT_264: usize = 2; -fn aiur_fn_264(inp: [G; IN_264], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_264], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; match __v_1.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_264] = [__v_0, __v_2]; record.function_queries[264].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __v_4: G = __r_arr[1]; let __v_5: G = G::from_u64(2); let __v_6: G = if unconstrained { aiur::execute::CodegenBytes2::less_than(&__v_3, &__v_5) } else { aiur::execute::bytes2_less_than_value(__v_3, __v_5, record) }; let __v_7: G = G::from_u64(1); if (__v_6 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_6.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("aggr structural: path side must be 0 or 1".to_string()) }); } let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __v_9: G = __r_arr[1]; let __mc_out___mc_0: [G; 1] = '__mc_0: { match __v_3.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_247] = { let __args: [G; IN_247] = [__v_8, __v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(247, &__args[..])?) { [G::ZERO; OUT_247] } else { let (__hash, __hit) = record.function_queries[247].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[247].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[247].bump_multiplicity(__i); } let __ret: [G; OUT_247] = unsafe { *(record.function_queries[247].output_at(__i).as_ptr() as *const [G; OUT_247]) }; __ret }, _ => { aiur_fn_247::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; break '__mc_0 [__v_10]; }, _ => { let __r_arr: [G; OUT_247] = { let __args: [G; IN_247] = [__v_0, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(247, &__args[..])?) { [G::ZERO; OUT_247] } else { let (__hash, __hit) = record.function_queries[247].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[247].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[247].bump_multiplicity(__i); } let __ret: [G; OUT_247] = unsafe { *(record.function_queries[247].output_at(__i).as_ptr() as *const [G; OUT_247]) }; __ret }, _ => { aiur_fn_247::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; break '__mc_0 [__v_10]; }, } }; let __v_10: G = __mc_out___mc_0[0]; let __v_11: G = G::from_u64(1); let __v_12: G = __v_1 - __v_11; let __r_arr: [G; OUT_264] = { let __args: [G; IN_264] = [__v_10, __v_12, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(264, &__args[..])?) { [G::ZERO; OUT_264] } else { let (__hash, __hit) = record.function_queries[264].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[264].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[264].bump_multiplicity(__i); } let __ret: [G; OUT_264] = unsafe { *(record.function_queries[264].output_at(__i).as_ptr() as *const [G; OUT_264]) }; __ret }, _ => { aiur_fn_264::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __ret: [G; OUT_264] = [__v_13, __v_14]; record.function_queries[264].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } -const INPUT_SIZE_265: usize = 2; -const IN_265: usize = 2; -const OUT_265: usize = 1; -fn aiur_fn_265(inp: [G; IN_265], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_265], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = G::from_u64(6); let __loaded: [G; 32] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 32 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 32] = __args[..32].try_into().unwrap(); __arr }; let __v_3: G = __loaded[0]; let __v_4: G = __loaded[1]; let __v_5: G = __loaded[2]; let __v_6: G = __loaded[3]; let __v_7: G = __loaded[4]; let __v_8: G = __loaded[5]; let __v_9: G = __loaded[6]; let __v_10: G = __loaded[7]; let __v_11: G = __loaded[8]; let __v_12: G = __loaded[9]; let __v_13: G = __loaded[10]; let __v_14: G = __loaded[11]; let __v_15: G = __loaded[12]; let __v_16: G = __loaded[13]; let __v_17: G = __loaded[14]; let __v_18: G = __loaded[15]; let __v_19: G = __loaded[16]; let __v_20: G = __loaded[17]; let __v_21: G = __loaded[18]; let __v_22: G = __loaded[19]; let __v_23: G = __loaded[20]; let __v_24: G = __loaded[21]; let __v_25: G = __loaded[22]; let __v_26: G = __loaded[23]; let __v_27: G = __loaded[24]; let __v_28: G = __loaded[25]; let __v_29: G = __loaded[26]; let __v_30: G = __loaded[27]; let __v_31: G = __loaded[28]; let __v_32: G = __loaded[29]; let __v_33: G = __loaded[30]; let __v_34: G = __loaded[31]; let __io_pair: (G, G) = { let __key: [G; 32] = [__v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34]; let __info = io_buffer.get_info(__v_2, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_35: G = __io_pair.0; let __v_36: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_2, __v_35, __v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_37]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_38: G = __r_arr[0]; let __v_39: G = __r_arr[1]; let __v_40: G = G::from_u64(2); let __v_41: G = if unconstrained { aiur::execute::CodegenBytes2::less_than(&__v_38, &__v_40) } else { aiur::execute::bytes2_less_than_value(__v_38, __v_40, record) }; let __v_42: G = G::from_u64(1); if (__v_41 != __v_42) { return Err(ExecError::AssertEqMismatch { lhs: __v_41.as_canonical_u64(), rhs: __v_42.as_canonical_u64(), msg: Some("aggr structural: discharge choice must be 0 or 1".to_string()) }); } match __v_38.as_canonical_u64() { 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_39.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_43: G = __loaded[0]; let __v_44: G = __loaded[1]; let __v_45: G = __loaded[2]; let __v_46: G = G::from_u64(1); if (__v_43 != __v_46) { return Err(ExecError::AssertEqMismatch { lhs: __v_43.as_canonical_u64(), rhs: __v_46.as_canonical_u64(), msg: Some("aggr structural: carried choice has trailing bytes".to_string()) }); } if (__v_44 != __v_46) { return Err(ExecError::AssertEqMismatch { lhs: __v_44.as_canonical_u64(), rhs: __v_46.as_canonical_u64(), msg: Some("aggr structural: carried choice has trailing bytes".to_string()) }); } if (__v_45 != __v_46) { return Err(ExecError::AssertEqMismatch { lhs: __v_45.as_canonical_u64(), rhs: __v_46.as_canonical_u64(), msg: Some("aggr structural: carried choice has trailing bytes".to_string()) }); } let __v_47: G = G::from_u64(0); let __ret: [G; OUT_265] = [__v_47]; record.function_queries[265].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_39]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_43: G = __r_arr[0]; let __v_44: G = __r_arr[1]; let __v_45: G = G::from_u64(65); let __v_46: G = if unconstrained { aiur::execute::CodegenBytes2::less_than(&__v_43, &__v_45) } else { aiur::execute::bytes2_less_than_value(__v_43, __v_45, record) }; let __v_47: G = G::from_u64(1); if (__v_46 != __v_47) { return Err(ExecError::AssertEqMismatch { lhs: __v_46.as_canonical_u64(), rhs: __v_47.as_canonical_u64(), msg: Some("aggr structural: Merkle path exceeds 64 steps".to_string()) }); } let __r_arr: [G; OUT_248] = { let __args: [G; IN_248] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(248, &__args[..])?) { [G::ZERO; OUT_248] } else { let (__hash, __hit) = record.function_queries[248].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[248].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[248].bump_multiplicity(__i); } let __ret: [G; OUT_248] = unsafe { *(record.function_queries[248].output_at(__i).as_ptr() as *const [G; OUT_248]) }; __ret }, _ => { aiur_fn_248::(__args, __hash, record, io_buffer)? }, } } }; let __v_48: G = __r_arr[0]; let __r_arr: [G; OUT_264] = { let __args: [G; IN_264] = [__v_48, __v_43, __v_44]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(264, &__args[..])?) { [G::ZERO; OUT_264] } else { let (__hash, __hit) = record.function_queries[264].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[264].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[264].bump_multiplicity(__i); } let __ret: [G; OUT_264] = unsafe { *(record.function_queries[264].output_at(__i).as_ptr() as *const [G; OUT_264]) }; __ret }, _ => { aiur_fn_264::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __v_50: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_50.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_51: G = __loaded[0]; let __v_52: G = __loaded[1]; let __v_53: G = __loaded[2]; if (__v_51 != __v_47) { return Err(ExecError::AssertEqMismatch { lhs: __v_51.as_canonical_u64(), rhs: __v_47.as_canonical_u64(), msg: Some("aggr structural: trailing bytes after Merkle path".to_string()) }); } if (__v_52 != __v_47) { return Err(ExecError::AssertEqMismatch { lhs: __v_52.as_canonical_u64(), rhs: __v_47.as_canonical_u64(), msg: Some("aggr structural: trailing bytes after Merkle path".to_string()) }); } if (__v_53 != __v_47) { return Err(ExecError::AssertEqMismatch { lhs: __v_53.as_canonical_u64(), rhs: __v_47.as_canonical_u64(), msg: Some("aggr structural: trailing bytes after Merkle path".to_string()) }); } let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_49, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; if (__v_54 != __v_47) { return Err(ExecError::AssertEqMismatch { lhs: __v_54.as_canonical_u64(), rhs: __v_47.as_canonical_u64(), msg: Some("aggr structural: assumption path does not reach subject root".to_string()) }); } let __ret: [G; OUT_265] = [__v_47]; record.function_queries[265].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } -const INPUT_SIZE_266: usize = 4; -const IN_266: usize = 4; -const OUT_266: usize = 0; -fn aiur_fn_266(inp: [G; IN_266], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_266], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __r_arr: [G; OUT_261] = { let __args: [G; IN_261] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(261, &__args[..])?) { [G::ZERO; OUT_261] } else { let (__hash, __hit) = record.function_queries[261].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[261].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[261].bump_multiplicity(__i); } let __ret: [G; OUT_261] = unsafe { *(record.function_queries[261].output_at(__i).as_ptr() as *const [G; OUT_261]) }; __ret }, _ => { aiur_fn_261::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = __r_arr[1]; let __v_6: G = __r_arr[2]; let __v_7: G = __r_arr[3]; match __v_4.as_canonical_u64() { 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_3.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_8: G = __loaded[0]; let __v_9: G = __loaded[1]; let __v_10: G = __loaded[2]; let __v_11: G = G::from_u64(1); if (__v_8 != __v_11) { return Err(ExecError::AssertEqMismatch { lhs: __v_8.as_canonical_u64(), rhs: __v_11.as_canonical_u64(), msg: Some("aggr structural: output has an extra assumption".to_string()) }); } if (__v_9 != __v_11) { return Err(ExecError::AssertEqMismatch { lhs: __v_9.as_canonical_u64(), rhs: __v_11.as_canonical_u64(), msg: Some("aggr structural: output has an extra assumption".to_string()) }); } if (__v_10 != __v_11) { return Err(ExecError::AssertEqMismatch { lhs: __v_10.as_canonical_u64(), rhs: __v_11.as_canonical_u64(), msg: Some("aggr structural: output has an extra assumption".to_string()) }); } let __ret: [G; OUT_266] = []; record.function_queries[266].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __r_arr: [G; OUT_265] = { let __args: [G; IN_265] = [__v_5, __v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(265, &__args[..])?) { [G::ZERO; OUT_265] } else { let (__hash, __hit) = record.function_queries[265].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[265].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[265].bump_multiplicity(__i); } let __ret: [G; OUT_265] = unsafe { *(record.function_queries[265].output_at(__i).as_ptr() as *const [G; OUT_265]) }; __ret }, _ => { aiur_fn_265::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; match __v_8.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_266] = { let __args: [G; IN_266] = [__v_6, __v_7, __v_2, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(266, &__args[..])?) { [G::ZERO; OUT_266] } else { let (__hash, __hit) = record.function_queries[266].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[266].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[266].bump_multiplicity(__i); } let __ret: [G; OUT_266] = unsafe { *(record.function_queries[266].output_at(__i).as_ptr() as *const [G; OUT_266]) }; __ret }, _ => { aiur_fn_266::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_266] = []; record.function_queries[266].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_3.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_9: G = __loaded[0]; let __v_10: G = __loaded[1]; let __v_11: G = __loaded[2]; match __v_9.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_5, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __v_13: G = G::from_u64(1); if (__v_12 != __v_13) { return Err(ExecError::AssertEqMismatch { lhs: __v_12.as_canonical_u64(), rhs: __v_13.as_canonical_u64(), msg: Some("aggr structural: outstanding assumption mismatch".to_string()) }); } let __r_arr: [G; OUT_266] = { let __args: [G; IN_266] = [__v_6, __v_7, __v_2, __v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(266, &__args[..])?) { [G::ZERO; OUT_266] } else { let (__hash, __hit) = record.function_queries[266].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[266].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[266].bump_multiplicity(__i); } let __ret: [G; OUT_266] = unsafe { *(record.function_queries[266].output_at(__i).as_ptr() as *const [G; OUT_266]) }; __ret }, _ => { aiur_fn_266::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_266] = []; record.function_queries[266].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_9.as_canonical_u64())); }, } }, } }, _ => { return Err(ExecError::MatchNoCase(__v_4.as_canonical_u64())); }, } }) } -const INPUT_SIZE_267: usize = 1; -const IN_267: usize = 1; -const OUT_267: usize = 3; -fn aiur_fn_267(inp: [G; IN_267], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_267], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; match __v_1.as_canonical_u64() { 0u64 => { let __v_3: G = G::from_u64(1); let __ret: [G; OUT_267] = [__v_3, __v_3, __v_2]; record.function_queries[267].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __v_4: G = __r_arr[1]; let __v_5: G = G::from_u64(0); let __ret: [G; OUT_267] = [__v_5, __v_3, __v_4]; record.function_queries[267].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } -const INPUT_SIZE_268: usize = 1; -const IN_268: usize = 1; -const OUT_268: usize = 8; -fn aiur_fn_268(inp: [G; IN_268], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_268], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __v_3: G = G::from_u64(229); if (__v_1 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_1.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("aggr: claim is not CheckEnv".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = __r_arr[1]; let __v_6: G = G::from_u64(4); if (__v_4 != __v_6) { return Err(ExecError::AssertEqMismatch { lhs: __v_4.as_canonical_u64(), rhs: __v_6.as_canonical_u64(), msg: Some("aggr: unsupported object format".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = __r_arr[1]; let __v_9: G = G::from_u64(3); let __v_10: G = __v_7 - __v_9; let __v_11: G = __v_7 - __v_6; let __v_12: G = __v_10 * __v_11; let __v_13: G = G::from_u64(5); let __v_14: G = __v_7 - __v_13; let __v_15: G = __v_12 * __v_14; let __v_16: G = G::from_u64(0); if (__v_15 != __v_16) { return Err(ExecError::AssertEqMismatch { lhs: __v_15.as_canonical_u64(), rhs: __v_16.as_canonical_u64(), msg: Some("aggr: wrong validator identity".to_string()) }); } let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = __r_arr[1]; let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __r_arr: [G; OUT_267] = { let __args: [G; IN_267] = [__v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(267, &__args[..])?) { [G::ZERO; OUT_267] } else { let (__hash, __hit) = record.function_queries[267].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[267].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[267].bump_multiplicity(__i); } let __ret: [G; OUT_267] = unsafe { *(record.function_queries[267].output_at(__i).as_ptr() as *const [G; OUT_267]) }; __ret }, _ => { aiur_fn_267::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __v_23: G = __r_arr[2]; let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_23]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_24: G = __r_arr[0]; let __v_25: G = __r_arr[1]; let __r_arr: [G; OUT_267] = { let __args: [G; IN_267] = [__v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(267, &__args[..])?) { [G::ZERO; OUT_267] } else { let (__hash, __hit) = record.function_queries[267].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[267].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[267].bump_multiplicity(__i); } let __ret: [G; OUT_267] = unsafe { *(record.function_queries[267].output_at(__i).as_ptr() as *const [G; OUT_267]) }; __ret }, _ => { aiur_fn_267::(__args, __hash, record, io_buffer)? }, } } }; let __v_26: G = __r_arr[0]; let __v_27: G = __r_arr[1]; let __v_28: G = __r_arr[2]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_28.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_29: G = __loaded[0]; let __v_30: G = __loaded[1]; let __v_31: G = __loaded[2]; let __v_32: G = G::from_u64(1); if (__v_29 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_29.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr: trailing bytes after CheckEnv claim".to_string()) }); } if (__v_30 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_30.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr: trailing bytes after CheckEnv claim".to_string()) }); } if (__v_31 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_31.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr: trailing bytes after CheckEnv claim".to_string()) }); } let __ret: [G; OUT_268] = [__v_17, __v_19, __v_21, __v_22, __v_7, __v_24, __v_26, __v_27]; record.function_queries[268].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +fn aiur_fn_264(inp: [G; IN_264], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_264], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_1.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; match __v_2.as_canonical_u64() { 1u64 => { let __v_5: G = G::from_u64(0); let __ret: [G; OUT_264] = [__v_5, __v_1]; record.function_queries[264].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_250] = { let __args: [G; IN_250] = [__v_3, __v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(250, &__args[..])?) { [G::ZERO; OUT_250] } else { let (__hash, __hit) = record.function_queries[250].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[250].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[250].bump_multiplicity(__i); } let __ret: [G; OUT_250] = unsafe { *(record.function_queries[250].output_at(__i).as_ptr() as *const [G; OUT_250]) }; __ret }, _ => { aiur_fn_250::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; match __v_5.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_264] = { let __args: [G; IN_264] = [__v_0, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(264, &__args[..])?) { [G::ZERO; OUT_264] } else { let (__hash, __hit) = record.function_queries[264].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[264].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[264].bump_multiplicity(__i); } let __ret: [G; OUT_264] = unsafe { *(record.function_queries[264].output_at(__i).as_ptr() as *const [G; OUT_264]) }; __ret }, _ => { aiur_fn_264::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = __r_arr[1]; let __ret: [G; OUT_264] = [__v_6, __v_7]; record.function_queries[264].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_6: G = G::from_u64(1); let __ret: [G; OUT_264] = [__v_6, __v_4]; record.function_queries[264].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_6: G = G::from_u64(0); let __ret: [G; OUT_264] = [__v_6, __v_1]; record.function_queries[264].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } +const INPUT_SIZE_265: usize = 4; +const IN_265: usize = 4; +const OUT_265: usize = 0; +fn aiur_fn_265(inp: [G; IN_265], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_265], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __r_arr: [G; OUT_263] = { let __args: [G; IN_263] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(263, &__args[..])?) { [G::ZERO; OUT_263] } else { let (__hash, __hit) = record.function_queries[263].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[263].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[263].bump_multiplicity(__i); } let __ret: [G; OUT_263] = unsafe { *(record.function_queries[263].output_at(__i).as_ptr() as *const [G; OUT_263]) }; __ret }, _ => { aiur_fn_263::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = __r_arr[1]; let __v_6: G = __r_arr[2]; let __v_7: G = __r_arr[3]; let __v_8: G = __r_arr[4]; match __v_4.as_canonical_u64() { 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_3.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_9: G = __loaded[0]; let __v_10: G = __loaded[1]; let __v_11: G = __loaded[2]; let __v_12: G = G::from_u64(1); if (__v_9 != __v_12) { return Err(ExecError::AssertEqMismatch { lhs: __v_9.as_canonical_u64(), rhs: __v_12.as_canonical_u64(), msg: Some("aggr: output has an extra assumption".to_string()) }); } if (__v_10 != __v_12) { return Err(ExecError::AssertEqMismatch { lhs: __v_10.as_canonical_u64(), rhs: __v_12.as_canonical_u64(), msg: Some("aggr: output has an extra assumption".to_string()) }); } if (__v_11 != __v_12) { return Err(ExecError::AssertEqMismatch { lhs: __v_11.as_canonical_u64(), rhs: __v_12.as_canonical_u64(), msg: Some("aggr: output has an extra assumption".to_string()) }); } let __ret: [G; OUT_265] = []; record.function_queries[265].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __r_arr: [G; OUT_264] = { let __args: [G; IN_264] = [__v_5, __v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(264, &__args[..])?) { [G::ZERO; OUT_264] } else { let (__hash, __hit) = record.function_queries[264].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[264].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[264].bump_multiplicity(__i); } let __ret: [G; OUT_264] = unsafe { *(record.function_queries[264].output_at(__i).as_ptr() as *const [G; OUT_264]) }; __ret }, _ => { aiur_fn_264::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = __r_arr[1]; match __v_9.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_265] = { let __args: [G; IN_265] = [__v_7, __v_8, __v_10, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(265, &__args[..])?) { [G::ZERO; OUT_265] } else { let (__hash, __hit) = record.function_queries[265].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[265].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[265].bump_multiplicity(__i); } let __ret: [G; OUT_265] = unsafe { *(record.function_queries[265].output_at(__i).as_ptr() as *const [G; OUT_265]) }; __ret }, _ => { aiur_fn_265::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_265] = []; record.function_queries[265].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_3.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_11: G = __loaded[0]; let __v_12: G = __loaded[1]; let __v_13: G = __loaded[2]; match __v_11.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_5, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_14: G = __r_arr[0]; let __v_15: G = G::from_u64(1); if (__v_14 != __v_15) { return Err(ExecError::AssertEqMismatch { lhs: __v_14.as_canonical_u64(), rhs: __v_15.as_canonical_u64(), msg: Some("aggr: outstanding assumption mismatch".to_string()) }); } let __r_arr: [G; OUT_265] = { let __args: [G; IN_265] = [__v_7, __v_8, __v_10, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(265, &__args[..])?) { [G::ZERO; OUT_265] } else { let (__hash, __hit) = record.function_queries[265].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[265].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[265].bump_multiplicity(__i); } let __ret: [G; OUT_265] = unsafe { *(record.function_queries[265].output_at(__i).as_ptr() as *const [G; OUT_265]) }; __ret }, _ => { aiur_fn_265::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_265] = []; record.function_queries[265].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_11.as_canonical_u64())); }, } }, } }, _ => { return Err(ExecError::MatchNoCase(__v_4.as_canonical_u64())); }, } }) } +const INPUT_SIZE_266: usize = 3; +const IN_266: usize = 3; +const OUT_266: usize = 2; +fn aiur_fn_266(inp: [G; IN_266], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_266], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; match __v_1.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_266] = [__v_0, __v_2]; record.function_queries[266].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __v_4: G = __r_arr[1]; let __v_5: G = G::from_u64(2); let __v_6: G = if unconstrained { aiur::execute::CodegenBytes2::less_than(&__v_3, &__v_5) } else { aiur::execute::bytes2_less_than_value(__v_3, __v_5, record) }; let __v_7: G = G::from_u64(1); if (__v_6 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_6.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("aggr structural: path side must be 0 or 1".to_string()) }); } let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __v_9: G = __r_arr[1]; let __mc_out___mc_0: [G; 1] = '__mc_0: { match __v_3.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_247] = { let __args: [G; IN_247] = [__v_8, __v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(247, &__args[..])?) { [G::ZERO; OUT_247] } else { let (__hash, __hit) = record.function_queries[247].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[247].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[247].bump_multiplicity(__i); } let __ret: [G; OUT_247] = unsafe { *(record.function_queries[247].output_at(__i).as_ptr() as *const [G; OUT_247]) }; __ret }, _ => { aiur_fn_247::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; break '__mc_0 [__v_10]; }, _ => { let __r_arr: [G; OUT_247] = { let __args: [G; IN_247] = [__v_0, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(247, &__args[..])?) { [G::ZERO; OUT_247] } else { let (__hash, __hit) = record.function_queries[247].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[247].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[247].bump_multiplicity(__i); } let __ret: [G; OUT_247] = unsafe { *(record.function_queries[247].output_at(__i).as_ptr() as *const [G; OUT_247]) }; __ret }, _ => { aiur_fn_247::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; break '__mc_0 [__v_10]; }, } }; let __v_10: G = __mc_out___mc_0[0]; let __v_11: G = G::from_u64(1); let __v_12: G = __v_1 - __v_11; let __r_arr: [G; OUT_266] = { let __args: [G; IN_266] = [__v_10, __v_12, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(266, &__args[..])?) { [G::ZERO; OUT_266] } else { let (__hash, __hit) = record.function_queries[266].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[266].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[266].bump_multiplicity(__i); } let __ret: [G; OUT_266] = unsafe { *(record.function_queries[266].output_at(__i).as_ptr() as *const [G; OUT_266]) }; __ret }, _ => { aiur_fn_266::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __ret: [G; OUT_266] = [__v_13, __v_14]; record.function_queries[266].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } +const INPUT_SIZE_267: usize = 4; +const IN_267: usize = 4; +const OUT_267: usize = 1; +fn aiur_fn_267(inp: [G; IN_267], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_267], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = G::from_u64(6); let __loaded: [G; 32] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 32 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 32] = __args[..32].try_into().unwrap(); __arr }; let __v_5: G = __loaded[0]; let __v_6: G = __loaded[1]; let __v_7: G = __loaded[2]; let __v_8: G = __loaded[3]; let __v_9: G = __loaded[4]; let __v_10: G = __loaded[5]; let __v_11: G = __loaded[6]; let __v_12: G = __loaded[7]; let __v_13: G = __loaded[8]; let __v_14: G = __loaded[9]; let __v_15: G = __loaded[10]; let __v_16: G = __loaded[11]; let __v_17: G = __loaded[12]; let __v_18: G = __loaded[13]; let __v_19: G = __loaded[14]; let __v_20: G = __loaded[15]; let __v_21: G = __loaded[16]; let __v_22: G = __loaded[17]; let __v_23: G = __loaded[18]; let __v_24: G = __loaded[19]; let __v_25: G = __loaded[20]; let __v_26: G = __loaded[21]; let __v_27: G = __loaded[22]; let __v_28: G = __loaded[23]; let __v_29: G = __loaded[24]; let __v_30: G = __loaded[25]; let __v_31: G = __loaded[26]; let __v_32: G = __loaded[27]; let __v_33: G = __loaded[28]; let __v_34: G = __loaded[29]; let __v_35: G = __loaded[30]; let __v_36: G = __loaded[31]; let __io_pair: (G, G) = { let __key: [G; 32] = [__v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36]; let __info = io_buffer.get_info(__v_4, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_37: G = __io_pair.0; let __v_38: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_4, __v_37, __v_38]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_39: G = __r_arr[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_39]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_40: G = __r_arr[0]; let __v_41: G = __r_arr[1]; let __v_42: G = G::from_u64(2); let __v_43: G = if unconstrained { aiur::execute::CodegenBytes2::less_than(&__v_40, &__v_42) } else { aiur::execute::bytes2_less_than_value(__v_40, __v_42, record) }; let __v_44: G = G::from_u64(1); if (__v_43 != __v_44) { return Err(ExecError::AssertEqMismatch { lhs: __v_43.as_canonical_u64(), rhs: __v_44.as_canonical_u64(), msg: Some("aggr structural: discharge choice must be 0 or 1".to_string()) }); } match __v_40.as_canonical_u64() { 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_41.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_45: G = __loaded[0]; let __v_46: G = __loaded[1]; let __v_47: G = __loaded[2]; let __v_48: G = G::from_u64(1); if (__v_45 != __v_48) { return Err(ExecError::AssertEqMismatch { lhs: __v_45.as_canonical_u64(), rhs: __v_48.as_canonical_u64(), msg: Some("aggr structural: carried choice has trailing bytes".to_string()) }); } if (__v_46 != __v_48) { return Err(ExecError::AssertEqMismatch { lhs: __v_46.as_canonical_u64(), rhs: __v_48.as_canonical_u64(), msg: Some("aggr structural: carried choice has trailing bytes".to_string()) }); } if (__v_47 != __v_48) { return Err(ExecError::AssertEqMismatch { lhs: __v_47.as_canonical_u64(), rhs: __v_48.as_canonical_u64(), msg: Some("aggr structural: carried choice has trailing bytes".to_string()) }); } let __v_49: G = G::from_u64(0); let __ret: [G; OUT_267] = [__v_49]; record.function_queries[267].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_41]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_45: G = __r_arr[0]; let __v_46: G = __r_arr[1]; let __v_47: G = G::from_u64(0); let __v_48: G = if unconstrained { aiur::execute::CodegenBytes2::less_than(&__v_47, &__v_45) } else { aiur::execute::bytes2_less_than_value(__v_47, __v_45, record) }; let __v_49: G = G::from_u64(1); if (__v_48 != __v_49) { return Err(ExecError::AssertEqMismatch { lhs: __v_48.as_canonical_u64(), rhs: __v_49.as_canonical_u64(), msg: Some("aggr structural: Merkle path must have at least 1 step".to_string()) }); } let __v_50: G = G::from_u64(65); let __v_51: G = if unconstrained { aiur::execute::CodegenBytes2::less_than(&__v_45, &__v_50) } else { aiur::execute::bytes2_less_than_value(__v_45, __v_50, record) }; if (__v_51 != __v_49) { return Err(ExecError::AssertEqMismatch { lhs: __v_51.as_canonical_u64(), rhs: __v_49.as_canonical_u64(), msg: Some("aggr structural: Merkle path exceeds 64 steps".to_string()) }); } let __r_arr: [G; OUT_248] = { let __args: [G; IN_248] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(248, &__args[..])?) { [G::ZERO; OUT_248] } else { let (__hash, __hit) = record.function_queries[248].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[248].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[248].bump_multiplicity(__i); } let __ret: [G; OUT_248] = unsafe { *(record.function_queries[248].output_at(__i).as_ptr() as *const [G; OUT_248]) }; __ret }, _ => { aiur_fn_248::(__args, __hash, record, io_buffer)? }, } } }; let __v_52: G = __r_arr[0]; let __v_53: G = __v_45 - __v_49; let __r_arr: [G; OUT_266] = { let __args: [G; IN_266] = [__v_52, __v_53, __v_46]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(266, &__args[..])?) { [G::ZERO; OUT_266] } else { let (__hash, __hit) = record.function_queries[266].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[266].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[266].bump_multiplicity(__i); } let __ret: [G; OUT_266] = unsafe { *(record.function_queries[266].output_at(__i).as_ptr() as *const [G; OUT_266]) }; __ret }, _ => { aiur_fn_266::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __v_55: G = __r_arr[1]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_55]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_56: G = __r_arr[0]; let __v_57: G = __r_arr[1]; let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_57]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_58: G = __r_arr[0]; let __v_59: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_59.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_60: G = __loaded[0]; let __v_61: G = __loaded[1]; let __v_62: G = __loaded[2]; if (__v_60 != __v_49) { return Err(ExecError::AssertEqMismatch { lhs: __v_60.as_canonical_u64(), rhs: __v_49.as_canonical_u64(), msg: Some("aggr structural: trailing bytes after Merkle path".to_string()) }); } if (__v_61 != __v_49) { return Err(ExecError::AssertEqMismatch { lhs: __v_61.as_canonical_u64(), rhs: __v_49.as_canonical_u64(), msg: Some("aggr structural: trailing bytes after Merkle path".to_string()) }); } if (__v_62 != __v_49) { return Err(ExecError::AssertEqMismatch { lhs: __v_62.as_canonical_u64(), rhs: __v_49.as_canonical_u64(), msg: Some("aggr structural: trailing bytes after Merkle path".to_string()) }); } match __v_1.as_canonical_u64() { 0u64 => { let __v_63: G = G::from_u64(0); if (__v_56 != __v_63) { return Err(ExecError::AssertEqMismatch { lhs: __v_56.as_canonical_u64(), rhs: __v_63.as_canonical_u64(), msg: Some("aggr structural: left assumption must be discharged by right child".to_string()) }); } let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_58, __v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_64: G = __r_arr[0]; let __v_65: G = G::from_u64(1); if (__v_64 != __v_65) { return Err(ExecError::AssertEqMismatch { lhs: __v_64.as_canonical_u64(), rhs: __v_65.as_canonical_u64(), msg: Some("aggr structural: final step sibling is not left subject root".to_string()) }); } let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_54, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_66: G = __r_arr[0]; if (__v_66 != __v_65) { return Err(ExecError::AssertEqMismatch { lhs: __v_66.as_canonical_u64(), rhs: __v_65.as_canonical_u64(), msg: Some("aggr structural: assumption path does not reach right subject root".to_string()) }); } let __ret: [G; OUT_267] = [__v_65]; record.function_queries[267].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 2u64 => { let __v_63: G = G::from_u64(1); if (__v_56 != __v_63) { return Err(ExecError::AssertEqMismatch { lhs: __v_56.as_canonical_u64(), rhs: __v_63.as_canonical_u64(), msg: Some("aggr structural: right assumption must be discharged by left child".to_string()) }); } let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_58, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_64: G = __r_arr[0]; if (__v_64 != __v_63) { return Err(ExecError::AssertEqMismatch { lhs: __v_64.as_canonical_u64(), rhs: __v_63.as_canonical_u64(), msg: Some("aggr structural: final step sibling is not right subject root".to_string()) }); } let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_54, __v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_65: G = __r_arr[0]; if (__v_65 != __v_63) { return Err(ExecError::AssertEqMismatch { lhs: __v_65.as_canonical_u64(), rhs: __v_63.as_canonical_u64(), msg: Some("aggr structural: assumption path does not reach left subject root".to_string()) }); } let __ret: [G; OUT_267] = [__v_63]; record.function_queries[267].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_63: G = G::from_u64(0); let __v_64: G = G::from_u64(1); if (__v_63 != __v_64) { return Err(ExecError::AssertEqMismatch { lhs: __v_63.as_canonical_u64(), rhs: __v_64.as_canonical_u64(), msg: Some("aggr structural: shared assumption cannot be discharged".to_string()) }); } let __ret: [G; OUT_267] = [__v_63]; record.function_queries[267].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, } }) } +const INPUT_SIZE_268: usize = 5; +const IN_268: usize = 5; +const OUT_268: usize = 0; +fn aiur_fn_268(inp: [G; IN_268], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_268], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __r_arr: [G; OUT_263] = { let __args: [G; IN_263] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(263, &__args[..])?) { [G::ZERO; OUT_263] } else { let (__hash, __hit) = record.function_queries[263].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[263].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[263].bump_multiplicity(__i); } let __ret: [G; OUT_263] = unsafe { *(record.function_queries[263].output_at(__i).as_ptr() as *const [G; OUT_263]) }; __ret }, _ => { aiur_fn_263::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __r_arr[1]; let __v_7: G = __r_arr[2]; let __v_8: G = __r_arr[3]; let __v_9: G = __r_arr[4]; match __v_5.as_canonical_u64() { 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_10: G = __loaded[0]; let __v_11: G = __loaded[1]; let __v_12: G = __loaded[2]; let __v_13: G = G::from_u64(1); if (__v_10 != __v_13) { return Err(ExecError::AssertEqMismatch { lhs: __v_10.as_canonical_u64(), rhs: __v_13.as_canonical_u64(), msg: Some("aggr structural: output has an extra assumption".to_string()) }); } if (__v_11 != __v_13) { return Err(ExecError::AssertEqMismatch { lhs: __v_11.as_canonical_u64(), rhs: __v_13.as_canonical_u64(), msg: Some("aggr structural: output has an extra assumption".to_string()) }); } if (__v_12 != __v_13) { return Err(ExecError::AssertEqMismatch { lhs: __v_12.as_canonical_u64(), rhs: __v_13.as_canonical_u64(), msg: Some("aggr structural: output has an extra assumption".to_string()) }); } let __ret: [G; OUT_268] = []; record.function_queries[268].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __r_arr: [G; OUT_267] = { let __args: [G; IN_267] = [__v_6, __v_7, __v_2, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(267, &__args[..])?) { [G::ZERO; OUT_267] } else { let (__hash, __hit) = record.function_queries[267].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[267].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[267].bump_multiplicity(__i); } let __ret: [G; OUT_267] = unsafe { *(record.function_queries[267].output_at(__i).as_ptr() as *const [G; OUT_267]) }; __ret }, _ => { aiur_fn_267::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; match __v_10.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_8, __v_9, __v_2, __v_3, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_268] = []; record.function_queries[268].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_11: G = __loaded[0]; let __v_12: G = __loaded[1]; let __v_13: G = __loaded[2]; match __v_11.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_6, __v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_14: G = __r_arr[0]; let __v_15: G = G::from_u64(1); if (__v_14 != __v_15) { return Err(ExecError::AssertEqMismatch { lhs: __v_14.as_canonical_u64(), rhs: __v_15.as_canonical_u64(), msg: Some("aggr structural: outstanding assumption mismatch".to_string()) }); } let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_8, __v_9, __v_2, __v_3, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_268] = []; record.function_queries[268].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_11.as_canonical_u64())); }, } }, } }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }) } const INPUT_SIZE_269: usize = 1; const IN_269: usize = 1; -const OUT_269: usize = 2; -fn aiur_fn_269(inp: [G; IN_269], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_269], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __v_3: G = G::from_u64(232); if (__v_1 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_1.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("aggr: claim is not Binding".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = __r_arr[1]; let __v_6: G = G::from_u64(2); if (__v_4 != __v_6) { return Err(ExecError::AssertEqMismatch { lhs: __v_4.as_canonical_u64(), rhs: __v_6.as_canonical_u64(), msg: Some("aggr: claim is not Binding".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = __r_arr[1]; let __v_9: G = G::from_u64(4); if (__v_7 != __v_9) { return Err(ExecError::AssertEqMismatch { lhs: __v_7.as_canonical_u64(), rhs: __v_9.as_canonical_u64(), msg: Some("aggr: unsupported object format".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = __r_arr[1]; let __v_12: G = G::from_u64(3); if (__v_10 != __v_12) { return Err(ExecError::AssertEqMismatch { lhs: __v_10.as_canonical_u64(), rhs: __v_12.as_canonical_u64(), msg: Some("aggr: wrong validator identity".to_string()) }); } let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_16.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_17: G = __loaded[0]; let __v_18: G = __loaded[1]; let __v_19: G = __loaded[2]; let __v_20: G = G::from_u64(1); if (__v_17 != __v_20) { return Err(ExecError::AssertEqMismatch { lhs: __v_17.as_canonical_u64(), rhs: __v_20.as_canonical_u64(), msg: Some("aggr: trailing bytes after Binding claim".to_string()) }); } if (__v_18 != __v_20) { return Err(ExecError::AssertEqMismatch { lhs: __v_18.as_canonical_u64(), rhs: __v_20.as_canonical_u64(), msg: Some("aggr: trailing bytes after Binding claim".to_string()) }); } if (__v_19 != __v_20) { return Err(ExecError::AssertEqMismatch { lhs: __v_19.as_canonical_u64(), rhs: __v_20.as_canonical_u64(), msg: Some("aggr: trailing bytes after Binding claim".to_string()) }); } let __ret: [G; OUT_269] = [__v_13, __v_15]; record.function_queries[269].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const OUT_269: usize = 3; +fn aiur_fn_269(inp: [G; IN_269], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_269], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; match __v_1.as_canonical_u64() { 0u64 => { let __v_3: G = G::from_u64(1); let __ret: [G; OUT_269] = [__v_3, __v_3, __v_2]; record.function_queries[269].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __v_4: G = __r_arr[1]; let __v_5: G = G::from_u64(0); let __ret: [G; OUT_269] = [__v_5, __v_3, __v_4]; record.function_queries[269].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } const INPUT_SIZE_270: usize = 1; const IN_270: usize = 1; -const OUT_270: usize = 1; -fn aiur_fn_270(inp: [G; IN_270], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_270], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __v_3: G = G::from_u64(232); if (__v_1 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_1.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("aggr: claim is not Catalog".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = __r_arr[1]; let __v_6: G = G::from_u64(0); if (__v_4 != __v_6) { return Err(ExecError::AssertEqMismatch { lhs: __v_4.as_canonical_u64(), rhs: __v_6.as_canonical_u64(), msg: Some("aggr: claim is not Catalog".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = __r_arr[1]; let __v_9: G = G::from_u64(4); if (__v_7 != __v_9) { return Err(ExecError::AssertEqMismatch { lhs: __v_7.as_canonical_u64(), rhs: __v_9.as_canonical_u64(), msg: Some("aggr: unsupported object format".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = __r_arr[1]; let __v_12: G = G::from_u64(3); if (__v_10 != __v_12) { return Err(ExecError::AssertEqMismatch { lhs: __v_10.as_canonical_u64(), rhs: __v_12.as_canonical_u64(), msg: Some("aggr: wrong validator identity".to_string()) }); } let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_14.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_15: G = __loaded[0]; let __v_16: G = __loaded[1]; let __v_17: G = __loaded[2]; let __v_18: G = G::from_u64(1); if (__v_15 != __v_18) { return Err(ExecError::AssertEqMismatch { lhs: __v_15.as_canonical_u64(), rhs: __v_18.as_canonical_u64(), msg: Some("aggr: trailing bytes after Catalog claim".to_string()) }); } if (__v_16 != __v_18) { return Err(ExecError::AssertEqMismatch { lhs: __v_16.as_canonical_u64(), rhs: __v_18.as_canonical_u64(), msg: Some("aggr: trailing bytes after Catalog claim".to_string()) }); } if (__v_17 != __v_18) { return Err(ExecError::AssertEqMismatch { lhs: __v_17.as_canonical_u64(), rhs: __v_18.as_canonical_u64(), msg: Some("aggr: trailing bytes after Catalog claim".to_string()) }); } let __ret: [G; OUT_270] = [__v_13]; record.function_queries[270].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_271: usize = 4; -const IN_271: usize = 4; -const OUT_271: usize = 1; -fn aiur_fn_271(inp: [G; IN_271], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_271], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; match __v_0.as_canonical_u64() { 1u64 => { match __v_2.as_canonical_u64() { 1u64 => { let __v_4: G = G::from_u64(1); let __ret: [G; OUT_271] = [__v_4]; record.function_queries[271].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_4: G = G::from_u64(0); let __ret: [G; OUT_271] = [__v_4]; record.function_queries[271].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }, 0u64 => { match __v_2.as_canonical_u64() { 1u64 => { let __v_4: G = G::from_u64(0); let __ret: [G; OUT_271] = [__v_4]; record.function_queries[271].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_1, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __ret: [G; OUT_271] = [__v_4]; record.function_queries[271].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_0.as_canonical_u64())); }, } }) } -const INPUT_SIZE_272: usize = 10; -const IN_272: usize = 10; -const OUT_272: usize = 0; -fn aiur_fn_272(inp: [G; IN_272], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_272], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_0, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = G::from_u64(1); if (__v_10 != __v_11) { return Err(ExecError::AssertEqMismatch { lhs: __v_10.as_canonical_u64(), rhs: __v_11.as_canonical_u64(), msg: Some("aggr: claims differ in specification digest".to_string()) }); } let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_1, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; if (__v_12 != __v_11) { return Err(ExecError::AssertEqMismatch { lhs: __v_12.as_canonical_u64(), rhs: __v_11.as_canonical_u64(), msg: Some("aggr: claims differ in binding hash".to_string()) }); } let __r_arr: [G; OUT_271] = { let __args: [G; IN_271] = [__v_2, __v_3, __v_7, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(271, &__args[..])?) { [G::ZERO; OUT_271] } else { let (__hash, __hit) = record.function_queries[271].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[271].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[271].bump_multiplicity(__i); } let __ret: [G; OUT_271] = unsafe { *(record.function_queries[271].output_at(__i).as_ptr() as *const [G; OUT_271]) }; __ret }, _ => { aiur_fn_271::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; if (__v_13 != __v_11) { return Err(ExecError::AssertEqMismatch { lhs: __v_13.as_canonical_u64(), rhs: __v_11.as_canonical_u64(), msg: Some("aggr: claims differ in axioms".to_string()) }); } if (__v_4 != __v_9) { return Err(ExecError::AssertEqMismatch { lhs: __v_4.as_canonical_u64(), rhs: __v_9.as_canonical_u64(), msg: Some("aggr: claims differ in whether they assume their binding".to_string()) }); } let __ret: [G; OUT_272] = []; record.function_queries[272].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_273: usize = 8; -const IN_273: usize = 8; +const OUT_270: usize = 8; +fn aiur_fn_270(inp: [G; IN_270], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_270], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __v_3: G = G::from_u64(229); if (__v_1 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_1.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("aggr: claim is not CheckEnv".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = __r_arr[1]; let __v_6: G = G::from_u64(4); if (__v_4 != __v_6) { return Err(ExecError::AssertEqMismatch { lhs: __v_4.as_canonical_u64(), rhs: __v_6.as_canonical_u64(), msg: Some("aggr: unsupported object format".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = __r_arr[1]; let __v_9: G = G::from_u64(3); let __v_10: G = __v_7 - __v_9; let __v_11: G = __v_7 - __v_6; let __v_12: G = __v_10 * __v_11; let __v_13: G = G::from_u64(5); let __v_14: G = __v_7 - __v_13; let __v_15: G = __v_12 * __v_14; let __v_16: G = G::from_u64(0); if (__v_15 != __v_16) { return Err(ExecError::AssertEqMismatch { lhs: __v_15.as_canonical_u64(), rhs: __v_16.as_canonical_u64(), msg: Some("aggr: wrong validator identity".to_string()) }); } let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = __r_arr[1]; let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __r_arr: [G; OUT_269] = { let __args: [G; IN_269] = [__v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(269, &__args[..])?) { [G::ZERO; OUT_269] } else { let (__hash, __hit) = record.function_queries[269].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[269].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[269].bump_multiplicity(__i); } let __ret: [G; OUT_269] = unsafe { *(record.function_queries[269].output_at(__i).as_ptr() as *const [G; OUT_269]) }; __ret }, _ => { aiur_fn_269::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __v_23: G = __r_arr[2]; let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_23]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_24: G = __r_arr[0]; let __v_25: G = __r_arr[1]; let __r_arr: [G; OUT_269] = { let __args: [G; IN_269] = [__v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(269, &__args[..])?) { [G::ZERO; OUT_269] } else { let (__hash, __hit) = record.function_queries[269].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[269].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[269].bump_multiplicity(__i); } let __ret: [G; OUT_269] = unsafe { *(record.function_queries[269].output_at(__i).as_ptr() as *const [G; OUT_269]) }; __ret }, _ => { aiur_fn_269::(__args, __hash, record, io_buffer)? }, } } }; let __v_26: G = __r_arr[0]; let __v_27: G = __r_arr[1]; let __v_28: G = __r_arr[2]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_28.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_29: G = __loaded[0]; let __v_30: G = __loaded[1]; let __v_31: G = __loaded[2]; let __v_32: G = G::from_u64(1); if (__v_29 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_29.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr: trailing bytes after CheckEnv claim".to_string()) }); } if (__v_30 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_30.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr: trailing bytes after CheckEnv claim".to_string()) }); } if (__v_31 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_31.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr: trailing bytes after CheckEnv claim".to_string()) }); } let __ret: [G; OUT_270] = [__v_17, __v_19, __v_21, __v_22, __v_7, __v_24, __v_26, __v_27]; record.function_queries[270].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_271: usize = 1; +const IN_271: usize = 1; +const OUT_271: usize = 2; +fn aiur_fn_271(inp: [G; IN_271], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_271], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __v_3: G = G::from_u64(232); if (__v_1 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_1.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("aggr: claim is not Binding".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = __r_arr[1]; let __v_6: G = G::from_u64(2); if (__v_4 != __v_6) { return Err(ExecError::AssertEqMismatch { lhs: __v_4.as_canonical_u64(), rhs: __v_6.as_canonical_u64(), msg: Some("aggr: claim is not Binding".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = __r_arr[1]; let __v_9: G = G::from_u64(4); if (__v_7 != __v_9) { return Err(ExecError::AssertEqMismatch { lhs: __v_7.as_canonical_u64(), rhs: __v_9.as_canonical_u64(), msg: Some("aggr: unsupported object format".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = __r_arr[1]; let __v_12: G = G::from_u64(3); if (__v_10 != __v_12) { return Err(ExecError::AssertEqMismatch { lhs: __v_10.as_canonical_u64(), rhs: __v_12.as_canonical_u64(), msg: Some("aggr: wrong validator identity".to_string()) }); } let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_16.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_17: G = __loaded[0]; let __v_18: G = __loaded[1]; let __v_19: G = __loaded[2]; let __v_20: G = G::from_u64(1); if (__v_17 != __v_20) { return Err(ExecError::AssertEqMismatch { lhs: __v_17.as_canonical_u64(), rhs: __v_20.as_canonical_u64(), msg: Some("aggr: trailing bytes after Binding claim".to_string()) }); } if (__v_18 != __v_20) { return Err(ExecError::AssertEqMismatch { lhs: __v_18.as_canonical_u64(), rhs: __v_20.as_canonical_u64(), msg: Some("aggr: trailing bytes after Binding claim".to_string()) }); } if (__v_19 != __v_20) { return Err(ExecError::AssertEqMismatch { lhs: __v_19.as_canonical_u64(), rhs: __v_20.as_canonical_u64(), msg: Some("aggr: trailing bytes after Binding claim".to_string()) }); } let __ret: [G; OUT_271] = [__v_13, __v_15]; record.function_queries[271].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_272: usize = 1; +const IN_272: usize = 1; +const OUT_272: usize = 1; +fn aiur_fn_272(inp: [G; IN_272], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_272], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __v_3: G = G::from_u64(232); if (__v_1 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_1.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("aggr: claim is not Catalog".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = __r_arr[1]; let __v_6: G = G::from_u64(0); if (__v_4 != __v_6) { return Err(ExecError::AssertEqMismatch { lhs: __v_4.as_canonical_u64(), rhs: __v_6.as_canonical_u64(), msg: Some("aggr: claim is not Catalog".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = __r_arr[1]; let __v_9: G = G::from_u64(4); if (__v_7 != __v_9) { return Err(ExecError::AssertEqMismatch { lhs: __v_7.as_canonical_u64(), rhs: __v_9.as_canonical_u64(), msg: Some("aggr: unsupported object format".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = __r_arr[1]; let __v_12: G = G::from_u64(3); if (__v_10 != __v_12) { return Err(ExecError::AssertEqMismatch { lhs: __v_10.as_canonical_u64(), rhs: __v_12.as_canonical_u64(), msg: Some("aggr: wrong validator identity".to_string()) }); } let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_14.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_15: G = __loaded[0]; let __v_16: G = __loaded[1]; let __v_17: G = __loaded[2]; let __v_18: G = G::from_u64(1); if (__v_15 != __v_18) { return Err(ExecError::AssertEqMismatch { lhs: __v_15.as_canonical_u64(), rhs: __v_18.as_canonical_u64(), msg: Some("aggr: trailing bytes after Catalog claim".to_string()) }); } if (__v_16 != __v_18) { return Err(ExecError::AssertEqMismatch { lhs: __v_16.as_canonical_u64(), rhs: __v_18.as_canonical_u64(), msg: Some("aggr: trailing bytes after Catalog claim".to_string()) }); } if (__v_17 != __v_18) { return Err(ExecError::AssertEqMismatch { lhs: __v_17.as_canonical_u64(), rhs: __v_18.as_canonical_u64(), msg: Some("aggr: trailing bytes after Catalog claim".to_string()) }); } let __ret: [G; OUT_272] = [__v_13]; record.function_queries[272].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_273: usize = 4; +const IN_273: usize = 4; const OUT_273: usize = 1; -fn aiur_fn_273(inp: [G; IN_273], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_273], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = G::from_u64(4); let __io_pair: (G, G) = { let __key: [G; 8] = [__v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7]; let __info = io_buffer.get_info(__v_8, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_9: G = __io_pair.0; let __v_10: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_8, __v_9, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_u64(103); let __v_13: G = G::from_u64(230); let __v_14: G = G::from_u64(9); let __v_15: G = G::from_u64(106); let __v_16: G = G::from_u64(133); let __v_17: G = G::from_u64(174); let __v_18: G = G::from_u64(187); let __v_19: G = G::from_u64(114); let __v_20: G = G::from_u64(243); let __v_21: G = G::from_u64(110); let __v_22: G = G::from_u64(60); let __v_23: G = G::from_u64(58); let __v_24: G = G::from_u64(245); let __v_25: G = G::from_u64(79); let __v_26: G = G::from_u64(165); let __v_27: G = G::from_u64(127); let __v_28: G = G::from_u64(82); let __v_29: G = G::from_u64(14); let __v_30: G = G::from_u64(81); let __v_31: G = G::from_u64(140); let __v_32: G = G::from_u64(104); let __v_33: G = G::from_u64(5); let __v_34: G = G::from_u64(155); let __v_35: G = G::from_u64(171); let __v_36: G = G::from_u64(217); let __v_37: G = G::from_u64(131); let __v_38: G = G::from_u64(31); let __v_39: G = G::from_u64(25); let __v_40: G = G::from_u64(205); let __v_41: G = G::from_u64(224); let __v_42: G = G::from_u64(91); let __v_43: G = G::from_u64(1); let __v_44: G = { let __values: [G; 3] = [__v_43, __v_43, __v_43]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_45: G = G::from_u64(0); let __v_46: G = { let __values: [G; 8] = [__v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_47: G = { let __values: [G; 32] = [__v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_12, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_48: G = { let __values: [G; 34] = [__v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_11, __v_44, __v_45, __v_45, __v_46, __v_47, __v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_49]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_50: G = __r_arr[0]; let __v_51: G = __r_arr[1]; let __v_52: G = __r_arr[2]; let __v_53: G = __r_arr[3]; let __v_54: G = __r_arr[4]; let __v_55: G = __r_arr[5]; let __v_56: G = __r_arr[6]; let __v_57: G = __r_arr[7]; let __v_58: G = __r_arr[8]; let __v_59: G = __r_arr[9]; let __v_60: G = __r_arr[10]; let __v_61: G = __r_arr[11]; let __v_62: G = __r_arr[12]; let __v_63: G = __r_arr[13]; let __v_64: G = __r_arr[14]; let __v_65: G = __r_arr[15]; let __v_66: G = __r_arr[16]; let __v_67: G = __r_arr[17]; let __v_68: G = __r_arr[18]; let __v_69: G = __r_arr[19]; let __v_70: G = __r_arr[20]; let __v_71: G = __r_arr[21]; let __v_72: G = __r_arr[22]; let __v_73: G = __r_arr[23]; let __v_74: G = __r_arr[24]; let __v_75: G = __r_arr[25]; let __v_76: G = __r_arr[26]; let __v_77: G = __r_arr[27]; let __v_78: G = __r_arr[28]; let __v_79: G = __r_arr[29]; let __v_80: G = __r_arr[30]; let __v_81: G = __r_arr[31]; let __v_82: G = G::from_u64(256); let __v_83: G = __v_82 * __v_51; let __v_84: G = __v_50 + __v_83; let __v_85: G = G::from_u64(65536); let __v_86: G = __v_85 * __v_52; let __v_87: G = __v_84 + __v_86; let __v_88: G = G::from_u64(16777216); let __v_89: G = __v_88 * __v_53; let __v_90: G = __v_87 + __v_89; let __v_91: G = __v_82 * __v_55; let __v_92: G = __v_54 + __v_91; let __v_93: G = __v_85 * __v_56; let __v_94: G = __v_92 + __v_93; let __v_95: G = __v_88 * __v_57; let __v_96: G = __v_94 + __v_95; let __v_97: G = __v_82 * __v_59; let __v_98: G = __v_58 + __v_97; let __v_99: G = __v_85 * __v_60; let __v_100: G = __v_98 + __v_99; let __v_101: G = __v_88 * __v_61; let __v_102: G = __v_100 + __v_101; let __v_103: G = __v_82 * __v_63; let __v_104: G = __v_62 + __v_103; let __v_105: G = __v_85 * __v_64; let __v_106: G = __v_104 + __v_105; let __v_107: G = __v_88 * __v_65; let __v_108: G = __v_106 + __v_107; let __v_109: G = __v_82 * __v_67; let __v_110: G = __v_66 + __v_109; let __v_111: G = __v_85 * __v_68; let __v_112: G = __v_110 + __v_111; let __v_113: G = __v_88 * __v_69; let __v_114: G = __v_112 + __v_113; let __v_115: G = __v_82 * __v_71; let __v_116: G = __v_70 + __v_115; let __v_117: G = __v_85 * __v_72; let __v_118: G = __v_116 + __v_117; let __v_119: G = __v_88 * __v_73; let __v_120: G = __v_118 + __v_119; let __v_121: G = __v_82 * __v_75; let __v_122: G = __v_74 + __v_121; let __v_123: G = __v_85 * __v_76; let __v_124: G = __v_122 + __v_123; let __v_125: G = __v_88 * __v_77; let __v_126: G = __v_124 + __v_125; let __v_127: G = __v_82 * __v_79; let __v_128: G = __v_78 + __v_127; let __v_129: G = __v_85 * __v_80; let __v_130: G = __v_128 + __v_129; let __v_131: G = __v_88 * __v_81; let __v_132: G = __v_130 + __v_131; if (__v_90 != __v_0) { return Err(ExecError::AssertEqMismatch { lhs: __v_90.as_canonical_u64(), rhs: __v_0.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } if (__v_96 != __v_1) { return Err(ExecError::AssertEqMismatch { lhs: __v_96.as_canonical_u64(), rhs: __v_1.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } if (__v_102 != __v_2) { return Err(ExecError::AssertEqMismatch { lhs: __v_102.as_canonical_u64(), rhs: __v_2.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } if (__v_108 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_108.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } if (__v_114 != __v_4) { return Err(ExecError::AssertEqMismatch { lhs: __v_114.as_canonical_u64(), rhs: __v_4.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } if (__v_120 != __v_5) { return Err(ExecError::AssertEqMismatch { lhs: __v_120.as_canonical_u64(), rhs: __v_5.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } if (__v_126 != __v_6) { return Err(ExecError::AssertEqMismatch { lhs: __v_126.as_canonical_u64(), rhs: __v_6.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } if (__v_132 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_132.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } let __ret: [G; OUT_273] = [__v_11]; record.function_queries[273].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_274: usize = 2; -const IN_274: usize = 2; -const OUT_274: usize = 1; -fn aiur_fn_274(inp: [G; IN_274], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_274], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_326] = { let __args: [G; IN_326] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(326, &__args[..])?) { [G::ZERO; OUT_326] } else { let (__hash, __hit) = record.function_queries[326].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[326].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[326].bump_multiplicity(__i); } let __ret: [G; OUT_326] = unsafe { *(record.function_queries[326].output_at(__i).as_ptr() as *const [G; OUT_326]) }; __ret }, _ => { aiur_fn_326::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __v_3: G = __r_arr[1]; let __v_4: G = __r_arr[2]; let __v_5: G = __r_arr[3]; let __v_6: G = __r_arr[4]; let __v_7: G = __r_arr[5]; let __v_8: G = __r_arr[6]; let __v_9: G = __r_arr[7]; let __v_10: G = G::from_u64(256); let __v_11: G = __v_10 * __v_3; let __v_12: G = __v_2 + __v_11; let __v_13: G = G::from_u64(65536); let __v_14: G = __v_13 * __v_4; let __v_15: G = __v_12 + __v_14; let __v_16: G = G::from_u64(16777216); let __v_17: G = __v_16 * __v_5; let __v_18: G = __v_15 + __v_17; let __v_19: G = G::from_u64(4294967296); let __v_20: G = __v_19 * __v_6; let __v_21: G = __v_18 + __v_20; let __v_22: G = G::from_u64(1099511627776); let __v_23: G = __v_22 * __v_7; let __v_24: G = __v_21 + __v_23; let __v_25: G = G::from_u64(281474976710656); let __v_26: G = __v_25 * __v_8; let __v_27: G = __v_24 + __v_26; let __v_28: G = G::from_u64(72057594037927936); let __v_29: G = __v_28 * __v_9; let __v_30: G = __v_27 + __v_29; let __ret: [G; OUT_274] = [__v_30]; record.function_queries[274].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_275: usize = 2; -const IN_275: usize = 2; -const OUT_275: usize = 8; -fn aiur_fn_275(inp: [G; IN_275], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_275], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __v_3: G = G::from_u64(1); let __v_4: G = __v_1 + __v_3; let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_0, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = G::from_u64(2); let __v_7: G = __v_1 + __v_6; let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_0, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __v_9: G = G::from_u64(3); let __v_10: G = __v_1 + __v_9; let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_0, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_u64(4); let __v_13: G = __v_1 + __v_12; let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_0, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __v_14: G = __r_arr[0]; let __v_15: G = G::from_u64(5); let __v_16: G = __v_1 + __v_15; let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_0, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = G::from_u64(6); let __v_19: G = __v_1 + __v_18; let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_0, __v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __v_20: G = __r_arr[0]; let __v_21: G = G::from_u64(7); let __v_22: G = __v_1 + __v_21; let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_0, __v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __ret: [G; OUT_275] = [__v_2, __v_5, __v_8, __v_11, __v_14, __v_17, __v_20, __v_23]; record.function_queries[275].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_276: usize = 16; -const IN_276: usize = 16; -const OUT_276: usize = 0; -fn aiur_fn_276(inp: [G; IN_276], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_276], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; if (__v_0 != __v_8) { return Err(ExecError::AssertEqMismatch { lhs: __v_0.as_canonical_u64(), rhs: __v_8.as_canonical_u64(), msg: None }); } if (__v_1 != __v_9) { return Err(ExecError::AssertEqMismatch { lhs: __v_1.as_canonical_u64(), rhs: __v_9.as_canonical_u64(), msg: None }); } if (__v_2 != __v_10) { return Err(ExecError::AssertEqMismatch { lhs: __v_2.as_canonical_u64(), rhs: __v_10.as_canonical_u64(), msg: None }); } if (__v_3 != __v_11) { return Err(ExecError::AssertEqMismatch { lhs: __v_3.as_canonical_u64(), rhs: __v_11.as_canonical_u64(), msg: None }); } if (__v_4 != __v_12) { return Err(ExecError::AssertEqMismatch { lhs: __v_4.as_canonical_u64(), rhs: __v_12.as_canonical_u64(), msg: None }); } if (__v_5 != __v_13) { return Err(ExecError::AssertEqMismatch { lhs: __v_5.as_canonical_u64(), rhs: __v_13.as_canonical_u64(), msg: None }); } if (__v_6 != __v_14) { return Err(ExecError::AssertEqMismatch { lhs: __v_6.as_canonical_u64(), rhs: __v_14.as_canonical_u64(), msg: None }); } if (__v_7 != __v_15) { return Err(ExecError::AssertEqMismatch { lhs: __v_7.as_canonical_u64(), rhs: __v_15.as_canonical_u64(), msg: None }); } let __ret: [G; OUT_276] = []; record.function_queries[276].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_277: usize = 1; -const IN_277: usize = 1; -const OUT_277: usize = 1; -fn aiur_fn_277(inp: [G; IN_277], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_277], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; match __v_1.as_canonical_u64() { 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_3.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_4: G = __loaded[0]; let __v_5: G = __loaded[1]; let __v_6: G = __loaded[2]; let __v_7: G = G::from_u64(1); if (__v_4 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_4.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("aggr: child proof must expose exactly one claim".to_string()) }); } if (__v_5 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_5.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("aggr: child proof must expose exactly one claim".to_string()) }); } if (__v_6 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_6.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("aggr: child proof must expose exactly one claim".to_string()) }); } let __ret: [G; OUT_277] = [__v_2]; record.function_queries[277].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } -const INPUT_SIZE_278: usize = 17; -const IN_278: usize = 17; -const OUT_278: usize = 12; -fn aiur_fn_278(inp: [G; IN_278], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_278], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = G::from_u64(1); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_0]; let __info = io_buffer.get_info(__v_17, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_18: G = __io_pair.0; let __v_19: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_17, __v_18, __v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_20: G = __r_arr[0]; let __v_21: G = G::from_u64(103); let __v_22: G = G::from_u64(230); let __v_23: G = G::from_u64(9); let __v_24: G = G::from_u64(106); let __v_25: G = G::from_u64(133); let __v_26: G = G::from_u64(174); let __v_27: G = G::from_u64(187); let __v_28: G = G::from_u64(114); let __v_29: G = G::from_u64(243); let __v_30: G = G::from_u64(110); let __v_31: G = G::from_u64(60); let __v_32: G = G::from_u64(58); let __v_33: G = G::from_u64(245); let __v_34: G = G::from_u64(79); let __v_35: G = G::from_u64(165); let __v_36: G = G::from_u64(127); let __v_37: G = G::from_u64(82); let __v_38: G = G::from_u64(14); let __v_39: G = G::from_u64(81); let __v_40: G = G::from_u64(140); let __v_41: G = G::from_u64(104); let __v_42: G = G::from_u64(5); let __v_43: G = G::from_u64(155); let __v_44: G = G::from_u64(171); let __v_45: G = G::from_u64(217); let __v_46: G = G::from_u64(131); let __v_47: G = G::from_u64(31); let __v_48: G = G::from_u64(25); let __v_49: G = G::from_u64(205); let __v_50: G = G::from_u64(224); let __v_51: G = G::from_u64(91); let __v_52: G = { let __values: [G; 3] = [__v_17, __v_17, __v_17]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_53: G = G::from_u64(0); let __v_54: G = { let __values: [G; 8] = [__v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_55: G = { let __values: [G; 32] = [__v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_21, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_56: G = { let __values: [G; 34] = [__v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_20, __v_52, __v_53, __v_53, __v_54, __v_55, __v_56]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_57: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_57]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_58: G = __r_arr[0]; let __v_59: G = __r_arr[1]; let __v_60: G = __r_arr[2]; let __v_61: G = __r_arr[3]; let __v_62: G = __r_arr[4]; let __v_63: G = __r_arr[5]; let __v_64: G = __r_arr[6]; let __v_65: G = __r_arr[7]; let __v_66: G = __r_arr[8]; let __v_67: G = __r_arr[9]; let __v_68: G = __r_arr[10]; let __v_69: G = __r_arr[11]; let __v_70: G = __r_arr[12]; let __v_71: G = __r_arr[13]; let __v_72: G = __r_arr[14]; let __v_73: G = __r_arr[15]; let __v_74: G = __r_arr[16]; let __v_75: G = __r_arr[17]; let __v_76: G = __r_arr[18]; let __v_77: G = __r_arr[19]; let __v_78: G = __r_arr[20]; let __v_79: G = __r_arr[21]; let __v_80: G = __r_arr[22]; let __v_81: G = __r_arr[23]; let __v_82: G = __r_arr[24]; let __v_83: G = __r_arr[25]; let __v_84: G = __r_arr[26]; let __v_85: G = __r_arr[27]; let __v_86: G = __r_arr[28]; let __v_87: G = __r_arr[29]; let __v_88: G = __r_arr[30]; let __v_89: G = __r_arr[31]; let __v_90: G = G::from_u64(256); let __v_91: G = __v_90 * __v_59; let __v_92: G = __v_58 + __v_91; let __v_93: G = G::from_u64(65536); let __v_94: G = __v_93 * __v_60; let __v_95: G = __v_92 + __v_94; let __v_96: G = G::from_u64(16777216); let __v_97: G = __v_96 * __v_61; let __v_98: G = __v_95 + __v_97; let __v_99: G = __v_90 * __v_63; let __v_100: G = __v_62 + __v_99; let __v_101: G = __v_93 * __v_64; let __v_102: G = __v_100 + __v_101; let __v_103: G = __v_96 * __v_65; let __v_104: G = __v_102 + __v_103; let __v_105: G = __v_90 * __v_67; let __v_106: G = __v_66 + __v_105; let __v_107: G = __v_93 * __v_68; let __v_108: G = __v_106 + __v_107; let __v_109: G = __v_96 * __v_69; let __v_110: G = __v_108 + __v_109; let __v_111: G = __v_90 * __v_71; let __v_112: G = __v_70 + __v_111; let __v_113: G = __v_93 * __v_72; let __v_114: G = __v_112 + __v_113; let __v_115: G = __v_96 * __v_73; let __v_116: G = __v_114 + __v_115; let __v_117: G = __v_90 * __v_75; let __v_118: G = __v_74 + __v_117; let __v_119: G = __v_93 * __v_76; let __v_120: G = __v_118 + __v_119; let __v_121: G = __v_96 * __v_77; let __v_122: G = __v_120 + __v_121; let __v_123: G = __v_90 * __v_79; let __v_124: G = __v_78 + __v_123; let __v_125: G = __v_93 * __v_80; let __v_126: G = __v_124 + __v_125; let __v_127: G = __v_96 * __v_81; let __v_128: G = __v_126 + __v_127; let __v_129: G = __v_90 * __v_83; let __v_130: G = __v_82 + __v_129; let __v_131: G = __v_93 * __v_84; let __v_132: G = __v_130 + __v_131; let __v_133: G = __v_96 * __v_85; let __v_134: G = __v_132 + __v_133; let __v_135: G = __v_90 * __v_87; let __v_136: G = __v_86 + __v_135; let __v_137: G = __v_93 * __v_88; let __v_138: G = __v_136 + __v_137; let __v_139: G = __v_96 * __v_89; let __v_140: G = __v_138 + __v_139; let __mc_out___mc_0: [G; 0] = '__mc_0: { match __v_0.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_98, __v_104, __v_110, __v_116, __v_122, __v_128, __v_134, __v_140, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_0 []; }, _ => { let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_98, __v_104, __v_110, __v_116, __v_122, __v_128, __v_134, __v_140, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_0 []; }, } }; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_141: G = __r_arr[0]; let __v_142: G = __r_arr[1]; let __v_143: G = __r_arr[2]; let __v_144: G = __r_arr[3]; let __v_145: G = __r_arr[4]; let __v_146: G = __r_arr[5]; let __v_147: G = __r_arr[6]; let __v_148: G = __r_arr[7]; let __v_149: G = __r_arr[8]; let __v_150: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_150]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_151: G = __r_arr[0]; let __v_152: G = __r_arr[1]; let __v_153: G = __r_arr[2]; let __v_154: G = __r_arr[3]; let __v_155: G = __r_arr[4]; let __v_156: G = __r_arr[5]; let __v_157: G = __r_arr[6]; let __v_158: G = __r_arr[7]; let __v_159: G = __r_arr[8]; let __v_160: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_160]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_161: G = __r_arr[0]; let __v_162: G = __r_arr[1]; let __v_163: G = __r_arr[2]; let __v_164: G = __r_arr[3]; let __v_165: G = __r_arr[4]; let __v_166: G = __r_arr[5]; let __v_167: G = __r_arr[6]; let __v_168: G = __r_arr[7]; let __v_169: G = __r_arr[8]; let __v_170: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_170]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_171: G = __r_arr[0]; let __v_172: G = __r_arr[1]; let __v_173: G = __r_arr[2]; let __v_174: G = __r_arr[3]; let __v_175: G = __r_arr[4]; let __v_176: G = __r_arr[5]; let __v_177: G = __r_arr[6]; let __v_178: G = __r_arr[7]; let __v_179: G = __r_arr[8]; let __v_180: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_180]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_181: G = __r_arr[0]; let __v_182: G = __r_arr[1]; let __v_183: G = __r_arr[2]; let __v_184: G = __r_arr[3]; let __v_185: G = __r_arr[4]; let __v_186: G = __r_arr[5]; let __v_187: G = __r_arr[6]; let __v_188: G = __r_arr[7]; let __v_189: G = __r_arr[8]; let __v_190: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_190]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_191: G = __r_arr[0]; let __v_192: G = __r_arr[1]; let __v_193: G = __r_arr[2]; let __v_194: G = __r_arr[3]; let __v_195: G = __r_arr[4]; let __v_196: G = __r_arr[5]; let __v_197: G = __r_arr[6]; let __v_198: G = __r_arr[7]; let __v_199: G = __r_arr[8]; let __v_200: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_200]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_201: G = __r_arr[0]; let __v_202: G = __r_arr[1]; let __v_203: G = __r_arr[2]; let __v_204: G = __r_arr[3]; let __v_205: G = __r_arr[4]; let __v_206: G = __r_arr[5]; let __v_207: G = __r_arr[6]; let __v_208: G = __r_arr[7]; let __v_209: G = __r_arr[8]; let __v_210: G = __r_arr[9]; let __v_211: G = G::from_u64(0); let __v_212: G = G::from_u64(1); let __v_213: G = { let __values: [G; 10] = [__v_212, __v_212, __v_212, __v_212, __v_212, __v_212, __v_212, __v_212, __v_212, __v_212]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_214: G = { let __values: [G; 10] = [__v_211, __v_202, __v_203, __v_204, __v_205, __v_206, __v_207, __v_208, __v_209, __v_213]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_215: G = { let __values: [G; 10] = [__v_211, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_199, __v_214]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_216: G = { let __values: [G; 10] = [__v_211, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_215]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_217: G = { let __values: [G; 10] = [__v_211, __v_172, __v_173, __v_174, __v_175, __v_176, __v_177, __v_178, __v_179, __v_216]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_218: G = { let __values: [G; 10] = [__v_211, __v_162, __v_163, __v_164, __v_165, __v_166, __v_167, __v_168, __v_169, __v_217]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_219: G = { let __values: [G; 10] = [__v_211, __v_152, __v_153, __v_154, __v_155, __v_156, __v_157, __v_158, __v_159, __v_218]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_220: G = { let __values: [G; 10] = [__v_211, __v_142, __v_143, __v_144, __v_145, __v_146, __v_147, __v_148, __v_149, __v_219]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_210]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_221: G = __r_arr[0]; let __v_222: G = __r_arr[1]; let __v_223: G = __r_arr[2]; let __v_224: G = __r_arr[3]; let __v_225: G = __r_arr[4]; let __v_226: G = __r_arr[5]; let __v_227: G = __r_arr[6]; let __v_228: G = __r_arr[7]; let __v_229: G = __r_arr[8]; let __v_230: G = __r_arr[9]; let __r_arr: [G; OUT_106] = { let __args: [G; IN_106] = [__v_230, __v_221]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(106, &__args[..])?) { [G::ZERO; OUT_106] } else { let (__hash, __hit) = record.function_queries[106].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[106].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[106].bump_multiplicity(__i); } let __ret: [G; OUT_106] = unsafe { *(record.function_queries[106].output_at(__i).as_ptr() as *const [G; OUT_106]) }; __ret }, _ => { aiur_fn_106::(__args, __hash, record, io_buffer)? }, } } }; let __v_231: G = __r_arr[0]; let __v_232: G = __r_arr[1]; let __v_233: G = __r_arr[2]; let __r_arr: [G; OUT_107] = { let __args: [G; IN_107] = [__v_233]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(107, &__args[..])?) { [G::ZERO; OUT_107] } else { let (__hash, __hit) = record.function_queries[107].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[107].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[107].bump_multiplicity(__i); } let __ret: [G; OUT_107] = unsafe { *(record.function_queries[107].output_at(__i).as_ptr() as *const [G; OUT_107]) }; __ret }, _ => { aiur_fn_107::(__args, __hash, record, io_buffer)? }, } } }; let __v_234: G = __r_arr[0]; let __v_235: G = __r_arr[1]; let __v_236: G = __r_arr[2]; let __r_arr: [G; OUT_108] = { let __args: [G; IN_108] = [__v_236, __v_221]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(108, &__args[..])?) { [G::ZERO; OUT_108] } else { let (__hash, __hit) = record.function_queries[108].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[108].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[108].bump_multiplicity(__i); } let __ret: [G; OUT_108] = unsafe { *(record.function_queries[108].output_at(__i).as_ptr() as *const [G; OUT_108]) }; __ret }, _ => { aiur_fn_108::(__args, __hash, record, io_buffer)? }, } } }; let __v_237: G = __r_arr[0]; let __v_238: G = __r_arr[1]; let __v_239: G = { let __values: [G; 10] = [__v_211, __v_222, __v_223, __v_224, __v_225, __v_226, __v_227, __v_228, __v_229, __v_232]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_322] = { let __args: [G; IN_322] = [__v_220, __v_239]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(322, &__args[..])?) { [G::ZERO; OUT_322] } else { let (__hash, __hit) = record.function_queries[322].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[322].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[322].bump_multiplicity(__i); } let __ret: [G; OUT_322] = unsafe { *(record.function_queries[322].output_at(__i).as_ptr() as *const [G; OUT_322]) }; __ret }, _ => { aiur_fn_322::(__args, __hash, record, io_buffer)? }, } } }; let __v_240: G = __r_arr[0]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_238.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_241: G = __loaded[0]; let __v_242: G = __loaded[1]; let __v_243: G = __loaded[2]; if (__v_241 != __v_212) { return Err(ExecError::AssertEqMismatch { lhs: __v_241.as_canonical_u64(), rhs: __v_212.as_canonical_u64(), msg: None }); } if (__v_242 != __v_212) { return Err(ExecError::AssertEqMismatch { lhs: __v_242.as_canonical_u64(), rhs: __v_212.as_canonical_u64(), msg: None }); } if (__v_243 != __v_212) { return Err(ExecError::AssertEqMismatch { lhs: __v_243.as_canonical_u64(), rhs: __v_212.as_canonical_u64(), msg: None }); } let __ret: [G; OUT_278] = [__v_141, __v_151, __v_161, __v_171, __v_181, __v_191, __v_201, __v_240, __v_231, __v_234, __v_235, __v_237]; record.function_queries[278].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_279: usize = 13; -const IN_279: usize = 13; +fn aiur_fn_273(inp: [G; IN_273], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_273], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; match __v_0.as_canonical_u64() { 1u64 => { match __v_2.as_canonical_u64() { 1u64 => { let __v_4: G = G::from_u64(1); let __ret: [G; OUT_273] = [__v_4]; record.function_queries[273].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_4: G = G::from_u64(0); let __ret: [G; OUT_273] = [__v_4]; record.function_queries[273].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }, 0u64 => { match __v_2.as_canonical_u64() { 1u64 => { let __v_4: G = G::from_u64(0); let __ret: [G; OUT_273] = [__v_4]; record.function_queries[273].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_1, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __ret: [G; OUT_273] = [__v_4]; record.function_queries[273].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }, _ => { return Err(ExecError::MatchNoCase(__v_0.as_canonical_u64())); }, } }) } +const INPUT_SIZE_274: usize = 10; +const IN_274: usize = 10; +const OUT_274: usize = 0; +fn aiur_fn_274(inp: [G; IN_274], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_274], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_0, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = G::from_u64(1); if (__v_10 != __v_11) { return Err(ExecError::AssertEqMismatch { lhs: __v_10.as_canonical_u64(), rhs: __v_11.as_canonical_u64(), msg: Some("aggr: claims differ in specification digest".to_string()) }); } let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_1, __v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; if (__v_12 != __v_11) { return Err(ExecError::AssertEqMismatch { lhs: __v_12.as_canonical_u64(), rhs: __v_11.as_canonical_u64(), msg: Some("aggr: claims differ in binding hash".to_string()) }); } let __r_arr: [G; OUT_273] = { let __args: [G; IN_273] = [__v_2, __v_3, __v_7, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(273, &__args[..])?) { [G::ZERO; OUT_273] } else { let (__hash, __hit) = record.function_queries[273].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[273].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[273].bump_multiplicity(__i); } let __ret: [G; OUT_273] = unsafe { *(record.function_queries[273].output_at(__i).as_ptr() as *const [G; OUT_273]) }; __ret }, _ => { aiur_fn_273::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; if (__v_13 != __v_11) { return Err(ExecError::AssertEqMismatch { lhs: __v_13.as_canonical_u64(), rhs: __v_11.as_canonical_u64(), msg: Some("aggr: claims differ in axioms".to_string()) }); } if (__v_4 != __v_9) { return Err(ExecError::AssertEqMismatch { lhs: __v_4.as_canonical_u64(), rhs: __v_9.as_canonical_u64(), msg: Some("aggr: claims differ in whether they assume their binding".to_string()) }); } let __ret: [G; OUT_274] = []; record.function_queries[274].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_275: usize = 8; +const IN_275: usize = 8; +const OUT_275: usize = 1; +fn aiur_fn_275(inp: [G; IN_275], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_275], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = G::from_u64(4); let __io_pair: (G, G) = { let __key: [G; 8] = [__v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7]; let __info = io_buffer.get_info(__v_8, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_9: G = __io_pair.0; let __v_10: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_8, __v_9, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_u64(103); let __v_13: G = G::from_u64(230); let __v_14: G = G::from_u64(9); let __v_15: G = G::from_u64(106); let __v_16: G = G::from_u64(133); let __v_17: G = G::from_u64(174); let __v_18: G = G::from_u64(187); let __v_19: G = G::from_u64(114); let __v_20: G = G::from_u64(243); let __v_21: G = G::from_u64(110); let __v_22: G = G::from_u64(60); let __v_23: G = G::from_u64(58); let __v_24: G = G::from_u64(245); let __v_25: G = G::from_u64(79); let __v_26: G = G::from_u64(165); let __v_27: G = G::from_u64(127); let __v_28: G = G::from_u64(82); let __v_29: G = G::from_u64(14); let __v_30: G = G::from_u64(81); let __v_31: G = G::from_u64(140); let __v_32: G = G::from_u64(104); let __v_33: G = G::from_u64(5); let __v_34: G = G::from_u64(155); let __v_35: G = G::from_u64(171); let __v_36: G = G::from_u64(217); let __v_37: G = G::from_u64(131); let __v_38: G = G::from_u64(31); let __v_39: G = G::from_u64(25); let __v_40: G = G::from_u64(205); let __v_41: G = G::from_u64(224); let __v_42: G = G::from_u64(91); let __v_43: G = G::from_u64(1); let __v_44: G = { let __values: [G; 3] = [__v_43, __v_43, __v_43]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_45: G = G::from_u64(0); let __v_46: G = { let __values: [G; 8] = [__v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_47: G = { let __values: [G; 32] = [__v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_12, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_48: G = { let __values: [G; 34] = [__v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_11, __v_44, __v_45, __v_45, __v_46, __v_47, __v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_49]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_50: G = __r_arr[0]; let __v_51: G = __r_arr[1]; let __v_52: G = __r_arr[2]; let __v_53: G = __r_arr[3]; let __v_54: G = __r_arr[4]; let __v_55: G = __r_arr[5]; let __v_56: G = __r_arr[6]; let __v_57: G = __r_arr[7]; let __v_58: G = __r_arr[8]; let __v_59: G = __r_arr[9]; let __v_60: G = __r_arr[10]; let __v_61: G = __r_arr[11]; let __v_62: G = __r_arr[12]; let __v_63: G = __r_arr[13]; let __v_64: G = __r_arr[14]; let __v_65: G = __r_arr[15]; let __v_66: G = __r_arr[16]; let __v_67: G = __r_arr[17]; let __v_68: G = __r_arr[18]; let __v_69: G = __r_arr[19]; let __v_70: G = __r_arr[20]; let __v_71: G = __r_arr[21]; let __v_72: G = __r_arr[22]; let __v_73: G = __r_arr[23]; let __v_74: G = __r_arr[24]; let __v_75: G = __r_arr[25]; let __v_76: G = __r_arr[26]; let __v_77: G = __r_arr[27]; let __v_78: G = __r_arr[28]; let __v_79: G = __r_arr[29]; let __v_80: G = __r_arr[30]; let __v_81: G = __r_arr[31]; let __v_82: G = G::from_u64(256); let __v_83: G = __v_82 * __v_51; let __v_84: G = __v_50 + __v_83; let __v_85: G = G::from_u64(65536); let __v_86: G = __v_85 * __v_52; let __v_87: G = __v_84 + __v_86; let __v_88: G = G::from_u64(16777216); let __v_89: G = __v_88 * __v_53; let __v_90: G = __v_87 + __v_89; let __v_91: G = __v_82 * __v_55; let __v_92: G = __v_54 + __v_91; let __v_93: G = __v_85 * __v_56; let __v_94: G = __v_92 + __v_93; let __v_95: G = __v_88 * __v_57; let __v_96: G = __v_94 + __v_95; let __v_97: G = __v_82 * __v_59; let __v_98: G = __v_58 + __v_97; let __v_99: G = __v_85 * __v_60; let __v_100: G = __v_98 + __v_99; let __v_101: G = __v_88 * __v_61; let __v_102: G = __v_100 + __v_101; let __v_103: G = __v_82 * __v_63; let __v_104: G = __v_62 + __v_103; let __v_105: G = __v_85 * __v_64; let __v_106: G = __v_104 + __v_105; let __v_107: G = __v_88 * __v_65; let __v_108: G = __v_106 + __v_107; let __v_109: G = __v_82 * __v_67; let __v_110: G = __v_66 + __v_109; let __v_111: G = __v_85 * __v_68; let __v_112: G = __v_110 + __v_111; let __v_113: G = __v_88 * __v_69; let __v_114: G = __v_112 + __v_113; let __v_115: G = __v_82 * __v_71; let __v_116: G = __v_70 + __v_115; let __v_117: G = __v_85 * __v_72; let __v_118: G = __v_116 + __v_117; let __v_119: G = __v_88 * __v_73; let __v_120: G = __v_118 + __v_119; let __v_121: G = __v_82 * __v_75; let __v_122: G = __v_74 + __v_121; let __v_123: G = __v_85 * __v_76; let __v_124: G = __v_122 + __v_123; let __v_125: G = __v_88 * __v_77; let __v_126: G = __v_124 + __v_125; let __v_127: G = __v_82 * __v_79; let __v_128: G = __v_78 + __v_127; let __v_129: G = __v_85 * __v_80; let __v_130: G = __v_128 + __v_129; let __v_131: G = __v_88 * __v_81; let __v_132: G = __v_130 + __v_131; if (__v_90 != __v_0) { return Err(ExecError::AssertEqMismatch { lhs: __v_90.as_canonical_u64(), rhs: __v_0.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } if (__v_96 != __v_1) { return Err(ExecError::AssertEqMismatch { lhs: __v_96.as_canonical_u64(), rhs: __v_1.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } if (__v_102 != __v_2) { return Err(ExecError::AssertEqMismatch { lhs: __v_102.as_canonical_u64(), rhs: __v_2.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } if (__v_108 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_108.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } if (__v_114 != __v_4) { return Err(ExecError::AssertEqMismatch { lhs: __v_114.as_canonical_u64(), rhs: __v_4.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } if (__v_120 != __v_5) { return Err(ExecError::AssertEqMismatch { lhs: __v_120.as_canonical_u64(), rhs: __v_5.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } if (__v_126 != __v_6) { return Err(ExecError::AssertEqMismatch { lhs: __v_126.as_canonical_u64(), rhs: __v_6.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } if (__v_132 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_132.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("aggr: claim preimage digest mismatch".to_string()) }); } let __ret: [G; OUT_275] = [__v_11]; record.function_queries[275].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_276: usize = 2; +const IN_276: usize = 2; +const OUT_276: usize = 1; +fn aiur_fn_276(inp: [G; IN_276], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_276], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_328] = { let __args: [G; IN_328] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(328, &__args[..])?) { [G::ZERO; OUT_328] } else { let (__hash, __hit) = record.function_queries[328].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[328].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[328].bump_multiplicity(__i); } let __ret: [G; OUT_328] = unsafe { *(record.function_queries[328].output_at(__i).as_ptr() as *const [G; OUT_328]) }; __ret }, _ => { aiur_fn_328::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __v_3: G = __r_arr[1]; let __v_4: G = __r_arr[2]; let __v_5: G = __r_arr[3]; let __v_6: G = __r_arr[4]; let __v_7: G = __r_arr[5]; let __v_8: G = __r_arr[6]; let __v_9: G = __r_arr[7]; let __v_10: G = G::from_u64(256); let __v_11: G = __v_10 * __v_3; let __v_12: G = __v_2 + __v_11; let __v_13: G = G::from_u64(65536); let __v_14: G = __v_13 * __v_4; let __v_15: G = __v_12 + __v_14; let __v_16: G = G::from_u64(16777216); let __v_17: G = __v_16 * __v_5; let __v_18: G = __v_15 + __v_17; let __v_19: G = G::from_u64(4294967296); let __v_20: G = __v_19 * __v_6; let __v_21: G = __v_18 + __v_20; let __v_22: G = G::from_u64(1099511627776); let __v_23: G = __v_22 * __v_7; let __v_24: G = __v_21 + __v_23; let __v_25: G = G::from_u64(281474976710656); let __v_26: G = __v_25 * __v_8; let __v_27: G = __v_24 + __v_26; let __v_28: G = G::from_u64(72057594037927936); let __v_29: G = __v_28 * __v_9; let __v_30: G = __v_27 + __v_29; let __ret: [G; OUT_276] = [__v_30]; record.function_queries[276].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_277: usize = 2; +const IN_277: usize = 2; +const OUT_277: usize = 8; +fn aiur_fn_277(inp: [G; IN_277], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_277], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __v_3: G = G::from_u64(1); let __v_4: G = __v_1 + __v_3; let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_0, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = G::from_u64(2); let __v_7: G = __v_1 + __v_6; let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_0, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __v_9: G = G::from_u64(3); let __v_10: G = __v_1 + __v_9; let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_0, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_u64(4); let __v_13: G = __v_1 + __v_12; let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_0, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __v_14: G = __r_arr[0]; let __v_15: G = G::from_u64(5); let __v_16: G = __v_1 + __v_15; let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_0, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = G::from_u64(6); let __v_19: G = __v_1 + __v_18; let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_0, __v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __v_20: G = __r_arr[0]; let __v_21: G = G::from_u64(7); let __v_22: G = __v_1 + __v_21; let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_0, __v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __ret: [G; OUT_277] = [__v_2, __v_5, __v_8, __v_11, __v_14, __v_17, __v_20, __v_23]; record.function_queries[277].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_278: usize = 16; +const IN_278: usize = 16; +const OUT_278: usize = 0; +fn aiur_fn_278(inp: [G; IN_278], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_278], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; if (__v_0 != __v_8) { return Err(ExecError::AssertEqMismatch { lhs: __v_0.as_canonical_u64(), rhs: __v_8.as_canonical_u64(), msg: None }); } if (__v_1 != __v_9) { return Err(ExecError::AssertEqMismatch { lhs: __v_1.as_canonical_u64(), rhs: __v_9.as_canonical_u64(), msg: None }); } if (__v_2 != __v_10) { return Err(ExecError::AssertEqMismatch { lhs: __v_2.as_canonical_u64(), rhs: __v_10.as_canonical_u64(), msg: None }); } if (__v_3 != __v_11) { return Err(ExecError::AssertEqMismatch { lhs: __v_3.as_canonical_u64(), rhs: __v_11.as_canonical_u64(), msg: None }); } if (__v_4 != __v_12) { return Err(ExecError::AssertEqMismatch { lhs: __v_4.as_canonical_u64(), rhs: __v_12.as_canonical_u64(), msg: None }); } if (__v_5 != __v_13) { return Err(ExecError::AssertEqMismatch { lhs: __v_5.as_canonical_u64(), rhs: __v_13.as_canonical_u64(), msg: None }); } if (__v_6 != __v_14) { return Err(ExecError::AssertEqMismatch { lhs: __v_6.as_canonical_u64(), rhs: __v_14.as_canonical_u64(), msg: None }); } if (__v_7 != __v_15) { return Err(ExecError::AssertEqMismatch { lhs: __v_7.as_canonical_u64(), rhs: __v_15.as_canonical_u64(), msg: None }); } let __ret: [G; OUT_278] = []; record.function_queries[278].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_279: usize = 1; +const IN_279: usize = 1; const OUT_279: usize = 1; -fn aiur_fn_279(inp: [G; IN_279], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_279], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = G::from_u64(0); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_12]; let __info = io_buffer.get_info(__v_13, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_14: G = __io_pair.0; let __v_15: G = __io_pair.1; let __v_16: G = G::from_u64(2); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_12]; let __info = io_buffer.get_info(__v_16, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_17: G = __io_pair.0; let __v_18: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_16, __v_17, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __r_arr: [G; OUT_28] = { let __args: [G; IN_28] = [__v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(28, &__args[..])?) { [G::ZERO; OUT_28] } else { let (__hash, __hit) = record.function_queries[28].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[28].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[28].bump_multiplicity(__i); } let __ret: [G; OUT_28] = unsafe { *(record.function_queries[28].output_at(__i).as_ptr() as *const [G; OUT_28]) }; __ret }, _ => { aiur_fn_28::(__args, __hash, record, io_buffer)? }, } } }; let __v_20: G = __r_arr[0]; let __v_21: G = __r_arr[1]; let __r_arr: [G; OUT_241] = { let __args: [G; IN_241] = [__v_21, __v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(241, &__args[..])?) { [G::ZERO; OUT_241] } else { let (__hash, __hit) = record.function_queries[241].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[241].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[241].bump_multiplicity(__i); } let __ret: [G; OUT_241] = unsafe { *(record.function_queries[241].output_at(__i).as_ptr() as *const [G; OUT_241]) }; __ret }, _ => { aiur_fn_241::(__args, __hash, record, io_buffer)? }, } } }; let __v_22: G = __r_arr[0]; let __v_23: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_23.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_24: G = __loaded[0]; let __v_25: G = __loaded[1]; let __v_26: G = __loaded[2]; let __v_27: G = G::from_u64(1); if (__v_24 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_24.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: None }); } if (__v_25 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_25.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: None }); } if (__v_26 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_26.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_31] = { let __args: [G; IN_31] = [__v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(31, &__args[..])?) { [G::ZERO; OUT_31] } else { let (__hash, __hit) = record.function_queries[31].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[31].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[31].bump_multiplicity(__i); } let __ret: [G; OUT_31] = unsafe { *(record.function_queries[31].output_at(__i).as_ptr() as *const [G; OUT_31]) }; __ret }, _ => { aiur_fn_31::(__args, __hash, record, io_buffer)? }, } } }; let __v_28: G = __r_arr[0]; let __v_29: G = __r_arr[1]; let __r_arr: [G; OUT_65] = { let __args: [G; IN_65] = [__v_29, __v_28]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(65, &__args[..])?) { [G::ZERO; OUT_65] } else { let (__hash, __hit) = record.function_queries[65].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[65].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[65].bump_multiplicity(__i); } let __ret: [G; OUT_65] = unsafe { *(record.function_queries[65].output_at(__i).as_ptr() as *const [G; OUT_65]) }; __ret }, _ => { aiur_fn_65::(__args, __hash, record, io_buffer)? }, } } }; let __v_30: G = __r_arr[0]; let __v_31: G = __r_arr[1]; let __r_arr: [G; OUT_31] = { let __args: [G; IN_31] = [__v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(31, &__args[..])?) { [G::ZERO; OUT_31] } else { let (__hash, __hit) = record.function_queries[31].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[31].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[31].bump_multiplicity(__i); } let __ret: [G; OUT_31] = unsafe { *(record.function_queries[31].output_at(__i).as_ptr() as *const [G; OUT_31]) }; __ret }, _ => { aiur_fn_31::(__args, __hash, record, io_buffer)? }, } } }; let __v_32: G = __r_arr[0]; let __v_33: G = __r_arr[1]; let __r_arr: [G; OUT_68] = { let __args: [G; IN_68] = [__v_33, __v_32]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(68, &__args[..])?) { [G::ZERO; OUT_68] } else { let (__hash, __hit) = record.function_queries[68].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[68].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[68].bump_multiplicity(__i); } let __ret: [G; OUT_68] = unsafe { *(record.function_queries[68].output_at(__i).as_ptr() as *const [G; OUT_68]) }; __ret }, _ => { aiur_fn_68::(__args, __hash, record, io_buffer)? }, } } }; let __v_34: G = __r_arr[0]; let __v_35: G = __r_arr[1]; let __r_arr: [G; OUT_31] = { let __args: [G; IN_31] = [__v_35]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(31, &__args[..])?) { [G::ZERO; OUT_31] } else { let (__hash, __hit) = record.function_queries[31].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[31].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[31].bump_multiplicity(__i); } let __ret: [G; OUT_31] = unsafe { *(record.function_queries[31].output_at(__i).as_ptr() as *const [G; OUT_31]) }; __ret }, _ => { aiur_fn_31::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __r_arr: [G; OUT_70] = { let __args: [G; IN_70] = [__v_37, __v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(70, &__args[..])?) { [G::ZERO; OUT_70] } else { let (__hash, __hit) = record.function_queries[70].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[70].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[70].bump_multiplicity(__i); } let __ret: [G; OUT_70] = unsafe { *(record.function_queries[70].output_at(__i).as_ptr() as *const [G; OUT_70]) }; __ret }, _ => { aiur_fn_70::(__args, __hash, record, io_buffer)? }, } } }; let __v_38: G = __r_arr[0]; let __v_39: G = __r_arr[1]; let __v_40: G = __v_14 + __v_15; if (__v_39 != __v_40) { return Err(ExecError::AssertEqMismatch { lhs: __v_39.as_canonical_u64(), rhs: __v_40.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_238] = { let __args: [G; IN_238] = [__v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_30, __v_34, __v_38]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(238, &__args[..])?) { [G::ZERO; OUT_238] } else { let (__hash, __hit) = record.function_queries[238].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[238].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[238].bump_multiplicity(__i); } let __ret: [G; OUT_238] = unsafe { *(record.function_queries[238].output_at(__i).as_ptr() as *const [G; OUT_238]) }; __ret }, _ => { aiur_fn_238::(__args, __hash, record, io_buffer)? }, } } }; let __v_41: G = __r_arr[0]; let __r_arr: [G; OUT_223] = { let __args: [G; IN_223] = [__v_41, __v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(223, &__args[..])?) { [G::ZERO; OUT_223] } else { let (__hash, __hit) = record.function_queries[223].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[223].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[223].bump_multiplicity(__i); } let __ret: [G; OUT_223] = unsafe { *(record.function_queries[223].output_at(__i).as_ptr() as *const [G; OUT_223]) }; __ret }, _ => { aiur_fn_223::(__args, __hash, record, io_buffer)? }, } } }; let __v_42: G = __r_arr[0]; if (__v_42 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_42.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: None }); } if (__v_27 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_27.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: None }); } let __ret: [G; OUT_279] = [__v_22]; record.function_queries[279].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_280: usize = 28; -const IN_280: usize = 28; -const OUT_280: usize = 8; -fn aiur_fn_280(inp: [G; IN_280], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_280], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __r_arr: [G; OUT_278] = { let __args: [G; IN_278] = [__v_0, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(278, &__args[..])?) { [G::ZERO; OUT_278] } else { let (__hash, __hit) = record.function_queries[278].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[278].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[278].bump_multiplicity(__i); } let __ret: [G; OUT_278] = unsafe { *(record.function_queries[278].output_at(__i).as_ptr() as *const [G; OUT_278]) }; __ret }, _ => { aiur_fn_278::(__args, __hash, record, io_buffer)? }, } } }; let __v_28: G = __r_arr[0]; let __v_29: G = __r_arr[1]; let __v_30: G = __r_arr[2]; let __v_31: G = __r_arr[3]; let __v_32: G = __r_arr[4]; let __v_33: G = __r_arr[5]; let __v_34: G = __r_arr[6]; let __v_35: G = __r_arr[7]; let __v_36: G = __r_arr[8]; let __v_37: G = __r_arr[9]; let __v_38: G = __r_arr[10]; let __v_39: G = __r_arr[11]; let __r_arr: [G; OUT_279] = { let __args: [G; IN_279] = [__v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(279, &__args[..])?) { [G::ZERO; OUT_279] } else { let (__hash, __hit) = record.function_queries[279].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[279].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[279].bump_multiplicity(__i); } let __ret: [G; OUT_279] = unsafe { *(record.function_queries[279].output_at(__i).as_ptr() as *const [G; OUT_279]) }; __ret }, _ => { aiur_fn_279::(__args, __hash, record, io_buffer)? }, } } }; let __v_40: G = __r_arr[0]; let __r_arr: [G; OUT_277] = { let __args: [G; IN_277] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(277, &__args[..])?) { [G::ZERO; OUT_277] } else { let (__hash, __hit) = record.function_queries[277].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[277].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[277].bump_multiplicity(__i); } let __ret: [G; OUT_277] = unsafe { *(record.function_queries[277].output_at(__i).as_ptr() as *const [G; OUT_277]) }; __ret }, _ => { aiur_fn_277::(__args, __hash, record, io_buffer)? }, } } }; let __v_41: G = __r_arr[0]; let __v_42: G = G::from_u64(0); let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_41, __v_42]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __v_43: G = __r_arr[0]; if (__v_43 != __v_42) { return Err(ExecError::AssertEqMismatch { lhs: __v_43.as_canonical_u64(), rhs: __v_42.as_canonical_u64(), msg: Some("aggr: child has the wrong claim channel".to_string()) }); } match __v_0.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_317] = { let __args: [G; IN_317] = [__v_41]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(317, &__args[..])?) { [G::ZERO; OUT_317] } else { let (__hash, __hit) = record.function_queries[317].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[317].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[317].bump_multiplicity(__i); } let __ret: [G; OUT_317] = unsafe { *(record.function_queries[317].output_at(__i).as_ptr() as *const [G; OUT_317]) }; __ret }, _ => { aiur_fn_317::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; let __v_45: G = G::from_u64(10); if (__v_44 != __v_45) { return Err(ExecError::AssertEqMismatch { lhs: __v_44.as_canonical_u64(), rhs: __v_45.as_canonical_u64(), msg: Some("aggr: IxVM child must expose a 10-word verify_claim claim".to_string()) }); } let __v_46: G = G::from_u64(1); let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_41, __v_46]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __v_47: G = __r_arr[0]; if (__v_47 != __v_18) { return Err(ExecError::AssertEqMismatch { lhs: __v_47.as_canonical_u64(), rhs: __v_18.as_canonical_u64(), msg: Some("aggr: IxVM child has the wrong entrypoint".to_string()) }); } let __v_48: G = G::from_u64(2); let __r_arr: [G; OUT_275] = { let __args: [G; IN_275] = [__v_41, __v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(275, &__args[..])?) { [G::ZERO; OUT_275] } else { let (__hash, __hit) = record.function_queries[275].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[275].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[275].bump_multiplicity(__i); } let __ret: [G; OUT_275] = unsafe { *(record.function_queries[275].output_at(__i).as_ptr() as *const [G; OUT_275]) }; __ret }, _ => { aiur_fn_275::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __v_50: G = __r_arr[1]; let __v_51: G = __r_arr[2]; let __v_52: G = __r_arr[3]; let __v_53: G = __r_arr[4]; let __v_54: G = __r_arr[5]; let __v_55: G = __r_arr[6]; let __v_56: G = __r_arr[7]; let __ret: [G; OUT_280] = [__v_49, __v_50, __v_51, __v_52, __v_53, __v_54, __v_55, __v_56]; record.function_queries[280].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_317] = { let __args: [G; IN_317] = [__v_41]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(317, &__args[..])?) { [G::ZERO; OUT_317] } else { let (__hash, __hit) = record.function_queries[317].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[317].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[317].bump_multiplicity(__i); } let __ret: [G; OUT_317] = unsafe { *(record.function_queries[317].output_at(__i).as_ptr() as *const [G; OUT_317]) }; __ret }, _ => { aiur_fn_317::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; let __v_45: G = G::from_u64(18); if (__v_44 != __v_45) { return Err(ExecError::AssertEqMismatch { lhs: __v_44.as_canonical_u64(), rhs: __v_45.as_canonical_u64(), msg: Some("aggr: recursive child must expose an 18-word ix_aggr claim".to_string()) }); } let __v_46: G = G::from_u64(1); let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_41, __v_46]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __v_47: G = __r_arr[0]; if (__v_47 != __v_19) { return Err(ExecError::AssertEqMismatch { lhs: __v_47.as_canonical_u64(), rhs: __v_19.as_canonical_u64(), msg: Some("aggr: recursive child has the wrong entrypoint".to_string()) }); } let __v_48: G = G::from_u64(2); let __r_arr: [G; OUT_275] = { let __args: [G; IN_275] = [__v_41, __v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(275, &__args[..])?) { [G::ZERO; OUT_275] } else { let (__hash, __hit) = record.function_queries[275].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[275].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[275].bump_multiplicity(__i); } let __ret: [G; OUT_275] = unsafe { *(record.function_queries[275].output_at(__i).as_ptr() as *const [G; OUT_275]) }; __ret }, _ => { aiur_fn_275::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __v_50: G = __r_arr[1]; let __v_51: G = __r_arr[2]; let __v_52: G = __r_arr[3]; let __v_53: G = __r_arr[4]; let __v_54: G = __r_arr[5]; let __v_55: G = __r_arr[6]; let __v_56: G = __r_arr[7]; let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_49, __v_50, __v_51, __v_52, __v_53, __v_54, __v_55, __v_56, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __v_57: G = G::from_u64(10); let __r_arr: [G; OUT_275] = { let __args: [G; IN_275] = [__v_41, __v_57]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(275, &__args[..])?) { [G::ZERO; OUT_275] } else { let (__hash, __hit) = record.function_queries[275].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[275].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[275].bump_multiplicity(__i); } let __ret: [G; OUT_275] = unsafe { *(record.function_queries[275].output_at(__i).as_ptr() as *const [G; OUT_275]) }; __ret }, _ => { aiur_fn_275::(__args, __hash, record, io_buffer)? }, } } }; let __v_58: G = __r_arr[0]; let __v_59: G = __r_arr[1]; let __v_60: G = __r_arr[2]; let __v_61: G = __r_arr[3]; let __v_62: G = __r_arr[4]; let __v_63: G = __r_arr[5]; let __v_64: G = __r_arr[6]; let __v_65: G = __r_arr[7]; let __ret: [G; OUT_280] = [__v_58, __v_59, __v_60, __v_61, __v_62, __v_63, __v_64, __v_65]; record.function_queries[280].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } -const INPUT_SIZE_281: usize = 35; -const IN_281: usize = 35; -const OUT_281: usize = 0; -fn aiur_fn_281(inp: [G; IN_281], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_281], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = inp[34]; let __v_35: G = G::from_u64(0); let __r_arr: [G; OUT_280] = { let __args: [G; IN_280] = [__v_0, __v_35, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(280, &__args[..])?) { [G::ZERO; OUT_280] } else { let (__hash, __hit) = record.function_queries[280].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[280].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[280].bump_multiplicity(__i); } let __ret: [G; OUT_280] = unsafe { *(record.function_queries[280].output_at(__i).as_ptr() as *const [G; OUT_280]) }; __ret }, _ => { aiur_fn_280::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __v_38: G = __r_arr[2]; let __v_39: G = __r_arr[3]; let __v_40: G = __r_arr[4]; let __v_41: G = __r_arr[5]; let __v_42: G = __r_arr[6]; let __v_43: G = __r_arr[7]; let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_281] = []; record.function_queries[281].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_282: usize = 36; -const IN_282: usize = 36; -const OUT_282: usize = 0; -fn aiur_fn_282(inp: [G; IN_282], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_282], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = inp[34]; let __v_35: G = inp[35]; let __v_36: G = G::from_u64(0); let __r_arr: [G; OUT_280] = { let __args: [G; IN_280] = [__v_0, __v_36, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(280, &__args[..])?) { [G::ZERO; OUT_280] } else { let (__hash, __hit) = record.function_queries[280].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[280].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[280].bump_multiplicity(__i); } let __ret: [G; OUT_280] = unsafe { *(record.function_queries[280].output_at(__i).as_ptr() as *const [G; OUT_280]) }; __ret }, _ => { aiur_fn_280::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = __r_arr[1]; let __v_39: G = __r_arr[2]; let __v_40: G = __r_arr[3]; let __v_41: G = __r_arr[4]; let __v_42: G = __r_arr[5]; let __v_43: G = __r_arr[6]; let __v_44: G = __r_arr[7]; let __v_45: G = G::from_u64(1); let __r_arr: [G; OUT_280] = { let __args: [G; IN_280] = [__v_1, __v_45, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(280, &__args[..])?) { [G::ZERO; OUT_280] } else { let (__hash, __hit) = record.function_queries[280].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[280].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[280].bump_multiplicity(__i); } let __ret: [G; OUT_280] = unsafe { *(record.function_queries[280].output_at(__i).as_ptr() as *const [G; OUT_280]) }; __ret }, _ => { aiur_fn_280::(__args, __hash, record, io_buffer)? }, } } }; let __v_46: G = __r_arr[0]; let __v_47: G = __r_arr[1]; let __v_48: G = __r_arr[2]; let __v_49: G = __r_arr[3]; let __v_50: G = __r_arr[4]; let __v_51: G = __r_arr[5]; let __v_52: G = __r_arr[6]; let __v_53: G = __r_arr[7]; let __r_arr: [G; OUT_273] = { let __args: [G; IN_273] = [__v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(273, &__args[..])?) { [G::ZERO; OUT_273] } else { let (__hash, __hit) = record.function_queries[273].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[273].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[273].bump_multiplicity(__i); } let __ret: [G; OUT_273] = unsafe { *(record.function_queries[273].output_at(__i).as_ptr() as *const [G; OUT_273]) }; __ret }, _ => { aiur_fn_273::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_54]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __v_55: G = __r_arr[0]; let __v_56: G = __r_arr[1]; let __v_57: G = __r_arr[2]; let __v_58: G = __r_arr[3]; let __v_59: G = __r_arr[4]; let __v_60: G = __r_arr[5]; let __v_61: G = __r_arr[6]; let __v_62: G = __r_arr[7]; let __r_arr: [G; OUT_273] = { let __args: [G; IN_273] = [__v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(273, &__args[..])?) { [G::ZERO; OUT_273] } else { let (__hash, __hit) = record.function_queries[273].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[273].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[273].bump_multiplicity(__i); } let __ret: [G; OUT_273] = unsafe { *(record.function_queries[273].output_at(__i).as_ptr() as *const [G; OUT_273]) }; __ret }, _ => { aiur_fn_273::(__args, __hash, record, io_buffer)? }, } } }; let __v_63: G = __r_arr[0]; let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_63]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __v_64: G = __r_arr[0]; let __v_65: G = __r_arr[1]; let __v_66: G = __r_arr[2]; let __v_67: G = __r_arr[3]; let __v_68: G = __r_arr[4]; let __v_69: G = __r_arr[5]; let __v_70: G = __r_arr[6]; let __v_71: G = __r_arr[7]; let __v_72: G = G::from_u64(2); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_72]; let __info = io_buffer.get_info(__v_72, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_73: G = __io_pair.0; let __v_74: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_72, __v_73, __v_74]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_75: G = __r_arr[0]; let __v_76: G = G::from_u64(103); let __v_77: G = G::from_u64(230); let __v_78: G = G::from_u64(9); let __v_79: G = G::from_u64(106); let __v_80: G = G::from_u64(133); let __v_81: G = G::from_u64(174); let __v_82: G = G::from_u64(187); let __v_83: G = G::from_u64(114); let __v_84: G = G::from_u64(243); let __v_85: G = G::from_u64(110); let __v_86: G = G::from_u64(60); let __v_87: G = G::from_u64(58); let __v_88: G = G::from_u64(245); let __v_89: G = G::from_u64(79); let __v_90: G = G::from_u64(165); let __v_91: G = G::from_u64(127); let __v_92: G = G::from_u64(82); let __v_93: G = G::from_u64(14); let __v_94: G = G::from_u64(81); let __v_95: G = G::from_u64(140); let __v_96: G = G::from_u64(104); let __v_97: G = G::from_u64(5); let __v_98: G = G::from_u64(155); let __v_99: G = G::from_u64(171); let __v_100: G = G::from_u64(217); let __v_101: G = G::from_u64(131); let __v_102: G = G::from_u64(31); let __v_103: G = G::from_u64(25); let __v_104: G = G::from_u64(205); let __v_105: G = G::from_u64(224); let __v_106: G = G::from_u64(91); let __v_107: G = { let __values: [G; 3] = [__v_45, __v_45, __v_45]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_108: G = { let __values: [G; 8] = [__v_36, __v_36, __v_36, __v_36, __v_36, __v_36, __v_36, __v_36]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_109: G = { let __values: [G; 32] = [__v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_76, __v_82, __v_83, __v_84, __v_85, __v_86, __v_87, __v_88, __v_89, __v_90, __v_91, __v_92, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_99, __v_100, __v_101, __v_102, __v_103, __v_104, __v_105, __v_106]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_110: G = { let __values: [G; 34] = [__v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_75, __v_107, __v_36, __v_36, __v_108, __v_109, __v_110]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_111: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_111]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_112: G = __r_arr[0]; let __v_113: G = __r_arr[1]; let __v_114: G = __r_arr[2]; let __v_115: G = __r_arr[3]; let __v_116: G = __r_arr[4]; let __v_117: G = __r_arr[5]; let __v_118: G = __r_arr[6]; let __v_119: G = __r_arr[7]; let __v_120: G = __r_arr[8]; let __v_121: G = __r_arr[9]; let __v_122: G = __r_arr[10]; let __v_123: G = __r_arr[11]; let __v_124: G = __r_arr[12]; let __v_125: G = __r_arr[13]; let __v_126: G = __r_arr[14]; let __v_127: G = __r_arr[15]; let __v_128: G = __r_arr[16]; let __v_129: G = __r_arr[17]; let __v_130: G = __r_arr[18]; let __v_131: G = __r_arr[19]; let __v_132: G = __r_arr[20]; let __v_133: G = __r_arr[21]; let __v_134: G = __r_arr[22]; let __v_135: G = __r_arr[23]; let __v_136: G = __r_arr[24]; let __v_137: G = __r_arr[25]; let __v_138: G = __r_arr[26]; let __v_139: G = __r_arr[27]; let __v_140: G = __r_arr[28]; let __v_141: G = __r_arr[29]; let __v_142: G = __r_arr[30]; let __v_143: G = __r_arr[31]; let __v_144: G = G::from_u64(256); let __v_145: G = __v_144 * __v_113; let __v_146: G = __v_112 + __v_145; let __v_147: G = G::from_u64(65536); let __v_148: G = __v_147 * __v_114; let __v_149: G = __v_146 + __v_148; let __v_150: G = G::from_u64(16777216); let __v_151: G = __v_150 * __v_115; let __v_152: G = __v_149 + __v_151; let __v_153: G = __v_144 * __v_117; let __v_154: G = __v_116 + __v_153; let __v_155: G = __v_147 * __v_118; let __v_156: G = __v_154 + __v_155; let __v_157: G = __v_150 * __v_119; let __v_158: G = __v_156 + __v_157; let __v_159: G = __v_144 * __v_121; let __v_160: G = __v_120 + __v_159; let __v_161: G = __v_147 * __v_122; let __v_162: G = __v_160 + __v_161; let __v_163: G = __v_150 * __v_123; let __v_164: G = __v_162 + __v_163; let __v_165: G = __v_144 * __v_125; let __v_166: G = __v_124 + __v_165; let __v_167: G = __v_147 * __v_126; let __v_168: G = __v_166 + __v_167; let __v_169: G = __v_150 * __v_127; let __v_170: G = __v_168 + __v_169; let __v_171: G = __v_144 * __v_129; let __v_172: G = __v_128 + __v_171; let __v_173: G = __v_147 * __v_130; let __v_174: G = __v_172 + __v_173; let __v_175: G = __v_150 * __v_131; let __v_176: G = __v_174 + __v_175; let __v_177: G = __v_144 * __v_133; let __v_178: G = __v_132 + __v_177; let __v_179: G = __v_147 * __v_134; let __v_180: G = __v_178 + __v_179; let __v_181: G = __v_150 * __v_135; let __v_182: G = __v_180 + __v_181; let __v_183: G = __v_144 * __v_137; let __v_184: G = __v_136 + __v_183; let __v_185: G = __v_147 * __v_138; let __v_186: G = __v_184 + __v_185; let __v_187: G = __v_150 * __v_139; let __v_188: G = __v_186 + __v_187; let __v_189: G = __v_144 * __v_141; let __v_190: G = __v_140 + __v_189; let __v_191: G = __v_147 * __v_142; let __v_192: G = __v_190 + __v_191; let __v_193: G = __v_150 * __v_143; let __v_194: G = __v_192 + __v_193; if (__v_152 != __v_28) { return Err(ExecError::AssertEqMismatch { lhs: __v_152.as_canonical_u64(), rhs: __v_28.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } if (__v_158 != __v_29) { return Err(ExecError::AssertEqMismatch { lhs: __v_158.as_canonical_u64(), rhs: __v_29.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } if (__v_164 != __v_30) { return Err(ExecError::AssertEqMismatch { lhs: __v_164.as_canonical_u64(), rhs: __v_30.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } if (__v_170 != __v_31) { return Err(ExecError::AssertEqMismatch { lhs: __v_170.as_canonical_u64(), rhs: __v_31.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } if (__v_176 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_176.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } if (__v_182 != __v_33) { return Err(ExecError::AssertEqMismatch { lhs: __v_182.as_canonical_u64(), rhs: __v_33.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } if (__v_188 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_188.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } if (__v_194 != __v_35) { return Err(ExecError::AssertEqMismatch { lhs: __v_194.as_canonical_u64(), rhs: __v_35.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_75]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __v_195: G = __r_arr[0]; let __v_196: G = __r_arr[1]; let __v_197: G = __r_arr[2]; let __v_198: G = __r_arr[3]; let __v_199: G = __r_arr[4]; let __v_200: G = __r_arr[5]; let __v_201: G = __r_arr[6]; let __v_202: G = __r_arr[7]; let __r_arr: [G; OUT_272] = { let __args: [G; IN_272] = [__v_55, __v_56, __v_57, __v_58, __v_59, __v_64, __v_65, __v_66, __v_67, __v_68]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(272, &__args[..])?) { [G::ZERO; OUT_272] } else { let (__hash, __hit) = record.function_queries[272].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[272].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[272].bump_multiplicity(__i); } let __ret: [G; OUT_272] = unsafe { *(record.function_queries[272].output_at(__i).as_ptr() as *const [G; OUT_272]) }; __ret }, _ => { aiur_fn_272::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_272] = { let __args: [G; IN_272] = [__v_55, __v_56, __v_57, __v_58, __v_59, __v_195, __v_196, __v_197, __v_198, __v_199]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(272, &__args[..])?) { [G::ZERO; OUT_272] } else { let (__hash, __hit) = record.function_queries[272].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[272].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[272].bump_multiplicity(__i); } let __ret: [G; OUT_272] = unsafe { *(record.function_queries[272].output_at(__i).as_ptr() as *const [G; OUT_272]) }; __ret }, _ => { aiur_fn_272::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_257] = { let __args: [G; IN_257] = [__v_60]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(257, &__args[..])?) { [G::ZERO; OUT_257] } else { let (__hash, __hit) = record.function_queries[257].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[257].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[257].bump_multiplicity(__i); } let __ret: [G; OUT_257] = unsafe { *(record.function_queries[257].output_at(__i).as_ptr() as *const [G; OUT_257]) }; __ret }, _ => { aiur_fn_257::(__args, __hash, record, io_buffer)? }, } } }; let __v_203: G = __r_arr[0]; let __r_arr: [G; OUT_257] = { let __args: [G; IN_257] = [__v_69]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(257, &__args[..])?) { [G::ZERO; OUT_257] } else { let (__hash, __hit) = record.function_queries[257].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[257].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[257].bump_multiplicity(__i); } let __ret: [G; OUT_257] = unsafe { *(record.function_queries[257].output_at(__i).as_ptr() as *const [G; OUT_257]) }; __ret }, _ => { aiur_fn_257::(__args, __hash, record, io_buffer)? }, } } }; let __v_204: G = __r_arr[0]; let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_61, __v_62]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_205: G = __r_arr[0]; let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_70, __v_71]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_206: G = __r_arr[0]; let __r_arr: [G; OUT_257] = { let __args: [G; IN_257] = [__v_200]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(257, &__args[..])?) { [G::ZERO; OUT_257] } else { let (__hash, __hit) = record.function_queries[257].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[257].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[257].bump_multiplicity(__i); } let __ret: [G; OUT_257] = unsafe { *(record.function_queries[257].output_at(__i).as_ptr() as *const [G; OUT_257]) }; __ret }, _ => { aiur_fn_257::(__args, __hash, record, io_buffer)? }, } } }; let __v_207: G = __r_arr[0]; let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_201, __v_202]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_208: G = __r_arr[0]; let __r_arr: [G; OUT_260] = { let __args: [G; IN_260] = [__v_203, __v_204, __v_207]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(260, &__args[..])?) { [G::ZERO; OUT_260] } else { let (__hash, __hit) = record.function_queries[260].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[260].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[260].bump_multiplicity(__i); } let __ret: [G; OUT_260] = unsafe { *(record.function_queries[260].output_at(__i).as_ptr() as *const [G; OUT_260]) }; __ret }, _ => { aiur_fn_260::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_263] = { let __args: [G; IN_263] = [__v_205, __v_206, __v_207, __v_208]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(263, &__args[..])?) { [G::ZERO; OUT_263] } else { let (__hash, __hit) = record.function_queries[263].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[263].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[263].bump_multiplicity(__i); } let __ret: [G; OUT_263] = unsafe { *(record.function_queries[263].output_at(__i).as_ptr() as *const [G; OUT_263]) }; __ret }, _ => { aiur_fn_263::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_282] = []; record.function_queries[282].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_283: usize = 36; -const IN_283: usize = 36; +fn aiur_fn_279(inp: [G; IN_279], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_279], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; match __v_1.as_canonical_u64() { 0u64 => { let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_3.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_4: G = __loaded[0]; let __v_5: G = __loaded[1]; let __v_6: G = __loaded[2]; let __v_7: G = G::from_u64(1); if (__v_4 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_4.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("aggr: child proof must expose exactly one claim".to_string()) }); } if (__v_5 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_5.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("aggr: child proof must expose exactly one claim".to_string()) }); } if (__v_6 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_6.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("aggr: child proof must expose exactly one claim".to_string()) }); } let __ret: [G; OUT_279] = [__v_2]; record.function_queries[279].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } +const INPUT_SIZE_280: usize = 17; +const IN_280: usize = 17; +const OUT_280: usize = 12; +fn aiur_fn_280(inp: [G; IN_280], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_280], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = G::from_u64(1); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_0]; let __info = io_buffer.get_info(__v_17, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_18: G = __io_pair.0; let __v_19: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_17, __v_18, __v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_20: G = __r_arr[0]; let __v_21: G = G::from_u64(103); let __v_22: G = G::from_u64(230); let __v_23: G = G::from_u64(9); let __v_24: G = G::from_u64(106); let __v_25: G = G::from_u64(133); let __v_26: G = G::from_u64(174); let __v_27: G = G::from_u64(187); let __v_28: G = G::from_u64(114); let __v_29: G = G::from_u64(243); let __v_30: G = G::from_u64(110); let __v_31: G = G::from_u64(60); let __v_32: G = G::from_u64(58); let __v_33: G = G::from_u64(245); let __v_34: G = G::from_u64(79); let __v_35: G = G::from_u64(165); let __v_36: G = G::from_u64(127); let __v_37: G = G::from_u64(82); let __v_38: G = G::from_u64(14); let __v_39: G = G::from_u64(81); let __v_40: G = G::from_u64(140); let __v_41: G = G::from_u64(104); let __v_42: G = G::from_u64(5); let __v_43: G = G::from_u64(155); let __v_44: G = G::from_u64(171); let __v_45: G = G::from_u64(217); let __v_46: G = G::from_u64(131); let __v_47: G = G::from_u64(31); let __v_48: G = G::from_u64(25); let __v_49: G = G::from_u64(205); let __v_50: G = G::from_u64(224); let __v_51: G = G::from_u64(91); let __v_52: G = { let __values: [G; 3] = [__v_17, __v_17, __v_17]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_53: G = G::from_u64(0); let __v_54: G = { let __values: [G; 8] = [__v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_55: G = { let __values: [G; 32] = [__v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_21, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_56: G = { let __values: [G; 34] = [__v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_20, __v_52, __v_53, __v_53, __v_54, __v_55, __v_56]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_57: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_57]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_58: G = __r_arr[0]; let __v_59: G = __r_arr[1]; let __v_60: G = __r_arr[2]; let __v_61: G = __r_arr[3]; let __v_62: G = __r_arr[4]; let __v_63: G = __r_arr[5]; let __v_64: G = __r_arr[6]; let __v_65: G = __r_arr[7]; let __v_66: G = __r_arr[8]; let __v_67: G = __r_arr[9]; let __v_68: G = __r_arr[10]; let __v_69: G = __r_arr[11]; let __v_70: G = __r_arr[12]; let __v_71: G = __r_arr[13]; let __v_72: G = __r_arr[14]; let __v_73: G = __r_arr[15]; let __v_74: G = __r_arr[16]; let __v_75: G = __r_arr[17]; let __v_76: G = __r_arr[18]; let __v_77: G = __r_arr[19]; let __v_78: G = __r_arr[20]; let __v_79: G = __r_arr[21]; let __v_80: G = __r_arr[22]; let __v_81: G = __r_arr[23]; let __v_82: G = __r_arr[24]; let __v_83: G = __r_arr[25]; let __v_84: G = __r_arr[26]; let __v_85: G = __r_arr[27]; let __v_86: G = __r_arr[28]; let __v_87: G = __r_arr[29]; let __v_88: G = __r_arr[30]; let __v_89: G = __r_arr[31]; let __v_90: G = G::from_u64(256); let __v_91: G = __v_90 * __v_59; let __v_92: G = __v_58 + __v_91; let __v_93: G = G::from_u64(65536); let __v_94: G = __v_93 * __v_60; let __v_95: G = __v_92 + __v_94; let __v_96: G = G::from_u64(16777216); let __v_97: G = __v_96 * __v_61; let __v_98: G = __v_95 + __v_97; let __v_99: G = __v_90 * __v_63; let __v_100: G = __v_62 + __v_99; let __v_101: G = __v_93 * __v_64; let __v_102: G = __v_100 + __v_101; let __v_103: G = __v_96 * __v_65; let __v_104: G = __v_102 + __v_103; let __v_105: G = __v_90 * __v_67; let __v_106: G = __v_66 + __v_105; let __v_107: G = __v_93 * __v_68; let __v_108: G = __v_106 + __v_107; let __v_109: G = __v_96 * __v_69; let __v_110: G = __v_108 + __v_109; let __v_111: G = __v_90 * __v_71; let __v_112: G = __v_70 + __v_111; let __v_113: G = __v_93 * __v_72; let __v_114: G = __v_112 + __v_113; let __v_115: G = __v_96 * __v_73; let __v_116: G = __v_114 + __v_115; let __v_117: G = __v_90 * __v_75; let __v_118: G = __v_74 + __v_117; let __v_119: G = __v_93 * __v_76; let __v_120: G = __v_118 + __v_119; let __v_121: G = __v_96 * __v_77; let __v_122: G = __v_120 + __v_121; let __v_123: G = __v_90 * __v_79; let __v_124: G = __v_78 + __v_123; let __v_125: G = __v_93 * __v_80; let __v_126: G = __v_124 + __v_125; let __v_127: G = __v_96 * __v_81; let __v_128: G = __v_126 + __v_127; let __v_129: G = __v_90 * __v_83; let __v_130: G = __v_82 + __v_129; let __v_131: G = __v_93 * __v_84; let __v_132: G = __v_130 + __v_131; let __v_133: G = __v_96 * __v_85; let __v_134: G = __v_132 + __v_133; let __v_135: G = __v_90 * __v_87; let __v_136: G = __v_86 + __v_135; let __v_137: G = __v_93 * __v_88; let __v_138: G = __v_136 + __v_137; let __v_139: G = __v_96 * __v_89; let __v_140: G = __v_138 + __v_139; let __mc_out___mc_0: [G; 0] = '__mc_0: { match __v_0.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_278] = { let __args: [G; IN_278] = [__v_98, __v_104, __v_110, __v_116, __v_122, __v_128, __v_134, __v_140, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(278, &__args[..])?) { [G::ZERO; OUT_278] } else { let (__hash, __hit) = record.function_queries[278].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[278].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[278].bump_multiplicity(__i); } let __ret: [G; OUT_278] = unsafe { *(record.function_queries[278].output_at(__i).as_ptr() as *const [G; OUT_278]) }; __ret }, _ => { aiur_fn_278::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_0 []; }, _ => { let __r_arr: [G; OUT_278] = { let __args: [G; IN_278] = [__v_98, __v_104, __v_110, __v_116, __v_122, __v_128, __v_134, __v_140, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(278, &__args[..])?) { [G::ZERO; OUT_278] } else { let (__hash, __hit) = record.function_queries[278].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[278].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[278].bump_multiplicity(__i); } let __ret: [G; OUT_278] = unsafe { *(record.function_queries[278].output_at(__i).as_ptr() as *const [G; OUT_278]) }; __ret }, _ => { aiur_fn_278::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_0 []; }, } }; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_141: G = __r_arr[0]; let __v_142: G = __r_arr[1]; let __v_143: G = __r_arr[2]; let __v_144: G = __r_arr[3]; let __v_145: G = __r_arr[4]; let __v_146: G = __r_arr[5]; let __v_147: G = __r_arr[6]; let __v_148: G = __r_arr[7]; let __v_149: G = __r_arr[8]; let __v_150: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_150]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_151: G = __r_arr[0]; let __v_152: G = __r_arr[1]; let __v_153: G = __r_arr[2]; let __v_154: G = __r_arr[3]; let __v_155: G = __r_arr[4]; let __v_156: G = __r_arr[5]; let __v_157: G = __r_arr[6]; let __v_158: G = __r_arr[7]; let __v_159: G = __r_arr[8]; let __v_160: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_160]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_161: G = __r_arr[0]; let __v_162: G = __r_arr[1]; let __v_163: G = __r_arr[2]; let __v_164: G = __r_arr[3]; let __v_165: G = __r_arr[4]; let __v_166: G = __r_arr[5]; let __v_167: G = __r_arr[6]; let __v_168: G = __r_arr[7]; let __v_169: G = __r_arr[8]; let __v_170: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_170]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_171: G = __r_arr[0]; let __v_172: G = __r_arr[1]; let __v_173: G = __r_arr[2]; let __v_174: G = __r_arr[3]; let __v_175: G = __r_arr[4]; let __v_176: G = __r_arr[5]; let __v_177: G = __r_arr[6]; let __v_178: G = __r_arr[7]; let __v_179: G = __r_arr[8]; let __v_180: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_180]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_181: G = __r_arr[0]; let __v_182: G = __r_arr[1]; let __v_183: G = __r_arr[2]; let __v_184: G = __r_arr[3]; let __v_185: G = __r_arr[4]; let __v_186: G = __r_arr[5]; let __v_187: G = __r_arr[6]; let __v_188: G = __r_arr[7]; let __v_189: G = __r_arr[8]; let __v_190: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_190]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_191: G = __r_arr[0]; let __v_192: G = __r_arr[1]; let __v_193: G = __r_arr[2]; let __v_194: G = __r_arr[3]; let __v_195: G = __r_arr[4]; let __v_196: G = __r_arr[5]; let __v_197: G = __r_arr[6]; let __v_198: G = __r_arr[7]; let __v_199: G = __r_arr[8]; let __v_200: G = __r_arr[9]; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_200]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_201: G = __r_arr[0]; let __v_202: G = __r_arr[1]; let __v_203: G = __r_arr[2]; let __v_204: G = __r_arr[3]; let __v_205: G = __r_arr[4]; let __v_206: G = __r_arr[5]; let __v_207: G = __r_arr[6]; let __v_208: G = __r_arr[7]; let __v_209: G = __r_arr[8]; let __v_210: G = __r_arr[9]; let __v_211: G = G::from_u64(0); let __v_212: G = G::from_u64(1); let __v_213: G = { let __values: [G; 10] = [__v_212, __v_212, __v_212, __v_212, __v_212, __v_212, __v_212, __v_212, __v_212, __v_212]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_214: G = { let __values: [G; 10] = [__v_211, __v_202, __v_203, __v_204, __v_205, __v_206, __v_207, __v_208, __v_209, __v_213]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_215: G = { let __values: [G; 10] = [__v_211, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_199, __v_214]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_216: G = { let __values: [G; 10] = [__v_211, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_215]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_217: G = { let __values: [G; 10] = [__v_211, __v_172, __v_173, __v_174, __v_175, __v_176, __v_177, __v_178, __v_179, __v_216]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_218: G = { let __values: [G; 10] = [__v_211, __v_162, __v_163, __v_164, __v_165, __v_166, __v_167, __v_168, __v_169, __v_217]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_219: G = { let __values: [G; 10] = [__v_211, __v_152, __v_153, __v_154, __v_155, __v_156, __v_157, __v_158, __v_159, __v_218]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_220: G = { let __values: [G; 10] = [__v_211, __v_142, __v_143, __v_144, __v_145, __v_146, __v_147, __v_148, __v_149, __v_219]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_93] = { let __args: [G; IN_93] = [__v_210]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(93, &__args[..])?) { [G::ZERO; OUT_93] } else { let (__hash, __hit) = record.function_queries[93].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[93].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[93].bump_multiplicity(__i); } let __ret: [G; OUT_93] = unsafe { *(record.function_queries[93].output_at(__i).as_ptr() as *const [G; OUT_93]) }; __ret }, _ => { aiur_fn_93::(__args, __hash, record, io_buffer)? }, } } }; let __v_221: G = __r_arr[0]; let __v_222: G = __r_arr[1]; let __v_223: G = __r_arr[2]; let __v_224: G = __r_arr[3]; let __v_225: G = __r_arr[4]; let __v_226: G = __r_arr[5]; let __v_227: G = __r_arr[6]; let __v_228: G = __r_arr[7]; let __v_229: G = __r_arr[8]; let __v_230: G = __r_arr[9]; let __r_arr: [G; OUT_106] = { let __args: [G; IN_106] = [__v_230, __v_221]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(106, &__args[..])?) { [G::ZERO; OUT_106] } else { let (__hash, __hit) = record.function_queries[106].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[106].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[106].bump_multiplicity(__i); } let __ret: [G; OUT_106] = unsafe { *(record.function_queries[106].output_at(__i).as_ptr() as *const [G; OUT_106]) }; __ret }, _ => { aiur_fn_106::(__args, __hash, record, io_buffer)? }, } } }; let __v_231: G = __r_arr[0]; let __v_232: G = __r_arr[1]; let __v_233: G = __r_arr[2]; let __r_arr: [G; OUT_107] = { let __args: [G; IN_107] = [__v_233]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(107, &__args[..])?) { [G::ZERO; OUT_107] } else { let (__hash, __hit) = record.function_queries[107].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[107].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[107].bump_multiplicity(__i); } let __ret: [G; OUT_107] = unsafe { *(record.function_queries[107].output_at(__i).as_ptr() as *const [G; OUT_107]) }; __ret }, _ => { aiur_fn_107::(__args, __hash, record, io_buffer)? }, } } }; let __v_234: G = __r_arr[0]; let __v_235: G = __r_arr[1]; let __v_236: G = __r_arr[2]; let __r_arr: [G; OUT_108] = { let __args: [G; IN_108] = [__v_236, __v_221]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(108, &__args[..])?) { [G::ZERO; OUT_108] } else { let (__hash, __hit) = record.function_queries[108].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[108].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[108].bump_multiplicity(__i); } let __ret: [G; OUT_108] = unsafe { *(record.function_queries[108].output_at(__i).as_ptr() as *const [G; OUT_108]) }; __ret }, _ => { aiur_fn_108::(__args, __hash, record, io_buffer)? }, } } }; let __v_237: G = __r_arr[0]; let __v_238: G = __r_arr[1]; let __v_239: G = { let __values: [G; 10] = [__v_211, __v_222, __v_223, __v_224, __v_225, __v_226, __v_227, __v_228, __v_229, __v_232]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_324] = { let __args: [G; IN_324] = [__v_220, __v_239]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(324, &__args[..])?) { [G::ZERO; OUT_324] } else { let (__hash, __hit) = record.function_queries[324].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[324].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[324].bump_multiplicity(__i); } let __ret: [G; OUT_324] = unsafe { *(record.function_queries[324].output_at(__i).as_ptr() as *const [G; OUT_324]) }; __ret }, _ => { aiur_fn_324::(__args, __hash, record, io_buffer)? }, } } }; let __v_240: G = __r_arr[0]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_238.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_241: G = __loaded[0]; let __v_242: G = __loaded[1]; let __v_243: G = __loaded[2]; if (__v_241 != __v_212) { return Err(ExecError::AssertEqMismatch { lhs: __v_241.as_canonical_u64(), rhs: __v_212.as_canonical_u64(), msg: None }); } if (__v_242 != __v_212) { return Err(ExecError::AssertEqMismatch { lhs: __v_242.as_canonical_u64(), rhs: __v_212.as_canonical_u64(), msg: None }); } if (__v_243 != __v_212) { return Err(ExecError::AssertEqMismatch { lhs: __v_243.as_canonical_u64(), rhs: __v_212.as_canonical_u64(), msg: None }); } let __ret: [G; OUT_280] = [__v_141, __v_151, __v_161, __v_171, __v_181, __v_191, __v_201, __v_240, __v_231, __v_234, __v_235, __v_237]; record.function_queries[280].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_281: usize = 13; +const IN_281: usize = 13; +const OUT_281: usize = 1; +fn aiur_fn_281(inp: [G; IN_281], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_281], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = G::from_u64(0); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_12]; let __info = io_buffer.get_info(__v_13, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_14: G = __io_pair.0; let __v_15: G = __io_pair.1; let __v_16: G = G::from_u64(2); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_12]; let __info = io_buffer.get_info(__v_16, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_17: G = __io_pair.0; let __v_18: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_16, __v_17, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __r_arr: [G; OUT_28] = { let __args: [G; IN_28] = [__v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(28, &__args[..])?) { [G::ZERO; OUT_28] } else { let (__hash, __hit) = record.function_queries[28].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[28].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[28].bump_multiplicity(__i); } let __ret: [G; OUT_28] = unsafe { *(record.function_queries[28].output_at(__i).as_ptr() as *const [G; OUT_28]) }; __ret }, _ => { aiur_fn_28::(__args, __hash, record, io_buffer)? }, } } }; let __v_20: G = __r_arr[0]; let __v_21: G = __r_arr[1]; let __r_arr: [G; OUT_241] = { let __args: [G; IN_241] = [__v_21, __v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(241, &__args[..])?) { [G::ZERO; OUT_241] } else { let (__hash, __hit) = record.function_queries[241].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[241].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[241].bump_multiplicity(__i); } let __ret: [G; OUT_241] = unsafe { *(record.function_queries[241].output_at(__i).as_ptr() as *const [G; OUT_241]) }; __ret }, _ => { aiur_fn_241::(__args, __hash, record, io_buffer)? }, } } }; let __v_22: G = __r_arr[0]; let __v_23: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_23.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_24: G = __loaded[0]; let __v_25: G = __loaded[1]; let __v_26: G = __loaded[2]; let __v_27: G = G::from_u64(1); if (__v_24 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_24.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: None }); } if (__v_25 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_25.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: None }); } if (__v_26 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_26.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_31] = { let __args: [G; IN_31] = [__v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(31, &__args[..])?) { [G::ZERO; OUT_31] } else { let (__hash, __hit) = record.function_queries[31].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[31].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[31].bump_multiplicity(__i); } let __ret: [G; OUT_31] = unsafe { *(record.function_queries[31].output_at(__i).as_ptr() as *const [G; OUT_31]) }; __ret }, _ => { aiur_fn_31::(__args, __hash, record, io_buffer)? }, } } }; let __v_28: G = __r_arr[0]; let __v_29: G = __r_arr[1]; let __r_arr: [G; OUT_65] = { let __args: [G; IN_65] = [__v_29, __v_28]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(65, &__args[..])?) { [G::ZERO; OUT_65] } else { let (__hash, __hit) = record.function_queries[65].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[65].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[65].bump_multiplicity(__i); } let __ret: [G; OUT_65] = unsafe { *(record.function_queries[65].output_at(__i).as_ptr() as *const [G; OUT_65]) }; __ret }, _ => { aiur_fn_65::(__args, __hash, record, io_buffer)? }, } } }; let __v_30: G = __r_arr[0]; let __v_31: G = __r_arr[1]; let __r_arr: [G; OUT_31] = { let __args: [G; IN_31] = [__v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(31, &__args[..])?) { [G::ZERO; OUT_31] } else { let (__hash, __hit) = record.function_queries[31].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[31].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[31].bump_multiplicity(__i); } let __ret: [G; OUT_31] = unsafe { *(record.function_queries[31].output_at(__i).as_ptr() as *const [G; OUT_31]) }; __ret }, _ => { aiur_fn_31::(__args, __hash, record, io_buffer)? }, } } }; let __v_32: G = __r_arr[0]; let __v_33: G = __r_arr[1]; let __r_arr: [G; OUT_68] = { let __args: [G; IN_68] = [__v_33, __v_32]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(68, &__args[..])?) { [G::ZERO; OUT_68] } else { let (__hash, __hit) = record.function_queries[68].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[68].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[68].bump_multiplicity(__i); } let __ret: [G; OUT_68] = unsafe { *(record.function_queries[68].output_at(__i).as_ptr() as *const [G; OUT_68]) }; __ret }, _ => { aiur_fn_68::(__args, __hash, record, io_buffer)? }, } } }; let __v_34: G = __r_arr[0]; let __v_35: G = __r_arr[1]; let __r_arr: [G; OUT_31] = { let __args: [G; IN_31] = [__v_35]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(31, &__args[..])?) { [G::ZERO; OUT_31] } else { let (__hash, __hit) = record.function_queries[31].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[31].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[31].bump_multiplicity(__i); } let __ret: [G; OUT_31] = unsafe { *(record.function_queries[31].output_at(__i).as_ptr() as *const [G; OUT_31]) }; __ret }, _ => { aiur_fn_31::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __r_arr: [G; OUT_70] = { let __args: [G; IN_70] = [__v_37, __v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(70, &__args[..])?) { [G::ZERO; OUT_70] } else { let (__hash, __hit) = record.function_queries[70].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[70].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[70].bump_multiplicity(__i); } let __ret: [G; OUT_70] = unsafe { *(record.function_queries[70].output_at(__i).as_ptr() as *const [G; OUT_70]) }; __ret }, _ => { aiur_fn_70::(__args, __hash, record, io_buffer)? }, } } }; let __v_38: G = __r_arr[0]; let __v_39: G = __r_arr[1]; let __v_40: G = __v_14 + __v_15; if (__v_39 != __v_40) { return Err(ExecError::AssertEqMismatch { lhs: __v_39.as_canonical_u64(), rhs: __v_40.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_238] = { let __args: [G; IN_238] = [__v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_30, __v_34, __v_38]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(238, &__args[..])?) { [G::ZERO; OUT_238] } else { let (__hash, __hit) = record.function_queries[238].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[238].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[238].bump_multiplicity(__i); } let __ret: [G; OUT_238] = unsafe { *(record.function_queries[238].output_at(__i).as_ptr() as *const [G; OUT_238]) }; __ret }, _ => { aiur_fn_238::(__args, __hash, record, io_buffer)? }, } } }; let __v_41: G = __r_arr[0]; let __r_arr: [G; OUT_223] = { let __args: [G; IN_223] = [__v_41, __v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(223, &__args[..])?) { [G::ZERO; OUT_223] } else { let (__hash, __hit) = record.function_queries[223].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[223].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[223].bump_multiplicity(__i); } let __ret: [G; OUT_223] = unsafe { *(record.function_queries[223].output_at(__i).as_ptr() as *const [G; OUT_223]) }; __ret }, _ => { aiur_fn_223::(__args, __hash, record, io_buffer)? }, } } }; let __v_42: G = __r_arr[0]; if (__v_42 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_42.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: None }); } if (__v_27 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_27.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: None }); } let __ret: [G; OUT_281] = [__v_22]; record.function_queries[281].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_282: usize = 28; +const IN_282: usize = 28; +const OUT_282: usize = 8; +fn aiur_fn_282(inp: [G; IN_282], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_282], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __r_arr: [G; OUT_280] = { let __args: [G; IN_280] = [__v_0, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(280, &__args[..])?) { [G::ZERO; OUT_280] } else { let (__hash, __hit) = record.function_queries[280].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[280].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[280].bump_multiplicity(__i); } let __ret: [G; OUT_280] = unsafe { *(record.function_queries[280].output_at(__i).as_ptr() as *const [G; OUT_280]) }; __ret }, _ => { aiur_fn_280::(__args, __hash, record, io_buffer)? }, } } }; let __v_28: G = __r_arr[0]; let __v_29: G = __r_arr[1]; let __v_30: G = __r_arr[2]; let __v_31: G = __r_arr[3]; let __v_32: G = __r_arr[4]; let __v_33: G = __r_arr[5]; let __v_34: G = __r_arr[6]; let __v_35: G = __r_arr[7]; let __v_36: G = __r_arr[8]; let __v_37: G = __r_arr[9]; let __v_38: G = __r_arr[10]; let __v_39: G = __r_arr[11]; let __r_arr: [G; OUT_281] = { let __args: [G; IN_281] = [__v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(281, &__args[..])?) { [G::ZERO; OUT_281] } else { let (__hash, __hit) = record.function_queries[281].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[281].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[281].bump_multiplicity(__i); } let __ret: [G; OUT_281] = unsafe { *(record.function_queries[281].output_at(__i).as_ptr() as *const [G; OUT_281]) }; __ret }, _ => { aiur_fn_281::(__args, __hash, record, io_buffer)? }, } } }; let __v_40: G = __r_arr[0]; let __r_arr: [G; OUT_279] = { let __args: [G; IN_279] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(279, &__args[..])?) { [G::ZERO; OUT_279] } else { let (__hash, __hit) = record.function_queries[279].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[279].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[279].bump_multiplicity(__i); } let __ret: [G; OUT_279] = unsafe { *(record.function_queries[279].output_at(__i).as_ptr() as *const [G; OUT_279]) }; __ret }, _ => { aiur_fn_279::(__args, __hash, record, io_buffer)? }, } } }; let __v_41: G = __r_arr[0]; let __v_42: G = G::from_u64(0); let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_41, __v_42]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __v_43: G = __r_arr[0]; if (__v_43 != __v_42) { return Err(ExecError::AssertEqMismatch { lhs: __v_43.as_canonical_u64(), rhs: __v_42.as_canonical_u64(), msg: Some("aggr: child has the wrong claim channel".to_string()) }); } match __v_0.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_319] = { let __args: [G; IN_319] = [__v_41]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(319, &__args[..])?) { [G::ZERO; OUT_319] } else { let (__hash, __hit) = record.function_queries[319].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[319].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[319].bump_multiplicity(__i); } let __ret: [G; OUT_319] = unsafe { *(record.function_queries[319].output_at(__i).as_ptr() as *const [G; OUT_319]) }; __ret }, _ => { aiur_fn_319::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; let __v_45: G = G::from_u64(10); if (__v_44 != __v_45) { return Err(ExecError::AssertEqMismatch { lhs: __v_44.as_canonical_u64(), rhs: __v_45.as_canonical_u64(), msg: Some("aggr: IxVM child must expose a 10-word verify_claim claim".to_string()) }); } let __v_46: G = G::from_u64(1); let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_41, __v_46]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __v_47: G = __r_arr[0]; if (__v_47 != __v_18) { return Err(ExecError::AssertEqMismatch { lhs: __v_47.as_canonical_u64(), rhs: __v_18.as_canonical_u64(), msg: Some("aggr: IxVM child has the wrong entrypoint".to_string()) }); } let __v_48: G = G::from_u64(2); let __r_arr: [G; OUT_277] = { let __args: [G; IN_277] = [__v_41, __v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(277, &__args[..])?) { [G::ZERO; OUT_277] } else { let (__hash, __hit) = record.function_queries[277].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[277].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[277].bump_multiplicity(__i); } let __ret: [G; OUT_277] = unsafe { *(record.function_queries[277].output_at(__i).as_ptr() as *const [G; OUT_277]) }; __ret }, _ => { aiur_fn_277::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __v_50: G = __r_arr[1]; let __v_51: G = __r_arr[2]; let __v_52: G = __r_arr[3]; let __v_53: G = __r_arr[4]; let __v_54: G = __r_arr[5]; let __v_55: G = __r_arr[6]; let __v_56: G = __r_arr[7]; let __ret: [G; OUT_282] = [__v_49, __v_50, __v_51, __v_52, __v_53, __v_54, __v_55, __v_56]; record.function_queries[282].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_319] = { let __args: [G; IN_319] = [__v_41]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(319, &__args[..])?) { [G::ZERO; OUT_319] } else { let (__hash, __hit) = record.function_queries[319].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[319].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[319].bump_multiplicity(__i); } let __ret: [G; OUT_319] = unsafe { *(record.function_queries[319].output_at(__i).as_ptr() as *const [G; OUT_319]) }; __ret }, _ => { aiur_fn_319::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; let __v_45: G = G::from_u64(18); if (__v_44 != __v_45) { return Err(ExecError::AssertEqMismatch { lhs: __v_44.as_canonical_u64(), rhs: __v_45.as_canonical_u64(), msg: Some("aggr: recursive child must expose an 18-word ix_aggr claim".to_string()) }); } let __v_46: G = G::from_u64(1); let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_41, __v_46]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __v_47: G = __r_arr[0]; if (__v_47 != __v_19) { return Err(ExecError::AssertEqMismatch { lhs: __v_47.as_canonical_u64(), rhs: __v_19.as_canonical_u64(), msg: Some("aggr: recursive child has the wrong entrypoint".to_string()) }); } let __v_48: G = G::from_u64(2); let __r_arr: [G; OUT_277] = { let __args: [G; IN_277] = [__v_41, __v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(277, &__args[..])?) { [G::ZERO; OUT_277] } else { let (__hash, __hit) = record.function_queries[277].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[277].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[277].bump_multiplicity(__i); } let __ret: [G; OUT_277] = unsafe { *(record.function_queries[277].output_at(__i).as_ptr() as *const [G; OUT_277]) }; __ret }, _ => { aiur_fn_277::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __v_50: G = __r_arr[1]; let __v_51: G = __r_arr[2]; let __v_52: G = __r_arr[3]; let __v_53: G = __r_arr[4]; let __v_54: G = __r_arr[5]; let __v_55: G = __r_arr[6]; let __v_56: G = __r_arr[7]; let __r_arr: [G; OUT_278] = { let __args: [G; IN_278] = [__v_49, __v_50, __v_51, __v_52, __v_53, __v_54, __v_55, __v_56, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(278, &__args[..])?) { [G::ZERO; OUT_278] } else { let (__hash, __hit) = record.function_queries[278].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[278].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[278].bump_multiplicity(__i); } let __ret: [G; OUT_278] = unsafe { *(record.function_queries[278].output_at(__i).as_ptr() as *const [G; OUT_278]) }; __ret }, _ => { aiur_fn_278::(__args, __hash, record, io_buffer)? }, } } }; let __v_57: G = G::from_u64(10); let __r_arr: [G; OUT_277] = { let __args: [G; IN_277] = [__v_41, __v_57]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(277, &__args[..])?) { [G::ZERO; OUT_277] } else { let (__hash, __hit) = record.function_queries[277].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[277].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[277].bump_multiplicity(__i); } let __ret: [G; OUT_277] = unsafe { *(record.function_queries[277].output_at(__i).as_ptr() as *const [G; OUT_277]) }; __ret }, _ => { aiur_fn_277::(__args, __hash, record, io_buffer)? }, } } }; let __v_58: G = __r_arr[0]; let __v_59: G = __r_arr[1]; let __v_60: G = __r_arr[2]; let __v_61: G = __r_arr[3]; let __v_62: G = __r_arr[4]; let __v_63: G = __r_arr[5]; let __v_64: G = __r_arr[6]; let __v_65: G = __r_arr[7]; let __ret: [G; OUT_282] = [__v_58, __v_59, __v_60, __v_61, __v_62, __v_63, __v_64, __v_65]; record.function_queries[282].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } +const INPUT_SIZE_283: usize = 35; +const IN_283: usize = 35; const OUT_283: usize = 0; -fn aiur_fn_283(inp: [G; IN_283], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_283], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = inp[34]; let __v_35: G = inp[35]; let __v_36: G = G::from_u64(0); let __r_arr: [G; OUT_280] = { let __args: [G; IN_280] = [__v_0, __v_36, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(280, &__args[..])?) { [G::ZERO; OUT_280] } else { let (__hash, __hit) = record.function_queries[280].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[280].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[280].bump_multiplicity(__i); } let __ret: [G; OUT_280] = unsafe { *(record.function_queries[280].output_at(__i).as_ptr() as *const [G; OUT_280]) }; __ret }, _ => { aiur_fn_280::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = __r_arr[1]; let __v_39: G = __r_arr[2]; let __v_40: G = __r_arr[3]; let __v_41: G = __r_arr[4]; let __v_42: G = __r_arr[5]; let __v_43: G = __r_arr[6]; let __v_44: G = __r_arr[7]; let __v_45: G = G::from_u64(1); let __r_arr: [G; OUT_280] = { let __args: [G; IN_280] = [__v_1, __v_45, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(280, &__args[..])?) { [G::ZERO; OUT_280] } else { let (__hash, __hit) = record.function_queries[280].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[280].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[280].bump_multiplicity(__i); } let __ret: [G; OUT_280] = unsafe { *(record.function_queries[280].output_at(__i).as_ptr() as *const [G; OUT_280]) }; __ret }, _ => { aiur_fn_280::(__args, __hash, record, io_buffer)? }, } } }; let __v_46: G = __r_arr[0]; let __v_47: G = __r_arr[1]; let __v_48: G = __r_arr[2]; let __v_49: G = __r_arr[3]; let __v_50: G = __r_arr[4]; let __v_51: G = __r_arr[5]; let __v_52: G = __r_arr[6]; let __v_53: G = __r_arr[7]; let __r_arr: [G; OUT_273] = { let __args: [G; IN_273] = [__v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(273, &__args[..])?) { [G::ZERO; OUT_273] } else { let (__hash, __hit) = record.function_queries[273].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[273].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[273].bump_multiplicity(__i); } let __ret: [G; OUT_273] = unsafe { *(record.function_queries[273].output_at(__i).as_ptr() as *const [G; OUT_273]) }; __ret }, _ => { aiur_fn_273::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_54]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __v_55: G = __r_arr[0]; let __v_56: G = __r_arr[1]; let __v_57: G = __r_arr[2]; let __v_58: G = __r_arr[3]; let __v_59: G = __r_arr[4]; let __v_60: G = __r_arr[5]; let __v_61: G = __r_arr[6]; let __v_62: G = __r_arr[7]; let __r_arr: [G; OUT_273] = { let __args: [G; IN_273] = [__v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(273, &__args[..])?) { [G::ZERO; OUT_273] } else { let (__hash, __hit) = record.function_queries[273].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[273].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[273].bump_multiplicity(__i); } let __ret: [G; OUT_273] = unsafe { *(record.function_queries[273].output_at(__i).as_ptr() as *const [G; OUT_273]) }; __ret }, _ => { aiur_fn_273::(__args, __hash, record, io_buffer)? }, } } }; let __v_63: G = __r_arr[0]; let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_63]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __v_64: G = __r_arr[0]; let __v_65: G = __r_arr[1]; let __v_66: G = __r_arr[2]; let __v_67: G = __r_arr[3]; let __v_68: G = __r_arr[4]; let __v_69: G = __r_arr[5]; let __v_70: G = __r_arr[6]; let __v_71: G = __r_arr[7]; let __v_72: G = G::from_u64(2); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_72]; let __info = io_buffer.get_info(__v_72, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_73: G = __io_pair.0; let __v_74: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_72, __v_73, __v_74]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_75: G = __r_arr[0]; let __v_76: G = G::from_u64(103); let __v_77: G = G::from_u64(230); let __v_78: G = G::from_u64(9); let __v_79: G = G::from_u64(106); let __v_80: G = G::from_u64(133); let __v_81: G = G::from_u64(174); let __v_82: G = G::from_u64(187); let __v_83: G = G::from_u64(114); let __v_84: G = G::from_u64(243); let __v_85: G = G::from_u64(110); let __v_86: G = G::from_u64(60); let __v_87: G = G::from_u64(58); let __v_88: G = G::from_u64(245); let __v_89: G = G::from_u64(79); let __v_90: G = G::from_u64(165); let __v_91: G = G::from_u64(127); let __v_92: G = G::from_u64(82); let __v_93: G = G::from_u64(14); let __v_94: G = G::from_u64(81); let __v_95: G = G::from_u64(140); let __v_96: G = G::from_u64(104); let __v_97: G = G::from_u64(5); let __v_98: G = G::from_u64(155); let __v_99: G = G::from_u64(171); let __v_100: G = G::from_u64(217); let __v_101: G = G::from_u64(131); let __v_102: G = G::from_u64(31); let __v_103: G = G::from_u64(25); let __v_104: G = G::from_u64(205); let __v_105: G = G::from_u64(224); let __v_106: G = G::from_u64(91); let __v_107: G = { let __values: [G; 3] = [__v_45, __v_45, __v_45]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_108: G = { let __values: [G; 8] = [__v_36, __v_36, __v_36, __v_36, __v_36, __v_36, __v_36, __v_36]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_109: G = { let __values: [G; 32] = [__v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_76, __v_82, __v_83, __v_84, __v_85, __v_86, __v_87, __v_88, __v_89, __v_90, __v_91, __v_92, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_99, __v_100, __v_101, __v_102, __v_103, __v_104, __v_105, __v_106]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_110: G = { let __values: [G; 34] = [__v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_75, __v_107, __v_36, __v_36, __v_108, __v_109, __v_110]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_111: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_111]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_112: G = __r_arr[0]; let __v_113: G = __r_arr[1]; let __v_114: G = __r_arr[2]; let __v_115: G = __r_arr[3]; let __v_116: G = __r_arr[4]; let __v_117: G = __r_arr[5]; let __v_118: G = __r_arr[6]; let __v_119: G = __r_arr[7]; let __v_120: G = __r_arr[8]; let __v_121: G = __r_arr[9]; let __v_122: G = __r_arr[10]; let __v_123: G = __r_arr[11]; let __v_124: G = __r_arr[12]; let __v_125: G = __r_arr[13]; let __v_126: G = __r_arr[14]; let __v_127: G = __r_arr[15]; let __v_128: G = __r_arr[16]; let __v_129: G = __r_arr[17]; let __v_130: G = __r_arr[18]; let __v_131: G = __r_arr[19]; let __v_132: G = __r_arr[20]; let __v_133: G = __r_arr[21]; let __v_134: G = __r_arr[22]; let __v_135: G = __r_arr[23]; let __v_136: G = __r_arr[24]; let __v_137: G = __r_arr[25]; let __v_138: G = __r_arr[26]; let __v_139: G = __r_arr[27]; let __v_140: G = __r_arr[28]; let __v_141: G = __r_arr[29]; let __v_142: G = __r_arr[30]; let __v_143: G = __r_arr[31]; let __v_144: G = G::from_u64(256); let __v_145: G = __v_144 * __v_113; let __v_146: G = __v_112 + __v_145; let __v_147: G = G::from_u64(65536); let __v_148: G = __v_147 * __v_114; let __v_149: G = __v_146 + __v_148; let __v_150: G = G::from_u64(16777216); let __v_151: G = __v_150 * __v_115; let __v_152: G = __v_149 + __v_151; let __v_153: G = __v_144 * __v_117; let __v_154: G = __v_116 + __v_153; let __v_155: G = __v_147 * __v_118; let __v_156: G = __v_154 + __v_155; let __v_157: G = __v_150 * __v_119; let __v_158: G = __v_156 + __v_157; let __v_159: G = __v_144 * __v_121; let __v_160: G = __v_120 + __v_159; let __v_161: G = __v_147 * __v_122; let __v_162: G = __v_160 + __v_161; let __v_163: G = __v_150 * __v_123; let __v_164: G = __v_162 + __v_163; let __v_165: G = __v_144 * __v_125; let __v_166: G = __v_124 + __v_165; let __v_167: G = __v_147 * __v_126; let __v_168: G = __v_166 + __v_167; let __v_169: G = __v_150 * __v_127; let __v_170: G = __v_168 + __v_169; let __v_171: G = __v_144 * __v_129; let __v_172: G = __v_128 + __v_171; let __v_173: G = __v_147 * __v_130; let __v_174: G = __v_172 + __v_173; let __v_175: G = __v_150 * __v_131; let __v_176: G = __v_174 + __v_175; let __v_177: G = __v_144 * __v_133; let __v_178: G = __v_132 + __v_177; let __v_179: G = __v_147 * __v_134; let __v_180: G = __v_178 + __v_179; let __v_181: G = __v_150 * __v_135; let __v_182: G = __v_180 + __v_181; let __v_183: G = __v_144 * __v_137; let __v_184: G = __v_136 + __v_183; let __v_185: G = __v_147 * __v_138; let __v_186: G = __v_184 + __v_185; let __v_187: G = __v_150 * __v_139; let __v_188: G = __v_186 + __v_187; let __v_189: G = __v_144 * __v_141; let __v_190: G = __v_140 + __v_189; let __v_191: G = __v_147 * __v_142; let __v_192: G = __v_190 + __v_191; let __v_193: G = __v_150 * __v_143; let __v_194: G = __v_192 + __v_193; if (__v_152 != __v_28) { return Err(ExecError::AssertEqMismatch { lhs: __v_152.as_canonical_u64(), rhs: __v_28.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } if (__v_158 != __v_29) { return Err(ExecError::AssertEqMismatch { lhs: __v_158.as_canonical_u64(), rhs: __v_29.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } if (__v_164 != __v_30) { return Err(ExecError::AssertEqMismatch { lhs: __v_164.as_canonical_u64(), rhs: __v_30.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } if (__v_170 != __v_31) { return Err(ExecError::AssertEqMismatch { lhs: __v_170.as_canonical_u64(), rhs: __v_31.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } if (__v_176 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_176.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } if (__v_182 != __v_33) { return Err(ExecError::AssertEqMismatch { lhs: __v_182.as_canonical_u64(), rhs: __v_33.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } if (__v_188 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_188.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } if (__v_194 != __v_35) { return Err(ExecError::AssertEqMismatch { lhs: __v_194.as_canonical_u64(), rhs: __v_35.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_75]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __v_195: G = __r_arr[0]; let __v_196: G = __r_arr[1]; let __v_197: G = __r_arr[2]; let __v_198: G = __r_arr[3]; let __v_199: G = __r_arr[4]; let __v_200: G = __r_arr[5]; let __v_201: G = __r_arr[6]; let __v_202: G = __r_arr[7]; let __r_arr: [G; OUT_272] = { let __args: [G; IN_272] = [__v_55, __v_56, __v_57, __v_58, __v_59, __v_64, __v_65, __v_66, __v_67, __v_68]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(272, &__args[..])?) { [G::ZERO; OUT_272] } else { let (__hash, __hit) = record.function_queries[272].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[272].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[272].bump_multiplicity(__i); } let __ret: [G; OUT_272] = unsafe { *(record.function_queries[272].output_at(__i).as_ptr() as *const [G; OUT_272]) }; __ret }, _ => { aiur_fn_272::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_272] = { let __args: [G; IN_272] = [__v_55, __v_56, __v_57, __v_58, __v_59, __v_195, __v_196, __v_197, __v_198, __v_199]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(272, &__args[..])?) { [G::ZERO; OUT_272] } else { let (__hash, __hit) = record.function_queries[272].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[272].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[272].bump_multiplicity(__i); } let __ret: [G; OUT_272] = unsafe { *(record.function_queries[272].output_at(__i).as_ptr() as *const [G; OUT_272]) }; __ret }, _ => { aiur_fn_272::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_247] = { let __args: [G; IN_247] = [__v_60, __v_69]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(247, &__args[..])?) { [G::ZERO; OUT_247] } else { let (__hash, __hit) = record.function_queries[247].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[247].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[247].bump_multiplicity(__i); } let __ret: [G; OUT_247] = unsafe { *(record.function_queries[247].output_at(__i).as_ptr() as *const [G; OUT_247]) }; __ret }, _ => { aiur_fn_247::(__args, __hash, record, io_buffer)? }, } } }; let __v_203: G = __r_arr[0]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_200, __v_203]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_204: G = __r_arr[0]; if (__v_204 != __v_45) { return Err(ExecError::AssertEqMismatch { lhs: __v_204.as_canonical_u64(), rhs: __v_45.as_canonical_u64(), msg: Some("aggr structural: output subject root is not nodeHash(left, right)".to_string()) }); } let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_61, __v_62]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_205: G = __r_arr[0]; let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_70, __v_71]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_206: G = __r_arr[0]; let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_201, __v_202]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_207: G = __r_arr[0]; let __r_arr: [G; OUT_266] = { let __args: [G; IN_266] = [__v_205, __v_206, __v_200, __v_207]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(266, &__args[..])?) { [G::ZERO; OUT_266] } else { let (__hash, __hit) = record.function_queries[266].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[266].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[266].bump_multiplicity(__i); } let __ret: [G; OUT_266] = unsafe { *(record.function_queries[266].output_at(__i).as_ptr() as *const [G; OUT_266]) }; __ret }, _ => { aiur_fn_266::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_283] = []; record.function_queries[283].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_284: usize = 35; -const IN_284: usize = 35; +fn aiur_fn_283(inp: [G; IN_283], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_283], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = inp[34]; let __v_35: G = G::from_u64(0); let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_0, __v_35, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __v_38: G = __r_arr[2]; let __v_39: G = __r_arr[3]; let __v_40: G = __r_arr[4]; let __v_41: G = __r_arr[5]; let __v_42: G = __r_arr[6]; let __v_43: G = __r_arr[7]; let __r_arr: [G; OUT_278] = { let __args: [G; IN_278] = [__v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(278, &__args[..])?) { [G::ZERO; OUT_278] } else { let (__hash, __hit) = record.function_queries[278].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[278].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[278].bump_multiplicity(__i); } let __ret: [G; OUT_278] = unsafe { *(record.function_queries[278].output_at(__i).as_ptr() as *const [G; OUT_278]) }; __ret }, _ => { aiur_fn_278::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_283] = []; record.function_queries[283].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_284: usize = 36; +const IN_284: usize = 36; const OUT_284: usize = 0; -fn aiur_fn_284(inp: [G; IN_284], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_284], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = inp[34]; let __v_35: G = G::from_u64(0); let __r_arr: [G; OUT_280] = { let __args: [G; IN_280] = [__v_0, __v_35, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(280, &__args[..])?) { [G::ZERO; OUT_280] } else { let (__hash, __hit) = record.function_queries[280].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[280].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[280].bump_multiplicity(__i); } let __ret: [G; OUT_280] = unsafe { *(record.function_queries[280].output_at(__i).as_ptr() as *const [G; OUT_280]) }; __ret }, _ => { aiur_fn_280::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __v_38: G = __r_arr[2]; let __v_39: G = __r_arr[3]; let __v_40: G = __r_arr[4]; let __v_41: G = __r_arr[5]; let __v_42: G = __r_arr[6]; let __v_43: G = __r_arr[7]; let __v_44: G = G::from_u64(1); let __r_arr: [G; OUT_280] = { let __args: [G; IN_280] = [__v_35, __v_44, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(280, &__args[..])?) { [G::ZERO; OUT_280] } else { let (__hash, __hit) = record.function_queries[280].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[280].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[280].bump_multiplicity(__i); } let __ret: [G; OUT_280] = unsafe { *(record.function_queries[280].output_at(__i).as_ptr() as *const [G; OUT_280]) }; __ret }, _ => { aiur_fn_280::(__args, __hash, record, io_buffer)? }, } } }; let __v_45: G = __r_arr[0]; let __v_46: G = __r_arr[1]; let __v_47: G = __r_arr[2]; let __v_48: G = __r_arr[3]; let __v_49: G = __r_arr[4]; let __v_50: G = __r_arr[5]; let __v_51: G = __r_arr[6]; let __v_52: G = __r_arr[7]; let __r_arr: [G; OUT_273] = { let __args: [G; IN_273] = [__v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(273, &__args[..])?) { [G::ZERO; OUT_273] } else { let (__hash, __hit) = record.function_queries[273].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[273].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[273].bump_multiplicity(__i); } let __ret: [G; OUT_273] = unsafe { *(record.function_queries[273].output_at(__i).as_ptr() as *const [G; OUT_273]) }; __ret }, _ => { aiur_fn_273::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_53]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __v_55: G = __r_arr[1]; let __v_56: G = __r_arr[2]; let __v_57: G = __r_arr[3]; let __v_58: G = __r_arr[4]; let __v_59: G = __r_arr[5]; let __v_60: G = __r_arr[6]; let __v_61: G = __r_arr[7]; let __v_62: G = G::from_u64(4); if (__v_58 != __v_62) { return Err(ExecError::AssertEqMismatch { lhs: __v_58.as_canonical_u64(), rhs: __v_62.as_canonical_u64(), msg: Some("aggr discharge: the claim does not assume its binding".to_string()) }); } let __r_arr: [G; OUT_273] = { let __args: [G; IN_273] = [__v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(273, &__args[..])?) { [G::ZERO; OUT_273] } else { let (__hash, __hit) = record.function_queries[273].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[273].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[273].bump_multiplicity(__i); } let __ret: [G; OUT_273] = unsafe { *(record.function_queries[273].output_at(__i).as_ptr() as *const [G; OUT_273]) }; __ret }, _ => { aiur_fn_273::(__args, __hash, record, io_buffer)? }, } } }; let __v_63: G = __r_arr[0]; let __r_arr: [G; OUT_269] = { let __args: [G; IN_269] = [__v_63]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(269, &__args[..])?) { [G::ZERO; OUT_269] } else { let (__hash, __hit) = record.function_queries[269].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[269].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[269].bump_multiplicity(__i); } let __ret: [G; OUT_269] = unsafe { *(record.function_queries[269].output_at(__i).as_ptr() as *const [G; OUT_269]) }; __ret }, _ => { aiur_fn_269::(__args, __hash, record, io_buffer)? }, } } }; let __v_64: G = __r_arr[0]; let __v_65: G = __r_arr[1]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_54, __v_64]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_66: G = __r_arr[0]; if (__v_66 != __v_44) { return Err(ExecError::AssertEqMismatch { lhs: __v_66.as_canonical_u64(), rhs: __v_44.as_canonical_u64(), msg: Some("aggr discharge: the binding claim is for another specification digest".to_string()) }); } let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_55, __v_65]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_67: G = __r_arr[0]; if (__v_67 != __v_44) { return Err(ExecError::AssertEqMismatch { lhs: __v_67.as_canonical_u64(), rhs: __v_44.as_canonical_u64(), msg: Some("aggr discharge: the binding claim is for another binding".to_string()) }); } let __v_68: G = G::from_u64(2); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_68]; let __info = io_buffer.get_info(__v_68, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_69: G = __io_pair.0; let __v_70: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_68, __v_69, __v_70]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_71: G = __r_arr[0]; let __v_72: G = G::from_u64(103); let __v_73: G = G::from_u64(230); let __v_74: G = G::from_u64(9); let __v_75: G = G::from_u64(106); let __v_76: G = G::from_u64(133); let __v_77: G = G::from_u64(174); let __v_78: G = G::from_u64(187); let __v_79: G = G::from_u64(114); let __v_80: G = G::from_u64(243); let __v_81: G = G::from_u64(110); let __v_82: G = G::from_u64(60); let __v_83: G = G::from_u64(58); let __v_84: G = G::from_u64(245); let __v_85: G = G::from_u64(79); let __v_86: G = G::from_u64(165); let __v_87: G = G::from_u64(127); let __v_88: G = G::from_u64(82); let __v_89: G = G::from_u64(14); let __v_90: G = G::from_u64(81); let __v_91: G = G::from_u64(140); let __v_92: G = G::from_u64(104); let __v_93: G = G::from_u64(5); let __v_94: G = G::from_u64(155); let __v_95: G = G::from_u64(171); let __v_96: G = G::from_u64(217); let __v_97: G = G::from_u64(131); let __v_98: G = G::from_u64(31); let __v_99: G = G::from_u64(25); let __v_100: G = G::from_u64(205); let __v_101: G = G::from_u64(224); let __v_102: G = G::from_u64(91); let __v_103: G = { let __values: [G; 3] = [__v_44, __v_44, __v_44]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_104: G = { let __values: [G; 8] = [__v_35, __v_35, __v_35, __v_35, __v_35, __v_35, __v_35, __v_35]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_105: G = { let __values: [G; 32] = [__v_72, __v_73, __v_74, __v_75, __v_76, __v_77, __v_72, __v_78, __v_79, __v_80, __v_81, __v_82, __v_83, __v_84, __v_85, __v_86, __v_87, __v_88, __v_89, __v_90, __v_91, __v_92, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_99, __v_100, __v_101, __v_102]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_106: G = { let __values: [G; 34] = [__v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_71, __v_103, __v_35, __v_35, __v_104, __v_105, __v_106]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_107: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_107]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_108: G = __r_arr[0]; let __v_109: G = __r_arr[1]; let __v_110: G = __r_arr[2]; let __v_111: G = __r_arr[3]; let __v_112: G = __r_arr[4]; let __v_113: G = __r_arr[5]; let __v_114: G = __r_arr[6]; let __v_115: G = __r_arr[7]; let __v_116: G = __r_arr[8]; let __v_117: G = __r_arr[9]; let __v_118: G = __r_arr[10]; let __v_119: G = __r_arr[11]; let __v_120: G = __r_arr[12]; let __v_121: G = __r_arr[13]; let __v_122: G = __r_arr[14]; let __v_123: G = __r_arr[15]; let __v_124: G = __r_arr[16]; let __v_125: G = __r_arr[17]; let __v_126: G = __r_arr[18]; let __v_127: G = __r_arr[19]; let __v_128: G = __r_arr[20]; let __v_129: G = __r_arr[21]; let __v_130: G = __r_arr[22]; let __v_131: G = __r_arr[23]; let __v_132: G = __r_arr[24]; let __v_133: G = __r_arr[25]; let __v_134: G = __r_arr[26]; let __v_135: G = __r_arr[27]; let __v_136: G = __r_arr[28]; let __v_137: G = __r_arr[29]; let __v_138: G = __r_arr[30]; let __v_139: G = __r_arr[31]; let __v_140: G = G::from_u64(256); let __v_141: G = __v_140 * __v_109; let __v_142: G = __v_108 + __v_141; let __v_143: G = G::from_u64(65536); let __v_144: G = __v_143 * __v_110; let __v_145: G = __v_142 + __v_144; let __v_146: G = G::from_u64(16777216); let __v_147: G = __v_146 * __v_111; let __v_148: G = __v_145 + __v_147; let __v_149: G = __v_140 * __v_113; let __v_150: G = __v_112 + __v_149; let __v_151: G = __v_143 * __v_114; let __v_152: G = __v_150 + __v_151; let __v_153: G = __v_146 * __v_115; let __v_154: G = __v_152 + __v_153; let __v_155: G = __v_140 * __v_117; let __v_156: G = __v_116 + __v_155; let __v_157: G = __v_143 * __v_118; let __v_158: G = __v_156 + __v_157; let __v_159: G = __v_146 * __v_119; let __v_160: G = __v_158 + __v_159; let __v_161: G = __v_140 * __v_121; let __v_162: G = __v_120 + __v_161; let __v_163: G = __v_143 * __v_122; let __v_164: G = __v_162 + __v_163; let __v_165: G = __v_146 * __v_123; let __v_166: G = __v_164 + __v_165; let __v_167: G = __v_140 * __v_125; let __v_168: G = __v_124 + __v_167; let __v_169: G = __v_143 * __v_126; let __v_170: G = __v_168 + __v_169; let __v_171: G = __v_146 * __v_127; let __v_172: G = __v_170 + __v_171; let __v_173: G = __v_140 * __v_129; let __v_174: G = __v_128 + __v_173; let __v_175: G = __v_143 * __v_130; let __v_176: G = __v_174 + __v_175; let __v_177: G = __v_146 * __v_131; let __v_178: G = __v_176 + __v_177; let __v_179: G = __v_140 * __v_133; let __v_180: G = __v_132 + __v_179; let __v_181: G = __v_143 * __v_134; let __v_182: G = __v_180 + __v_181; let __v_183: G = __v_146 * __v_135; let __v_184: G = __v_182 + __v_183; let __v_185: G = __v_140 * __v_137; let __v_186: G = __v_136 + __v_185; let __v_187: G = __v_143 * __v_138; let __v_188: G = __v_186 + __v_187; let __v_189: G = __v_146 * __v_139; let __v_190: G = __v_188 + __v_189; if (__v_148 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_148.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } if (__v_154 != __v_28) { return Err(ExecError::AssertEqMismatch { lhs: __v_154.as_canonical_u64(), rhs: __v_28.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } if (__v_160 != __v_29) { return Err(ExecError::AssertEqMismatch { lhs: __v_160.as_canonical_u64(), rhs: __v_29.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } if (__v_166 != __v_30) { return Err(ExecError::AssertEqMismatch { lhs: __v_166.as_canonical_u64(), rhs: __v_30.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } if (__v_172 != __v_31) { return Err(ExecError::AssertEqMismatch { lhs: __v_172.as_canonical_u64(), rhs: __v_31.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } if (__v_178 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_178.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } if (__v_184 != __v_33) { return Err(ExecError::AssertEqMismatch { lhs: __v_184.as_canonical_u64(), rhs: __v_33.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } if (__v_190 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_190.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_71]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __v_191: G = __r_arr[0]; let __v_192: G = __r_arr[1]; let __v_193: G = __r_arr[2]; let __v_194: G = __r_arr[3]; let __v_195: G = __r_arr[4]; let __v_196: G = __r_arr[5]; let __v_197: G = __r_arr[6]; let __v_198: G = __r_arr[7]; let __v_199: G = G::from_u64(3); let __r_arr: [G; OUT_272] = { let __args: [G; IN_272] = [__v_54, __v_55, __v_56, __v_57, __v_199, __v_191, __v_192, __v_193, __v_194, __v_195]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(272, &__args[..])?) { [G::ZERO; OUT_272] } else { let (__hash, __hit) = record.function_queries[272].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[272].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[272].bump_multiplicity(__i); } let __ret: [G; OUT_272] = unsafe { *(record.function_queries[272].output_at(__i).as_ptr() as *const [G; OUT_272]) }; __ret }, _ => { aiur_fn_272::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_59, __v_196]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_200: G = __r_arr[0]; if (__v_200 != __v_44) { return Err(ExecError::AssertEqMismatch { lhs: __v_200.as_canonical_u64(), rhs: __v_44.as_canonical_u64(), msg: Some("aggr discharge: output subject root differs from the child's".to_string()) }); } let __r_arr: [G; OUT_271] = { let __args: [G; IN_271] = [__v_60, __v_61, __v_197, __v_198]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(271, &__args[..])?) { [G::ZERO; OUT_271] } else { let (__hash, __hit) = record.function_queries[271].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[271].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[271].bump_multiplicity(__i); } let __ret: [G; OUT_271] = unsafe { *(record.function_queries[271].output_at(__i).as_ptr() as *const [G; OUT_271]) }; __ret }, _ => { aiur_fn_271::(__args, __hash, record, io_buffer)? }, } } }; let __v_201: G = __r_arr[0]; if (__v_201 != __v_44) { return Err(ExecError::AssertEqMismatch { lhs: __v_201.as_canonical_u64(), rhs: __v_44.as_canonical_u64(), msg: Some("aggr discharge: output assumptions differ from the child's".to_string()) }); } let __ret: [G; OUT_284] = []; record.function_queries[284].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_285: usize = 1; -const IN_285: usize = 1; -const OUT_285: usize = 1; -fn aiur_fn_285(inp: [G; IN_285], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_285], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; match __v_0.as_canonical_u64() { 0u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_285] = [__v_1]; record.function_queries[285].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_285] = [__v_1]; record.function_queries[285].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 2u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_285] = [__v_1]; record.function_queries[285].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 29u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_285] = [__v_1]; record.function_queries[285].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 30u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_285] = [__v_1]; record.function_queries[285].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 31u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_285] = [__v_1]; record.function_queries[285].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 37u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_285] = [__v_1]; record.function_queries[285].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 38u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_285] = [__v_1]; record.function_queries[285].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 39u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_285] = [__v_1]; record.function_queries[285].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 40u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_285] = [__v_1]; record.function_queries[285].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 56u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_285] = [__v_1]; record.function_queries[285].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 57u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_285] = [__v_1]; record.function_queries[285].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_1: G = G::from_u64(0); let __ret: [G; OUT_285] = [__v_1]; record.function_queries[285].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } -const INPUT_SIZE_286: usize = 3; -const IN_286: usize = 3; -const OUT_286: usize = 1; -fn aiur_fn_286(inp: [G; IN_286], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_286], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; match __v_2.as_canonical_u64() { 0u64 => { let __v_3: G = G::from_u64(1); let __v_4: G = { let __values: [G; 3] = [__v_3, __v_3, __v_3]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_286] = [__v_4]; record.function_queries[286].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __v_4: G = __r_arr[1]; if (__v_3 != __v_1) { return Err(ExecError::AssertEqMismatch { lhs: __v_3.as_canonical_u64(), rhs: __v_1.as_canonical_u64(), msg: Some("aggr cross-binding: the binding does not bind every role in order".to_string()) }); } let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __r_arr[1]; let __v_7: G = G::from_u64(1); let __v_8: G = __v_1 + __v_7; let __v_9: G = __v_2 - __v_7; let __r_arr: [G; OUT_286] = { let __args: [G; IN_286] = [__v_6, __v_8, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(286, &__args[..])?) { [G::ZERO; OUT_286] } else { let (__hash, __hit) = record.function_queries[286].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[286].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[286].bump_multiplicity(__i); } let __ret: [G; OUT_286] = unsafe { *(record.function_queries[286].output_at(__i).as_ptr() as *const [G; OUT_286]) }; __ret }, _ => { aiur_fn_286::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __r_arr: [G; OUT_285] = { let __args: [G; IN_285] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(285, &__args[..])?) { [G::ZERO; OUT_285] } else { let (__hash, __hit) = record.function_queries[285].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[285].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[285].bump_multiplicity(__i); } let __ret: [G; OUT_285] = unsafe { *(record.function_queries[285].output_at(__i).as_ptr() as *const [G; OUT_285]) }; __ret }, _ => { aiur_fn_285::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; match __v_11.as_canonical_u64() { 1u64 => { let __v_12: G = G::from_u64(0); let __v_13: G = { let __values: [G; 3] = [__v_12, __v_5, __v_10]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_286] = [__v_13]; record.function_queries[286].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __ret: [G; OUT_286] = [__v_10]; record.function_queries[286].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, } }) } -const INPUT_SIZE_287: usize = 2; -const IN_287: usize = 2; +fn aiur_fn_284(inp: [G; IN_284], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_284], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = inp[34]; let __v_35: G = inp[35]; let __v_36: G = G::from_u64(0); let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_0, __v_36, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = __r_arr[1]; let __v_39: G = __r_arr[2]; let __v_40: G = __r_arr[3]; let __v_41: G = __r_arr[4]; let __v_42: G = __r_arr[5]; let __v_43: G = __r_arr[6]; let __v_44: G = __r_arr[7]; let __v_45: G = G::from_u64(1); let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_1, __v_45, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __v_46: G = __r_arr[0]; let __v_47: G = __r_arr[1]; let __v_48: G = __r_arr[2]; let __v_49: G = __r_arr[3]; let __v_50: G = __r_arr[4]; let __v_51: G = __r_arr[5]; let __v_52: G = __r_arr[6]; let __v_53: G = __r_arr[7]; let __r_arr: [G; OUT_275] = { let __args: [G; IN_275] = [__v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(275, &__args[..])?) { [G::ZERO; OUT_275] } else { let (__hash, __hit) = record.function_queries[275].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[275].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[275].bump_multiplicity(__i); } let __ret: [G; OUT_275] = unsafe { *(record.function_queries[275].output_at(__i).as_ptr() as *const [G; OUT_275]) }; __ret }, _ => { aiur_fn_275::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __r_arr: [G; OUT_270] = { let __args: [G; IN_270] = [__v_54]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(270, &__args[..])?) { [G::ZERO; OUT_270] } else { let (__hash, __hit) = record.function_queries[270].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[270].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[270].bump_multiplicity(__i); } let __ret: [G; OUT_270] = unsafe { *(record.function_queries[270].output_at(__i).as_ptr() as *const [G; OUT_270]) }; __ret }, _ => { aiur_fn_270::(__args, __hash, record, io_buffer)? }, } } }; let __v_55: G = __r_arr[0]; let __v_56: G = __r_arr[1]; let __v_57: G = __r_arr[2]; let __v_58: G = __r_arr[3]; let __v_59: G = __r_arr[4]; let __v_60: G = __r_arr[5]; let __v_61: G = __r_arr[6]; let __v_62: G = __r_arr[7]; let __r_arr: [G; OUT_275] = { let __args: [G; IN_275] = [__v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(275, &__args[..])?) { [G::ZERO; OUT_275] } else { let (__hash, __hit) = record.function_queries[275].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[275].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[275].bump_multiplicity(__i); } let __ret: [G; OUT_275] = unsafe { *(record.function_queries[275].output_at(__i).as_ptr() as *const [G; OUT_275]) }; __ret }, _ => { aiur_fn_275::(__args, __hash, record, io_buffer)? }, } } }; let __v_63: G = __r_arr[0]; let __r_arr: [G; OUT_270] = { let __args: [G; IN_270] = [__v_63]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(270, &__args[..])?) { [G::ZERO; OUT_270] } else { let (__hash, __hit) = record.function_queries[270].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[270].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[270].bump_multiplicity(__i); } let __ret: [G; OUT_270] = unsafe { *(record.function_queries[270].output_at(__i).as_ptr() as *const [G; OUT_270]) }; __ret }, _ => { aiur_fn_270::(__args, __hash, record, io_buffer)? }, } } }; let __v_64: G = __r_arr[0]; let __v_65: G = __r_arr[1]; let __v_66: G = __r_arr[2]; let __v_67: G = __r_arr[3]; let __v_68: G = __r_arr[4]; let __v_69: G = __r_arr[5]; let __v_70: G = __r_arr[6]; let __v_71: G = __r_arr[7]; let __v_72: G = G::from_u64(2); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_72]; let __info = io_buffer.get_info(__v_72, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_73: G = __io_pair.0; let __v_74: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_72, __v_73, __v_74]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_75: G = __r_arr[0]; let __v_76: G = G::from_u64(103); let __v_77: G = G::from_u64(230); let __v_78: G = G::from_u64(9); let __v_79: G = G::from_u64(106); let __v_80: G = G::from_u64(133); let __v_81: G = G::from_u64(174); let __v_82: G = G::from_u64(187); let __v_83: G = G::from_u64(114); let __v_84: G = G::from_u64(243); let __v_85: G = G::from_u64(110); let __v_86: G = G::from_u64(60); let __v_87: G = G::from_u64(58); let __v_88: G = G::from_u64(245); let __v_89: G = G::from_u64(79); let __v_90: G = G::from_u64(165); let __v_91: G = G::from_u64(127); let __v_92: G = G::from_u64(82); let __v_93: G = G::from_u64(14); let __v_94: G = G::from_u64(81); let __v_95: G = G::from_u64(140); let __v_96: G = G::from_u64(104); let __v_97: G = G::from_u64(5); let __v_98: G = G::from_u64(155); let __v_99: G = G::from_u64(171); let __v_100: G = G::from_u64(217); let __v_101: G = G::from_u64(131); let __v_102: G = G::from_u64(31); let __v_103: G = G::from_u64(25); let __v_104: G = G::from_u64(205); let __v_105: G = G::from_u64(224); let __v_106: G = G::from_u64(91); let __v_107: G = { let __values: [G; 3] = [__v_45, __v_45, __v_45]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_108: G = { let __values: [G; 8] = [__v_36, __v_36, __v_36, __v_36, __v_36, __v_36, __v_36, __v_36]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_109: G = { let __values: [G; 32] = [__v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_76, __v_82, __v_83, __v_84, __v_85, __v_86, __v_87, __v_88, __v_89, __v_90, __v_91, __v_92, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_99, __v_100, __v_101, __v_102, __v_103, __v_104, __v_105, __v_106]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_110: G = { let __values: [G; 34] = [__v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_75, __v_107, __v_36, __v_36, __v_108, __v_109, __v_110]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_111: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_111]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_112: G = __r_arr[0]; let __v_113: G = __r_arr[1]; let __v_114: G = __r_arr[2]; let __v_115: G = __r_arr[3]; let __v_116: G = __r_arr[4]; let __v_117: G = __r_arr[5]; let __v_118: G = __r_arr[6]; let __v_119: G = __r_arr[7]; let __v_120: G = __r_arr[8]; let __v_121: G = __r_arr[9]; let __v_122: G = __r_arr[10]; let __v_123: G = __r_arr[11]; let __v_124: G = __r_arr[12]; let __v_125: G = __r_arr[13]; let __v_126: G = __r_arr[14]; let __v_127: G = __r_arr[15]; let __v_128: G = __r_arr[16]; let __v_129: G = __r_arr[17]; let __v_130: G = __r_arr[18]; let __v_131: G = __r_arr[19]; let __v_132: G = __r_arr[20]; let __v_133: G = __r_arr[21]; let __v_134: G = __r_arr[22]; let __v_135: G = __r_arr[23]; let __v_136: G = __r_arr[24]; let __v_137: G = __r_arr[25]; let __v_138: G = __r_arr[26]; let __v_139: G = __r_arr[27]; let __v_140: G = __r_arr[28]; let __v_141: G = __r_arr[29]; let __v_142: G = __r_arr[30]; let __v_143: G = __r_arr[31]; let __v_144: G = G::from_u64(256); let __v_145: G = __v_144 * __v_113; let __v_146: G = __v_112 + __v_145; let __v_147: G = G::from_u64(65536); let __v_148: G = __v_147 * __v_114; let __v_149: G = __v_146 + __v_148; let __v_150: G = G::from_u64(16777216); let __v_151: G = __v_150 * __v_115; let __v_152: G = __v_149 + __v_151; let __v_153: G = __v_144 * __v_117; let __v_154: G = __v_116 + __v_153; let __v_155: G = __v_147 * __v_118; let __v_156: G = __v_154 + __v_155; let __v_157: G = __v_150 * __v_119; let __v_158: G = __v_156 + __v_157; let __v_159: G = __v_144 * __v_121; let __v_160: G = __v_120 + __v_159; let __v_161: G = __v_147 * __v_122; let __v_162: G = __v_160 + __v_161; let __v_163: G = __v_150 * __v_123; let __v_164: G = __v_162 + __v_163; let __v_165: G = __v_144 * __v_125; let __v_166: G = __v_124 + __v_165; let __v_167: G = __v_147 * __v_126; let __v_168: G = __v_166 + __v_167; let __v_169: G = __v_150 * __v_127; let __v_170: G = __v_168 + __v_169; let __v_171: G = __v_144 * __v_129; let __v_172: G = __v_128 + __v_171; let __v_173: G = __v_147 * __v_130; let __v_174: G = __v_172 + __v_173; let __v_175: G = __v_150 * __v_131; let __v_176: G = __v_174 + __v_175; let __v_177: G = __v_144 * __v_133; let __v_178: G = __v_132 + __v_177; let __v_179: G = __v_147 * __v_134; let __v_180: G = __v_178 + __v_179; let __v_181: G = __v_150 * __v_135; let __v_182: G = __v_180 + __v_181; let __v_183: G = __v_144 * __v_137; let __v_184: G = __v_136 + __v_183; let __v_185: G = __v_147 * __v_138; let __v_186: G = __v_184 + __v_185; let __v_187: G = __v_150 * __v_139; let __v_188: G = __v_186 + __v_187; let __v_189: G = __v_144 * __v_141; let __v_190: G = __v_140 + __v_189; let __v_191: G = __v_147 * __v_142; let __v_192: G = __v_190 + __v_191; let __v_193: G = __v_150 * __v_143; let __v_194: G = __v_192 + __v_193; if (__v_152 != __v_28) { return Err(ExecError::AssertEqMismatch { lhs: __v_152.as_canonical_u64(), rhs: __v_28.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } if (__v_158 != __v_29) { return Err(ExecError::AssertEqMismatch { lhs: __v_158.as_canonical_u64(), rhs: __v_29.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } if (__v_164 != __v_30) { return Err(ExecError::AssertEqMismatch { lhs: __v_164.as_canonical_u64(), rhs: __v_30.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } if (__v_170 != __v_31) { return Err(ExecError::AssertEqMismatch { lhs: __v_170.as_canonical_u64(), rhs: __v_31.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } if (__v_176 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_176.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } if (__v_182 != __v_33) { return Err(ExecError::AssertEqMismatch { lhs: __v_182.as_canonical_u64(), rhs: __v_33.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } if (__v_188 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_188.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } if (__v_194 != __v_35) { return Err(ExecError::AssertEqMismatch { lhs: __v_194.as_canonical_u64(), rhs: __v_35.as_canonical_u64(), msg: Some("aggr: output claim digest mismatch".to_string()) }); } let __r_arr: [G; OUT_270] = { let __args: [G; IN_270] = [__v_75]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(270, &__args[..])?) { [G::ZERO; OUT_270] } else { let (__hash, __hit) = record.function_queries[270].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[270].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[270].bump_multiplicity(__i); } let __ret: [G; OUT_270] = unsafe { *(record.function_queries[270].output_at(__i).as_ptr() as *const [G; OUT_270]) }; __ret }, _ => { aiur_fn_270::(__args, __hash, record, io_buffer)? }, } } }; let __v_195: G = __r_arr[0]; let __v_196: G = __r_arr[1]; let __v_197: G = __r_arr[2]; let __v_198: G = __r_arr[3]; let __v_199: G = __r_arr[4]; let __v_200: G = __r_arr[5]; let __v_201: G = __r_arr[6]; let __v_202: G = __r_arr[7]; let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_55, __v_56, __v_57, __v_58, __v_59, __v_64, __v_65, __v_66, __v_67, __v_68]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_55, __v_56, __v_57, __v_58, __v_59, __v_195, __v_196, __v_197, __v_198, __v_199]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_257] = { let __args: [G; IN_257] = [__v_60]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(257, &__args[..])?) { [G::ZERO; OUT_257] } else { let (__hash, __hit) = record.function_queries[257].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[257].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[257].bump_multiplicity(__i); } let __ret: [G; OUT_257] = unsafe { *(record.function_queries[257].output_at(__i).as_ptr() as *const [G; OUT_257]) }; __ret }, _ => { aiur_fn_257::(__args, __hash, record, io_buffer)? }, } } }; let __v_203: G = __r_arr[0]; let __r_arr: [G; OUT_257] = { let __args: [G; IN_257] = [__v_69]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(257, &__args[..])?) { [G::ZERO; OUT_257] } else { let (__hash, __hit) = record.function_queries[257].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[257].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[257].bump_multiplicity(__i); } let __ret: [G; OUT_257] = unsafe { *(record.function_queries[257].output_at(__i).as_ptr() as *const [G; OUT_257]) }; __ret }, _ => { aiur_fn_257::(__args, __hash, record, io_buffer)? }, } } }; let __v_204: G = __r_arr[0]; let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_61, __v_62]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_205: G = __r_arr[0]; let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_70, __v_71]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_206: G = __r_arr[0]; let __r_arr: [G; OUT_257] = { let __args: [G; IN_257] = [__v_200]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(257, &__args[..])?) { [G::ZERO; OUT_257] } else { let (__hash, __hit) = record.function_queries[257].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[257].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[257].bump_multiplicity(__i); } let __ret: [G; OUT_257] = unsafe { *(record.function_queries[257].output_at(__i).as_ptr() as *const [G; OUT_257]) }; __ret }, _ => { aiur_fn_257::(__args, __hash, record, io_buffer)? }, } } }; let __v_207: G = __r_arr[0]; let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_201, __v_202]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_208: G = __r_arr[0]; let __r_arr: [G; OUT_262] = { let __args: [G; IN_262] = [__v_203, __v_205]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(262, &__args[..])?) { [G::ZERO; OUT_262] } else { let (__hash, __hit) = record.function_queries[262].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[262].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[262].bump_multiplicity(__i); } let __ret: [G; OUT_262] = unsafe { *(record.function_queries[262].output_at(__i).as_ptr() as *const [G; OUT_262]) }; __ret }, _ => { aiur_fn_262::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_262] = { let __args: [G; IN_262] = [__v_204, __v_206]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(262, &__args[..])?) { [G::ZERO; OUT_262] } else { let (__hash, __hit) = record.function_queries[262].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[262].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[262].bump_multiplicity(__i); } let __ret: [G; OUT_262] = unsafe { *(record.function_queries[262].output_at(__i).as_ptr() as *const [G; OUT_262]) }; __ret }, _ => { aiur_fn_262::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_261] = { let __args: [G; IN_261] = [__v_203, __v_204, __v_207]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(261, &__args[..])?) { [G::ZERO; OUT_261] } else { let (__hash, __hit) = record.function_queries[261].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[261].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[261].bump_multiplicity(__i); } let __ret: [G; OUT_261] = unsafe { *(record.function_queries[261].output_at(__i).as_ptr() as *const [G; OUT_261]) }; __ret }, _ => { aiur_fn_261::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_265] = { let __args: [G; IN_265] = [__v_205, __v_206, __v_207, __v_208]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(265, &__args[..])?) { [G::ZERO; OUT_265] } else { let (__hash, __hit) = record.function_queries[265].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[265].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[265].bump_multiplicity(__i); } let __ret: [G; OUT_265] = unsafe { *(record.function_queries[265].output_at(__i).as_ptr() as *const [G; OUT_265]) }; __ret }, _ => { aiur_fn_265::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_284] = []; record.function_queries[284].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_285: usize = 36; +const IN_285: usize = 36; +const OUT_285: usize = 0; +fn aiur_fn_285(inp: [G; IN_285], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_285], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = inp[34]; let __v_35: G = inp[35]; let __v_36: G = G::from_u64(0); let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_0, __v_36, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = __r_arr[1]; let __v_39: G = __r_arr[2]; let __v_40: G = __r_arr[3]; let __v_41: G = __r_arr[4]; let __v_42: G = __r_arr[5]; let __v_43: G = __r_arr[6]; let __v_44: G = __r_arr[7]; let __v_45: G = G::from_u64(1); let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_1, __v_45, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __v_46: G = __r_arr[0]; let __v_47: G = __r_arr[1]; let __v_48: G = __r_arr[2]; let __v_49: G = __r_arr[3]; let __v_50: G = __r_arr[4]; let __v_51: G = __r_arr[5]; let __v_52: G = __r_arr[6]; let __v_53: G = __r_arr[7]; let __r_arr: [G; OUT_275] = { let __args: [G; IN_275] = [__v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(275, &__args[..])?) { [G::ZERO; OUT_275] } else { let (__hash, __hit) = record.function_queries[275].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[275].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[275].bump_multiplicity(__i); } let __ret: [G; OUT_275] = unsafe { *(record.function_queries[275].output_at(__i).as_ptr() as *const [G; OUT_275]) }; __ret }, _ => { aiur_fn_275::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __r_arr: [G; OUT_270] = { let __args: [G; IN_270] = [__v_54]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(270, &__args[..])?) { [G::ZERO; OUT_270] } else { let (__hash, __hit) = record.function_queries[270].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[270].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[270].bump_multiplicity(__i); } let __ret: [G; OUT_270] = unsafe { *(record.function_queries[270].output_at(__i).as_ptr() as *const [G; OUT_270]) }; __ret }, _ => { aiur_fn_270::(__args, __hash, record, io_buffer)? }, } } }; let __v_55: G = __r_arr[0]; let __v_56: G = __r_arr[1]; let __v_57: G = __r_arr[2]; let __v_58: G = __r_arr[3]; let __v_59: G = __r_arr[4]; let __v_60: G = __r_arr[5]; let __v_61: G = __r_arr[6]; let __v_62: G = __r_arr[7]; let __r_arr: [G; OUT_275] = { let __args: [G; IN_275] = [__v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(275, &__args[..])?) { [G::ZERO; OUT_275] } else { let (__hash, __hit) = record.function_queries[275].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[275].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[275].bump_multiplicity(__i); } let __ret: [G; OUT_275] = unsafe { *(record.function_queries[275].output_at(__i).as_ptr() as *const [G; OUT_275]) }; __ret }, _ => { aiur_fn_275::(__args, __hash, record, io_buffer)? }, } } }; let __v_63: G = __r_arr[0]; let __r_arr: [G; OUT_270] = { let __args: [G; IN_270] = [__v_63]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(270, &__args[..])?) { [G::ZERO; OUT_270] } else { let (__hash, __hit) = record.function_queries[270].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[270].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[270].bump_multiplicity(__i); } let __ret: [G; OUT_270] = unsafe { *(record.function_queries[270].output_at(__i).as_ptr() as *const [G; OUT_270]) }; __ret }, _ => { aiur_fn_270::(__args, __hash, record, io_buffer)? }, } } }; let __v_64: G = __r_arr[0]; let __v_65: G = __r_arr[1]; let __v_66: G = __r_arr[2]; let __v_67: G = __r_arr[3]; let __v_68: G = __r_arr[4]; let __v_69: G = __r_arr[5]; let __v_70: G = __r_arr[6]; let __v_71: G = __r_arr[7]; let __v_72: G = G::from_u64(2); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_72]; let __info = io_buffer.get_info(__v_72, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_73: G = __io_pair.0; let __v_74: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_72, __v_73, __v_74]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_75: G = __r_arr[0]; let __v_76: G = G::from_u64(103); let __v_77: G = G::from_u64(230); let __v_78: G = G::from_u64(9); let __v_79: G = G::from_u64(106); let __v_80: G = G::from_u64(133); let __v_81: G = G::from_u64(174); let __v_82: G = G::from_u64(187); let __v_83: G = G::from_u64(114); let __v_84: G = G::from_u64(243); let __v_85: G = G::from_u64(110); let __v_86: G = G::from_u64(60); let __v_87: G = G::from_u64(58); let __v_88: G = G::from_u64(245); let __v_89: G = G::from_u64(79); let __v_90: G = G::from_u64(165); let __v_91: G = G::from_u64(127); let __v_92: G = G::from_u64(82); let __v_93: G = G::from_u64(14); let __v_94: G = G::from_u64(81); let __v_95: G = G::from_u64(140); let __v_96: G = G::from_u64(104); let __v_97: G = G::from_u64(5); let __v_98: G = G::from_u64(155); let __v_99: G = G::from_u64(171); let __v_100: G = G::from_u64(217); let __v_101: G = G::from_u64(131); let __v_102: G = G::from_u64(31); let __v_103: G = G::from_u64(25); let __v_104: G = G::from_u64(205); let __v_105: G = G::from_u64(224); let __v_106: G = G::from_u64(91); let __v_107: G = { let __values: [G; 3] = [__v_45, __v_45, __v_45]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_108: G = { let __values: [G; 8] = [__v_36, __v_36, __v_36, __v_36, __v_36, __v_36, __v_36, __v_36]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_109: G = { let __values: [G; 32] = [__v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_76, __v_82, __v_83, __v_84, __v_85, __v_86, __v_87, __v_88, __v_89, __v_90, __v_91, __v_92, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_99, __v_100, __v_101, __v_102, __v_103, __v_104, __v_105, __v_106]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_110: G = { let __values: [G; 34] = [__v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_75, __v_107, __v_36, __v_36, __v_108, __v_109, __v_110]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_111: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_111]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_112: G = __r_arr[0]; let __v_113: G = __r_arr[1]; let __v_114: G = __r_arr[2]; let __v_115: G = __r_arr[3]; let __v_116: G = __r_arr[4]; let __v_117: G = __r_arr[5]; let __v_118: G = __r_arr[6]; let __v_119: G = __r_arr[7]; let __v_120: G = __r_arr[8]; let __v_121: G = __r_arr[9]; let __v_122: G = __r_arr[10]; let __v_123: G = __r_arr[11]; let __v_124: G = __r_arr[12]; let __v_125: G = __r_arr[13]; let __v_126: G = __r_arr[14]; let __v_127: G = __r_arr[15]; let __v_128: G = __r_arr[16]; let __v_129: G = __r_arr[17]; let __v_130: G = __r_arr[18]; let __v_131: G = __r_arr[19]; let __v_132: G = __r_arr[20]; let __v_133: G = __r_arr[21]; let __v_134: G = __r_arr[22]; let __v_135: G = __r_arr[23]; let __v_136: G = __r_arr[24]; let __v_137: G = __r_arr[25]; let __v_138: G = __r_arr[26]; let __v_139: G = __r_arr[27]; let __v_140: G = __r_arr[28]; let __v_141: G = __r_arr[29]; let __v_142: G = __r_arr[30]; let __v_143: G = __r_arr[31]; let __v_144: G = G::from_u64(256); let __v_145: G = __v_144 * __v_113; let __v_146: G = __v_112 + __v_145; let __v_147: G = G::from_u64(65536); let __v_148: G = __v_147 * __v_114; let __v_149: G = __v_146 + __v_148; let __v_150: G = G::from_u64(16777216); let __v_151: G = __v_150 * __v_115; let __v_152: G = __v_149 + __v_151; let __v_153: G = __v_144 * __v_117; let __v_154: G = __v_116 + __v_153; let __v_155: G = __v_147 * __v_118; let __v_156: G = __v_154 + __v_155; let __v_157: G = __v_150 * __v_119; let __v_158: G = __v_156 + __v_157; let __v_159: G = __v_144 * __v_121; let __v_160: G = __v_120 + __v_159; let __v_161: G = __v_147 * __v_122; let __v_162: G = __v_160 + __v_161; let __v_163: G = __v_150 * __v_123; let __v_164: G = __v_162 + __v_163; let __v_165: G = __v_144 * __v_125; let __v_166: G = __v_124 + __v_165; let __v_167: G = __v_147 * __v_126; let __v_168: G = __v_166 + __v_167; let __v_169: G = __v_150 * __v_127; let __v_170: G = __v_168 + __v_169; let __v_171: G = __v_144 * __v_129; let __v_172: G = __v_128 + __v_171; let __v_173: G = __v_147 * __v_130; let __v_174: G = __v_172 + __v_173; let __v_175: G = __v_150 * __v_131; let __v_176: G = __v_174 + __v_175; let __v_177: G = __v_144 * __v_133; let __v_178: G = __v_132 + __v_177; let __v_179: G = __v_147 * __v_134; let __v_180: G = __v_178 + __v_179; let __v_181: G = __v_150 * __v_135; let __v_182: G = __v_180 + __v_181; let __v_183: G = __v_144 * __v_137; let __v_184: G = __v_136 + __v_183; let __v_185: G = __v_147 * __v_138; let __v_186: G = __v_184 + __v_185; let __v_187: G = __v_150 * __v_139; let __v_188: G = __v_186 + __v_187; let __v_189: G = __v_144 * __v_141; let __v_190: G = __v_140 + __v_189; let __v_191: G = __v_147 * __v_142; let __v_192: G = __v_190 + __v_191; let __v_193: G = __v_150 * __v_143; let __v_194: G = __v_192 + __v_193; if (__v_152 != __v_28) { return Err(ExecError::AssertEqMismatch { lhs: __v_152.as_canonical_u64(), rhs: __v_28.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } if (__v_158 != __v_29) { return Err(ExecError::AssertEqMismatch { lhs: __v_158.as_canonical_u64(), rhs: __v_29.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } if (__v_164 != __v_30) { return Err(ExecError::AssertEqMismatch { lhs: __v_164.as_canonical_u64(), rhs: __v_30.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } if (__v_170 != __v_31) { return Err(ExecError::AssertEqMismatch { lhs: __v_170.as_canonical_u64(), rhs: __v_31.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } if (__v_176 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_176.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } if (__v_182 != __v_33) { return Err(ExecError::AssertEqMismatch { lhs: __v_182.as_canonical_u64(), rhs: __v_33.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } if (__v_188 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_188.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } if (__v_194 != __v_35) { return Err(ExecError::AssertEqMismatch { lhs: __v_194.as_canonical_u64(), rhs: __v_35.as_canonical_u64(), msg: Some("aggr structural: output claim digest mismatch".to_string()) }); } let __r_arr: [G; OUT_270] = { let __args: [G; IN_270] = [__v_75]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(270, &__args[..])?) { [G::ZERO; OUT_270] } else { let (__hash, __hit) = record.function_queries[270].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[270].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[270].bump_multiplicity(__i); } let __ret: [G; OUT_270] = unsafe { *(record.function_queries[270].output_at(__i).as_ptr() as *const [G; OUT_270]) }; __ret }, _ => { aiur_fn_270::(__args, __hash, record, io_buffer)? }, } } }; let __v_195: G = __r_arr[0]; let __v_196: G = __r_arr[1]; let __v_197: G = __r_arr[2]; let __v_198: G = __r_arr[3]; let __v_199: G = __r_arr[4]; let __v_200: G = __r_arr[5]; let __v_201: G = __r_arr[6]; let __v_202: G = __r_arr[7]; let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_55, __v_56, __v_57, __v_58, __v_59, __v_64, __v_65, __v_66, __v_67, __v_68]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_55, __v_56, __v_57, __v_58, __v_59, __v_195, __v_196, __v_197, __v_198, __v_199]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_60, __v_69]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_203: G = __r_arr[0]; if (__v_203 != __v_36) { return Err(ExecError::AssertEqMismatch { lhs: __v_203.as_canonical_u64(), rhs: __v_36.as_canonical_u64(), msg: Some("aggr structural: child subject roots must be distinct".to_string()) }); } let __r_arr: [G; OUT_247] = { let __args: [G; IN_247] = [__v_60, __v_69]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(247, &__args[..])?) { [G::ZERO; OUT_247] } else { let (__hash, __hit) = record.function_queries[247].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[247].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[247].bump_multiplicity(__i); } let __ret: [G; OUT_247] = unsafe { *(record.function_queries[247].output_at(__i).as_ptr() as *const [G; OUT_247]) }; __ret }, _ => { aiur_fn_247::(__args, __hash, record, io_buffer)? }, } } }; let __v_204: G = __r_arr[0]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_200, __v_204]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_205: G = __r_arr[0]; if (__v_205 != __v_45) { return Err(ExecError::AssertEqMismatch { lhs: __v_205.as_canonical_u64(), rhs: __v_45.as_canonical_u64(), msg: Some("aggr structural: output subject root is not nodeHash(left, right)".to_string()) }); } let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_61, __v_62]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_206: G = __r_arr[0]; let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_70, __v_71]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_207: G = __r_arr[0]; let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_201, __v_202]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_208: G = __r_arr[0]; let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_206, __v_207, __v_60, __v_69, __v_208]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_285] = []; record.function_queries[285].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_286: usize = 35; +const IN_286: usize = 35; +const OUT_286: usize = 0; +fn aiur_fn_286(inp: [G; IN_286], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_286], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = inp[34]; let __v_35: G = G::from_u64(0); let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_0, __v_35, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __v_38: G = __r_arr[2]; let __v_39: G = __r_arr[3]; let __v_40: G = __r_arr[4]; let __v_41: G = __r_arr[5]; let __v_42: G = __r_arr[6]; let __v_43: G = __r_arr[7]; let __v_44: G = G::from_u64(1); let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_35, __v_44, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __v_45: G = __r_arr[0]; let __v_46: G = __r_arr[1]; let __v_47: G = __r_arr[2]; let __v_48: G = __r_arr[3]; let __v_49: G = __r_arr[4]; let __v_50: G = __r_arr[5]; let __v_51: G = __r_arr[6]; let __v_52: G = __r_arr[7]; let __r_arr: [G; OUT_275] = { let __args: [G; IN_275] = [__v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(275, &__args[..])?) { [G::ZERO; OUT_275] } else { let (__hash, __hit) = record.function_queries[275].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[275].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[275].bump_multiplicity(__i); } let __ret: [G; OUT_275] = unsafe { *(record.function_queries[275].output_at(__i).as_ptr() as *const [G; OUT_275]) }; __ret }, _ => { aiur_fn_275::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; let __r_arr: [G; OUT_270] = { let __args: [G; IN_270] = [__v_53]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(270, &__args[..])?) { [G::ZERO; OUT_270] } else { let (__hash, __hit) = record.function_queries[270].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[270].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[270].bump_multiplicity(__i); } let __ret: [G; OUT_270] = unsafe { *(record.function_queries[270].output_at(__i).as_ptr() as *const [G; OUT_270]) }; __ret }, _ => { aiur_fn_270::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __v_55: G = __r_arr[1]; let __v_56: G = __r_arr[2]; let __v_57: G = __r_arr[3]; let __v_58: G = __r_arr[4]; let __v_59: G = __r_arr[5]; let __v_60: G = __r_arr[6]; let __v_61: G = __r_arr[7]; let __v_62: G = G::from_u64(4); if (__v_58 != __v_62) { return Err(ExecError::AssertEqMismatch { lhs: __v_58.as_canonical_u64(), rhs: __v_62.as_canonical_u64(), msg: Some("aggr discharge: the claim does not assume its binding".to_string()) }); } let __r_arr: [G; OUT_275] = { let __args: [G; IN_275] = [__v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(275, &__args[..])?) { [G::ZERO; OUT_275] } else { let (__hash, __hit) = record.function_queries[275].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[275].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[275].bump_multiplicity(__i); } let __ret: [G; OUT_275] = unsafe { *(record.function_queries[275].output_at(__i).as_ptr() as *const [G; OUT_275]) }; __ret }, _ => { aiur_fn_275::(__args, __hash, record, io_buffer)? }, } } }; let __v_63: G = __r_arr[0]; let __r_arr: [G; OUT_271] = { let __args: [G; IN_271] = [__v_63]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(271, &__args[..])?) { [G::ZERO; OUT_271] } else { let (__hash, __hit) = record.function_queries[271].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[271].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[271].bump_multiplicity(__i); } let __ret: [G; OUT_271] = unsafe { *(record.function_queries[271].output_at(__i).as_ptr() as *const [G; OUT_271]) }; __ret }, _ => { aiur_fn_271::(__args, __hash, record, io_buffer)? }, } } }; let __v_64: G = __r_arr[0]; let __v_65: G = __r_arr[1]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_54, __v_64]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_66: G = __r_arr[0]; if (__v_66 != __v_44) { return Err(ExecError::AssertEqMismatch { lhs: __v_66.as_canonical_u64(), rhs: __v_44.as_canonical_u64(), msg: Some("aggr discharge: the binding claim is for another specification digest".to_string()) }); } let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_55, __v_65]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_67: G = __r_arr[0]; if (__v_67 != __v_44) { return Err(ExecError::AssertEqMismatch { lhs: __v_67.as_canonical_u64(), rhs: __v_44.as_canonical_u64(), msg: Some("aggr discharge: the binding claim is for another binding".to_string()) }); } let __v_68: G = G::from_u64(2); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_68]; let __info = io_buffer.get_info(__v_68, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_69: G = __io_pair.0; let __v_70: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_68, __v_69, __v_70]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_71: G = __r_arr[0]; let __v_72: G = G::from_u64(103); let __v_73: G = G::from_u64(230); let __v_74: G = G::from_u64(9); let __v_75: G = G::from_u64(106); let __v_76: G = G::from_u64(133); let __v_77: G = G::from_u64(174); let __v_78: G = G::from_u64(187); let __v_79: G = G::from_u64(114); let __v_80: G = G::from_u64(243); let __v_81: G = G::from_u64(110); let __v_82: G = G::from_u64(60); let __v_83: G = G::from_u64(58); let __v_84: G = G::from_u64(245); let __v_85: G = G::from_u64(79); let __v_86: G = G::from_u64(165); let __v_87: G = G::from_u64(127); let __v_88: G = G::from_u64(82); let __v_89: G = G::from_u64(14); let __v_90: G = G::from_u64(81); let __v_91: G = G::from_u64(140); let __v_92: G = G::from_u64(104); let __v_93: G = G::from_u64(5); let __v_94: G = G::from_u64(155); let __v_95: G = G::from_u64(171); let __v_96: G = G::from_u64(217); let __v_97: G = G::from_u64(131); let __v_98: G = G::from_u64(31); let __v_99: G = G::from_u64(25); let __v_100: G = G::from_u64(205); let __v_101: G = G::from_u64(224); let __v_102: G = G::from_u64(91); let __v_103: G = { let __values: [G; 3] = [__v_44, __v_44, __v_44]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_104: G = { let __values: [G; 8] = [__v_35, __v_35, __v_35, __v_35, __v_35, __v_35, __v_35, __v_35]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_105: G = { let __values: [G; 32] = [__v_72, __v_73, __v_74, __v_75, __v_76, __v_77, __v_72, __v_78, __v_79, __v_80, __v_81, __v_82, __v_83, __v_84, __v_85, __v_86, __v_87, __v_88, __v_89, __v_90, __v_91, __v_92, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_99, __v_100, __v_101, __v_102]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_106: G = { let __values: [G; 34] = [__v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44, __v_44]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_71, __v_103, __v_35, __v_35, __v_104, __v_105, __v_106]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_107: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_107]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_108: G = __r_arr[0]; let __v_109: G = __r_arr[1]; let __v_110: G = __r_arr[2]; let __v_111: G = __r_arr[3]; let __v_112: G = __r_arr[4]; let __v_113: G = __r_arr[5]; let __v_114: G = __r_arr[6]; let __v_115: G = __r_arr[7]; let __v_116: G = __r_arr[8]; let __v_117: G = __r_arr[9]; let __v_118: G = __r_arr[10]; let __v_119: G = __r_arr[11]; let __v_120: G = __r_arr[12]; let __v_121: G = __r_arr[13]; let __v_122: G = __r_arr[14]; let __v_123: G = __r_arr[15]; let __v_124: G = __r_arr[16]; let __v_125: G = __r_arr[17]; let __v_126: G = __r_arr[18]; let __v_127: G = __r_arr[19]; let __v_128: G = __r_arr[20]; let __v_129: G = __r_arr[21]; let __v_130: G = __r_arr[22]; let __v_131: G = __r_arr[23]; let __v_132: G = __r_arr[24]; let __v_133: G = __r_arr[25]; let __v_134: G = __r_arr[26]; let __v_135: G = __r_arr[27]; let __v_136: G = __r_arr[28]; let __v_137: G = __r_arr[29]; let __v_138: G = __r_arr[30]; let __v_139: G = __r_arr[31]; let __v_140: G = G::from_u64(256); let __v_141: G = __v_140 * __v_109; let __v_142: G = __v_108 + __v_141; let __v_143: G = G::from_u64(65536); let __v_144: G = __v_143 * __v_110; let __v_145: G = __v_142 + __v_144; let __v_146: G = G::from_u64(16777216); let __v_147: G = __v_146 * __v_111; let __v_148: G = __v_145 + __v_147; let __v_149: G = __v_140 * __v_113; let __v_150: G = __v_112 + __v_149; let __v_151: G = __v_143 * __v_114; let __v_152: G = __v_150 + __v_151; let __v_153: G = __v_146 * __v_115; let __v_154: G = __v_152 + __v_153; let __v_155: G = __v_140 * __v_117; let __v_156: G = __v_116 + __v_155; let __v_157: G = __v_143 * __v_118; let __v_158: G = __v_156 + __v_157; let __v_159: G = __v_146 * __v_119; let __v_160: G = __v_158 + __v_159; let __v_161: G = __v_140 * __v_121; let __v_162: G = __v_120 + __v_161; let __v_163: G = __v_143 * __v_122; let __v_164: G = __v_162 + __v_163; let __v_165: G = __v_146 * __v_123; let __v_166: G = __v_164 + __v_165; let __v_167: G = __v_140 * __v_125; let __v_168: G = __v_124 + __v_167; let __v_169: G = __v_143 * __v_126; let __v_170: G = __v_168 + __v_169; let __v_171: G = __v_146 * __v_127; let __v_172: G = __v_170 + __v_171; let __v_173: G = __v_140 * __v_129; let __v_174: G = __v_128 + __v_173; let __v_175: G = __v_143 * __v_130; let __v_176: G = __v_174 + __v_175; let __v_177: G = __v_146 * __v_131; let __v_178: G = __v_176 + __v_177; let __v_179: G = __v_140 * __v_133; let __v_180: G = __v_132 + __v_179; let __v_181: G = __v_143 * __v_134; let __v_182: G = __v_180 + __v_181; let __v_183: G = __v_146 * __v_135; let __v_184: G = __v_182 + __v_183; let __v_185: G = __v_140 * __v_137; let __v_186: G = __v_136 + __v_185; let __v_187: G = __v_143 * __v_138; let __v_188: G = __v_186 + __v_187; let __v_189: G = __v_146 * __v_139; let __v_190: G = __v_188 + __v_189; if (__v_148 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_148.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } if (__v_154 != __v_28) { return Err(ExecError::AssertEqMismatch { lhs: __v_154.as_canonical_u64(), rhs: __v_28.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } if (__v_160 != __v_29) { return Err(ExecError::AssertEqMismatch { lhs: __v_160.as_canonical_u64(), rhs: __v_29.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } if (__v_166 != __v_30) { return Err(ExecError::AssertEqMismatch { lhs: __v_166.as_canonical_u64(), rhs: __v_30.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } if (__v_172 != __v_31) { return Err(ExecError::AssertEqMismatch { lhs: __v_172.as_canonical_u64(), rhs: __v_31.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } if (__v_178 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_178.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } if (__v_184 != __v_33) { return Err(ExecError::AssertEqMismatch { lhs: __v_184.as_canonical_u64(), rhs: __v_33.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } if (__v_190 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_190.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: Some("aggr discharge: output claim digest mismatch".to_string()) }); } let __r_arr: [G; OUT_270] = { let __args: [G; IN_270] = [__v_71]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(270, &__args[..])?) { [G::ZERO; OUT_270] } else { let (__hash, __hit) = record.function_queries[270].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[270].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[270].bump_multiplicity(__i); } let __ret: [G; OUT_270] = unsafe { *(record.function_queries[270].output_at(__i).as_ptr() as *const [G; OUT_270]) }; __ret }, _ => { aiur_fn_270::(__args, __hash, record, io_buffer)? }, } } }; let __v_191: G = __r_arr[0]; let __v_192: G = __r_arr[1]; let __v_193: G = __r_arr[2]; let __v_194: G = __r_arr[3]; let __v_195: G = __r_arr[4]; let __v_196: G = __r_arr[5]; let __v_197: G = __r_arr[6]; let __v_198: G = __r_arr[7]; let __v_199: G = G::from_u64(3); let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_54, __v_55, __v_56, __v_57, __v_199, __v_191, __v_192, __v_193, __v_194, __v_195]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_59, __v_196]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_200: G = __r_arr[0]; if (__v_200 != __v_44) { return Err(ExecError::AssertEqMismatch { lhs: __v_200.as_canonical_u64(), rhs: __v_44.as_canonical_u64(), msg: Some("aggr discharge: output subject root differs from the child's".to_string()) }); } let __r_arr: [G; OUT_273] = { let __args: [G; IN_273] = [__v_60, __v_61, __v_197, __v_198]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(273, &__args[..])?) { [G::ZERO; OUT_273] } else { let (__hash, __hit) = record.function_queries[273].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[273].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[273].bump_multiplicity(__i); } let __ret: [G; OUT_273] = unsafe { *(record.function_queries[273].output_at(__i).as_ptr() as *const [G; OUT_273]) }; __ret }, _ => { aiur_fn_273::(__args, __hash, record, io_buffer)? }, } } }; let __v_201: G = __r_arr[0]; if (__v_201 != __v_44) { return Err(ExecError::AssertEqMismatch { lhs: __v_201.as_canonical_u64(), rhs: __v_44.as_canonical_u64(), msg: Some("aggr discharge: output assumptions differ from the child's".to_string()) }); } let __ret: [G; OUT_286] = []; record.function_queries[286].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_287: usize = 1; +const IN_287: usize = 1; const OUT_287: usize = 1; -fn aiur_fn_287(inp: [G; IN_287], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_287], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; match __v_2.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_287] = [__v_1]; record.function_queries[287].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_287] = { let __args: [G; IN_287] = [__v_4, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(287, &__args[..])?) { [G::ZERO; OUT_287] } else { let (__hash, __hit) = record.function_queries[287].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[287].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[287].bump_multiplicity(__i); } let __ret: [G; OUT_287] = unsafe { *(record.function_queries[287].output_at(__i).as_ptr() as *const [G; OUT_287]) }; __ret }, _ => { aiur_fn_287::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __r_arr: [G; OUT_245] = { let __args: [G; IN_245] = [__v_3, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(245, &__args[..])?) { [G::ZERO; OUT_245] } else { let (__hash, __hit) = record.function_queries[245].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[245].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[245].bump_multiplicity(__i); } let __ret: [G; OUT_245] = unsafe { *(record.function_queries[245].output_at(__i).as_ptr() as *const [G; OUT_245]) }; __ret }, _ => { aiur_fn_245::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __ret: [G; OUT_287] = [__v_6]; record.function_queries[287].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } -const INPUT_SIZE_288: usize = 1; -const IN_288: usize = 1; +fn aiur_fn_287(inp: [G; IN_287], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_287], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; match __v_0.as_canonical_u64() { 0u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_287] = [__v_1]; record.function_queries[287].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_287] = [__v_1]; record.function_queries[287].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 2u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_287] = [__v_1]; record.function_queries[287].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 29u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_287] = [__v_1]; record.function_queries[287].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 30u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_287] = [__v_1]; record.function_queries[287].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 31u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_287] = [__v_1]; record.function_queries[287].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 37u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_287] = [__v_1]; record.function_queries[287].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 38u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_287] = [__v_1]; record.function_queries[287].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 39u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_287] = [__v_1]; record.function_queries[287].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 40u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_287] = [__v_1]; record.function_queries[287].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 56u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_287] = [__v_1]; record.function_queries[287].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 57u64 => { let __v_1: G = G::from_u64(1); let __ret: [G; OUT_287] = [__v_1]; record.function_queries[287].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_1: G = G::from_u64(0); let __ret: [G; OUT_287] = [__v_1]; record.function_queries[287].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } +const INPUT_SIZE_288: usize = 3; +const IN_288: usize = 3; const OUT_288: usize = 1; -fn aiur_fn_288(inp: [G; IN_288], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_288], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_246] = { let __args: [G; IN_246] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(246, &__args[..])?) { [G::ZERO; OUT_246] } else { let (__hash, __hit) = record.function_queries[246].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[246].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[246].bump_multiplicity(__i); } let __ret: [G; OUT_246] = unsafe { *(record.function_queries[246].output_at(__i).as_ptr() as *const [G; OUT_246]) }; __ret }, _ => { aiur_fn_246::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __v_3: G = __r_arr[2]; let __v_4: G = __r_arr[3]; let __v_5: G = __r_arr[4]; let __v_6: G = __r_arr[5]; let __v_7: G = __r_arr[6]; let __v_8: G = __r_arr[7]; let __r_arr: [G; OUT_273] = { let __args: [G; IN_273] = [__v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(273, &__args[..])?) { [G::ZERO; OUT_273] } else { let (__hash, __hit) = record.function_queries[273].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[273].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[273].bump_multiplicity(__i); } let __ret: [G; OUT_273] = unsafe { *(record.function_queries[273].output_at(__i).as_ptr() as *const [G; OUT_273]) }; __ret }, _ => { aiur_fn_273::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = __r_arr[1]; let __v_12: G = G::from_u64(232); if (__v_10 != __v_12) { return Err(ExecError::AssertEqMismatch { lhs: __v_10.as_canonical_u64(), rhs: __v_12.as_canonical_u64(), msg: Some("aggr cross-binding: not a role binding section".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __v_15: G = G::from_u64(3); if (__v_13 != __v_15) { return Err(ExecError::AssertEqMismatch { lhs: __v_13.as_canonical_u64(), rhs: __v_15.as_canonical_u64(), msg: Some("aggr cross-binding: not a role binding section".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = __r_arr[1]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __v_19: G = __r_arr[1]; let __v_20: G = G::from_u64(103); if (__v_18 != __v_20) { return Err(ExecError::AssertEqMismatch { lhs: __v_18.as_canonical_u64(), rhs: __v_20.as_canonical_u64(), msg: Some("aggr cross-binding: the binding does not bind every role".to_string()) }); } let __v_21: G = G::from_u64(0); let __r_arr: [G; OUT_286] = { let __args: [G; IN_286] = [__v_19, __v_21, __v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(286, &__args[..])?) { [G::ZERO; OUT_286] } else { let (__hash, __hit) = record.function_queries[286].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[286].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[286].bump_multiplicity(__i); } let __ret: [G; OUT_286] = unsafe { *(record.function_queries[286].output_at(__i).as_ptr() as *const [G; OUT_286]) }; __ret }, _ => { aiur_fn_286::(__args, __hash, record, io_buffer)? }, } } }; let __v_22: G = __r_arr[0]; let __v_23: G = G::from_u64(1); let __v_24: G = { let __values: [G; 3] = [__v_23, __v_23, __v_23]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_287] = { let __args: [G; IN_287] = [__v_22, __v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(287, &__args[..])?) { [G::ZERO; OUT_287] } else { let (__hash, __hit) = record.function_queries[287].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[287].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[287].bump_multiplicity(__i); } let __ret: [G; OUT_287] = unsafe { *(record.function_queries[287].output_at(__i).as_ptr() as *const [G; OUT_287]) }; __ret }, _ => { aiur_fn_287::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __v_26: G = G::from_u64(4); let __v_27: G = { let __values: [G; 3] = [__v_21, __v_26, __v_25]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_28: G = { let __values: [G; 3] = [__v_21, __v_12, __v_27]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_75] = { let __args: [G; IN_75] = [__v_28]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(75, &__args[..])?) { [G::ZERO; OUT_75] } else { let (__hash, __hit) = record.function_queries[75].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[75].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[75].bump_multiplicity(__i); } let __ret: [G; OUT_75] = unsafe { *(record.function_queries[75].output_at(__i).as_ptr() as *const [G; OUT_75]) }; __ret }, _ => { aiur_fn_75::(__args, __hash, record, io_buffer)? }, } } }; let __v_29: G = __r_arr[0]; let __ret: [G; OUT_288] = [__v_29]; record.function_queries[288].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_289: usize = 5; -const IN_289: usize = 5; +fn aiur_fn_288(inp: [G; IN_288], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_288], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; match __v_2.as_canonical_u64() { 0u64 => { let __v_3: G = G::from_u64(1); let __v_4: G = { let __values: [G; 3] = [__v_3, __v_3, __v_3]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_288] = [__v_4]; record.function_queries[288].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __v_4: G = __r_arr[1]; if (__v_3 != __v_1) { return Err(ExecError::AssertEqMismatch { lhs: __v_3.as_canonical_u64(), rhs: __v_1.as_canonical_u64(), msg: Some("aggr cross-binding: the binding does not bind every role in order".to_string()) }); } let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __r_arr[1]; let __v_7: G = G::from_u64(1); let __v_8: G = __v_1 + __v_7; let __v_9: G = __v_2 - __v_7; let __r_arr: [G; OUT_288] = { let __args: [G; IN_288] = [__v_6, __v_8, __v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(288, &__args[..])?) { [G::ZERO; OUT_288] } else { let (__hash, __hit) = record.function_queries[288].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[288].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[288].bump_multiplicity(__i); } let __ret: [G; OUT_288] = unsafe { *(record.function_queries[288].output_at(__i).as_ptr() as *const [G; OUT_288]) }; __ret }, _ => { aiur_fn_288::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __r_arr: [G; OUT_287] = { let __args: [G; IN_287] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(287, &__args[..])?) { [G::ZERO; OUT_287] } else { let (__hash, __hit) = record.function_queries[287].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[287].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[287].bump_multiplicity(__i); } let __ret: [G; OUT_287] = unsafe { *(record.function_queries[287].output_at(__i).as_ptr() as *const [G; OUT_287]) }; __ret }, _ => { aiur_fn_287::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; match __v_11.as_canonical_u64() { 1u64 => { let __v_12: G = G::from_u64(0); let __v_13: G = { let __values: [G; 3] = [__v_12, __v_5, __v_10]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_288] = [__v_13]; record.function_queries[288].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __ret: [G; OUT_288] = [__v_10]; record.function_queries[288].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, } }) } +const INPUT_SIZE_289: usize = 2; +const IN_289: usize = 2; const OUT_289: usize = 1; -fn aiur_fn_289(inp: [G; IN_289], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_289], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = G::from_u64(3); let __v_6: G = __v_4 - __v_5; let __v_7: G = G::from_u64(5); let __v_8: G = __v_4 - __v_7; let __v_9: G = __v_6 * __v_8; let __v_10: G = G::from_u64(0); if (__v_9 != __v_10) { return Err(ExecError::AssertEqMismatch { lhs: __v_9.as_canonical_u64(), rhs: __v_10.as_canonical_u64(), msg: Some("aggr cross-binding: the claim assumes its binding".to_string()) }); } match __v_4.as_canonical_u64() { 5u64 => { let __ret: [G; OUT_289] = [__v_1]; record.function_queries[289].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_288] = { let __args: [G; IN_288] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(288, &__args[..])?) { [G::ZERO; OUT_288] } else { let (__hash, __hit) = record.function_queries[288].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[288].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[288].bump_multiplicity(__i); } let __ret: [G; OUT_288] = unsafe { *(record.function_queries[288].output_at(__i).as_ptr() as *const [G; OUT_288]) }; __ret }, _ => { aiur_fn_288::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __ret: [G; OUT_289] = [__v_11]; record.function_queries[289].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } -const INPUT_SIZE_290: usize = 34; -const IN_290: usize = 34; -const OUT_290: usize = 0; -fn aiur_fn_290(inp: [G; IN_290], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_290], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = G::from_u64(1); let __v_35: G = G::from_u64(0); let __r_arr: [G; OUT_280] = { let __args: [G; IN_280] = [__v_34, __v_35, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(280, &__args[..])?) { [G::ZERO; OUT_280] } else { let (__hash, __hit) = record.function_queries[280].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[280].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[280].bump_multiplicity(__i); } let __ret: [G; OUT_280] = unsafe { *(record.function_queries[280].output_at(__i).as_ptr() as *const [G; OUT_280]) }; __ret }, _ => { aiur_fn_280::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __v_38: G = __r_arr[2]; let __v_39: G = __r_arr[3]; let __v_40: G = __r_arr[4]; let __v_41: G = __r_arr[5]; let __v_42: G = __r_arr[6]; let __v_43: G = __r_arr[7]; let __r_arr: [G; OUT_280] = { let __args: [G; IN_280] = [__v_34, __v_34, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(280, &__args[..])?) { [G::ZERO; OUT_280] } else { let (__hash, __hit) = record.function_queries[280].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[280].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[280].bump_multiplicity(__i); } let __ret: [G; OUT_280] = unsafe { *(record.function_queries[280].output_at(__i).as_ptr() as *const [G; OUT_280]) }; __ret }, _ => { aiur_fn_280::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; let __v_45: G = __r_arr[1]; let __v_46: G = __r_arr[2]; let __v_47: G = __r_arr[3]; let __v_48: G = __r_arr[4]; let __v_49: G = __r_arr[5]; let __v_50: G = __r_arr[6]; let __v_51: G = __r_arr[7]; let __r_arr: [G; OUT_273] = { let __args: [G; IN_273] = [__v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(273, &__args[..])?) { [G::ZERO; OUT_273] } else { let (__hash, __hit) = record.function_queries[273].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[273].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[273].bump_multiplicity(__i); } let __ret: [G; OUT_273] = unsafe { *(record.function_queries[273].output_at(__i).as_ptr() as *const [G; OUT_273]) }; __ret }, _ => { aiur_fn_273::(__args, __hash, record, io_buffer)? }, } } }; let __v_52: G = __r_arr[0]; let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_52]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; let __v_54: G = __r_arr[1]; let __v_55: G = __r_arr[2]; let __v_56: G = __r_arr[3]; let __v_57: G = __r_arr[4]; let __v_58: G = __r_arr[5]; let __v_59: G = __r_arr[6]; let __v_60: G = __r_arr[7]; let __r_arr: [G; OUT_273] = { let __args: [G; IN_273] = [__v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(273, &__args[..])?) { [G::ZERO; OUT_273] } else { let (__hash, __hit) = record.function_queries[273].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[273].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[273].bump_multiplicity(__i); } let __ret: [G; OUT_273] = unsafe { *(record.function_queries[273].output_at(__i).as_ptr() as *const [G; OUT_273]) }; __ret }, _ => { aiur_fn_273::(__args, __hash, record, io_buffer)? }, } } }; let __v_61: G = __r_arr[0]; let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_61]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __v_62: G = __r_arr[0]; let __v_63: G = __r_arr[1]; let __v_64: G = __r_arr[2]; let __v_65: G = __r_arr[3]; let __v_66: G = __r_arr[4]; let __v_67: G = __r_arr[5]; let __v_68: G = __r_arr[6]; let __v_69: G = __r_arr[7]; let __r_arr: [G; OUT_289] = { let __args: [G; IN_289] = [__v_53, __v_54, __v_55, __v_56, __v_57]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(289, &__args[..])?) { [G::ZERO; OUT_289] } else { let (__hash, __hit) = record.function_queries[289].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[289].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[289].bump_multiplicity(__i); } let __ret: [G; OUT_289] = unsafe { *(record.function_queries[289].output_at(__i).as_ptr() as *const [G; OUT_289]) }; __ret }, _ => { aiur_fn_289::(__args, __hash, record, io_buffer)? }, } } }; let __v_70: G = __r_arr[0]; let __r_arr: [G; OUT_289] = { let __args: [G; IN_289] = [__v_62, __v_63, __v_64, __v_65, __v_66]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(289, &__args[..])?) { [G::ZERO; OUT_289] } else { let (__hash, __hit) = record.function_queries[289].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[289].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[289].bump_multiplicity(__i); } let __ret: [G; OUT_289] = unsafe { *(record.function_queries[289].output_at(__i).as_ptr() as *const [G; OUT_289]) }; __ret }, _ => { aiur_fn_289::(__args, __hash, record, io_buffer)? }, } } }; let __v_71: G = __r_arr[0]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_70, __v_71]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_72: G = __r_arr[0]; if (__v_72 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_72.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: Some("aggr cross-binding: the bindings' literal classes differ".to_string()) }); } let __v_73: G = G::from_u64(2); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_73]; let __info = io_buffer.get_info(__v_73, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_74: G = __io_pair.0; let __v_75: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_73, __v_74, __v_75]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_76: G = __r_arr[0]; let __v_77: G = G::from_u64(103); let __v_78: G = G::from_u64(230); let __v_79: G = G::from_u64(9); let __v_80: G = G::from_u64(106); let __v_81: G = G::from_u64(133); let __v_82: G = G::from_u64(174); let __v_83: G = G::from_u64(187); let __v_84: G = G::from_u64(114); let __v_85: G = G::from_u64(243); let __v_86: G = G::from_u64(110); let __v_87: G = G::from_u64(60); let __v_88: G = G::from_u64(58); let __v_89: G = G::from_u64(245); let __v_90: G = G::from_u64(79); let __v_91: G = G::from_u64(165); let __v_92: G = G::from_u64(127); let __v_93: G = G::from_u64(82); let __v_94: G = G::from_u64(14); let __v_95: G = G::from_u64(81); let __v_96: G = G::from_u64(140); let __v_97: G = G::from_u64(104); let __v_98: G = G::from_u64(5); let __v_99: G = G::from_u64(155); let __v_100: G = G::from_u64(171); let __v_101: G = G::from_u64(217); let __v_102: G = G::from_u64(131); let __v_103: G = G::from_u64(31); let __v_104: G = G::from_u64(25); let __v_105: G = G::from_u64(205); let __v_106: G = G::from_u64(224); let __v_107: G = G::from_u64(91); let __v_108: G = { let __values: [G; 3] = [__v_34, __v_34, __v_34]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_109: G = { let __values: [G; 8] = [__v_35, __v_35, __v_35, __v_35, __v_35, __v_35, __v_35, __v_35]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_110: G = { let __values: [G; 32] = [__v_77, __v_78, __v_79, __v_80, __v_81, __v_82, __v_77, __v_83, __v_84, __v_85, __v_86, __v_87, __v_88, __v_89, __v_90, __v_91, __v_92, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_99, __v_100, __v_101, __v_102, __v_103, __v_104, __v_105, __v_106, __v_107]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_111: G = { let __values: [G; 34] = [__v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_76, __v_108, __v_35, __v_35, __v_109, __v_110, __v_111]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_112: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_112]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_113: G = __r_arr[0]; let __v_114: G = __r_arr[1]; let __v_115: G = __r_arr[2]; let __v_116: G = __r_arr[3]; let __v_117: G = __r_arr[4]; let __v_118: G = __r_arr[5]; let __v_119: G = __r_arr[6]; let __v_120: G = __r_arr[7]; let __v_121: G = __r_arr[8]; let __v_122: G = __r_arr[9]; let __v_123: G = __r_arr[10]; let __v_124: G = __r_arr[11]; let __v_125: G = __r_arr[12]; let __v_126: G = __r_arr[13]; let __v_127: G = __r_arr[14]; let __v_128: G = __r_arr[15]; let __v_129: G = __r_arr[16]; let __v_130: G = __r_arr[17]; let __v_131: G = __r_arr[18]; let __v_132: G = __r_arr[19]; let __v_133: G = __r_arr[20]; let __v_134: G = __r_arr[21]; let __v_135: G = __r_arr[22]; let __v_136: G = __r_arr[23]; let __v_137: G = __r_arr[24]; let __v_138: G = __r_arr[25]; let __v_139: G = __r_arr[26]; let __v_140: G = __r_arr[27]; let __v_141: G = __r_arr[28]; let __v_142: G = __r_arr[29]; let __v_143: G = __r_arr[30]; let __v_144: G = __r_arr[31]; let __v_145: G = G::from_u64(256); let __v_146: G = __v_145 * __v_114; let __v_147: G = __v_113 + __v_146; let __v_148: G = G::from_u64(65536); let __v_149: G = __v_148 * __v_115; let __v_150: G = __v_147 + __v_149; let __v_151: G = G::from_u64(16777216); let __v_152: G = __v_151 * __v_116; let __v_153: G = __v_150 + __v_152; let __v_154: G = __v_145 * __v_118; let __v_155: G = __v_117 + __v_154; let __v_156: G = __v_148 * __v_119; let __v_157: G = __v_155 + __v_156; let __v_158: G = __v_151 * __v_120; let __v_159: G = __v_157 + __v_158; let __v_160: G = __v_145 * __v_122; let __v_161: G = __v_121 + __v_160; let __v_162: G = __v_148 * __v_123; let __v_163: G = __v_161 + __v_162; let __v_164: G = __v_151 * __v_124; let __v_165: G = __v_163 + __v_164; let __v_166: G = __v_145 * __v_126; let __v_167: G = __v_125 + __v_166; let __v_168: G = __v_148 * __v_127; let __v_169: G = __v_167 + __v_168; let __v_170: G = __v_151 * __v_128; let __v_171: G = __v_169 + __v_170; let __v_172: G = __v_145 * __v_130; let __v_173: G = __v_129 + __v_172; let __v_174: G = __v_148 * __v_131; let __v_175: G = __v_173 + __v_174; let __v_176: G = __v_151 * __v_132; let __v_177: G = __v_175 + __v_176; let __v_178: G = __v_145 * __v_134; let __v_179: G = __v_133 + __v_178; let __v_180: G = __v_148 * __v_135; let __v_181: G = __v_179 + __v_180; let __v_182: G = __v_151 * __v_136; let __v_183: G = __v_181 + __v_182; let __v_184: G = __v_145 * __v_138; let __v_185: G = __v_137 + __v_184; let __v_186: G = __v_148 * __v_139; let __v_187: G = __v_185 + __v_186; let __v_188: G = __v_151 * __v_140; let __v_189: G = __v_187 + __v_188; let __v_190: G = __v_145 * __v_142; let __v_191: G = __v_141 + __v_190; let __v_192: G = __v_148 * __v_143; let __v_193: G = __v_191 + __v_192; let __v_194: G = __v_151 * __v_144; let __v_195: G = __v_193 + __v_194; if (__v_153 != __v_26) { return Err(ExecError::AssertEqMismatch { lhs: __v_153.as_canonical_u64(), rhs: __v_26.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } if (__v_159 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_159.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } if (__v_165 != __v_28) { return Err(ExecError::AssertEqMismatch { lhs: __v_165.as_canonical_u64(), rhs: __v_28.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } if (__v_171 != __v_29) { return Err(ExecError::AssertEqMismatch { lhs: __v_171.as_canonical_u64(), rhs: __v_29.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } if (__v_177 != __v_30) { return Err(ExecError::AssertEqMismatch { lhs: __v_177.as_canonical_u64(), rhs: __v_30.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } if (__v_183 != __v_31) { return Err(ExecError::AssertEqMismatch { lhs: __v_183.as_canonical_u64(), rhs: __v_31.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } if (__v_189 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_189.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } if (__v_195 != __v_33) { return Err(ExecError::AssertEqMismatch { lhs: __v_195.as_canonical_u64(), rhs: __v_33.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_76]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __v_196: G = __r_arr[0]; let __v_197: G = __r_arr[1]; let __v_198: G = __r_arr[2]; let __v_199: G = __r_arr[3]; let __v_200: G = __r_arr[4]; let __v_201: G = __r_arr[5]; let __v_202: G = __r_arr[6]; let __v_203: G = __r_arr[7]; let __r_arr: [G; OUT_272] = { let __args: [G; IN_272] = [__v_53, __v_70, __v_55, __v_56, __v_98, __v_196, __v_197, __v_198, __v_199, __v_200]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(272, &__args[..])?) { [G::ZERO; OUT_272] } else { let (__hash, __hit) = record.function_queries[272].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[272].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[272].bump_multiplicity(__i); } let __ret: [G; OUT_272] = unsafe { *(record.function_queries[272].output_at(__i).as_ptr() as *const [G; OUT_272]) }; __ret }, _ => { aiur_fn_272::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_272] = { let __args: [G; IN_272] = [__v_62, __v_71, __v_64, __v_65, __v_98, __v_196, __v_197, __v_198, __v_199, __v_200]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(272, &__args[..])?) { [G::ZERO; OUT_272] } else { let (__hash, __hit) = record.function_queries[272].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[272].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[272].bump_multiplicity(__i); } let __ret: [G; OUT_272] = unsafe { *(record.function_queries[272].output_at(__i).as_ptr() as *const [G; OUT_272]) }; __ret }, _ => { aiur_fn_272::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_247] = { let __args: [G; IN_247] = [__v_58, __v_67]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(247, &__args[..])?) { [G::ZERO; OUT_247] } else { let (__hash, __hit) = record.function_queries[247].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[247].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[247].bump_multiplicity(__i); } let __ret: [G; OUT_247] = unsafe { *(record.function_queries[247].output_at(__i).as_ptr() as *const [G; OUT_247]) }; __ret }, _ => { aiur_fn_247::(__args, __hash, record, io_buffer)? }, } } }; let __v_204: G = __r_arr[0]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_201, __v_204]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_205: G = __r_arr[0]; if (__v_205 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_205.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: Some("aggr cross-binding: output subject root is not nodeHash(left, right)".to_string()) }); } let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_59, __v_60]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_206: G = __r_arr[0]; let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_68, __v_69]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_207: G = __r_arr[0]; let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_202, __v_203]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_208: G = __r_arr[0]; let __r_arr: [G; OUT_266] = { let __args: [G; IN_266] = [__v_206, __v_207, __v_201, __v_208]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(266, &__args[..])?) { [G::ZERO; OUT_266] } else { let (__hash, __hit) = record.function_queries[266].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[266].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[266].bump_multiplicity(__i); } let __ret: [G; OUT_266] = unsafe { *(record.function_queries[266].output_at(__i).as_ptr() as *const [G; OUT_266]) }; __ret }, _ => { aiur_fn_266::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_290] = []; record.function_queries[290].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_291: usize = 8; -const IN_291: usize = 8; +fn aiur_fn_289(inp: [G; IN_289], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_289], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; match __v_2.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_289] = [__v_1]; record.function_queries[289].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_289] = { let __args: [G; IN_289] = [__v_4, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(289, &__args[..])?) { [G::ZERO; OUT_289] } else { let (__hash, __hit) = record.function_queries[289].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[289].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[289].bump_multiplicity(__i); } let __ret: [G; OUT_289] = unsafe { *(record.function_queries[289].output_at(__i).as_ptr() as *const [G; OUT_289]) }; __ret }, _ => { aiur_fn_289::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __r_arr: [G; OUT_245] = { let __args: [G; IN_245] = [__v_3, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(245, &__args[..])?) { [G::ZERO; OUT_245] } else { let (__hash, __hit) = record.function_queries[245].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[245].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[245].bump_multiplicity(__i); } let __ret: [G; OUT_245] = unsafe { *(record.function_queries[245].output_at(__i).as_ptr() as *const [G; OUT_245]) }; __ret }, _ => { aiur_fn_245::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __ret: [G; OUT_289] = [__v_6]; record.function_queries[289].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } +const INPUT_SIZE_290: usize = 1; +const IN_290: usize = 1; +const OUT_290: usize = 1; +fn aiur_fn_290(inp: [G; IN_290], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_290], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_246] = { let __args: [G; IN_246] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(246, &__args[..])?) { [G::ZERO; OUT_246] } else { let (__hash, __hit) = record.function_queries[246].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[246].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[246].bump_multiplicity(__i); } let __ret: [G; OUT_246] = unsafe { *(record.function_queries[246].output_at(__i).as_ptr() as *const [G; OUT_246]) }; __ret }, _ => { aiur_fn_246::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __v_3: G = __r_arr[2]; let __v_4: G = __r_arr[3]; let __v_5: G = __r_arr[4]; let __v_6: G = __r_arr[5]; let __v_7: G = __r_arr[6]; let __v_8: G = __r_arr[7]; let __r_arr: [G; OUT_275] = { let __args: [G; IN_275] = [__v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(275, &__args[..])?) { [G::ZERO; OUT_275] } else { let (__hash, __hit) = record.function_queries[275].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[275].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[275].bump_multiplicity(__i); } let __ret: [G; OUT_275] = unsafe { *(record.function_queries[275].output_at(__i).as_ptr() as *const [G; OUT_275]) }; __ret }, _ => { aiur_fn_275::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = __r_arr[1]; let __v_12: G = G::from_u64(232); if (__v_10 != __v_12) { return Err(ExecError::AssertEqMismatch { lhs: __v_10.as_canonical_u64(), rhs: __v_12.as_canonical_u64(), msg: Some("aggr cross-binding: not a role binding section".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __v_15: G = G::from_u64(3); if (__v_13 != __v_15) { return Err(ExecError::AssertEqMismatch { lhs: __v_13.as_canonical_u64(), rhs: __v_15.as_canonical_u64(), msg: Some("aggr cross-binding: not a role binding section".to_string()) }); } let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = __r_arr[1]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __v_19: G = __r_arr[1]; let __v_20: G = G::from_u64(103); if (__v_18 != __v_20) { return Err(ExecError::AssertEqMismatch { lhs: __v_18.as_canonical_u64(), rhs: __v_20.as_canonical_u64(), msg: Some("aggr cross-binding: the binding does not bind every role".to_string()) }); } let __v_21: G = G::from_u64(0); let __r_arr: [G; OUT_288] = { let __args: [G; IN_288] = [__v_19, __v_21, __v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(288, &__args[..])?) { [G::ZERO; OUT_288] } else { let (__hash, __hit) = record.function_queries[288].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[288].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[288].bump_multiplicity(__i); } let __ret: [G; OUT_288] = unsafe { *(record.function_queries[288].output_at(__i).as_ptr() as *const [G; OUT_288]) }; __ret }, _ => { aiur_fn_288::(__args, __hash, record, io_buffer)? }, } } }; let __v_22: G = __r_arr[0]; let __v_23: G = G::from_u64(1); let __v_24: G = { let __values: [G; 3] = [__v_23, __v_23, __v_23]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_289] = { let __args: [G; IN_289] = [__v_22, __v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(289, &__args[..])?) { [G::ZERO; OUT_289] } else { let (__hash, __hit) = record.function_queries[289].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[289].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[289].bump_multiplicity(__i); } let __ret: [G; OUT_289] = unsafe { *(record.function_queries[289].output_at(__i).as_ptr() as *const [G; OUT_289]) }; __ret }, _ => { aiur_fn_289::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __v_26: G = G::from_u64(4); let __v_27: G = { let __values: [G; 3] = [__v_21, __v_26, __v_25]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_28: G = { let __values: [G; 3] = [__v_21, __v_12, __v_27]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_75] = { let __args: [G; IN_75] = [__v_28]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(75, &__args[..])?) { [G::ZERO; OUT_75] } else { let (__hash, __hit) = record.function_queries[75].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[75].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[75].bump_multiplicity(__i); } let __ret: [G; OUT_75] = unsafe { *(record.function_queries[75].output_at(__i).as_ptr() as *const [G; OUT_75]) }; __ret }, _ => { aiur_fn_75::(__args, __hash, record, io_buffer)? }, } } }; let __v_29: G = __r_arr[0]; let __ret: [G; OUT_290] = [__v_29]; record.function_queries[290].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_291: usize = 5; +const IN_291: usize = 5; const OUT_291: usize = 1; -fn aiur_fn_291(inp: [G; IN_291], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_291], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __r_arr: [G; OUT_273] = { let __args: [G; IN_273] = [__v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(273, &__args[..])?) { [G::ZERO; OUT_273] } else { let (__hash, __hit) = record.function_queries[273].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[273].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[273].bump_multiplicity(__i); } let __ret: [G; OUT_273] = unsafe { *(record.function_queries[273].output_at(__i).as_ptr() as *const [G; OUT_273]) }; __ret }, _ => { aiur_fn_273::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = __r_arr[1]; match __v_9.as_canonical_u64() { 229u64 => { let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = __r_arr[1]; let __v_13: G = __r_arr[2]; let __v_14: G = __r_arr[3]; let __v_15: G = __r_arr[4]; let __v_16: G = __r_arr[5]; let __v_17: G = __r_arr[6]; let __v_18: G = __r_arr[7]; let __v_19: G = G::from_u64(3); let __v_20: G = __v_15 - __v_19; let __v_21: G = G::from_u64(5); let __v_22: G = __v_15 - __v_21; let __v_23: G = __v_20 * __v_22; let __v_24: G = G::from_u64(0); if (__v_23 != __v_24) { return Err(ExecError::AssertEqMismatch { lhs: __v_23.as_canonical_u64(), rhs: __v_24.as_canonical_u64(), msg: Some("aggr catalog: a member claim assumes its binding".to_string()) }); } let __v_25: G = G::from_u64(1); let __r_arr: [G; OUT_271] = { let __args: [G; IN_271] = [__v_17, __v_18, __v_25, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(271, &__args[..])?) { [G::ZERO; OUT_271] } else { let (__hash, __hit) = record.function_queries[271].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[271].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[271].bump_multiplicity(__i); } let __ret: [G; OUT_271] = unsafe { *(record.function_queries[271].output_at(__i).as_ptr() as *const [G; OUT_271]) }; __ret }, _ => { aiur_fn_271::(__args, __hash, record, io_buffer)? }, } } }; let __v_26: G = __r_arr[0]; if (__v_26 != __v_25) { return Err(ExecError::AssertEqMismatch { lhs: __v_26.as_canonical_u64(), rhs: __v_25.as_canonical_u64(), msg: Some("aggr catalog: a member claim has assumptions".to_string()) }); } let __r_arr: [G; OUT_75] = { let __args: [G; IN_75] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(75, &__args[..])?) { [G::ZERO; OUT_75] } else { let (__hash, __hit) = record.function_queries[75].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[75].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[75].bump_multiplicity(__i); } let __ret: [G; OUT_75] = unsafe { *(record.function_queries[75].output_at(__i).as_ptr() as *const [G; OUT_75]) }; __ret }, _ => { aiur_fn_75::(__args, __hash, record, io_buffer)? }, } } }; let __v_27: G = __r_arr[0]; let __v_28: G = { let __values: [G; 3] = [__v_25, __v_25, __v_25]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_29: G = { let __values: [G; 3] = [__v_24, __v_27, __v_28]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_291] = [__v_29]; record.function_queries[291].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_270] = { let __args: [G; IN_270] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(270, &__args[..])?) { [G::ZERO; OUT_270] } else { let (__hash, __hit) = record.function_queries[270].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[270].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[270].bump_multiplicity(__i); } let __ret: [G; OUT_270] = unsafe { *(record.function_queries[270].output_at(__i).as_ptr() as *const [G; OUT_270]) }; __ret }, _ => { aiur_fn_270::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __r_arr: [G; OUT_257] = { let __args: [G; IN_257] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(257, &__args[..])?) { [G::ZERO; OUT_257] } else { let (__hash, __hit) = record.function_queries[257].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[257].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[257].bump_multiplicity(__i); } let __ret: [G; OUT_257] = unsafe { *(record.function_queries[257].output_at(__i).as_ptr() as *const [G; OUT_257]) }; __ret }, _ => { aiur_fn_257::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __ret: [G; OUT_291] = [__v_12]; record.function_queries[291].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } +fn aiur_fn_291(inp: [G; IN_291], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_291], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = G::from_u64(3); let __v_6: G = __v_4 - __v_5; let __v_7: G = G::from_u64(5); let __v_8: G = __v_4 - __v_7; let __v_9: G = __v_6 * __v_8; let __v_10: G = G::from_u64(0); if (__v_9 != __v_10) { return Err(ExecError::AssertEqMismatch { lhs: __v_9.as_canonical_u64(), rhs: __v_10.as_canonical_u64(), msg: Some("aggr cross-binding: the claim assumes its binding".to_string()) }); } match __v_4.as_canonical_u64() { 5u64 => { let __ret: [G; OUT_291] = [__v_1]; record.function_queries[291].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_290] = { let __args: [G; IN_290] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(290, &__args[..])?) { [G::ZERO; OUT_290] } else { let (__hash, __hit) = record.function_queries[290].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[290].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[290].bump_multiplicity(__i); } let __ret: [G; OUT_290] = unsafe { *(record.function_queries[290].output_at(__i).as_ptr() as *const [G; OUT_290]) }; __ret }, _ => { aiur_fn_290::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __ret: [G; OUT_291] = [__v_11]; record.function_queries[291].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } const INPUT_SIZE_292: usize = 34; const IN_292: usize = 34; const OUT_292: usize = 0; -fn aiur_fn_292(inp: [G; IN_292], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_292], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = G::from_u64(1); let __v_35: G = G::from_u64(0); let __r_arr: [G; OUT_280] = { let __args: [G; IN_280] = [__v_34, __v_35, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(280, &__args[..])?) { [G::ZERO; OUT_280] } else { let (__hash, __hit) = record.function_queries[280].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[280].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[280].bump_multiplicity(__i); } let __ret: [G; OUT_280] = unsafe { *(record.function_queries[280].output_at(__i).as_ptr() as *const [G; OUT_280]) }; __ret }, _ => { aiur_fn_280::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __v_38: G = __r_arr[2]; let __v_39: G = __r_arr[3]; let __v_40: G = __r_arr[4]; let __v_41: G = __r_arr[5]; let __v_42: G = __r_arr[6]; let __v_43: G = __r_arr[7]; let __r_arr: [G; OUT_280] = { let __args: [G; IN_280] = [__v_34, __v_34, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(280, &__args[..])?) { [G::ZERO; OUT_280] } else { let (__hash, __hit) = record.function_queries[280].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[280].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[280].bump_multiplicity(__i); } let __ret: [G; OUT_280] = unsafe { *(record.function_queries[280].output_at(__i).as_ptr() as *const [G; OUT_280]) }; __ret }, _ => { aiur_fn_280::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; let __v_45: G = __r_arr[1]; let __v_46: G = __r_arr[2]; let __v_47: G = __r_arr[3]; let __v_48: G = __r_arr[4]; let __v_49: G = __r_arr[5]; let __v_50: G = __r_arr[6]; let __v_51: G = __r_arr[7]; let __r_arr: [G; OUT_291] = { let __args: [G; IN_291] = [__v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(291, &__args[..])?) { [G::ZERO; OUT_291] } else { let (__hash, __hit) = record.function_queries[291].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[291].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[291].bump_multiplicity(__i); } let __ret: [G; OUT_291] = unsafe { *(record.function_queries[291].output_at(__i).as_ptr() as *const [G; OUT_291]) }; __ret }, _ => { aiur_fn_291::(__args, __hash, record, io_buffer)? }, } } }; let __v_52: G = __r_arr[0]; let __r_arr: [G; OUT_291] = { let __args: [G; IN_291] = [__v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(291, &__args[..])?) { [G::ZERO; OUT_291] } else { let (__hash, __hit) = record.function_queries[291].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[291].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[291].bump_multiplicity(__i); } let __ret: [G; OUT_291] = unsafe { *(record.function_queries[291].output_at(__i).as_ptr() as *const [G; OUT_291]) }; __ret }, _ => { aiur_fn_291::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; let __v_54: G = G::from_u64(2); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_54]; let __info = io_buffer.get_info(__v_54, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_55: G = __io_pair.0; let __v_56: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_54, __v_55, __v_56]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_57: G = __r_arr[0]; let __v_58: G = G::from_u64(103); let __v_59: G = G::from_u64(230); let __v_60: G = G::from_u64(9); let __v_61: G = G::from_u64(106); let __v_62: G = G::from_u64(133); let __v_63: G = G::from_u64(174); let __v_64: G = G::from_u64(187); let __v_65: G = G::from_u64(114); let __v_66: G = G::from_u64(243); let __v_67: G = G::from_u64(110); let __v_68: G = G::from_u64(60); let __v_69: G = G::from_u64(58); let __v_70: G = G::from_u64(245); let __v_71: G = G::from_u64(79); let __v_72: G = G::from_u64(165); let __v_73: G = G::from_u64(127); let __v_74: G = G::from_u64(82); let __v_75: G = G::from_u64(14); let __v_76: G = G::from_u64(81); let __v_77: G = G::from_u64(140); let __v_78: G = G::from_u64(104); let __v_79: G = G::from_u64(5); let __v_80: G = G::from_u64(155); let __v_81: G = G::from_u64(171); let __v_82: G = G::from_u64(217); let __v_83: G = G::from_u64(131); let __v_84: G = G::from_u64(31); let __v_85: G = G::from_u64(25); let __v_86: G = G::from_u64(205); let __v_87: G = G::from_u64(224); let __v_88: G = G::from_u64(91); let __v_89: G = { let __values: [G; 3] = [__v_34, __v_34, __v_34]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_90: G = { let __values: [G; 8] = [__v_35, __v_35, __v_35, __v_35, __v_35, __v_35, __v_35, __v_35]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_91: G = { let __values: [G; 32] = [__v_58, __v_59, __v_60, __v_61, __v_62, __v_63, __v_58, __v_64, __v_65, __v_66, __v_67, __v_68, __v_69, __v_70, __v_71, __v_72, __v_73, __v_74, __v_75, __v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_82, __v_83, __v_84, __v_85, __v_86, __v_87, __v_88]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_92: G = { let __values: [G; 34] = [__v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_57, __v_89, __v_35, __v_35, __v_90, __v_91, __v_92]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_93: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_93]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_94: G = __r_arr[0]; let __v_95: G = __r_arr[1]; let __v_96: G = __r_arr[2]; let __v_97: G = __r_arr[3]; let __v_98: G = __r_arr[4]; let __v_99: G = __r_arr[5]; let __v_100: G = __r_arr[6]; let __v_101: G = __r_arr[7]; let __v_102: G = __r_arr[8]; let __v_103: G = __r_arr[9]; let __v_104: G = __r_arr[10]; let __v_105: G = __r_arr[11]; let __v_106: G = __r_arr[12]; let __v_107: G = __r_arr[13]; let __v_108: G = __r_arr[14]; let __v_109: G = __r_arr[15]; let __v_110: G = __r_arr[16]; let __v_111: G = __r_arr[17]; let __v_112: G = __r_arr[18]; let __v_113: G = __r_arr[19]; let __v_114: G = __r_arr[20]; let __v_115: G = __r_arr[21]; let __v_116: G = __r_arr[22]; let __v_117: G = __r_arr[23]; let __v_118: G = __r_arr[24]; let __v_119: G = __r_arr[25]; let __v_120: G = __r_arr[26]; let __v_121: G = __r_arr[27]; let __v_122: G = __r_arr[28]; let __v_123: G = __r_arr[29]; let __v_124: G = __r_arr[30]; let __v_125: G = __r_arr[31]; let __v_126: G = G::from_u64(256); let __v_127: G = __v_126 * __v_95; let __v_128: G = __v_94 + __v_127; let __v_129: G = G::from_u64(65536); let __v_130: G = __v_129 * __v_96; let __v_131: G = __v_128 + __v_130; let __v_132: G = G::from_u64(16777216); let __v_133: G = __v_132 * __v_97; let __v_134: G = __v_131 + __v_133; let __v_135: G = __v_126 * __v_99; let __v_136: G = __v_98 + __v_135; let __v_137: G = __v_129 * __v_100; let __v_138: G = __v_136 + __v_137; let __v_139: G = __v_132 * __v_101; let __v_140: G = __v_138 + __v_139; let __v_141: G = __v_126 * __v_103; let __v_142: G = __v_102 + __v_141; let __v_143: G = __v_129 * __v_104; let __v_144: G = __v_142 + __v_143; let __v_145: G = __v_132 * __v_105; let __v_146: G = __v_144 + __v_145; let __v_147: G = __v_126 * __v_107; let __v_148: G = __v_106 + __v_147; let __v_149: G = __v_129 * __v_108; let __v_150: G = __v_148 + __v_149; let __v_151: G = __v_132 * __v_109; let __v_152: G = __v_150 + __v_151; let __v_153: G = __v_126 * __v_111; let __v_154: G = __v_110 + __v_153; let __v_155: G = __v_129 * __v_112; let __v_156: G = __v_154 + __v_155; let __v_157: G = __v_132 * __v_113; let __v_158: G = __v_156 + __v_157; let __v_159: G = __v_126 * __v_115; let __v_160: G = __v_114 + __v_159; let __v_161: G = __v_129 * __v_116; let __v_162: G = __v_160 + __v_161; let __v_163: G = __v_132 * __v_117; let __v_164: G = __v_162 + __v_163; let __v_165: G = __v_126 * __v_119; let __v_166: G = __v_118 + __v_165; let __v_167: G = __v_129 * __v_120; let __v_168: G = __v_166 + __v_167; let __v_169: G = __v_132 * __v_121; let __v_170: G = __v_168 + __v_169; let __v_171: G = __v_126 * __v_123; let __v_172: G = __v_122 + __v_171; let __v_173: G = __v_129 * __v_124; let __v_174: G = __v_172 + __v_173; let __v_175: G = __v_132 * __v_125; let __v_176: G = __v_174 + __v_175; if (__v_134 != __v_26) { return Err(ExecError::AssertEqMismatch { lhs: __v_134.as_canonical_u64(), rhs: __v_26.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } if (__v_140 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_140.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } if (__v_146 != __v_28) { return Err(ExecError::AssertEqMismatch { lhs: __v_146.as_canonical_u64(), rhs: __v_28.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } if (__v_152 != __v_29) { return Err(ExecError::AssertEqMismatch { lhs: __v_152.as_canonical_u64(), rhs: __v_29.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } if (__v_158 != __v_30) { return Err(ExecError::AssertEqMismatch { lhs: __v_158.as_canonical_u64(), rhs: __v_30.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } if (__v_164 != __v_31) { return Err(ExecError::AssertEqMismatch { lhs: __v_164.as_canonical_u64(), rhs: __v_31.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } if (__v_170 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_170.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } if (__v_176 != __v_33) { return Err(ExecError::AssertEqMismatch { lhs: __v_176.as_canonical_u64(), rhs: __v_33.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } let __r_arr: [G; OUT_270] = { let __args: [G; IN_270] = [__v_57]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(270, &__args[..])?) { [G::ZERO; OUT_270] } else { let (__hash, __hit) = record.function_queries[270].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[270].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[270].bump_multiplicity(__i); } let __ret: [G; OUT_270] = unsafe { *(record.function_queries[270].output_at(__i).as_ptr() as *const [G; OUT_270]) }; __ret }, _ => { aiur_fn_270::(__args, __hash, record, io_buffer)? }, } } }; let __v_177: G = __r_arr[0]; let __r_arr: [G; OUT_257] = { let __args: [G; IN_257] = [__v_177]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(257, &__args[..])?) { [G::ZERO; OUT_257] } else { let (__hash, __hit) = record.function_queries[257].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[257].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[257].bump_multiplicity(__i); } let __ret: [G; OUT_257] = unsafe { *(record.function_queries[257].output_at(__i).as_ptr() as *const [G; OUT_257]) }; __ret }, _ => { aiur_fn_257::(__args, __hash, record, io_buffer)? }, } } }; let __v_178: G = __r_arr[0]; let __r_arr: [G; OUT_260] = { let __args: [G; IN_260] = [__v_52, __v_53, __v_178]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(260, &__args[..])?) { [G::ZERO; OUT_260] } else { let (__hash, __hit) = record.function_queries[260].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[260].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[260].bump_multiplicity(__i); } let __ret: [G; OUT_260] = unsafe { *(record.function_queries[260].output_at(__i).as_ptr() as *const [G; OUT_260]) }; __ret }, _ => { aiur_fn_260::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_292] = []; record.function_queries[292].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_293: usize = 1; -const IN_293: usize = 1; -const OUT_293: usize = 19; -fn aiur_fn_293(inp: [G; IN_293], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_293], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __v_3: G = G::from_u64(82); if (__v_1 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_1.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("aggr range: statement has the wrong tag".to_string()) }); } let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __v_9: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __v_13: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_14: G = __r_arr[0]; let __v_15: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __v_19: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_20: G = __r_arr[0]; let __v_21: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_21]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_22: G = __r_arr[0]; let __v_23: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_23]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_24: G = __r_arr[0]; let __v_25: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_26: G = __r_arr[0]; let __v_27: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_28: G = __r_arr[0]; let __v_29: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_29]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_30: G = __r_arr[0]; let __v_31: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_32: G = __r_arr[0]; let __v_33: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_33]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_34: G = __r_arr[0]; let __v_35: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_35]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_37]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_38: G = __r_arr[0]; let __v_39: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_39]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_40: G = __r_arr[0]; let __v_41: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_41]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_42: G = __r_arr[0]; let __v_43: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; let __v_45: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_45]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_46: G = __r_arr[0]; let __v_47: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_47]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_48: G = __r_arr[0]; let __v_49: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_49]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_50: G = __r_arr[0]; let __v_51: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_51]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_52: G = __r_arr[0]; let __v_53: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_53]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __v_55: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_55]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_56: G = __r_arr[0]; let __v_57: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_57]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_58: G = __r_arr[0]; let __v_59: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_59]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_60: G = __r_arr[0]; let __v_61: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_61]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_62: G = __r_arr[0]; let __v_63: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_63]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_64: G = __r_arr[0]; let __v_65: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_65]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_66: G = __r_arr[0]; let __v_67: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_67]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_68: G = __r_arr[0]; let __v_69: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_69.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_70: G = __loaded[0]; let __v_71: G = __loaded[1]; let __v_72: G = __loaded[2]; let __v_73: G = G::from_u64(1); if (__v_70 != __v_73) { return Err(ExecError::AssertEqMismatch { lhs: __v_70.as_canonical_u64(), rhs: __v_73.as_canonical_u64(), msg: Some("aggr range: trailing bytes after the statement".to_string()) }); } if (__v_71 != __v_73) { return Err(ExecError::AssertEqMismatch { lhs: __v_71.as_canonical_u64(), rhs: __v_73.as_canonical_u64(), msg: Some("aggr range: trailing bytes after the statement".to_string()) }); } if (__v_72 != __v_73) { return Err(ExecError::AssertEqMismatch { lhs: __v_72.as_canonical_u64(), rhs: __v_73.as_canonical_u64(), msg: Some("aggr range: trailing bytes after the statement".to_string()) }); } let __v_74: G = G::from_u64(256); let __v_75: G = __v_74 * __v_40; let __v_76: G = __v_38 + __v_75; let __v_77: G = G::from_u64(65536); let __v_78: G = __v_77 * __v_42; let __v_79: G = __v_76 + __v_78; let __v_80: G = G::from_u64(16777216); let __v_81: G = __v_80 * __v_44; let __v_82: G = __v_79 + __v_81; let __v_83: G = G::from_u64(4294967296); let __v_84: G = __v_83 * __v_46; let __v_85: G = __v_82 + __v_84; let __v_86: G = G::from_u64(1099511627776); let __v_87: G = __v_86 * __v_48; let __v_88: G = __v_85 + __v_87; let __v_89: G = G::from_u64(281474976710656); let __v_90: G = __v_89 * __v_50; let __v_91: G = __v_88 + __v_90; let __v_92: G = G::from_u64(72057594037927936); let __v_93: G = __v_92 * __v_52; let __v_94: G = __v_91 + __v_93; let __v_95: G = __v_74 * __v_56; let __v_96: G = __v_54 + __v_95; let __v_97: G = __v_77 * __v_58; let __v_98: G = __v_96 + __v_97; let __v_99: G = __v_80 * __v_60; let __v_100: G = __v_98 + __v_99; let __v_101: G = __v_83 * __v_62; let __v_102: G = __v_100 + __v_101; let __v_103: G = __v_86 * __v_64; let __v_104: G = __v_102 + __v_103; let __v_105: G = __v_89 * __v_66; let __v_106: G = __v_104 + __v_105; let __v_107: G = __v_92 * __v_68; let __v_108: G = __v_106 + __v_107; let __ret: [G; OUT_293] = [__v_4, __v_6, __v_8, __v_10, __v_12, __v_14, __v_16, __v_18, __v_20, __v_22, __v_24, __v_26, __v_28, __v_30, __v_32, __v_34, __v_36, __v_94, __v_108]; record.function_queries[293].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_294: usize = 1; -const IN_294: usize = 1; -const OUT_294: usize = 2; -fn aiur_fn_294(inp: [G; IN_294], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_294], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __v_4: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __v_24: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __v_26: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_27: G = __r_arr[0]; let __v_28: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_28]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_29: G = __r_arr[0]; let __v_30: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_30]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_31: G = __r_arr[0]; let __v_32: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_32]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_33: G = __r_arr[0]; let __v_34: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_34]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_35: G = __r_arr[0]; let __v_36: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_38]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_39: G = __r_arr[0]; let __v_40: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_41: G = __r_arr[0]; let __v_42: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_42]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_43: G = __r_arr[0]; let __v_44: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_44]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_45: G = __r_arr[0]; let __v_46: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_46]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_47: G = __r_arr[0]; let __v_48: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __v_50: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_50]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_51: G = __r_arr[0]; let __v_52: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_52]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; let __v_54: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_54]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_55: G = __r_arr[0]; let __v_56: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_56]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_57: G = __r_arr[0]; let __v_58: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_58]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_59: G = __r_arr[0]; let __v_60: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_60]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_61: G = __r_arr[0]; let __v_62: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_62]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_63: G = __r_arr[0]; let __v_64: G = __r_arr[1]; let __v_65: G = { let __values: [G; 32] = [__v_1, __v_3, __v_5, __v_7, __v_9, __v_11, __v_13, __v_15, __v_17, __v_19, __v_21, __v_23, __v_25, __v_27, __v_29, __v_31, __v_33, __v_35, __v_37, __v_39, __v_41, __v_43, __v_45, __v_47, __v_49, __v_51, __v_53, __v_55, __v_57, __v_59, __v_61, __v_63]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_294] = [__v_65, __v_64]; record.function_queries[294].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_295: usize = 2; -const IN_295: usize = 2; -const OUT_295: usize = 2; -fn aiur_fn_295(inp: [G; IN_295], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_295], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __v_2: G = G::from_u64(1); let __v_3: G = { let __values: [G; 3] = [__v_2, __v_2, __v_2]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_295] = [__v_3, __v_0]; record.function_queries[295].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_294] = { let __args: [G; IN_294] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(294, &__args[..])?) { [G::ZERO; OUT_294] } else { let (__hash, __hit) = record.function_queries[294].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[294].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[294].bump_multiplicity(__i); } let __ret: [G; OUT_294] = unsafe { *(record.function_queries[294].output_at(__i).as_ptr() as *const [G; OUT_294]) }; __ret }, _ => { aiur_fn_294::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __v_3: G = __r_arr[1]; let __v_4: G = G::from_u64(1); let __v_5: G = __v_1 - __v_4; let __r_arr: [G; OUT_295] = { let __args: [G; IN_295] = [__v_3, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(295, &__args[..])?) { [G::ZERO; OUT_295] } else { let (__hash, __hit) = record.function_queries[295].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[295].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[295].bump_multiplicity(__i); } let __ret: [G; OUT_295] = unsafe { *(record.function_queries[295].output_at(__i).as_ptr() as *const [G; OUT_295]) }; __ret }, _ => { aiur_fn_295::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = __r_arr[1]; let __v_8: G = G::from_u64(0); let __v_9: G = { let __values: [G; 3] = [__v_8, __v_2, __v_6]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_295] = [__v_9, __v_7]; record.function_queries[295].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } -const INPUT_SIZE_296: usize = 2; -const IN_296: usize = 2; +fn aiur_fn_292(inp: [G; IN_292], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_292], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = G::from_u64(1); let __v_35: G = G::from_u64(0); let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_34, __v_35, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __v_38: G = __r_arr[2]; let __v_39: G = __r_arr[3]; let __v_40: G = __r_arr[4]; let __v_41: G = __r_arr[5]; let __v_42: G = __r_arr[6]; let __v_43: G = __r_arr[7]; let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_34, __v_34, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; let __v_45: G = __r_arr[1]; let __v_46: G = __r_arr[2]; let __v_47: G = __r_arr[3]; let __v_48: G = __r_arr[4]; let __v_49: G = __r_arr[5]; let __v_50: G = __r_arr[6]; let __v_51: G = __r_arr[7]; let __r_arr: [G; OUT_275] = { let __args: [G; IN_275] = [__v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(275, &__args[..])?) { [G::ZERO; OUT_275] } else { let (__hash, __hit) = record.function_queries[275].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[275].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[275].bump_multiplicity(__i); } let __ret: [G; OUT_275] = unsafe { *(record.function_queries[275].output_at(__i).as_ptr() as *const [G; OUT_275]) }; __ret }, _ => { aiur_fn_275::(__args, __hash, record, io_buffer)? }, } } }; let __v_52: G = __r_arr[0]; let __r_arr: [G; OUT_270] = { let __args: [G; IN_270] = [__v_52]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(270, &__args[..])?) { [G::ZERO; OUT_270] } else { let (__hash, __hit) = record.function_queries[270].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[270].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[270].bump_multiplicity(__i); } let __ret: [G; OUT_270] = unsafe { *(record.function_queries[270].output_at(__i).as_ptr() as *const [G; OUT_270]) }; __ret }, _ => { aiur_fn_270::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; let __v_54: G = __r_arr[1]; let __v_55: G = __r_arr[2]; let __v_56: G = __r_arr[3]; let __v_57: G = __r_arr[4]; let __v_58: G = __r_arr[5]; let __v_59: G = __r_arr[6]; let __v_60: G = __r_arr[7]; let __r_arr: [G; OUT_275] = { let __args: [G; IN_275] = [__v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(275, &__args[..])?) { [G::ZERO; OUT_275] } else { let (__hash, __hit) = record.function_queries[275].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[275].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[275].bump_multiplicity(__i); } let __ret: [G; OUT_275] = unsafe { *(record.function_queries[275].output_at(__i).as_ptr() as *const [G; OUT_275]) }; __ret }, _ => { aiur_fn_275::(__args, __hash, record, io_buffer)? }, } } }; let __v_61: G = __r_arr[0]; let __r_arr: [G; OUT_270] = { let __args: [G; IN_270] = [__v_61]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(270, &__args[..])?) { [G::ZERO; OUT_270] } else { let (__hash, __hit) = record.function_queries[270].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[270].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[270].bump_multiplicity(__i); } let __ret: [G; OUT_270] = unsafe { *(record.function_queries[270].output_at(__i).as_ptr() as *const [G; OUT_270]) }; __ret }, _ => { aiur_fn_270::(__args, __hash, record, io_buffer)? }, } } }; let __v_62: G = __r_arr[0]; let __v_63: G = __r_arr[1]; let __v_64: G = __r_arr[2]; let __v_65: G = __r_arr[3]; let __v_66: G = __r_arr[4]; let __v_67: G = __r_arr[5]; let __v_68: G = __r_arr[6]; let __v_69: G = __r_arr[7]; let __r_arr: [G; OUT_291] = { let __args: [G; IN_291] = [__v_53, __v_54, __v_55, __v_56, __v_57]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(291, &__args[..])?) { [G::ZERO; OUT_291] } else { let (__hash, __hit) = record.function_queries[291].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[291].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[291].bump_multiplicity(__i); } let __ret: [G; OUT_291] = unsafe { *(record.function_queries[291].output_at(__i).as_ptr() as *const [G; OUT_291]) }; __ret }, _ => { aiur_fn_291::(__args, __hash, record, io_buffer)? }, } } }; let __v_70: G = __r_arr[0]; let __r_arr: [G; OUT_291] = { let __args: [G; IN_291] = [__v_62, __v_63, __v_64, __v_65, __v_66]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(291, &__args[..])?) { [G::ZERO; OUT_291] } else { let (__hash, __hit) = record.function_queries[291].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[291].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[291].bump_multiplicity(__i); } let __ret: [G; OUT_291] = unsafe { *(record.function_queries[291].output_at(__i).as_ptr() as *const [G; OUT_291]) }; __ret }, _ => { aiur_fn_291::(__args, __hash, record, io_buffer)? }, } } }; let __v_71: G = __r_arr[0]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_70, __v_71]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_72: G = __r_arr[0]; if (__v_72 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_72.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: Some("aggr cross-binding: the bindings' literal classes differ".to_string()) }); } let __v_73: G = G::from_u64(2); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_73]; let __info = io_buffer.get_info(__v_73, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_74: G = __io_pair.0; let __v_75: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_73, __v_74, __v_75]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_76: G = __r_arr[0]; let __v_77: G = G::from_u64(103); let __v_78: G = G::from_u64(230); let __v_79: G = G::from_u64(9); let __v_80: G = G::from_u64(106); let __v_81: G = G::from_u64(133); let __v_82: G = G::from_u64(174); let __v_83: G = G::from_u64(187); let __v_84: G = G::from_u64(114); let __v_85: G = G::from_u64(243); let __v_86: G = G::from_u64(110); let __v_87: G = G::from_u64(60); let __v_88: G = G::from_u64(58); let __v_89: G = G::from_u64(245); let __v_90: G = G::from_u64(79); let __v_91: G = G::from_u64(165); let __v_92: G = G::from_u64(127); let __v_93: G = G::from_u64(82); let __v_94: G = G::from_u64(14); let __v_95: G = G::from_u64(81); let __v_96: G = G::from_u64(140); let __v_97: G = G::from_u64(104); let __v_98: G = G::from_u64(5); let __v_99: G = G::from_u64(155); let __v_100: G = G::from_u64(171); let __v_101: G = G::from_u64(217); let __v_102: G = G::from_u64(131); let __v_103: G = G::from_u64(31); let __v_104: G = G::from_u64(25); let __v_105: G = G::from_u64(205); let __v_106: G = G::from_u64(224); let __v_107: G = G::from_u64(91); let __v_108: G = { let __values: [G; 3] = [__v_34, __v_34, __v_34]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_109: G = { let __values: [G; 8] = [__v_35, __v_35, __v_35, __v_35, __v_35, __v_35, __v_35, __v_35]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_110: G = { let __values: [G; 32] = [__v_77, __v_78, __v_79, __v_80, __v_81, __v_82, __v_77, __v_83, __v_84, __v_85, __v_86, __v_87, __v_88, __v_89, __v_90, __v_91, __v_92, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_99, __v_100, __v_101, __v_102, __v_103, __v_104, __v_105, __v_106, __v_107]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_111: G = { let __values: [G; 34] = [__v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_76, __v_108, __v_35, __v_35, __v_109, __v_110, __v_111]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_112: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_112]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_113: G = __r_arr[0]; let __v_114: G = __r_arr[1]; let __v_115: G = __r_arr[2]; let __v_116: G = __r_arr[3]; let __v_117: G = __r_arr[4]; let __v_118: G = __r_arr[5]; let __v_119: G = __r_arr[6]; let __v_120: G = __r_arr[7]; let __v_121: G = __r_arr[8]; let __v_122: G = __r_arr[9]; let __v_123: G = __r_arr[10]; let __v_124: G = __r_arr[11]; let __v_125: G = __r_arr[12]; let __v_126: G = __r_arr[13]; let __v_127: G = __r_arr[14]; let __v_128: G = __r_arr[15]; let __v_129: G = __r_arr[16]; let __v_130: G = __r_arr[17]; let __v_131: G = __r_arr[18]; let __v_132: G = __r_arr[19]; let __v_133: G = __r_arr[20]; let __v_134: G = __r_arr[21]; let __v_135: G = __r_arr[22]; let __v_136: G = __r_arr[23]; let __v_137: G = __r_arr[24]; let __v_138: G = __r_arr[25]; let __v_139: G = __r_arr[26]; let __v_140: G = __r_arr[27]; let __v_141: G = __r_arr[28]; let __v_142: G = __r_arr[29]; let __v_143: G = __r_arr[30]; let __v_144: G = __r_arr[31]; let __v_145: G = G::from_u64(256); let __v_146: G = __v_145 * __v_114; let __v_147: G = __v_113 + __v_146; let __v_148: G = G::from_u64(65536); let __v_149: G = __v_148 * __v_115; let __v_150: G = __v_147 + __v_149; let __v_151: G = G::from_u64(16777216); let __v_152: G = __v_151 * __v_116; let __v_153: G = __v_150 + __v_152; let __v_154: G = __v_145 * __v_118; let __v_155: G = __v_117 + __v_154; let __v_156: G = __v_148 * __v_119; let __v_157: G = __v_155 + __v_156; let __v_158: G = __v_151 * __v_120; let __v_159: G = __v_157 + __v_158; let __v_160: G = __v_145 * __v_122; let __v_161: G = __v_121 + __v_160; let __v_162: G = __v_148 * __v_123; let __v_163: G = __v_161 + __v_162; let __v_164: G = __v_151 * __v_124; let __v_165: G = __v_163 + __v_164; let __v_166: G = __v_145 * __v_126; let __v_167: G = __v_125 + __v_166; let __v_168: G = __v_148 * __v_127; let __v_169: G = __v_167 + __v_168; let __v_170: G = __v_151 * __v_128; let __v_171: G = __v_169 + __v_170; let __v_172: G = __v_145 * __v_130; let __v_173: G = __v_129 + __v_172; let __v_174: G = __v_148 * __v_131; let __v_175: G = __v_173 + __v_174; let __v_176: G = __v_151 * __v_132; let __v_177: G = __v_175 + __v_176; let __v_178: G = __v_145 * __v_134; let __v_179: G = __v_133 + __v_178; let __v_180: G = __v_148 * __v_135; let __v_181: G = __v_179 + __v_180; let __v_182: G = __v_151 * __v_136; let __v_183: G = __v_181 + __v_182; let __v_184: G = __v_145 * __v_138; let __v_185: G = __v_137 + __v_184; let __v_186: G = __v_148 * __v_139; let __v_187: G = __v_185 + __v_186; let __v_188: G = __v_151 * __v_140; let __v_189: G = __v_187 + __v_188; let __v_190: G = __v_145 * __v_142; let __v_191: G = __v_141 + __v_190; let __v_192: G = __v_148 * __v_143; let __v_193: G = __v_191 + __v_192; let __v_194: G = __v_151 * __v_144; let __v_195: G = __v_193 + __v_194; if (__v_153 != __v_26) { return Err(ExecError::AssertEqMismatch { lhs: __v_153.as_canonical_u64(), rhs: __v_26.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } if (__v_159 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_159.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } if (__v_165 != __v_28) { return Err(ExecError::AssertEqMismatch { lhs: __v_165.as_canonical_u64(), rhs: __v_28.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } if (__v_171 != __v_29) { return Err(ExecError::AssertEqMismatch { lhs: __v_171.as_canonical_u64(), rhs: __v_29.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } if (__v_177 != __v_30) { return Err(ExecError::AssertEqMismatch { lhs: __v_177.as_canonical_u64(), rhs: __v_30.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } if (__v_183 != __v_31) { return Err(ExecError::AssertEqMismatch { lhs: __v_183.as_canonical_u64(), rhs: __v_31.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } if (__v_189 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_189.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } if (__v_195 != __v_33) { return Err(ExecError::AssertEqMismatch { lhs: __v_195.as_canonical_u64(), rhs: __v_33.as_canonical_u64(), msg: Some("aggr cross-binding: output claim digest mismatch".to_string()) }); } let __r_arr: [G; OUT_270] = { let __args: [G; IN_270] = [__v_76]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(270, &__args[..])?) { [G::ZERO; OUT_270] } else { let (__hash, __hit) = record.function_queries[270].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[270].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[270].bump_multiplicity(__i); } let __ret: [G; OUT_270] = unsafe { *(record.function_queries[270].output_at(__i).as_ptr() as *const [G; OUT_270]) }; __ret }, _ => { aiur_fn_270::(__args, __hash, record, io_buffer)? }, } } }; let __v_196: G = __r_arr[0]; let __v_197: G = __r_arr[1]; let __v_198: G = __r_arr[2]; let __v_199: G = __r_arr[3]; let __v_200: G = __r_arr[4]; let __v_201: G = __r_arr[5]; let __v_202: G = __r_arr[6]; let __v_203: G = __r_arr[7]; let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_53, __v_70, __v_55, __v_56, __v_98, __v_196, __v_197, __v_198, __v_199, __v_200]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_62, __v_71, __v_64, __v_65, __v_98, __v_196, __v_197, __v_198, __v_199, __v_200]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_58, __v_67]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_204: G = __r_arr[0]; if (__v_204 != __v_35) { return Err(ExecError::AssertEqMismatch { lhs: __v_204.as_canonical_u64(), rhs: __v_35.as_canonical_u64(), msg: Some("aggr cross-binding: child subject roots must be distinct".to_string()) }); } let __r_arr: [G; OUT_247] = { let __args: [G; IN_247] = [__v_58, __v_67]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(247, &__args[..])?) { [G::ZERO; OUT_247] } else { let (__hash, __hit) = record.function_queries[247].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[247].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[247].bump_multiplicity(__i); } let __ret: [G; OUT_247] = unsafe { *(record.function_queries[247].output_at(__i).as_ptr() as *const [G; OUT_247]) }; __ret }, _ => { aiur_fn_247::(__args, __hash, record, io_buffer)? }, } } }; let __v_205: G = __r_arr[0]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_201, __v_205]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_206: G = __r_arr[0]; if (__v_206 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_206.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: Some("aggr cross-binding: output subject root is not nodeHash(left, right)".to_string()) }); } let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_59, __v_60]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_207: G = __r_arr[0]; let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_68, __v_69]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_208: G = __r_arr[0]; let __r_arr: [G; OUT_258] = { let __args: [G; IN_258] = [__v_202, __v_203]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(258, &__args[..])?) { [G::ZERO; OUT_258] } else { let (__hash, __hit) = record.function_queries[258].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[258].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[258].bump_multiplicity(__i); } let __ret: [G; OUT_258] = unsafe { *(record.function_queries[258].output_at(__i).as_ptr() as *const [G; OUT_258]) }; __ret }, _ => { aiur_fn_258::(__args, __hash, record, io_buffer)? }, } } }; let __v_209: G = __r_arr[0]; let __r_arr: [G; OUT_268] = { let __args: [G; IN_268] = [__v_207, __v_208, __v_58, __v_67, __v_209]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(268, &__args[..])?) { [G::ZERO; OUT_268] } else { let (__hash, __hit) = record.function_queries[268].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[268].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[268].bump_multiplicity(__i); } let __ret: [G; OUT_268] = unsafe { *(record.function_queries[268].output_at(__i).as_ptr() as *const [G; OUT_268]) }; __ret }, _ => { aiur_fn_268::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_292] = []; record.function_queries[292].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_293: usize = 8; +const IN_293: usize = 8; +const OUT_293: usize = 1; +fn aiur_fn_293(inp: [G; IN_293], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_293], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __r_arr: [G; OUT_275] = { let __args: [G; IN_275] = [__v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(275, &__args[..])?) { [G::ZERO; OUT_275] } else { let (__hash, __hit) = record.function_queries[275].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[275].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[275].bump_multiplicity(__i); } let __ret: [G; OUT_275] = unsafe { *(record.function_queries[275].output_at(__i).as_ptr() as *const [G; OUT_275]) }; __ret }, _ => { aiur_fn_275::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = __r_arr[1]; match __v_9.as_canonical_u64() { 229u64 => { let __r_arr: [G; OUT_270] = { let __args: [G; IN_270] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(270, &__args[..])?) { [G::ZERO; OUT_270] } else { let (__hash, __hit) = record.function_queries[270].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[270].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[270].bump_multiplicity(__i); } let __ret: [G; OUT_270] = unsafe { *(record.function_queries[270].output_at(__i).as_ptr() as *const [G; OUT_270]) }; __ret }, _ => { aiur_fn_270::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = __r_arr[1]; let __v_13: G = __r_arr[2]; let __v_14: G = __r_arr[3]; let __v_15: G = __r_arr[4]; let __v_16: G = __r_arr[5]; let __v_17: G = __r_arr[6]; let __v_18: G = __r_arr[7]; let __v_19: G = G::from_u64(3); let __v_20: G = __v_15 - __v_19; let __v_21: G = G::from_u64(5); let __v_22: G = __v_15 - __v_21; let __v_23: G = __v_20 * __v_22; let __v_24: G = G::from_u64(0); if (__v_23 != __v_24) { return Err(ExecError::AssertEqMismatch { lhs: __v_23.as_canonical_u64(), rhs: __v_24.as_canonical_u64(), msg: Some("aggr catalog: a member claim assumes its binding".to_string()) }); } let __v_25: G = G::from_u64(1); let __r_arr: [G; OUT_273] = { let __args: [G; IN_273] = [__v_17, __v_18, __v_25, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(273, &__args[..])?) { [G::ZERO; OUT_273] } else { let (__hash, __hit) = record.function_queries[273].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[273].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[273].bump_multiplicity(__i); } let __ret: [G; OUT_273] = unsafe { *(record.function_queries[273].output_at(__i).as_ptr() as *const [G; OUT_273]) }; __ret }, _ => { aiur_fn_273::(__args, __hash, record, io_buffer)? }, } } }; let __v_26: G = __r_arr[0]; if (__v_26 != __v_25) { return Err(ExecError::AssertEqMismatch { lhs: __v_26.as_canonical_u64(), rhs: __v_25.as_canonical_u64(), msg: Some("aggr catalog: a member claim has assumptions".to_string()) }); } let __r_arr: [G; OUT_75] = { let __args: [G; IN_75] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(75, &__args[..])?) { [G::ZERO; OUT_75] } else { let (__hash, __hit) = record.function_queries[75].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[75].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[75].bump_multiplicity(__i); } let __ret: [G; OUT_75] = unsafe { *(record.function_queries[75].output_at(__i).as_ptr() as *const [G; OUT_75]) }; __ret }, _ => { aiur_fn_75::(__args, __hash, record, io_buffer)? }, } } }; let __v_27: G = __r_arr[0]; let __v_28: G = { let __values: [G; 3] = [__v_25, __v_25, __v_25]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_29: G = { let __values: [G; 3] = [__v_24, __v_27, __v_28]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_293] = [__v_29]; record.function_queries[293].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_272] = { let __args: [G; IN_272] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(272, &__args[..])?) { [G::ZERO; OUT_272] } else { let (__hash, __hit) = record.function_queries[272].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[272].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[272].bump_multiplicity(__i); } let __ret: [G; OUT_272] = unsafe { *(record.function_queries[272].output_at(__i).as_ptr() as *const [G; OUT_272]) }; __ret }, _ => { aiur_fn_272::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __r_arr: [G; OUT_257] = { let __args: [G; IN_257] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(257, &__args[..])?) { [G::ZERO; OUT_257] } else { let (__hash, __hit) = record.function_queries[257].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[257].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[257].bump_multiplicity(__i); } let __ret: [G; OUT_257] = unsafe { *(record.function_queries[257].output_at(__i).as_ptr() as *const [G; OUT_257]) }; __ret }, _ => { aiur_fn_257::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __ret: [G; OUT_293] = [__v_12]; record.function_queries[293].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } +const INPUT_SIZE_294: usize = 34; +const IN_294: usize = 34; +const OUT_294: usize = 0; +fn aiur_fn_294(inp: [G; IN_294], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_294], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = G::from_u64(1); let __v_35: G = G::from_u64(0); let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_34, __v_35, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __v_38: G = __r_arr[2]; let __v_39: G = __r_arr[3]; let __v_40: G = __r_arr[4]; let __v_41: G = __r_arr[5]; let __v_42: G = __r_arr[6]; let __v_43: G = __r_arr[7]; let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_34, __v_34, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; let __v_45: G = __r_arr[1]; let __v_46: G = __r_arr[2]; let __v_47: G = __r_arr[3]; let __v_48: G = __r_arr[4]; let __v_49: G = __r_arr[5]; let __v_50: G = __r_arr[6]; let __v_51: G = __r_arr[7]; let __r_arr: [G; OUT_293] = { let __args: [G; IN_293] = [__v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(293, &__args[..])?) { [G::ZERO; OUT_293] } else { let (__hash, __hit) = record.function_queries[293].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[293].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[293].bump_multiplicity(__i); } let __ret: [G; OUT_293] = unsafe { *(record.function_queries[293].output_at(__i).as_ptr() as *const [G; OUT_293]) }; __ret }, _ => { aiur_fn_293::(__args, __hash, record, io_buffer)? }, } } }; let __v_52: G = __r_arr[0]; let __r_arr: [G; OUT_293] = { let __args: [G; IN_293] = [__v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(293, &__args[..])?) { [G::ZERO; OUT_293] } else { let (__hash, __hit) = record.function_queries[293].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[293].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[293].bump_multiplicity(__i); } let __ret: [G; OUT_293] = unsafe { *(record.function_queries[293].output_at(__i).as_ptr() as *const [G; OUT_293]) }; __ret }, _ => { aiur_fn_293::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; let __v_54: G = G::from_u64(2); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_54]; let __info = io_buffer.get_info(__v_54, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_55: G = __io_pair.0; let __v_56: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_54, __v_55, __v_56]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_57: G = __r_arr[0]; let __v_58: G = G::from_u64(103); let __v_59: G = G::from_u64(230); let __v_60: G = G::from_u64(9); let __v_61: G = G::from_u64(106); let __v_62: G = G::from_u64(133); let __v_63: G = G::from_u64(174); let __v_64: G = G::from_u64(187); let __v_65: G = G::from_u64(114); let __v_66: G = G::from_u64(243); let __v_67: G = G::from_u64(110); let __v_68: G = G::from_u64(60); let __v_69: G = G::from_u64(58); let __v_70: G = G::from_u64(245); let __v_71: G = G::from_u64(79); let __v_72: G = G::from_u64(165); let __v_73: G = G::from_u64(127); let __v_74: G = G::from_u64(82); let __v_75: G = G::from_u64(14); let __v_76: G = G::from_u64(81); let __v_77: G = G::from_u64(140); let __v_78: G = G::from_u64(104); let __v_79: G = G::from_u64(5); let __v_80: G = G::from_u64(155); let __v_81: G = G::from_u64(171); let __v_82: G = G::from_u64(217); let __v_83: G = G::from_u64(131); let __v_84: G = G::from_u64(31); let __v_85: G = G::from_u64(25); let __v_86: G = G::from_u64(205); let __v_87: G = G::from_u64(224); let __v_88: G = G::from_u64(91); let __v_89: G = { let __values: [G; 3] = [__v_34, __v_34, __v_34]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_90: G = { let __values: [G; 8] = [__v_35, __v_35, __v_35, __v_35, __v_35, __v_35, __v_35, __v_35]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_91: G = { let __values: [G; 32] = [__v_58, __v_59, __v_60, __v_61, __v_62, __v_63, __v_58, __v_64, __v_65, __v_66, __v_67, __v_68, __v_69, __v_70, __v_71, __v_72, __v_73, __v_74, __v_75, __v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_82, __v_83, __v_84, __v_85, __v_86, __v_87, __v_88]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_92: G = { let __values: [G; 34] = [__v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34, __v_34]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_57, __v_89, __v_35, __v_35, __v_90, __v_91, __v_92]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_93: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_93]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_94: G = __r_arr[0]; let __v_95: G = __r_arr[1]; let __v_96: G = __r_arr[2]; let __v_97: G = __r_arr[3]; let __v_98: G = __r_arr[4]; let __v_99: G = __r_arr[5]; let __v_100: G = __r_arr[6]; let __v_101: G = __r_arr[7]; let __v_102: G = __r_arr[8]; let __v_103: G = __r_arr[9]; let __v_104: G = __r_arr[10]; let __v_105: G = __r_arr[11]; let __v_106: G = __r_arr[12]; let __v_107: G = __r_arr[13]; let __v_108: G = __r_arr[14]; let __v_109: G = __r_arr[15]; let __v_110: G = __r_arr[16]; let __v_111: G = __r_arr[17]; let __v_112: G = __r_arr[18]; let __v_113: G = __r_arr[19]; let __v_114: G = __r_arr[20]; let __v_115: G = __r_arr[21]; let __v_116: G = __r_arr[22]; let __v_117: G = __r_arr[23]; let __v_118: G = __r_arr[24]; let __v_119: G = __r_arr[25]; let __v_120: G = __r_arr[26]; let __v_121: G = __r_arr[27]; let __v_122: G = __r_arr[28]; let __v_123: G = __r_arr[29]; let __v_124: G = __r_arr[30]; let __v_125: G = __r_arr[31]; let __v_126: G = G::from_u64(256); let __v_127: G = __v_126 * __v_95; let __v_128: G = __v_94 + __v_127; let __v_129: G = G::from_u64(65536); let __v_130: G = __v_129 * __v_96; let __v_131: G = __v_128 + __v_130; let __v_132: G = G::from_u64(16777216); let __v_133: G = __v_132 * __v_97; let __v_134: G = __v_131 + __v_133; let __v_135: G = __v_126 * __v_99; let __v_136: G = __v_98 + __v_135; let __v_137: G = __v_129 * __v_100; let __v_138: G = __v_136 + __v_137; let __v_139: G = __v_132 * __v_101; let __v_140: G = __v_138 + __v_139; let __v_141: G = __v_126 * __v_103; let __v_142: G = __v_102 + __v_141; let __v_143: G = __v_129 * __v_104; let __v_144: G = __v_142 + __v_143; let __v_145: G = __v_132 * __v_105; let __v_146: G = __v_144 + __v_145; let __v_147: G = __v_126 * __v_107; let __v_148: G = __v_106 + __v_147; let __v_149: G = __v_129 * __v_108; let __v_150: G = __v_148 + __v_149; let __v_151: G = __v_132 * __v_109; let __v_152: G = __v_150 + __v_151; let __v_153: G = __v_126 * __v_111; let __v_154: G = __v_110 + __v_153; let __v_155: G = __v_129 * __v_112; let __v_156: G = __v_154 + __v_155; let __v_157: G = __v_132 * __v_113; let __v_158: G = __v_156 + __v_157; let __v_159: G = __v_126 * __v_115; let __v_160: G = __v_114 + __v_159; let __v_161: G = __v_129 * __v_116; let __v_162: G = __v_160 + __v_161; let __v_163: G = __v_132 * __v_117; let __v_164: G = __v_162 + __v_163; let __v_165: G = __v_126 * __v_119; let __v_166: G = __v_118 + __v_165; let __v_167: G = __v_129 * __v_120; let __v_168: G = __v_166 + __v_167; let __v_169: G = __v_132 * __v_121; let __v_170: G = __v_168 + __v_169; let __v_171: G = __v_126 * __v_123; let __v_172: G = __v_122 + __v_171; let __v_173: G = __v_129 * __v_124; let __v_174: G = __v_172 + __v_173; let __v_175: G = __v_132 * __v_125; let __v_176: G = __v_174 + __v_175; if (__v_134 != __v_26) { return Err(ExecError::AssertEqMismatch { lhs: __v_134.as_canonical_u64(), rhs: __v_26.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } if (__v_140 != __v_27) { return Err(ExecError::AssertEqMismatch { lhs: __v_140.as_canonical_u64(), rhs: __v_27.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } if (__v_146 != __v_28) { return Err(ExecError::AssertEqMismatch { lhs: __v_146.as_canonical_u64(), rhs: __v_28.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } if (__v_152 != __v_29) { return Err(ExecError::AssertEqMismatch { lhs: __v_152.as_canonical_u64(), rhs: __v_29.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } if (__v_158 != __v_30) { return Err(ExecError::AssertEqMismatch { lhs: __v_158.as_canonical_u64(), rhs: __v_30.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } if (__v_164 != __v_31) { return Err(ExecError::AssertEqMismatch { lhs: __v_164.as_canonical_u64(), rhs: __v_31.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } if (__v_170 != __v_32) { return Err(ExecError::AssertEqMismatch { lhs: __v_170.as_canonical_u64(), rhs: __v_32.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } if (__v_176 != __v_33) { return Err(ExecError::AssertEqMismatch { lhs: __v_176.as_canonical_u64(), rhs: __v_33.as_canonical_u64(), msg: Some("aggr catalog: output claim digest mismatch".to_string()) }); } let __r_arr: [G; OUT_272] = { let __args: [G; IN_272] = [__v_57]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(272, &__args[..])?) { [G::ZERO; OUT_272] } else { let (__hash, __hit) = record.function_queries[272].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[272].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[272].bump_multiplicity(__i); } let __ret: [G; OUT_272] = unsafe { *(record.function_queries[272].output_at(__i).as_ptr() as *const [G; OUT_272]) }; __ret }, _ => { aiur_fn_272::(__args, __hash, record, io_buffer)? }, } } }; let __v_177: G = __r_arr[0]; let __r_arr: [G; OUT_257] = { let __args: [G; IN_257] = [__v_177]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(257, &__args[..])?) { [G::ZERO; OUT_257] } else { let (__hash, __hit) = record.function_queries[257].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[257].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[257].bump_multiplicity(__i); } let __ret: [G; OUT_257] = unsafe { *(record.function_queries[257].output_at(__i).as_ptr() as *const [G; OUT_257]) }; __ret }, _ => { aiur_fn_257::(__args, __hash, record, io_buffer)? }, } } }; let __v_178: G = __r_arr[0]; let __r_arr: [G; OUT_260] = { let __args: [G; IN_260] = [__v_52, __v_53, __v_178]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(260, &__args[..])?) { [G::ZERO; OUT_260] } else { let (__hash, __hit) = record.function_queries[260].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[260].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[260].bump_multiplicity(__i); } let __ret: [G; OUT_260] = unsafe { *(record.function_queries[260].output_at(__i).as_ptr() as *const [G; OUT_260]) }; __ret }, _ => { aiur_fn_260::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_294] = []; record.function_queries[294].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_295: usize = 1; +const IN_295: usize = 1; +const OUT_295: usize = 19; +fn aiur_fn_295(inp: [G; IN_295], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_295], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __v_3: G = G::from_u64(82); if (__v_1 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_1.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("aggr range: statement has the wrong tag".to_string()) }); } let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __v_9: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_9]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __v_11: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __v_13: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_14: G = __r_arr[0]; let __v_15: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __v_19: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_20: G = __r_arr[0]; let __v_21: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_21]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_22: G = __r_arr[0]; let __v_23: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_23]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_24: G = __r_arr[0]; let __v_25: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_26: G = __r_arr[0]; let __v_27: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_27]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_28: G = __r_arr[0]; let __v_29: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_29]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_30: G = __r_arr[0]; let __v_31: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_31]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_32: G = __r_arr[0]; let __v_33: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_33]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_34: G = __r_arr[0]; let __v_35: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_35]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __v_37: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_37]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_38: G = __r_arr[0]; let __v_39: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_39]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_40: G = __r_arr[0]; let __v_41: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_41]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_42: G = __r_arr[0]; let __v_43: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_44: G = __r_arr[0]; let __v_45: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_45]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_46: G = __r_arr[0]; let __v_47: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_47]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_48: G = __r_arr[0]; let __v_49: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_49]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_50: G = __r_arr[0]; let __v_51: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_51]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_52: G = __r_arr[0]; let __v_53: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_53]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __v_55: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_55]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_56: G = __r_arr[0]; let __v_57: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_57]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_58: G = __r_arr[0]; let __v_59: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_59]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_60: G = __r_arr[0]; let __v_61: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_61]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_62: G = __r_arr[0]; let __v_63: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_63]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_64: G = __r_arr[0]; let __v_65: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_65]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_66: G = __r_arr[0]; let __v_67: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_67]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_68: G = __r_arr[0]; let __v_69: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_69.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_70: G = __loaded[0]; let __v_71: G = __loaded[1]; let __v_72: G = __loaded[2]; let __v_73: G = G::from_u64(1); if (__v_70 != __v_73) { return Err(ExecError::AssertEqMismatch { lhs: __v_70.as_canonical_u64(), rhs: __v_73.as_canonical_u64(), msg: Some("aggr range: trailing bytes after the statement".to_string()) }); } if (__v_71 != __v_73) { return Err(ExecError::AssertEqMismatch { lhs: __v_71.as_canonical_u64(), rhs: __v_73.as_canonical_u64(), msg: Some("aggr range: trailing bytes after the statement".to_string()) }); } if (__v_72 != __v_73) { return Err(ExecError::AssertEqMismatch { lhs: __v_72.as_canonical_u64(), rhs: __v_73.as_canonical_u64(), msg: Some("aggr range: trailing bytes after the statement".to_string()) }); } let __v_74: G = G::from_u64(256); let __v_75: G = __v_74 * __v_40; let __v_76: G = __v_38 + __v_75; let __v_77: G = G::from_u64(65536); let __v_78: G = __v_77 * __v_42; let __v_79: G = __v_76 + __v_78; let __v_80: G = G::from_u64(16777216); let __v_81: G = __v_80 * __v_44; let __v_82: G = __v_79 + __v_81; let __v_83: G = G::from_u64(4294967296); let __v_84: G = __v_83 * __v_46; let __v_85: G = __v_82 + __v_84; let __v_86: G = G::from_u64(1099511627776); let __v_87: G = __v_86 * __v_48; let __v_88: G = __v_85 + __v_87; let __v_89: G = G::from_u64(281474976710656); let __v_90: G = __v_89 * __v_50; let __v_91: G = __v_88 + __v_90; let __v_92: G = G::from_u64(72057594037927936); let __v_93: G = __v_92 * __v_52; let __v_94: G = __v_91 + __v_93; let __v_95: G = __v_74 * __v_56; let __v_96: G = __v_54 + __v_95; let __v_97: G = __v_77 * __v_58; let __v_98: G = __v_96 + __v_97; let __v_99: G = __v_80 * __v_60; let __v_100: G = __v_98 + __v_99; let __v_101: G = __v_83 * __v_62; let __v_102: G = __v_100 + __v_101; let __v_103: G = __v_86 * __v_64; let __v_104: G = __v_102 + __v_103; let __v_105: G = __v_89 * __v_66; let __v_106: G = __v_104 + __v_105; let __v_107: G = __v_92 * __v_68; let __v_108: G = __v_106 + __v_107; let __ret: [G; OUT_295] = [__v_4, __v_6, __v_8, __v_10, __v_12, __v_14, __v_16, __v_18, __v_20, __v_22, __v_24, __v_26, __v_28, __v_30, __v_32, __v_34, __v_36, __v_94, __v_108]; record.function_queries[295].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_296: usize = 1; +const IN_296: usize = 1; const OUT_296: usize = 2; -fn aiur_fn_296(inp: [G; IN_296], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_296], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __v_2: G = G::from_u64(1); let __v_3: G = { let __values: [G; 3] = [__v_2, __v_2, __v_2]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_296] = [__v_3, __v_0]; record.function_queries[296].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __v_3: G = __r_arr[1]; let __v_4: G = G::from_u64(1); let __v_5: G = __v_1 - __v_4; let __r_arr: [G; OUT_296] = { let __args: [G; IN_296] = [__v_3, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(296, &__args[..])?) { [G::ZERO; OUT_296] } else { let (__hash, __hit) = record.function_queries[296].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[296].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[296].bump_multiplicity(__i); } let __ret: [G; OUT_296] = unsafe { *(record.function_queries[296].output_at(__i).as_ptr() as *const [G; OUT_296]) }; __ret }, _ => { aiur_fn_296::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = __r_arr[1]; let __v_8: G = G::from_u64(0); let __v_9: G = { let __values: [G; 3] = [__v_8, __v_2, __v_6]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_296] = [__v_9, __v_7]; record.function_queries[296].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } -const INPUT_SIZE_297: usize = 1; -const IN_297: usize = 1; -const OUT_297: usize = 5; -fn aiur_fn_297(inp: [G; IN_297], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_297], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_27] = { let __args: [G; IN_27] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(27, &__args[..])?) { [G::ZERO; OUT_27] } else { let (__hash, __hit) = record.function_queries[27].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[27].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[27].bump_multiplicity(__i); } let __ret: [G; OUT_27] = unsafe { *(record.function_queries[27].output_at(__i).as_ptr() as *const [G; OUT_27]) }; __ret }, _ => { aiur_fn_27::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __v_3: G = __r_arr[2]; let __v_4: G = __r_arr[3]; let __v_5: G = __r_arr[4]; let __v_6: G = __r_arr[5]; let __v_7: G = __r_arr[6]; let __v_8: G = __r_arr[7]; let __v_9: G = __r_arr[8]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __r_arr: [G; OUT_296] = { let __args: [G; IN_296] = [__v_9, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(296, &__args[..])?) { [G::ZERO; OUT_296] } else { let (__hash, __hit) = record.function_queries[296].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[296].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[296].bump_multiplicity(__i); } let __ret: [G; OUT_296] = unsafe { *(record.function_queries[296].output_at(__i).as_ptr() as *const [G; OUT_296]) }; __ret }, _ => { aiur_fn_296::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = __r_arr[1]; let __r_arr: [G; OUT_27] = { let __args: [G; IN_27] = [__v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(27, &__args[..])?) { [G::ZERO; OUT_27] } else { let (__hash, __hit) = record.function_queries[27].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[27].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[27].bump_multiplicity(__i); } let __ret: [G; OUT_27] = unsafe { *(record.function_queries[27].output_at(__i).as_ptr() as *const [G; OUT_27]) }; __ret }, _ => { aiur_fn_27::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __v_15: G = __r_arr[2]; let __v_16: G = __r_arr[3]; let __v_17: G = __r_arr[4]; let __v_18: G = __r_arr[5]; let __v_19: G = __r_arr[6]; let __v_20: G = __r_arr[7]; let __v_21: G = __r_arr[8]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_22: G = __r_arr[0]; let __r_arr: [G; OUT_295] = { let __args: [G; IN_295] = [__v_21, __v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(295, &__args[..])?) { [G::ZERO; OUT_295] } else { let (__hash, __hit) = record.function_queries[295].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[295].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[295].bump_multiplicity(__i); } let __ret: [G; OUT_295] = unsafe { *(record.function_queries[295].output_at(__i).as_ptr() as *const [G; OUT_295]) }; __ret }, _ => { aiur_fn_295::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __v_24: G = __r_arr[1]; let __r_arr: [G; OUT_27] = { let __args: [G; IN_27] = [__v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(27, &__args[..])?) { [G::ZERO; OUT_27] } else { let (__hash, __hit) = record.function_queries[27].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[27].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[27].bump_multiplicity(__i); } let __ret: [G; OUT_27] = unsafe { *(record.function_queries[27].output_at(__i).as_ptr() as *const [G; OUT_27]) }; __ret }, _ => { aiur_fn_27::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __v_26: G = __r_arr[1]; let __v_27: G = __r_arr[2]; let __v_28: G = __r_arr[3]; let __v_29: G = __r_arr[4]; let __v_30: G = __r_arr[5]; let __v_31: G = __r_arr[6]; let __v_32: G = __r_arr[7]; let __v_33: G = __r_arr[8]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_34: G = __r_arr[0]; let __r_arr: [G; OUT_296] = { let __args: [G; IN_296] = [__v_33, __v_34]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(296, &__args[..])?) { [G::ZERO; OUT_296] } else { let (__hash, __hit) = record.function_queries[296].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[296].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[296].bump_multiplicity(__i); } let __ret: [G; OUT_296] = unsafe { *(record.function_queries[296].output_at(__i).as_ptr() as *const [G; OUT_296]) }; __ret }, _ => { aiur_fn_296::(__args, __hash, record, io_buffer)? }, } } }; let __v_35: G = __r_arr[0]; let __v_36: G = __r_arr[1]; let __r_arr: [G; OUT_28] = { let __args: [G; IN_28] = [__v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(28, &__args[..])?) { [G::ZERO; OUT_28] } else { let (__hash, __hit) = record.function_queries[28].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[28].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[28].bump_multiplicity(__i); } let __ret: [G; OUT_28] = unsafe { *(record.function_queries[28].output_at(__i).as_ptr() as *const [G; OUT_28]) }; __ret }, _ => { aiur_fn_28::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = __r_arr[1]; let __r_arr: [G; OUT_241] = { let __args: [G; IN_241] = [__v_38, __v_37]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(241, &__args[..])?) { [G::ZERO; OUT_241] } else { let (__hash, __hit) = record.function_queries[241].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[241].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[241].bump_multiplicity(__i); } let __ret: [G; OUT_241] = unsafe { *(record.function_queries[241].output_at(__i).as_ptr() as *const [G; OUT_241]) }; __ret }, _ => { aiur_fn_241::(__args, __hash, record, io_buffer)? }, } } }; let __v_39: G = __r_arr[0]; let __v_40: G = __r_arr[1]; let __ret: [G; OUT_297] = [__v_11, __v_23, __v_35, __v_39, __v_40]; record.function_queries[297].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +fn aiur_fn_296(inp: [G; IN_296], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_296], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_2]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __v_4: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __v_24: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __v_26: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_27: G = __r_arr[0]; let __v_28: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_28]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_29: G = __r_arr[0]; let __v_30: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_30]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_31: G = __r_arr[0]; let __v_32: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_32]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_33: G = __r_arr[0]; let __v_34: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_34]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_35: G = __r_arr[0]; let __v_36: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_38]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_39: G = __r_arr[0]; let __v_40: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_40]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_41: G = __r_arr[0]; let __v_42: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_42]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_43: G = __r_arr[0]; let __v_44: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_44]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_45: G = __r_arr[0]; let __v_46: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_46]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_47: G = __r_arr[0]; let __v_48: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __v_50: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_50]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_51: G = __r_arr[0]; let __v_52: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_52]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_53: G = __r_arr[0]; let __v_54: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_54]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_55: G = __r_arr[0]; let __v_56: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_56]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_57: G = __r_arr[0]; let __v_58: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_58]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_59: G = __r_arr[0]; let __v_60: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_60]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_61: G = __r_arr[0]; let __v_62: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_62]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_63: G = __r_arr[0]; let __v_64: G = __r_arr[1]; let __v_65: G = { let __values: [G; 32] = [__v_1, __v_3, __v_5, __v_7, __v_9, __v_11, __v_13, __v_15, __v_17, __v_19, __v_21, __v_23, __v_25, __v_27, __v_29, __v_31, __v_33, __v_35, __v_37, __v_39, __v_41, __v_43, __v_45, __v_47, __v_49, __v_51, __v_53, __v_55, __v_57, __v_59, __v_61, __v_63]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_296] = [__v_65, __v_64]; record.function_queries[296].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_297: usize = 2; +const IN_297: usize = 2; +const OUT_297: usize = 2; +fn aiur_fn_297(inp: [G; IN_297], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_297], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __v_2: G = G::from_u64(1); let __v_3: G = { let __values: [G; 3] = [__v_2, __v_2, __v_2]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_297] = [__v_3, __v_0]; record.function_queries[297].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_296] = { let __args: [G; IN_296] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(296, &__args[..])?) { [G::ZERO; OUT_296] } else { let (__hash, __hit) = record.function_queries[296].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[296].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[296].bump_multiplicity(__i); } let __ret: [G; OUT_296] = unsafe { *(record.function_queries[296].output_at(__i).as_ptr() as *const [G; OUT_296]) }; __ret }, _ => { aiur_fn_296::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __v_3: G = __r_arr[1]; let __v_4: G = G::from_u64(1); let __v_5: G = __v_1 - __v_4; let __r_arr: [G; OUT_297] = { let __args: [G; IN_297] = [__v_3, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(297, &__args[..])?) { [G::ZERO; OUT_297] } else { let (__hash, __hit) = record.function_queries[297].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[297].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[297].bump_multiplicity(__i); } let __ret: [G; OUT_297] = unsafe { *(record.function_queries[297].output_at(__i).as_ptr() as *const [G; OUT_297]) }; __ret }, _ => { aiur_fn_297::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = __r_arr[1]; let __v_8: G = G::from_u64(0); let __v_9: G = { let __values: [G; 3] = [__v_8, __v_2, __v_6]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_297] = [__v_9, __v_7]; record.function_queries[297].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } const INPUT_SIZE_298: usize = 2; const IN_298: usize = 2; const OUT_298: usize = 2; -fn aiur_fn_298(inp: [G; IN_298], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_298], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __v_2: G = G::from_u64(1); let __v_3: G = { let __values: [G; 6] = [__v_2, __v_2, __v_2, __v_2, __v_2, __v_2]; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 6)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_298] = [__v_3, __v_0]; record.function_queries[298].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_297] = { let __args: [G; IN_297] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(297, &__args[..])?) { [G::ZERO; OUT_297] } else { let (__hash, __hit) = record.function_queries[297].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[297].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[297].bump_multiplicity(__i); } let __ret: [G; OUT_297] = unsafe { *(record.function_queries[297].output_at(__i).as_ptr() as *const [G; OUT_297]) }; __ret }, _ => { aiur_fn_297::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __v_3: G = __r_arr[1]; let __v_4: G = __r_arr[2]; let __v_5: G = __r_arr[3]; let __v_6: G = __r_arr[4]; let __v_7: G = G::from_u64(1); let __v_8: G = __v_1 - __v_7; let __r_arr: [G; OUT_298] = { let __args: [G; IN_298] = [__v_6, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(298, &__args[..])?) { [G::ZERO; OUT_298] } else { let (__hash, __hit) = record.function_queries[298].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[298].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[298].bump_multiplicity(__i); } let __ret: [G; OUT_298] = unsafe { *(record.function_queries[298].output_at(__i).as_ptr() as *const [G; OUT_298]) }; __ret }, _ => { aiur_fn_298::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = __r_arr[1]; let __v_11: G = G::from_u64(0); let __v_12: G = { let __values: [G; 6] = [__v_11, __v_2, __v_3, __v_4, __v_5, __v_9]; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 6)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_298] = [__v_12, __v_10]; record.function_queries[298].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } +fn aiur_fn_298(inp: [G; IN_298], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_298], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __v_2: G = G::from_u64(1); let __v_3: G = { let __values: [G; 3] = [__v_2, __v_2, __v_2]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_298] = [__v_3, __v_0]; record.function_queries[298].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __v_3: G = __r_arr[1]; let __v_4: G = G::from_u64(1); let __v_5: G = __v_1 - __v_4; let __r_arr: [G; OUT_298] = { let __args: [G; IN_298] = [__v_3, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(298, &__args[..])?) { [G::ZERO; OUT_298] } else { let (__hash, __hit) = record.function_queries[298].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[298].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[298].bump_multiplicity(__i); } let __ret: [G; OUT_298] = unsafe { *(record.function_queries[298].output_at(__i).as_ptr() as *const [G; OUT_298]) }; __ret }, _ => { aiur_fn_298::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = __r_arr[1]; let __v_8: G = G::from_u64(0); let __v_9: G = { let __values: [G; 3] = [__v_8, __v_2, __v_6]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_298] = [__v_9, __v_7]; record.function_queries[298].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } const INPUT_SIZE_299: usize = 1; const IN_299: usize = 1; -const OUT_299: usize = 10; -fn aiur_fn_299(inp: [G; IN_299], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_299], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_28] = { let __args: [G; IN_28] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(28, &__args[..])?) { [G::ZERO; OUT_28] } else { let (__hash, __hit) = record.function_queries[28].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[28].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[28].bump_multiplicity(__i); } let __ret: [G; OUT_28] = unsafe { *(record.function_queries[28].output_at(__i).as_ptr() as *const [G; OUT_28]) }; __ret }, _ => { aiur_fn_28::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __r_arr: [G; OUT_243] = { let __args: [G; IN_243] = [__v_2, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(243, &__args[..])?) { [G::ZERO; OUT_243] } else { let (__hash, __hit) = record.function_queries[243].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[243].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[243].bump_multiplicity(__i); } let __ret: [G; OUT_243] = unsafe { *(record.function_queries[243].output_at(__i).as_ptr() as *const [G; OUT_243]) }; __ret }, _ => { aiur_fn_243::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __v_4: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __ret: [G; OUT_299] = [__v_3, __v_5, __v_7, __v_9, __v_11, __v_13, __v_15, __v_17, __v_19, __v_20]; record.function_queries[299].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const OUT_299: usize = 5; +fn aiur_fn_299(inp: [G; IN_299], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_299], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_27] = { let __args: [G; IN_27] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(27, &__args[..])?) { [G::ZERO; OUT_27] } else { let (__hash, __hit) = record.function_queries[27].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[27].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[27].bump_multiplicity(__i); } let __ret: [G; OUT_27] = unsafe { *(record.function_queries[27].output_at(__i).as_ptr() as *const [G; OUT_27]) }; __ret }, _ => { aiur_fn_27::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __v_3: G = __r_arr[2]; let __v_4: G = __r_arr[3]; let __v_5: G = __r_arr[4]; let __v_6: G = __r_arr[5]; let __v_7: G = __r_arr[6]; let __v_8: G = __r_arr[7]; let __v_9: G = __r_arr[8]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_10: G = __r_arr[0]; let __r_arr: [G; OUT_298] = { let __args: [G; IN_298] = [__v_9, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(298, &__args[..])?) { [G::ZERO; OUT_298] } else { let (__hash, __hit) = record.function_queries[298].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[298].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[298].bump_multiplicity(__i); } let __ret: [G; OUT_298] = unsafe { *(record.function_queries[298].output_at(__i).as_ptr() as *const [G; OUT_298]) }; __ret }, _ => { aiur_fn_298::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = __r_arr[1]; let __r_arr: [G; OUT_27] = { let __args: [G; IN_27] = [__v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(27, &__args[..])?) { [G::ZERO; OUT_27] } else { let (__hash, __hit) = record.function_queries[27].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[27].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[27].bump_multiplicity(__i); } let __ret: [G; OUT_27] = unsafe { *(record.function_queries[27].output_at(__i).as_ptr() as *const [G; OUT_27]) }; __ret }, _ => { aiur_fn_27::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __v_15: G = __r_arr[2]; let __v_16: G = __r_arr[3]; let __v_17: G = __r_arr[4]; let __v_18: G = __r_arr[5]; let __v_19: G = __r_arr[6]; let __v_20: G = __r_arr[7]; let __v_21: G = __r_arr[8]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_22: G = __r_arr[0]; let __r_arr: [G; OUT_297] = { let __args: [G; IN_297] = [__v_21, __v_22]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(297, &__args[..])?) { [G::ZERO; OUT_297] } else { let (__hash, __hit) = record.function_queries[297].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[297].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[297].bump_multiplicity(__i); } let __ret: [G; OUT_297] = unsafe { *(record.function_queries[297].output_at(__i).as_ptr() as *const [G; OUT_297]) }; __ret }, _ => { aiur_fn_297::(__args, __hash, record, io_buffer)? }, } } }; let __v_23: G = __r_arr[0]; let __v_24: G = __r_arr[1]; let __r_arr: [G; OUT_27] = { let __args: [G; IN_27] = [__v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(27, &__args[..])?) { [G::ZERO; OUT_27] } else { let (__hash, __hit) = record.function_queries[27].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[27].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[27].bump_multiplicity(__i); } let __ret: [G; OUT_27] = unsafe { *(record.function_queries[27].output_at(__i).as_ptr() as *const [G; OUT_27]) }; __ret }, _ => { aiur_fn_27::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __v_26: G = __r_arr[1]; let __v_27: G = __r_arr[2]; let __v_28: G = __r_arr[3]; let __v_29: G = __r_arr[4]; let __v_30: G = __r_arr[5]; let __v_31: G = __r_arr[6]; let __v_32: G = __r_arr[7]; let __v_33: G = __r_arr[8]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_34: G = __r_arr[0]; let __r_arr: [G; OUT_298] = { let __args: [G; IN_298] = [__v_33, __v_34]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(298, &__args[..])?) { [G::ZERO; OUT_298] } else { let (__hash, __hit) = record.function_queries[298].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[298].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[298].bump_multiplicity(__i); } let __ret: [G; OUT_298] = unsafe { *(record.function_queries[298].output_at(__i).as_ptr() as *const [G; OUT_298]) }; __ret }, _ => { aiur_fn_298::(__args, __hash, record, io_buffer)? }, } } }; let __v_35: G = __r_arr[0]; let __v_36: G = __r_arr[1]; let __r_arr: [G; OUT_28] = { let __args: [G; IN_28] = [__v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(28, &__args[..])?) { [G::ZERO; OUT_28] } else { let (__hash, __hit) = record.function_queries[28].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[28].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[28].bump_multiplicity(__i); } let __ret: [G; OUT_28] = unsafe { *(record.function_queries[28].output_at(__i).as_ptr() as *const [G; OUT_28]) }; __ret }, _ => { aiur_fn_28::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = __r_arr[1]; let __r_arr: [G; OUT_241] = { let __args: [G; IN_241] = [__v_38, __v_37]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(241, &__args[..])?) { [G::ZERO; OUT_241] } else { let (__hash, __hit) = record.function_queries[241].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[241].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[241].bump_multiplicity(__i); } let __ret: [G; OUT_241] = unsafe { *(record.function_queries[241].output_at(__i).as_ptr() as *const [G; OUT_241]) }; __ret }, _ => { aiur_fn_241::(__args, __hash, record, io_buffer)? }, } } }; let __v_39: G = __r_arr[0]; let __v_40: G = __r_arr[1]; let __ret: [G; OUT_299] = [__v_11, __v_23, __v_35, __v_39, __v_40]; record.function_queries[299].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } const INPUT_SIZE_300: usize = 2; const IN_300: usize = 2; const OUT_300: usize = 2; -fn aiur_fn_300(inp: [G; IN_300], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_300], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __v_2: G = G::from_u64(1); let __v_3: G = { let __values: [G; 11] = [__v_2, __v_2, __v_2, __v_2, __v_2, __v_2, __v_2, __v_2, __v_2, __v_2, __v_2]; let __mq = record.memory_queries.get_index_mut(7).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(11))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 11)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_300] = [__v_3, __v_0]; record.function_queries[300].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_299] = { let __args: [G; IN_299] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(299, &__args[..])?) { [G::ZERO; OUT_299] } else { let (__hash, __hit) = record.function_queries[299].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[299].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[299].bump_multiplicity(__i); } let __ret: [G; OUT_299] = unsafe { *(record.function_queries[299].output_at(__i).as_ptr() as *const [G; OUT_299]) }; __ret }, _ => { aiur_fn_299::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __v_3: G = __r_arr[1]; let __v_4: G = __r_arr[2]; let __v_5: G = __r_arr[3]; let __v_6: G = __r_arr[4]; let __v_7: G = __r_arr[5]; let __v_8: G = __r_arr[6]; let __v_9: G = __r_arr[7]; let __v_10: G = __r_arr[8]; let __v_11: G = __r_arr[9]; let __v_12: G = G::from_u64(1); let __v_13: G = __v_1 - __v_12; let __r_arr: [G; OUT_300] = { let __args: [G; IN_300] = [__v_11, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(300, &__args[..])?) { [G::ZERO; OUT_300] } else { let (__hash, __hit) = record.function_queries[300].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[300].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[300].bump_multiplicity(__i); } let __ret: [G; OUT_300] = unsafe { *(record.function_queries[300].output_at(__i).as_ptr() as *const [G; OUT_300]) }; __ret }, _ => { aiur_fn_300::(__args, __hash, record, io_buffer)? }, } } }; let __v_14: G = __r_arr[0]; let __v_15: G = __r_arr[1]; let __v_16: G = G::from_u64(0); let __v_17: G = { let __values: [G; 11] = [__v_16, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_14]; let __mq = record.memory_queries.get_index_mut(7).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(11))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 11)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_300] = [__v_17, __v_15]; record.function_queries[300].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } -const INPUT_SIZE_301: usize = 2; -const IN_301: usize = 2; -const OUT_301: usize = 2; -fn aiur_fn_301(inp: [G; IN_301], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_301], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = G::from_u64(0); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_0]; let __info = io_buffer.get_info(__v_2, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_3: G = __io_pair.0; let __v_4: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_2, __v_3, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __r_arr: [G; OUT_75] = { let __args: [G; IN_75] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(75, &__args[..])?) { [G::ZERO; OUT_75] } else { let (__hash, __hit) = record.function_queries[75].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[75].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[75].bump_multiplicity(__i); } let __ret: [G; OUT_75] = unsafe { *(record.function_queries[75].output_at(__i).as_ptr() as *const [G; OUT_75]) }; __ret }, _ => { aiur_fn_75::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_6, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = G::from_u64(1); if (__v_7 != __v_8) { return Err(ExecError::AssertEqMismatch { lhs: __v_7.as_canonical_u64(), rhs: __v_8.as_canonical_u64(), msg: Some("aggr range: preamble does not hash to the statement's batch digest".to_string()) }); } let __r_arr: [G; OUT_27] = { let __args: [G; IN_27] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(27, &__args[..])?) { [G::ZERO; OUT_27] } else { let (__hash, __hit) = record.function_queries[27].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[27].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[27].bump_multiplicity(__i); } let __ret: [G; OUT_27] = unsafe { *(record.function_queries[27].output_at(__i).as_ptr() as *const [G; OUT_27]) }; __ret }, _ => { aiur_fn_27::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = __r_arr[1]; let __v_11: G = __r_arr[2]; let __v_12: G = __r_arr[3]; let __v_13: G = __r_arr[4]; let __v_14: G = __r_arr[5]; let __v_15: G = __r_arr[6]; let __v_16: G = __r_arr[7]; let __v_17: G = __r_arr[8]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __r_arr: [G; OUT_298] = { let __args: [G; IN_298] = [__v_17, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(298, &__args[..])?) { [G::ZERO; OUT_298] } else { let (__hash, __hit) = record.function_queries[298].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[298].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[298].bump_multiplicity(__i); } let __ret: [G; OUT_298] = unsafe { *(record.function_queries[298].output_at(__i).as_ptr() as *const [G; OUT_298]) }; __ret }, _ => { aiur_fn_298::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __r_arr: [G; OUT_27] = { let __args: [G; IN_27] = [__v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(27, &__args[..])?) { [G::ZERO; OUT_27] } else { let (__hash, __hit) = record.function_queries[27].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[27].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[27].bump_multiplicity(__i); } let __ret: [G; OUT_27] = unsafe { *(record.function_queries[27].output_at(__i).as_ptr() as *const [G; OUT_27]) }; __ret }, _ => { aiur_fn_27::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __v_23: G = __r_arr[2]; let __v_24: G = __r_arr[3]; let __v_25: G = __r_arr[4]; let __v_26: G = __r_arr[5]; let __v_27: G = __r_arr[6]; let __v_28: G = __r_arr[7]; let __v_29: G = __r_arr[8]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_30: G = __r_arr[0]; let __r_arr: [G; OUT_300] = { let __args: [G; IN_300] = [__v_29, __v_30]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(300, &__args[..])?) { [G::ZERO; OUT_300] } else { let (__hash, __hit) = record.function_queries[300].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[300].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[300].bump_multiplicity(__i); } let __ret: [G; OUT_300] = unsafe { *(record.function_queries[300].output_at(__i).as_ptr() as *const [G; OUT_300]) }; __ret }, _ => { aiur_fn_300::(__args, __hash, record, io_buffer)? }, } } }; let __v_31: G = __r_arr[0]; let __v_32: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_32.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_33: G = __loaded[0]; let __v_34: G = __loaded[1]; let __v_35: G = __loaded[2]; if (__v_33 != __v_8) { return Err(ExecError::AssertEqMismatch { lhs: __v_33.as_canonical_u64(), rhs: __v_8.as_canonical_u64(), msg: Some("aggr range: trailing bytes after the preamble".to_string()) }); } if (__v_34 != __v_8) { return Err(ExecError::AssertEqMismatch { lhs: __v_34.as_canonical_u64(), rhs: __v_8.as_canonical_u64(), msg: Some("aggr range: trailing bytes after the preamble".to_string()) }); } if (__v_35 != __v_8) { return Err(ExecError::AssertEqMismatch { lhs: __v_35.as_canonical_u64(), rhs: __v_8.as_canonical_u64(), msg: Some("aggr range: trailing bytes after the preamble".to_string()) }); } let __ret: [G; OUT_301] = [__v_19, __v_31]; record.function_queries[301].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_302: usize = 14; -const IN_302: usize = 14; -const OUT_302: usize = 5; -fn aiur_fn_302(inp: [G; IN_302], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_302], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; match __v_0.as_canonical_u64() { _ => { let __mc_out___mc_0: [G; 1] = '__mc_0: { match __v_9.as_canonical_u64() { 0u64 => { let __v_14: G = G::from_u64(1); let __v_15: G = { let __values: [G; 3] = [__v_14, __v_14, __v_14]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; break '__mc_0 [__v_15]; }, 1u64 => { break '__mc_0 [__v_10]; }, _ => { return Err(ExecError::MatchNoCase(__v_9.as_canonical_u64())); }, } }; let __v_14: G = __mc_out___mc_0[0]; let __r_arr: [G; OUT_219] = { let __args: [G; IN_219] = [__v_7, __v_14, __v_12, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(219, &__args[..])?) { [G::ZERO; OUT_219] } else { let (__hash, __hit) = record.function_queries[219].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[219].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[219].bump_multiplicity(__i); } let __ret: [G; OUT_219] = unsafe { *(record.function_queries[219].output_at(__i).as_ptr() as *const [G; OUT_219]) }; __ret }, _ => { aiur_fn_219::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __r_arr[1]; let __v_17: G = __r_arr[2]; let __v_18: G = __r_arr[3]; let __v_19: G = __r_arr[4]; let __ret: [G; OUT_302] = [__v_15, __v_16, __v_17, __v_18, __v_19]; record.function_queries[302].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } -const INPUT_SIZE_303: usize = 23; -const IN_303: usize = 23; -const OUT_303: usize = 3; -fn aiur_fn_303(inp: [G; IN_303], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_303], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; match __v_15.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_303] = [__v_21, __v_22, __v_13]; record.function_queries[303].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __loaded: [G; 6] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let __ptr_u64 = __v_12.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 6 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 6] = __args[..6].try_into().unwrap(); __arr }; let __v_23: G = __loaded[0]; let __v_24: G = __loaded[1]; let __v_25: G = __loaded[2]; let __v_26: G = __loaded[3]; let __v_27: G = __loaded[4]; let __v_28: G = __loaded[5]; match __v_23.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_62] = { let __args: [G; IN_62] = [__v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(62, &__args[..])?) { [G::ZERO; OUT_62] } else { let (__hash, __hit) = record.function_queries[62].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[62].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[62].bump_multiplicity(__i); } let __ret: [G; OUT_62] = unsafe { *(record.function_queries[62].output_at(__i).as_ptr() as *const [G; OUT_62]) }; __ret }, _ => { aiur_fn_62::(__args, __hash, record, io_buffer)? }, } } }; let __v_29: G = __r_arr[0]; let __v_30: G = __r_arr[1]; let __v_31: G = __r_arr[2]; let __v_32: G = __r_arr[3]; let __v_33: G = __r_arr[4]; let __v_34: G = __r_arr[5]; let __v_35: G = __r_arr[6]; let __v_36: G = __r_arr[7]; let __v_37: G = __r_arr[8]; let __v_38: G = __r_arr[9]; let __v_39: G = __r_arr[10]; let __v_40: G = __r_arr[11]; let __v_41: G = __r_arr[12]; let __v_42: G = __r_arr[13]; let __v_43: G = __r_arr[14]; let __v_44: G = __r_arr[15]; let __v_45: G = __r_arr[16]; let __v_46: G = __r_arr[17]; let __v_47: G = __r_arr[18]; let __v_48: G = __r_arr[19]; let __v_49: G = __r_arr[20]; let __v_50: G = __r_arr[21]; let __v_51: G = __r_arr[22]; let __v_52: G = __r_arr[23]; let __v_53: G = __r_arr[24]; let __r_arr: [G; OUT_235] = { let __args: [G; IN_235] = [__v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_24, __v_25, __v_26, __v_27, __v_14, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_16, __v_17, __v_18, __v_19, __v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(235, &__args[..])?) { [G::ZERO; OUT_235] } else { let (__hash, __hit) = record.function_queries[235].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[235].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[235].bump_multiplicity(__i); } let __ret: [G; OUT_235] = unsafe { *(record.function_queries[235].output_at(__i).as_ptr() as *const [G; OUT_235]) }; __ret }, _ => { aiur_fn_235::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __v_55: G = __r_arr[1]; let __v_56: G = G::from_u64(1); let __v_57: G = __v_14 + __v_56; let __v_58: G = __v_15 - __v_56; let __v_59: G = __v_21 + __v_54; let __v_60: G = __v_22 + __v_55; let __r_arr: [G; OUT_303] = { let __args: [G; IN_303] = [__v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_28, __v_53, __v_57, __v_58, __v_16, __v_17, __v_18, __v_19, __v_20, __v_59, __v_60]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(303, &__args[..])?) { [G::ZERO; OUT_303] } else { let (__hash, __hit) = record.function_queries[303].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[303].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[303].bump_multiplicity(__i); } let __ret: [G; OUT_303] = unsafe { *(record.function_queries[303].output_at(__i).as_ptr() as *const [G; OUT_303]) }; __ret }, _ => { aiur_fn_303::(__args, __hash, record, io_buffer)? }, } } }; let __v_61: G = __r_arr[0]; let __v_62: G = __r_arr[1]; let __v_63: G = __r_arr[2]; let __ret: [G; OUT_303] = [__v_61, __v_62, __v_63]; record.function_queries[303].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_23.as_canonical_u64())); }, } }, } }) } -const INPUT_SIZE_304: usize = 8; -const IN_304: usize = 8; -const OUT_304: usize = 19; -fn aiur_fn_304(inp: [G; IN_304], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_304], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = G::from_u64(2); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_8]; let __info = io_buffer.get_info(__v_8, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_9: G = __io_pair.0; let __v_10: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_8, __v_9, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_u64(103); let __v_13: G = G::from_u64(230); let __v_14: G = G::from_u64(9); let __v_15: G = G::from_u64(106); let __v_16: G = G::from_u64(133); let __v_17: G = G::from_u64(174); let __v_18: G = G::from_u64(187); let __v_19: G = G::from_u64(114); let __v_20: G = G::from_u64(243); let __v_21: G = G::from_u64(110); let __v_22: G = G::from_u64(60); let __v_23: G = G::from_u64(58); let __v_24: G = G::from_u64(245); let __v_25: G = G::from_u64(79); let __v_26: G = G::from_u64(165); let __v_27: G = G::from_u64(127); let __v_28: G = G::from_u64(82); let __v_29: G = G::from_u64(14); let __v_30: G = G::from_u64(81); let __v_31: G = G::from_u64(140); let __v_32: G = G::from_u64(104); let __v_33: G = G::from_u64(5); let __v_34: G = G::from_u64(155); let __v_35: G = G::from_u64(171); let __v_36: G = G::from_u64(217); let __v_37: G = G::from_u64(131); let __v_38: G = G::from_u64(31); let __v_39: G = G::from_u64(25); let __v_40: G = G::from_u64(205); let __v_41: G = G::from_u64(224); let __v_42: G = G::from_u64(91); let __v_43: G = G::from_u64(1); let __v_44: G = { let __values: [G; 3] = [__v_43, __v_43, __v_43]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_45: G = G::from_u64(0); let __v_46: G = { let __values: [G; 8] = [__v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_47: G = { let __values: [G; 32] = [__v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_12, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_48: G = { let __values: [G; 34] = [__v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_11, __v_44, __v_45, __v_45, __v_46, __v_47, __v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_49]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_50: G = __r_arr[0]; let __v_51: G = __r_arr[1]; let __v_52: G = __r_arr[2]; let __v_53: G = __r_arr[3]; let __v_54: G = __r_arr[4]; let __v_55: G = __r_arr[5]; let __v_56: G = __r_arr[6]; let __v_57: G = __r_arr[7]; let __v_58: G = __r_arr[8]; let __v_59: G = __r_arr[9]; let __v_60: G = __r_arr[10]; let __v_61: G = __r_arr[11]; let __v_62: G = __r_arr[12]; let __v_63: G = __r_arr[13]; let __v_64: G = __r_arr[14]; let __v_65: G = __r_arr[15]; let __v_66: G = __r_arr[16]; let __v_67: G = __r_arr[17]; let __v_68: G = __r_arr[18]; let __v_69: G = __r_arr[19]; let __v_70: G = __r_arr[20]; let __v_71: G = __r_arr[21]; let __v_72: G = __r_arr[22]; let __v_73: G = __r_arr[23]; let __v_74: G = __r_arr[24]; let __v_75: G = __r_arr[25]; let __v_76: G = __r_arr[26]; let __v_77: G = __r_arr[27]; let __v_78: G = __r_arr[28]; let __v_79: G = __r_arr[29]; let __v_80: G = __r_arr[30]; let __v_81: G = __r_arr[31]; let __v_82: G = G::from_u64(256); let __v_83: G = __v_82 * __v_51; let __v_84: G = __v_50 + __v_83; let __v_85: G = G::from_u64(65536); let __v_86: G = __v_85 * __v_52; let __v_87: G = __v_84 + __v_86; let __v_88: G = G::from_u64(16777216); let __v_89: G = __v_88 * __v_53; let __v_90: G = __v_87 + __v_89; let __v_91: G = __v_82 * __v_55; let __v_92: G = __v_54 + __v_91; let __v_93: G = __v_85 * __v_56; let __v_94: G = __v_92 + __v_93; let __v_95: G = __v_88 * __v_57; let __v_96: G = __v_94 + __v_95; let __v_97: G = __v_82 * __v_59; let __v_98: G = __v_58 + __v_97; let __v_99: G = __v_85 * __v_60; let __v_100: G = __v_98 + __v_99; let __v_101: G = __v_88 * __v_61; let __v_102: G = __v_100 + __v_101; let __v_103: G = __v_82 * __v_63; let __v_104: G = __v_62 + __v_103; let __v_105: G = __v_85 * __v_64; let __v_106: G = __v_104 + __v_105; let __v_107: G = __v_88 * __v_65; let __v_108: G = __v_106 + __v_107; let __v_109: G = __v_82 * __v_67; let __v_110: G = __v_66 + __v_109; let __v_111: G = __v_85 * __v_68; let __v_112: G = __v_110 + __v_111; let __v_113: G = __v_88 * __v_69; let __v_114: G = __v_112 + __v_113; let __v_115: G = __v_82 * __v_71; let __v_116: G = __v_70 + __v_115; let __v_117: G = __v_85 * __v_72; let __v_118: G = __v_116 + __v_117; let __v_119: G = __v_88 * __v_73; let __v_120: G = __v_118 + __v_119; let __v_121: G = __v_82 * __v_75; let __v_122: G = __v_74 + __v_121; let __v_123: G = __v_85 * __v_76; let __v_124: G = __v_122 + __v_123; let __v_125: G = __v_88 * __v_77; let __v_126: G = __v_124 + __v_125; let __v_127: G = __v_82 * __v_79; let __v_128: G = __v_78 + __v_127; let __v_129: G = __v_85 * __v_80; let __v_130: G = __v_128 + __v_129; let __v_131: G = __v_88 * __v_81; let __v_132: G = __v_130 + __v_131; if (__v_90 != __v_0) { return Err(ExecError::AssertEqMismatch { lhs: __v_90.as_canonical_u64(), rhs: __v_0.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } if (__v_96 != __v_1) { return Err(ExecError::AssertEqMismatch { lhs: __v_96.as_canonical_u64(), rhs: __v_1.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } if (__v_102 != __v_2) { return Err(ExecError::AssertEqMismatch { lhs: __v_102.as_canonical_u64(), rhs: __v_2.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } if (__v_108 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_108.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } if (__v_114 != __v_4) { return Err(ExecError::AssertEqMismatch { lhs: __v_114.as_canonical_u64(), rhs: __v_4.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } if (__v_120 != __v_5) { return Err(ExecError::AssertEqMismatch { lhs: __v_120.as_canonical_u64(), rhs: __v_5.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } if (__v_126 != __v_6) { return Err(ExecError::AssertEqMismatch { lhs: __v_126.as_canonical_u64(), rhs: __v_6.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } if (__v_132 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_132.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } let __r_arr: [G; OUT_293] = { let __args: [G; IN_293] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(293, &__args[..])?) { [G::ZERO; OUT_293] } else { let (__hash, __hit) = record.function_queries[293].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[293].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[293].bump_multiplicity(__i); } let __ret: [G; OUT_293] = unsafe { *(record.function_queries[293].output_at(__i).as_ptr() as *const [G; OUT_293]) }; __ret }, _ => { aiur_fn_293::(__args, __hash, record, io_buffer)? }, } } }; let __v_133: G = __r_arr[0]; let __v_134: G = __r_arr[1]; let __v_135: G = __r_arr[2]; let __v_136: G = __r_arr[3]; let __v_137: G = __r_arr[4]; let __v_138: G = __r_arr[5]; let __v_139: G = __r_arr[6]; let __v_140: G = __r_arr[7]; let __v_141: G = __r_arr[8]; let __v_142: G = __r_arr[9]; let __v_143: G = __r_arr[10]; let __v_144: G = __r_arr[11]; let __v_145: G = __r_arr[12]; let __v_146: G = __r_arr[13]; let __v_147: G = __r_arr[14]; let __v_148: G = __r_arr[15]; let __v_149: G = __r_arr[16]; let __v_150: G = __r_arr[17]; let __v_151: G = __r_arr[18]; let __ret: [G; OUT_304] = [__v_133, __v_134, __v_135, __v_136, __v_137, __v_138, __v_139, __v_140, __v_141, __v_142, __v_143, __v_144, __v_145, __v_146, __v_147, __v_148, __v_149, __v_150, __v_151]; record.function_queries[304].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_305: usize = 27; -const IN_305: usize = 27; -const OUT_305: usize = 19; -fn aiur_fn_305(inp: [G; IN_305], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_305], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = G::from_u64(1); let __r_arr: [G; OUT_280] = { let __args: [G; IN_280] = [__v_27, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(280, &__args[..])?) { [G::ZERO; OUT_280] } else { let (__hash, __hit) = record.function_queries[280].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[280].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[280].bump_multiplicity(__i); } let __ret: [G; OUT_280] = unsafe { *(record.function_queries[280].output_at(__i).as_ptr() as *const [G; OUT_280]) }; __ret }, _ => { aiur_fn_280::(__args, __hash, record, io_buffer)? }, } } }; let __v_28: G = __r_arr[0]; let __v_29: G = __r_arr[1]; let __v_30: G = __r_arr[2]; let __v_31: G = __r_arr[3]; let __v_32: G = __r_arr[4]; let __v_33: G = __r_arr[5]; let __v_34: G = __r_arr[6]; let __v_35: G = __r_arr[7]; let __r_arr: [G; OUT_273] = { let __args: [G; IN_273] = [__v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(273, &__args[..])?) { [G::ZERO; OUT_273] } else { let (__hash, __hit) = record.function_queries[273].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[273].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[273].bump_multiplicity(__i); } let __ret: [G; OUT_273] = unsafe { *(record.function_queries[273].output_at(__i).as_ptr() as *const [G; OUT_273]) }; __ret }, _ => { aiur_fn_273::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __r_arr: [G; OUT_293] = { let __args: [G; IN_293] = [__v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(293, &__args[..])?) { [G::ZERO; OUT_293] } else { let (__hash, __hit) = record.function_queries[293].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[293].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[293].bump_multiplicity(__i); } let __ret: [G; OUT_293] = unsafe { *(record.function_queries[293].output_at(__i).as_ptr() as *const [G; OUT_293]) }; __ret }, _ => { aiur_fn_293::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = __r_arr[1]; let __v_39: G = __r_arr[2]; let __v_40: G = __r_arr[3]; let __v_41: G = __r_arr[4]; let __v_42: G = __r_arr[5]; let __v_43: G = __r_arr[6]; let __v_44: G = __r_arr[7]; let __v_45: G = __r_arr[8]; let __v_46: G = __r_arr[9]; let __v_47: G = __r_arr[10]; let __v_48: G = __r_arr[11]; let __v_49: G = __r_arr[12]; let __v_50: G = __r_arr[13]; let __v_51: G = __r_arr[14]; let __v_52: G = __r_arr[15]; let __v_53: G = __r_arr[16]; let __v_54: G = __r_arr[17]; let __v_55: G = __r_arr[18]; let __ret: [G; OUT_305] = [__v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53, __v_54, __v_55]; record.function_queries[305].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_306: usize = 24; -const IN_306: usize = 24; -const OUT_306: usize = 0; -fn aiur_fn_306(inp: [G; IN_306], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_306], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = G::from_u64(0); let __r_arr: [G; OUT_278] = { let __args: [G; IN_278] = [__v_24, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(278, &__args[..])?) { [G::ZERO; OUT_278] } else { let (__hash, __hit) = record.function_queries[278].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[278].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[278].bump_multiplicity(__i); } let __ret: [G; OUT_278] = unsafe { *(record.function_queries[278].output_at(__i).as_ptr() as *const [G; OUT_278]) }; __ret }, _ => { aiur_fn_278::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __v_26: G = __r_arr[1]; let __v_27: G = __r_arr[2]; let __v_28: G = __r_arr[3]; let __v_29: G = __r_arr[4]; let __v_30: G = __r_arr[5]; let __v_31: G = __r_arr[6]; let __v_32: G = __r_arr[7]; let __v_33: G = __r_arr[8]; let __v_34: G = __r_arr[9]; let __v_35: G = __r_arr[10]; let __v_36: G = __r_arr[11]; let __r_arr: [G; OUT_304] = { let __args: [G; IN_304] = [__v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(304, &__args[..])?) { [G::ZERO; OUT_304] } else { let (__hash, __hit) = record.function_queries[304].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[304].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[304].bump_multiplicity(__i); } let __ret: [G; OUT_304] = unsafe { *(record.function_queries[304].output_at(__i).as_ptr() as *const [G; OUT_304]) }; __ret }, _ => { aiur_fn_304::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = __r_arr[1]; let __v_39: G = __r_arr[2]; let __v_40: G = __r_arr[3]; let __v_41: G = __r_arr[4]; let __v_42: G = __r_arr[5]; let __v_43: G = __r_arr[6]; let __v_44: G = __r_arr[7]; let __v_45: G = __r_arr[8]; let __v_46: G = __r_arr[9]; let __v_47: G = __r_arr[10]; let __v_48: G = __r_arr[11]; let __v_49: G = __r_arr[12]; let __v_50: G = __r_arr[13]; let __v_51: G = __r_arr[14]; let __v_52: G = __r_arr[15]; let __v_53: G = __r_arr[16]; let __v_54: G = __r_arr[17]; let __v_55: G = __r_arr[18]; let __r_arr: [G; OUT_301] = { let __args: [G; IN_301] = [__v_24, __v_37]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(301, &__args[..])?) { [G::ZERO; OUT_301] } else { let (__hash, __hit) = record.function_queries[301].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[301].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[301].bump_multiplicity(__i); } let __ret: [G; OUT_301] = unsafe { *(record.function_queries[301].output_at(__i).as_ptr() as *const [G; OUT_301]) }; __ret }, _ => { aiur_fn_301::(__args, __hash, record, io_buffer)? }, } } }; let __v_56: G = __r_arr[0]; let __v_57: G = __r_arr[1]; let __r_arr: [G; OUT_302] = { let __args: [G; IN_302] = [__v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_56, __v_57]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(302, &__args[..])?) { [G::ZERO; OUT_302] } else { let (__hash, __hit) = record.function_queries[302].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[302].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[302].bump_multiplicity(__i); } let __ret: [G; OUT_302] = unsafe { *(record.function_queries[302].output_at(__i).as_ptr() as *const [G; OUT_302]) }; __ret }, _ => { aiur_fn_302::(__args, __hash, record, io_buffer)? }, } } }; let __v_58: G = __r_arr[0]; let __v_59: G = __r_arr[1]; let __v_60: G = __r_arr[2]; let __v_61: G = __r_arr[3]; let __v_62: G = __r_arr[4]; let __r_arr: [G; OUT_233] = { let __args: [G; IN_233] = [__v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(233, &__args[..])?) { [G::ZERO; OUT_233] } else { let (__hash, __hit) = record.function_queries[233].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[233].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[233].bump_multiplicity(__i); } let __ret: [G; OUT_233] = unsafe { *(record.function_queries[233].output_at(__i).as_ptr() as *const [G; OUT_233]) }; __ret }, _ => { aiur_fn_233::(__args, __hash, record, io_buffer)? }, } } }; let __v_63: G = __r_arr[0]; let __v_64: G = G::from_u64(1); if (__v_63 != __v_64) { return Err(ExecError::AssertEqMismatch { lhs: __v_63.as_canonical_u64(), rhs: __v_64.as_canonical_u64(), msg: Some("aggr range: the range ends before it starts".to_string()) }); } let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_65: G = __r_arr[0]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_66: G = __r_arr[0]; let __v_67: G = __v_66 - __v_65; let __io_pair: (G, G) = { let __key: [G; 1] = [__v_64]; let __info = io_buffer.get_info(__v_24, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_68: G = __io_pair.0; let __v_69: G = __io_pair.1; let __r_arr: [G; OUT_31] = { let __args: [G; IN_31] = [__v_68]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(31, &__args[..])?) { [G::ZERO; OUT_31] } else { let (__hash, __hit) = record.function_queries[31].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[31].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[31].bump_multiplicity(__i); } let __ret: [G; OUT_31] = unsafe { *(record.function_queries[31].output_at(__i).as_ptr() as *const [G; OUT_31]) }; __ret }, _ => { aiur_fn_31::(__args, __hash, record, io_buffer)? }, } } }; let __v_70: G = __r_arr[0]; let __v_71: G = __r_arr[1]; if (__v_70 != __v_67) { return Err(ExecError::AssertEqMismatch { lhs: __v_70.as_canonical_u64(), rhs: __v_67.as_canonical_u64(), msg: Some("aggr range: proof count differs from the range".to_string()) }); } let __r_arr: [G; OUT_325] = { let __args: [G; IN_325] = [__v_56, __v_65]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(325, &__args[..])?) { [G::ZERO; OUT_325] } else { let (__hash, __hit) = record.function_queries[325].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[325].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[325].bump_multiplicity(__i); } let __ret: [G; OUT_325] = unsafe { *(record.function_queries[325].output_at(__i).as_ptr() as *const [G; OUT_325]) }; __ret }, _ => { aiur_fn_325::(__args, __hash, record, io_buffer)? }, } } }; let __v_72: G = __r_arr[0]; let __r_arr: [G; OUT_303] = { let __args: [G; IN_303] = [__v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_72, __v_71, __v_65, __v_67, __v_62, __v_58, __v_59, __v_60, __v_61, __v_24, __v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(303, &__args[..])?) { [G::ZERO; OUT_303] } else { let (__hash, __hit) = record.function_queries[303].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[303].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[303].bump_multiplicity(__i); } let __ret: [G; OUT_303] = unsafe { *(record.function_queries[303].output_at(__i).as_ptr() as *const [G; OUT_303]) }; __ret }, _ => { aiur_fn_303::(__args, __hash, record, io_buffer)? }, } } }; let __v_73: G = __r_arr[0]; let __v_74: G = __r_arr[1]; let __v_75: G = __r_arr[2]; let __v_76: G = __v_68 + __v_69; if (__v_75 != __v_76) { return Err(ExecError::AssertEqMismatch { lhs: __v_75.as_canonical_u64(), rhs: __v_76.as_canonical_u64(), msg: Some("aggr range: trailing bytes after the proofs".to_string()) }); } let __v_77: G = __v_73 - __v_54; let __v_78: G = G::from_bool(__v_77 == G::ZERO); let __v_79: G = __v_74 - __v_55; let __v_80: G = G::from_bool(__v_79 == G::ZERO); let __v_81: G = __v_78 * __v_80; if (__v_81 != __v_64) { return Err(ExecError::AssertEqMismatch { lhs: __v_81.as_canonical_u64(), rhs: __v_64.as_canonical_u64(), msg: Some("aggr range: residual sum differs from the statement".to_string()) }); } let __ret: [G; OUT_306] = []; record.function_queries[306].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_307: usize = 34; -const IN_307: usize = 34; -const OUT_307: usize = 0; -fn aiur_fn_307(inp: [G; IN_307], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_307], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = G::from_u64(0); let __r_arr: [G; OUT_305] = { let __args: [G; IN_305] = [__v_34, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(305, &__args[..])?) { [G::ZERO; OUT_305] } else { let (__hash, __hit) = record.function_queries[305].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[305].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[305].bump_multiplicity(__i); } let __ret: [G; OUT_305] = unsafe { *(record.function_queries[305].output_at(__i).as_ptr() as *const [G; OUT_305]) }; __ret }, _ => { aiur_fn_305::(__args, __hash, record, io_buffer)? }, } } }; let __v_35: G = __r_arr[0]; let __v_36: G = __r_arr[1]; let __v_37: G = __r_arr[2]; let __v_38: G = __r_arr[3]; let __v_39: G = __r_arr[4]; let __v_40: G = __r_arr[5]; let __v_41: G = __r_arr[6]; let __v_42: G = __r_arr[7]; let __v_43: G = __r_arr[8]; let __v_44: G = __r_arr[9]; let __v_45: G = __r_arr[10]; let __v_46: G = __r_arr[11]; let __v_47: G = __r_arr[12]; let __v_48: G = __r_arr[13]; let __v_49: G = __r_arr[14]; let __v_50: G = __r_arr[15]; let __v_51: G = __r_arr[16]; let __v_52: G = __r_arr[17]; let __v_53: G = __r_arr[18]; let __v_54: G = G::from_u64(1); let __r_arr: [G; OUT_305] = { let __args: [G; IN_305] = [__v_54, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(305, &__args[..])?) { [G::ZERO; OUT_305] } else { let (__hash, __hit) = record.function_queries[305].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[305].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[305].bump_multiplicity(__i); } let __ret: [G; OUT_305] = unsafe { *(record.function_queries[305].output_at(__i).as_ptr() as *const [G; OUT_305]) }; __ret }, _ => { aiur_fn_305::(__args, __hash, record, io_buffer)? }, } } }; let __v_55: G = __r_arr[0]; let __v_56: G = __r_arr[1]; let __v_57: G = __r_arr[2]; let __v_58: G = __r_arr[3]; let __v_59: G = __r_arr[4]; let __v_60: G = __r_arr[5]; let __v_61: G = __r_arr[6]; let __v_62: G = __r_arr[7]; let __v_63: G = __r_arr[8]; let __v_64: G = __r_arr[9]; let __v_65: G = __r_arr[10]; let __v_66: G = __r_arr[11]; let __v_67: G = __r_arr[12]; let __v_68: G = __r_arr[13]; let __v_69: G = __r_arr[14]; let __v_70: G = __r_arr[15]; let __v_71: G = __r_arr[16]; let __v_72: G = __r_arr[17]; let __v_73: G = __r_arr[18]; let __r_arr: [G; OUT_304] = { let __args: [G; IN_304] = [__v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(304, &__args[..])?) { [G::ZERO; OUT_304] } else { let (__hash, __hit) = record.function_queries[304].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[304].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[304].bump_multiplicity(__i); } let __ret: [G; OUT_304] = unsafe { *(record.function_queries[304].output_at(__i).as_ptr() as *const [G; OUT_304]) }; __ret }, _ => { aiur_fn_304::(__args, __hash, record, io_buffer)? }, } } }; let __v_74: G = __r_arr[0]; let __v_75: G = __r_arr[1]; let __v_76: G = __r_arr[2]; let __v_77: G = __r_arr[3]; let __v_78: G = __r_arr[4]; let __v_79: G = __r_arr[5]; let __v_80: G = __r_arr[6]; let __v_81: G = __r_arr[7]; let __v_82: G = __r_arr[8]; let __v_83: G = __r_arr[9]; let __v_84: G = __r_arr[10]; let __v_85: G = __r_arr[11]; let __v_86: G = __r_arr[12]; let __v_87: G = __r_arr[13]; let __v_88: G = __r_arr[14]; let __v_89: G = __r_arr[15]; let __v_90: G = __r_arr[16]; let __v_91: G = __r_arr[17]; let __v_92: G = __r_arr[18]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_35, __v_55]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_93: G = __r_arr[0]; if (__v_93 != __v_54) { return Err(ExecError::AssertEqMismatch { lhs: __v_93.as_canonical_u64(), rhs: __v_54.as_canonical_u64(), msg: Some("aggr range: children verify different batches".to_string()) }); } let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_74, __v_35]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_94: G = __r_arr[0]; if (__v_94 != __v_54) { return Err(ExecError::AssertEqMismatch { lhs: __v_94.as_canonical_u64(), rhs: __v_54.as_canonical_u64(), msg: Some("aggr range: output names a different batch than the children".to_string()) }); } let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_95: G = __r_arr[0]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_56, __v_57, __v_58, __v_59, __v_60, __v_61, __v_62, __v_63]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_96: G = __r_arr[0]; if (__v_95 != __v_96) { return Err(ExecError::AssertEqMismatch { lhs: __v_95.as_canonical_u64(), rhs: __v_96.as_canonical_u64(), msg: Some("aggr range: children are not adjacent".to_string()) }); } let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_75, __v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_82]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_97: G = __r_arr[0]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_98: G = __r_arr[0]; if (__v_97 != __v_98) { return Err(ExecError::AssertEqMismatch { lhs: __v_97.as_canonical_u64(), rhs: __v_98.as_canonical_u64(), msg: Some("aggr range: output starts elsewhere than the left child".to_string()) }); } let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_83, __v_84, __v_85, __v_86, __v_87, __v_88, __v_89, __v_90]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_99: G = __r_arr[0]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_64, __v_65, __v_66, __v_67, __v_68, __v_69, __v_70, __v_71]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_100: G = __r_arr[0]; if (__v_99 != __v_100) { return Err(ExecError::AssertEqMismatch { lhs: __v_99.as_canonical_u64(), rhs: __v_100.as_canonical_u64(), msg: Some("aggr range: output ends elsewhere than the right child".to_string()) }); } let __v_101: G = __v_52 + __v_72; let __v_102: G = __v_53 + __v_73; let __v_103: G = __v_101 - __v_91; let __v_104: G = G::from_bool(__v_103 == G::ZERO); let __v_105: G = __v_102 - __v_92; let __v_106: G = G::from_bool(__v_105 == G::ZERO); let __v_107: G = __v_104 * __v_106; if (__v_107 != __v_54) { return Err(ExecError::AssertEqMismatch { lhs: __v_107.as_canonical_u64(), rhs: __v_54.as_canonical_u64(), msg: Some("aggr range: output residual is not the children's sum".to_string()) }); } let __ret: [G; OUT_307] = []; record.function_queries[307].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_308: usize = 34; -const IN_308: usize = 34; +fn aiur_fn_300(inp: [G; IN_300], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_300], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __v_2: G = G::from_u64(1); let __v_3: G = { let __values: [G; 6] = [__v_2, __v_2, __v_2, __v_2, __v_2, __v_2]; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 6)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_300] = [__v_3, __v_0]; record.function_queries[300].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_299] = { let __args: [G; IN_299] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(299, &__args[..])?) { [G::ZERO; OUT_299] } else { let (__hash, __hit) = record.function_queries[299].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[299].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[299].bump_multiplicity(__i); } let __ret: [G; OUT_299] = unsafe { *(record.function_queries[299].output_at(__i).as_ptr() as *const [G; OUT_299]) }; __ret }, _ => { aiur_fn_299::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __v_3: G = __r_arr[1]; let __v_4: G = __r_arr[2]; let __v_5: G = __r_arr[3]; let __v_6: G = __r_arr[4]; let __v_7: G = G::from_u64(1); let __v_8: G = __v_1 - __v_7; let __r_arr: [G; OUT_300] = { let __args: [G; IN_300] = [__v_6, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(300, &__args[..])?) { [G::ZERO; OUT_300] } else { let (__hash, __hit) = record.function_queries[300].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[300].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[300].bump_multiplicity(__i); } let __ret: [G; OUT_300] = unsafe { *(record.function_queries[300].output_at(__i).as_ptr() as *const [G; OUT_300]) }; __ret }, _ => { aiur_fn_300::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = __r_arr[1]; let __v_11: G = G::from_u64(0); let __v_12: G = { let __values: [G; 6] = [__v_11, __v_2, __v_3, __v_4, __v_5, __v_9]; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 6)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_300] = [__v_12, __v_10]; record.function_queries[300].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } +const INPUT_SIZE_301: usize = 1; +const IN_301: usize = 1; +const OUT_301: usize = 10; +fn aiur_fn_301(inp: [G; IN_301], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_301], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __r_arr: [G; OUT_28] = { let __args: [G; IN_28] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(28, &__args[..])?) { [G::ZERO; OUT_28] } else { let (__hash, __hit) = record.function_queries[28].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[28].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[28].bump_multiplicity(__i); } let __ret: [G; OUT_28] = unsafe { *(record.function_queries[28].output_at(__i).as_ptr() as *const [G; OUT_28]) }; __ret }, _ => { aiur_fn_28::(__args, __hash, record, io_buffer)? }, } } }; let __v_1: G = __r_arr[0]; let __v_2: G = __r_arr[1]; let __r_arr: [G; OUT_243] = { let __args: [G; IN_243] = [__v_2, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(243, &__args[..])?) { [G::ZERO; OUT_243] } else { let (__hash, __hit) = record.function_queries[243].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[243].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[243].bump_multiplicity(__i); } let __ret: [G; OUT_243] = unsafe { *(record.function_queries[243].output_at(__i).as_ptr() as *const [G; OUT_243]) }; __ret }, _ => { aiur_fn_243::(__args, __hash, record, io_buffer)? }, } } }; let __v_3: G = __r_arr[0]; let __v_4: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_12]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __ret: [G; OUT_301] = [__v_3, __v_5, __v_7, __v_9, __v_11, __v_13, __v_15, __v_17, __v_19, __v_20]; record.function_queries[301].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_302: usize = 2; +const IN_302: usize = 2; +const OUT_302: usize = 2; +fn aiur_fn_302(inp: [G; IN_302], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_302], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __v_2: G = G::from_u64(1); let __v_3: G = { let __values: [G; 11] = [__v_2, __v_2, __v_2, __v_2, __v_2, __v_2, __v_2, __v_2, __v_2, __v_2, __v_2]; let __mq = record.memory_queries.get_index_mut(7).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(11))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 11)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_302] = [__v_3, __v_0]; record.function_queries[302].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __r_arr: [G; OUT_301] = { let __args: [G; IN_301] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(301, &__args[..])?) { [G::ZERO; OUT_301] } else { let (__hash, __hit) = record.function_queries[301].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[301].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[301].bump_multiplicity(__i); } let __ret: [G; OUT_301] = unsafe { *(record.function_queries[301].output_at(__i).as_ptr() as *const [G; OUT_301]) }; __ret }, _ => { aiur_fn_301::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __v_3: G = __r_arr[1]; let __v_4: G = __r_arr[2]; let __v_5: G = __r_arr[3]; let __v_6: G = __r_arr[4]; let __v_7: G = __r_arr[5]; let __v_8: G = __r_arr[6]; let __v_9: G = __r_arr[7]; let __v_10: G = __r_arr[8]; let __v_11: G = __r_arr[9]; let __v_12: G = G::from_u64(1); let __v_13: G = __v_1 - __v_12; let __r_arr: [G; OUT_302] = { let __args: [G; IN_302] = [__v_11, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(302, &__args[..])?) { [G::ZERO; OUT_302] } else { let (__hash, __hit) = record.function_queries[302].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[302].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[302].bump_multiplicity(__i); } let __ret: [G; OUT_302] = unsafe { *(record.function_queries[302].output_at(__i).as_ptr() as *const [G; OUT_302]) }; __ret }, _ => { aiur_fn_302::(__args, __hash, record, io_buffer)? }, } } }; let __v_14: G = __r_arr[0]; let __v_15: G = __r_arr[1]; let __v_16: G = G::from_u64(0); let __v_17: G = { let __values: [G; 11] = [__v_16, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_14]; let __mq = record.memory_queries.get_index_mut(7).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(11))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 11)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_302] = [__v_17, __v_15]; record.function_queries[302].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } +const INPUT_SIZE_303: usize = 2; +const IN_303: usize = 2; +const OUT_303: usize = 2; +fn aiur_fn_303(inp: [G; IN_303], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_303], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = G::from_u64(0); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_0]; let __info = io_buffer.get_info(__v_2, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_3: G = __io_pair.0; let __v_4: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_2, __v_3, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __r_arr: [G; OUT_75] = { let __args: [G; IN_75] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(75, &__args[..])?) { [G::ZERO; OUT_75] } else { let (__hash, __hit) = record.function_queries[75].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[75].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[75].bump_multiplicity(__i); } let __ret: [G; OUT_75] = unsafe { *(record.function_queries[75].output_at(__i).as_ptr() as *const [G; OUT_75]) }; __ret }, _ => { aiur_fn_75::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_6, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = G::from_u64(1); if (__v_7 != __v_8) { return Err(ExecError::AssertEqMismatch { lhs: __v_7.as_canonical_u64(), rhs: __v_8.as_canonical_u64(), msg: Some("aggr range: preamble does not hash to the statement's batch digest".to_string()) }); } let __r_arr: [G; OUT_27] = { let __args: [G; IN_27] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(27, &__args[..])?) { [G::ZERO; OUT_27] } else { let (__hash, __hit) = record.function_queries[27].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[27].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[27].bump_multiplicity(__i); } let __ret: [G; OUT_27] = unsafe { *(record.function_queries[27].output_at(__i).as_ptr() as *const [G; OUT_27]) }; __ret }, _ => { aiur_fn_27::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = __r_arr[1]; let __v_11: G = __r_arr[2]; let __v_12: G = __r_arr[3]; let __v_13: G = __r_arr[4]; let __v_14: G = __r_arr[5]; let __v_15: G = __r_arr[6]; let __v_16: G = __r_arr[7]; let __v_17: G = __r_arr[8]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_18: G = __r_arr[0]; let __r_arr: [G; OUT_300] = { let __args: [G; IN_300] = [__v_17, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(300, &__args[..])?) { [G::ZERO; OUT_300] } else { let (__hash, __hit) = record.function_queries[300].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[300].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[300].bump_multiplicity(__i); } let __ret: [G; OUT_300] = unsafe { *(record.function_queries[300].output_at(__i).as_ptr() as *const [G; OUT_300]) }; __ret }, _ => { aiur_fn_300::(__args, __hash, record, io_buffer)? }, } } }; let __v_19: G = __r_arr[0]; let __v_20: G = __r_arr[1]; let __r_arr: [G; OUT_27] = { let __args: [G; IN_27] = [__v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(27, &__args[..])?) { [G::ZERO; OUT_27] } else { let (__hash, __hit) = record.function_queries[27].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[27].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[27].bump_multiplicity(__i); } let __ret: [G; OUT_27] = unsafe { *(record.function_queries[27].output_at(__i).as_ptr() as *const [G; OUT_27]) }; __ret }, _ => { aiur_fn_27::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = __r_arr[1]; let __v_23: G = __r_arr[2]; let __v_24: G = __r_arr[3]; let __v_25: G = __r_arr[4]; let __v_26: G = __r_arr[5]; let __v_27: G = __r_arr[6]; let __v_28: G = __r_arr[7]; let __v_29: G = __r_arr[8]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_30: G = __r_arr[0]; let __r_arr: [G; OUT_302] = { let __args: [G; IN_302] = [__v_29, __v_30]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(302, &__args[..])?) { [G::ZERO; OUT_302] } else { let (__hash, __hit) = record.function_queries[302].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[302].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[302].bump_multiplicity(__i); } let __ret: [G; OUT_302] = unsafe { *(record.function_queries[302].output_at(__i).as_ptr() as *const [G; OUT_302]) }; __ret }, _ => { aiur_fn_302::(__args, __hash, record, io_buffer)? }, } } }; let __v_31: G = __r_arr[0]; let __v_32: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_32.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_33: G = __loaded[0]; let __v_34: G = __loaded[1]; let __v_35: G = __loaded[2]; if (__v_33 != __v_8) { return Err(ExecError::AssertEqMismatch { lhs: __v_33.as_canonical_u64(), rhs: __v_8.as_canonical_u64(), msg: Some("aggr range: trailing bytes after the preamble".to_string()) }); } if (__v_34 != __v_8) { return Err(ExecError::AssertEqMismatch { lhs: __v_34.as_canonical_u64(), rhs: __v_8.as_canonical_u64(), msg: Some("aggr range: trailing bytes after the preamble".to_string()) }); } if (__v_35 != __v_8) { return Err(ExecError::AssertEqMismatch { lhs: __v_35.as_canonical_u64(), rhs: __v_8.as_canonical_u64(), msg: Some("aggr range: trailing bytes after the preamble".to_string()) }); } let __ret: [G; OUT_303] = [__v_19, __v_31]; record.function_queries[303].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_304: usize = 14; +const IN_304: usize = 14; +const OUT_304: usize = 5; +fn aiur_fn_304(inp: [G; IN_304], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_304], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; match __v_0.as_canonical_u64() { _ => { let __mc_out___mc_0: [G; 1] = '__mc_0: { match __v_9.as_canonical_u64() { 0u64 => { let __v_14: G = G::from_u64(1); let __v_15: G = { let __values: [G; 3] = [__v_14, __v_14, __v_14]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; break '__mc_0 [__v_15]; }, 1u64 => { break '__mc_0 [__v_10]; }, _ => { return Err(ExecError::MatchNoCase(__v_9.as_canonical_u64())); }, } }; let __v_14: G = __mc_out___mc_0[0]; let __r_arr: [G; OUT_219] = { let __args: [G; IN_219] = [__v_7, __v_14, __v_12, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(219, &__args[..])?) { [G::ZERO; OUT_219] } else { let (__hash, __hit) = record.function_queries[219].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[219].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[219].bump_multiplicity(__i); } let __ret: [G; OUT_219] = unsafe { *(record.function_queries[219].output_at(__i).as_ptr() as *const [G; OUT_219]) }; __ret }, _ => { aiur_fn_219::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __v_16: G = __r_arr[1]; let __v_17: G = __r_arr[2]; let __v_18: G = __r_arr[3]; let __v_19: G = __r_arr[4]; let __ret: [G; OUT_304] = [__v_15, __v_16, __v_17, __v_18, __v_19]; record.function_queries[304].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }) } +const INPUT_SIZE_305: usize = 23; +const IN_305: usize = 23; +const OUT_305: usize = 3; +fn aiur_fn_305(inp: [G; IN_305], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_305], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; match __v_15.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_305] = [__v_21, __v_22, __v_13]; record.function_queries[305].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __loaded: [G; 6] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let __ptr_u64 = __v_12.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 6 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 6] = __args[..6].try_into().unwrap(); __arr }; let __v_23: G = __loaded[0]; let __v_24: G = __loaded[1]; let __v_25: G = __loaded[2]; let __v_26: G = __loaded[3]; let __v_27: G = __loaded[4]; let __v_28: G = __loaded[5]; match __v_23.as_canonical_u64() { 0u64 => { let __r_arr: [G; OUT_62] = { let __args: [G; IN_62] = [__v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(62, &__args[..])?) { [G::ZERO; OUT_62] } else { let (__hash, __hit) = record.function_queries[62].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[62].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[62].bump_multiplicity(__i); } let __ret: [G; OUT_62] = unsafe { *(record.function_queries[62].output_at(__i).as_ptr() as *const [G; OUT_62]) }; __ret }, _ => { aiur_fn_62::(__args, __hash, record, io_buffer)? }, } } }; let __v_29: G = __r_arr[0]; let __v_30: G = __r_arr[1]; let __v_31: G = __r_arr[2]; let __v_32: G = __r_arr[3]; let __v_33: G = __r_arr[4]; let __v_34: G = __r_arr[5]; let __v_35: G = __r_arr[6]; let __v_36: G = __r_arr[7]; let __v_37: G = __r_arr[8]; let __v_38: G = __r_arr[9]; let __v_39: G = __r_arr[10]; let __v_40: G = __r_arr[11]; let __v_41: G = __r_arr[12]; let __v_42: G = __r_arr[13]; let __v_43: G = __r_arr[14]; let __v_44: G = __r_arr[15]; let __v_45: G = __r_arr[16]; let __v_46: G = __r_arr[17]; let __v_47: G = __r_arr[18]; let __v_48: G = __r_arr[19]; let __v_49: G = __r_arr[20]; let __v_50: G = __r_arr[21]; let __v_51: G = __r_arr[22]; let __v_52: G = __r_arr[23]; let __v_53: G = __r_arr[24]; let __r_arr: [G; OUT_235] = { let __args: [G; IN_235] = [__v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_24, __v_25, __v_26, __v_27, __v_14, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_16, __v_17, __v_18, __v_19, __v_20]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(235, &__args[..])?) { [G::ZERO; OUT_235] } else { let (__hash, __hit) = record.function_queries[235].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[235].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[235].bump_multiplicity(__i); } let __ret: [G; OUT_235] = unsafe { *(record.function_queries[235].output_at(__i).as_ptr() as *const [G; OUT_235]) }; __ret }, _ => { aiur_fn_235::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __v_55: G = __r_arr[1]; let __v_56: G = G::from_u64(1); let __v_57: G = __v_14 + __v_56; let __v_58: G = __v_15 - __v_56; let __v_59: G = __v_21 + __v_54; let __v_60: G = __v_22 + __v_55; let __r_arr: [G; OUT_305] = { let __args: [G; IN_305] = [__v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_28, __v_53, __v_57, __v_58, __v_16, __v_17, __v_18, __v_19, __v_20, __v_59, __v_60]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(305, &__args[..])?) { [G::ZERO; OUT_305] } else { let (__hash, __hit) = record.function_queries[305].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[305].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[305].bump_multiplicity(__i); } let __ret: [G; OUT_305] = unsafe { *(record.function_queries[305].output_at(__i).as_ptr() as *const [G; OUT_305]) }; __ret }, _ => { aiur_fn_305::(__args, __hash, record, io_buffer)? }, } } }; let __v_61: G = __r_arr[0]; let __v_62: G = __r_arr[1]; let __v_63: G = __r_arr[2]; let __ret: [G; OUT_305] = [__v_61, __v_62, __v_63]; record.function_queries[305].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_23.as_canonical_u64())); }, } }, } }) } +const INPUT_SIZE_306: usize = 8; +const IN_306: usize = 8; +const OUT_306: usize = 19; +fn aiur_fn_306(inp: [G; IN_306], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_306], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = G::from_u64(2); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_8]; let __info = io_buffer.get_info(__v_8, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_9: G = __io_pair.0; let __v_10: G = __io_pair.1; let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_8, __v_9, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_u64(103); let __v_13: G = G::from_u64(230); let __v_14: G = G::from_u64(9); let __v_15: G = G::from_u64(106); let __v_16: G = G::from_u64(133); let __v_17: G = G::from_u64(174); let __v_18: G = G::from_u64(187); let __v_19: G = G::from_u64(114); let __v_20: G = G::from_u64(243); let __v_21: G = G::from_u64(110); let __v_22: G = G::from_u64(60); let __v_23: G = G::from_u64(58); let __v_24: G = G::from_u64(245); let __v_25: G = G::from_u64(79); let __v_26: G = G::from_u64(165); let __v_27: G = G::from_u64(127); let __v_28: G = G::from_u64(82); let __v_29: G = G::from_u64(14); let __v_30: G = G::from_u64(81); let __v_31: G = G::from_u64(140); let __v_32: G = G::from_u64(104); let __v_33: G = G::from_u64(5); let __v_34: G = G::from_u64(155); let __v_35: G = G::from_u64(171); let __v_36: G = G::from_u64(217); let __v_37: G = G::from_u64(131); let __v_38: G = G::from_u64(31); let __v_39: G = G::from_u64(25); let __v_40: G = G::from_u64(205); let __v_41: G = G::from_u64(224); let __v_42: G = G::from_u64(91); let __v_43: G = G::from_u64(1); let __v_44: G = { let __values: [G; 3] = [__v_43, __v_43, __v_43]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_45: G = G::from_u64(0); let __v_46: G = { let __values: [G; 8] = [__v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45, __v_45]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_47: G = { let __values: [G; 32] = [__v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_12, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_48: G = { let __values: [G; 34] = [__v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_11, __v_44, __v_45, __v_45, __v_46, __v_47, __v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_49]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_50: G = __r_arr[0]; let __v_51: G = __r_arr[1]; let __v_52: G = __r_arr[2]; let __v_53: G = __r_arr[3]; let __v_54: G = __r_arr[4]; let __v_55: G = __r_arr[5]; let __v_56: G = __r_arr[6]; let __v_57: G = __r_arr[7]; let __v_58: G = __r_arr[8]; let __v_59: G = __r_arr[9]; let __v_60: G = __r_arr[10]; let __v_61: G = __r_arr[11]; let __v_62: G = __r_arr[12]; let __v_63: G = __r_arr[13]; let __v_64: G = __r_arr[14]; let __v_65: G = __r_arr[15]; let __v_66: G = __r_arr[16]; let __v_67: G = __r_arr[17]; let __v_68: G = __r_arr[18]; let __v_69: G = __r_arr[19]; let __v_70: G = __r_arr[20]; let __v_71: G = __r_arr[21]; let __v_72: G = __r_arr[22]; let __v_73: G = __r_arr[23]; let __v_74: G = __r_arr[24]; let __v_75: G = __r_arr[25]; let __v_76: G = __r_arr[26]; let __v_77: G = __r_arr[27]; let __v_78: G = __r_arr[28]; let __v_79: G = __r_arr[29]; let __v_80: G = __r_arr[30]; let __v_81: G = __r_arr[31]; let __v_82: G = G::from_u64(256); let __v_83: G = __v_82 * __v_51; let __v_84: G = __v_50 + __v_83; let __v_85: G = G::from_u64(65536); let __v_86: G = __v_85 * __v_52; let __v_87: G = __v_84 + __v_86; let __v_88: G = G::from_u64(16777216); let __v_89: G = __v_88 * __v_53; let __v_90: G = __v_87 + __v_89; let __v_91: G = __v_82 * __v_55; let __v_92: G = __v_54 + __v_91; let __v_93: G = __v_85 * __v_56; let __v_94: G = __v_92 + __v_93; let __v_95: G = __v_88 * __v_57; let __v_96: G = __v_94 + __v_95; let __v_97: G = __v_82 * __v_59; let __v_98: G = __v_58 + __v_97; let __v_99: G = __v_85 * __v_60; let __v_100: G = __v_98 + __v_99; let __v_101: G = __v_88 * __v_61; let __v_102: G = __v_100 + __v_101; let __v_103: G = __v_82 * __v_63; let __v_104: G = __v_62 + __v_103; let __v_105: G = __v_85 * __v_64; let __v_106: G = __v_104 + __v_105; let __v_107: G = __v_88 * __v_65; let __v_108: G = __v_106 + __v_107; let __v_109: G = __v_82 * __v_67; let __v_110: G = __v_66 + __v_109; let __v_111: G = __v_85 * __v_68; let __v_112: G = __v_110 + __v_111; let __v_113: G = __v_88 * __v_69; let __v_114: G = __v_112 + __v_113; let __v_115: G = __v_82 * __v_71; let __v_116: G = __v_70 + __v_115; let __v_117: G = __v_85 * __v_72; let __v_118: G = __v_116 + __v_117; let __v_119: G = __v_88 * __v_73; let __v_120: G = __v_118 + __v_119; let __v_121: G = __v_82 * __v_75; let __v_122: G = __v_74 + __v_121; let __v_123: G = __v_85 * __v_76; let __v_124: G = __v_122 + __v_123; let __v_125: G = __v_88 * __v_77; let __v_126: G = __v_124 + __v_125; let __v_127: G = __v_82 * __v_79; let __v_128: G = __v_78 + __v_127; let __v_129: G = __v_85 * __v_80; let __v_130: G = __v_128 + __v_129; let __v_131: G = __v_88 * __v_81; let __v_132: G = __v_130 + __v_131; if (__v_90 != __v_0) { return Err(ExecError::AssertEqMismatch { lhs: __v_90.as_canonical_u64(), rhs: __v_0.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } if (__v_96 != __v_1) { return Err(ExecError::AssertEqMismatch { lhs: __v_96.as_canonical_u64(), rhs: __v_1.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } if (__v_102 != __v_2) { return Err(ExecError::AssertEqMismatch { lhs: __v_102.as_canonical_u64(), rhs: __v_2.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } if (__v_108 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_108.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } if (__v_114 != __v_4) { return Err(ExecError::AssertEqMismatch { lhs: __v_114.as_canonical_u64(), rhs: __v_4.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } if (__v_120 != __v_5) { return Err(ExecError::AssertEqMismatch { lhs: __v_120.as_canonical_u64(), rhs: __v_5.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } if (__v_126 != __v_6) { return Err(ExecError::AssertEqMismatch { lhs: __v_126.as_canonical_u64(), rhs: __v_6.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } if (__v_132 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_132.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("aggr range: output statement digest mismatch".to_string()) }); } let __r_arr: [G; OUT_295] = { let __args: [G; IN_295] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(295, &__args[..])?) { [G::ZERO; OUT_295] } else { let (__hash, __hit) = record.function_queries[295].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[295].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[295].bump_multiplicity(__i); } let __ret: [G; OUT_295] = unsafe { *(record.function_queries[295].output_at(__i).as_ptr() as *const [G; OUT_295]) }; __ret }, _ => { aiur_fn_295::(__args, __hash, record, io_buffer)? }, } } }; let __v_133: G = __r_arr[0]; let __v_134: G = __r_arr[1]; let __v_135: G = __r_arr[2]; let __v_136: G = __r_arr[3]; let __v_137: G = __r_arr[4]; let __v_138: G = __r_arr[5]; let __v_139: G = __r_arr[6]; let __v_140: G = __r_arr[7]; let __v_141: G = __r_arr[8]; let __v_142: G = __r_arr[9]; let __v_143: G = __r_arr[10]; let __v_144: G = __r_arr[11]; let __v_145: G = __r_arr[12]; let __v_146: G = __r_arr[13]; let __v_147: G = __r_arr[14]; let __v_148: G = __r_arr[15]; let __v_149: G = __r_arr[16]; let __v_150: G = __r_arr[17]; let __v_151: G = __r_arr[18]; let __ret: [G; OUT_306] = [__v_133, __v_134, __v_135, __v_136, __v_137, __v_138, __v_139, __v_140, __v_141, __v_142, __v_143, __v_144, __v_145, __v_146, __v_147, __v_148, __v_149, __v_150, __v_151]; record.function_queries[306].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_307: usize = 27; +const IN_307: usize = 27; +const OUT_307: usize = 19; +fn aiur_fn_307(inp: [G; IN_307], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_307], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = G::from_u64(1); let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_27, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __v_28: G = __r_arr[0]; let __v_29: G = __r_arr[1]; let __v_30: G = __r_arr[2]; let __v_31: G = __r_arr[3]; let __v_32: G = __r_arr[4]; let __v_33: G = __r_arr[5]; let __v_34: G = __r_arr[6]; let __v_35: G = __r_arr[7]; let __r_arr: [G; OUT_275] = { let __args: [G; IN_275] = [__v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(275, &__args[..])?) { [G::ZERO; OUT_275] } else { let (__hash, __hit) = record.function_queries[275].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[275].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[275].bump_multiplicity(__i); } let __ret: [G; OUT_275] = unsafe { *(record.function_queries[275].output_at(__i).as_ptr() as *const [G; OUT_275]) }; __ret }, _ => { aiur_fn_275::(__args, __hash, record, io_buffer)? }, } } }; let __v_36: G = __r_arr[0]; let __r_arr: [G; OUT_295] = { let __args: [G; IN_295] = [__v_36]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(295, &__args[..])?) { [G::ZERO; OUT_295] } else { let (__hash, __hit) = record.function_queries[295].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[295].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[295].bump_multiplicity(__i); } let __ret: [G; OUT_295] = unsafe { *(record.function_queries[295].output_at(__i).as_ptr() as *const [G; OUT_295]) }; __ret }, _ => { aiur_fn_295::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = __r_arr[1]; let __v_39: G = __r_arr[2]; let __v_40: G = __r_arr[3]; let __v_41: G = __r_arr[4]; let __v_42: G = __r_arr[5]; let __v_43: G = __r_arr[6]; let __v_44: G = __r_arr[7]; let __v_45: G = __r_arr[8]; let __v_46: G = __r_arr[9]; let __v_47: G = __r_arr[10]; let __v_48: G = __r_arr[11]; let __v_49: G = __r_arr[12]; let __v_50: G = __r_arr[13]; let __v_51: G = __r_arr[14]; let __v_52: G = __r_arr[15]; let __v_53: G = __r_arr[16]; let __v_54: G = __r_arr[17]; let __v_55: G = __r_arr[18]; let __ret: [G; OUT_307] = [__v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53, __v_54, __v_55]; record.function_queries[307].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_308: usize = 24; +const IN_308: usize = 24; const OUT_308: usize = 0; -fn aiur_fn_308(inp: [G; IN_308], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_308], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = G::from_u64(0); let __r_arr: [G; OUT_305] = { let __args: [G; IN_305] = [__v_34, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(305, &__args[..])?) { [G::ZERO; OUT_305] } else { let (__hash, __hit) = record.function_queries[305].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[305].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[305].bump_multiplicity(__i); } let __ret: [G; OUT_305] = unsafe { *(record.function_queries[305].output_at(__i).as_ptr() as *const [G; OUT_305]) }; __ret }, _ => { aiur_fn_305::(__args, __hash, record, io_buffer)? }, } } }; let __v_35: G = __r_arr[0]; let __v_36: G = __r_arr[1]; let __v_37: G = __r_arr[2]; let __v_38: G = __r_arr[3]; let __v_39: G = __r_arr[4]; let __v_40: G = __r_arr[5]; let __v_41: G = __r_arr[6]; let __v_42: G = __r_arr[7]; let __v_43: G = __r_arr[8]; let __v_44: G = __r_arr[9]; let __v_45: G = __r_arr[10]; let __v_46: G = __r_arr[11]; let __v_47: G = __r_arr[12]; let __v_48: G = __r_arr[13]; let __v_49: G = __r_arr[14]; let __v_50: G = __r_arr[15]; let __v_51: G = __r_arr[16]; let __v_52: G = __r_arr[17]; let __v_53: G = __r_arr[18]; let __r_arr: [G; OUT_278] = { let __args: [G; IN_278] = [__v_34, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(278, &__args[..])?) { [G::ZERO; OUT_278] } else { let (__hash, __hit) = record.function_queries[278].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[278].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[278].bump_multiplicity(__i); } let __ret: [G; OUT_278] = unsafe { *(record.function_queries[278].output_at(__i).as_ptr() as *const [G; OUT_278]) }; __ret }, _ => { aiur_fn_278::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __v_55: G = __r_arr[1]; let __v_56: G = __r_arr[2]; let __v_57: G = __r_arr[3]; let __v_58: G = __r_arr[4]; let __v_59: G = __r_arr[5]; let __v_60: G = __r_arr[6]; let __v_61: G = __r_arr[7]; let __v_62: G = __r_arr[8]; let __v_63: G = __r_arr[9]; let __v_64: G = __r_arr[10]; let __v_65: G = __r_arr[11]; let __v_66: G = G::from_u64(1); let __r_arr: [G; OUT_301] = { let __args: [G; IN_301] = [__v_66, __v_35]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(301, &__args[..])?) { [G::ZERO; OUT_301] } else { let (__hash, __hit) = record.function_queries[301].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[301].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[301].bump_multiplicity(__i); } let __ret: [G; OUT_301] = unsafe { *(record.function_queries[301].output_at(__i).as_ptr() as *const [G; OUT_301]) }; __ret }, _ => { aiur_fn_301::(__args, __hash, record, io_buffer)? }, } } }; let __v_67: G = __r_arr[0]; let __v_68: G = __r_arr[1]; let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_67]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_69: G = __r_arr[0]; let __r_arr: [G; OUT_230] = { let __args: [G; IN_230] = [__v_69]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(230, &__args[..])?) { [G::ZERO; OUT_230] } else { let (__hash, __hit) = record.function_queries[230].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[230].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[230].bump_multiplicity(__i); } let __ret: [G; OUT_230] = unsafe { *(record.function_queries[230].output_at(__i).as_ptr() as *const [G; OUT_230]) }; __ret }, _ => { aiur_fn_230::(__args, __hash, record, io_buffer)? }, } } }; let __v_70: G = __r_arr[0]; if (__v_70 != __v_66) { return Err(ExecError::AssertEqMismatch { lhs: __v_70.as_canonical_u64(), rhs: __v_66.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_71: G = __r_arr[0]; if (__v_71 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_71.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: Some("aggr range: root range does not start at shard 0".to_string()) }); } let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_72: G = __r_arr[0]; if (__v_72 != __v_69) { return Err(ExecError::AssertEqMismatch { lhs: __v_72.as_canonical_u64(), rhs: __v_69.as_canonical_u64(), msg: Some("aggr range: root range does not end at the last shard".to_string()) }); } let __r_arr: [G; OUT_234] = { let __args: [G; IN_234] = [__v_68, __v_34, __v_34]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(234, &__args[..])?) { [G::ZERO; OUT_234] } else { let (__hash, __hit) = record.function_queries[234].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[234].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[234].bump_multiplicity(__i); } let __ret: [G; OUT_234] = unsafe { *(record.function_queries[234].output_at(__i).as_ptr() as *const [G; OUT_234]) }; __ret }, _ => { aiur_fn_234::(__args, __hash, record, io_buffer)? }, } } }; let __v_73: G = __r_arr[0]; if (__v_73 != __v_66) { return Err(ExecError::AssertEqMismatch { lhs: __v_73.as_canonical_u64(), rhs: __v_66.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_302] = { let __args: [G; IN_302] = [__v_54, __v_55, __v_56, __v_57, __v_58, __v_59, __v_60, __v_61, __v_62, __v_63, __v_64, __v_65, __v_67, __v_68]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(302, &__args[..])?) { [G::ZERO; OUT_302] } else { let (__hash, __hit) = record.function_queries[302].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[302].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[302].bump_multiplicity(__i); } let __ret: [G; OUT_302] = unsafe { *(record.function_queries[302].output_at(__i).as_ptr() as *const [G; OUT_302]) }; __ret }, _ => { aiur_fn_302::(__args, __hash, record, io_buffer)? }, } } }; let __v_74: G = __r_arr[0]; let __v_75: G = __r_arr[1]; let __v_76: G = __r_arr[2]; let __v_77: G = __r_arr[3]; let __v_78: G = __r_arr[4]; let __r_arr: [G; OUT_226] = { let __args: [G; IN_226] = [__v_52, __v_53, __v_68, __v_74, __v_75, __v_76, __v_77]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(226, &__args[..])?) { [G::ZERO; OUT_226] } else { let (__hash, __hit) = record.function_queries[226].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[226].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[226].bump_multiplicity(__i); } let __ret: [G; OUT_226] = unsafe { *(record.function_queries[226].output_at(__i).as_ptr() as *const [G; OUT_226]) }; __ret }, _ => { aiur_fn_226::(__args, __hash, record, io_buffer)? }, } } }; let __v_79: G = __r_arr[0]; let __v_80: G = __r_arr[1]; let __v_81: G = G::from_bool(__v_79 == G::ZERO); let __v_82: G = G::from_bool(__v_80 == G::ZERO); let __v_83: G = __v_81 * __v_82; if (__v_83 != __v_66) { return Err(ExecError::AssertEqMismatch { lhs: __v_83.as_canonical_u64(), rhs: __v_66.as_canonical_u64(), msg: Some("aggr range: the batch does not balance".to_string()) }); } let __r_arr: [G; OUT_237] = { let __args: [G; IN_237] = [__v_67]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(237, &__args[..])?) { [G::ZERO; OUT_237] } else { let (__hash, __hit) = record.function_queries[237].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[237].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[237].bump_multiplicity(__i); } let __ret: [G; OUT_237] = unsafe { *(record.function_queries[237].output_at(__i).as_ptr() as *const [G; OUT_237]) }; __ret }, _ => { aiur_fn_237::(__args, __hash, record, io_buffer)? }, } } }; let __v_84: G = __r_arr[0]; let __r_arr: [G; OUT_277] = { let __args: [G; IN_277] = [__v_84]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(277, &__args[..])?) { [G::ZERO; OUT_277] } else { let (__hash, __hit) = record.function_queries[277].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[277].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[277].bump_multiplicity(__i); } let __ret: [G; OUT_277] = unsafe { *(record.function_queries[277].output_at(__i).as_ptr() as *const [G; OUT_277]) }; __ret }, _ => { aiur_fn_277::(__args, __hash, record, io_buffer)? }, } } }; let __v_85: G = __r_arr[0]; let __r_arr: [G; OUT_317] = { let __args: [G; IN_317] = [__v_85]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(317, &__args[..])?) { [G::ZERO; OUT_317] } else { let (__hash, __hit) = record.function_queries[317].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[317].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[317].bump_multiplicity(__i); } let __ret: [G; OUT_317] = unsafe { *(record.function_queries[317].output_at(__i).as_ptr() as *const [G; OUT_317]) }; __ret }, _ => { aiur_fn_317::(__args, __hash, record, io_buffer)? }, } } }; let __v_86: G = __r_arr[0]; let __v_87: G = G::from_u64(10); if (__v_86 != __v_87) { return Err(ExecError::AssertEqMismatch { lhs: __v_86.as_canonical_u64(), rhs: __v_87.as_canonical_u64(), msg: Some("aggr range: batch must carry a 10-word verify_claim claim".to_string()) }); } let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_85, __v_34]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __v_88: G = __r_arr[0]; if (__v_88 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_88.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: Some("aggr range: batch claim has the wrong channel".to_string()) }); } let __r_arr: [G; OUT_274] = { let __args: [G; IN_274] = [__v_85, __v_66]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(274, &__args[..])?) { [G::ZERO; OUT_274] } else { let (__hash, __hit) = record.function_queries[274].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[274].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[274].bump_multiplicity(__i); } let __ret: [G; OUT_274] = unsafe { *(record.function_queries[274].output_at(__i).as_ptr() as *const [G; OUT_274]) }; __ret }, _ => { aiur_fn_274::(__args, __hash, record, io_buffer)? }, } } }; let __v_89: G = __r_arr[0]; if (__v_89 != __v_16) { return Err(ExecError::AssertEqMismatch { lhs: __v_89.as_canonical_u64(), rhs: __v_16.as_canonical_u64(), msg: Some("aggr range: batch claim has the wrong entrypoint".to_string()) }); } let __v_90: G = G::from_u64(2); let __r_arr: [G; OUT_275] = { let __args: [G; IN_275] = [__v_85, __v_90]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(275, &__args[..])?) { [G::ZERO; OUT_275] } else { let (__hash, __hit) = record.function_queries[275].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[275].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[275].bump_multiplicity(__i); } let __ret: [G; OUT_275] = unsafe { *(record.function_queries[275].output_at(__i).as_ptr() as *const [G; OUT_275]) }; __ret }, _ => { aiur_fn_275::(__args, __hash, record, io_buffer)? }, } } }; let __v_91: G = __r_arr[0]; let __v_92: G = __r_arr[1]; let __v_93: G = __r_arr[2]; let __v_94: G = __r_arr[3]; let __v_95: G = __r_arr[4]; let __v_96: G = __r_arr[5]; let __v_97: G = __r_arr[6]; let __v_98: G = __r_arr[7]; let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_91, __v_92, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_308] = []; record.function_queries[308].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } -const INPUT_SIZE_309: usize = 16; -const IN_309: usize = 16; +fn aiur_fn_308(inp: [G; IN_308], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_308], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = G::from_u64(0); let __r_arr: [G; OUT_280] = { let __args: [G; IN_280] = [__v_24, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(280, &__args[..])?) { [G::ZERO; OUT_280] } else { let (__hash, __hit) = record.function_queries[280].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[280].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[280].bump_multiplicity(__i); } let __ret: [G; OUT_280] = unsafe { *(record.function_queries[280].output_at(__i).as_ptr() as *const [G; OUT_280]) }; __ret }, _ => { aiur_fn_280::(__args, __hash, record, io_buffer)? }, } } }; let __v_25: G = __r_arr[0]; let __v_26: G = __r_arr[1]; let __v_27: G = __r_arr[2]; let __v_28: G = __r_arr[3]; let __v_29: G = __r_arr[4]; let __v_30: G = __r_arr[5]; let __v_31: G = __r_arr[6]; let __v_32: G = __r_arr[7]; let __v_33: G = __r_arr[8]; let __v_34: G = __r_arr[9]; let __v_35: G = __r_arr[10]; let __v_36: G = __r_arr[11]; let __r_arr: [G; OUT_306] = { let __args: [G; IN_306] = [__v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(306, &__args[..])?) { [G::ZERO; OUT_306] } else { let (__hash, __hit) = record.function_queries[306].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[306].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[306].bump_multiplicity(__i); } let __ret: [G; OUT_306] = unsafe { *(record.function_queries[306].output_at(__i).as_ptr() as *const [G; OUT_306]) }; __ret }, _ => { aiur_fn_306::(__args, __hash, record, io_buffer)? }, } } }; let __v_37: G = __r_arr[0]; let __v_38: G = __r_arr[1]; let __v_39: G = __r_arr[2]; let __v_40: G = __r_arr[3]; let __v_41: G = __r_arr[4]; let __v_42: G = __r_arr[5]; let __v_43: G = __r_arr[6]; let __v_44: G = __r_arr[7]; let __v_45: G = __r_arr[8]; let __v_46: G = __r_arr[9]; let __v_47: G = __r_arr[10]; let __v_48: G = __r_arr[11]; let __v_49: G = __r_arr[12]; let __v_50: G = __r_arr[13]; let __v_51: G = __r_arr[14]; let __v_52: G = __r_arr[15]; let __v_53: G = __r_arr[16]; let __v_54: G = __r_arr[17]; let __v_55: G = __r_arr[18]; let __r_arr: [G; OUT_303] = { let __args: [G; IN_303] = [__v_24, __v_37]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(303, &__args[..])?) { [G::ZERO; OUT_303] } else { let (__hash, __hit) = record.function_queries[303].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[303].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[303].bump_multiplicity(__i); } let __ret: [G; OUT_303] = unsafe { *(record.function_queries[303].output_at(__i).as_ptr() as *const [G; OUT_303]) }; __ret }, _ => { aiur_fn_303::(__args, __hash, record, io_buffer)? }, } } }; let __v_56: G = __r_arr[0]; let __v_57: G = __r_arr[1]; let __r_arr: [G; OUT_304] = { let __args: [G; IN_304] = [__v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_56, __v_57]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(304, &__args[..])?) { [G::ZERO; OUT_304] } else { let (__hash, __hit) = record.function_queries[304].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[304].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[304].bump_multiplicity(__i); } let __ret: [G; OUT_304] = unsafe { *(record.function_queries[304].output_at(__i).as_ptr() as *const [G; OUT_304]) }; __ret }, _ => { aiur_fn_304::(__args, __hash, record, io_buffer)? }, } } }; let __v_58: G = __r_arr[0]; let __v_59: G = __r_arr[1]; let __v_60: G = __r_arr[2]; let __v_61: G = __r_arr[3]; let __v_62: G = __r_arr[4]; let __r_arr: [G; OUT_233] = { let __args: [G; IN_233] = [__v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(233, &__args[..])?) { [G::ZERO; OUT_233] } else { let (__hash, __hit) = record.function_queries[233].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[233].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[233].bump_multiplicity(__i); } let __ret: [G; OUT_233] = unsafe { *(record.function_queries[233].output_at(__i).as_ptr() as *const [G; OUT_233]) }; __ret }, _ => { aiur_fn_233::(__args, __hash, record, io_buffer)? }, } } }; let __v_63: G = __r_arr[0]; let __v_64: G = G::from_u64(1); if (__v_63 != __v_64) { return Err(ExecError::AssertEqMismatch { lhs: __v_63.as_canonical_u64(), rhs: __v_64.as_canonical_u64(), msg: Some("aggr range: the range ends before it starts".to_string()) }); } let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_65: G = __r_arr[0]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_66: G = __r_arr[0]; let __v_67: G = __v_66 - __v_65; let __io_pair: (G, G) = { let __key: [G; 1] = [__v_64]; let __info = io_buffer.get_info(__v_24, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_68: G = __io_pair.0; let __v_69: G = __io_pair.1; let __r_arr: [G; OUT_31] = { let __args: [G; IN_31] = [__v_68]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(31, &__args[..])?) { [G::ZERO; OUT_31] } else { let (__hash, __hit) = record.function_queries[31].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[31].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[31].bump_multiplicity(__i); } let __ret: [G; OUT_31] = unsafe { *(record.function_queries[31].output_at(__i).as_ptr() as *const [G; OUT_31]) }; __ret }, _ => { aiur_fn_31::(__args, __hash, record, io_buffer)? }, } } }; let __v_70: G = __r_arr[0]; let __v_71: G = __r_arr[1]; if (__v_70 != __v_67) { return Err(ExecError::AssertEqMismatch { lhs: __v_70.as_canonical_u64(), rhs: __v_67.as_canonical_u64(), msg: Some("aggr range: proof count differs from the range".to_string()) }); } let __r_arr: [G; OUT_327] = { let __args: [G; IN_327] = [__v_56, __v_65]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(327, &__args[..])?) { [G::ZERO; OUT_327] } else { let (__hash, __hit) = record.function_queries[327].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[327].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[327].bump_multiplicity(__i); } let __ret: [G; OUT_327] = unsafe { *(record.function_queries[327].output_at(__i).as_ptr() as *const [G; OUT_327]) }; __ret }, _ => { aiur_fn_327::(__args, __hash, record, io_buffer)? }, } } }; let __v_72: G = __r_arr[0]; let __r_arr: [G; OUT_305] = { let __args: [G; IN_305] = [__v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_72, __v_71, __v_65, __v_67, __v_62, __v_58, __v_59, __v_60, __v_61, __v_24, __v_24]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(305, &__args[..])?) { [G::ZERO; OUT_305] } else { let (__hash, __hit) = record.function_queries[305].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[305].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[305].bump_multiplicity(__i); } let __ret: [G; OUT_305] = unsafe { *(record.function_queries[305].output_at(__i).as_ptr() as *const [G; OUT_305]) }; __ret }, _ => { aiur_fn_305::(__args, __hash, record, io_buffer)? }, } } }; let __v_73: G = __r_arr[0]; let __v_74: G = __r_arr[1]; let __v_75: G = __r_arr[2]; let __v_76: G = __v_68 + __v_69; if (__v_75 != __v_76) { return Err(ExecError::AssertEqMismatch { lhs: __v_75.as_canonical_u64(), rhs: __v_76.as_canonical_u64(), msg: Some("aggr range: trailing bytes after the proofs".to_string()) }); } let __v_77: G = __v_73 - __v_54; let __v_78: G = G::from_bool(__v_77 == G::ZERO); let __v_79: G = __v_74 - __v_55; let __v_80: G = G::from_bool(__v_79 == G::ZERO); let __v_81: G = __v_78 * __v_80; if (__v_81 != __v_64) { return Err(ExecError::AssertEqMismatch { lhs: __v_81.as_canonical_u64(), rhs: __v_64.as_canonical_u64(), msg: Some("aggr range: residual sum differs from the statement".to_string()) }); } let __ret: [G; OUT_308] = []; record.function_queries[308].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_309: usize = 34; +const IN_309: usize = 34; const OUT_309: usize = 0; -fn aiur_fn_309(inp: [G; IN_309], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_309], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = G::from_u64(3); let __v_17: G = G::from_u64(0); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_17]; let __info = io_buffer.get_info(__v_16, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_18: G = __io_pair.0; let __v_19: G = __io_pair.1; let __v_20: G = G::from_u64(80); if (__v_19 != __v_20) { return Err(ExecError::AssertEqMismatch { lhs: __v_19.as_canonical_u64(), rhs: __v_20.as_canonical_u64(), msg: Some("aggr: allowed blob must be exactly 80 bytes".to_string()) }); } let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_16, __v_18, __v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = G::from_u64(103); let __v_23: G = G::from_u64(230); let __v_24: G = G::from_u64(9); let __v_25: G = G::from_u64(106); let __v_26: G = G::from_u64(133); let __v_27: G = G::from_u64(174); let __v_28: G = G::from_u64(187); let __v_29: G = G::from_u64(114); let __v_30: G = G::from_u64(243); let __v_31: G = G::from_u64(110); let __v_32: G = G::from_u64(60); let __v_33: G = G::from_u64(58); let __v_34: G = G::from_u64(245); let __v_35: G = G::from_u64(79); let __v_36: G = G::from_u64(165); let __v_37: G = G::from_u64(127); let __v_38: G = G::from_u64(82); let __v_39: G = G::from_u64(14); let __v_40: G = G::from_u64(81); let __v_41: G = G::from_u64(140); let __v_42: G = G::from_u64(104); let __v_43: G = G::from_u64(5); let __v_44: G = G::from_u64(155); let __v_45: G = G::from_u64(171); let __v_46: G = G::from_u64(217); let __v_47: G = G::from_u64(131); let __v_48: G = G::from_u64(31); let __v_49: G = G::from_u64(25); let __v_50: G = G::from_u64(205); let __v_51: G = G::from_u64(224); let __v_52: G = G::from_u64(91); let __v_53: G = G::from_u64(1); let __v_54: G = { let __values: [G; 3] = [__v_53, __v_53, __v_53]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_55: G = { let __values: [G; 8] = [__v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_56: G = { let __values: [G; 32] = [__v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_22, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_57: G = { let __values: [G; 34] = [__v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_21, __v_54, __v_17, __v_17, __v_55, __v_56, __v_57]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_58: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_58]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_59: G = __r_arr[0]; let __v_60: G = __r_arr[1]; let __v_61: G = __r_arr[2]; let __v_62: G = __r_arr[3]; let __v_63: G = __r_arr[4]; let __v_64: G = __r_arr[5]; let __v_65: G = __r_arr[6]; let __v_66: G = __r_arr[7]; let __v_67: G = __r_arr[8]; let __v_68: G = __r_arr[9]; let __v_69: G = __r_arr[10]; let __v_70: G = __r_arr[11]; let __v_71: G = __r_arr[12]; let __v_72: G = __r_arr[13]; let __v_73: G = __r_arr[14]; let __v_74: G = __r_arr[15]; let __v_75: G = __r_arr[16]; let __v_76: G = __r_arr[17]; let __v_77: G = __r_arr[18]; let __v_78: G = __r_arr[19]; let __v_79: G = __r_arr[20]; let __v_80: G = __r_arr[21]; let __v_81: G = __r_arr[22]; let __v_82: G = __r_arr[23]; let __v_83: G = __r_arr[24]; let __v_84: G = __r_arr[25]; let __v_85: G = __r_arr[26]; let __v_86: G = __r_arr[27]; let __v_87: G = __r_arr[28]; let __v_88: G = __r_arr[29]; let __v_89: G = __r_arr[30]; let __v_90: G = __r_arr[31]; let __v_91: G = G::from_u64(256); let __v_92: G = __v_91 * __v_60; let __v_93: G = __v_59 + __v_92; let __v_94: G = G::from_u64(65536); let __v_95: G = __v_94 * __v_61; let __v_96: G = __v_93 + __v_95; let __v_97: G = G::from_u64(16777216); let __v_98: G = __v_97 * __v_62; let __v_99: G = __v_96 + __v_98; let __v_100: G = __v_91 * __v_64; let __v_101: G = __v_63 + __v_100; let __v_102: G = __v_94 * __v_65; let __v_103: G = __v_101 + __v_102; let __v_104: G = __v_97 * __v_66; let __v_105: G = __v_103 + __v_104; let __v_106: G = __v_91 * __v_68; let __v_107: G = __v_67 + __v_106; let __v_108: G = __v_94 * __v_69; let __v_109: G = __v_107 + __v_108; let __v_110: G = __v_97 * __v_70; let __v_111: G = __v_109 + __v_110; let __v_112: G = __v_91 * __v_72; let __v_113: G = __v_71 + __v_112; let __v_114: G = __v_94 * __v_73; let __v_115: G = __v_113 + __v_114; let __v_116: G = __v_97 * __v_74; let __v_117: G = __v_115 + __v_116; let __v_118: G = __v_91 * __v_76; let __v_119: G = __v_75 + __v_118; let __v_120: G = __v_94 * __v_77; let __v_121: G = __v_119 + __v_120; let __v_122: G = __v_97 * __v_78; let __v_123: G = __v_121 + __v_122; let __v_124: G = __v_91 * __v_80; let __v_125: G = __v_79 + __v_124; let __v_126: G = __v_94 * __v_81; let __v_127: G = __v_125 + __v_126; let __v_128: G = __v_97 * __v_82; let __v_129: G = __v_127 + __v_128; let __v_130: G = __v_91 * __v_84; let __v_131: G = __v_83 + __v_130; let __v_132: G = __v_94 * __v_85; let __v_133: G = __v_131 + __v_132; let __v_134: G = __v_97 * __v_86; let __v_135: G = __v_133 + __v_134; let __v_136: G = __v_91 * __v_88; let __v_137: G = __v_87 + __v_136; let __v_138: G = __v_94 * __v_89; let __v_139: G = __v_137 + __v_138; let __v_140: G = __v_97 * __v_90; let __v_141: G = __v_139 + __v_140; if (__v_99 != __v_0) { return Err(ExecError::AssertEqMismatch { lhs: __v_99.as_canonical_u64(), rhs: __v_0.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } if (__v_105 != __v_1) { return Err(ExecError::AssertEqMismatch { lhs: __v_105.as_canonical_u64(), rhs: __v_1.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } if (__v_111 != __v_2) { return Err(ExecError::AssertEqMismatch { lhs: __v_111.as_canonical_u64(), rhs: __v_2.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } if (__v_117 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_117.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } if (__v_123 != __v_4) { return Err(ExecError::AssertEqMismatch { lhs: __v_123.as_canonical_u64(), rhs: __v_4.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } if (__v_129 != __v_5) { return Err(ExecError::AssertEqMismatch { lhs: __v_129.as_canonical_u64(), rhs: __v_5.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } if (__v_135 != __v_6) { return Err(ExecError::AssertEqMismatch { lhs: __v_135.as_canonical_u64(), rhs: __v_6.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } if (__v_141 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_141.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_21]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_142: G = __r_arr[0]; let __v_143: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_143]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_144: G = __r_arr[0]; let __v_145: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_145]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_146: G = __r_arr[0]; let __v_147: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_147]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_148: G = __r_arr[0]; let __v_149: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_149]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_150: G = __r_arr[0]; let __v_151: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_151]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_152: G = __r_arr[0]; let __v_153: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_153]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_154: G = __r_arr[0]; let __v_155: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_155]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_156: G = __r_arr[0]; let __v_157: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_157]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_158: G = __r_arr[0]; let __v_159: G = __r_arr[1]; let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_159]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_160: G = __r_arr[0]; let __v_161: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_161]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_162: G = __r_arr[0]; let __v_163: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_163]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_164: G = __r_arr[0]; let __v_165: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_165]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_166: G = __r_arr[0]; let __v_167: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_167]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_168: G = __r_arr[0]; let __v_169: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_169]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_170: G = __r_arr[0]; let __v_171: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_171]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_172: G = __r_arr[0]; let __v_173: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_173]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_174: G = __r_arr[0]; let __v_175: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_175]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_176: G = __r_arr[0]; let __v_177: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_177.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_178: G = __loaded[0]; let __v_179: G = __loaded[1]; let __v_180: G = __loaded[2]; if (__v_178 != __v_53) { return Err(ExecError::AssertEqMismatch { lhs: __v_178.as_canonical_u64(), rhs: __v_53.as_canonical_u64(), msg: Some("aggr: allowed blob must be exactly 80 bytes".to_string()) }); } if (__v_179 != __v_53) { return Err(ExecError::AssertEqMismatch { lhs: __v_179.as_canonical_u64(), rhs: __v_53.as_canonical_u64(), msg: Some("aggr: allowed blob must be exactly 80 bytes".to_string()) }); } if (__v_180 != __v_53) { return Err(ExecError::AssertEqMismatch { lhs: __v_180.as_canonical_u64(), rhs: __v_53.as_canonical_u64(), msg: Some("aggr: allowed blob must be exactly 80 bytes".to_string()) }); } let __r_arr: [G; OUT_246] = { let __args: [G; IN_246] = [__v_142]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(246, &__args[..])?) { [G::ZERO; OUT_246] } else { let (__hash, __hit) = record.function_queries[246].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[246].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[246].bump_multiplicity(__i); } let __ret: [G; OUT_246] = unsafe { *(record.function_queries[246].output_at(__i).as_ptr() as *const [G; OUT_246]) }; __ret }, _ => { aiur_fn_246::(__args, __hash, record, io_buffer)? }, } } }; let __v_181: G = __r_arr[0]; let __v_182: G = __r_arr[1]; let __v_183: G = __r_arr[2]; let __v_184: G = __r_arr[3]; let __v_185: G = __r_arr[4]; let __v_186: G = __r_arr[5]; let __v_187: G = __r_arr[6]; let __v_188: G = __r_arr[7]; let __r_arr: [G; OUT_246] = { let __args: [G; IN_246] = [__v_160]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(246, &__args[..])?) { [G::ZERO; OUT_246] } else { let (__hash, __hit) = record.function_queries[246].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[246].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[246].bump_multiplicity(__i); } let __ret: [G; OUT_246] = unsafe { *(record.function_queries[246].output_at(__i).as_ptr() as *const [G; OUT_246]) }; __ret }, _ => { aiur_fn_246::(__args, __hash, record, io_buffer)? }, } } }; let __v_189: G = __r_arr[0]; let __v_190: G = __r_arr[1]; let __v_191: G = __r_arr[2]; let __v_192: G = __r_arr[3]; let __v_193: G = __r_arr[4]; let __v_194: G = __r_arr[5]; let __v_195: G = __r_arr[6]; let __v_196: G = __r_arr[7]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_144, __v_146, __v_148, __v_150, __v_152, __v_154, __v_156, __v_158]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_197: G = __r_arr[0]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_162, __v_164, __v_166, __v_168, __v_170, __v_172, __v_174, __v_176]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_198: G = __r_arr[0]; let __v_199: G = G::from_u64(6); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_17]; let __info = io_buffer.get_info(__v_199, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_200: G = __io_pair.0; let __v_201: G = __io_pair.1; if (__v_201 != __v_53) { return Err(ExecError::AssertEqMismatch { lhs: __v_201.as_canonical_u64(), rhs: __v_53.as_canonical_u64(), msg: Some("aggr: shape hint must be exactly one byte".to_string()) }); } let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_199, __v_200, __v_201]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_202: G = __r_arr[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_202]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_203: G = __r_arr[0]; let __v_204: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_204.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_205: G = __loaded[0]; let __v_206: G = __loaded[1]; let __v_207: G = __loaded[2]; if (__v_205 != __v_53) { return Err(ExecError::AssertEqMismatch { lhs: __v_205.as_canonical_u64(), rhs: __v_53.as_canonical_u64(), msg: Some("aggr: trailing bytes after shape hint".to_string()) }); } if (__v_206 != __v_53) { return Err(ExecError::AssertEqMismatch { lhs: __v_206.as_canonical_u64(), rhs: __v_53.as_canonical_u64(), msg: Some("aggr: trailing bytes after shape hint".to_string()) }); } if (__v_207 != __v_53) { return Err(ExecError::AssertEqMismatch { lhs: __v_207.as_canonical_u64(), rhs: __v_53.as_canonical_u64(), msg: Some("aggr: trailing bytes after shape hint".to_string()) }); } match __v_203.as_canonical_u64() { 0u64 => { let __v_208: G = G::from_u64(0); let __r_arr: [G; OUT_281] = { let __args: [G; IN_281] = [__v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(281, &__args[..])?) { [G::ZERO; OUT_281] } else { let (__hash, __hit) = record.function_queries[281].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[281].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[281].bump_multiplicity(__i); } let __ret: [G; OUT_281] = unsafe { *(record.function_queries[281].output_at(__i).as_ptr() as *const [G; OUT_281]) }; __ret }, _ => { aiur_fn_281::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_208: G = G::from_u64(1); let __r_arr: [G; OUT_281] = { let __args: [G; IN_281] = [__v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(281, &__args[..])?) { [G::ZERO; OUT_281] } else { let (__hash, __hit) = record.function_queries[281].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[281].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[281].bump_multiplicity(__i); } let __ret: [G; OUT_281] = unsafe { *(record.function_queries[281].output_at(__i).as_ptr() as *const [G; OUT_281]) }; __ret }, _ => { aiur_fn_281::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 2u64 => { let __v_208: G = G::from_u64(0); let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_208, __v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 3u64 => { let __v_208: G = G::from_u64(0); let __v_209: G = G::from_u64(1); let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_208, __v_209, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 4u64 => { let __v_208: G = G::from_u64(1); let __v_209: G = G::from_u64(0); let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_208, __v_209, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 5u64 => { let __v_208: G = G::from_u64(1); let __r_arr: [G; OUT_282] = { let __args: [G; IN_282] = [__v_208, __v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(282, &__args[..])?) { [G::ZERO; OUT_282] } else { let (__hash, __hit) = record.function_queries[282].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[282].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[282].bump_multiplicity(__i); } let __ret: [G; OUT_282] = unsafe { *(record.function_queries[282].output_at(__i).as_ptr() as *const [G; OUT_282]) }; __ret }, _ => { aiur_fn_282::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 6u64 => { let __v_208: G = G::from_u64(0); let __r_arr: [G; OUT_283] = { let __args: [G; IN_283] = [__v_208, __v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(283, &__args[..])?) { [G::ZERO; OUT_283] } else { let (__hash, __hit) = record.function_queries[283].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[283].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[283].bump_multiplicity(__i); } let __ret: [G; OUT_283] = unsafe { *(record.function_queries[283].output_at(__i).as_ptr() as *const [G; OUT_283]) }; __ret }, _ => { aiur_fn_283::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 7u64 => { let __v_208: G = G::from_u64(0); let __v_209: G = G::from_u64(1); let __r_arr: [G; OUT_283] = { let __args: [G; IN_283] = [__v_208, __v_209, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(283, &__args[..])?) { [G::ZERO; OUT_283] } else { let (__hash, __hit) = record.function_queries[283].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[283].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[283].bump_multiplicity(__i); } let __ret: [G; OUT_283] = unsafe { *(record.function_queries[283].output_at(__i).as_ptr() as *const [G; OUT_283]) }; __ret }, _ => { aiur_fn_283::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 8u64 => { let __v_208: G = G::from_u64(1); let __v_209: G = G::from_u64(0); let __r_arr: [G; OUT_283] = { let __args: [G; IN_283] = [__v_208, __v_209, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(283, &__args[..])?) { [G::ZERO; OUT_283] } else { let (__hash, __hit) = record.function_queries[283].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[283].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[283].bump_multiplicity(__i); } let __ret: [G; OUT_283] = unsafe { *(record.function_queries[283].output_at(__i).as_ptr() as *const [G; OUT_283]) }; __ret }, _ => { aiur_fn_283::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 9u64 => { let __v_208: G = G::from_u64(1); let __r_arr: [G; OUT_283] = { let __args: [G; IN_283] = [__v_208, __v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(283, &__args[..])?) { [G::ZERO; OUT_283] } else { let (__hash, __hit) = record.function_queries[283].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[283].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[283].bump_multiplicity(__i); } let __ret: [G; OUT_283] = unsafe { *(record.function_queries[283].output_at(__i).as_ptr() as *const [G; OUT_283]) }; __ret }, _ => { aiur_fn_283::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 10u64 => { let __r_arr: [G; OUT_306] = { let __args: [G; IN_306] = [__v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(306, &__args[..])?) { [G::ZERO; OUT_306] } else { let (__hash, __hit) = record.function_queries[306].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[306].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[306].bump_multiplicity(__i); } let __ret: [G; OUT_306] = unsafe { *(record.function_queries[306].output_at(__i).as_ptr() as *const [G; OUT_306]) }; __ret }, _ => { aiur_fn_306::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 11u64 => { let __r_arr: [G; OUT_307] = { let __args: [G; IN_307] = [__v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(307, &__args[..])?) { [G::ZERO; OUT_307] } else { let (__hash, __hit) = record.function_queries[307].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[307].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[307].bump_multiplicity(__i); } let __ret: [G; OUT_307] = unsafe { *(record.function_queries[307].output_at(__i).as_ptr() as *const [G; OUT_307]) }; __ret }, _ => { aiur_fn_307::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 12u64 => { let __r_arr: [G; OUT_308] = { let __args: [G; IN_308] = [__v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(308, &__args[..])?) { [G::ZERO; OUT_308] } else { let (__hash, __hit) = record.function_queries[308].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[308].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[308].bump_multiplicity(__i); } let __ret: [G; OUT_308] = unsafe { *(record.function_queries[308].output_at(__i).as_ptr() as *const [G; OUT_308]) }; __ret }, _ => { aiur_fn_308::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 13u64 => { let __v_208: G = G::from_u64(0); let __r_arr: [G; OUT_284] = { let __args: [G; IN_284] = [__v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(284, &__args[..])?) { [G::ZERO; OUT_284] } else { let (__hash, __hit) = record.function_queries[284].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[284].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[284].bump_multiplicity(__i); } let __ret: [G; OUT_284] = unsafe { *(record.function_queries[284].output_at(__i).as_ptr() as *const [G; OUT_284]) }; __ret }, _ => { aiur_fn_284::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 14u64 => { let __v_208: G = G::from_u64(1); let __r_arr: [G; OUT_284] = { let __args: [G; IN_284] = [__v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(284, &__args[..])?) { [G::ZERO; OUT_284] } else { let (__hash, __hit) = record.function_queries[284].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[284].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[284].bump_multiplicity(__i); } let __ret: [G; OUT_284] = unsafe { *(record.function_queries[284].output_at(__i).as_ptr() as *const [G; OUT_284]) }; __ret }, _ => { aiur_fn_284::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 15u64 => { let __r_arr: [G; OUT_290] = { let __args: [G; IN_290] = [__v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(290, &__args[..])?) { [G::ZERO; OUT_290] } else { let (__hash, __hit) = record.function_queries[290].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[290].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[290].bump_multiplicity(__i); } let __ret: [G; OUT_290] = unsafe { *(record.function_queries[290].output_at(__i).as_ptr() as *const [G; OUT_290]) }; __ret }, _ => { aiur_fn_290::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 16u64 => { let __r_arr: [G; OUT_292] = { let __args: [G; IN_292] = [__v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(292, &__args[..])?) { [G::ZERO; OUT_292] } else { let (__hash, __hit) = record.function_queries[292].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[292].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[292].bump_multiplicity(__i); } let __ret: [G; OUT_292] = unsafe { *(record.function_queries[292].output_at(__i).as_ptr() as *const [G; OUT_292]) }; __ret }, _ => { aiur_fn_292::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_203.as_canonical_u64())); }, } }) } -const INPUT_SIZE_310: usize = 2; -const IN_310: usize = 2; -const OUT_310: usize = 1; -fn aiur_fn_310(inp: [G; IN_310], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_310], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_324] = { let __args: [G; IN_324] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(324, &__args[..])?) { [G::ZERO; OUT_324] } else { let (__hash, __hit) = record.function_queries[324].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[324].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[324].bump_multiplicity(__i); } let __ret: [G; OUT_324] = unsafe { *(record.function_queries[324].output_at(__i).as_ptr() as *const [G; OUT_324]) }; __ret }, _ => { aiur_fn_324::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_2.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_3: G = __loaded[0]; let __v_4: G = __loaded[1]; let __v_5: G = __loaded[2]; match __v_3.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_310] = [__v_4]; record.function_queries[310].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_3.as_canonical_u64())); }, } }) } -const INPUT_SIZE_311: usize = 1; -const IN_311: usize = 1; -const OUT_311: usize = 1; -fn aiur_fn_311(inp: [G; IN_311], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_311], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 26] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(8).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(26))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 26 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 26] = __args[..26].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; let __v_6: G = __loaded[5]; let __v_7: G = __loaded[6]; let __v_8: G = __loaded[7]; let __v_9: G = __loaded[8]; let __v_10: G = __loaded[9]; let __v_11: G = __loaded[10]; let __v_12: G = __loaded[11]; let __v_13: G = __loaded[12]; let __v_14: G = __loaded[13]; let __v_15: G = __loaded[14]; let __v_16: G = __loaded[15]; let __v_17: G = __loaded[16]; let __v_18: G = __loaded[17]; let __v_19: G = __loaded[18]; let __v_20: G = __loaded[19]; let __v_21: G = __loaded[20]; let __v_22: G = __loaded[21]; let __v_23: G = __loaded[22]; let __v_24: G = __loaded[23]; let __v_25: G = __loaded[24]; let __v_26: G = __loaded[25]; match __v_1.as_canonical_u64() { 1u64 => { let __v_27: G = G::from_u64(0); let __ret: [G; OUT_311] = [__v_27]; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_311] = { let __args: [G; IN_311] = [__v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(311, &__args[..])?) { [G::ZERO; OUT_311] } else { let (__hash, __hit) = record.function_queries[311].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[311].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[311].bump_multiplicity(__i); } let __ret: [G; OUT_311] = unsafe { *(record.function_queries[311].output_at(__i).as_ptr() as *const [G; OUT_311]) }; __ret }, _ => { aiur_fn_311::(__args, __hash, record, io_buffer)? }, } } }; let __v_27: G = __r_arr[0]; let __v_28: G = G::from_u64(1); let __v_29: G = __v_27 + __v_28; let __ret: [G; OUT_311] = [__v_29]; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } -const INPUT_SIZE_312: usize = 1; -const IN_312: usize = 1; +fn aiur_fn_309(inp: [G; IN_309], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_309], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = G::from_u64(0); let __r_arr: [G; OUT_307] = { let __args: [G; IN_307] = [__v_34, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(307, &__args[..])?) { [G::ZERO; OUT_307] } else { let (__hash, __hit) = record.function_queries[307].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[307].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[307].bump_multiplicity(__i); } let __ret: [G; OUT_307] = unsafe { *(record.function_queries[307].output_at(__i).as_ptr() as *const [G; OUT_307]) }; __ret }, _ => { aiur_fn_307::(__args, __hash, record, io_buffer)? }, } } }; let __v_35: G = __r_arr[0]; let __v_36: G = __r_arr[1]; let __v_37: G = __r_arr[2]; let __v_38: G = __r_arr[3]; let __v_39: G = __r_arr[4]; let __v_40: G = __r_arr[5]; let __v_41: G = __r_arr[6]; let __v_42: G = __r_arr[7]; let __v_43: G = __r_arr[8]; let __v_44: G = __r_arr[9]; let __v_45: G = __r_arr[10]; let __v_46: G = __r_arr[11]; let __v_47: G = __r_arr[12]; let __v_48: G = __r_arr[13]; let __v_49: G = __r_arr[14]; let __v_50: G = __r_arr[15]; let __v_51: G = __r_arr[16]; let __v_52: G = __r_arr[17]; let __v_53: G = __r_arr[18]; let __v_54: G = G::from_u64(1); let __r_arr: [G; OUT_307] = { let __args: [G; IN_307] = [__v_54, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(307, &__args[..])?) { [G::ZERO; OUT_307] } else { let (__hash, __hit) = record.function_queries[307].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[307].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[307].bump_multiplicity(__i); } let __ret: [G; OUT_307] = unsafe { *(record.function_queries[307].output_at(__i).as_ptr() as *const [G; OUT_307]) }; __ret }, _ => { aiur_fn_307::(__args, __hash, record, io_buffer)? }, } } }; let __v_55: G = __r_arr[0]; let __v_56: G = __r_arr[1]; let __v_57: G = __r_arr[2]; let __v_58: G = __r_arr[3]; let __v_59: G = __r_arr[4]; let __v_60: G = __r_arr[5]; let __v_61: G = __r_arr[6]; let __v_62: G = __r_arr[7]; let __v_63: G = __r_arr[8]; let __v_64: G = __r_arr[9]; let __v_65: G = __r_arr[10]; let __v_66: G = __r_arr[11]; let __v_67: G = __r_arr[12]; let __v_68: G = __r_arr[13]; let __v_69: G = __r_arr[14]; let __v_70: G = __r_arr[15]; let __v_71: G = __r_arr[16]; let __v_72: G = __r_arr[17]; let __v_73: G = __r_arr[18]; let __r_arr: [G; OUT_306] = { let __args: [G; IN_306] = [__v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(306, &__args[..])?) { [G::ZERO; OUT_306] } else { let (__hash, __hit) = record.function_queries[306].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[306].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[306].bump_multiplicity(__i); } let __ret: [G; OUT_306] = unsafe { *(record.function_queries[306].output_at(__i).as_ptr() as *const [G; OUT_306]) }; __ret }, _ => { aiur_fn_306::(__args, __hash, record, io_buffer)? }, } } }; let __v_74: G = __r_arr[0]; let __v_75: G = __r_arr[1]; let __v_76: G = __r_arr[2]; let __v_77: G = __r_arr[3]; let __v_78: G = __r_arr[4]; let __v_79: G = __r_arr[5]; let __v_80: G = __r_arr[6]; let __v_81: G = __r_arr[7]; let __v_82: G = __r_arr[8]; let __v_83: G = __r_arr[9]; let __v_84: G = __r_arr[10]; let __v_85: G = __r_arr[11]; let __v_86: G = __r_arr[12]; let __v_87: G = __r_arr[13]; let __v_88: G = __r_arr[14]; let __v_89: G = __r_arr[15]; let __v_90: G = __r_arr[16]; let __v_91: G = __r_arr[17]; let __v_92: G = __r_arr[18]; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_35, __v_55]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_93: G = __r_arr[0]; if (__v_93 != __v_54) { return Err(ExecError::AssertEqMismatch { lhs: __v_93.as_canonical_u64(), rhs: __v_54.as_canonical_u64(), msg: Some("aggr range: children verify different batches".to_string()) }); } let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_74, __v_35]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_94: G = __r_arr[0]; if (__v_94 != __v_54) { return Err(ExecError::AssertEqMismatch { lhs: __v_94.as_canonical_u64(), rhs: __v_54.as_canonical_u64(), msg: Some("aggr range: output names a different batch than the children".to_string()) }); } let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_95: G = __r_arr[0]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_56, __v_57, __v_58, __v_59, __v_60, __v_61, __v_62, __v_63]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_96: G = __r_arr[0]; if (__v_95 != __v_96) { return Err(ExecError::AssertEqMismatch { lhs: __v_95.as_canonical_u64(), rhs: __v_96.as_canonical_u64(), msg: Some("aggr range: children are not adjacent".to_string()) }); } let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_75, __v_76, __v_77, __v_78, __v_79, __v_80, __v_81, __v_82]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_97: G = __r_arr[0]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_98: G = __r_arr[0]; if (__v_97 != __v_98) { return Err(ExecError::AssertEqMismatch { lhs: __v_97.as_canonical_u64(), rhs: __v_98.as_canonical_u64(), msg: Some("aggr range: output starts elsewhere than the left child".to_string()) }); } let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_83, __v_84, __v_85, __v_86, __v_87, __v_88, __v_89, __v_90]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_99: G = __r_arr[0]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_64, __v_65, __v_66, __v_67, __v_68, __v_69, __v_70, __v_71]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_100: G = __r_arr[0]; if (__v_99 != __v_100) { return Err(ExecError::AssertEqMismatch { lhs: __v_99.as_canonical_u64(), rhs: __v_100.as_canonical_u64(), msg: Some("aggr range: output ends elsewhere than the right child".to_string()) }); } let __v_101: G = __v_52 + __v_72; let __v_102: G = __v_53 + __v_73; let __v_103: G = __v_101 - __v_91; let __v_104: G = G::from_bool(__v_103 == G::ZERO); let __v_105: G = __v_102 - __v_92; let __v_106: G = G::from_bool(__v_105 == G::ZERO); let __v_107: G = __v_104 * __v_106; if (__v_107 != __v_54) { return Err(ExecError::AssertEqMismatch { lhs: __v_107.as_canonical_u64(), rhs: __v_54.as_canonical_u64(), msg: Some("aggr range: output residual is not the children's sum".to_string()) }); } let __ret: [G; OUT_309] = []; record.function_queries[309].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_310: usize = 34; +const IN_310: usize = 34; +const OUT_310: usize = 0; +fn aiur_fn_310(inp: [G; IN_310], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_310], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = inp[16]; let __v_17: G = inp[17]; let __v_18: G = inp[18]; let __v_19: G = inp[19]; let __v_20: G = inp[20]; let __v_21: G = inp[21]; let __v_22: G = inp[22]; let __v_23: G = inp[23]; let __v_24: G = inp[24]; let __v_25: G = inp[25]; let __v_26: G = inp[26]; let __v_27: G = inp[27]; let __v_28: G = inp[28]; let __v_29: G = inp[29]; let __v_30: G = inp[30]; let __v_31: G = inp[31]; let __v_32: G = inp[32]; let __v_33: G = inp[33]; let __v_34: G = G::from_u64(0); let __r_arr: [G; OUT_307] = { let __args: [G; IN_307] = [__v_34, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(307, &__args[..])?) { [G::ZERO; OUT_307] } else { let (__hash, __hit) = record.function_queries[307].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[307].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[307].bump_multiplicity(__i); } let __ret: [G; OUT_307] = unsafe { *(record.function_queries[307].output_at(__i).as_ptr() as *const [G; OUT_307]) }; __ret }, _ => { aiur_fn_307::(__args, __hash, record, io_buffer)? }, } } }; let __v_35: G = __r_arr[0]; let __v_36: G = __r_arr[1]; let __v_37: G = __r_arr[2]; let __v_38: G = __r_arr[3]; let __v_39: G = __r_arr[4]; let __v_40: G = __r_arr[5]; let __v_41: G = __r_arr[6]; let __v_42: G = __r_arr[7]; let __v_43: G = __r_arr[8]; let __v_44: G = __r_arr[9]; let __v_45: G = __r_arr[10]; let __v_46: G = __r_arr[11]; let __v_47: G = __r_arr[12]; let __v_48: G = __r_arr[13]; let __v_49: G = __r_arr[14]; let __v_50: G = __r_arr[15]; let __v_51: G = __r_arr[16]; let __v_52: G = __r_arr[17]; let __v_53: G = __r_arr[18]; let __r_arr: [G; OUT_280] = { let __args: [G; IN_280] = [__v_34, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(280, &__args[..])?) { [G::ZERO; OUT_280] } else { let (__hash, __hit) = record.function_queries[280].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[280].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[280].bump_multiplicity(__i); } let __ret: [G; OUT_280] = unsafe { *(record.function_queries[280].output_at(__i).as_ptr() as *const [G; OUT_280]) }; __ret }, _ => { aiur_fn_280::(__args, __hash, record, io_buffer)? }, } } }; let __v_54: G = __r_arr[0]; let __v_55: G = __r_arr[1]; let __v_56: G = __r_arr[2]; let __v_57: G = __r_arr[3]; let __v_58: G = __r_arr[4]; let __v_59: G = __r_arr[5]; let __v_60: G = __r_arr[6]; let __v_61: G = __r_arr[7]; let __v_62: G = __r_arr[8]; let __v_63: G = __r_arr[9]; let __v_64: G = __r_arr[10]; let __v_65: G = __r_arr[11]; let __v_66: G = G::from_u64(1); let __r_arr: [G; OUT_303] = { let __args: [G; IN_303] = [__v_66, __v_35]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(303, &__args[..])?) { [G::ZERO; OUT_303] } else { let (__hash, __hit) = record.function_queries[303].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[303].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[303].bump_multiplicity(__i); } let __ret: [G; OUT_303] = unsafe { *(record.function_queries[303].output_at(__i).as_ptr() as *const [G; OUT_303]) }; __ret }, _ => { aiur_fn_303::(__args, __hash, record, io_buffer)? }, } } }; let __v_67: G = __r_arr[0]; let __v_68: G = __r_arr[1]; let __r_arr: [G; OUT_317] = { let __args: [G; IN_317] = [__v_67]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(317, &__args[..])?) { [G::ZERO; OUT_317] } else { let (__hash, __hit) = record.function_queries[317].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[317].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[317].bump_multiplicity(__i); } let __ret: [G; OUT_317] = unsafe { *(record.function_queries[317].output_at(__i).as_ptr() as *const [G; OUT_317]) }; __ret }, _ => { aiur_fn_317::(__args, __hash, record, io_buffer)? }, } } }; let __v_69: G = __r_arr[0]; let __r_arr: [G; OUT_230] = { let __args: [G; IN_230] = [__v_69]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(230, &__args[..])?) { [G::ZERO; OUT_230] } else { let (__hash, __hit) = record.function_queries[230].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[230].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[230].bump_multiplicity(__i); } let __ret: [G; OUT_230] = unsafe { *(record.function_queries[230].output_at(__i).as_ptr() as *const [G; OUT_230]) }; __ret }, _ => { aiur_fn_230::(__args, __hash, record, io_buffer)? }, } } }; let __v_70: G = __r_arr[0]; if (__v_70 != __v_66) { return Err(ExecError::AssertEqMismatch { lhs: __v_70.as_canonical_u64(), rhs: __v_66.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_71: G = __r_arr[0]; if (__v_71 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_71.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: Some("aggr range: root range does not start at shard 0".to_string()) }); } let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_72: G = __r_arr[0]; if (__v_72 != __v_69) { return Err(ExecError::AssertEqMismatch { lhs: __v_72.as_canonical_u64(), rhs: __v_69.as_canonical_u64(), msg: Some("aggr range: root range does not end at the last shard".to_string()) }); } let __r_arr: [G; OUT_234] = { let __args: [G; IN_234] = [__v_68, __v_34, __v_34]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(234, &__args[..])?) { [G::ZERO; OUT_234] } else { let (__hash, __hit) = record.function_queries[234].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[234].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[234].bump_multiplicity(__i); } let __ret: [G; OUT_234] = unsafe { *(record.function_queries[234].output_at(__i).as_ptr() as *const [G; OUT_234]) }; __ret }, _ => { aiur_fn_234::(__args, __hash, record, io_buffer)? }, } } }; let __v_73: G = __r_arr[0]; if (__v_73 != __v_66) { return Err(ExecError::AssertEqMismatch { lhs: __v_73.as_canonical_u64(), rhs: __v_66.as_canonical_u64(), msg: None }); } let __r_arr: [G; OUT_304] = { let __args: [G; IN_304] = [__v_54, __v_55, __v_56, __v_57, __v_58, __v_59, __v_60, __v_61, __v_62, __v_63, __v_64, __v_65, __v_67, __v_68]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(304, &__args[..])?) { [G::ZERO; OUT_304] } else { let (__hash, __hit) = record.function_queries[304].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[304].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[304].bump_multiplicity(__i); } let __ret: [G; OUT_304] = unsafe { *(record.function_queries[304].output_at(__i).as_ptr() as *const [G; OUT_304]) }; __ret }, _ => { aiur_fn_304::(__args, __hash, record, io_buffer)? }, } } }; let __v_74: G = __r_arr[0]; let __v_75: G = __r_arr[1]; let __v_76: G = __r_arr[2]; let __v_77: G = __r_arr[3]; let __v_78: G = __r_arr[4]; let __r_arr: [G; OUT_226] = { let __args: [G; IN_226] = [__v_52, __v_53, __v_68, __v_74, __v_75, __v_76, __v_77]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(226, &__args[..])?) { [G::ZERO; OUT_226] } else { let (__hash, __hit) = record.function_queries[226].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[226].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[226].bump_multiplicity(__i); } let __ret: [G; OUT_226] = unsafe { *(record.function_queries[226].output_at(__i).as_ptr() as *const [G; OUT_226]) }; __ret }, _ => { aiur_fn_226::(__args, __hash, record, io_buffer)? }, } } }; let __v_79: G = __r_arr[0]; let __v_80: G = __r_arr[1]; let __v_81: G = G::from_bool(__v_79 == G::ZERO); let __v_82: G = G::from_bool(__v_80 == G::ZERO); let __v_83: G = __v_81 * __v_82; if (__v_83 != __v_66) { return Err(ExecError::AssertEqMismatch { lhs: __v_83.as_canonical_u64(), rhs: __v_66.as_canonical_u64(), msg: Some("aggr range: the batch does not balance".to_string()) }); } let __r_arr: [G; OUT_237] = { let __args: [G; IN_237] = [__v_67]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(237, &__args[..])?) { [G::ZERO; OUT_237] } else { let (__hash, __hit) = record.function_queries[237].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[237].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[237].bump_multiplicity(__i); } let __ret: [G; OUT_237] = unsafe { *(record.function_queries[237].output_at(__i).as_ptr() as *const [G; OUT_237]) }; __ret }, _ => { aiur_fn_237::(__args, __hash, record, io_buffer)? }, } } }; let __v_84: G = __r_arr[0]; let __r_arr: [G; OUT_279] = { let __args: [G; IN_279] = [__v_84]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(279, &__args[..])?) { [G::ZERO; OUT_279] } else { let (__hash, __hit) = record.function_queries[279].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[279].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[279].bump_multiplicity(__i); } let __ret: [G; OUT_279] = unsafe { *(record.function_queries[279].output_at(__i).as_ptr() as *const [G; OUT_279]) }; __ret }, _ => { aiur_fn_279::(__args, __hash, record, io_buffer)? }, } } }; let __v_85: G = __r_arr[0]; let __r_arr: [G; OUT_319] = { let __args: [G; IN_319] = [__v_85]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(319, &__args[..])?) { [G::ZERO; OUT_319] } else { let (__hash, __hit) = record.function_queries[319].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[319].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[319].bump_multiplicity(__i); } let __ret: [G; OUT_319] = unsafe { *(record.function_queries[319].output_at(__i).as_ptr() as *const [G; OUT_319]) }; __ret }, _ => { aiur_fn_319::(__args, __hash, record, io_buffer)? }, } } }; let __v_86: G = __r_arr[0]; let __v_87: G = G::from_u64(10); if (__v_86 != __v_87) { return Err(ExecError::AssertEqMismatch { lhs: __v_86.as_canonical_u64(), rhs: __v_87.as_canonical_u64(), msg: Some("aggr range: batch must carry a 10-word verify_claim claim".to_string()) }); } let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_85, __v_34]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __v_88: G = __r_arr[0]; if (__v_88 != __v_34) { return Err(ExecError::AssertEqMismatch { lhs: __v_88.as_canonical_u64(), rhs: __v_34.as_canonical_u64(), msg: Some("aggr range: batch claim has the wrong channel".to_string()) }); } let __r_arr: [G; OUT_276] = { let __args: [G; IN_276] = [__v_85, __v_66]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(276, &__args[..])?) { [G::ZERO; OUT_276] } else { let (__hash, __hit) = record.function_queries[276].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[276].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[276].bump_multiplicity(__i); } let __ret: [G; OUT_276] = unsafe { *(record.function_queries[276].output_at(__i).as_ptr() as *const [G; OUT_276]) }; __ret }, _ => { aiur_fn_276::(__args, __hash, record, io_buffer)? }, } } }; let __v_89: G = __r_arr[0]; if (__v_89 != __v_16) { return Err(ExecError::AssertEqMismatch { lhs: __v_89.as_canonical_u64(), rhs: __v_16.as_canonical_u64(), msg: Some("aggr range: batch claim has the wrong entrypoint".to_string()) }); } let __v_90: G = G::from_u64(2); let __r_arr: [G; OUT_277] = { let __args: [G; IN_277] = [__v_85, __v_90]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(277, &__args[..])?) { [G::ZERO; OUT_277] } else { let (__hash, __hit) = record.function_queries[277].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[277].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[277].bump_multiplicity(__i); } let __ret: [G; OUT_277] = unsafe { *(record.function_queries[277].output_at(__i).as_ptr() as *const [G; OUT_277]) }; __ret }, _ => { aiur_fn_277::(__args, __hash, record, io_buffer)? }, } } }; let __v_91: G = __r_arr[0]; let __v_92: G = __r_arr[1]; let __v_93: G = __r_arr[2]; let __v_94: G = __r_arr[3]; let __v_95: G = __r_arr[4]; let __v_96: G = __r_arr[5]; let __v_97: G = __r_arr[6]; let __v_98: G = __r_arr[7]; let __r_arr: [G; OUT_278] = { let __args: [G; IN_278] = [__v_91, __v_92, __v_93, __v_94, __v_95, __v_96, __v_97, __v_98, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(278, &__args[..])?) { [G::ZERO; OUT_278] } else { let (__hash, __hit) = record.function_queries[278].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[278].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[278].bump_multiplicity(__i); } let __ret: [G; OUT_278] = unsafe { *(record.function_queries[278].output_at(__i).as_ptr() as *const [G; OUT_278]) }; __ret }, _ => { aiur_fn_278::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_310] = []; record.function_queries[310].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }) } +const INPUT_SIZE_311: usize = 16; +const IN_311: usize = 16; +const OUT_311: usize = 0; +fn aiur_fn_311(inp: [G; IN_311], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_311], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __v_15: G = inp[15]; let __v_16: G = G::from_u64(3); let __v_17: G = G::from_u64(0); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_17]; let __info = io_buffer.get_info(__v_16, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_18: G = __io_pair.0; let __v_19: G = __io_pair.1; let __v_20: G = G::from_u64(80); if (__v_19 != __v_20) { return Err(ExecError::AssertEqMismatch { lhs: __v_19.as_canonical_u64(), rhs: __v_20.as_canonical_u64(), msg: Some("aggr: allowed blob must be exactly 80 bytes".to_string()) }); } let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_16, __v_18, __v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_21: G = __r_arr[0]; let __v_22: G = G::from_u64(103); let __v_23: G = G::from_u64(230); let __v_24: G = G::from_u64(9); let __v_25: G = G::from_u64(106); let __v_26: G = G::from_u64(133); let __v_27: G = G::from_u64(174); let __v_28: G = G::from_u64(187); let __v_29: G = G::from_u64(114); let __v_30: G = G::from_u64(243); let __v_31: G = G::from_u64(110); let __v_32: G = G::from_u64(60); let __v_33: G = G::from_u64(58); let __v_34: G = G::from_u64(245); let __v_35: G = G::from_u64(79); let __v_36: G = G::from_u64(165); let __v_37: G = G::from_u64(127); let __v_38: G = G::from_u64(82); let __v_39: G = G::from_u64(14); let __v_40: G = G::from_u64(81); let __v_41: G = G::from_u64(140); let __v_42: G = G::from_u64(104); let __v_43: G = G::from_u64(5); let __v_44: G = G::from_u64(155); let __v_45: G = G::from_u64(171); let __v_46: G = G::from_u64(217); let __v_47: G = G::from_u64(131); let __v_48: G = G::from_u64(31); let __v_49: G = G::from_u64(25); let __v_50: G = G::from_u64(205); let __v_51: G = G::from_u64(224); let __v_52: G = G::from_u64(91); let __v_53: G = G::from_u64(1); let __v_54: G = { let __values: [G; 3] = [__v_53, __v_53, __v_53]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_55: G = { let __values: [G; 8] = [__v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17, __v_17]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_56: G = { let __values: [G; 32] = [__v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_22, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52]; let __mq = record.memory_queries.get_index_mut(9).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_57: G = { let __values: [G; 34] = [__v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53, __v_53]; let __mq = record.memory_queries.get_index_mut(10).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_78] = { let __args: [G; IN_78] = [__v_21, __v_54, __v_17, __v_17, __v_55, __v_56, __v_57]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(78, &__args[..])?) { [G::ZERO; OUT_78] } else { let (__hash, __hit) = record.function_queries[78].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[78].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[78].bump_multiplicity(__i); } let __ret: [G; OUT_78] = unsafe { *(record.function_queries[78].output_at(__i).as_ptr() as *const [G; OUT_78]) }; __ret }, _ => { aiur_fn_78::(__args, __hash, record, io_buffer)? }, } } }; let __v_58: G = __r_arr[0]; let __r_arr: [G; OUT_77] = { let __args: [G; IN_77] = [__v_58]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(77, &__args[..])?) { [G::ZERO; OUT_77] } else { let (__hash, __hit) = record.function_queries[77].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[77].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[77].bump_multiplicity(__i); } let __ret: [G; OUT_77] = unsafe { *(record.function_queries[77].output_at(__i).as_ptr() as *const [G; OUT_77]) }; __ret }, _ => { aiur_fn_77::(__args, __hash, record, io_buffer)? }, } } }; let __v_59: G = __r_arr[0]; let __v_60: G = __r_arr[1]; let __v_61: G = __r_arr[2]; let __v_62: G = __r_arr[3]; let __v_63: G = __r_arr[4]; let __v_64: G = __r_arr[5]; let __v_65: G = __r_arr[6]; let __v_66: G = __r_arr[7]; let __v_67: G = __r_arr[8]; let __v_68: G = __r_arr[9]; let __v_69: G = __r_arr[10]; let __v_70: G = __r_arr[11]; let __v_71: G = __r_arr[12]; let __v_72: G = __r_arr[13]; let __v_73: G = __r_arr[14]; let __v_74: G = __r_arr[15]; let __v_75: G = __r_arr[16]; let __v_76: G = __r_arr[17]; let __v_77: G = __r_arr[18]; let __v_78: G = __r_arr[19]; let __v_79: G = __r_arr[20]; let __v_80: G = __r_arr[21]; let __v_81: G = __r_arr[22]; let __v_82: G = __r_arr[23]; let __v_83: G = __r_arr[24]; let __v_84: G = __r_arr[25]; let __v_85: G = __r_arr[26]; let __v_86: G = __r_arr[27]; let __v_87: G = __r_arr[28]; let __v_88: G = __r_arr[29]; let __v_89: G = __r_arr[30]; let __v_90: G = __r_arr[31]; let __v_91: G = G::from_u64(256); let __v_92: G = __v_91 * __v_60; let __v_93: G = __v_59 + __v_92; let __v_94: G = G::from_u64(65536); let __v_95: G = __v_94 * __v_61; let __v_96: G = __v_93 + __v_95; let __v_97: G = G::from_u64(16777216); let __v_98: G = __v_97 * __v_62; let __v_99: G = __v_96 + __v_98; let __v_100: G = __v_91 * __v_64; let __v_101: G = __v_63 + __v_100; let __v_102: G = __v_94 * __v_65; let __v_103: G = __v_101 + __v_102; let __v_104: G = __v_97 * __v_66; let __v_105: G = __v_103 + __v_104; let __v_106: G = __v_91 * __v_68; let __v_107: G = __v_67 + __v_106; let __v_108: G = __v_94 * __v_69; let __v_109: G = __v_107 + __v_108; let __v_110: G = __v_97 * __v_70; let __v_111: G = __v_109 + __v_110; let __v_112: G = __v_91 * __v_72; let __v_113: G = __v_71 + __v_112; let __v_114: G = __v_94 * __v_73; let __v_115: G = __v_113 + __v_114; let __v_116: G = __v_97 * __v_74; let __v_117: G = __v_115 + __v_116; let __v_118: G = __v_91 * __v_76; let __v_119: G = __v_75 + __v_118; let __v_120: G = __v_94 * __v_77; let __v_121: G = __v_119 + __v_120; let __v_122: G = __v_97 * __v_78; let __v_123: G = __v_121 + __v_122; let __v_124: G = __v_91 * __v_80; let __v_125: G = __v_79 + __v_124; let __v_126: G = __v_94 * __v_81; let __v_127: G = __v_125 + __v_126; let __v_128: G = __v_97 * __v_82; let __v_129: G = __v_127 + __v_128; let __v_130: G = __v_91 * __v_84; let __v_131: G = __v_83 + __v_130; let __v_132: G = __v_94 * __v_85; let __v_133: G = __v_131 + __v_132; let __v_134: G = __v_97 * __v_86; let __v_135: G = __v_133 + __v_134; let __v_136: G = __v_91 * __v_88; let __v_137: G = __v_87 + __v_136; let __v_138: G = __v_94 * __v_89; let __v_139: G = __v_137 + __v_138; let __v_140: G = __v_97 * __v_90; let __v_141: G = __v_139 + __v_140; if (__v_99 != __v_0) { return Err(ExecError::AssertEqMismatch { lhs: __v_99.as_canonical_u64(), rhs: __v_0.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } if (__v_105 != __v_1) { return Err(ExecError::AssertEqMismatch { lhs: __v_105.as_canonical_u64(), rhs: __v_1.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } if (__v_111 != __v_2) { return Err(ExecError::AssertEqMismatch { lhs: __v_111.as_canonical_u64(), rhs: __v_2.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } if (__v_117 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_117.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } if (__v_123 != __v_4) { return Err(ExecError::AssertEqMismatch { lhs: __v_123.as_canonical_u64(), rhs: __v_4.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } if (__v_129 != __v_5) { return Err(ExecError::AssertEqMismatch { lhs: __v_129.as_canonical_u64(), rhs: __v_5.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } if (__v_135 != __v_6) { return Err(ExecError::AssertEqMismatch { lhs: __v_135.as_canonical_u64(), rhs: __v_6.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } if (__v_141 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_141.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("aggr: allowed blob digest mismatch".to_string()) }); } let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_21]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_142: G = __r_arr[0]; let __v_143: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_143]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_144: G = __r_arr[0]; let __v_145: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_145]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_146: G = __r_arr[0]; let __v_147: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_147]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_148: G = __r_arr[0]; let __v_149: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_149]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_150: G = __r_arr[0]; let __v_151: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_151]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_152: G = __r_arr[0]; let __v_153: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_153]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_154: G = __r_arr[0]; let __v_155: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_155]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_156: G = __r_arr[0]; let __v_157: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_157]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_158: G = __r_arr[0]; let __v_159: G = __r_arr[1]; let __r_arr: [G; OUT_244] = { let __args: [G; IN_244] = [__v_159]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(244, &__args[..])?) { [G::ZERO; OUT_244] } else { let (__hash, __hit) = record.function_queries[244].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[244].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[244].bump_multiplicity(__i); } let __ret: [G; OUT_244] = unsafe { *(record.function_queries[244].output_at(__i).as_ptr() as *const [G; OUT_244]) }; __ret }, _ => { aiur_fn_244::(__args, __hash, record, io_buffer)? }, } } }; let __v_160: G = __r_arr[0]; let __v_161: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_161]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_162: G = __r_arr[0]; let __v_163: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_163]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_164: G = __r_arr[0]; let __v_165: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_165]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_166: G = __r_arr[0]; let __v_167: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_167]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_168: G = __r_arr[0]; let __v_169: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_169]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_170: G = __r_arr[0]; let __v_171: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_171]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_172: G = __r_arr[0]; let __v_173: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_173]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_174: G = __r_arr[0]; let __v_175: G = __r_arr[1]; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_175]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_176: G = __r_arr[0]; let __v_177: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_177.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_178: G = __loaded[0]; let __v_179: G = __loaded[1]; let __v_180: G = __loaded[2]; if (__v_178 != __v_53) { return Err(ExecError::AssertEqMismatch { lhs: __v_178.as_canonical_u64(), rhs: __v_53.as_canonical_u64(), msg: Some("aggr: allowed blob must be exactly 80 bytes".to_string()) }); } if (__v_179 != __v_53) { return Err(ExecError::AssertEqMismatch { lhs: __v_179.as_canonical_u64(), rhs: __v_53.as_canonical_u64(), msg: Some("aggr: allowed blob must be exactly 80 bytes".to_string()) }); } if (__v_180 != __v_53) { return Err(ExecError::AssertEqMismatch { lhs: __v_180.as_canonical_u64(), rhs: __v_53.as_canonical_u64(), msg: Some("aggr: allowed blob must be exactly 80 bytes".to_string()) }); } let __r_arr: [G; OUT_246] = { let __args: [G; IN_246] = [__v_142]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(246, &__args[..])?) { [G::ZERO; OUT_246] } else { let (__hash, __hit) = record.function_queries[246].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[246].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[246].bump_multiplicity(__i); } let __ret: [G; OUT_246] = unsafe { *(record.function_queries[246].output_at(__i).as_ptr() as *const [G; OUT_246]) }; __ret }, _ => { aiur_fn_246::(__args, __hash, record, io_buffer)? }, } } }; let __v_181: G = __r_arr[0]; let __v_182: G = __r_arr[1]; let __v_183: G = __r_arr[2]; let __v_184: G = __r_arr[3]; let __v_185: G = __r_arr[4]; let __v_186: G = __r_arr[5]; let __v_187: G = __r_arr[6]; let __v_188: G = __r_arr[7]; let __r_arr: [G; OUT_246] = { let __args: [G; IN_246] = [__v_160]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(246, &__args[..])?) { [G::ZERO; OUT_246] } else { let (__hash, __hit) = record.function_queries[246].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[246].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[246].bump_multiplicity(__i); } let __ret: [G; OUT_246] = unsafe { *(record.function_queries[246].output_at(__i).as_ptr() as *const [G; OUT_246]) }; __ret }, _ => { aiur_fn_246::(__args, __hash, record, io_buffer)? }, } } }; let __v_189: G = __r_arr[0]; let __v_190: G = __r_arr[1]; let __v_191: G = __r_arr[2]; let __v_192: G = __r_arr[3]; let __v_193: G = __r_arr[4]; let __v_194: G = __r_arr[5]; let __v_195: G = __r_arr[6]; let __v_196: G = __r_arr[7]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_144, __v_146, __v_148, __v_150, __v_152, __v_154, __v_156, __v_158]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_197: G = __r_arr[0]; let __r_arr: [G; OUT_14] = { let __args: [G; IN_14] = [__v_162, __v_164, __v_166, __v_168, __v_170, __v_172, __v_174, __v_176]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(14, &__args[..])?) { [G::ZERO; OUT_14] } else { let (__hash, __hit) = record.function_queries[14].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[14].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[14].bump_multiplicity(__i); } let __ret: [G; OUT_14] = unsafe { *(record.function_queries[14].output_at(__i).as_ptr() as *const [G; OUT_14]) }; __ret }, _ => { aiur_fn_14::(__args, __hash, record, io_buffer)? }, } } }; let __v_198: G = __r_arr[0]; let __v_199: G = G::from_u64(6); let __io_pair: (G, G) = { let __key: [G; 1] = [__v_17]; let __info = io_buffer.get_info(__v_199, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_200: G = __io_pair.0; let __v_201: G = __io_pair.1; if (__v_201 != __v_53) { return Err(ExecError::AssertEqMismatch { lhs: __v_201.as_canonical_u64(), rhs: __v_53.as_canonical_u64(), msg: Some("aggr: shape hint must be exactly one byte".to_string()) }); } let __r_arr: [G; OUT_3] = { let __args: [G; IN_3] = [__v_199, __v_200, __v_201]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(3, &__args[..])?) { [G::ZERO; OUT_3] } else { let (__hash, __hit) = record.function_queries[3].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[3].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[3].bump_multiplicity(__i); } let __ret: [G; OUT_3] = unsafe { *(record.function_queries[3].output_at(__i).as_ptr() as *const [G; OUT_3]) }; __ret }, _ => { aiur_fn_3::(__args, __hash, record, io_buffer)? }, } } }; let __v_202: G = __r_arr[0]; let __r_arr: [G; OUT_90] = { let __args: [G; IN_90] = [__v_202]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(90, &__args[..])?) { [G::ZERO; OUT_90] } else { let (__hash, __hit) = record.function_queries[90].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[90].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[90].bump_multiplicity(__i); } let __ret: [G; OUT_90] = unsafe { *(record.function_queries[90].output_at(__i).as_ptr() as *const [G; OUT_90]) }; __ret }, _ => { aiur_fn_90::(__args, __hash, record, io_buffer)? }, } } }; let __v_203: G = __r_arr[0]; let __v_204: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_204.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_205: G = __loaded[0]; let __v_206: G = __loaded[1]; let __v_207: G = __loaded[2]; if (__v_205 != __v_53) { return Err(ExecError::AssertEqMismatch { lhs: __v_205.as_canonical_u64(), rhs: __v_53.as_canonical_u64(), msg: Some("aggr: trailing bytes after shape hint".to_string()) }); } if (__v_206 != __v_53) { return Err(ExecError::AssertEqMismatch { lhs: __v_206.as_canonical_u64(), rhs: __v_53.as_canonical_u64(), msg: Some("aggr: trailing bytes after shape hint".to_string()) }); } if (__v_207 != __v_53) { return Err(ExecError::AssertEqMismatch { lhs: __v_207.as_canonical_u64(), rhs: __v_53.as_canonical_u64(), msg: Some("aggr: trailing bytes after shape hint".to_string()) }); } match __v_203.as_canonical_u64() { 0u64 => { let __v_208: G = G::from_u64(0); let __r_arr: [G; OUT_283] = { let __args: [G; IN_283] = [__v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(283, &__args[..])?) { [G::ZERO; OUT_283] } else { let (__hash, __hit) = record.function_queries[283].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[283].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[283].bump_multiplicity(__i); } let __ret: [G; OUT_283] = unsafe { *(record.function_queries[283].output_at(__i).as_ptr() as *const [G; OUT_283]) }; __ret }, _ => { aiur_fn_283::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 1u64 => { let __v_208: G = G::from_u64(1); let __r_arr: [G; OUT_283] = { let __args: [G; IN_283] = [__v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(283, &__args[..])?) { [G::ZERO; OUT_283] } else { let (__hash, __hit) = record.function_queries[283].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[283].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[283].bump_multiplicity(__i); } let __ret: [G; OUT_283] = unsafe { *(record.function_queries[283].output_at(__i).as_ptr() as *const [G; OUT_283]) }; __ret }, _ => { aiur_fn_283::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 2u64 => { let __v_208: G = G::from_u64(0); let __r_arr: [G; OUT_284] = { let __args: [G; IN_284] = [__v_208, __v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(284, &__args[..])?) { [G::ZERO; OUT_284] } else { let (__hash, __hit) = record.function_queries[284].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[284].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[284].bump_multiplicity(__i); } let __ret: [G; OUT_284] = unsafe { *(record.function_queries[284].output_at(__i).as_ptr() as *const [G; OUT_284]) }; __ret }, _ => { aiur_fn_284::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 3u64 => { let __v_208: G = G::from_u64(0); let __v_209: G = G::from_u64(1); let __r_arr: [G; OUT_284] = { let __args: [G; IN_284] = [__v_208, __v_209, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(284, &__args[..])?) { [G::ZERO; OUT_284] } else { let (__hash, __hit) = record.function_queries[284].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[284].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[284].bump_multiplicity(__i); } let __ret: [G; OUT_284] = unsafe { *(record.function_queries[284].output_at(__i).as_ptr() as *const [G; OUT_284]) }; __ret }, _ => { aiur_fn_284::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 4u64 => { let __v_208: G = G::from_u64(1); let __v_209: G = G::from_u64(0); let __r_arr: [G; OUT_284] = { let __args: [G; IN_284] = [__v_208, __v_209, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(284, &__args[..])?) { [G::ZERO; OUT_284] } else { let (__hash, __hit) = record.function_queries[284].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[284].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[284].bump_multiplicity(__i); } let __ret: [G; OUT_284] = unsafe { *(record.function_queries[284].output_at(__i).as_ptr() as *const [G; OUT_284]) }; __ret }, _ => { aiur_fn_284::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 5u64 => { let __v_208: G = G::from_u64(1); let __r_arr: [G; OUT_284] = { let __args: [G; IN_284] = [__v_208, __v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(284, &__args[..])?) { [G::ZERO; OUT_284] } else { let (__hash, __hit) = record.function_queries[284].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[284].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[284].bump_multiplicity(__i); } let __ret: [G; OUT_284] = unsafe { *(record.function_queries[284].output_at(__i).as_ptr() as *const [G; OUT_284]) }; __ret }, _ => { aiur_fn_284::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 6u64 => { let __v_208: G = G::from_u64(0); let __r_arr: [G; OUT_285] = { let __args: [G; IN_285] = [__v_208, __v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(285, &__args[..])?) { [G::ZERO; OUT_285] } else { let (__hash, __hit) = record.function_queries[285].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[285].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[285].bump_multiplicity(__i); } let __ret: [G; OUT_285] = unsafe { *(record.function_queries[285].output_at(__i).as_ptr() as *const [G; OUT_285]) }; __ret }, _ => { aiur_fn_285::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 7u64 => { let __v_208: G = G::from_u64(0); let __v_209: G = G::from_u64(1); let __r_arr: [G; OUT_285] = { let __args: [G; IN_285] = [__v_208, __v_209, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(285, &__args[..])?) { [G::ZERO; OUT_285] } else { let (__hash, __hit) = record.function_queries[285].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[285].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[285].bump_multiplicity(__i); } let __ret: [G; OUT_285] = unsafe { *(record.function_queries[285].output_at(__i).as_ptr() as *const [G; OUT_285]) }; __ret }, _ => { aiur_fn_285::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 8u64 => { let __v_208: G = G::from_u64(1); let __v_209: G = G::from_u64(0); let __r_arr: [G; OUT_285] = { let __args: [G; IN_285] = [__v_208, __v_209, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(285, &__args[..])?) { [G::ZERO; OUT_285] } else { let (__hash, __hit) = record.function_queries[285].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[285].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[285].bump_multiplicity(__i); } let __ret: [G; OUT_285] = unsafe { *(record.function_queries[285].output_at(__i).as_ptr() as *const [G; OUT_285]) }; __ret }, _ => { aiur_fn_285::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 9u64 => { let __v_208: G = G::from_u64(1); let __r_arr: [G; OUT_285] = { let __args: [G; IN_285] = [__v_208, __v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(285, &__args[..])?) { [G::ZERO; OUT_285] } else { let (__hash, __hit) = record.function_queries[285].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[285].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[285].bump_multiplicity(__i); } let __ret: [G; OUT_285] = unsafe { *(record.function_queries[285].output_at(__i).as_ptr() as *const [G; OUT_285]) }; __ret }, _ => { aiur_fn_285::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 10u64 => { let __r_arr: [G; OUT_308] = { let __args: [G; IN_308] = [__v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(308, &__args[..])?) { [G::ZERO; OUT_308] } else { let (__hash, __hit) = record.function_queries[308].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[308].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[308].bump_multiplicity(__i); } let __ret: [G; OUT_308] = unsafe { *(record.function_queries[308].output_at(__i).as_ptr() as *const [G; OUT_308]) }; __ret }, _ => { aiur_fn_308::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 11u64 => { let __r_arr: [G; OUT_309] = { let __args: [G; IN_309] = [__v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(309, &__args[..])?) { [G::ZERO; OUT_309] } else { let (__hash, __hit) = record.function_queries[309].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[309].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[309].bump_multiplicity(__i); } let __ret: [G; OUT_309] = unsafe { *(record.function_queries[309].output_at(__i).as_ptr() as *const [G; OUT_309]) }; __ret }, _ => { aiur_fn_309::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 12u64 => { let __r_arr: [G; OUT_310] = { let __args: [G; IN_310] = [__v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(310, &__args[..])?) { [G::ZERO; OUT_310] } else { let (__hash, __hit) = record.function_queries[310].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[310].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[310].bump_multiplicity(__i); } let __ret: [G; OUT_310] = unsafe { *(record.function_queries[310].output_at(__i).as_ptr() as *const [G; OUT_310]) }; __ret }, _ => { aiur_fn_310::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 13u64 => { let __v_208: G = G::from_u64(0); let __r_arr: [G; OUT_286] = { let __args: [G; IN_286] = [__v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(286, &__args[..])?) { [G::ZERO; OUT_286] } else { let (__hash, __hit) = record.function_queries[286].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[286].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[286].bump_multiplicity(__i); } let __ret: [G; OUT_286] = unsafe { *(record.function_queries[286].output_at(__i).as_ptr() as *const [G; OUT_286]) }; __ret }, _ => { aiur_fn_286::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 14u64 => { let __v_208: G = G::from_u64(1); let __r_arr: [G; OUT_286] = { let __args: [G; IN_286] = [__v_208, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(286, &__args[..])?) { [G::ZERO; OUT_286] } else { let (__hash, __hit) = record.function_queries[286].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[286].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[286].bump_multiplicity(__i); } let __ret: [G; OUT_286] = unsafe { *(record.function_queries[286].output_at(__i).as_ptr() as *const [G; OUT_286]) }; __ret }, _ => { aiur_fn_286::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 15u64 => { let __r_arr: [G; OUT_292] = { let __args: [G; IN_292] = [__v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(292, &__args[..])?) { [G::ZERO; OUT_292] } else { let (__hash, __hit) = record.function_queries[292].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[292].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[292].bump_multiplicity(__i); } let __ret: [G; OUT_292] = unsafe { *(record.function_queries[292].output_at(__i).as_ptr() as *const [G; OUT_292]) }; __ret }, _ => { aiur_fn_292::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 16u64 => { let __r_arr: [G; OUT_294] = { let __args: [G; IN_294] = [__v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_15]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(294, &__args[..])?) { [G::ZERO; OUT_294] } else { let (__hash, __hit) = record.function_queries[294].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[294].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[294].bump_multiplicity(__i); } let __ret: [G; OUT_294] = unsafe { *(record.function_queries[294].output_at(__i).as_ptr() as *const [G; OUT_294]) }; __ret }, _ => { aiur_fn_294::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_311] = []; record.function_queries[311].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_203.as_canonical_u64())); }, } }) } +const INPUT_SIZE_312: usize = 2; +const IN_312: usize = 2; const OUT_312: usize = 1; -fn aiur_fn_312(inp: [G; IN_312], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_312], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; match __v_1.as_canonical_u64() { 1u64 => { let __v_4: G = G::from_u64(0); let __ret: [G; OUT_312] = [__v_4]; record.function_queries[312].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_312] = { let __args: [G; IN_312] = [__v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(312, &__args[..])?) { [G::ZERO; OUT_312] } else { let (__hash, __hit) = record.function_queries[312].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[312].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[312].bump_multiplicity(__i); } let __ret: [G; OUT_312] = unsafe { *(record.function_queries[312].output_at(__i).as_ptr() as *const [G; OUT_312]) }; __ret }, _ => { aiur_fn_312::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = G::from_u64(1); let __v_6: G = __v_4 + __v_5; let __ret: [G; OUT_312] = [__v_6]; record.function_queries[312].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } +fn aiur_fn_312(inp: [G; IN_312], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_312], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_326] = { let __args: [G; IN_326] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(326, &__args[..])?) { [G::ZERO; OUT_326] } else { let (__hash, __hit) = record.function_queries[326].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[326].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[326].bump_multiplicity(__i); } let __ret: [G; OUT_326] = unsafe { *(record.function_queries[326].output_at(__i).as_ptr() as *const [G; OUT_326]) }; __ret }, _ => { aiur_fn_326::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_2.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_3: G = __loaded[0]; let __v_4: G = __loaded[1]; let __v_5: G = __loaded[2]; match __v_3.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_312] = [__v_4]; record.function_queries[312].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_3.as_canonical_u64())); }, } }) } const INPUT_SIZE_313: usize = 1; const IN_313: usize = 1; const OUT_313: usize = 1; -fn aiur_fn_313(inp: [G; IN_313], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_313], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; match __v_1.as_canonical_u64() { 1u64 => { let __v_5: G = G::from_u64(0); let __ret: [G; OUT_313] = [__v_5]; record.function_queries[313].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = G::from_u64(1); let __v_7: G = __v_5 + __v_6; let __ret: [G; OUT_313] = [__v_7]; record.function_queries[313].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } -const INPUT_SIZE_314: usize = 2; -const IN_314: usize = 2; +fn aiur_fn_313(inp: [G; IN_313], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_313], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 26] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(8).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(26))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 26 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 26] = __args[..26].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; let __v_6: G = __loaded[5]; let __v_7: G = __loaded[6]; let __v_8: G = __loaded[7]; let __v_9: G = __loaded[8]; let __v_10: G = __loaded[9]; let __v_11: G = __loaded[10]; let __v_12: G = __loaded[11]; let __v_13: G = __loaded[12]; let __v_14: G = __loaded[13]; let __v_15: G = __loaded[14]; let __v_16: G = __loaded[15]; let __v_17: G = __loaded[16]; let __v_18: G = __loaded[17]; let __v_19: G = __loaded[18]; let __v_20: G = __loaded[19]; let __v_21: G = __loaded[20]; let __v_22: G = __loaded[21]; let __v_23: G = __loaded[22]; let __v_24: G = __loaded[23]; let __v_25: G = __loaded[24]; let __v_26: G = __loaded[25]; match __v_1.as_canonical_u64() { 1u64 => { let __v_27: G = G::from_u64(0); let __ret: [G; OUT_313] = [__v_27]; record.function_queries[313].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_313] = { let __args: [G; IN_313] = [__v_26]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(313, &__args[..])?) { [G::ZERO; OUT_313] } else { let (__hash, __hit) = record.function_queries[313].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[313].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[313].bump_multiplicity(__i); } let __ret: [G; OUT_313] = unsafe { *(record.function_queries[313].output_at(__i).as_ptr() as *const [G; OUT_313]) }; __ret }, _ => { aiur_fn_313::(__args, __hash, record, io_buffer)? }, } } }; let __v_27: G = __r_arr[0]; let __v_28: G = G::from_u64(1); let __v_29: G = __v_27 + __v_28; let __ret: [G; OUT_313] = [__v_29]; record.function_queries[313].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } +const INPUT_SIZE_314: usize = 1; +const IN_314: usize = 1; const OUT_314: usize = 1; -fn aiur_fn_314(inp: [G; IN_314], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_314], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; match __v_2.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_314] = [__v_1]; record.function_queries[314].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_5: G = G::from_u64(0); let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_4, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = { let __values: [G; 3] = [__v_5, __v_3, __v_6]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_314] = [__v_7]; record.function_queries[314].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } +fn aiur_fn_314(inp: [G; IN_314], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_314], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; match __v_1.as_canonical_u64() { 1u64 => { let __v_4: G = G::from_u64(0); let __ret: [G; OUT_314] = [__v_4]; record.function_queries[314].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_314] = { let __args: [G; IN_314] = [__v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(314, &__args[..])?) { [G::ZERO; OUT_314] } else { let (__hash, __hit) = record.function_queries[314].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[314].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[314].bump_multiplicity(__i); } let __ret: [G; OUT_314] = unsafe { *(record.function_queries[314].output_at(__i).as_ptr() as *const [G; OUT_314]) }; __ret }, _ => { aiur_fn_314::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = G::from_u64(1); let __v_6: G = __v_4 + __v_5; let __ret: [G; OUT_314] = [__v_6]; record.function_queries[314].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } const INPUT_SIZE_315: usize = 1; const IN_315: usize = 1; const OUT_315: usize = 1; -fn aiur_fn_315(inp: [G; IN_315], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_315], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 6] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 6 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 6] = __args[..6].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; let __v_6: G = __loaded[5]; match __v_1.as_canonical_u64() { 1u64 => { let __v_7: G = G::from_u64(0); let __ret: [G; OUT_315] = [__v_7]; record.function_queries[315].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = G::from_u64(1); let __v_9: G = __v_7 + __v_8; let __ret: [G; OUT_315] = [__v_9]; record.function_queries[315].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } -const INPUT_SIZE_316: usize = 1; -const IN_316: usize = 1; +fn aiur_fn_315(inp: [G; IN_315], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_315], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; match __v_1.as_canonical_u64() { 1u64 => { let __v_5: G = G::from_u64(0); let __ret: [G; OUT_315] = [__v_5]; record.function_queries[315].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_315] = { let __args: [G; IN_315] = [__v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(315, &__args[..])?) { [G::ZERO; OUT_315] } else { let (__hash, __hit) = record.function_queries[315].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[315].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[315].bump_multiplicity(__i); } let __ret: [G; OUT_315] = unsafe { *(record.function_queries[315].output_at(__i).as_ptr() as *const [G; OUT_315]) }; __ret }, _ => { aiur_fn_315::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = G::from_u64(1); let __v_7: G = __v_5 + __v_6; let __ret: [G; OUT_315] = [__v_7]; record.function_queries[315].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } +const INPUT_SIZE_316: usize = 2; +const IN_316: usize = 2; const OUT_316: usize = 1; -fn aiur_fn_316(inp: [G; IN_316], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_316], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 7] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(4).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(7))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 7 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 7] = __args[..7].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; let __v_6: G = __loaded[5]; let __v_7: G = __loaded[6]; match __v_1.as_canonical_u64() { 1u64 => { let __v_8: G = G::from_u64(0); let __ret: [G; OUT_316] = [__v_8]; record.function_queries[316].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_316] = { let __args: [G; IN_316] = [__v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(316, &__args[..])?) { [G::ZERO; OUT_316] } else { let (__hash, __hit) = record.function_queries[316].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[316].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[316].bump_multiplicity(__i); } let __ret: [G; OUT_316] = unsafe { *(record.function_queries[316].output_at(__i).as_ptr() as *const [G; OUT_316]) }; __ret }, _ => { aiur_fn_316::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __v_9: G = G::from_u64(1); let __v_10: G = __v_8 + __v_9; let __ret: [G; OUT_316] = [__v_10]; record.function_queries[316].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } +fn aiur_fn_316(inp: [G; IN_316], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_316], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; match __v_2.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_316] = [__v_1]; record.function_queries[316].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_5: G = G::from_u64(0); let __r_arr: [G; OUT_316] = { let __args: [G; IN_316] = [__v_4, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(316, &__args[..])?) { [G::ZERO; OUT_316] } else { let (__hash, __hit) = record.function_queries[316].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[316].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[316].bump_multiplicity(__i); } let __ret: [G; OUT_316] = unsafe { *(record.function_queries[316].output_at(__i).as_ptr() as *const [G; OUT_316]) }; __ret }, _ => { aiur_fn_316::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = { let __values: [G; 3] = [__v_5, __v_3, __v_6]; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_316] = [__v_7]; record.function_queries[316].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } const INPUT_SIZE_317: usize = 1; const IN_317: usize = 1; const OUT_317: usize = 1; -fn aiur_fn_317(inp: [G; IN_317], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_317], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 10] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 10 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 10] = __args[..10].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; let __v_6: G = __loaded[5]; let __v_7: G = __loaded[6]; let __v_8: G = __loaded[7]; let __v_9: G = __loaded[8]; let __v_10: G = __loaded[9]; match __v_1.as_canonical_u64() { 1u64 => { let __v_11: G = G::from_u64(0); let __ret: [G; OUT_317] = [__v_11]; record.function_queries[317].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_317] = { let __args: [G; IN_317] = [__v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(317, &__args[..])?) { [G::ZERO; OUT_317] } else { let (__hash, __hit) = record.function_queries[317].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[317].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[317].bump_multiplicity(__i); } let __ret: [G; OUT_317] = unsafe { *(record.function_queries[317].output_at(__i).as_ptr() as *const [G; OUT_317]) }; __ret }, _ => { aiur_fn_317::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_u64(1); let __v_13: G = __v_11 + __v_12; let __ret: [G; OUT_317] = [__v_13]; record.function_queries[317].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } +fn aiur_fn_317(inp: [G; IN_317], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_317], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 6] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 6 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 6] = __args[..6].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; let __v_6: G = __loaded[5]; match __v_1.as_canonical_u64() { 1u64 => { let __v_7: G = G::from_u64(0); let __ret: [G; OUT_317] = [__v_7]; record.function_queries[317].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_317] = { let __args: [G; IN_317] = [__v_6]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(317, &__args[..])?) { [G::ZERO; OUT_317] } else { let (__hash, __hit) = record.function_queries[317].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[317].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[317].bump_multiplicity(__i); } let __ret: [G; OUT_317] = unsafe { *(record.function_queries[317].output_at(__i).as_ptr() as *const [G; OUT_317]) }; __ret }, _ => { aiur_fn_317::(__args, __hash, record, io_buffer)? }, } } }; let __v_7: G = __r_arr[0]; let __v_8: G = G::from_u64(1); let __v_9: G = __v_7 + __v_8; let __ret: [G; OUT_317] = [__v_9]; record.function_queries[317].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } const INPUT_SIZE_318: usize = 1; const IN_318: usize = 1; const OUT_318: usize = 1; -fn aiur_fn_318(inp: [G; IN_318], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_318], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 11] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(7).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(11))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 11 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 11] = __args[..11].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; let __v_6: G = __loaded[5]; let __v_7: G = __loaded[6]; let __v_8: G = __loaded[7]; let __v_9: G = __loaded[8]; let __v_10: G = __loaded[9]; let __v_11: G = __loaded[10]; match __v_1.as_canonical_u64() { 1u64 => { let __v_12: G = G::from_u64(0); let __ret: [G; OUT_318] = [__v_12]; record.function_queries[318].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_318] = { let __args: [G; IN_318] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(318, &__args[..])?) { [G::ZERO; OUT_318] } else { let (__hash, __hit) = record.function_queries[318].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[318].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[318].bump_multiplicity(__i); } let __ret: [G; OUT_318] = unsafe { *(record.function_queries[318].output_at(__i).as_ptr() as *const [G; OUT_318]) }; __ret }, _ => { aiur_fn_318::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __v_13: G = G::from_u64(1); let __v_14: G = __v_12 + __v_13; let __ret: [G; OUT_318] = [__v_14]; record.function_queries[318].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } -const INPUT_SIZE_319: usize = 2; -const IN_319: usize = 2; -const OUT_319: usize = 4; -fn aiur_fn_319(inp: [G; IN_319], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_319], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_325] = { let __args: [G; IN_325] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(325, &__args[..])?) { [G::ZERO; OUT_325] } else { let (__hash, __hit) = record.function_queries[325].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[325].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[325].bump_multiplicity(__i); } let __ret: [G; OUT_325] = unsafe { *(record.function_queries[325].output_at(__i).as_ptr() as *const [G; OUT_325]) }; __ret }, _ => { aiur_fn_325::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __loaded: [G; 6] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let __ptr_u64 = __v_2.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 6 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 6] = __args[..6].try_into().unwrap(); __arr }; let __v_3: G = __loaded[0]; let __v_4: G = __loaded[1]; let __v_5: G = __loaded[2]; let __v_6: G = __loaded[3]; let __v_7: G = __loaded[4]; let __v_8: G = __loaded[5]; match __v_3.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_319] = [__v_4, __v_5, __v_6, __v_7]; record.function_queries[319].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_3.as_canonical_u64())); }, } }) } +fn aiur_fn_318(inp: [G; IN_318], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_318], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 7] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(4).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(7))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 7 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 7] = __args[..7].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; let __v_6: G = __loaded[5]; let __v_7: G = __loaded[6]; match __v_1.as_canonical_u64() { 1u64 => { let __v_8: G = G::from_u64(0); let __ret: [G; OUT_318] = [__v_8]; record.function_queries[318].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_318] = { let __args: [G; IN_318] = [__v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(318, &__args[..])?) { [G::ZERO; OUT_318] } else { let (__hash, __hit) = record.function_queries[318].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[318].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[318].bump_multiplicity(__i); } let __ret: [G; OUT_318] = unsafe { *(record.function_queries[318].output_at(__i).as_ptr() as *const [G; OUT_318]) }; __ret }, _ => { aiur_fn_318::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __v_9: G = G::from_u64(1); let __v_10: G = __v_8 + __v_9; let __ret: [G; OUT_318] = [__v_10]; record.function_queries[318].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } +const INPUT_SIZE_319: usize = 1; +const IN_319: usize = 1; +const OUT_319: usize = 1; +fn aiur_fn_319(inp: [G; IN_319], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_319], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 10] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 10 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 10] = __args[..10].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; let __v_6: G = __loaded[5]; let __v_7: G = __loaded[6]; let __v_8: G = __loaded[7]; let __v_9: G = __loaded[8]; let __v_10: G = __loaded[9]; match __v_1.as_canonical_u64() { 1u64 => { let __v_11: G = G::from_u64(0); let __ret: [G; OUT_319] = [__v_11]; record.function_queries[319].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_319] = { let __args: [G; IN_319] = [__v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(319, &__args[..])?) { [G::ZERO; OUT_319] } else { let (__hash, __hit) = record.function_queries[319].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[319].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[319].bump_multiplicity(__i); } let __ret: [G; OUT_319] = unsafe { *(record.function_queries[319].output_at(__i).as_ptr() as *const [G; OUT_319]) }; __ret }, _ => { aiur_fn_319::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_u64(1); let __v_13: G = __v_11 + __v_12; let __ret: [G; OUT_319] = [__v_13]; record.function_queries[319].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } const INPUT_SIZE_320: usize = 1; const IN_320: usize = 1; const OUT_320: usize = 1; -fn aiur_fn_320(inp: [G; IN_320], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_320], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; match __v_1.as_canonical_u64() { 1u64 => { let __v_4: G = G::from_u64(1); let __ret: [G; OUT_320] = [__v_4]; record.function_queries[320].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_4: G = G::from_u64(0); let __ret: [G; OUT_320] = [__v_4]; record.function_queries[320].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } +fn aiur_fn_320(inp: [G; IN_320], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_320], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 11] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(7).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(11))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 11 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 11] = __args[..11].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; let __v_6: G = __loaded[5]; let __v_7: G = __loaded[6]; let __v_8: G = __loaded[7]; let __v_9: G = __loaded[8]; let __v_10: G = __loaded[9]; let __v_11: G = __loaded[10]; match __v_1.as_canonical_u64() { 1u64 => { let __v_12: G = G::from_u64(0); let __ret: [G; OUT_320] = [__v_12]; record.function_queries[320].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_320] = { let __args: [G; IN_320] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(320, &__args[..])?) { [G::ZERO; OUT_320] } else { let (__hash, __hit) = record.function_queries[320].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[320].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[320].bump_multiplicity(__i); } let __ret: [G; OUT_320] = unsafe { *(record.function_queries[320].output_at(__i).as_ptr() as *const [G; OUT_320]) }; __ret }, _ => { aiur_fn_320::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __v_13: G = G::from_u64(1); let __v_14: G = __v_12 + __v_13; let __ret: [G; OUT_320] = [__v_14]; record.function_queries[320].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } const INPUT_SIZE_321: usize = 2; const IN_321: usize = 2; -const OUT_321: usize = 2; -fn aiur_fn_321(inp: [G; IN_321], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_321], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_327] = { let __args: [G; IN_327] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(327, &__args[..])?) { [G::ZERO; OUT_327] } else { let (__hash, __hit) = record.function_queries[327].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[327].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[327].bump_multiplicity(__i); } let __ret: [G; OUT_327] = unsafe { *(record.function_queries[327].output_at(__i).as_ptr() as *const [G; OUT_327]) }; __ret }, _ => { aiur_fn_327::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_2.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_3: G = __loaded[0]; let __v_4: G = __loaded[1]; let __v_5: G = __loaded[2]; let __v_6: G = __loaded[3]; match __v_3.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_321] = [__v_4, __v_5]; record.function_queries[321].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_3.as_canonical_u64())); }, } }) } -const INPUT_SIZE_322: usize = 2; -const IN_322: usize = 2; +const OUT_321: usize = 4; +fn aiur_fn_321(inp: [G; IN_321], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_321], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_327] = { let __args: [G; IN_327] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(327, &__args[..])?) { [G::ZERO; OUT_327] } else { let (__hash, __hit) = record.function_queries[327].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[327].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[327].bump_multiplicity(__i); } let __ret: [G; OUT_327] = unsafe { *(record.function_queries[327].output_at(__i).as_ptr() as *const [G; OUT_327]) }; __ret }, _ => { aiur_fn_327::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __loaded: [G; 6] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let __ptr_u64 = __v_2.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 6 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 6] = __args[..6].try_into().unwrap(); __arr }; let __v_3: G = __loaded[0]; let __v_4: G = __loaded[1]; let __v_5: G = __loaded[2]; let __v_6: G = __loaded[3]; let __v_7: G = __loaded[4]; let __v_8: G = __loaded[5]; match __v_3.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_321] = [__v_4, __v_5, __v_6, __v_7]; record.function_queries[321].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_3.as_canonical_u64())); }, } }) } +const INPUT_SIZE_322: usize = 1; +const IN_322: usize = 1; const OUT_322: usize = 1; -fn aiur_fn_322(inp: [G; IN_322], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_322], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 10] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 10 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 10] = __args[..10].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; let __v_5: G = __loaded[3]; let __v_6: G = __loaded[4]; let __v_7: G = __loaded[5]; let __v_8: G = __loaded[6]; let __v_9: G = __loaded[7]; let __v_10: G = __loaded[8]; let __v_11: G = __loaded[9]; match __v_2.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_322] = [__v_1]; record.function_queries[322].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_12: G = G::from_u64(0); let __r_arr: [G; OUT_322] = { let __args: [G; IN_322] = [__v_11, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(322, &__args[..])?) { [G::ZERO; OUT_322] } else { let (__hash, __hit) = record.function_queries[322].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[322].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[322].bump_multiplicity(__i); } let __ret: [G; OUT_322] = unsafe { *(record.function_queries[322].output_at(__i).as_ptr() as *const [G; OUT_322]) }; __ret }, _ => { aiur_fn_322::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = { let __values: [G; 10] = [__v_12, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_13]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_322] = [__v_14]; record.function_queries[322].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } -const INPUT_SIZE_323: usize = 1; -const IN_323: usize = 1; -const OUT_323: usize = 1; -fn aiur_fn_323(inp: [G; IN_323], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_323], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 5] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(2).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(5))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 5 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 5] = __args[..5].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; match __v_1.as_canonical_u64() { 1u64 => { let __v_6: G = G::from_u64(0); let __ret: [G; OUT_323] = [__v_6]; record.function_queries[323].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_323] = { let __args: [G; IN_323] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(323, &__args[..])?) { [G::ZERO; OUT_323] } else { let (__hash, __hit) = record.function_queries[323].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[323].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[323].bump_multiplicity(__i); } let __ret: [G; OUT_323] = unsafe { *(record.function_queries[323].output_at(__i).as_ptr() as *const [G; OUT_323]) }; __ret }, _ => { aiur_fn_323::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = G::from_u64(1); let __v_8: G = __v_6 + __v_7; let __ret: [G; OUT_323] = [__v_8]; record.function_queries[323].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } +fn aiur_fn_322(inp: [G; IN_322], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_322], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; match __v_1.as_canonical_u64() { 1u64 => { let __v_4: G = G::from_u64(1); let __ret: [G; OUT_322] = [__v_4]; record.function_queries[322].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_4: G = G::from_u64(0); let __ret: [G; OUT_322] = [__v_4]; record.function_queries[322].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } +const INPUT_SIZE_323: usize = 2; +const IN_323: usize = 2; +const OUT_323: usize = 2; +fn aiur_fn_323(inp: [G; IN_323], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_323], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_329] = { let __args: [G; IN_329] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(329, &__args[..])?) { [G::ZERO; OUT_329] } else { let (__hash, __hit) = record.function_queries[329].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[329].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[329].bump_multiplicity(__i); } let __ret: [G; OUT_329] = unsafe { *(record.function_queries[329].output_at(__i).as_ptr() as *const [G; OUT_329]) }; __ret }, _ => { aiur_fn_329::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_2.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_3: G = __loaded[0]; let __v_4: G = __loaded[1]; let __v_5: G = __loaded[2]; let __v_6: G = __loaded[3]; match __v_3.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_323] = [__v_4, __v_5]; record.function_queries[323].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_3.as_canonical_u64())); }, } }) } const INPUT_SIZE_324: usize = 2; const IN_324: usize = 2; const OUT_324: usize = 1; -fn aiur_fn_324(inp: [G; IN_324], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_324], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_324] = [__v_0]; record.function_queries[324].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_2: G = G::from_u64(1); let __v_3: G = __v_1 - __v_2; let __r_arr: [G; OUT_324] = { let __args: [G; IN_324] = [__v_0, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(324, &__args[..])?) { [G::ZERO; OUT_324] } else { let (__hash, __hit) = record.function_queries[324].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[324].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[324].bump_multiplicity(__i); } let __ret: [G; OUT_324] = unsafe { *(record.function_queries[324].output_at(__i).as_ptr() as *const [G; OUT_324]) }; __ret }, _ => { aiur_fn_324::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_5: G = __loaded[0]; let __v_6: G = __loaded[1]; let __v_7: G = __loaded[2]; match __v_5.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_324] = [__v_7]; record.function_queries[324].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, } }) } -const INPUT_SIZE_325: usize = 2; -const IN_325: usize = 2; +fn aiur_fn_324(inp: [G; IN_324], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_324], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __loaded: [G; 10] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 10 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 10] = __args[..10].try_into().unwrap(); __arr }; let __v_2: G = __loaded[0]; let __v_3: G = __loaded[1]; let __v_4: G = __loaded[2]; let __v_5: G = __loaded[3]; let __v_6: G = __loaded[4]; let __v_7: G = __loaded[5]; let __v_8: G = __loaded[6]; let __v_9: G = __loaded[7]; let __v_10: G = __loaded[8]; let __v_11: G = __loaded[9]; match __v_2.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_324] = [__v_1]; record.function_queries[324].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_12: G = G::from_u64(0); let __r_arr: [G; OUT_324] = { let __args: [G; IN_324] = [__v_11, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(324, &__args[..])?) { [G::ZERO; OUT_324] } else { let (__hash, __hit) = record.function_queries[324].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[324].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[324].bump_multiplicity(__i); } let __ret: [G; OUT_324] = unsafe { *(record.function_queries[324].output_at(__i).as_ptr() as *const [G; OUT_324]) }; __ret }, _ => { aiur_fn_324::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = { let __values: [G; 10] = [__v_12, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_13]; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 10)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __ret: [G; OUT_324] = [__v_14]; record.function_queries[324].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } +const INPUT_SIZE_325: usize = 1; +const IN_325: usize = 1; const OUT_325: usize = 1; -fn aiur_fn_325(inp: [G; IN_325], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_325], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_325] = [__v_0]; record.function_queries[325].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_2: G = G::from_u64(1); let __v_3: G = __v_1 - __v_2; let __r_arr: [G; OUT_325] = { let __args: [G; IN_325] = [__v_0, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(325, &__args[..])?) { [G::ZERO; OUT_325] } else { let (__hash, __hit) = record.function_queries[325].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[325].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[325].bump_multiplicity(__i); } let __ret: [G; OUT_325] = unsafe { *(record.function_queries[325].output_at(__i).as_ptr() as *const [G; OUT_325]) }; __ret }, _ => { aiur_fn_325::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __loaded: [G; 6] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 6 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 6] = __args[..6].try_into().unwrap(); __arr }; let __v_5: G = __loaded[0]; let __v_6: G = __loaded[1]; let __v_7: G = __loaded[2]; let __v_8: G = __loaded[3]; let __v_9: G = __loaded[4]; let __v_10: G = __loaded[5]; match __v_5.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_325] = [__v_10]; record.function_queries[325].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, } }) } +fn aiur_fn_325(inp: [G; IN_325], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_325], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __loaded: [G; 5] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(2).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(5))?; let __ptr_u64 = __v_0.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 5 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 5] = __args[..5].try_into().unwrap(); __arr }; let __v_1: G = __loaded[0]; let __v_2: G = __loaded[1]; let __v_3: G = __loaded[2]; let __v_4: G = __loaded[3]; let __v_5: G = __loaded[4]; match __v_1.as_canonical_u64() { 1u64 => { let __v_6: G = G::from_u64(0); let __ret: [G; OUT_325] = [__v_6]; record.function_queries[325].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_325] = { let __args: [G; IN_325] = [__v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(325, &__args[..])?) { [G::ZERO; OUT_325] } else { let (__hash, __hit) = record.function_queries[325].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[325].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[325].bump_multiplicity(__i); } let __ret: [G; OUT_325] = unsafe { *(record.function_queries[325].output_at(__i).as_ptr() as *const [G; OUT_325]) }; __ret }, _ => { aiur_fn_325::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = G::from_u64(1); let __v_8: G = __v_6 + __v_7; let __ret: [G; OUT_325] = [__v_8]; record.function_queries[325].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_1.as_canonical_u64())); }, } }) } const INPUT_SIZE_326: usize = 2; const IN_326: usize = 2; -const OUT_326: usize = 8; -fn aiur_fn_326(inp: [G; IN_326], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_326], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_328] = { let __args: [G; IN_328] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(328, &__args[..])?) { [G::ZERO; OUT_328] } else { let (__hash, __hit) = record.function_queries[328].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[328].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[328].bump_multiplicity(__i); } let __ret: [G; OUT_328] = unsafe { *(record.function_queries[328].output_at(__i).as_ptr() as *const [G; OUT_328]) }; __ret }, _ => { aiur_fn_328::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __loaded: [G; 10] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let __ptr_u64 = __v_2.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 10 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 10] = __args[..10].try_into().unwrap(); __arr }; let __v_3: G = __loaded[0]; let __v_4: G = __loaded[1]; let __v_5: G = __loaded[2]; let __v_6: G = __loaded[3]; let __v_7: G = __loaded[4]; let __v_8: G = __loaded[5]; let __v_9: G = __loaded[6]; let __v_10: G = __loaded[7]; let __v_11: G = __loaded[8]; let __v_12: G = __loaded[9]; match __v_3.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_326] = [__v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11]; record.function_queries[326].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_3.as_canonical_u64())); }, } }) } +const OUT_326: usize = 1; +fn aiur_fn_326(inp: [G; IN_326], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_326], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_326] = [__v_0]; record.function_queries[326].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_2: G = G::from_u64(1); let __v_3: G = __v_1 - __v_2; let __r_arr: [G; OUT_326] = { let __args: [G; IN_326] = [__v_0, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(326, &__args[..])?) { [G::ZERO; OUT_326] } else { let (__hash, __hit) = record.function_queries[326].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[326].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[326].bump_multiplicity(__i); } let __ret: [G; OUT_326] = unsafe { *(record.function_queries[326].output_at(__i).as_ptr() as *const [G; OUT_326]) }; __ret }, _ => { aiur_fn_326::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(0).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_5: G = __loaded[0]; let __v_6: G = __loaded[1]; let __v_7: G = __loaded[2]; match __v_5.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_326] = [__v_7]; record.function_queries[326].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, } }) } const INPUT_SIZE_327: usize = 2; const IN_327: usize = 2; const OUT_327: usize = 1; -fn aiur_fn_327(inp: [G; IN_327], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_327], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_327] = [__v_0]; record.function_queries[327].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_2: G = G::from_u64(1); let __v_3: G = __v_1 - __v_2; let __r_arr: [G; OUT_327] = { let __args: [G; IN_327] = [__v_0, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(327, &__args[..])?) { [G::ZERO; OUT_327] } else { let (__hash, __hit) = record.function_queries[327].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[327].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[327].bump_multiplicity(__i); } let __ret: [G; OUT_327] = unsafe { *(record.function_queries[327].output_at(__i).as_ptr() as *const [G; OUT_327]) }; __ret }, _ => { aiur_fn_327::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_5: G = __loaded[0]; let __v_6: G = __loaded[1]; let __v_7: G = __loaded[2]; let __v_8: G = __loaded[3]; match __v_5.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_327] = [__v_8]; record.function_queries[327].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, } }) } +fn aiur_fn_327(inp: [G; IN_327], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_327], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_327] = [__v_0]; record.function_queries[327].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_2: G = G::from_u64(1); let __v_3: G = __v_1 - __v_2; let __r_arr: [G; OUT_327] = { let __args: [G; IN_327] = [__v_0, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(327, &__args[..])?) { [G::ZERO; OUT_327] } else { let (__hash, __hit) = record.function_queries[327].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[327].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[327].bump_multiplicity(__i); } let __ret: [G; OUT_327] = unsafe { *(record.function_queries[327].output_at(__i).as_ptr() as *const [G; OUT_327]) }; __ret }, _ => { aiur_fn_327::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __loaded: [G; 6] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(3).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(6))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 6 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 6] = __args[..6].try_into().unwrap(); __arr }; let __v_5: G = __loaded[0]; let __v_6: G = __loaded[1]; let __v_7: G = __loaded[2]; let __v_8: G = __loaded[3]; let __v_9: G = __loaded[4]; let __v_10: G = __loaded[5]; match __v_5.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_327] = [__v_10]; record.function_queries[327].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, } }) } const INPUT_SIZE_328: usize = 2; const IN_328: usize = 2; -const OUT_328: usize = 1; -fn aiur_fn_328(inp: [G; IN_328], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_328], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_328] = [__v_0]; record.function_queries[328].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_2: G = G::from_u64(1); let __v_3: G = __v_1 - __v_2; let __r_arr: [G; OUT_328] = { let __args: [G; IN_328] = [__v_0, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(328, &__args[..])?) { [G::ZERO; OUT_328] } else { let (__hash, __hit) = record.function_queries[328].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[328].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[328].bump_multiplicity(__i); } let __ret: [G; OUT_328] = unsafe { *(record.function_queries[328].output_at(__i).as_ptr() as *const [G; OUT_328]) }; __ret }, _ => { aiur_fn_328::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __loaded: [G; 10] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 10 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 10] = __args[..10].try_into().unwrap(); __arr }; let __v_5: G = __loaded[0]; let __v_6: G = __loaded[1]; let __v_7: G = __loaded[2]; let __v_8: G = __loaded[3]; let __v_9: G = __loaded[4]; let __v_10: G = __loaded[5]; let __v_11: G = __loaded[6]; let __v_12: G = __loaded[7]; let __v_13: G = __loaded[8]; let __v_14: G = __loaded[9]; match __v_5.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_328] = [__v_14]; record.function_queries[328].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, } }) } -pub(crate) fn execute_generated(fun_idx: usize, args: &[G], record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result, ExecError> { if (record.memory_queries.get_index(0).map(|(size, _)| (*size)) != Some(3)) { return Err(ExecError::InvalidMemorySize(3)); } if (record.memory_queries.get_index(1).map(|(size, _)| (*size)) != Some(4)) { return Err(ExecError::InvalidMemorySize(4)); } if (record.memory_queries.get_index(2).map(|(size, _)| (*size)) != Some(5)) { return Err(ExecError::InvalidMemorySize(5)); } if (record.memory_queries.get_index(3).map(|(size, _)| (*size)) != Some(6)) { return Err(ExecError::InvalidMemorySize(6)); } if (record.memory_queries.get_index(4).map(|(size, _)| (*size)) != Some(7)) { return Err(ExecError::InvalidMemorySize(7)); } if (record.memory_queries.get_index(5).map(|(size, _)| (*size)) != Some(8)) { return Err(ExecError::InvalidMemorySize(8)); } if (record.memory_queries.get_index(6).map(|(size, _)| (*size)) != Some(10)) { return Err(ExecError::InvalidMemorySize(10)); } if (record.memory_queries.get_index(7).map(|(size, _)| (*size)) != Some(11)) { return Err(ExecError::InvalidMemorySize(11)); } if (record.memory_queries.get_index(8).map(|(size, _)| (*size)) != Some(26)) { return Err(ExecError::InvalidMemorySize(26)); } if (record.memory_queries.get_index(9).map(|(size, _)| (*size)) != Some(32)) { return Err(ExecError::InvalidMemorySize(32)); } if (record.memory_queries.get_index(10).map(|(size, _)| (*size)) != Some(34)) { return Err(ExecError::InvalidMemorySize(34)); } let __input_hash = aiur::querymap::hash_g_slice(args); match (fun_idx as u64) { 0u64 => { let __inp: [G; IN_0] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_0::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 1u64 => { let __inp: [G; IN_1] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_1::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 2u64 => { let __inp: [G; IN_2] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_2::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 3u64 => { let __inp: [G; IN_3] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_3::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 4u64 => { let __inp: [G; IN_4] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_4::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 5u64 => { let __inp: [G; IN_5] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_5::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 6u64 => { let __inp: [G; IN_6] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_6::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 7u64 => { let __inp: [G; IN_7] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_7::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 8u64 => { let __inp: [G; IN_8] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_8::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 9u64 => { let __inp: [G; IN_9] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_9::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 10u64 => { let __inp: [G; IN_10] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_10::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 11u64 => { let __inp: [G; IN_11] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_11::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 12u64 => { let __inp: [G; IN_12] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_12::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 13u64 => { let __inp: [G; IN_13] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_13::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 14u64 => { let __inp: [G; IN_14] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_14::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 15u64 => { let __inp: [G; IN_15] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_15::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 16u64 => { let __inp: [G; IN_16] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_16::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 17u64 => { let __inp: [G; IN_17] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_17::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 18u64 => { let __inp: [G; IN_18] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_18::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 19u64 => { let __inp: [G; IN_19] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_19::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 20u64 => { let __inp: [G; IN_20] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_20::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 21u64 => { let __inp: [G; IN_21] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_21::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 22u64 => { let __inp: [G; IN_22] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_22::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 23u64 => { let __inp: [G; IN_23] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_23::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 24u64 => { let __inp: [G; IN_24] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_24::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 25u64 => { let __inp: [G; IN_25] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_25::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 26u64 => { let __inp: [G; IN_26] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_26::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 27u64 => { let __inp: [G; IN_27] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_27::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 28u64 => { let __inp: [G; IN_28] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_28::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 29u64 => { let __inp: [G; IN_29] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_29::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 30u64 => { let __inp: [G; IN_30] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_30::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 31u64 => { let __inp: [G; IN_31] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_31::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 32u64 => { let __inp: [G; IN_32] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_32::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 33u64 => { let __inp: [G; IN_33] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_33::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 34u64 => { let __inp: [G; IN_34] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_34::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 35u64 => { let __inp: [G; IN_35] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_35::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 36u64 => { let __inp: [G; IN_36] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_36::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 37u64 => { let __inp: [G; IN_37] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_37::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 38u64 => { let __inp: [G; IN_38] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_38::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 39u64 => { let __inp: [G; IN_39] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_39::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 40u64 => { let __inp: [G; IN_40] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_40::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 41u64 => { let __inp: [G; IN_41] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_41::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 42u64 => { let __inp: [G; IN_42] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_42::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 43u64 => { let __inp: [G; IN_43] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_43::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 44u64 => { let __inp: [G; IN_44] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_44::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 45u64 => { let __inp: [G; IN_45] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_45::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 46u64 => { let __inp: [G; IN_46] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_46::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 47u64 => { let __inp: [G; IN_47] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_47::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 48u64 => { let __inp: [G; IN_48] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_48::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 49u64 => { let __inp: [G; IN_49] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_49::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 50u64 => { let __inp: [G; IN_50] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_50::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 51u64 => { let __inp: [G; IN_51] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_51::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 52u64 => { let __inp: [G; IN_52] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_52::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 53u64 => { let __inp: [G; IN_53] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_53::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 54u64 => { let __inp: [G; IN_54] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_54::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 55u64 => { let __inp: [G; IN_55] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_55::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 56u64 => { let __inp: [G; IN_56] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_56::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 57u64 => { let __inp: [G; IN_57] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_57::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 58u64 => { let __inp: [G; IN_58] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_58::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 59u64 => { let __inp: [G; IN_59] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_59::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 60u64 => { let __inp: [G; IN_60] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_60::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 61u64 => { let __inp: [G; IN_61] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_61::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 62u64 => { let __inp: [G; IN_62] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_62::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 63u64 => { let __inp: [G; IN_63] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_63::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 64u64 => { let __inp: [G; IN_64] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_64::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 65u64 => { let __inp: [G; IN_65] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_65::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 66u64 => { let __inp: [G; IN_66] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_66::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 67u64 => { let __inp: [G; IN_67] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_67::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 68u64 => { let __inp: [G; IN_68] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_68::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 69u64 => { let __inp: [G; IN_69] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_69::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 70u64 => { let __inp: [G; IN_70] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_70::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 71u64 => { let __inp: [G; IN_71] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_71::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 72u64 => { let __inp: [G; IN_72] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_72::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 73u64 => { let __inp: [G; IN_73] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_73::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 74u64 => { let __inp: [G; IN_74] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_74::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 75u64 => { let __inp: [G; IN_75] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_75::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 76u64 => { let __inp: [G; IN_76] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_76::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 77u64 => { let __inp: [G; IN_77] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_77::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 78u64 => { let __inp: [G; IN_78] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_78::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 79u64 => { let __inp: [G; IN_79] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_79::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 80u64 => { let __inp: [G; IN_80] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_80::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 81u64 => { let __inp: [G; IN_81] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_81::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 82u64 => { let __inp: [G; IN_82] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_82::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 83u64 => { let __inp: [G; IN_83] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_83::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 84u64 => { let __inp: [G; IN_84] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_84::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 85u64 => { let __inp: [G; IN_85] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_85::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 86u64 => { let __inp: [G; IN_86] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_86::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 87u64 => { let __inp: [G; IN_87] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_87::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 88u64 => { let __inp: [G; IN_88] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_88::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 89u64 => { let __inp: [G; IN_89] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_89::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 90u64 => { let __inp: [G; IN_90] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_90::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 91u64 => { let __inp: [G; IN_91] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_91::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 92u64 => { let __inp: [G; IN_92] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_92::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 93u64 => { let __inp: [G; IN_93] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_93::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 94u64 => { let __inp: [G; IN_94] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_94::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 95u64 => { let __inp: [G; IN_95] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_95::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 96u64 => { let __inp: [G; IN_96] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_96::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 97u64 => { let __inp: [G; IN_97] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_97::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 98u64 => { let __inp: [G; IN_98] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_98::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 99u64 => { let __inp: [G; IN_99] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_99::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 100u64 => { let __inp: [G; IN_100] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_100::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 101u64 => { let __inp: [G; IN_101] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_101::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 102u64 => { let __inp: [G; IN_102] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_102::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 103u64 => { let __inp: [G; IN_103] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_103::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 104u64 => { let __inp: [G; IN_104] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_104::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 105u64 => { let __inp: [G; IN_105] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_105::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 106u64 => { let __inp: [G; IN_106] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_106::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 107u64 => { let __inp: [G; IN_107] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_107::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 108u64 => { let __inp: [G; IN_108] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_108::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 109u64 => { let __inp: [G; IN_109] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_109::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 110u64 => { let __inp: [G; IN_110] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_110::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 111u64 => { let __inp: [G; IN_111] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_111::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 112u64 => { let __inp: [G; IN_112] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_112::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 113u64 => { let __inp: [G; IN_113] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_113::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 114u64 => { let __inp: [G; IN_114] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_114::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 115u64 => { let __inp: [G; IN_115] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_115::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 116u64 => { let __inp: [G; IN_116] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_116::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 117u64 => { let __inp: [G; IN_117] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_117::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 118u64 => { let __inp: [G; IN_118] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_118::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 119u64 => { let __inp: [G; IN_119] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_119::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 120u64 => { let __inp: [G; IN_120] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_120::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 121u64 => { let __inp: [G; IN_121] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_121::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 122u64 => { let __inp: [G; IN_122] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_122::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 123u64 => { let __inp: [G; IN_123] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_123::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 124u64 => { let __inp: [G; IN_124] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_124::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 125u64 => { let __inp: [G; IN_125] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_125::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 126u64 => { let __inp: [G; IN_126] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_126::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 127u64 => { let __inp: [G; IN_127] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_127::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 128u64 => { let __inp: [G; IN_128] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_128::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 129u64 => { let __inp: [G; IN_129] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_129::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 130u64 => { let __inp: [G; IN_130] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_130::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 131u64 => { let __inp: [G; IN_131] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_131::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 132u64 => { let __inp: [G; IN_132] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_132::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 133u64 => { let __inp: [G; IN_133] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_133::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 134u64 => { let __inp: [G; IN_134] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_134::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 135u64 => { let __inp: [G; IN_135] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_135::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 136u64 => { let __inp: [G; IN_136] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_136::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 137u64 => { let __inp: [G; IN_137] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_137::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 138u64 => { let __inp: [G; IN_138] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_138::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 139u64 => { let __inp: [G; IN_139] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_139::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 140u64 => { let __inp: [G; IN_140] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_140::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 141u64 => { let __inp: [G; IN_141] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_141::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 142u64 => { let __inp: [G; IN_142] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_142::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 143u64 => { let __inp: [G; IN_143] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_143::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 144u64 => { let __inp: [G; IN_144] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_144::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 145u64 => { let __inp: [G; IN_145] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_145::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 146u64 => { let __inp: [G; IN_146] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_146::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 147u64 => { let __inp: [G; IN_147] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_147::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 148u64 => { let __inp: [G; IN_148] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_148::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 149u64 => { let __inp: [G; IN_149] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_149::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 150u64 => { let __inp: [G; IN_150] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_150::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 151u64 => { let __inp: [G; IN_151] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_151::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 152u64 => { let __inp: [G; IN_152] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_152::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 153u64 => { let __inp: [G; IN_153] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_153::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 154u64 => { let __inp: [G; IN_154] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_154::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 155u64 => { let __inp: [G; IN_155] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_155::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 156u64 => { let __inp: [G; IN_156] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_156::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 157u64 => { let __inp: [G; IN_157] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_157::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 158u64 => { let __inp: [G; IN_158] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_158::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 159u64 => { let __inp: [G; IN_159] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_159::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 160u64 => { let __inp: [G; IN_160] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_160::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 161u64 => { let __inp: [G; IN_161] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_161::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 162u64 => { let __inp: [G; IN_162] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_162::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 163u64 => { let __inp: [G; IN_163] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_163::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 164u64 => { let __inp: [G; IN_164] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_164::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 165u64 => { let __inp: [G; IN_165] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_165::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 166u64 => { let __inp: [G; IN_166] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_166::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 167u64 => { let __inp: [G; IN_167] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_167::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 168u64 => { let __inp: [G; IN_168] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_168::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 169u64 => { let __inp: [G; IN_169] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_169::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 170u64 => { let __inp: [G; IN_170] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_170::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 171u64 => { let __inp: [G; IN_171] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_171::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 172u64 => { let __inp: [G; IN_172] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_172::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 173u64 => { let __inp: [G; IN_173] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_173::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 174u64 => { let __inp: [G; IN_174] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_174::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 175u64 => { let __inp: [G; IN_175] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_175::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 176u64 => { let __inp: [G; IN_176] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_176::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 177u64 => { let __inp: [G; IN_177] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_177::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 178u64 => { let __inp: [G; IN_178] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_178::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 179u64 => { let __inp: [G; IN_179] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_179::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 180u64 => { let __inp: [G; IN_180] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_180::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 181u64 => { let __inp: [G; IN_181] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_181::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 182u64 => { let __inp: [G; IN_182] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_182::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 183u64 => { let __inp: [G; IN_183] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_183::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 184u64 => { let __inp: [G; IN_184] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_184::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 185u64 => { let __inp: [G; IN_185] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_185::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 186u64 => { let __inp: [G; IN_186] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_186::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 187u64 => { let __inp: [G; IN_187] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_187::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 188u64 => { let __inp: [G; IN_188] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_188::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 189u64 => { let __inp: [G; IN_189] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_189::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 190u64 => { let __inp: [G; IN_190] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_190::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 191u64 => { let __inp: [G; IN_191] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_191::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 192u64 => { let __inp: [G; IN_192] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_192::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 193u64 => { let __inp: [G; IN_193] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_193::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 194u64 => { let __inp: [G; IN_194] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_194::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 195u64 => { let __inp: [G; IN_195] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_195::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 196u64 => { let __inp: [G; IN_196] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_196::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 197u64 => { let __inp: [G; IN_197] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_197::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 198u64 => { let __inp: [G; IN_198] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_198::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 199u64 => { let __inp: [G; IN_199] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_199::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 200u64 => { let __inp: [G; IN_200] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_200::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 201u64 => { let __inp: [G; IN_201] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_201::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 202u64 => { let __inp: [G; IN_202] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_202::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 203u64 => { let __inp: [G; IN_203] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_203::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 204u64 => { let __inp: [G; IN_204] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_204::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 205u64 => { let __inp: [G; IN_205] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_205::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 206u64 => { let __inp: [G; IN_206] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_206::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 207u64 => { let __inp: [G; IN_207] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_207::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 208u64 => { let __inp: [G; IN_208] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_208::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 209u64 => { let __inp: [G; IN_209] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_209::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 210u64 => { let __inp: [G; IN_210] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_210::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 211u64 => { let __inp: [G; IN_211] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_211::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 212u64 => { let __inp: [G; IN_212] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_212::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 213u64 => { let __inp: [G; IN_213] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_213::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 214u64 => { let __inp: [G; IN_214] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_214::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 215u64 => { let __inp: [G; IN_215] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_215::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 216u64 => { let __inp: [G; IN_216] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_216::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 217u64 => { let __inp: [G; IN_217] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_217::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 218u64 => { let __inp: [G; IN_218] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_218::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 219u64 => { let __inp: [G; IN_219] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_219::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 220u64 => { let __inp: [G; IN_220] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_220::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 221u64 => { let __inp: [G; IN_221] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_221::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 222u64 => { let __inp: [G; IN_222] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_222::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 223u64 => { let __inp: [G; IN_223] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_223::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 224u64 => { let __inp: [G; IN_224] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_224::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 225u64 => { let __inp: [G; IN_225] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_225::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 226u64 => { let __inp: [G; IN_226] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_226::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 227u64 => { let __inp: [G; IN_227] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_227::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 228u64 => { let __inp: [G; IN_228] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_228::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 229u64 => { let __inp: [G; IN_229] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_229::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 230u64 => { let __inp: [G; IN_230] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_230::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 231u64 => { let __inp: [G; IN_231] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_231::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 232u64 => { let __inp: [G; IN_232] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_232::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 233u64 => { let __inp: [G; IN_233] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_233::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 234u64 => { let __inp: [G; IN_234] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_234::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 235u64 => { let __inp: [G; IN_235] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_235::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 236u64 => { let __inp: [G; IN_236] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_236::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 237u64 => { let __inp: [G; IN_237] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_237::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 238u64 => { let __inp: [G; IN_238] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_238::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 239u64 => { let __inp: [G; IN_239] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_239::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 240u64 => { let __inp: [G; IN_240] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_240::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 241u64 => { let __inp: [G; IN_241] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_241::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 242u64 => { let __inp: [G; IN_242] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_242::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 243u64 => { let __inp: [G; IN_243] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_243::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 244u64 => { let __inp: [G; IN_244] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_244::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 245u64 => { let __inp: [G; IN_245] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_245::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 246u64 => { let __inp: [G; IN_246] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_246::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 247u64 => { let __inp: [G; IN_247] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_247::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 248u64 => { let __inp: [G; IN_248] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_248::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 249u64 => { let __inp: [G; IN_249] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_249::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 250u64 => { let __inp: [G; IN_250] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_250::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 251u64 => { let __inp: [G; IN_251] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_251::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 252u64 => { let __inp: [G; IN_252] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_252::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 253u64 => { let __inp: [G; IN_253] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_253::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 254u64 => { let __inp: [G; IN_254] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_254::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 255u64 => { let __inp: [G; IN_255] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_255::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 256u64 => { let __inp: [G; IN_256] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_256::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 257u64 => { let __inp: [G; IN_257] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_257::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 258u64 => { let __inp: [G; IN_258] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_258::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 259u64 => { let __inp: [G; IN_259] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_259::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 260u64 => { let __inp: [G; IN_260] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_260::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 261u64 => { let __inp: [G; IN_261] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_261::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 262u64 => { let __inp: [G; IN_262] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_262::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 263u64 => { let __inp: [G; IN_263] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_263::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 264u64 => { let __inp: [G; IN_264] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_264::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 265u64 => { let __inp: [G; IN_265] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_265::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 266u64 => { let __inp: [G; IN_266] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_266::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 267u64 => { let __inp: [G; IN_267] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_267::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 268u64 => { let __inp: [G; IN_268] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_268::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 269u64 => { let __inp: [G; IN_269] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_269::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 270u64 => { let __inp: [G; IN_270] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_270::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 271u64 => { let __inp: [G; IN_271] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_271::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 272u64 => { let __inp: [G; IN_272] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_272::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 273u64 => { let __inp: [G; IN_273] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_273::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 274u64 => { let __inp: [G; IN_274] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_274::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 275u64 => { let __inp: [G; IN_275] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_275::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 276u64 => { let __inp: [G; IN_276] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_276::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 277u64 => { let __inp: [G; IN_277] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_277::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 278u64 => { let __inp: [G; IN_278] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_278::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 279u64 => { let __inp: [G; IN_279] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_279::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 280u64 => { let __inp: [G; IN_280] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_280::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 281u64 => { let __inp: [G; IN_281] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_281::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 282u64 => { let __inp: [G; IN_282] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_282::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 283u64 => { let __inp: [G; IN_283] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_283::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 284u64 => { let __inp: [G; IN_284] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_284::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 285u64 => { let __inp: [G; IN_285] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_285::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 286u64 => { let __inp: [G; IN_286] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_286::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 287u64 => { let __inp: [G; IN_287] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_287::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 288u64 => { let __inp: [G; IN_288] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_288::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 289u64 => { let __inp: [G; IN_289] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_289::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 290u64 => { let __inp: [G; IN_290] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_290::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 291u64 => { let __inp: [G; IN_291] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_291::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 292u64 => { let __inp: [G; IN_292] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_292::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 293u64 => { let __inp: [G; IN_293] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_293::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 294u64 => { let __inp: [G; IN_294] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_294::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 295u64 => { let __inp: [G; IN_295] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_295::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 296u64 => { let __inp: [G; IN_296] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_296::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 297u64 => { let __inp: [G; IN_297] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_297::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 298u64 => { let __inp: [G; IN_298] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_298::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 299u64 => { let __inp: [G; IN_299] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_299::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 300u64 => { let __inp: [G; IN_300] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_300::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 301u64 => { let __inp: [G; IN_301] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_301::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 302u64 => { let __inp: [G; IN_302] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_302::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 303u64 => { let __inp: [G; IN_303] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_303::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 304u64 => { let __inp: [G; IN_304] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_304::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 305u64 => { let __inp: [G; IN_305] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_305::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 306u64 => { let __inp: [G; IN_306] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_306::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 307u64 => { let __inp: [G; IN_307] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_307::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 308u64 => { let __inp: [G; IN_308] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_308::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 309u64 => { let __inp: [G; IN_309] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_309::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 310u64 => { let __inp: [G; IN_310] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_310::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 311u64 => { let __inp: [G; IN_311] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_311::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 312u64 => { let __inp: [G; IN_312] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_312::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 313u64 => { let __inp: [G; IN_313] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_313::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 314u64 => { let __inp: [G; IN_314] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_314::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 315u64 => { let __inp: [G; IN_315] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_315::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 316u64 => { let __inp: [G; IN_316] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_316::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 317u64 => { let __inp: [G; IN_317] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_317::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 318u64 => { let __inp: [G; IN_318] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_318::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 319u64 => { let __inp: [G; IN_319] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_319::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 320u64 => { let __inp: [G; IN_320] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_320::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 321u64 => { let __inp: [G; IN_321] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_321::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 322u64 => { let __inp: [G; IN_322] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_322::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 323u64 => { let __inp: [G; IN_323] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_323::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 324u64 => { let __inp: [G; IN_324] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_324::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 325u64 => { let __inp: [G; IN_325] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_325::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 326u64 => { let __inp: [G; IN_326] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_326::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 327u64 => { let __inp: [G; IN_327] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_327::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 328u64 => { let __inp: [G; IN_328] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_328::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, _ => { return Err(ExecError::InvalidFunIdx(fun_idx)); }, } } +const OUT_328: usize = 8; +fn aiur_fn_328(inp: [G; IN_328], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_328], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __r_arr: [G; OUT_330] = { let __args: [G; IN_330] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(330, &__args[..])?) { [G::ZERO; OUT_330] } else { let (__hash, __hit) = record.function_queries[330].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[330].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[330].bump_multiplicity(__i); } let __ret: [G; OUT_330] = unsafe { *(record.function_queries[330].output_at(__i).as_ptr() as *const [G; OUT_330]) }; __ret }, _ => { aiur_fn_330::(__args, __hash, record, io_buffer)? }, } } }; let __v_2: G = __r_arr[0]; let __loaded: [G; 10] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let __ptr_u64 = __v_2.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 10 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 10] = __args[..10].try_into().unwrap(); __arr }; let __v_3: G = __loaded[0]; let __v_4: G = __loaded[1]; let __v_5: G = __loaded[2]; let __v_6: G = __loaded[3]; let __v_7: G = __loaded[4]; let __v_8: G = __loaded[5]; let __v_9: G = __loaded[6]; let __v_10: G = __loaded[7]; let __v_11: G = __loaded[8]; let __v_12: G = __loaded[9]; match __v_3.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_328] = [__v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11]; record.function_queries[328].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_3.as_canonical_u64())); }, } }) } +const INPUT_SIZE_329: usize = 2; +const IN_329: usize = 2; +const OUT_329: usize = 1; +fn aiur_fn_329(inp: [G; IN_329], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_329], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_329] = [__v_0]; record.function_queries[329].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_2: G = G::from_u64(1); let __v_3: G = __v_1 - __v_2; let __r_arr: [G; OUT_329] = { let __args: [G; IN_329] = [__v_0, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(329, &__args[..])?) { [G::ZERO; OUT_329] } else { let (__hash, __hit) = record.function_queries[329].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[329].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[329].bump_multiplicity(__i); } let __ret: [G; OUT_329] = unsafe { *(record.function_queries[329].output_at(__i).as_ptr() as *const [G; OUT_329]) }; __ret }, _ => { aiur_fn_329::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __loaded: [G; 4] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(4))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 4 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 4] = __args[..4].try_into().unwrap(); __arr }; let __v_5: G = __loaded[0]; let __v_6: G = __loaded[1]; let __v_7: G = __loaded[2]; let __v_8: G = __loaded[3]; match __v_5.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_329] = [__v_8]; record.function_queries[329].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, } }) } +const INPUT_SIZE_330: usize = 2; +const IN_330: usize = 2; +const OUT_330: usize = 1; +fn aiur_fn_330(inp: [G; IN_330], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_330], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; match __v_1.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_330] = [__v_0]; record.function_queries[330].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __v_2: G = G::from_u64(1); let __v_3: G = __v_1 - __v_2; let __r_arr: [G; OUT_330] = { let __args: [G; IN_330] = [__v_0, __v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(330, &__args[..])?) { [G::ZERO; OUT_330] } else { let (__hash, __hit) = record.function_queries[330].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[330].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[330].bump_multiplicity(__i); } let __ret: [G; OUT_330] = unsafe { *(record.function_queries[330].output_at(__i).as_ptr() as *const [G; OUT_330]) }; __ret }, _ => { aiur_fn_330::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __loaded: [G; 10] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(6).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(10))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 10 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 10] = __args[..10].try_into().unwrap(); __arr }; let __v_5: G = __loaded[0]; let __v_6: G = __loaded[1]; let __v_7: G = __loaded[2]; let __v_8: G = __loaded[3]; let __v_9: G = __loaded[4]; let __v_10: G = __loaded[5]; let __v_11: G = __loaded[6]; let __v_12: G = __loaded[7]; let __v_13: G = __loaded[8]; let __v_14: G = __loaded[9]; match __v_5.as_canonical_u64() { 0u64 => { let __ret: [G; OUT_330] = [__v_14]; record.function_queries[330].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, } }) } +pub(crate) fn execute_generated(fun_idx: usize, args: &[G], record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result, ExecError> { if (record.memory_queries.get_index(0).map(|(size, _)| (*size)) != Some(3)) { return Err(ExecError::InvalidMemorySize(3)); } if (record.memory_queries.get_index(1).map(|(size, _)| (*size)) != Some(4)) { return Err(ExecError::InvalidMemorySize(4)); } if (record.memory_queries.get_index(2).map(|(size, _)| (*size)) != Some(5)) { return Err(ExecError::InvalidMemorySize(5)); } if (record.memory_queries.get_index(3).map(|(size, _)| (*size)) != Some(6)) { return Err(ExecError::InvalidMemorySize(6)); } if (record.memory_queries.get_index(4).map(|(size, _)| (*size)) != Some(7)) { return Err(ExecError::InvalidMemorySize(7)); } if (record.memory_queries.get_index(5).map(|(size, _)| (*size)) != Some(8)) { return Err(ExecError::InvalidMemorySize(8)); } if (record.memory_queries.get_index(6).map(|(size, _)| (*size)) != Some(10)) { return Err(ExecError::InvalidMemorySize(10)); } if (record.memory_queries.get_index(7).map(|(size, _)| (*size)) != Some(11)) { return Err(ExecError::InvalidMemorySize(11)); } if (record.memory_queries.get_index(8).map(|(size, _)| (*size)) != Some(26)) { return Err(ExecError::InvalidMemorySize(26)); } if (record.memory_queries.get_index(9).map(|(size, _)| (*size)) != Some(32)) { return Err(ExecError::InvalidMemorySize(32)); } if (record.memory_queries.get_index(10).map(|(size, _)| (*size)) != Some(34)) { return Err(ExecError::InvalidMemorySize(34)); } let __input_hash = aiur::querymap::hash_g_slice(args); match (fun_idx as u64) { 0u64 => { let __inp: [G; IN_0] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_0::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 1u64 => { let __inp: [G; IN_1] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_1::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 2u64 => { let __inp: [G; IN_2] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_2::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 3u64 => { let __inp: [G; IN_3] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_3::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 4u64 => { let __inp: [G; IN_4] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_4::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 5u64 => { let __inp: [G; IN_5] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_5::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 6u64 => { let __inp: [G; IN_6] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_6::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 7u64 => { let __inp: [G; IN_7] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_7::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 8u64 => { let __inp: [G; IN_8] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_8::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 9u64 => { let __inp: [G; IN_9] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_9::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 10u64 => { let __inp: [G; IN_10] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_10::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 11u64 => { let __inp: [G; IN_11] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_11::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 12u64 => { let __inp: [G; IN_12] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_12::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 13u64 => { let __inp: [G; IN_13] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_13::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 14u64 => { let __inp: [G; IN_14] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_14::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 15u64 => { let __inp: [G; IN_15] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_15::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 16u64 => { let __inp: [G; IN_16] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_16::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 17u64 => { let __inp: [G; IN_17] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_17::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 18u64 => { let __inp: [G; IN_18] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_18::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 19u64 => { let __inp: [G; IN_19] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_19::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 20u64 => { let __inp: [G; IN_20] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_20::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 21u64 => { let __inp: [G; IN_21] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_21::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 22u64 => { let __inp: [G; IN_22] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_22::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 23u64 => { let __inp: [G; IN_23] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_23::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 24u64 => { let __inp: [G; IN_24] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_24::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 25u64 => { let __inp: [G; IN_25] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_25::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 26u64 => { let __inp: [G; IN_26] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_26::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 27u64 => { let __inp: [G; IN_27] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_27::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 28u64 => { let __inp: [G; IN_28] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_28::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 29u64 => { let __inp: [G; IN_29] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_29::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 30u64 => { let __inp: [G; IN_30] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_30::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 31u64 => { let __inp: [G; IN_31] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_31::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 32u64 => { let __inp: [G; IN_32] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_32::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 33u64 => { let __inp: [G; IN_33] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_33::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 34u64 => { let __inp: [G; IN_34] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_34::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 35u64 => { let __inp: [G; IN_35] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_35::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 36u64 => { let __inp: [G; IN_36] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_36::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 37u64 => { let __inp: [G; IN_37] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_37::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 38u64 => { let __inp: [G; IN_38] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_38::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 39u64 => { let __inp: [G; IN_39] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_39::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 40u64 => { let __inp: [G; IN_40] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_40::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 41u64 => { let __inp: [G; IN_41] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_41::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 42u64 => { let __inp: [G; IN_42] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_42::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 43u64 => { let __inp: [G; IN_43] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_43::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 44u64 => { let __inp: [G; IN_44] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_44::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 45u64 => { let __inp: [G; IN_45] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_45::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 46u64 => { let __inp: [G; IN_46] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_46::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 47u64 => { let __inp: [G; IN_47] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_47::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 48u64 => { let __inp: [G; IN_48] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_48::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 49u64 => { let __inp: [G; IN_49] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_49::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 50u64 => { let __inp: [G; IN_50] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_50::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 51u64 => { let __inp: [G; IN_51] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_51::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 52u64 => { let __inp: [G; IN_52] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_52::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 53u64 => { let __inp: [G; IN_53] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_53::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 54u64 => { let __inp: [G; IN_54] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_54::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 55u64 => { let __inp: [G; IN_55] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_55::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 56u64 => { let __inp: [G; IN_56] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_56::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 57u64 => { let __inp: [G; IN_57] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_57::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 58u64 => { let __inp: [G; IN_58] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_58::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 59u64 => { let __inp: [G; IN_59] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_59::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 60u64 => { let __inp: [G; IN_60] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_60::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 61u64 => { let __inp: [G; IN_61] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_61::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 62u64 => { let __inp: [G; IN_62] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_62::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 63u64 => { let __inp: [G; IN_63] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_63::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 64u64 => { let __inp: [G; IN_64] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_64::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 65u64 => { let __inp: [G; IN_65] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_65::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 66u64 => { let __inp: [G; IN_66] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_66::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 67u64 => { let __inp: [G; IN_67] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_67::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 68u64 => { let __inp: [G; IN_68] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_68::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 69u64 => { let __inp: [G; IN_69] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_69::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 70u64 => { let __inp: [G; IN_70] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_70::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 71u64 => { let __inp: [G; IN_71] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_71::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 72u64 => { let __inp: [G; IN_72] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_72::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 73u64 => { let __inp: [G; IN_73] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_73::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 74u64 => { let __inp: [G; IN_74] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_74::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 75u64 => { let __inp: [G; IN_75] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_75::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 76u64 => { let __inp: [G; IN_76] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_76::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 77u64 => { let __inp: [G; IN_77] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_77::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 78u64 => { let __inp: [G; IN_78] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_78::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 79u64 => { let __inp: [G; IN_79] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_79::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 80u64 => { let __inp: [G; IN_80] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_80::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 81u64 => { let __inp: [G; IN_81] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_81::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 82u64 => { let __inp: [G; IN_82] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_82::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 83u64 => { let __inp: [G; IN_83] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_83::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 84u64 => { let __inp: [G; IN_84] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_84::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 85u64 => { let __inp: [G; IN_85] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_85::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 86u64 => { let __inp: [G; IN_86] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_86::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 87u64 => { let __inp: [G; IN_87] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_87::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 88u64 => { let __inp: [G; IN_88] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_88::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 89u64 => { let __inp: [G; IN_89] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_89::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 90u64 => { let __inp: [G; IN_90] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_90::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 91u64 => { let __inp: [G; IN_91] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_91::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 92u64 => { let __inp: [G; IN_92] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_92::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 93u64 => { let __inp: [G; IN_93] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_93::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 94u64 => { let __inp: [G; IN_94] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_94::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 95u64 => { let __inp: [G; IN_95] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_95::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 96u64 => { let __inp: [G; IN_96] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_96::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 97u64 => { let __inp: [G; IN_97] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_97::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 98u64 => { let __inp: [G; IN_98] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_98::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 99u64 => { let __inp: [G; IN_99] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_99::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 100u64 => { let __inp: [G; IN_100] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_100::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 101u64 => { let __inp: [G; IN_101] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_101::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 102u64 => { let __inp: [G; IN_102] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_102::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 103u64 => { let __inp: [G; IN_103] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_103::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 104u64 => { let __inp: [G; IN_104] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_104::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 105u64 => { let __inp: [G; IN_105] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_105::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 106u64 => { let __inp: [G; IN_106] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_106::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 107u64 => { let __inp: [G; IN_107] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_107::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 108u64 => { let __inp: [G; IN_108] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_108::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 109u64 => { let __inp: [G; IN_109] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_109::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 110u64 => { let __inp: [G; IN_110] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_110::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 111u64 => { let __inp: [G; IN_111] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_111::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 112u64 => { let __inp: [G; IN_112] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_112::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 113u64 => { let __inp: [G; IN_113] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_113::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 114u64 => { let __inp: [G; IN_114] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_114::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 115u64 => { let __inp: [G; IN_115] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_115::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 116u64 => { let __inp: [G; IN_116] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_116::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 117u64 => { let __inp: [G; IN_117] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_117::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 118u64 => { let __inp: [G; IN_118] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_118::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 119u64 => { let __inp: [G; IN_119] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_119::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 120u64 => { let __inp: [G; IN_120] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_120::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 121u64 => { let __inp: [G; IN_121] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_121::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 122u64 => { let __inp: [G; IN_122] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_122::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 123u64 => { let __inp: [G; IN_123] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_123::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 124u64 => { let __inp: [G; IN_124] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_124::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 125u64 => { let __inp: [G; IN_125] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_125::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 126u64 => { let __inp: [G; IN_126] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_126::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 127u64 => { let __inp: [G; IN_127] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_127::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 128u64 => { let __inp: [G; IN_128] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_128::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 129u64 => { let __inp: [G; IN_129] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_129::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 130u64 => { let __inp: [G; IN_130] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_130::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 131u64 => { let __inp: [G; IN_131] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_131::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 132u64 => { let __inp: [G; IN_132] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_132::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 133u64 => { let __inp: [G; IN_133] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_133::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 134u64 => { let __inp: [G; IN_134] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_134::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 135u64 => { let __inp: [G; IN_135] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_135::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 136u64 => { let __inp: [G; IN_136] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_136::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 137u64 => { let __inp: [G; IN_137] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_137::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 138u64 => { let __inp: [G; IN_138] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_138::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 139u64 => { let __inp: [G; IN_139] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_139::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 140u64 => { let __inp: [G; IN_140] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_140::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 141u64 => { let __inp: [G; IN_141] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_141::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 142u64 => { let __inp: [G; IN_142] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_142::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 143u64 => { let __inp: [G; IN_143] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_143::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 144u64 => { let __inp: [G; IN_144] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_144::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 145u64 => { let __inp: [G; IN_145] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_145::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 146u64 => { let __inp: [G; IN_146] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_146::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 147u64 => { let __inp: [G; IN_147] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_147::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 148u64 => { let __inp: [G; IN_148] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_148::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 149u64 => { let __inp: [G; IN_149] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_149::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 150u64 => { let __inp: [G; IN_150] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_150::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 151u64 => { let __inp: [G; IN_151] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_151::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 152u64 => { let __inp: [G; IN_152] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_152::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 153u64 => { let __inp: [G; IN_153] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_153::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 154u64 => { let __inp: [G; IN_154] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_154::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 155u64 => { let __inp: [G; IN_155] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_155::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 156u64 => { let __inp: [G; IN_156] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_156::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 157u64 => { let __inp: [G; IN_157] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_157::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 158u64 => { let __inp: [G; IN_158] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_158::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 159u64 => { let __inp: [G; IN_159] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_159::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 160u64 => { let __inp: [G; IN_160] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_160::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 161u64 => { let __inp: [G; IN_161] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_161::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 162u64 => { let __inp: [G; IN_162] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_162::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 163u64 => { let __inp: [G; IN_163] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_163::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 164u64 => { let __inp: [G; IN_164] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_164::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 165u64 => { let __inp: [G; IN_165] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_165::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 166u64 => { let __inp: [G; IN_166] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_166::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 167u64 => { let __inp: [G; IN_167] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_167::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 168u64 => { let __inp: [G; IN_168] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_168::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 169u64 => { let __inp: [G; IN_169] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_169::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 170u64 => { let __inp: [G; IN_170] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_170::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 171u64 => { let __inp: [G; IN_171] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_171::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 172u64 => { let __inp: [G; IN_172] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_172::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 173u64 => { let __inp: [G; IN_173] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_173::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 174u64 => { let __inp: [G; IN_174] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_174::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 175u64 => { let __inp: [G; IN_175] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_175::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 176u64 => { let __inp: [G; IN_176] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_176::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 177u64 => { let __inp: [G; IN_177] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_177::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 178u64 => { let __inp: [G; IN_178] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_178::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 179u64 => { let __inp: [G; IN_179] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_179::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 180u64 => { let __inp: [G; IN_180] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_180::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 181u64 => { let __inp: [G; IN_181] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_181::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 182u64 => { let __inp: [G; IN_182] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_182::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 183u64 => { let __inp: [G; IN_183] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_183::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 184u64 => { let __inp: [G; IN_184] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_184::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 185u64 => { let __inp: [G; IN_185] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_185::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 186u64 => { let __inp: [G; IN_186] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_186::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 187u64 => { let __inp: [G; IN_187] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_187::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 188u64 => { let __inp: [G; IN_188] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_188::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 189u64 => { let __inp: [G; IN_189] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_189::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 190u64 => { let __inp: [G; IN_190] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_190::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 191u64 => { let __inp: [G; IN_191] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_191::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 192u64 => { let __inp: [G; IN_192] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_192::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 193u64 => { let __inp: [G; IN_193] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_193::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 194u64 => { let __inp: [G; IN_194] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_194::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 195u64 => { let __inp: [G; IN_195] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_195::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 196u64 => { let __inp: [G; IN_196] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_196::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 197u64 => { let __inp: [G; IN_197] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_197::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 198u64 => { let __inp: [G; IN_198] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_198::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 199u64 => { let __inp: [G; IN_199] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_199::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 200u64 => { let __inp: [G; IN_200] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_200::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 201u64 => { let __inp: [G; IN_201] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_201::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 202u64 => { let __inp: [G; IN_202] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_202::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 203u64 => { let __inp: [G; IN_203] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_203::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 204u64 => { let __inp: [G; IN_204] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_204::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 205u64 => { let __inp: [G; IN_205] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_205::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 206u64 => { let __inp: [G; IN_206] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_206::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 207u64 => { let __inp: [G; IN_207] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_207::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 208u64 => { let __inp: [G; IN_208] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_208::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 209u64 => { let __inp: [G; IN_209] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_209::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 210u64 => { let __inp: [G; IN_210] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_210::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 211u64 => { let __inp: [G; IN_211] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_211::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 212u64 => { let __inp: [G; IN_212] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_212::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 213u64 => { let __inp: [G; IN_213] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_213::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 214u64 => { let __inp: [G; IN_214] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_214::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 215u64 => { let __inp: [G; IN_215] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_215::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 216u64 => { let __inp: [G; IN_216] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_216::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 217u64 => { let __inp: [G; IN_217] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_217::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 218u64 => { let __inp: [G; IN_218] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_218::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 219u64 => { let __inp: [G; IN_219] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_219::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 220u64 => { let __inp: [G; IN_220] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_220::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 221u64 => { let __inp: [G; IN_221] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_221::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 222u64 => { let __inp: [G; IN_222] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_222::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 223u64 => { let __inp: [G; IN_223] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_223::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 224u64 => { let __inp: [G; IN_224] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_224::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 225u64 => { let __inp: [G; IN_225] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_225::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 226u64 => { let __inp: [G; IN_226] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_226::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 227u64 => { let __inp: [G; IN_227] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_227::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 228u64 => { let __inp: [G; IN_228] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_228::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 229u64 => { let __inp: [G; IN_229] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_229::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 230u64 => { let __inp: [G; IN_230] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_230::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 231u64 => { let __inp: [G; IN_231] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_231::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 232u64 => { let __inp: [G; IN_232] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_232::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 233u64 => { let __inp: [G; IN_233] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_233::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 234u64 => { let __inp: [G; IN_234] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_234::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 235u64 => { let __inp: [G; IN_235] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_235::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 236u64 => { let __inp: [G; IN_236] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_236::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 237u64 => { let __inp: [G; IN_237] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_237::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 238u64 => { let __inp: [G; IN_238] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_238::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 239u64 => { let __inp: [G; IN_239] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_239::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 240u64 => { let __inp: [G; IN_240] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_240::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 241u64 => { let __inp: [G; IN_241] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_241::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 242u64 => { let __inp: [G; IN_242] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_242::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 243u64 => { let __inp: [G; IN_243] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_243::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 244u64 => { let __inp: [G; IN_244] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_244::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 245u64 => { let __inp: [G; IN_245] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_245::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 246u64 => { let __inp: [G; IN_246] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_246::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 247u64 => { let __inp: [G; IN_247] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_247::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 248u64 => { let __inp: [G; IN_248] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_248::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 249u64 => { let __inp: [G; IN_249] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_249::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 250u64 => { let __inp: [G; IN_250] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_250::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 251u64 => { let __inp: [G; IN_251] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_251::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 252u64 => { let __inp: [G; IN_252] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_252::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 253u64 => { let __inp: [G; IN_253] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_253::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 254u64 => { let __inp: [G; IN_254] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_254::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 255u64 => { let __inp: [G; IN_255] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_255::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 256u64 => { let __inp: [G; IN_256] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_256::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 257u64 => { let __inp: [G; IN_257] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_257::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 258u64 => { let __inp: [G; IN_258] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_258::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 259u64 => { let __inp: [G; IN_259] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_259::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 260u64 => { let __inp: [G; IN_260] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_260::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 261u64 => { let __inp: [G; IN_261] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_261::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 262u64 => { let __inp: [G; IN_262] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_262::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 263u64 => { let __inp: [G; IN_263] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_263::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 264u64 => { let __inp: [G; IN_264] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_264::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 265u64 => { let __inp: [G; IN_265] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_265::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 266u64 => { let __inp: [G; IN_266] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_266::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 267u64 => { let __inp: [G; IN_267] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_267::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 268u64 => { let __inp: [G; IN_268] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_268::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 269u64 => { let __inp: [G; IN_269] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_269::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 270u64 => { let __inp: [G; IN_270] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_270::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 271u64 => { let __inp: [G; IN_271] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_271::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 272u64 => { let __inp: [G; IN_272] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_272::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 273u64 => { let __inp: [G; IN_273] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_273::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 274u64 => { let __inp: [G; IN_274] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_274::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 275u64 => { let __inp: [G; IN_275] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_275::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 276u64 => { let __inp: [G; IN_276] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_276::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 277u64 => { let __inp: [G; IN_277] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_277::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 278u64 => { let __inp: [G; IN_278] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_278::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 279u64 => { let __inp: [G; IN_279] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_279::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 280u64 => { let __inp: [G; IN_280] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_280::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 281u64 => { let __inp: [G; IN_281] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_281::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 282u64 => { let __inp: [G; IN_282] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_282::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 283u64 => { let __inp: [G; IN_283] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_283::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 284u64 => { let __inp: [G; IN_284] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_284::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 285u64 => { let __inp: [G; IN_285] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_285::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 286u64 => { let __inp: [G; IN_286] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_286::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 287u64 => { let __inp: [G; IN_287] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_287::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 288u64 => { let __inp: [G; IN_288] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_288::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 289u64 => { let __inp: [G; IN_289] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_289::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 290u64 => { let __inp: [G; IN_290] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_290::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 291u64 => { let __inp: [G; IN_291] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_291::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 292u64 => { let __inp: [G; IN_292] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_292::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 293u64 => { let __inp: [G; IN_293] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_293::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 294u64 => { let __inp: [G; IN_294] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_294::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 295u64 => { let __inp: [G; IN_295] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_295::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 296u64 => { let __inp: [G; IN_296] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_296::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 297u64 => { let __inp: [G; IN_297] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_297::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 298u64 => { let __inp: [G; IN_298] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_298::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 299u64 => { let __inp: [G; IN_299] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_299::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 300u64 => { let __inp: [G; IN_300] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_300::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 301u64 => { let __inp: [G; IN_301] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_301::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 302u64 => { let __inp: [G; IN_302] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_302::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 303u64 => { let __inp: [G; IN_303] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_303::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 304u64 => { let __inp: [G; IN_304] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_304::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 305u64 => { let __inp: [G; IN_305] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_305::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 306u64 => { let __inp: [G; IN_306] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_306::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 307u64 => { let __inp: [G; IN_307] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_307::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 308u64 => { let __inp: [G; IN_308] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_308::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 309u64 => { let __inp: [G; IN_309] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_309::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 310u64 => { let __inp: [G; IN_310] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_310::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 311u64 => { let __inp: [G; IN_311] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_311::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 312u64 => { let __inp: [G; IN_312] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_312::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 313u64 => { let __inp: [G; IN_313] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_313::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 314u64 => { let __inp: [G; IN_314] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_314::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 315u64 => { let __inp: [G; IN_315] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_315::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 316u64 => { let __inp: [G; IN_316] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_316::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 317u64 => { let __inp: [G; IN_317] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_317::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 318u64 => { let __inp: [G; IN_318] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_318::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 319u64 => { let __inp: [G; IN_319] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_319::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 320u64 => { let __inp: [G; IN_320] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_320::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 321u64 => { let __inp: [G; IN_321] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_321::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 322u64 => { let __inp: [G; IN_322] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_322::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 323u64 => { let __inp: [G; IN_323] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_323::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 324u64 => { let __inp: [G; IN_324] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_324::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 325u64 => { let __inp: [G; IN_325] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_325::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 326u64 => { let __inp: [G; IN_326] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_326::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 327u64 => { let __inp: [G; IN_327] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_327::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 328u64 => { let __inp: [G; IN_328] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_328::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 329u64 => { let __inp: [G; IN_329] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_329::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, 330u64 => { let __inp: [G; IN_330] = args.try_into().expect("input size mismatch"); let __out = aiur_fn_330::(__inp, __input_hash, record, io_buffer)?; return Ok(__out.to_vec()); }, _ => { return Err(ExecError::InvalidFunIdx(fun_idx)); }, } } diff --git a/crates/ixvm-codegen/src/aiur_ixvm.rs b/crates/ixvm-codegen/src/aiur_ixvm.rs index 8e4f0b521..1b239a28d 100644 --- a/crates/ixvm-codegen/src/aiur_ixvm.rs +++ b/crates/ixvm-codegen/src/aiur_ixvm.rs @@ -2844,7 +2844,7 @@ fn aiur_fn_707(inp: [G; IN_707], input_hash: u64, rec const INPUT_SIZE_708: usize = 15; const IN_708: usize = 15; const OUT_708: usize = 0; -fn aiur_fn_708(inp: [G; IN_708], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_708], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __r_arr: [G; OUT_707] = { let __args: [G; IN_707] = [__v_1, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(707, &__args[..])?) { [G::ZERO; OUT_707] } else { let (__hash, __hit) = record.function_queries[707].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[707].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[707].bump_multiplicity(__i); } let __ret: [G; OUT_707] = unsafe { *(record.function_queries[707].output_at(__i).as_ptr() as *const [G; OUT_707]) }; __ret }, _ => { aiur_fn_707::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; match __v_15.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_708] = []; record.function_queries[708].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __mc_out___mc_0: [G; 0] = '__mc_0: { match __v_14.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_707] = { let __args: [G; IN_707] = [__v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(707, &__args[..])?) { [G::ZERO; OUT_707] } else { let (__hash, __hit) = record.function_queries[707].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[707].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[707].bump_multiplicity(__i); } let __ret: [G; OUT_707] = unsafe { *(record.function_queries[707].output_at(__i).as_ptr() as *const [G; OUT_707]) }; __ret }, _ => { aiur_fn_707::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = G::from_u64(1); if (__v_16 != __v_17) { return Err(ExecError::AssertEqMismatch { lhs: __v_16.as_canonical_u64(), rhs: __v_17.as_canonical_u64(), msg: Some("CheckEnv: walk reached a constant outside the owned set".to_string()) }); } break '__mc_0 []; }, _ => { break '__mc_0 []; }, } }; let __r_arr: [G; OUT_228] = { let __args: [G; IN_228] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(228, &__args[..])?) { [G::ZERO; OUT_228] } else { let (__hash, __hit) = record.function_queries[228].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[228].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[228].bump_multiplicity(__i); } let __ret: [G; OUT_228] = unsafe { *(record.function_queries[228].output_at(__i).as_ptr() as *const [G; OUT_228]) }; __ret }, _ => { aiur_fn_228::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = __r_arr[1]; let __v_18: G = __r_arr[2]; let __v_19: G = __r_arr[3]; let __v_20: G = __r_arr[4]; let __v_21: G = __r_arr[5]; let __v_22: G = __r_arr[6]; let __v_23: G = __r_arr[7]; let __v_24: G = __r_arr[8]; let __v_25: G = __r_arr[9]; let __v_26: G = __r_arr[10]; let __v_27: G = __r_arr[11]; let __v_28: G = __r_arr[12]; let __v_29: G = __r_arr[13]; let __v_30: G = __r_arr[14]; let __v_31: G = __r_arr[15]; let __v_32: G = __r_arr[16]; let __v_33: G = __r_arr[17]; let __v_34: G = __r_arr[18]; let __v_35: G = __r_arr[19]; let __v_36: G = __r_arr[20]; let __v_37: G = __r_arr[21]; let __v_38: G = __r_arr[22]; let __v_39: G = __r_arr[23]; let __v_40: G = __r_arr[24]; let __v_41: G = __r_arr[25]; let __v_42: G = __r_arr[26]; let __v_43: G = __r_arr[27]; let __v_44: G = __r_arr[28]; let __v_45: G = __r_arr[29]; let __v_46: G = __r_arr[30]; let __v_47: G = __r_arr[31]; let __v_48: G = __r_arr[32]; let __v_49: G = __r_arr[33]; let __v_50: G = __r_arr[34]; let __v_51: G = __r_arr[35]; let __v_52: G = __r_arr[36]; let __v_53: G = __r_arr[37]; let __v_54: G = __r_arr[38]; let __v_55: G = __r_arr[39]; let __v_56: G = __r_arr[40]; let __v_57: G = __r_arr[41]; let __v_58: G = __r_arr[42]; let __v_59: G = __r_arr[43]; let __v_60: G = __r_arr[44]; let __v_61: G = __r_arr[45]; let __v_62: G = __r_arr[46]; let __v_63: G = __r_arr[47]; match __v_16.as_canonical_u64() { _ => { let __mc_out___mc_1: [G; 0] = '__mc_1: { match __v_16.as_canonical_u64() { 8u64 => { let __v_64: G = G::from_u64(0); let __r_arr: [G; OUT_700] = { let __args: [G; IN_700] = [__v_0, __v_1, __v_17, __v_17, __v_64]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(700, &__args[..])?) { [G::ZERO; OUT_700] } else { let (__hash, __hit) = record.function_queries[700].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[700].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[700].bump_multiplicity(__i); } let __ret: [G; OUT_700] = unsafe { *(record.function_queries[700].output_at(__i).as_ptr() as *const [G; OUT_700]) }; __ret }, _ => { aiur_fn_700::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_1 []; }, 7u64 => { match __v_17.as_canonical_u64() { _ => { let __r_arr: [G; OUT_690] = { let __args: [G; IN_690] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(690, &__args[..])?) { [G::ZERO; OUT_690] } else { let (__hash, __hit) = record.function_queries[690].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[690].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[690].bump_multiplicity(__i); } let __ret: [G; OUT_690] = unsafe { *(record.function_queries[690].output_at(__i).as_ptr() as *const [G; OUT_690]) }; __ret }, _ => { aiur_fn_690::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_708] = { let __args: [G; IN_708] = [__v_0, __v_25, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(708, &__args[..])?) { [G::ZERO; OUT_708] } else { let (__hash, __hit) = record.function_queries[708].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[708].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[708].bump_multiplicity(__i); } let __ret: [G; OUT_708] = unsafe { *(record.function_queries[708].output_at(__i).as_ptr() as *const [G; OUT_708]) }; __ret }, _ => { aiur_fn_708::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_1 []; }, } }, 6u64 => { match __v_17.as_canonical_u64() { _ => { let __r_arr: [G; OUT_690] = { let __args: [G; IN_690] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(690, &__args[..])?) { [G::ZERO; OUT_690] } else { let (__hash, __hit) = record.function_queries[690].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[690].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[690].bump_multiplicity(__i); } let __ret: [G; OUT_690] = unsafe { *(record.function_queries[690].output_at(__i).as_ptr() as *const [G; OUT_690]) }; __ret }, _ => { aiur_fn_690::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_708] = { let __args: [G; IN_708] = [__v_0, __v_25, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(708, &__args[..])?) { [G::ZERO; OUT_708] } else { let (__hash, __hit) = record.function_queries[708].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[708].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[708].bump_multiplicity(__i); } let __ret: [G; OUT_708] = unsafe { *(record.function_queries[708].output_at(__i).as_ptr() as *const [G; OUT_708]) }; __ret }, _ => { aiur_fn_708::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_1 []; }, } }, 4u64 => { match __v_17.as_canonical_u64() { _ => { let __r_arr: [G; OUT_690] = { let __args: [G; IN_690] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(690, &__args[..])?) { [G::ZERO; OUT_690] } else { let (__hash, __hit) = record.function_queries[690].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[690].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[690].bump_multiplicity(__i); } let __ret: [G; OUT_690] = unsafe { *(record.function_queries[690].output_at(__i).as_ptr() as *const [G; OUT_690]) }; __ret }, _ => { aiur_fn_690::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_708] = { let __args: [G; IN_708] = [__v_0, __v_33, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(708, &__args[..])?) { [G::ZERO; OUT_708] } else { let (__hash, __hit) = record.function_queries[708].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[708].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[708].bump_multiplicity(__i); } let __ret: [G; OUT_708] = unsafe { *(record.function_queries[708].output_at(__i).as_ptr() as *const [G; OUT_708]) }; __ret }, _ => { aiur_fn_708::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_1 []; }, } }, 5u64 => { match __v_17.as_canonical_u64() { _ => { let __r_arr: [G; OUT_690] = { let __args: [G; IN_690] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(690, &__args[..])?) { [G::ZERO; OUT_690] } else { let (__hash, __hit) = record.function_queries[690].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[690].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[690].bump_multiplicity(__i); } let __ret: [G; OUT_690] = unsafe { *(record.function_queries[690].output_at(__i).as_ptr() as *const [G; OUT_690]) }; __ret }, _ => { aiur_fn_690::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_708] = { let __args: [G; IN_708] = [__v_0, __v_25, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(708, &__args[..])?) { [G::ZERO; OUT_708] } else { let (__hash, __hit) = record.function_queries[708].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[708].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[708].bump_multiplicity(__i); } let __ret: [G; OUT_708] = unsafe { *(record.function_queries[708].output_at(__i).as_ptr() as *const [G; OUT_708]) }; __ret }, _ => { aiur_fn_708::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_1 []; }, } }, _ => { let __r_arr: [G; OUT_690] = { let __args: [G; IN_690] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(690, &__args[..])?) { [G::ZERO; OUT_690] } else { let (__hash, __hit) = record.function_queries[690].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[690].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[690].bump_multiplicity(__i); } let __ret: [G; OUT_690] = unsafe { *(record.function_queries[690].output_at(__i).as_ptr() as *const [G; OUT_690]) }; __ret }, _ => { aiur_fn_690::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_1 []; }, } }; let __r_arr: [G; OUT_696] = { let __args: [G; IN_696] = [__v_16, __v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53, __v_54, __v_55, __v_56, __v_57, __v_58, __v_59, __v_60, __v_61, __v_62, __v_63]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(696, &__args[..])?) { [G::ZERO; OUT_696] } else { let (__hash, __hit) = record.function_queries[696].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[696].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[696].bump_multiplicity(__i); } let __ret: [G; OUT_696] = unsafe { *(record.function_queries[696].output_at(__i).as_ptr() as *const [G; OUT_696]) }; __ret }, _ => { aiur_fn_696::(__args, __hash, record, io_buffer)? }, } } }; let __v_64: G = __r_arr[0]; let __v_65: G = G::from_u64(0); let __r_arr: [G; OUT_709] = { let __args: [G; IN_709] = [__v_0, __v_62, __v_64, __v_65, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(709, &__args[..])?) { [G::ZERO; OUT_709] } else { let (__hash, __hit) = record.function_queries[709].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[709].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[709].bump_multiplicity(__i); } let __ret: [G; OUT_709] = unsafe { *(record.function_queries[709].output_at(__i).as_ptr() as *const [G; OUT_709]) }; __ret }, _ => { aiur_fn_709::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_708] = []; record.function_queries[708].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, } }) } +fn aiur_fn_708(inp: [G; IN_708], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_708], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __v_14: G = inp[14]; let __r_arr: [G; OUT_707] = { let __args: [G; IN_707] = [__v_1, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(707, &__args[..])?) { [G::ZERO; OUT_707] } else { let (__hash, __hit) = record.function_queries[707].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[707].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[707].bump_multiplicity(__i); } let __ret: [G; OUT_707] = unsafe { *(record.function_queries[707].output_at(__i).as_ptr() as *const [G; OUT_707]) }; __ret }, _ => { aiur_fn_707::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; match __v_15.as_canonical_u64() { 1u64 => { let __v_16: G = G::from_u64(2); let __v_17: G = __v_14 - __v_16; let __v_18: G = G::from_bool(__v_17 == G::ZERO); let __v_19: G = G::from_u64(0); if (__v_18 != __v_19) { return Err(ExecError::AssertEqMismatch { lhs: __v_18.as_canonical_u64(), rhs: __v_19.as_canonical_u64(), msg: Some("Check: target is in the assumption set".to_string()) }); } let __ret: [G; OUT_708] = []; record.function_queries[708].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { let __mc_out___mc_0: [G; 1] = '__mc_0: { match __v_14.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_707] = { let __args: [G; IN_707] = [__v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(707, &__args[..])?) { [G::ZERO; OUT_707] } else { let (__hash, __hit) = record.function_queries[707].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[707].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[707].bump_multiplicity(__i); } let __ret: [G; OUT_707] = unsafe { *(record.function_queries[707].output_at(__i).as_ptr() as *const [G; OUT_707]) }; __ret }, _ => { aiur_fn_707::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = G::from_u64(1); if (__v_16 != __v_17) { return Err(ExecError::AssertEqMismatch { lhs: __v_16.as_canonical_u64(), rhs: __v_17.as_canonical_u64(), msg: Some("CheckEnv: walk reached a constant outside the owned set".to_string()) }); } break '__mc_0 [__v_17]; }, _ => { let __v_16: G = G::from_u64(0); break '__mc_0 [__v_16]; }, } }; let __v_16: G = __mc_out___mc_0[0]; let __r_arr: [G; OUT_228] = { let __args: [G; IN_228] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(228, &__args[..])?) { [G::ZERO; OUT_228] } else { let (__hash, __hit) = record.function_queries[228].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[228].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[228].bump_multiplicity(__i); } let __ret: [G; OUT_228] = unsafe { *(record.function_queries[228].output_at(__i).as_ptr() as *const [G; OUT_228]) }; __ret }, _ => { aiur_fn_228::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = __r_arr[1]; let __v_19: G = __r_arr[2]; let __v_20: G = __r_arr[3]; let __v_21: G = __r_arr[4]; let __v_22: G = __r_arr[5]; let __v_23: G = __r_arr[6]; let __v_24: G = __r_arr[7]; let __v_25: G = __r_arr[8]; let __v_26: G = __r_arr[9]; let __v_27: G = __r_arr[10]; let __v_28: G = __r_arr[11]; let __v_29: G = __r_arr[12]; let __v_30: G = __r_arr[13]; let __v_31: G = __r_arr[14]; let __v_32: G = __r_arr[15]; let __v_33: G = __r_arr[16]; let __v_34: G = __r_arr[17]; let __v_35: G = __r_arr[18]; let __v_36: G = __r_arr[19]; let __v_37: G = __r_arr[20]; let __v_38: G = __r_arr[21]; let __v_39: G = __r_arr[22]; let __v_40: G = __r_arr[23]; let __v_41: G = __r_arr[24]; let __v_42: G = __r_arr[25]; let __v_43: G = __r_arr[26]; let __v_44: G = __r_arr[27]; let __v_45: G = __r_arr[28]; let __v_46: G = __r_arr[29]; let __v_47: G = __r_arr[30]; let __v_48: G = __r_arr[31]; let __v_49: G = __r_arr[32]; let __v_50: G = __r_arr[33]; let __v_51: G = __r_arr[34]; let __v_52: G = __r_arr[35]; let __v_53: G = __r_arr[36]; let __v_54: G = __r_arr[37]; let __v_55: G = __r_arr[38]; let __v_56: G = __r_arr[39]; let __v_57: G = __r_arr[40]; let __v_58: G = __r_arr[41]; let __v_59: G = __r_arr[42]; let __v_60: G = __r_arr[43]; let __v_61: G = __r_arr[44]; let __v_62: G = __r_arr[45]; let __v_63: G = __r_arr[46]; let __v_64: G = __r_arr[47]; match __v_17.as_canonical_u64() { _ => { let __mc_out___mc_1: [G; 0] = '__mc_1: { match __v_17.as_canonical_u64() { 8u64 => { let __v_65: G = G::from_u64(0); let __r_arr: [G; OUT_700] = { let __args: [G; IN_700] = [__v_0, __v_1, __v_18, __v_18, __v_65]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(700, &__args[..])?) { [G::ZERO; OUT_700] } else { let (__hash, __hit) = record.function_queries[700].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[700].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[700].bump_multiplicity(__i); } let __ret: [G; OUT_700] = unsafe { *(record.function_queries[700].output_at(__i).as_ptr() as *const [G; OUT_700]) }; __ret }, _ => { aiur_fn_700::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_1 []; }, 7u64 => { match __v_18.as_canonical_u64() { _ => { let __r_arr: [G; OUT_690] = { let __args: [G; IN_690] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(690, &__args[..])?) { [G::ZERO; OUT_690] } else { let (__hash, __hit) = record.function_queries[690].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[690].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[690].bump_multiplicity(__i); } let __ret: [G; OUT_690] = unsafe { *(record.function_queries[690].output_at(__i).as_ptr() as *const [G; OUT_690]) }; __ret }, _ => { aiur_fn_690::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_708] = { let __args: [G; IN_708] = [__v_0, __v_26, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(708, &__args[..])?) { [G::ZERO; OUT_708] } else { let (__hash, __hit) = record.function_queries[708].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[708].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[708].bump_multiplicity(__i); } let __ret: [G; OUT_708] = unsafe { *(record.function_queries[708].output_at(__i).as_ptr() as *const [G; OUT_708]) }; __ret }, _ => { aiur_fn_708::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_1 []; }, } }, 6u64 => { match __v_18.as_canonical_u64() { _ => { let __r_arr: [G; OUT_690] = { let __args: [G; IN_690] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(690, &__args[..])?) { [G::ZERO; OUT_690] } else { let (__hash, __hit) = record.function_queries[690].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[690].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[690].bump_multiplicity(__i); } let __ret: [G; OUT_690] = unsafe { *(record.function_queries[690].output_at(__i).as_ptr() as *const [G; OUT_690]) }; __ret }, _ => { aiur_fn_690::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_708] = { let __args: [G; IN_708] = [__v_0, __v_26, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(708, &__args[..])?) { [G::ZERO; OUT_708] } else { let (__hash, __hit) = record.function_queries[708].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[708].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[708].bump_multiplicity(__i); } let __ret: [G; OUT_708] = unsafe { *(record.function_queries[708].output_at(__i).as_ptr() as *const [G; OUT_708]) }; __ret }, _ => { aiur_fn_708::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_1 []; }, } }, 4u64 => { match __v_18.as_canonical_u64() { _ => { let __r_arr: [G; OUT_690] = { let __args: [G; IN_690] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(690, &__args[..])?) { [G::ZERO; OUT_690] } else { let (__hash, __hit) = record.function_queries[690].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[690].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[690].bump_multiplicity(__i); } let __ret: [G; OUT_690] = unsafe { *(record.function_queries[690].output_at(__i).as_ptr() as *const [G; OUT_690]) }; __ret }, _ => { aiur_fn_690::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_708] = { let __args: [G; IN_708] = [__v_0, __v_34, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(708, &__args[..])?) { [G::ZERO; OUT_708] } else { let (__hash, __hit) = record.function_queries[708].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[708].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[708].bump_multiplicity(__i); } let __ret: [G; OUT_708] = unsafe { *(record.function_queries[708].output_at(__i).as_ptr() as *const [G; OUT_708]) }; __ret }, _ => { aiur_fn_708::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_1 []; }, } }, 5u64 => { match __v_18.as_canonical_u64() { _ => { let __r_arr: [G; OUT_690] = { let __args: [G; IN_690] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(690, &__args[..])?) { [G::ZERO; OUT_690] } else { let (__hash, __hit) = record.function_queries[690].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[690].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[690].bump_multiplicity(__i); } let __ret: [G; OUT_690] = unsafe { *(record.function_queries[690].output_at(__i).as_ptr() as *const [G; OUT_690]) }; __ret }, _ => { aiur_fn_690::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_708] = { let __args: [G; IN_708] = [__v_0, __v_26, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(708, &__args[..])?) { [G::ZERO; OUT_708] } else { let (__hash, __hit) = record.function_queries[708].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[708].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[708].bump_multiplicity(__i); } let __ret: [G; OUT_708] = unsafe { *(record.function_queries[708].output_at(__i).as_ptr() as *const [G; OUT_708]) }; __ret }, _ => { aiur_fn_708::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_1 []; }, } }, _ => { let __r_arr: [G; OUT_690] = { let __args: [G; IN_690] = [__v_0, __v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(690, &__args[..])?) { [G::ZERO; OUT_690] } else { let (__hash, __hit) = record.function_queries[690].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[690].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[690].bump_multiplicity(__i); } let __ret: [G; OUT_690] = unsafe { *(record.function_queries[690].output_at(__i).as_ptr() as *const [G; OUT_690]) }; __ret }, _ => { aiur_fn_690::(__args, __hash, record, io_buffer)? }, } } }; break '__mc_1 []; }, } }; let __r_arr: [G; OUT_696] = { let __args: [G; IN_696] = [__v_17, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42, __v_43, __v_44, __v_45, __v_46, __v_47, __v_48, __v_49, __v_50, __v_51, __v_52, __v_53, __v_54, __v_55, __v_56, __v_57, __v_58, __v_59, __v_60, __v_61, __v_62, __v_63, __v_64]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(696, &__args[..])?) { [G::ZERO; OUT_696] } else { let (__hash, __hit) = record.function_queries[696].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[696].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[696].bump_multiplicity(__i); } let __ret: [G; OUT_696] = unsafe { *(record.function_queries[696].output_at(__i).as_ptr() as *const [G; OUT_696]) }; __ret }, _ => { aiur_fn_696::(__args, __hash, record, io_buffer)? }, } } }; let __v_65: G = __r_arr[0]; let __v_66: G = G::from_u64(0); let __r_arr: [G; OUT_709] = { let __args: [G; IN_709] = [__v_0, __v_63, __v_65, __v_66, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_16]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(709, &__args[..])?) { [G::ZERO; OUT_709] } else { let (__hash, __hit) = record.function_queries[709].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[709].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[709].bump_multiplicity(__i); } let __ret: [G; OUT_709] = unsafe { *(record.function_queries[709].output_at(__i).as_ptr() as *const [G; OUT_709]) }; __ret }, _ => { aiur_fn_709::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_708] = []; record.function_queries[708].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, } }, } }) } const INPUT_SIZE_709: usize = 17; const IN_709: usize = 17; const OUT_709: usize = 0; @@ -2852,11 +2852,11 @@ fn aiur_fn_709(inp: [G; IN_709], input_hash: u64, rec const INPUT_SIZE_710: usize = 14; const IN_710: usize = 14; const OUT_710: usize = 0; -fn aiur_fn_710(inp: [G; IN_710], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_710], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_1.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_14: G = __loaded[0]; let __v_15: G = __loaded[1]; let __v_16: G = __loaded[2]; match __v_14.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_710] = []; record.function_queries[710].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __v_17: G = G::from_u64(1); let __r_arr: [G; OUT_708] = { let __args: [G; IN_708] = [__v_0, __v_15, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_17]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(708, &__args[..])?) { [G::ZERO; OUT_708] } else { let (__hash, __hit) = record.function_queries[708].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[708].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[708].bump_multiplicity(__i); } let __ret: [G; OUT_708] = unsafe { *(record.function_queries[708].output_at(__i).as_ptr() as *const [G; OUT_708]) }; __ret }, _ => { aiur_fn_708::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_710] = { let __args: [G; IN_710] = [__v_0, __v_16, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(710, &__args[..])?) { [G::ZERO; OUT_710] } else { let (__hash, __hit) = record.function_queries[710].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[710].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[710].bump_multiplicity(__i); } let __ret: [G; OUT_710] = unsafe { *(record.function_queries[710].output_at(__i).as_ptr() as *const [G; OUT_710]) }; __ret }, _ => { aiur_fn_710::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_710] = []; record.function_queries[710].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_14.as_canonical_u64())); }, } }) } +fn aiur_fn_710(inp: [G; IN_710], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_710], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = inp[8]; let __v_9: G = inp[9]; let __v_10: G = inp[10]; let __v_11: G = inp[11]; let __v_12: G = inp[12]; let __v_13: G = inp[13]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_1.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_14: G = __loaded[0]; let __v_15: G = __loaded[1]; let __v_16: G = __loaded[2]; match __v_14.as_canonical_u64() { 1u64 => { let __ret: [G; OUT_710] = []; record.function_queries[710].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_707] = { let __args: [G; IN_707] = [__v_15, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(707, &__args[..])?) { [G::ZERO; OUT_707] } else { let (__hash, __hit) = record.function_queries[707].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[707].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[707].bump_multiplicity(__i); } let __ret: [G; OUT_707] = unsafe { *(record.function_queries[707].output_at(__i).as_ptr() as *const [G; OUT_707]) }; __ret }, _ => { aiur_fn_707::(__args, __hash, record, io_buffer)? }, } } }; let __v_17: G = __r_arr[0]; let __v_18: G = G::from_u64(0); if (__v_17 != __v_18) { return Err(ExecError::AssertEqMismatch { lhs: __v_17.as_canonical_u64(), rhs: __v_18.as_canonical_u64(), msg: Some("CheckEnv: owned constant is in the assumption set".to_string()) }); } let __v_19: G = G::from_u64(1); let __r_arr: [G; OUT_708] = { let __args: [G; IN_708] = [__v_0, __v_15, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13, __v_19]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(708, &__args[..])?) { [G::ZERO; OUT_708] } else { let (__hash, __hit) = record.function_queries[708].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[708].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[708].bump_multiplicity(__i); } let __ret: [G; OUT_708] = unsafe { *(record.function_queries[708].output_at(__i).as_ptr() as *const [G; OUT_708]) }; __ret }, _ => { aiur_fn_708::(__args, __hash, record, io_buffer)? }, } } }; let __r_arr: [G; OUT_710] = { let __args: [G; IN_710] = [__v_0, __v_16, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_12, __v_13]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(710, &__args[..])?) { [G::ZERO; OUT_710] } else { let (__hash, __hit) = record.function_queries[710].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[710].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[710].bump_multiplicity(__i); } let __ret: [G; OUT_710] = unsafe { *(record.function_queries[710].output_at(__i).as_ptr() as *const [G; OUT_710]) }; __ret }, _ => { aiur_fn_710::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_710] = []; record.function_queries[710].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_14.as_canonical_u64())); }, } }) } const INPUT_SIZE_711: usize = 4; const IN_711: usize = 4; const OUT_711: usize = 0; -fn aiur_fn_711(inp: [G; IN_711], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_711], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; match __v_2.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_705] = { let __args: [G; IN_705] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(705, &__args[..])?) { [G::ZERO; OUT_705] } else { let (__hash, __hit) = record.function_queries[705].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[705].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[705].bump_multiplicity(__i); } let __ret: [G; OUT_705] = unsafe { *(record.function_queries[705].output_at(__i).as_ptr() as *const [G; OUT_705]) }; __ret }, _ => { aiur_fn_705::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __r_arr: [G; OUT_471] = { let __args: [G; IN_471] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(471, &__args[..])?) { [G::ZERO; OUT_471] } else { let (__hash, __hit) = record.function_queries[471].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[471].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[471].bump_multiplicity(__i); } let __ret: [G; OUT_471] = unsafe { *(record.function_queries[471].output_at(__i).as_ptr() as *const [G; OUT_471]) }; __ret }, _ => { aiur_fn_471::(__args, __hash, record, io_buffer)? }, } } }; let __v_5: G = __r_arr[0]; let __v_6: G = __r_arr[1]; let __v_7: G = __r_arr[2]; let __v_8: G = __r_arr[3]; let __v_9: G = __r_arr[4]; let __v_10: G = __r_arr[5]; match __v_5.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_713] = { let __args: [G; IN_713] = [__v_0, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(713, &__args[..])?) { [G::ZERO; OUT_713] } else { let (__hash, __hit) = record.function_queries[713].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[713].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[713].bump_multiplicity(__i); } let __ret: [G; OUT_713] = unsafe { *(record.function_queries[713].output_at(__i).as_ptr() as *const [G; OUT_713]) }; __ret }, _ => { aiur_fn_713::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_711] = []; record.function_queries[711].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_712] = { let __args: [G; IN_712] = [__v_0, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(712, &__args[..])?) { [G::ZERO; OUT_712] } else { let (__hash, __hit) = record.function_queries[712].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[712].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[712].bump_multiplicity(__i); } let __ret: [G; OUT_712] = unsafe { *(record.function_queries[712].output_at(__i).as_ptr() as *const [G; OUT_712]) }; __ret }, _ => { aiur_fn_712::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_711] = []; record.function_queries[711].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }, 0u64 => { let __r_arr: [G; OUT_705] = { let __args: [G; IN_705] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(705, &__args[..])?) { [G::ZERO; OUT_705] } else { let (__hash, __hit) = record.function_queries[705].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[705].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[705].bump_multiplicity(__i); } let __ret: [G; OUT_705] = unsafe { *(record.function_queries[705].output_at(__i).as_ptr() as *const [G; OUT_705]) }; __ret }, _ => { aiur_fn_705::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __v_5: G = G::from_u64(1); let __r_arr: [G; OUT_706] = { let __args: [G; IN_706] = [__v_4, __v_5, __v_5, __v_5, __v_5, __v_5, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(706, &__args[..])?) { [G::ZERO; OUT_706] } else { let (__hash, __hit) = record.function_queries[706].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[706].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[706].bump_multiplicity(__i); } let __ret: [G; OUT_706] = unsafe { *(record.function_queries[706].output_at(__i).as_ptr() as *const [G; OUT_706]) }; __ret }, _ => { aiur_fn_706::(__args, __hash, record, io_buffer)? }, } } }; let __v_6: G = __r_arr[0]; let __v_7: G = __r_arr[1]; let __v_8: G = __r_arr[2]; let __v_9: G = __r_arr[3]; let __v_10: G = __r_arr[4]; let __v_11: G = __r_arr[5]; let __r_arr: [G; OUT_705] = { let __args: [G; IN_705] = [__v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(705, &__args[..])?) { [G::ZERO; OUT_705] } else { let (__hash, __hit) = record.function_queries[705].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[705].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[705].bump_multiplicity(__i); } let __ret: [G; OUT_705] = unsafe { *(record.function_queries[705].output_at(__i).as_ptr() as *const [G; OUT_705]) }; __ret }, _ => { aiur_fn_705::(__args, __hash, record, io_buffer)? }, } } }; let __v_12: G = __r_arr[0]; let __r_arr: [G; OUT_706] = { let __args: [G; IN_706] = [__v_12, __v_5, __v_5, __v_5, __v_5, __v_5, __v_5]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(706, &__args[..])?) { [G::ZERO; OUT_706] } else { let (__hash, __hit) = record.function_queries[706].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[706].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[706].bump_multiplicity(__i); } let __ret: [G; OUT_706] = unsafe { *(record.function_queries[706].output_at(__i).as_ptr() as *const [G; OUT_706]) }; __ret }, _ => { aiur_fn_706::(__args, __hash, record, io_buffer)? }, } } }; let __v_13: G = __r_arr[0]; let __v_14: G = __r_arr[1]; let __v_15: G = __r_arr[2]; let __v_16: G = __r_arr[3]; let __v_17: G = __r_arr[4]; let __v_18: G = __r_arr[5]; let __r_arr: [G; OUT_710] = { let __args: [G; IN_710] = [__v_0, __v_4, __v_6, __v_7, __v_8, __v_9, __v_10, __v_11, __v_13, __v_14, __v_15, __v_16, __v_17, __v_18]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(710, &__args[..])?) { [G::ZERO; OUT_710] } else { let (__hash, __hit) = record.function_queries[710].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[710].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[710].bump_multiplicity(__i); } let __ret: [G; OUT_710] = unsafe { *(record.function_queries[710].output_at(__i).as_ptr() as *const [G; OUT_710]) }; __ret }, _ => { aiur_fn_710::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_711] = []; record.function_queries[711].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } +fn aiur_fn_711(inp: [G; IN_711], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_711], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __r_arr: [G; OUT_705] = { let __args: [G; IN_705] = [__v_1]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(705, &__args[..])?) { [G::ZERO; OUT_705] } else { let (__hash, __hit) = record.function_queries[705].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[705].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[705].bump_multiplicity(__i); } let __ret: [G; OUT_705] = unsafe { *(record.function_queries[705].output_at(__i).as_ptr() as *const [G; OUT_705]) }; __ret }, _ => { aiur_fn_705::(__args, __hash, record, io_buffer)? }, } } }; let __v_4: G = __r_arr[0]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_4.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_5: G = __loaded[0]; let __v_6: G = __loaded[1]; let __v_7: G = __loaded[2]; let __mc_out___mc_0: [G; 0] = '__mc_0: { match __v_5.as_canonical_u64() { 1u64 => { let __v_8: G = G::from_u64(0); let __v_9: G = G::from_u64(1); if (__v_8 != __v_9) { return Err(ExecError::AssertEqMismatch { lhs: __v_8.as_canonical_u64(), rhs: __v_9.as_canonical_u64(), msg: Some("CheckEnv: owned tree has no leaves".to_string()) }); } break '__mc_0 []; }, 0u64 => { break '__mc_0 []; }, _ => { return Err(ExecError::MatchNoCase(__v_5.as_canonical_u64())); }, } }; match __v_2.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_471] = { let __args: [G; IN_471] = [__v_0]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(471, &__args[..])?) { [G::ZERO; OUT_471] } else { let (__hash, __hit) = record.function_queries[471].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[471].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[471].bump_multiplicity(__i); } let __ret: [G; OUT_471] = unsafe { *(record.function_queries[471].output_at(__i).as_ptr() as *const [G; OUT_471]) }; __ret }, _ => { aiur_fn_471::(__args, __hash, record, io_buffer)? }, } } }; let __v_8: G = __r_arr[0]; let __v_9: G = __r_arr[1]; let __v_10: G = __r_arr[2]; let __v_11: G = __r_arr[3]; let __v_12: G = __r_arr[4]; let __v_13: G = __r_arr[5]; match __v_8.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_713] = { let __args: [G; IN_713] = [__v_0, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(713, &__args[..])?) { [G::ZERO; OUT_713] } else { let (__hash, __hit) = record.function_queries[713].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[713].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[713].bump_multiplicity(__i); } let __ret: [G; OUT_713] = unsafe { *(record.function_queries[713].output_at(__i).as_ptr() as *const [G; OUT_713]) }; __ret }, _ => { aiur_fn_713::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_711] = []; record.function_queries[711].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_712] = { let __args: [G; IN_712] = [__v_0, __v_4]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(712, &__args[..])?) { [G::ZERO; OUT_712] } else { let (__hash, __hit) = record.function_queries[712].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[712].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[712].bump_multiplicity(__i); } let __ret: [G; OUT_712] = unsafe { *(record.function_queries[712].output_at(__i).as_ptr() as *const [G; OUT_712]) }; __ret }, _ => { aiur_fn_712::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_711] = []; record.function_queries[711].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_8.as_canonical_u64())); }, } }, 0u64 => { let __v_8: G = G::from_u64(1); let __r_arr: [G; OUT_706] = { let __args: [G; IN_706] = [__v_4, __v_8, __v_8, __v_8, __v_8, __v_8, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(706, &__args[..])?) { [G::ZERO; OUT_706] } else { let (__hash, __hit) = record.function_queries[706].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[706].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[706].bump_multiplicity(__i); } let __ret: [G; OUT_706] = unsafe { *(record.function_queries[706].output_at(__i).as_ptr() as *const [G; OUT_706]) }; __ret }, _ => { aiur_fn_706::(__args, __hash, record, io_buffer)? }, } } }; let __v_9: G = __r_arr[0]; let __v_10: G = __r_arr[1]; let __v_11: G = __r_arr[2]; let __v_12: G = __r_arr[3]; let __v_13: G = __r_arr[4]; let __v_14: G = __r_arr[5]; let __r_arr: [G; OUT_705] = { let __args: [G; IN_705] = [__v_3]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(705, &__args[..])?) { [G::ZERO; OUT_705] } else { let (__hash, __hit) = record.function_queries[705].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[705].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[705].bump_multiplicity(__i); } let __ret: [G; OUT_705] = unsafe { *(record.function_queries[705].output_at(__i).as_ptr() as *const [G; OUT_705]) }; __ret }, _ => { aiur_fn_705::(__args, __hash, record, io_buffer)? }, } } }; let __v_15: G = __r_arr[0]; let __r_arr: [G; OUT_706] = { let __args: [G; IN_706] = [__v_15, __v_8, __v_8, __v_8, __v_8, __v_8, __v_8]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(706, &__args[..])?) { [G::ZERO; OUT_706] } else { let (__hash, __hit) = record.function_queries[706].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[706].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[706].bump_multiplicity(__i); } let __ret: [G; OUT_706] = unsafe { *(record.function_queries[706].output_at(__i).as_ptr() as *const [G; OUT_706]) }; __ret }, _ => { aiur_fn_706::(__args, __hash, record, io_buffer)? }, } } }; let __v_16: G = __r_arr[0]; let __v_17: G = __r_arr[1]; let __v_18: G = __r_arr[2]; let __v_19: G = __r_arr[3]; let __v_20: G = __r_arr[4]; let __v_21: G = __r_arr[5]; let __r_arr: [G; OUT_710] = { let __args: [G; IN_710] = [__v_0, __v_4, __v_9, __v_10, __v_11, __v_12, __v_13, __v_14, __v_16, __v_17, __v_18, __v_19, __v_20, __v_21]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(710, &__args[..])?) { [G::ZERO; OUT_710] } else { let (__hash, __hit) = record.function_queries[710].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[710].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[710].bump_multiplicity(__i); } let __ret: [G; OUT_710] = unsafe { *(record.function_queries[710].output_at(__i).as_ptr() as *const [G; OUT_710]) }; __ret }, _ => { aiur_fn_710::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_711] = []; record.function_queries[711].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_2.as_canonical_u64())); }, } }) } const INPUT_SIZE_712: usize = 2; const IN_712: usize = 2; const OUT_712: usize = 0; @@ -3020,7 +3020,7 @@ fn aiur_fn_751(inp: [G; IN_751], input_hash: u64, rec const INPUT_SIZE_752: usize = 8; const IN_752: usize = 8; const OUT_752: usize = 0; -fn aiur_fn_752(inp: [G; IN_752], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_752], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = G::from_u64(0); let __io_pair: (G, G) = { let __key: [G; 8] = [__v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7]; let __info = io_buffer.get_info(__v_8, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_9: G = __io_pair.0; let __v_10: G = __io_pair.1; let __r_arr: [G; OUT_5] = { let __args: [G; IN_5] = [__v_8, __v_9, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(5, &__args[..])?) { [G::ZERO; OUT_5] } else { let (__hash, __hit) = record.function_queries[5].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[5].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[5].bump_multiplicity(__i); } let __ret: [G; OUT_5] = unsafe { *(record.function_queries[5].output_at(__i).as_ptr() as *const [G; OUT_5]) }; __ret }, _ => { aiur_fn_5::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_u64(103); let __v_13: G = G::from_u64(230); let __v_14: G = G::from_u64(9); let __v_15: G = G::from_u64(106); let __v_16: G = G::from_u64(133); let __v_17: G = G::from_u64(174); let __v_18: G = G::from_u64(187); let __v_19: G = G::from_u64(114); let __v_20: G = G::from_u64(243); let __v_21: G = G::from_u64(110); let __v_22: G = G::from_u64(60); let __v_23: G = G::from_u64(58); let __v_24: G = G::from_u64(245); let __v_25: G = G::from_u64(79); let __v_26: G = G::from_u64(165); let __v_27: G = G::from_u64(127); let __v_28: G = G::from_u64(82); let __v_29: G = G::from_u64(14); let __v_30: G = G::from_u64(81); let __v_31: G = G::from_u64(140); let __v_32: G = G::from_u64(104); let __v_33: G = G::from_u64(5); let __v_34: G = G::from_u64(155); let __v_35: G = G::from_u64(171); let __v_36: G = G::from_u64(217); let __v_37: G = G::from_u64(131); let __v_38: G = G::from_u64(31); let __v_39: G = G::from_u64(25); let __v_40: G = G::from_u64(205); let __v_41: G = G::from_u64(224); let __v_42: G = G::from_u64(91); let __v_43: G = G::from_u64(1); let __v_44: G = { let __values: [G; 3] = [__v_43, __v_43, __v_43]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_45: G = { let __values: [G; 8] = [__v_8, __v_8, __v_8, __v_8, __v_8, __v_8, __v_8, __v_8]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_46: G = { let __values: [G; 32] = [__v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_12, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42]; let __mq = record.memory_queries.get_index_mut(12).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_47: G = { let __values: [G; 34] = [__v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43]; let __mq = record.memory_queries.get_index_mut(13).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_11, __v_44, __v_8, __v_8, __v_45, __v_46, __v_47]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_48: G = __r_arr[0]; let __r_arr: [G; OUT_25] = { let __args: [G; IN_25] = [__v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(25, &__args[..])?) { [G::ZERO; OUT_25] } else { let (__hash, __hit) = record.function_queries[25].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[25].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[25].bump_multiplicity(__i); } let __ret: [G; OUT_25] = unsafe { *(record.function_queries[25].output_at(__i).as_ptr() as *const [G; OUT_25]) }; __ret }, _ => { aiur_fn_25::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __v_50: G = __r_arr[1]; let __v_51: G = __r_arr[2]; let __v_52: G = __r_arr[3]; let __v_53: G = __r_arr[4]; let __v_54: G = __r_arr[5]; let __v_55: G = __r_arr[6]; let __v_56: G = __r_arr[7]; let __v_57: G = __r_arr[8]; let __v_58: G = __r_arr[9]; let __v_59: G = __r_arr[10]; let __v_60: G = __r_arr[11]; let __v_61: G = __r_arr[12]; let __v_62: G = __r_arr[13]; let __v_63: G = __r_arr[14]; let __v_64: G = __r_arr[15]; let __v_65: G = __r_arr[16]; let __v_66: G = __r_arr[17]; let __v_67: G = __r_arr[18]; let __v_68: G = __r_arr[19]; let __v_69: G = __r_arr[20]; let __v_70: G = __r_arr[21]; let __v_71: G = __r_arr[22]; let __v_72: G = __r_arr[23]; let __v_73: G = __r_arr[24]; let __v_74: G = __r_arr[25]; let __v_75: G = __r_arr[26]; let __v_76: G = __r_arr[27]; let __v_77: G = __r_arr[28]; let __v_78: G = __r_arr[29]; let __v_79: G = __r_arr[30]; let __v_80: G = __r_arr[31]; let __v_81: G = G::from_u64(256); let __v_82: G = __v_81 * __v_50; let __v_83: G = __v_49 + __v_82; let __v_84: G = G::from_u64(65536); let __v_85: G = __v_84 * __v_51; let __v_86: G = __v_83 + __v_85; let __v_87: G = G::from_u64(16777216); let __v_88: G = __v_87 * __v_52; let __v_89: G = __v_86 + __v_88; let __v_90: G = __v_81 * __v_54; let __v_91: G = __v_53 + __v_90; let __v_92: G = __v_84 * __v_55; let __v_93: G = __v_91 + __v_92; let __v_94: G = __v_87 * __v_56; let __v_95: G = __v_93 + __v_94; let __v_96: G = __v_81 * __v_58; let __v_97: G = __v_57 + __v_96; let __v_98: G = __v_84 * __v_59; let __v_99: G = __v_97 + __v_98; let __v_100: G = __v_87 * __v_60; let __v_101: G = __v_99 + __v_100; let __v_102: G = __v_81 * __v_62; let __v_103: G = __v_61 + __v_102; let __v_104: G = __v_84 * __v_63; let __v_105: G = __v_103 + __v_104; let __v_106: G = __v_87 * __v_64; let __v_107: G = __v_105 + __v_106; let __v_108: G = __v_81 * __v_66; let __v_109: G = __v_65 + __v_108; let __v_110: G = __v_84 * __v_67; let __v_111: G = __v_109 + __v_110; let __v_112: G = __v_87 * __v_68; let __v_113: G = __v_111 + __v_112; let __v_114: G = __v_81 * __v_70; let __v_115: G = __v_69 + __v_114; let __v_116: G = __v_84 * __v_71; let __v_117: G = __v_115 + __v_116; let __v_118: G = __v_87 * __v_72; let __v_119: G = __v_117 + __v_118; let __v_120: G = __v_81 * __v_74; let __v_121: G = __v_73 + __v_120; let __v_122: G = __v_84 * __v_75; let __v_123: G = __v_121 + __v_122; let __v_124: G = __v_87 * __v_76; let __v_125: G = __v_123 + __v_124; let __v_126: G = __v_81 * __v_78; let __v_127: G = __v_77 + __v_126; let __v_128: G = __v_84 * __v_79; let __v_129: G = __v_127 + __v_128; let __v_130: G = __v_87 * __v_80; let __v_131: G = __v_129 + __v_130; if (__v_89 != __v_0) { return Err(ExecError::AssertEqMismatch { lhs: __v_89.as_canonical_u64(), rhs: __v_0.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } if (__v_95 != __v_1) { return Err(ExecError::AssertEqMismatch { lhs: __v_95.as_canonical_u64(), rhs: __v_1.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } if (__v_101 != __v_2) { return Err(ExecError::AssertEqMismatch { lhs: __v_101.as_canonical_u64(), rhs: __v_2.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } if (__v_107 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_107.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } if (__v_113 != __v_4) { return Err(ExecError::AssertEqMismatch { lhs: __v_113.as_canonical_u64(), rhs: __v_4.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } if (__v_119 != __v_5) { return Err(ExecError::AssertEqMismatch { lhs: __v_119.as_canonical_u64(), rhs: __v_5.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } if (__v_125 != __v_6) { return Err(ExecError::AssertEqMismatch { lhs: __v_125.as_canonical_u64(), rhs: __v_6.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } if (__v_131 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_131.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } let __r_arr: [G; OUT_85] = { let __args: [G; IN_85] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(85, &__args[..])?) { [G::ZERO; OUT_85] } else { let (__hash, __hit) = record.function_queries[85].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[85].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[85].bump_multiplicity(__i); } let __ret: [G; OUT_85] = unsafe { *(record.function_queries[85].output_at(__i).as_ptr() as *const [G; OUT_85]) }; __ret }, _ => { aiur_fn_85::(__args, __hash, record, io_buffer)? }, } } }; let __v_132: G = __r_arr[0]; let __v_133: G = __r_arr[1]; let __v_134: G = __r_arr[2]; let __v_135: G = __r_arr[3]; let __v_136: G = __r_arr[4]; let __v_137: G = __r_arr[5]; let __v_138: G = __r_arr[6]; let __v_139: G = __r_arr[7]; let __v_140: G = __r_arr[8]; let __v_141: G = __r_arr[9]; if (__v_132 != __v_29) { return Err(ExecError::AssertEqMismatch { lhs: __v_132.as_canonical_u64(), rhs: __v_29.as_canonical_u64(), msg: Some("claim: wrong TagN flag".to_string()) }); } let __v_142: G = __v_134 + __v_135; let __v_143: G = __v_142 + __v_136; let __v_144: G = __v_143 + __v_137; let __v_145: G = __v_144 + __v_138; let __v_146: G = __v_145 + __v_139; let __v_147: G = __v_146 + __v_140; if (__v_147 != __v_8) { return Err(ExecError::AssertEqMismatch { lhs: __v_147.as_canonical_u64(), rhs: __v_8.as_canonical_u64(), msg: Some("claim: TagN size exceeds a single byte".to_string()) }); } let __r_arr: [G; OUT_83] = { let __args: [G; IN_83] = [__v_141]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(83, &__args[..])?) { [G::ZERO; OUT_83] } else { let (__hash, __hit) = record.function_queries[83].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[83].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[83].bump_multiplicity(__i); } let __ret: [G; OUT_83] = unsafe { *(record.function_queries[83].output_at(__i).as_ptr() as *const [G; OUT_83]) }; __ret }, _ => { aiur_fn_83::(__args, __hash, record, io_buffer)? }, } } }; let __v_148: G = __r_arr[0]; let __v_149: G = __r_arr[1]; let __v_150: G = G::from_u64(4); if (__v_148 != __v_150) { return Err(ExecError::AssertEqMismatch { lhs: __v_148.as_canonical_u64(), rhs: __v_150.as_canonical_u64(), msg: Some("claim: unsupported object format".to_string()) }); } let __r_arr: [G; OUT_83] = { let __args: [G; IN_83] = [__v_149]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(83, &__args[..])?) { [G::ZERO; OUT_83] } else { let (__hash, __hit) = record.function_queries[83].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[83].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[83].bump_multiplicity(__i); } let __ret: [G; OUT_83] = unsafe { *(record.function_queries[83].output_at(__i).as_ptr() as *const [G; OUT_83]) }; __ret }, _ => { aiur_fn_83::(__args, __hash, record, io_buffer)? }, } } }; let __v_151: G = __r_arr[0]; let __v_152: G = __r_arr[1]; match __v_133.as_canonical_u64() { 3u64 => { let __v_153: G = G::from_u64(3); if (__v_151 != __v_153) { return Err(ExecError::AssertEqMismatch { lhs: __v_151.as_canonical_u64(), rhs: __v_153.as_canonical_u64(), msg: Some("claim: wrong validator identity".to_string()) }); } let __r_arr: [G; OUT_718] = { let __args: [G; IN_718] = [__v_152]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(718, &__args[..])?) { [G::ZERO; OUT_718] } else { let (__hash, __hit) = record.function_queries[718].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[718].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[718].bump_multiplicity(__i); } let __ret: [G; OUT_718] = unsafe { *(record.function_queries[718].output_at(__i).as_ptr() as *const [G; OUT_718]) }; __ret }, _ => { aiur_fn_718::(__args, __hash, record, io_buffer)? }, } } }; let __v_154: G = __r_arr[0]; let __v_155: G = __r_arr[1]; let __v_156: G = __r_arr[2]; let __v_157: G = __r_arr[3]; let __r_arr: [G; OUT_468] = { let __args: [G; IN_468] = [__v_154, __v_153, __v_155, __v_156]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(468, &__args[..])?) { [G::ZERO; OUT_468] } else { let (__hash, __hit) = record.function_queries[468].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[468].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[468].bump_multiplicity(__i); } let __ret: [G; OUT_468] = unsafe { *(record.function_queries[468].output_at(__i).as_ptr() as *const [G; OUT_468]) }; __ret }, _ => { aiur_fn_468::(__args, __hash, record, io_buffer)? }, } } }; let __v_158: G = __r_arr[0]; let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_157]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_159: G = __r_arr[0]; let __v_160: G = __r_arr[1]; let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_160]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_161: G = __r_arr[0]; let __v_162: G = __r_arr[1]; let __r_arr: [G; OUT_717] = { let __args: [G; IN_717] = [__v_162]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(717, &__args[..])?) { [G::ZERO; OUT_717] } else { let (__hash, __hit) = record.function_queries[717].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[717].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[717].bump_multiplicity(__i); } let __ret: [G; OUT_717] = unsafe { *(record.function_queries[717].output_at(__i).as_ptr() as *const [G; OUT_717]) }; __ret }, _ => { aiur_fn_717::(__args, __hash, record, io_buffer)? }, } } }; let __v_163: G = __r_arr[0]; let __v_164: G = __r_arr[1]; let __v_165: G = __r_arr[2]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_165.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_166: G = __loaded[0]; let __v_167: G = __loaded[1]; let __v_168: G = __loaded[2]; let __v_169: G = G::from_u64(1); if (__v_166 != __v_169) { return Err(ExecError::AssertEqMismatch { lhs: __v_166.as_canonical_u64(), rhs: __v_169.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_167 != __v_169) { return Err(ExecError::AssertEqMismatch { lhs: __v_167.as_canonical_u64(), rhs: __v_169.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_168 != __v_169) { return Err(ExecError::AssertEqMismatch { lhs: __v_168.as_canonical_u64(), rhs: __v_169.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } let __r_arr: [G; OUT_757] = { let __args: [G; IN_757] = [__v_158, __v_159, __v_161, __v_163, __v_164]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(757, &__args[..])?) { [G::ZERO; OUT_757] } else { let (__hash, __hit) = record.function_queries[757].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[757].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[757].bump_multiplicity(__i); } let __ret: [G; OUT_757] = unsafe { *(record.function_queries[757].output_at(__i).as_ptr() as *const [G; OUT_757]) }; __ret }, _ => { aiur_fn_757::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 4u64 => { let __r_arr: [G; OUT_718] = { let __args: [G; IN_718] = [__v_152]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(718, &__args[..])?) { [G::ZERO; OUT_718] } else { let (__hash, __hit) = record.function_queries[718].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[718].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[718].bump_multiplicity(__i); } let __ret: [G; OUT_718] = unsafe { *(record.function_queries[718].output_at(__i).as_ptr() as *const [G; OUT_718]) }; __ret }, _ => { aiur_fn_718::(__args, __hash, record, io_buffer)? }, } } }; let __v_153: G = __r_arr[0]; let __v_154: G = __r_arr[1]; let __v_155: G = __r_arr[2]; let __v_156: G = __r_arr[3]; let __r_arr: [G; OUT_468] = { let __args: [G; IN_468] = [__v_153, __v_151, __v_154, __v_155]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(468, &__args[..])?) { [G::ZERO; OUT_468] } else { let (__hash, __hit) = record.function_queries[468].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[468].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[468].bump_multiplicity(__i); } let __ret: [G; OUT_468] = unsafe { *(record.function_queries[468].output_at(__i).as_ptr() as *const [G; OUT_468]) }; __ret }, _ => { aiur_fn_468::(__args, __hash, record, io_buffer)? }, } } }; let __v_157: G = __r_arr[0]; let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_156]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_158: G = __r_arr[0]; let __v_159: G = __r_arr[1]; let __r_arr: [G; OUT_717] = { let __args: [G; IN_717] = [__v_159]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(717, &__args[..])?) { [G::ZERO; OUT_717] } else { let (__hash, __hit) = record.function_queries[717].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[717].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[717].bump_multiplicity(__i); } let __ret: [G; OUT_717] = unsafe { *(record.function_queries[717].output_at(__i).as_ptr() as *const [G; OUT_717]) }; __ret }, _ => { aiur_fn_717::(__args, __hash, record, io_buffer)? }, } } }; let __v_160: G = __r_arr[0]; let __v_161: G = __r_arr[1]; let __v_162: G = __r_arr[2]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_162.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_163: G = __loaded[0]; let __v_164: G = __loaded[1]; let __v_165: G = __loaded[2]; let __v_166: G = G::from_u64(1); if (__v_163 != __v_166) { return Err(ExecError::AssertEqMismatch { lhs: __v_163.as_canonical_u64(), rhs: __v_166.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_164 != __v_166) { return Err(ExecError::AssertEqMismatch { lhs: __v_164.as_canonical_u64(), rhs: __v_166.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_165 != __v_166) { return Err(ExecError::AssertEqMismatch { lhs: __v_165.as_canonical_u64(), rhs: __v_166.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } match __v_160.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_698] = { let __args: [G; IN_698] = [__v_157, __v_158]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(698, &__args[..])?) { [G::ZERO; OUT_698] } else { let (__hash, __hit) = record.function_queries[698].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[698].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[698].bump_multiplicity(__i); } let __ret: [G; OUT_698] = unsafe { *(record.function_queries[698].output_at(__i).as_ptr() as *const [G; OUT_698]) }; __ret }, _ => { aiur_fn_698::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_705] = { let __args: [G; IN_705] = [__v_161]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(705, &__args[..])?) { [G::ZERO; OUT_705] } else { let (__hash, __hit) = record.function_queries[705].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[705].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[705].bump_multiplicity(__i); } let __ret: [G; OUT_705] = unsafe { *(record.function_queries[705].output_at(__i).as_ptr() as *const [G; OUT_705]) }; __ret }, _ => { aiur_fn_705::(__args, __hash, record, io_buffer)? }, } } }; let __v_167: G = __r_arr[0]; let __v_168: G = G::from_u64(1); let __r_arr: [G; OUT_706] = { let __args: [G; IN_706] = [__v_167, __v_168, __v_168, __v_168, __v_168, __v_168, __v_168]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(706, &__args[..])?) { [G::ZERO; OUT_706] } else { let (__hash, __hit) = record.function_queries[706].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[706].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[706].bump_multiplicity(__i); } let __ret: [G; OUT_706] = unsafe { *(record.function_queries[706].output_at(__i).as_ptr() as *const [G; OUT_706]) }; __ret }, _ => { aiur_fn_706::(__args, __hash, record, io_buffer)? }, } } }; let __v_169: G = __r_arr[0]; let __v_170: G = __r_arr[1]; let __v_171: G = __r_arr[2]; let __v_172: G = __r_arr[3]; let __v_173: G = __r_arr[4]; let __v_174: G = __r_arr[5]; let __v_175: G = G::from_u64(0); let __r_arr: [G; OUT_708] = { let __args: [G; IN_708] = [__v_157, __v_158, __v_168, __v_168, __v_168, __v_168, __v_168, __v_168, __v_169, __v_170, __v_171, __v_172, __v_173, __v_174, __v_175]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(708, &__args[..])?) { [G::ZERO; OUT_708] } else { let (__hash, __hit) = record.function_queries[708].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[708].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[708].bump_multiplicity(__i); } let __ret: [G; OUT_708] = unsafe { *(record.function_queries[708].output_at(__i).as_ptr() as *const [G; OUT_708]) }; __ret }, _ => { aiur_fn_708::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_160.as_canonical_u64())); }, } }, 5u64 => { let __r_arr: [G; OUT_718] = { let __args: [G; IN_718] = [__v_152]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(718, &__args[..])?) { [G::ZERO; OUT_718] } else { let (__hash, __hit) = record.function_queries[718].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[718].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[718].bump_multiplicity(__i); } let __ret: [G; OUT_718] = unsafe { *(record.function_queries[718].output_at(__i).as_ptr() as *const [G; OUT_718]) }; __ret }, _ => { aiur_fn_718::(__args, __hash, record, io_buffer)? }, } } }; let __v_153: G = __r_arr[0]; let __v_154: G = __r_arr[1]; let __v_155: G = __r_arr[2]; let __v_156: G = __r_arr[3]; let __r_arr: [G; OUT_468] = { let __args: [G; IN_468] = [__v_153, __v_151, __v_154, __v_155]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(468, &__args[..])?) { [G::ZERO; OUT_468] } else { let (__hash, __hit) = record.function_queries[468].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[468].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[468].bump_multiplicity(__i); } let __ret: [G; OUT_468] = unsafe { *(record.function_queries[468].output_at(__i).as_ptr() as *const [G; OUT_468]) }; __ret }, _ => { aiur_fn_468::(__args, __hash, record, io_buffer)? }, } } }; let __v_157: G = __r_arr[0]; let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_156]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_158: G = __r_arr[0]; let __v_159: G = __r_arr[1]; let __r_arr: [G; OUT_717] = { let __args: [G; IN_717] = [__v_159]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(717, &__args[..])?) { [G::ZERO; OUT_717] } else { let (__hash, __hit) = record.function_queries[717].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[717].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[717].bump_multiplicity(__i); } let __ret: [G; OUT_717] = unsafe { *(record.function_queries[717].output_at(__i).as_ptr() as *const [G; OUT_717]) }; __ret }, _ => { aiur_fn_717::(__args, __hash, record, io_buffer)? }, } } }; let __v_160: G = __r_arr[0]; let __v_161: G = __r_arr[1]; let __v_162: G = __r_arr[2]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_162.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_163: G = __loaded[0]; let __v_164: G = __loaded[1]; let __v_165: G = __loaded[2]; let __v_166: G = G::from_u64(1); if (__v_163 != __v_166) { return Err(ExecError::AssertEqMismatch { lhs: __v_163.as_canonical_u64(), rhs: __v_166.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_164 != __v_166) { return Err(ExecError::AssertEqMismatch { lhs: __v_164.as_canonical_u64(), rhs: __v_166.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_165 != __v_166) { return Err(ExecError::AssertEqMismatch { lhs: __v_165.as_canonical_u64(), rhs: __v_166.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } let __r_arr: [G; OUT_711] = { let __args: [G; IN_711] = [__v_157, __v_158, __v_160, __v_161]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(711, &__args[..])?) { [G::ZERO; OUT_711] } else { let (__hash, __hit) = record.function_queries[711].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[711].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[711].bump_multiplicity(__i); } let __ret: [G; OUT_711] = unsafe { *(record.function_queries[711].output_at(__i).as_ptr() as *const [G; OUT_711]) }; __ret }, _ => { aiur_fn_711::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 6u64 => { let __v_153: G = G::from_u64(0); if (__v_151 != __v_153) { return Err(ExecError::AssertEqMismatch { lhs: __v_151.as_canonical_u64(), rhs: __v_153.as_canonical_u64(), msg: Some("claim: wrong validator identity".to_string()) }); } let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_152]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_154: G = __r_arr[0]; let __v_155: G = __r_arr[1]; let __r_arr: [G; OUT_733] = { let __args: [G; IN_733] = [__v_155]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(733, &__args[..])?) { [G::ZERO; OUT_733] } else { let (__hash, __hit) = record.function_queries[733].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[733].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[733].bump_multiplicity(__i); } let __ret: [G; OUT_733] = unsafe { *(record.function_queries[733].output_at(__i).as_ptr() as *const [G; OUT_733]) }; __ret }, _ => { aiur_fn_733::(__args, __hash, record, io_buffer)? }, } } }; let __v_156: G = __r_arr[0]; let __v_157: G = __r_arr[1]; let __v_158: G = __r_arr[2]; let __v_159: G = __r_arr[3]; let __v_160: G = __r_arr[4]; let __v_161: G = __r_arr[5]; let __v_162: G = __r_arr[6]; let __v_163: G = __r_arr[7]; let __v_164: G = __r_arr[8]; let __v_165: G = __r_arr[9]; let __v_166: G = __r_arr[10]; let __v_167: G = __r_arr[11]; let __v_168: G = __r_arr[12]; let __v_169: G = __r_arr[13]; let __v_170: G = __r_arr[14]; let __v_171: G = __r_arr[15]; let __v_172: G = __r_arr[16]; let __v_173: G = __r_arr[17]; let __v_174: G = __r_arr[18]; let __v_175: G = __r_arr[19]; let __v_176: G = __r_arr[20]; let __v_177: G = __r_arr[21]; let __v_178: G = __r_arr[22]; let __v_179: G = __r_arr[23]; let __v_180: G = __r_arr[24]; let __v_181: G = __r_arr[25]; let __v_182: G = __r_arr[26]; let __v_183: G = __r_arr[27]; let __v_184: G = __r_arr[28]; let __v_185: G = __r_arr[29]; let __v_186: G = __r_arr[30]; let __v_187: G = __r_arr[31]; let __v_188: G = __r_arr[32]; let __v_189: G = __r_arr[33]; let __v_190: G = __r_arr[34]; let __v_191: G = __r_arr[35]; let __v_192: G = __r_arr[36]; let __v_193: G = __r_arr[37]; let __v_194: G = __r_arr[38]; let __v_195: G = __r_arr[39]; let __v_196: G = __r_arr[40]; let __v_197: G = __r_arr[41]; let __v_198: G = __r_arr[42]; let __v_199: G = __r_arr[43]; let __v_200: G = __r_arr[44]; let __v_201: G = __r_arr[45]; let __v_202: G = __r_arr[46]; let __v_203: G = __r_arr[47]; let __v_204: G = __r_arr[48]; let __v_205: G = __r_arr[49]; let __v_206: G = __r_arr[50]; let __v_207: G = __r_arr[51]; let __v_208: G = __r_arr[52]; let __v_209: G = __r_arr[53]; let __v_210: G = __r_arr[54]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_210.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_211: G = __loaded[0]; let __v_212: G = __loaded[1]; let __v_213: G = __loaded[2]; let __v_214: G = G::from_u64(1); if (__v_211 != __v_214) { return Err(ExecError::AssertEqMismatch { lhs: __v_211.as_canonical_u64(), rhs: __v_214.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_212 != __v_214) { return Err(ExecError::AssertEqMismatch { lhs: __v_212.as_canonical_u64(), rhs: __v_214.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_213 != __v_214) { return Err(ExecError::AssertEqMismatch { lhs: __v_213.as_canonical_u64(), rhs: __v_214.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } let __r_arr: [G; OUT_750] = { let __args: [G; IN_750] = [__v_154, __v_156, __v_157, __v_158, __v_159, __v_160, __v_161, __v_162, __v_163, __v_164, __v_165, __v_166, __v_167, __v_168, __v_169, __v_170, __v_171, __v_172, __v_173, __v_174, __v_175, __v_176, __v_177, __v_178, __v_179, __v_180, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_199, __v_200, __v_201, __v_202, __v_203, __v_204, __v_205, __v_206, __v_207, __v_208, __v_209]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(750, &__args[..])?) { [G::ZERO; OUT_750] } else { let (__hash, __hit) = record.function_queries[750].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[750].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[750].bump_multiplicity(__i); } let __ret: [G; OUT_750] = unsafe { *(record.function_queries[750].output_at(__i).as_ptr() as *const [G; OUT_750]) }; __ret }, _ => { aiur_fn_750::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 7u64 => { let __v_153: G = G::from_u64(0); if (__v_151 != __v_153) { return Err(ExecError::AssertEqMismatch { lhs: __v_151.as_canonical_u64(), rhs: __v_153.as_canonical_u64(), msg: Some("claim: wrong validator identity".to_string()) }); } let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_152]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_154: G = __r_arr[0]; let __v_155: G = __r_arr[1]; let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_155]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_156: G = __r_arr[0]; let __v_157: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_157.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_158: G = __loaded[0]; let __v_159: G = __loaded[1]; let __v_160: G = __loaded[2]; let __v_161: G = G::from_u64(1); if (__v_158 != __v_161) { return Err(ExecError::AssertEqMismatch { lhs: __v_158.as_canonical_u64(), rhs: __v_161.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_159 != __v_161) { return Err(ExecError::AssertEqMismatch { lhs: __v_159.as_canonical_u64(), rhs: __v_161.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_160 != __v_161) { return Err(ExecError::AssertEqMismatch { lhs: __v_160.as_canonical_u64(), rhs: __v_161.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } let __r_arr: [G; OUT_751] = { let __args: [G; IN_751] = [__v_154, __v_156]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(751, &__args[..])?) { [G::ZERO; OUT_751] } else { let (__hash, __hit) = record.function_queries[751].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[751].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[751].bump_multiplicity(__i); } let __ret: [G; OUT_751] = unsafe { *(record.function_queries[751].output_at(__i).as_ptr() as *const [G; OUT_751]) }; __ret }, _ => { aiur_fn_751::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 9u64 => { let __v_153: G = G::from_u64(2); if (__v_151 != __v_153) { return Err(ExecError::AssertEqMismatch { lhs: __v_151.as_canonical_u64(), rhs: __v_153.as_canonical_u64(), msg: Some("claim: wrong resource validator identity".to_string()) }); } let __v_154: G = G::from_u64(0); let __v_155: G = G::from_u64(1); if (__v_154 != __v_155) { return Err(ExecError::AssertEqMismatch { lhs: __v_154.as_canonical_u64(), rhs: __v_155.as_canonical_u64(), msg: Some("claim: resource-v1 is not implemented by IxVM".to_string()) }); } let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 10u64 => { let __v_153: G = G::from_u64(3); if (__v_151 != __v_153) { return Err(ExecError::AssertEqMismatch { lhs: __v_151.as_canonical_u64(), rhs: __v_153.as_canonical_u64(), msg: Some("claim: wrong validator identity".to_string()) }); } let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_152]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_154: G = __r_arr[0]; let __v_155: G = __r_arr[1]; let __v_156: G = G::from_u64(109); let __v_157: G = G::from_u64(2); let __v_158: G = G::from_u64(21); let __v_159: G = G::from_u64(62); let __v_160: G = G::from_u64(163); let __v_161: G = G::from_u64(17); let __v_162: G = G::from_u64(243); let __v_163: G = G::from_u64(239); let __v_164: G = G::from_u64(209); let __v_165: G = G::from_u64(119); let __v_166: G = G::from_u64(140); let __v_167: G = G::from_u64(73); let __v_168: G = G::from_u64(178); let __v_169: G = G::from_u64(238); let __v_170: G = G::from_u64(95); let __v_171: G = G::from_u64(49); let __v_172: G = G::from_u64(231); let __v_173: G = G::from_u64(154); let __v_174: G = G::from_u64(11); let __v_175: G = G::from_u64(174); let __v_176: G = G::from_u64(149); let __v_177: G = G::from_u64(229); let __v_178: G = G::from_u64(51); let __v_179: G = G::from_u64(160); let __v_180: G = G::from_u64(141); let __v_181: G = G::from_u64(20); let __v_182: G = G::from_u64(252); let __v_183: G = G::from_u64(5); let __v_184: G = G::from_u64(87); let __v_185: G = G::from_u64(45); let __v_186: G = { let __values: [G; 32] = [__v_156, __v_157, __v_158, __v_159, __v_160, __v_161, __v_162, __v_163, __v_164, __v_165, __v_166, __v_167, __v_168, __v_169, __v_170, __v_171, __v_172, __v_173, __v_174, __v_175, __v_176, __v_177, __v_178, __v_179, __v_180, __v_167, __v_181, __v_182, __v_183, __v_184, __v_185, __v_178]; let __mq = record.memory_queries.get_index_mut(12).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_154, __v_186]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_187: G = __r_arr[0]; let __v_188: G = G::from_u64(1); if (__v_187 != __v_188) { return Err(ExecError::AssertEqMismatch { lhs: __v_187.as_canonical_u64(), rhs: __v_188.as_canonical_u64(), msg: Some("claim: specification digest is not this circuit's".to_string()) }); } let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_155]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_189: G = __r_arr[0]; let __v_190: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_190.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_191: G = __loaded[0]; let __v_192: G = __loaded[1]; let __v_193: G = __loaded[2]; if (__v_191 != __v_188) { return Err(ExecError::AssertEqMismatch { lhs: __v_191.as_canonical_u64(), rhs: __v_188.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_192 != __v_188) { return Err(ExecError::AssertEqMismatch { lhs: __v_192.as_canonical_u64(), rhs: __v_188.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_193 != __v_188) { return Err(ExecError::AssertEqMismatch { lhs: __v_193.as_canonical_u64(), rhs: __v_188.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } let __r_arr: [G; OUT_488] = { let __args: [G; IN_488] = [__v_189]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(488, &__args[..])?) { [G::ZERO; OUT_488] } else { let (__hash, __hit) = record.function_queries[488].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[488].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[488].bump_multiplicity(__i); } let __ret: [G; OUT_488] = unsafe { *(record.function_queries[488].output_at(__i).as_ptr() as *const [G; OUT_488]) }; __ret }, _ => { aiur_fn_488::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_133.as_canonical_u64())); }, } }) } +fn aiur_fn_752(inp: [G; IN_752], input_hash: u64, record: &mut QueryRecord, io_buffer: &mut IOBuffer,) -> Result<[G; OUT_752], ExecError> { stacker::maybe_grow(64 * 1024, (4 * 1024) * 1024, || { let unconstrained = UNCONSTRAINED; aiur::execute::check_record_cap(&record.budget)?; let __v_0: G = inp[0]; let __v_1: G = inp[1]; let __v_2: G = inp[2]; let __v_3: G = inp[3]; let __v_4: G = inp[4]; let __v_5: G = inp[5]; let __v_6: G = inp[6]; let __v_7: G = inp[7]; let __v_8: G = G::from_u64(0); let __io_pair: (G, G) = { let __key: [G; 8] = [__v_0, __v_1, __v_2, __v_3, __v_4, __v_5, __v_6, __v_7]; let __info = io_buffer.get_info(__v_8, &__key[..])?; (G::from_usize(__info.idx), G::from_usize(__info.len)) }; let __v_9: G = __io_pair.0; let __v_10: G = __io_pair.1; let __r_arr: [G; OUT_5] = { let __args: [G; IN_5] = [__v_8, __v_9, __v_10]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(5, &__args[..])?) { [G::ZERO; OUT_5] } else { let (__hash, __hit) = record.function_queries[5].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[5].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[5].bump_multiplicity(__i); } let __ret: [G; OUT_5] = unsafe { *(record.function_queries[5].output_at(__i).as_ptr() as *const [G; OUT_5]) }; __ret }, _ => { aiur_fn_5::(__args, __hash, record, io_buffer)? }, } } }; let __v_11: G = __r_arr[0]; let __v_12: G = G::from_u64(103); let __v_13: G = G::from_u64(230); let __v_14: G = G::from_u64(9); let __v_15: G = G::from_u64(106); let __v_16: G = G::from_u64(133); let __v_17: G = G::from_u64(174); let __v_18: G = G::from_u64(187); let __v_19: G = G::from_u64(114); let __v_20: G = G::from_u64(243); let __v_21: G = G::from_u64(110); let __v_22: G = G::from_u64(60); let __v_23: G = G::from_u64(58); let __v_24: G = G::from_u64(245); let __v_25: G = G::from_u64(79); let __v_26: G = G::from_u64(165); let __v_27: G = G::from_u64(127); let __v_28: G = G::from_u64(82); let __v_29: G = G::from_u64(14); let __v_30: G = G::from_u64(81); let __v_31: G = G::from_u64(140); let __v_32: G = G::from_u64(104); let __v_33: G = G::from_u64(5); let __v_34: G = G::from_u64(155); let __v_35: G = G::from_u64(171); let __v_36: G = G::from_u64(217); let __v_37: G = G::from_u64(131); let __v_38: G = G::from_u64(31); let __v_39: G = G::from_u64(25); let __v_40: G = G::from_u64(205); let __v_41: G = G::from_u64(224); let __v_42: G = G::from_u64(91); let __v_43: G = G::from_u64(1); let __v_44: G = { let __values: [G; 3] = [__v_43, __v_43, __v_43]; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 3)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_45: G = { let __values: [G; 8] = [__v_8, __v_8, __v_8, __v_8, __v_8, __v_8, __v_8, __v_8]; let __mq = record.memory_queries.get_index_mut(5).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(8))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 8)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_46: G = { let __values: [G; 32] = [__v_12, __v_13, __v_14, __v_15, __v_16, __v_17, __v_12, __v_18, __v_19, __v_20, __v_21, __v_22, __v_23, __v_24, __v_25, __v_26, __v_27, __v_28, __v_29, __v_30, __v_31, __v_32, __v_33, __v_34, __v_35, __v_36, __v_37, __v_38, __v_39, __v_40, __v_41, __v_42]; let __mq = record.memory_queries.get_index_mut(12).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __v_47: G = { let __values: [G; 34] = [__v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43, __v_43]; let __mq = record.memory_queries.get_index_mut(13).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(34))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 34)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_26] = { let __args: [G; IN_26] = [__v_11, __v_44, __v_8, __v_8, __v_45, __v_46, __v_47]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(26, &__args[..])?) { [G::ZERO; OUT_26] } else { let (__hash, __hit) = record.function_queries[26].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[26].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[26].bump_multiplicity(__i); } let __ret: [G; OUT_26] = unsafe { *(record.function_queries[26].output_at(__i).as_ptr() as *const [G; OUT_26]) }; __ret }, _ => { aiur_fn_26::(__args, __hash, record, io_buffer)? }, } } }; let __v_48: G = __r_arr[0]; let __r_arr: [G; OUT_25] = { let __args: [G; IN_25] = [__v_48]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(25, &__args[..])?) { [G::ZERO; OUT_25] } else { let (__hash, __hit) = record.function_queries[25].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[25].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[25].bump_multiplicity(__i); } let __ret: [G; OUT_25] = unsafe { *(record.function_queries[25].output_at(__i).as_ptr() as *const [G; OUT_25]) }; __ret }, _ => { aiur_fn_25::(__args, __hash, record, io_buffer)? }, } } }; let __v_49: G = __r_arr[0]; let __v_50: G = __r_arr[1]; let __v_51: G = __r_arr[2]; let __v_52: G = __r_arr[3]; let __v_53: G = __r_arr[4]; let __v_54: G = __r_arr[5]; let __v_55: G = __r_arr[6]; let __v_56: G = __r_arr[7]; let __v_57: G = __r_arr[8]; let __v_58: G = __r_arr[9]; let __v_59: G = __r_arr[10]; let __v_60: G = __r_arr[11]; let __v_61: G = __r_arr[12]; let __v_62: G = __r_arr[13]; let __v_63: G = __r_arr[14]; let __v_64: G = __r_arr[15]; let __v_65: G = __r_arr[16]; let __v_66: G = __r_arr[17]; let __v_67: G = __r_arr[18]; let __v_68: G = __r_arr[19]; let __v_69: G = __r_arr[20]; let __v_70: G = __r_arr[21]; let __v_71: G = __r_arr[22]; let __v_72: G = __r_arr[23]; let __v_73: G = __r_arr[24]; let __v_74: G = __r_arr[25]; let __v_75: G = __r_arr[26]; let __v_76: G = __r_arr[27]; let __v_77: G = __r_arr[28]; let __v_78: G = __r_arr[29]; let __v_79: G = __r_arr[30]; let __v_80: G = __r_arr[31]; let __v_81: G = G::from_u64(256); let __v_82: G = __v_81 * __v_50; let __v_83: G = __v_49 + __v_82; let __v_84: G = G::from_u64(65536); let __v_85: G = __v_84 * __v_51; let __v_86: G = __v_83 + __v_85; let __v_87: G = G::from_u64(16777216); let __v_88: G = __v_87 * __v_52; let __v_89: G = __v_86 + __v_88; let __v_90: G = __v_81 * __v_54; let __v_91: G = __v_53 + __v_90; let __v_92: G = __v_84 * __v_55; let __v_93: G = __v_91 + __v_92; let __v_94: G = __v_87 * __v_56; let __v_95: G = __v_93 + __v_94; let __v_96: G = __v_81 * __v_58; let __v_97: G = __v_57 + __v_96; let __v_98: G = __v_84 * __v_59; let __v_99: G = __v_97 + __v_98; let __v_100: G = __v_87 * __v_60; let __v_101: G = __v_99 + __v_100; let __v_102: G = __v_81 * __v_62; let __v_103: G = __v_61 + __v_102; let __v_104: G = __v_84 * __v_63; let __v_105: G = __v_103 + __v_104; let __v_106: G = __v_87 * __v_64; let __v_107: G = __v_105 + __v_106; let __v_108: G = __v_81 * __v_66; let __v_109: G = __v_65 + __v_108; let __v_110: G = __v_84 * __v_67; let __v_111: G = __v_109 + __v_110; let __v_112: G = __v_87 * __v_68; let __v_113: G = __v_111 + __v_112; let __v_114: G = __v_81 * __v_70; let __v_115: G = __v_69 + __v_114; let __v_116: G = __v_84 * __v_71; let __v_117: G = __v_115 + __v_116; let __v_118: G = __v_87 * __v_72; let __v_119: G = __v_117 + __v_118; let __v_120: G = __v_81 * __v_74; let __v_121: G = __v_73 + __v_120; let __v_122: G = __v_84 * __v_75; let __v_123: G = __v_121 + __v_122; let __v_124: G = __v_87 * __v_76; let __v_125: G = __v_123 + __v_124; let __v_126: G = __v_81 * __v_78; let __v_127: G = __v_77 + __v_126; let __v_128: G = __v_84 * __v_79; let __v_129: G = __v_127 + __v_128; let __v_130: G = __v_87 * __v_80; let __v_131: G = __v_129 + __v_130; if (__v_89 != __v_0) { return Err(ExecError::AssertEqMismatch { lhs: __v_89.as_canonical_u64(), rhs: __v_0.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } if (__v_95 != __v_1) { return Err(ExecError::AssertEqMismatch { lhs: __v_95.as_canonical_u64(), rhs: __v_1.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } if (__v_101 != __v_2) { return Err(ExecError::AssertEqMismatch { lhs: __v_101.as_canonical_u64(), rhs: __v_2.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } if (__v_107 != __v_3) { return Err(ExecError::AssertEqMismatch { lhs: __v_107.as_canonical_u64(), rhs: __v_3.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } if (__v_113 != __v_4) { return Err(ExecError::AssertEqMismatch { lhs: __v_113.as_canonical_u64(), rhs: __v_4.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } if (__v_119 != __v_5) { return Err(ExecError::AssertEqMismatch { lhs: __v_119.as_canonical_u64(), rhs: __v_5.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } if (__v_125 != __v_6) { return Err(ExecError::AssertEqMismatch { lhs: __v_125.as_canonical_u64(), rhs: __v_6.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } if (__v_131 != __v_7) { return Err(ExecError::AssertEqMismatch { lhs: __v_131.as_canonical_u64(), rhs: __v_7.as_canonical_u64(), msg: Some("claim: bytes do not hash to the public claim digest".to_string()) }); } let __r_arr: [G; OUT_85] = { let __args: [G; IN_85] = [__v_11]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(85, &__args[..])?) { [G::ZERO; OUT_85] } else { let (__hash, __hit) = record.function_queries[85].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[85].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[85].bump_multiplicity(__i); } let __ret: [G; OUT_85] = unsafe { *(record.function_queries[85].output_at(__i).as_ptr() as *const [G; OUT_85]) }; __ret }, _ => { aiur_fn_85::(__args, __hash, record, io_buffer)? }, } } }; let __v_132: G = __r_arr[0]; let __v_133: G = __r_arr[1]; let __v_134: G = __r_arr[2]; let __v_135: G = __r_arr[3]; let __v_136: G = __r_arr[4]; let __v_137: G = __r_arr[5]; let __v_138: G = __r_arr[6]; let __v_139: G = __r_arr[7]; let __v_140: G = __r_arr[8]; let __v_141: G = __r_arr[9]; if (__v_132 != __v_29) { return Err(ExecError::AssertEqMismatch { lhs: __v_132.as_canonical_u64(), rhs: __v_29.as_canonical_u64(), msg: Some("claim: wrong TagN flag".to_string()) }); } let __v_142: G = __v_134 + __v_135; let __v_143: G = __v_142 + __v_136; let __v_144: G = __v_143 + __v_137; let __v_145: G = __v_144 + __v_138; let __v_146: G = __v_145 + __v_139; let __v_147: G = __v_146 + __v_140; if (__v_147 != __v_8) { return Err(ExecError::AssertEqMismatch { lhs: __v_147.as_canonical_u64(), rhs: __v_8.as_canonical_u64(), msg: Some("claim: TagN size exceeds a single byte".to_string()) }); } let __r_arr: [G; OUT_83] = { let __args: [G; IN_83] = [__v_141]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(83, &__args[..])?) { [G::ZERO; OUT_83] } else { let (__hash, __hit) = record.function_queries[83].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[83].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[83].bump_multiplicity(__i); } let __ret: [G; OUT_83] = unsafe { *(record.function_queries[83].output_at(__i).as_ptr() as *const [G; OUT_83]) }; __ret }, _ => { aiur_fn_83::(__args, __hash, record, io_buffer)? }, } } }; let __v_148: G = __r_arr[0]; let __v_149: G = __r_arr[1]; let __v_150: G = G::from_u64(4); if (__v_148 != __v_150) { return Err(ExecError::AssertEqMismatch { lhs: __v_148.as_canonical_u64(), rhs: __v_150.as_canonical_u64(), msg: Some("claim: unsupported object format".to_string()) }); } let __r_arr: [G; OUT_83] = { let __args: [G; IN_83] = [__v_149]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(83, &__args[..])?) { [G::ZERO; OUT_83] } else { let (__hash, __hit) = record.function_queries[83].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[83].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[83].bump_multiplicity(__i); } let __ret: [G; OUT_83] = unsafe { *(record.function_queries[83].output_at(__i).as_ptr() as *const [G; OUT_83]) }; __ret }, _ => { aiur_fn_83::(__args, __hash, record, io_buffer)? }, } } }; let __v_151: G = __r_arr[0]; let __v_152: G = __r_arr[1]; match __v_133.as_canonical_u64() { 3u64 => { let __v_153: G = G::from_u64(3); if (__v_151 != __v_153) { return Err(ExecError::AssertEqMismatch { lhs: __v_151.as_canonical_u64(), rhs: __v_153.as_canonical_u64(), msg: Some("claim: wrong validator identity".to_string()) }); } let __r_arr: [G; OUT_718] = { let __args: [G; IN_718] = [__v_152]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(718, &__args[..])?) { [G::ZERO; OUT_718] } else { let (__hash, __hit) = record.function_queries[718].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[718].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[718].bump_multiplicity(__i); } let __ret: [G; OUT_718] = unsafe { *(record.function_queries[718].output_at(__i).as_ptr() as *const [G; OUT_718]) }; __ret }, _ => { aiur_fn_718::(__args, __hash, record, io_buffer)? }, } } }; let __v_154: G = __r_arr[0]; let __v_155: G = __r_arr[1]; let __v_156: G = __r_arr[2]; let __v_157: G = __r_arr[3]; let __r_arr: [G; OUT_468] = { let __args: [G; IN_468] = [__v_154, __v_153, __v_155, __v_156]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(468, &__args[..])?) { [G::ZERO; OUT_468] } else { let (__hash, __hit) = record.function_queries[468].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[468].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[468].bump_multiplicity(__i); } let __ret: [G; OUT_468] = unsafe { *(record.function_queries[468].output_at(__i).as_ptr() as *const [G; OUT_468]) }; __ret }, _ => { aiur_fn_468::(__args, __hash, record, io_buffer)? }, } } }; let __v_158: G = __r_arr[0]; let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_157]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_159: G = __r_arr[0]; let __v_160: G = __r_arr[1]; let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_160]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_161: G = __r_arr[0]; let __v_162: G = __r_arr[1]; let __r_arr: [G; OUT_717] = { let __args: [G; IN_717] = [__v_162]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(717, &__args[..])?) { [G::ZERO; OUT_717] } else { let (__hash, __hit) = record.function_queries[717].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[717].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[717].bump_multiplicity(__i); } let __ret: [G; OUT_717] = unsafe { *(record.function_queries[717].output_at(__i).as_ptr() as *const [G; OUT_717]) }; __ret }, _ => { aiur_fn_717::(__args, __hash, record, io_buffer)? }, } } }; let __v_163: G = __r_arr[0]; let __v_164: G = __r_arr[1]; let __v_165: G = __r_arr[2]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_165.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_166: G = __loaded[0]; let __v_167: G = __loaded[1]; let __v_168: G = __loaded[2]; let __v_169: G = G::from_u64(1); if (__v_166 != __v_169) { return Err(ExecError::AssertEqMismatch { lhs: __v_166.as_canonical_u64(), rhs: __v_169.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_167 != __v_169) { return Err(ExecError::AssertEqMismatch { lhs: __v_167.as_canonical_u64(), rhs: __v_169.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_168 != __v_169) { return Err(ExecError::AssertEqMismatch { lhs: __v_168.as_canonical_u64(), rhs: __v_169.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } let __r_arr: [G; OUT_757] = { let __args: [G; IN_757] = [__v_158, __v_159, __v_161, __v_163, __v_164]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(757, &__args[..])?) { [G::ZERO; OUT_757] } else { let (__hash, __hit) = record.function_queries[757].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[757].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[757].bump_multiplicity(__i); } let __ret: [G; OUT_757] = unsafe { *(record.function_queries[757].output_at(__i).as_ptr() as *const [G; OUT_757]) }; __ret }, _ => { aiur_fn_757::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 4u64 => { let __r_arr: [G; OUT_718] = { let __args: [G; IN_718] = [__v_152]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(718, &__args[..])?) { [G::ZERO; OUT_718] } else { let (__hash, __hit) = record.function_queries[718].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[718].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[718].bump_multiplicity(__i); } let __ret: [G; OUT_718] = unsafe { *(record.function_queries[718].output_at(__i).as_ptr() as *const [G; OUT_718]) }; __ret }, _ => { aiur_fn_718::(__args, __hash, record, io_buffer)? }, } } }; let __v_153: G = __r_arr[0]; let __v_154: G = __r_arr[1]; let __v_155: G = __r_arr[2]; let __v_156: G = __r_arr[3]; let __r_arr: [G; OUT_468] = { let __args: [G; IN_468] = [__v_153, __v_151, __v_154, __v_155]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(468, &__args[..])?) { [G::ZERO; OUT_468] } else { let (__hash, __hit) = record.function_queries[468].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[468].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[468].bump_multiplicity(__i); } let __ret: [G; OUT_468] = unsafe { *(record.function_queries[468].output_at(__i).as_ptr() as *const [G; OUT_468]) }; __ret }, _ => { aiur_fn_468::(__args, __hash, record, io_buffer)? }, } } }; let __v_157: G = __r_arr[0]; let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_156]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_158: G = __r_arr[0]; let __v_159: G = __r_arr[1]; let __r_arr: [G; OUT_717] = { let __args: [G; IN_717] = [__v_159]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(717, &__args[..])?) { [G::ZERO; OUT_717] } else { let (__hash, __hit) = record.function_queries[717].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[717].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[717].bump_multiplicity(__i); } let __ret: [G; OUT_717] = unsafe { *(record.function_queries[717].output_at(__i).as_ptr() as *const [G; OUT_717]) }; __ret }, _ => { aiur_fn_717::(__args, __hash, record, io_buffer)? }, } } }; let __v_160: G = __r_arr[0]; let __v_161: G = __r_arr[1]; let __v_162: G = __r_arr[2]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_162.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_163: G = __loaded[0]; let __v_164: G = __loaded[1]; let __v_165: G = __loaded[2]; let __v_166: G = G::from_u64(1); if (__v_163 != __v_166) { return Err(ExecError::AssertEqMismatch { lhs: __v_163.as_canonical_u64(), rhs: __v_166.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_164 != __v_166) { return Err(ExecError::AssertEqMismatch { lhs: __v_164.as_canonical_u64(), rhs: __v_166.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_165 != __v_166) { return Err(ExecError::AssertEqMismatch { lhs: __v_165.as_canonical_u64(), rhs: __v_166.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } match __v_160.as_canonical_u64() { 1u64 => { let __r_arr: [G; OUT_698] = { let __args: [G; IN_698] = [__v_157, __v_158]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(698, &__args[..])?) { [G::ZERO; OUT_698] } else { let (__hash, __hit) = record.function_queries[698].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[698].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[698].bump_multiplicity(__i); } let __ret: [G; OUT_698] = unsafe { *(record.function_queries[698].output_at(__i).as_ptr() as *const [G; OUT_698]) }; __ret }, _ => { aiur_fn_698::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 0u64 => { let __r_arr: [G; OUT_705] = { let __args: [G; IN_705] = [__v_161]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(705, &__args[..])?) { [G::ZERO; OUT_705] } else { let (__hash, __hit) = record.function_queries[705].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[705].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[705].bump_multiplicity(__i); } let __ret: [G; OUT_705] = unsafe { *(record.function_queries[705].output_at(__i).as_ptr() as *const [G; OUT_705]) }; __ret }, _ => { aiur_fn_705::(__args, __hash, record, io_buffer)? }, } } }; let __v_167: G = __r_arr[0]; let __v_168: G = G::from_u64(1); let __r_arr: [G; OUT_706] = { let __args: [G; IN_706] = [__v_167, __v_168, __v_168, __v_168, __v_168, __v_168, __v_168]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(706, &__args[..])?) { [G::ZERO; OUT_706] } else { let (__hash, __hit) = record.function_queries[706].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[706].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[706].bump_multiplicity(__i); } let __ret: [G; OUT_706] = unsafe { *(record.function_queries[706].output_at(__i).as_ptr() as *const [G; OUT_706]) }; __ret }, _ => { aiur_fn_706::(__args, __hash, record, io_buffer)? }, } } }; let __v_169: G = __r_arr[0]; let __v_170: G = __r_arr[1]; let __v_171: G = __r_arr[2]; let __v_172: G = __r_arr[3]; let __v_173: G = __r_arr[4]; let __v_174: G = __r_arr[5]; let __v_175: G = G::from_u64(2); let __r_arr: [G; OUT_708] = { let __args: [G; IN_708] = [__v_157, __v_158, __v_168, __v_168, __v_168, __v_168, __v_168, __v_168, __v_169, __v_170, __v_171, __v_172, __v_173, __v_174, __v_175]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(708, &__args[..])?) { [G::ZERO; OUT_708] } else { let (__hash, __hit) = record.function_queries[708].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[708].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[708].bump_multiplicity(__i); } let __ret: [G; OUT_708] = unsafe { *(record.function_queries[708].output_at(__i).as_ptr() as *const [G; OUT_708]) }; __ret }, _ => { aiur_fn_708::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_160.as_canonical_u64())); }, } }, 5u64 => { let __r_arr: [G; OUT_718] = { let __args: [G; IN_718] = [__v_152]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(718, &__args[..])?) { [G::ZERO; OUT_718] } else { let (__hash, __hit) = record.function_queries[718].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[718].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[718].bump_multiplicity(__i); } let __ret: [G; OUT_718] = unsafe { *(record.function_queries[718].output_at(__i).as_ptr() as *const [G; OUT_718]) }; __ret }, _ => { aiur_fn_718::(__args, __hash, record, io_buffer)? }, } } }; let __v_153: G = __r_arr[0]; let __v_154: G = __r_arr[1]; let __v_155: G = __r_arr[2]; let __v_156: G = __r_arr[3]; let __r_arr: [G; OUT_468] = { let __args: [G; IN_468] = [__v_153, __v_151, __v_154, __v_155]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(468, &__args[..])?) { [G::ZERO; OUT_468] } else { let (__hash, __hit) = record.function_queries[468].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[468].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[468].bump_multiplicity(__i); } let __ret: [G; OUT_468] = unsafe { *(record.function_queries[468].output_at(__i).as_ptr() as *const [G; OUT_468]) }; __ret }, _ => { aiur_fn_468::(__args, __hash, record, io_buffer)? }, } } }; let __v_157: G = __r_arr[0]; let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_156]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_158: G = __r_arr[0]; let __v_159: G = __r_arr[1]; let __r_arr: [G; OUT_717] = { let __args: [G; IN_717] = [__v_159]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(717, &__args[..])?) { [G::ZERO; OUT_717] } else { let (__hash, __hit) = record.function_queries[717].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[717].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[717].bump_multiplicity(__i); } let __ret: [G; OUT_717] = unsafe { *(record.function_queries[717].output_at(__i).as_ptr() as *const [G; OUT_717]) }; __ret }, _ => { aiur_fn_717::(__args, __hash, record, io_buffer)? }, } } }; let __v_160: G = __r_arr[0]; let __v_161: G = __r_arr[1]; let __v_162: G = __r_arr[2]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_162.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_163: G = __loaded[0]; let __v_164: G = __loaded[1]; let __v_165: G = __loaded[2]; let __v_166: G = G::from_u64(1); if (__v_163 != __v_166) { return Err(ExecError::AssertEqMismatch { lhs: __v_163.as_canonical_u64(), rhs: __v_166.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_164 != __v_166) { return Err(ExecError::AssertEqMismatch { lhs: __v_164.as_canonical_u64(), rhs: __v_166.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_165 != __v_166) { return Err(ExecError::AssertEqMismatch { lhs: __v_165.as_canonical_u64(), rhs: __v_166.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } let __r_arr: [G; OUT_711] = { let __args: [G; IN_711] = [__v_157, __v_158, __v_160, __v_161]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(711, &__args[..])?) { [G::ZERO; OUT_711] } else { let (__hash, __hit) = record.function_queries[711].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[711].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[711].bump_multiplicity(__i); } let __ret: [G; OUT_711] = unsafe { *(record.function_queries[711].output_at(__i).as_ptr() as *const [G; OUT_711]) }; __ret }, _ => { aiur_fn_711::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 6u64 => { let __v_153: G = G::from_u64(0); if (__v_151 != __v_153) { return Err(ExecError::AssertEqMismatch { lhs: __v_151.as_canonical_u64(), rhs: __v_153.as_canonical_u64(), msg: Some("claim: wrong validator identity".to_string()) }); } let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_152]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_154: G = __r_arr[0]; let __v_155: G = __r_arr[1]; let __r_arr: [G; OUT_733] = { let __args: [G; IN_733] = [__v_155]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(733, &__args[..])?) { [G::ZERO; OUT_733] } else { let (__hash, __hit) = record.function_queries[733].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[733].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[733].bump_multiplicity(__i); } let __ret: [G; OUT_733] = unsafe { *(record.function_queries[733].output_at(__i).as_ptr() as *const [G; OUT_733]) }; __ret }, _ => { aiur_fn_733::(__args, __hash, record, io_buffer)? }, } } }; let __v_156: G = __r_arr[0]; let __v_157: G = __r_arr[1]; let __v_158: G = __r_arr[2]; let __v_159: G = __r_arr[3]; let __v_160: G = __r_arr[4]; let __v_161: G = __r_arr[5]; let __v_162: G = __r_arr[6]; let __v_163: G = __r_arr[7]; let __v_164: G = __r_arr[8]; let __v_165: G = __r_arr[9]; let __v_166: G = __r_arr[10]; let __v_167: G = __r_arr[11]; let __v_168: G = __r_arr[12]; let __v_169: G = __r_arr[13]; let __v_170: G = __r_arr[14]; let __v_171: G = __r_arr[15]; let __v_172: G = __r_arr[16]; let __v_173: G = __r_arr[17]; let __v_174: G = __r_arr[18]; let __v_175: G = __r_arr[19]; let __v_176: G = __r_arr[20]; let __v_177: G = __r_arr[21]; let __v_178: G = __r_arr[22]; let __v_179: G = __r_arr[23]; let __v_180: G = __r_arr[24]; let __v_181: G = __r_arr[25]; let __v_182: G = __r_arr[26]; let __v_183: G = __r_arr[27]; let __v_184: G = __r_arr[28]; let __v_185: G = __r_arr[29]; let __v_186: G = __r_arr[30]; let __v_187: G = __r_arr[31]; let __v_188: G = __r_arr[32]; let __v_189: G = __r_arr[33]; let __v_190: G = __r_arr[34]; let __v_191: G = __r_arr[35]; let __v_192: G = __r_arr[36]; let __v_193: G = __r_arr[37]; let __v_194: G = __r_arr[38]; let __v_195: G = __r_arr[39]; let __v_196: G = __r_arr[40]; let __v_197: G = __r_arr[41]; let __v_198: G = __r_arr[42]; let __v_199: G = __r_arr[43]; let __v_200: G = __r_arr[44]; let __v_201: G = __r_arr[45]; let __v_202: G = __r_arr[46]; let __v_203: G = __r_arr[47]; let __v_204: G = __r_arr[48]; let __v_205: G = __r_arr[49]; let __v_206: G = __r_arr[50]; let __v_207: G = __r_arr[51]; let __v_208: G = __r_arr[52]; let __v_209: G = __r_arr[53]; let __v_210: G = __r_arr[54]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_210.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_211: G = __loaded[0]; let __v_212: G = __loaded[1]; let __v_213: G = __loaded[2]; let __v_214: G = G::from_u64(1); if (__v_211 != __v_214) { return Err(ExecError::AssertEqMismatch { lhs: __v_211.as_canonical_u64(), rhs: __v_214.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_212 != __v_214) { return Err(ExecError::AssertEqMismatch { lhs: __v_212.as_canonical_u64(), rhs: __v_214.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_213 != __v_214) { return Err(ExecError::AssertEqMismatch { lhs: __v_213.as_canonical_u64(), rhs: __v_214.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } let __r_arr: [G; OUT_750] = { let __args: [G; IN_750] = [__v_154, __v_156, __v_157, __v_158, __v_159, __v_160, __v_161, __v_162, __v_163, __v_164, __v_165, __v_166, __v_167, __v_168, __v_169, __v_170, __v_171, __v_172, __v_173, __v_174, __v_175, __v_176, __v_177, __v_178, __v_179, __v_180, __v_181, __v_182, __v_183, __v_184, __v_185, __v_186, __v_187, __v_188, __v_189, __v_190, __v_191, __v_192, __v_193, __v_194, __v_195, __v_196, __v_197, __v_198, __v_199, __v_200, __v_201, __v_202, __v_203, __v_204, __v_205, __v_206, __v_207, __v_208, __v_209]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(750, &__args[..])?) { [G::ZERO; OUT_750] } else { let (__hash, __hit) = record.function_queries[750].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[750].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[750].bump_multiplicity(__i); } let __ret: [G; OUT_750] = unsafe { *(record.function_queries[750].output_at(__i).as_ptr() as *const [G; OUT_750]) }; __ret }, _ => { aiur_fn_750::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 7u64 => { let __v_153: G = G::from_u64(0); if (__v_151 != __v_153) { return Err(ExecError::AssertEqMismatch { lhs: __v_151.as_canonical_u64(), rhs: __v_153.as_canonical_u64(), msg: Some("claim: wrong validator identity".to_string()) }); } let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_152]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_154: G = __r_arr[0]; let __v_155: G = __r_arr[1]; let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_155]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_156: G = __r_arr[0]; let __v_157: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_157.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_158: G = __loaded[0]; let __v_159: G = __loaded[1]; let __v_160: G = __loaded[2]; let __v_161: G = G::from_u64(1); if (__v_158 != __v_161) { return Err(ExecError::AssertEqMismatch { lhs: __v_158.as_canonical_u64(), rhs: __v_161.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_159 != __v_161) { return Err(ExecError::AssertEqMismatch { lhs: __v_159.as_canonical_u64(), rhs: __v_161.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_160 != __v_161) { return Err(ExecError::AssertEqMismatch { lhs: __v_160.as_canonical_u64(), rhs: __v_161.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } let __r_arr: [G; OUT_751] = { let __args: [G; IN_751] = [__v_154, __v_156]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(751, &__args[..])?) { [G::ZERO; OUT_751] } else { let (__hash, __hit) = record.function_queries[751].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[751].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[751].bump_multiplicity(__i); } let __ret: [G; OUT_751] = unsafe { *(record.function_queries[751].output_at(__i).as_ptr() as *const [G; OUT_751]) }; __ret }, _ => { aiur_fn_751::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 9u64 => { let __v_153: G = G::from_u64(2); if (__v_151 != __v_153) { return Err(ExecError::AssertEqMismatch { lhs: __v_151.as_canonical_u64(), rhs: __v_153.as_canonical_u64(), msg: Some("claim: wrong resource validator identity".to_string()) }); } let __v_154: G = G::from_u64(0); let __v_155: G = G::from_u64(1); if (__v_154 != __v_155) { return Err(ExecError::AssertEqMismatch { lhs: __v_154.as_canonical_u64(), rhs: __v_155.as_canonical_u64(), msg: Some("claim: resource-v1 is not implemented by IxVM".to_string()) }); } let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, 10u64 => { let __v_153: G = G::from_u64(3); if (__v_151 != __v_153) { return Err(ExecError::AssertEqMismatch { lhs: __v_151.as_canonical_u64(), rhs: __v_153.as_canonical_u64(), msg: Some("claim: wrong validator identity".to_string()) }); } let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_152]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_154: G = __r_arr[0]; let __v_155: G = __r_arr[1]; let __v_156: G = G::from_u64(109); let __v_157: G = G::from_u64(2); let __v_158: G = G::from_u64(21); let __v_159: G = G::from_u64(62); let __v_160: G = G::from_u64(163); let __v_161: G = G::from_u64(17); let __v_162: G = G::from_u64(243); let __v_163: G = G::from_u64(239); let __v_164: G = G::from_u64(209); let __v_165: G = G::from_u64(119); let __v_166: G = G::from_u64(140); let __v_167: G = G::from_u64(73); let __v_168: G = G::from_u64(178); let __v_169: G = G::from_u64(238); let __v_170: G = G::from_u64(95); let __v_171: G = G::from_u64(49); let __v_172: G = G::from_u64(231); let __v_173: G = G::from_u64(154); let __v_174: G = G::from_u64(11); let __v_175: G = G::from_u64(174); let __v_176: G = G::from_u64(149); let __v_177: G = G::from_u64(229); let __v_178: G = G::from_u64(51); let __v_179: G = G::from_u64(160); let __v_180: G = G::from_u64(141); let __v_181: G = G::from_u64(20); let __v_182: G = G::from_u64(252); let __v_183: G = G::from_u64(5); let __v_184: G = G::from_u64(87); let __v_185: G = G::from_u64(45); let __v_186: G = { let __values: [G; 32] = [__v_156, __v_157, __v_158, __v_159, __v_160, __v_161, __v_162, __v_163, __v_164, __v_165, __v_166, __v_167, __v_168, __v_169, __v_170, __v_171, __v_172, __v_173, __v_174, __v_175, __v_176, __v_177, __v_178, __v_179, __v_180, __v_167, __v_181, __v_182, __v_183, __v_184, __v_185, __v_178]; let __mq = record.memory_queries.get_index_mut(12).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(32))?; let (__hash, __hit) = __mq.lookup(&__values[..]); match __hit { Some(__i) => { if (!unconstrained) { __mq.bump_multiplicity(__i); } __mq.output_at(__i)[0] }, _ => { aiur::execute::check_store(&record.budget, __mq, 32)?; let __ptr = G::from_usize(record.pointer_base + __mq.len()); __mq.insert_hashed(&__values[..], &[__ptr], G::from_bool(!unconstrained), __hash)?; __ptr }, } }; let __r_arr: [G; OUT_2] = { let __args: [G; IN_2] = [__v_154, __v_186]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(2, &__args[..])?) { [G::ZERO; OUT_2] } else { let (__hash, __hit) = record.function_queries[2].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[2].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[2].bump_multiplicity(__i); } let __ret: [G; OUT_2] = unsafe { *(record.function_queries[2].output_at(__i).as_ptr() as *const [G; OUT_2]) }; __ret }, _ => { aiur_fn_2::(__args, __hash, record, io_buffer)? }, } } }; let __v_187: G = __r_arr[0]; let __v_188: G = G::from_u64(1); if (__v_187 != __v_188) { return Err(ExecError::AssertEqMismatch { lhs: __v_187.as_canonical_u64(), rhs: __v_188.as_canonical_u64(), msg: Some("claim: specification digest is not this circuit's".to_string()) }); } let __r_arr: [G; OUT_102] = { let __args: [G; IN_102] = [__v_155]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(102, &__args[..])?) { [G::ZERO; OUT_102] } else { let (__hash, __hit) = record.function_queries[102].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[102].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[102].bump_multiplicity(__i); } let __ret: [G; OUT_102] = unsafe { *(record.function_queries[102].output_at(__i).as_ptr() as *const [G; OUT_102]) }; __ret }, _ => { aiur_fn_102::(__args, __hash, record, io_buffer)? }, } } }; let __v_189: G = __r_arr[0]; let __v_190: G = __r_arr[1]; let __loaded: [G; 3] = { let __base = record.pointer_base; let __mq = record.memory_queries.get_index_mut(1).map(|(_, q)| q).ok_or(ExecError::InvalidMemorySize(3))?; let __ptr_u64 = __v_190.as_canonical_u64(); let __idx = usize::try_from(__ptr_u64).ok().ok_or(ExecError::PointerTooLarge(__ptr_u64))?.checked_sub(__base).filter(|__i| ((*__i) < __mq.len())).ok_or(ExecError::UnboundPointer { ptr: __ptr_u64, size: 3 })?; if (!unconstrained) { __mq.bump_multiplicity(__idx); } let (__args, _) = __mq.get_index(__idx).expect("bounds checked above"); let __arr: [G; 3] = __args[..3].try_into().unwrap(); __arr }; let __v_191: G = __loaded[0]; let __v_192: G = __loaded[1]; let __v_193: G = __loaded[2]; if (__v_191 != __v_188) { return Err(ExecError::AssertEqMismatch { lhs: __v_191.as_canonical_u64(), rhs: __v_188.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_192 != __v_188) { return Err(ExecError::AssertEqMismatch { lhs: __v_192.as_canonical_u64(), rhs: __v_188.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } if (__v_193 != __v_188) { return Err(ExecError::AssertEqMismatch { lhs: __v_193.as_canonical_u64(), rhs: __v_188.as_canonical_u64(), msg: Some("claim: trailing bytes".to_string()) }); } let __r_arr: [G; OUT_488] = { let __args: [G; IN_488] = [__v_189]; let __cu = unconstrained; if ((!unconstrained) && record.defer_call(488, &__args[..])?) { [G::ZERO; OUT_488] } else { let (__hash, __hit) = record.function_queries[488].lookup(&__args[..]); match __hit { Some(__i) if (__cu || (record.function_queries[488].mult_at(__i) != G::ZERO)) => { if (!unconstrained) { record.function_queries[488].bump_multiplicity(__i); } let __ret: [G; OUT_488] = unsafe { *(record.function_queries[488].output_at(__i).as_ptr() as *const [G; OUT_488]) }; __ret }, _ => { aiur_fn_488::(__args, __hash, record, io_buffer)? }, } } }; let __ret: [G; OUT_752] = []; record.function_queries[752].finish_hashed(&inp[..], &__ret[..], !unconstrained, input_hash)?; return Ok(__ret); }, _ => { return Err(ExecError::MatchNoCase(__v_133.as_canonical_u64())); }, } }) } const INPUT_SIZE_753: usize = 3; const IN_753: usize = 3; const OUT_753: usize = 1; diff --git a/crates/kernel/Cargo.toml b/crates/kernel/Cargo.toml deleted file mode 100644 index 172fce482..000000000 --- a/crates/kernel/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "ix-kernel" -version.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -bignat = { workspace = true } -blake3 = { workspace = true } -dashmap = { workspace = true } -indexmap = { workspace = true } -ix-common = { workspace = true } -ixon = { workspace = true } -itertools = { workspace = true } -log = { workspace = true } -num-bigint = { workspace = true } -rustc-hash = { workspace = true } -smallvec = { workspace = true } - -[target.'cfg(not(target_arch = "riscv64"))'.dependencies] -dashmap = { workspace = true, features = ["rayon"] } -rayon = { workspace = true } -stacker = { workspace = true } - -[dev-dependencies] -quickcheck = { workspace = true } -quickcheck_macros = { workspace = true } - -[lints] -workspace = true diff --git a/crates/kernel/src/anon_env.rs b/crates/kernel/src/anon_env.rs deleted file mode 100644 index fe8375f78..000000000 --- a/crates/kernel/src/anon_env.rs +++ /dev/null @@ -1,174 +0,0 @@ -//! `AnonEnv<'a>` — restricted view over `IxonEnv` exposing only the -//! anonymous (metadata-free) sections. -//! -//! Held in lieu of `&IxonEnv` everywhere the anon-mode kernel needs -//! env access (constant materialization, blob lookup, dependency -//! walking). Crucially, it does **not** expose `named`, `names`, or -//! `comms` — Lean.Name → metadata mappings that the anon kernel must -//! never consult. -//! -//! Two construction paths: -//! -//! 1. [`AnonEnv::from_env`] — borrowed view over an existing -//! `IxonEnv`. The full env may still have metadata sections -//! populated, but anon-mode code can't reach them through this -//! wrapper. -//! -//! 2. [`Env::get_anon`] (Phase 3) — built fresh from `.ixe` bytes -//! with the named/names/comms sections skipped during load. -//! -//! Either way, code parameterized over `&AnonEnv<'_>` is structurally -//! prevented from accessing metadata. - -use rustc_hash::FxHashSet; -use std::collections::VecDeque; -use std::sync::Arc; - -use ix_common::address::Address; -use ixon::constant::Constant; -use ixon::env::Env as IxonEnv; - -/// Anonymous-only view over an `IxonEnv`. -#[derive(Clone, Copy, Debug)] -pub struct AnonEnv<'a> { - inner: &'a IxonEnv, -} - -impl<'a> AnonEnv<'a> { - /// Construct a view that exposes only the anonymous sections. - pub fn from_env(env: &'a IxonEnv) -> Self { - AnonEnv { inner: env } - } - - /// Materialize the constant at `addr`. - pub fn get_const(&self, addr: &Address) -> Option> { - self.inner.get_const(addr) - } - - /// Raw constant bytes (no materialization). - pub fn get_const_bytes(&self, addr: &Address) -> Option> { - self.inner.get_const_bytes(addr) - } - - /// Whether a constant is present in this view. - pub fn contains_const(&self, addr: &Address) -> bool { - self.inner.consts.contains_key(addr) - } - - /// Number of constants. - pub fn const_count(&self) -> usize { - self.inner.const_count() - } - - /// Get a blob by address (needed by anon ingress for string/nat - /// literals embedded in expressions). - pub fn get_blob(&self, addr: &Address) -> Option> { - self.inner.get_blob(addr) - } - - /// BFS-collect all addresses transitively reachable from `root` - /// via `Constant.refs`. The returned set includes `root`. - /// - /// Materializes every visited constant. Constants outside the - /// closure are not touched. - pub fn bfs_refs(&self, root: &Address) -> FxHashSet
{ - let mut visited: FxHashSet
= FxHashSet::default(); - let mut queue: VecDeque
= VecDeque::new(); - visited.insert(root.clone()); - queue.push_back(root.clone()); - while let Some(addr) = queue.pop_front() { - let refs: Option> = - self.get_const(&addr).map(|c| c.refs.clone()); - if let Some(rs) = refs { - for r in rs { - if visited.insert(r.clone()) { - queue.push_back(r); - } - } - } - } - visited - } - - /// Transitive dep addresses of `root`, excluding `root`, sorted. - pub fn transitive_deps_excl(&self, root: &Address) -> Vec
{ - let mut set = self.bfs_refs(root); - set.remove(root); - let mut v: Vec
= set.into_iter().collect(); - v.sort_unstable(); - v - } -} - -#[cfg(test)] -mod tests { - use super::*; - use ix_common::env::Name; - use ixon::constant::{Axiom, ConstantInfo}; - use ixon::env::Named; - use ixon::expr::Expr; - - fn axiom_const_with_refs(refs: Vec
) -> Constant { - Constant::with_tables( - ConstantInfo::Axio(Axiom { - is_unsafe: false, - lvls: 0, - typ: Expr::sort(0), - }), - Vec::new(), - refs, - Vec::new(), - ) - } - - fn n(s: &str) -> Name { - Name::str(Name::anon(), s.to_string()) - } - - #[test] - fn from_env_exposes_consts_and_blobs() { - let env = IxonEnv::new(); - let a = Address::hash(b"a"); - env.store_const(a.clone(), axiom_const_with_refs(vec![])); - let blob_addr = env.store_blob(b"hello".to_vec()); - let view = AnonEnv::from_env(&env); - assert!(view.contains_const(&a)); - assert_eq!(view.const_count(), 1); - assert_eq!(view.get_blob(&blob_addr), Some(b"hello".to_vec())); - } - - #[test] - fn bfs_refs_via_view_matches_env() { - let env = IxonEnv::new(); - let a = Address::hash(b"a"); - let b = Address::hash(b"b"); - let c = Address::hash(b"c"); - env.store_const(a.clone(), axiom_const_with_refs(vec![b.clone()])); - env.store_const(b.clone(), axiom_const_with_refs(vec![c.clone()])); - env.store_const(c.clone(), axiom_const_with_refs(vec![])); - let view = AnonEnv::from_env(&env); - let visited = view.bfs_refs(&a); - assert_eq!(visited.len(), 3); - assert!(visited.contains(&a)); - assert!(visited.contains(&b)); - assert!(visited.contains(&c)); - } - - /// The wrapper exposes no API to reach the named map. Compile-time - /// guarantee: this test just checks that we can construct an - /// `AnonEnv` from an env whose `named` map is populated, and the - /// public surface gives us no way to reach those entries. - #[test] - fn no_public_named_access() { - let env = IxonEnv::new(); - let a = Address::hash(b"a"); - env.store_const(a.clone(), axiom_const_with_refs(vec![])); - env.register_name(n("MyConst"), Named::with_addr(a.clone())); - assert_eq!(env.named_count(), 1); - let view = AnonEnv::from_env(&env); - // `view.contains_const(&a)` works — `view.lookup_name(...)` doesn't exist. - assert!(view.contains_const(&a)); - // (No assertion possible against absence of an API — this test - // documents intent.) - } -} diff --git a/crates/kernel/src/anon_work.rs b/crates/kernel/src/anon_work.rs deleted file mode 100644 index fd903afda..000000000 --- a/crates/kernel/src/anon_work.rs +++ /dev/null @@ -1,431 +0,0 @@ -//! Anon-mode work enumeration over `IxonEnv::consts`. -//! -//! Identifies the set of kernel-checkable target addresses without -//! consulting any metadata sections (`named`/`names`/`comms`), so it -//! works against an env loaded via [`ixon::env::Env::get_anon`]. -//! -//! The set produced here is the canonical work set for anon-mode -//! typechecking: it matches `rs_kernel_check_anon` in -//! `crates/ffi/src/kernel.rs` and is what an Aiur-style verifier -//! commits to. Callers iterate the returned `Vec` and -//! invoke `IxonChecker::check_const` on each item's `primary` address; -//! the kernel's internal block coordination handles checking every -//! member + ctor of `Block` items. -//! -//! ## Enumeration rules -//! -//! For each entry in `env.consts`: -//! - Projection variants (IPrj/CPrj/RPrj/DPrj) are skipped — they're -//! covered by checking their parent Muts block. -//! - Standalone variants (Defn/Recr/Axio/Quot) emit one `Standalone` -//! work item with the constant's own address. -//! - Muts blocks materialize to enumerate members, then emit one -//! `Block` item whose `primary` is the first member's projection -//! address (and `targets` includes that primary plus every other -//! member's projection plus one CPrj per constructor of each -//! inductive member). Checking the primary triggers the kernel's -//! block-coordination logic which covers every target. -//! -//! Dispatch on the outer `TagN` byte via -//! [`ixon::lazy::LazyConstant::peek_variant`] avoids body parse + -//! `Arc` allocation for the ~95% of constants that are -//! standalones or projections. - -use ix_common::address::Address; -use ixon::env::Env as IxonEnv; -use ixon::lazy::ConstVariantTag; - -use crate::ingress::{ - anon_ctor_proj_addr, anon_defn_proj_addr, anon_indc_proj_addr, - anon_recr_proj_addr, -}; - -/// A single anon-mode work item — one Ixon whole-unit check per -/// item suffices to typecheck every address in `targets`. -#[derive(Clone, Debug)] -pub enum AnonWorkItem { - /// A standalone (Defn/Recr/Axio/Quot) constant. `addr` is both the - /// kernel-checked target and the only address this item covers. - Standalone { addr: Address }, - /// A Muts block. Checking `primary` (the first member's projection - /// address) drives the kernel's block coordination, which in turn - /// typechecks every entry in `targets`. `targets[0] == primary`. - /// `block_addr` is the block's own `env.consts` key (the `Tag::Muts` - /// entry) — distinct from the projection `targets`, and the address - /// other constants' `refs` use to reference the block. Carried so the - /// set of `consts` keys a checked item certifies ([`proven_targets`]) - /// is in the same address space as `Constant.refs`. - /// - /// [`proven_targets`]: AnonWorkItem::proven_targets - Block { block_addr: Address, primary: Address, targets: Vec
}, -} - -impl AnonWorkItem { - /// The address to pass to the Ixon whole-unit checking entry. - pub fn primary(&self) -> &Address { - match self { - AnonWorkItem::Standalone { addr } => addr, - AnonWorkItem::Block { primary, .. } => primary, - } - } - - /// Every kernel-checked target this item covers (one for - /// `Standalone`, `targets.len()` for `Block`). - pub fn targets(&self) -> &[Address] { - match self { - AnonWorkItem::Standalone { addr } => core::slice::from_ref(addr), - AnonWorkItem::Block { targets, .. } => targets, - } - } - - /// Every `env.consts` key this item certifies (proves) well-typed when - /// its `primary` is checked — the *proven targets*, in the same address - /// space as `Constant.refs`, so a dependency ref can be matched against - /// the union of `proven_targets()` over all checked items: refs in that - /// union are already certified, the rest are the claim's open assumptions - /// (resolved later against other proofs). Unlike - /// [`targets`](Self::targets) (the addresses the kernel actually walks), - /// a `Block`'s proven set also includes the block's own `consts` key: - /// for a `Standalone` it's just its address; for a `Block` it's - /// `block_addr` plus every projection target (members + ctors). The - /// set includes synthesized semantic members whose projection records the - /// input may omit. It is not a physical-record coverage set; a host checks - /// carried records separately and does not add synthetic bytes to its input. - pub fn proven_targets(&self) -> Vec
{ - match self { - AnonWorkItem::Standalone { addr } => vec![addr.clone()], - AnonWorkItem::Block { block_addr, targets, .. } => { - let mut v = Vec::with_capacity(1 + targets.len()); - v.push(block_addr.clone()); - v.extend(targets.iter().cloned()); - v - }, - } - } -} - -fn mutual_work( - address: &Address, - members: &[ixon::constant::MutConst], -) -> Result { - use ixon::constant::MutConst; - crate::ingress::validate_serialized_muts(members)?; - let mut targets = Vec::new(); - for (index, member) in members.iter().enumerate() { - let index = u64::try_from(index).map_err(|error| error.to_string())?; - targets.push(match member { - MutConst::Defn(_) => anon_defn_proj_addr(address, index), - MutConst::Indc(_) => anon_indc_proj_addr(address, index), - MutConst::Recr(_) => anon_recr_proj_addr(address, index), - }); - if let MutConst::Indc(inductive) = member { - for constructor in 0..inductive.ctors.len() { - targets.push(anon_ctor_proj_addr( - address, - index, - u64::try_from(constructor).map_err(|error| error.to_string())?, - )); - } - } - } - Ok(AnonWorkItem::Block { - block_addr: address.clone(), - primary: targets[0].clone(), - targets, - }) -} - -/// Bound-host scheduling reads authenticated headers/owners, never cached ASTs. -/// A whole walk also checks exact key coverage, including carried projections. -#[cfg(not(target_arch = "riscv64"))] -pub fn build_artifact_work( - source: &crate::role_admission::ArtifactSource, -) -> Result, String> { - use ConstVariantTag as Tag; - let mut keys = source.addresses(); - keys.sort_unstable(); - let mut work = Vec::new(); - for address in &keys { - match source.record_tag(address).map_err(|error| error.to_string())? { - Tag::IPrj | Tag::CPrj | Tag::RPrj | Tag::DPrj => { - // Every carried projection must be genuine and resolve its actual - // owner/kind/index, even if no semantic subject selects it directly. - source - .authenticate_reference(address) - .map_err(|error| error.to_string())?; - }, - Tag::Muts => { - let record = - source.record(address).map_err(|error| error.to_string())?; - let ixon::constant::ConstantInfo::Muts(members) = - &record.constant().info - else { - return Err( - "authenticated mutual header differs from decoded record".into(), - ); - }; - work.push(mutual_work(address, members)?); - }, - _ => work.push(AnonWorkItem::Standalone { addr: address.clone() }), - } - } - let carried: rustc_hash::FxHashSet<_> = keys.iter().cloned().collect(); - let mut covered: Vec<_> = work - .iter() - .flat_map(AnonWorkItem::proven_targets) - .filter(|address| carried.contains(address)) - .collect(); - covered.sort_unstable(); - if covered != keys { - return Err( - "artifact work does not cover the exact raw record set once".into(), - ); - } - Ok(work) -} - -/// The items of `work` that a verdict on `roots` must check. With -/// `dependencies`, these are the items inside -/// [`ArtifactSource::verdict_closure`]; without, the items covering `roots` -/// themselves, whose dependencies are trusted. Either way the set includes -/// the closure of the binding's support: role admission checks a role -/// against its specification but defers its records' and certificates' -/// units, so no verdict that may rest on an admitted role omits them. -/// -/// [`ArtifactSource::verdict_closure`]: -/// crate::role_admission::ArtifactSource::verdict_closure -#[cfg(not(target_arch = "riscv64"))] -pub fn verdict_work( - source: &crate::role_admission::ArtifactSource, - work: &[AnonWorkItem], - roots: &[Address], - dependencies: bool, -) -> Result, String> { - let selected = if dependencies { - source.verdict_closure(roots) - } else { - source.verdict_closure(&[]).map(|mut support| { - support.extend(roots.iter().cloned()); - support - }) - } - .map_err(|error| error.to_string())?; - Ok( - work - .iter() - .filter(|item| match item { - AnonWorkItem::Standalone { addr } => selected.contains(addr), - AnonWorkItem::Block { block_addr, .. } => selected.contains(block_addr), - }) - .cloned() - .collect(), - ) -} - -/// Enumerate the anon-mode kernel work set from `env.consts`. -/// -/// Returns one `AnonWorkItem` per kernel-checkable group of -/// constants. The total number of checked target addresses is -/// `work.iter().map(|w| w.targets().len()).sum()`. -/// -/// Refuses corrupted entries and unsupported serialized mutual shapes before -/// promising coverage for their member targets. -pub fn build_anon_work(env: &IxonEnv) -> Result, String> { - use ConstVariantTag as Tag; - use ixon::constant::ConstantInfo as CI; - - let mut work: Vec = Vec::new(); - - // Sort keys for deterministic ordering across runs. - let mut keys: Vec
= - env.consts.iter().map(|e| e.key().clone()).collect(); - keys.sort_unstable(); - - for addr in keys { - let lc = env.consts.get(&addr).ok_or_else(|| { - format!("build_anon_work: missing const at {}", addr.hex()) - })?; - let tag = lc.value().peek_variant().map_err(|e| { - format!("build_anon_work: peek_variant {}: {e}", addr.hex()) - })?; - match tag { - Tag::IPrj | Tag::CPrj | Tag::RPrj | Tag::DPrj => { - // Skip — covered by parent block. - }, - Tag::Defn | Tag::Recr | Tag::Axio | Tag::Quot => { - work.push(AnonWorkItem::Standalone { addr: addr.clone() }); - }, - Tag::Muts => { - // Materialize once to enumerate members; the `Arc` - // drops at the end of this arm — no cache retention. - let constant = lc.value().get().map_err(|e| { - format!("build_anon_work: materialize Muts {}: {e}", addr.hex()) - })?; - let CI::Muts(members) = &constant.info else { - return Err(format!( - "build_anon_work: Tag::Muts but ConstantInfo is {:?} at {}", - constant.info.variant(), - addr.hex() - )); - }; - work.push(mutual_work(&addr, members)?); - }, - } - } - - Ok(work) -} - -/// The full dependency-closure address set of `roots`: their transitive Expr -/// `refs` PLUS each projection's structural `block: Address`. A refs-only walk -/// misses the block — projections (IPrj/CPrj/RPrj/DPrj) point at their Muts -/// block via a struct field, not the Expr ref table — so the kernel couldn't -/// resolve the projection and would spuriously fail. The returned set includes -/// constant AND referenced-blob addresses (external refs absent from `source` -/// are included as addresses but have no bytes to copy). -pub fn closure_addrs( - source: &IxonEnv, - roots: &[Address], -) -> std::collections::HashSet
{ - // The traversal lives in `ixon` (`Env::bfs_closure`) so bundle - // pruning/validation and the shard sub-env share one edge - // definition; this wrapper keeps the kernel-side std-HashSet - // signature. - source.bfs_closure(roots).into_iter().collect() -} - -/// Build a closure sub-env: serialize only the BFS dependency closure of -/// `roots` (their transitive `Constant.refs` plus referenced literal blobs), -/// copying each constant's GENUINE bytes via `store_const_lazy` so the guest's -/// per-const integrity check (`hash(bytes) == addr`) and the env merkle root -/// still hold. The guest decodes this instead of the whole env, so it pays only -/// its closure's decode, bounding memory use for large environments. -/// Missing hints are -/// performance-only: ingress falls back to `Regular(0)`, so the typecheck -/// result — and thus the committed claim — is unchanged. -/// External refs (not in `source`) are omitted and remain open assumptions, -/// exactly as in whole-env. -/// -/// Host-only: builds an `Env` via the `store_*` helpers, which are gated to -/// non-riscv64 (see `Env::store_blob`). The guest receives an already-built -/// sub-env from the host and only enumerates it via `build_anon_work`. -#[cfg(not(target_arch = "riscv64"))] -pub fn build_sub_env( - source: &IxonEnv, - roots: &[Address], -) -> Result, String> { - let sub = sub_env_of(source, roots); - let mut buf = Vec::new(); - sub.put(&mut buf).map_err(|e| format!("sub-env serialize: {e}"))?; - Ok(buf) -} - -/// The in-memory closure sub-env behind [`build_sub_env`]: copy the BFS -/// dependency closure of `roots` (genuine bytes + blobs + reducibility -/// hints), no Named section. A bound source's binding is copied with the -/// closure of the records it names, so the sub-env carries every record its -/// role admission reads and every unit a verdict on `roots` rests on (see -/// [`verdict_work`]). A binding that does not decode is copied as is, and -/// the sub-env's reader refuses it. -#[cfg(not(target_arch = "riscv64"))] -fn sub_env_of(source: &IxonEnv, roots: &[Address]) -> IxonEnv { - let mut roots = roots.to_vec(); - if let Some(binding) = &source.role_binding - && let Ok(table) = ixon::role_table::RoleTable::from_bytes(&binding.bytes) - { - roots.extend(table.records().cloned()); - } - let closure = closure_addrs(source, &roots); - let mut sub = IxonEnv::new(); - sub.role_binding = source.role_binding.clone(); - for addr in &closure { - if let Some(bytes) = source.get_const_bytes(addr) { - sub.store_const_lazy(addr.clone(), bytes); - } else if let Some(blob) = source.get_blob(addr) { - sub.store_blob(blob); - } - // else: external ref absent from `source` — omit; stays an open assumption. - // Carry the constant's reducibility hint so the guest reproduces vanilla - // kernel behavior; without hints the kernel forces `Regular(0)` and does - // extra def-eq work (the ~30% check overhead). - if let Some(h) = source.anon_hints.get(addr) { - sub.anon_hints.insert(addr.clone(), *h); - } - } - sub -} - -/// [`build_sub_env`] plus a name→address entry for every closure constant -/// named in the FULL view of the same env — a standalone `.ixe` whose names -/// still resolve (for `--consts`-style tools), extracted without recompiling -/// from source. -/// -/// METADATA IS DROPPED: each copied entry is `Named::with_addr` (empty -/// `ConstantMeta`), because real metadata references name addresses -/// throughout its tree and carrying it would require the full env's -/// hash-consed name index. The extract serves the ANON pipeline -/// (`check-rs --anon`, the zkVM hosts, `ix profile`/`ix shard`, -/// `bench-typecheck`), where metadata is never read and reducibility hints -/// travel in the `anon_hints` section instead. Meta-mode tools need the -/// source env. -#[cfg(not(target_arch = "riscv64"))] -pub fn build_sub_env_named( - source: &IxonEnv, - full: &IxonEnv, - roots: &[Address], -) -> Result, String> { - use ix_common::env::NameData; - - let sub = sub_env_of(source, roots); - // The Named section serializes keys as name HASHES resolved through the - // names section, so each key's component chain must be interned too. - fn intern_chain(sub: &IxonEnv, name: &ix_common::env::Name) { - let addr = Address::from_blake3_hash(*name.get_hash()); - if sub.get_name(&addr).is_some() { - return; - } - match name.as_data() { - NameData::Anonymous(_) => {}, - NameData::Str(parent, _, _) | NameData::Num(parent, _, _) => { - intern_chain(sub, parent); - }, - } - sub.store_name(addr, name.clone()); - } - for e in full.named.iter() { - if sub.get_const(&e.value().addr).is_some() { - intern_chain(&sub, e.key()); - sub.register_name( - e.key().clone(), - ixon::env::Named::with_addr(e.value().addr.clone()), - ); - } - } - let mut buf = Vec::new(); - sub.put(&mut buf).map_err(|e| format!("sub-env serialize: {e}"))?; - Ok(buf) -} - -/// The ingress-block address that owns `addr`: a projection (IPrj/CPrj/RPrj/ -/// DPrj) maps to its Muts `block`; anything else is its own block. Used to map -/// a resolved constant address to the `build_anon_work` item that covers it -/// (standalone → itself; a mutual-block member → the whole block). -pub fn block_of_addr(env: &IxonEnv, addr: &Address) -> Address { - use ixon::constant::ConstantInfo as CI; - match env.get_const(addr) { - Some(c) => match &c.info { - CI::IPrj(p) => p.block.clone(), - CI::CPrj(p) => p.block.clone(), - CI::RPrj(p) => p.block.clone(), - CI::DPrj(p) => p.block.clone(), - _ => addr.clone(), - }, - None => addr.clone(), - } -} - -/// The ingress-block address of a work item's `primary` — the key for matching -/// a resolved target constant to the work item that covers it. -pub fn work_block_addr(env: &IxonEnv, item: &AnonWorkItem) -> Address { - block_of_addr(env, item.primary()) -} diff --git a/crates/kernel/src/artifact_checker.rs b/crates/kernel/src/artifact_checker.rs deleted file mode 100644 index c825e4c62..000000000 --- a/crates/kernel/src/artifact_checker.rs +++ /dev/null @@ -1,358 +0,0 @@ -//! Sealed host workers. Source snapshots are shared; declaration/reduction -//! caches are private to a worker and cannot be seeded by a public KEnv caller. -//! A bound worker's primitive table holds only the roles its source has -//! admitted, and a unit holding a role that is not admitted refuses. - -use std::sync::Arc; - -use ix_common::address::Address; -use ix_common::env::Name; -use ixon::env::Env; - -use crate::env::KEnv; -use crate::error::TcError; -use crate::id::KId; -use crate::ingress::{IxonIngressLookups, ingress_name_with_input}; -use crate::input::Input; -use crate::mode::{Anon, CheckDupLevelParams, KernelMode}; -use crate::primitive::Primitives; -use crate::primitive_context::PrimitiveContext; -use crate::profile::ProfileSink; -use crate::role_admission::{ArtifactSource, Error}; -use crate::tc::TypeChecker; - -#[derive(Default)] -pub struct CheckStats { - pub fuel_used: u64, - pub def_eq_peak: u32, - pub hot_misses: String, -} - -type MakeChecker = for<'a> fn( - &'a mut KEnv, - &'a ArtifactSource, - &'a IxonIngressLookups, - PrimitiveContext, -) -> TypeChecker<'a, M>; - -fn make_named<'a, M: KernelMode>( - env: &'a mut KEnv, - source: &'a ArtifactSource, - lookups: &'a IxonIngressLookups, - primitives: PrimitiveContext, -) -> TypeChecker<'a, M> { - TypeChecker::for_artifact_named(env, source, lookups, primitives) -} -fn make_anon<'a>( - env: &'a mut KEnv, - source: &'a ArtifactSource, - lookups: &'a IxonIngressLookups, - primitives: PrimitiveContext, -) -> TypeChecker<'a, Anon> { - TypeChecker::for_artifact_anon(env, source, lookups, primitives) -} - -pub struct ArtifactChecker { - source: Result, - pub(crate) kernel: KEnv, - lookups: Arc, - primitives: PrimitiveContext, - make: MakeChecker, - last_check: CheckStats, - debug_label: Option, -} - -impl ArtifactChecker { - fn prepare( - source: Result, - named: bool, - make: MakeChecker, - ) -> Self { - let lookups = if named { - source.as_ref().map_or_else( - |_| Arc::new(IxonIngressLookups::default()), - ArtifactSource::lookups, - ) - } else { - Arc::new(IxonIngressLookups::default()) - }; - let primitives = match &source { - Ok(source) if source.binding().is_some() => PrimitiveContext::bound( - source.clone(), - source.admission().roles(), - |address| lookups.name_for_addr(address).cloned(), - ), - Ok(_) => PrimitiveContext::Legacy(Arc::new(Primitives::from_addr_names( - |address| lookups.name_for_addr(address).cloned(), - ))), - Err(_) => PrimitiveContext::UnpreparedBound, - }; - Self { - source, - kernel: KEnv::new(), - lookups, - primitives, - make, - last_check: CheckStats::default(), - debug_label: None, - } - } - - /// Borrowed capture requires quiescent input maps. Production drivers should - /// prepare once before worker creation and clone the resulting source. - pub fn new_named(source: &Env) -> Self { - Self::from_named_source(ArtifactSource::snapshot(source)) - } - pub fn from_named_source(source: Result) -> Self { - Self::prepare(source, true, make_named) - } - - fn source_for( - &self, - address: &Address, - ) -> Result<&ArtifactSource, TcError> { - let source = self - .source - .as_ref() - .map_err(|error| TcError::Other(error.to_string()))?; - if source.binding().is_some() { - // The certified checker reads no stream whose prelude it cannot read. - if let Some(reason) = source.prelude_refusal() { - return Err(TcError::Other(format!( - "the artifact's prelude is not admitted: {reason}" - ))); - } - let roles = source - .unit_roles(address) - .map_err(|error| TcError::Other(error.to_string()))?; - // A unit holding a bound role is checked only once that role's group - // has been admitted; its candidate address is otherwise only syntax. - if let Some(role) = roles.iter().find(|role| !source.is_admitted(**role)) - { - let candidate = self.primitives.candidate_address(*role); - return Err(TcError::Other(format!( - "bound unit requires unadmitted role {role:?} at {candidate:?}" - ))); - } - } - Ok(source) - } - - pub fn check_const(&mut self, address: &Address) -> Result<(), TcError> - where - M::MField>: CheckDupLevelParams, - { - self.last_check = CheckStats::default(); - self.source_for(address)?; - let source = self - .source - .as_ref() - .map_err(|error| TcError::Other(error.to_string()))?; - let id = KId::new( - address.clone(), - M::meta_field( - self.lookups.name_for_addr(address).cloned().unwrap_or_else(Name::anon), - ), - ); - self.check_id(&id, &source.clone()) - } - - /// The records a verdict on `roots` rests on, including both the reference - /// closure of `roots` and the binding's support closure. - pub fn verdict_closure( - &self, - roots: &[Address], - ) -> Result, TcError> { - let source = self - .source - .as_ref() - .map_err(|error| TcError::Other(error.to_string()))?; - source - .verdict_closure(roots) - .map_err(|error| TcError::Other(error.to_string())) - } - - /// Check `address` together with every record and blob in its - /// [`ArtifactSource::verdict_closure`]. - pub fn check_const_with_verdict_closure( - &mut self, - address: &Address, - ) -> Result<(), TcError> - where - M::MField>: CheckDupLevelParams, - { - let source = self.source_for(address)?.clone(); - let mut closure: Vec<_> = source - .verdict_closure(std::slice::from_ref(address)) - .map_err(|error| TcError::Other(error.to_string()))? - .into_iter() - .collect(); - closure.sort_unstable(); - for entry in &closure { - if source.contains_record(entry) { - if source - .record_tag(entry) - .map_err(|error| TcError::Other(error.to_string()))? - != ixon::lazy::ConstVariantTag::Muts - { - self.check_const(entry)?; - } - } else { - source - .blob(entry) - .map_err(|error| TcError::Other(error.to_string()))?; - } - } - if !closure.contains(address) { - self.check_const(address)?; - } - Ok(()) - } - - pub fn check_name(&mut self, name: &Name) -> Result<(), TcError> - where - M::MField>: CheckDupLevelParams, - { - self.last_check = CheckStats::default(); - let source = self - .source - .as_ref() - .map_err(|error| TcError::Other(error.to_string()))? - .clone(); - let named = source.named(name).ok_or_else(|| { - TcError::Other(format!("{}: missing Named entry", name.pretty())) - })?; - self.source_for(&named.addr)?; - let id = ingress_name_with_input( - &mut self.kernel, - Input::Artifact(&source), - &self.lookups, - name, - ) - .map_err(TcError::Other)?; - self.check_id(&id, &source) - } - - fn check_id( - &mut self, - id: &KId, - source: &ArtifactSource, - ) -> Result<(), TcError> - where - M::MField>: CheckDupLevelParams, - { - let mut checker = (self.make)( - &mut self.kernel, - source, - &self.lookups, - self.primitives.clone(), - ); - if let Some(label) = &self.debug_label { - checker.set_debug_label(label.clone()); - } - let result = checker.check_ixon_const(id); - self.last_check = CheckStats { - fuel_used: checker.fuel_used(), - def_eq_peak: checker.def_eq_peak, - hot_misses: checker.hot_miss_summary(), - }; - checker.finish_constant_accounting(); - result - } - - pub fn last_check(&self) -> &CheckStats { - &self.last_check - } - pub fn set_debug_label(&mut self, label: String) { - self.debug_label = Some(label); - } - pub fn perf(&self) -> &crate::perf::PerfCounters { - &self.kernel.perf - } - pub fn cache_sizes(&self) -> crate::env::KEnvCacheSizes { - self.kernel.cache_sizes() - } - pub fn clear_releasing_memory(&mut self) { - self.kernel.clear_releasing_memory(); - } - pub fn clear_with_capacity_limit(&mut self, max: usize) { - self.kernel.clear_with_capacity_limit(max); - } - pub fn clear_reduction_caches(&mut self) { - self.kernel.clear_reduction_caches(); - } - pub fn set_profile_sink(&mut self, sink: ProfileSink) { - self.kernel.profile_sink = Some(sink); - } - pub fn take_profile_sink(&mut self) -> Option { - self.kernel.profile_sink.take() - } -} - -impl ArtifactChecker { - /// Drive an actual carried unit owner. Its semantic primary may have no - /// separate projection record; arbitrary missing-address lookup is unchanged. - pub fn check_owner(&mut self, owner: &Address) -> Result<(), TcError> { - self.last_check = CheckStats::default(); - let source = self.source_for(owner)?.clone(); - let record = source - .record(owner) - .map_err(|error| TcError::Other(error.to_string()))?; - let members = crate::ingress::ingress_anon_block_input( - &mut self.kernel, - Input::Artifact(&source), - record.constant(), - owner, - ) - .map_err(TcError::Other)?; - let primary = members - .first() - .ok_or_else(|| TcError::Other("empty owner unit".into()))?; - self.check_id(primary, &source) - } - - pub fn check_work( - &mut self, - work: &crate::anon_work::AnonWorkItem, - ) -> Result<(), TcError> { - match work { - crate::anon_work::AnonWorkItem::Standalone { addr } => { - self.check_const(addr) - }, - crate::anon_work::AnonWorkItem::Block { block_addr, .. } => { - self.check_owner(block_addr) - }, - } - } - - /// Check every work item required for a sound verdict on `roots` (including - /// the binding's support closure via [`ArtifactSource::verdict_closure`]). - pub fn check_verdict( - &mut self, - roots: &[Address], - dependencies: bool, - ) -> Result<(), TcError> { - let source = self - .source - .as_ref() - .map_err(|error| TcError::Other(error.to_string()))? - .clone(); - let work = - crate::anon_work::build_artifact_work(&source).map_err(TcError::Other)?; - let selected = - crate::anon_work::verdict_work(&source, &work, roots, dependencies) - .map_err(TcError::Other)?; - for item in &selected { - self.check_work(item)?; - } - Ok(()) - } - /// Capturing from an mmap retains the loader's immutable-file lifetime - /// requirement. The anonymous worker never constructs metadata lookups. - pub fn new(source: &Env) -> Self { - Self::from_source(ArtifactSource::snapshot(source)) - } - pub fn from_source(source: Result) -> Self { - Self::prepare(source, false, make_anon) - } -} diff --git a/crates/kernel/src/bound_primitive_tests.rs b/crates/kernel/src/bound_primitive_tests.rs deleted file mode 100644 index 9c3fa8e74..000000000 --- a/crates/kernel/src/bound_primitive_tests.rs +++ /dev/null @@ -1,223 +0,0 @@ -//! Constructed native results against a validated, generated role binding. - -use super::*; -use crate::env::KEnv; -use crate::ingress::IxonIngressLookups; -use crate::mode::Anon; -use crate::primitive_context::PrimitiveContext; -use crate::role_admission::ArtifactSource; - -type E = KExpr; - -fn call_at( - source: &ArtifactSource, - role: Role, - levels: Vec>, - args: Vec, -) -> E { - let head = E::cnst( - KId::new(source.required_role(role).unwrap().clone(), ()), - levels.into(), - ); - args.into_iter().fold(head, E::app) -} - -fn call(source: &ArtifactSource, role: Role, args: Vec) -> E { - call_at(source, role, vec![], args) -} - -fn number(value: u64) -> E { - let value = Nat::from(value); - let key = Address::hash(&value.to_le_bytes()); - E::nat(value, key) -} - -fn string(value: &str) -> E { - E::str(value.to_owned(), Address::hash(value.as_bytes())) -} - -fn expect_number(value: &E, expected: u64) { - let ExprData::Nat(actual, _, _) = value.data() else { - panic!("expected a native Nat literal"); - }; - assert_eq!(actual.to_u64(), Some(expected)); -} - -fn same_type(checker: &mut TypeChecker<'_, Anon>, before: &E, after: &E) { - let before = checker.infer(before).unwrap(); - let after = checker.infer(after).unwrap(); - assert!(checker.is_def_eq(&before, &after).unwrap()); -} - -fn check_bound_reductions() { - let path = std::env::var("IX_ROLE_ARTIFACT") - .expect("IX_ROLE_ARTIFACT must name the validated all-role artifact"); - let bytes = std::fs::read(path).unwrap(); - let mut input = bytes.as_slice(); - let env = ixon::env::Env::get(&mut input).unwrap(); - assert!(input.is_empty()); - let source = ArtifactSource::prepare(env).unwrap(); - assert_eq!(source.admission().admitted_count(), Role::ALL.len()); - assert!(source.admission().refused().is_empty()); - let context = - PrimitiveContext::bound(source.clone(), source.admission().roles(), |_| { - None - }); - let lookups = IxonIngressLookups::default(); - let mut env = KEnv::::new(); - let mut checker = - TypeChecker::for_artifact_anon(&mut env, &source, &lookups, context); - assert!(checker.takes_extra(ExtraReduction::String)); - assert!(checker.takes_extra(ExtraReduction::BitVec)); - for disabled in [ - ExtraReduction::Decidable, - ExtraReduction::NatOffsets, - ExtraReduction::NatRecLinear, - ] { - assert!(!checker.takes_extra(disabled)); - } - - let append = - call(&source, Role::StringAppend, vec![string("aλ"), string("é🌍")]); - let appended = checker.try_reduce_string(&append).unwrap().unwrap(); - let ExprData::Str(value, _, _) = appended.data() else { - panic!("append did not produce a literal") - }; - assert_eq!(value, "aλé🌍"); - same_type(&mut checker, &append, &appended); - - let size = call(&source, Role::StringUtf8ByteSize, vec![appended]); - let sized = checker.try_reduce_string(&size).unwrap().unwrap(); - expect_number(&sized, 9); - same_type(&mut checker, &size, &sized); - for (value, expected) in [("aλ", 'λ'), ("", 'A')] { - let back = call(&source, Role::StringBack, vec![string(value)]); - let result = checker.try_reduce_string(&back).unwrap().unwrap(); - assert_eq!(checker.char_lit_value(&result), Some(expected)); - same_type(&mut checker, &back, &result); - } - let bytes = call(&source, Role::StringToByteArray, vec![string("")]); - let result = checker.try_reduce_string(&bytes).unwrap().unwrap(); - let ExprData::Const(id, _, _) = result.data() else { - panic!("expected ByteArray.empty") - }; - assert_eq!(&id.addr, source.required_role(Role::ByteArrayEmpty).unwrap()); - same_type(&mut checker, &bytes, &result); - let nonempty = call(&source, Role::StringToByteArray, vec![string("x")]); - assert!(checker.try_reduce_string(&nonempty).unwrap().is_none()); - - let equal = call(&source, Role::StringDecEq, vec![string("λ"), string("λ")]); - let result = checker.try_reduce_string(&equal).unwrap().unwrap(); - let (head, arguments) = collect_app_spine(&result); - let ExprData::Const(id, _, _) = head.data() else { - panic!("expected a Decidable constructor") - }; - assert_eq!(&id.addr, source.required_role(Role::DecidableIsTrue).unwrap()); - assert_eq!(arguments.len(), 2); - same_type(&mut checker, &equal, &result); - let unequal = - call(&source, Role::StringDecEq, vec![string("x"), string("y")]); - assert!(checker.try_reduce_string(&unequal).unwrap().is_none()); - - let char_type = call(&source, Role::Char, vec![]); - let nil = call_at( - &source, - Role::ListNil, - vec![KUniv::zero()], - vec![char_type.clone()], - ); - let chars = call_at( - &source, - Role::ListCons, - vec![KUniv::zero()], - vec![ - char_type, - call(&source, Role::CharOfNat, vec![number(u64::from(u32::from('λ')))]), - nil, - ], - ); - let from_list = call(&source, Role::StringOfList, vec![chars]); - let result = checker.try_reduce_string(&from_list).unwrap().unwrap(); - let ExprData::Str(value, _, _) = result.data() else { - panic!("expected constructor collapse to a literal") - }; - assert_eq!(value, "λ"); - same_type(&mut checker, &from_list, &result); - - let of_nat = - |width, n| call(&source, Role::BitVecOfNat, vec![number(width), number(n)]); - for (width, n, expected) in [(4, 19, 3), (0, 19, 0)] { - let term = - call(&source, Role::BitVecToNat, vec![number(width), of_nat(width, n)]); - let result = checker.try_reduce_bitvec(&term).unwrap().unwrap(); - expect_number(&result, expected); - same_type(&mut checker, &term, &result); - } - let width = call(&source, Role::NatAdd, vec![number(1), number(3)]); - let term = call(&source, Role::BitVecToNat, vec![width, of_nat(4, 19)]); - expect_number(&checker.try_reduce_bitvec(&term).unwrap().unwrap(), 3); - for (left, expected) in [(19, Role::BoolTrue), (5, Role::BoolFalse)] { - let term = call( - &source, - Role::BitVecUlt, - vec![number(4), of_nat(4, left), of_nat(4, 4)], - ); - let result = checker.try_reduce_bitvec(&term).unwrap().unwrap(); - let ExprData::Const(id, _, _) = result.data() else { - panic!("expected Bool result") - }; - assert_eq!(&id.addr, source.required_role(expected).unwrap()); - same_type(&mut checker, &term, &result); - } - for term in [ - call(&source, Role::BitVecToNat, vec![number(3), of_nat(4, 19)]), - call(&source, Role::BitVecUlt, vec![number(3), of_nat(4, 1), of_nat(4, 2)]), - call(&source, Role::BitVecToNat, vec![number(4), E::var(0, ())]), - call( - &source, - Role::BitVecUlt, - vec![number(4), E::var(0, ()), of_nat(4, 2)], - ), - ] { - assert!(checker.try_reduce_bitvec(&term).unwrap().is_none()); - } - - // A candidate table may know a head before its group is admitted. Another - // admitted head in the same family must not authorize the candidate's rule. - for role in [Role::StringAppend, Role::BitVecUlt] { - let admitted = source - .admission() - .roles() - .keys() - .copied() - .filter(|candidate| *candidate != role) - .collect(); - let context = PrimitiveContext::bound_with_table( - source.clone(), - source.admission().roles(), - admitted, - |_| None, - ); - let mut env = KEnv::::new(); - let mut candidate = - TypeChecker::for_artifact_anon(&mut env, &source, &lookups, context); - if role == Role::StringAppend { - assert!(candidate.try_reduce_string(&append).unwrap().is_none()); - } else { - let term = - call(&source, role, vec![number(4), of_nat(4, 1), of_nat(4, 2)]); - assert!(candidate.try_reduce_bitvec(&term).unwrap().is_none()); - } - } -} - -#[test] -#[ignore = "requires IX_ROLE_ARTIFACT from the validated Lean generator"] -fn admitted_bound_reducers_return_typed_results_only_for_specified_inputs() { - std::thread::Builder::new() - .stack_size(256 * 1024 * 1024) - .spawn(check_bound_reductions) - .unwrap() - .join() - .unwrap(); -} diff --git a/crates/kernel/src/check.rs b/crates/kernel/src/check.rs deleted file mode 100644 index 7b6748e6f..000000000 --- a/crates/kernel/src/check.rs +++ /dev/null @@ -1,2390 +0,0 @@ -//! Constant checking dispatch. - -use std::time::{Duration, Instant}; - -use rustc_hash::FxHashSet; - -use ix_common::address::Address; -use ix_common::env::{BinderInfo, DefinitionSafety, Name, QuotKind}; -use ixon::constant::DefKind; - -use super::constant::KConst; -use super::env::Addr; -use super::error::{TcError, u64_to_usize}; -use super::expr::{ExprData, KExpr}; -use super::id::KId; -use super::level::{KUniv, UnivData, univ_eq}; -use super::mode::{CheckDupLevelParams, KernelMode}; -use super::primitive::{Primitives, is_native_evaluation_addr}; -use super::tc::TypeChecker; - -/// Emit `[decl diff]` when a `Defn`'s value fails the `is_def_eq(val_ty, -/// ty)` check. The error itself (`DeclTypeMismatch`) carries no payload, -/// so without this gate the only signal is the constant's name. Under -/// `IX_DECL_DIFF=1` we dump `val_ty` / `ty` and their whnf forms to -/// pinpoint which sub-expression is stuck \u2014 sister tool to -/// `IX_APP_DIFF` in `infer.rs`. -#[cfg(not(target_arch = "riscv64"))] -static IX_DECL_DIFF: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_DECL_DIFF").is_ok()); -#[cfg(target_arch = "riscv64")] -static IX_DECL_DIFF: crate::EnvFlag = crate::EnvFlag::new(|| false); - -/// Per-phase timing for `Defn` checks. Set `IX_PHASE_TIMING=1` to see where a -/// slow constant spends its time. Noisy — gate on a single constant via focus -/// mode so only one line is printed. -#[cfg(not(target_arch = "riscv64"))] -static IX_PHASE_TIMING: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_PHASE_TIMING").is_ok()); -#[cfg(target_arch = "riscv64")] -static IX_PHASE_TIMING: crate::EnvFlag = crate::EnvFlag::new(|| false); - -#[derive(Clone, Copy, Debug, Default)] -struct ValidationTiming { - ty: Duration, - val: Duration, - rules: Duration, - univ: Duration, -} - -// Bound dependency traversal independently of type-inference fuel. -const MAX_DEFINITION_DEPENDENCY_STEPS: usize = 1_000_000; - -enum DefinitionDependencyTask { - Enter(KId), - Finish(KId, KConst), -} - -/// References in both the type and value, including projection heads. The -/// expression worklist visits shared syntax once without host-stack recursion. -fn definition_dependencies(c: &KConst) -> Vec> { - let KConst::Defn { ty, val, .. } = c else { return vec![] }; - let mut pending = vec![val, ty]; - let mut seen: FxHashSet = FxHashSet::default(); - let mut refs = vec![]; - while let Some(expr) = pending.pop() { - if !seen.insert(expr.hash_key()) { - continue; - } - match expr.data() { - ExprData::Const(id, ..) => refs.push(id.clone()), - ExprData::App(fun, arg, _) => pending.extend([arg, fun]), - ExprData::Lam(_, _, _, dom, body, _) - | ExprData::All(_, _, _, _, dom, body, _) => pending.extend([body, dom]), - ExprData::Let(_, ty, val, body, _, _) => { - pending.extend([body, val, ty]); - }, - ExprData::Prj(id, _, major, _) => { - refs.push(id.clone()); - pending.push(major); - }, - _ => {}, - } - } - refs -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum CheckBlockKind { - Defn, - Inductive, - Recursor, - Family, -} - -fn canonical_var(idx: u64) -> KExpr { - KExpr::var(idx, M::meta_field(Name::anon())) -} - -fn canonical_all( - bi: BinderInfo, - dom: KExpr, - body: KExpr, -) -> KExpr { - KExpr::all(M::meta_field(Name::anon()), M::meta_field(bi), dom, body) -} - -fn canonical_arrow(dom: KExpr, body: KExpr) -> KExpr { - canonical_all(BinderInfo::Default, dom, body) -} - -fn canonical_apps( - mut head: KExpr, - args: &[KExpr], -) -> KExpr { - for arg in args { - head = KExpr::app(head, arg.clone()); - } - head -} - -fn canonical_const(id: &KId, us: &[KUniv]) -> KExpr { - KExpr::cnst(id.clone(), us.iter().cloned().collect()) -} - -/// `α → α → Prop` at a point where `α` is `Var(0)`. -fn canonical_quot_relation() -> KExpr { - canonical_arrow( - canonical_var(0), - canonical_arrow(canonical_var(1), KExpr::sort(KUniv::zero())), - ) -} - -/// Exact semantic type required of the `Eq` prerequisite used by -/// `Environment.addQuot`. -fn canonical_eq_type() -> KExpr { - let u = KUniv::param(0, M::meta_field(Name::anon())); - canonical_all( - BinderInfo::Implicit, - KExpr::sort(u), - canonical_all( - BinderInfo::Default, - canonical_var(0), - canonical_all( - BinderInfo::Default, - canonical_var(1), - KExpr::sort(KUniv::zero()), - ), - ), - ) -} - -/// Exact semantic type required of the `Eq.refl` prerequisite used by -/// `Environment.addQuot`. -fn canonical_eq_refl_type(prims: &Primitives) -> KExpr { - let u = KUniv::param(0, M::meta_field(Name::anon())); - let result = canonical_apps( - canonical_const(&prims.eq, std::slice::from_ref(&u)), - &[canonical_var(1), canonical_var(0), canonical_var(0)], - ); - canonical_all( - BinderInfo::Implicit, - KExpr::sort(u), - canonical_all(BinderInfo::Default, canonical_var(0), result), - ) -} - -/// Canonical type installed by Lean's `Environment.addQuot` for each -/// reserved quotient primitive. Binder names/info are metadata in `KExpr`; -/// the returned de Bruijn structure, universes, primitive heads, and domains -/// are the semantic contract. -fn canonical_quot_type( - prims: &Primitives, - kind: QuotKind, -) -> KExpr { - let u = KUniv::param(0, M::meta_field(Name::anon())); - let v = KUniv::param(1, M::meta_field(Name::anon())); - let sort_u = KExpr::sort(u.clone()); - let prop = KExpr::sort(KUniv::zero()); - - match kind { - // Quot.{u} {α : Sort u} (r : α → α → Prop) : Sort u - QuotKind::Type => canonical_all( - BinderInfo::Implicit, - sort_u.clone(), - canonical_all(BinderInfo::Default, canonical_quot_relation(), sort_u), - ), - - // Quot.mk.{u} {α : Sort u} (r : α → α → Prop) (a : α) : Quot α r - QuotKind::Ctor => { - let result = canonical_apps( - canonical_const(&prims.quot_type, std::slice::from_ref(&u)), - &[canonical_var(2), canonical_var(1)], - ); - canonical_all( - BinderInfo::Implicit, - sort_u, - canonical_all( - BinderInfo::Default, - canonical_quot_relation(), - canonical_all(BinderInfo::Default, canonical_var(1), result), - ), - ) - }, - - // Quot.lift.{u,v} {α} {r} {β} (f) (h) (q) : β - QuotKind::Lift => { - let f_ty = canonical_arrow(canonical_var(2), canonical_var(1)); - let rab = - canonical_apps(canonical_var(4), &[canonical_var(1), canonical_var(0)]); - let fa = KExpr::app(canonical_var(3), canonical_var(2)); - let fb = KExpr::app(canonical_var(3), canonical_var(1)); - let fa_eq_fb = canonical_apps( - canonical_const(&prims.eq, std::slice::from_ref(&v)), - &[canonical_var(4), fa, fb], - ); - let h_ty = canonical_all( - BinderInfo::Default, - canonical_var(3), - canonical_all( - BinderInfo::Default, - canonical_var(4), - canonical_arrow(rab, fa_eq_fb), - ), - ); - let quot_r = canonical_apps( - canonical_const(&prims.quot_type, std::slice::from_ref(&u)), - &[canonical_var(4), canonical_var(3)], - ); - canonical_all( - BinderInfo::Implicit, - sort_u, - canonical_all( - BinderInfo::Implicit, - canonical_quot_relation(), - canonical_all( - BinderInfo::Implicit, - KExpr::sort(v), - canonical_all( - BinderInfo::Default, - f_ty, - canonical_all( - BinderInfo::Default, - h_ty, - canonical_arrow(quot_r, canonical_var(3)), - ), - ), - ), - ), - ) - }, - - // Quot.ind.{u} {α} {r} {β : Quot α r → Prop} (mk) {q} : β q - QuotKind::Ind => { - let quot_r_d2 = canonical_apps( - canonical_const(&prims.quot_type, std::slice::from_ref(&u)), - &[canonical_var(1), canonical_var(0)], - ); - let beta_ty = canonical_arrow(quot_r_d2, prop); - let quot_mk_a = canonical_apps( - canonical_const(&prims.quot_ctor, std::slice::from_ref(&u)), - &[canonical_var(3), canonical_var(2), canonical_var(0)], - ); - let mk_minor = canonical_all( - BinderInfo::Default, - canonical_var(2), - KExpr::app(canonical_var(1), quot_mk_a), - ); - let quot_r_d4 = canonical_apps( - canonical_const(&prims.quot_type, std::slice::from_ref(&u)), - &[canonical_var(3), canonical_var(2)], - ); - let result = KExpr::app(canonical_var(2), canonical_var(0)); - canonical_all( - BinderInfo::Implicit, - sort_u, - canonical_all( - BinderInfo::Implicit, - canonical_quot_relation(), - canonical_all( - BinderInfo::Implicit, - beta_ty, - canonical_all( - BinderInfo::Default, - mk_minor, - canonical_all(BinderInfo::Implicit, quot_r_d4, result), - ), - ), - ), - ) - }, - } -} - -impl TypeChecker<'_, M> { - /// Return the whole-block check key for a constant when its block has a - /// supported homogeneous shape. This is used by batch schedulers to avoid - /// assigning multiple workers to members of the same block. - pub fn coordinated_check_block_for_const( - &mut self, - id: &KId, - ) -> Result>, TcError> { - let Some(c) = self.try_get_const(id)? else { - return Ok(None); - }; - self.coordinated_block_for(&c) - } - - /// Type-check a single constant. Clears per-constant caches first. - pub fn check_const(&mut self, id: &KId) -> Result<(), TcError> - where - M::MField>: CheckDupLevelParams, - { - let c = self.get_const(id)?; - if let Some(block) = self.coordinated_block_for(&c)? { - if let Some(result) = self.env.block_check_results.get(&block).cloned() { - return result; - } - let result = self.check_block_body(&block, id); - self.env.block_check_results.insert(block, result.clone()); - return result; - } - - self.check_const_member_fresh(id) - } - - /// Check the complete serialized unit represented by an Ixon target. - /// Whole-unit success is cached only after every family phase succeeds. - /// Direct Lean source callers retain `check_const`'s member fallback. - pub fn check_ixon_const(&mut self, id: &KId) -> Result<(), TcError> - where - M::MField>: CheckDupLevelParams, - { - let c = self.get_const(id)?; - let block = match c { - KConst::Defn { block, .. } - | KConst::Indc { block, .. } - | KConst::Recr { block, .. } => Some(block), - KConst::Ctor { induct, .. } => match self.get_const(&induct)? { - KConst::Indc { block, .. } => Some(block), - _ => None, - }, - KConst::Axio { .. } | KConst::Quot { .. } => None, - }; - if let Some(block) = block - && self.try_get_block(&block)?.is_some() - { - if let Some(result) = self.env.block_check_results.get(&block).cloned() { - return result; - } - let result = self.check_block_body(&block, id); - self.env.block_check_results.insert(block, result.clone()); - return result; - } - self.check_const_member_fresh(id) - } - - fn check_const_member_fresh(&mut self, id: &KId) -> Result<(), TcError> - where - M::MField>: CheckDupLevelParams, - { - self.reset(); - self.begin_const(id); - - let c = self.get_const(id)?; - self.check_const_member(id, &c) - } - - fn check_const_member( - &mut self, - id: &KId, - c: &KConst, - ) -> Result<(), TcError> - where - M::MField>: CheckDupLevelParams, - { - // `Lean.trustCompiler : True` mentions no native-evaluation constant, so - // the reference check in `infer` cannot reach it: decline these - // declarations by their own address. - if is_native_evaluation_addr(&id.addr) { - return Err(TcError::NativeEvaluation(id.addr.clone())); - } - - let phase_timing = *IX_PHASE_TIMING; - let overall = if phase_timing { Some(Instant::now()) } else { None }; - - let dup_start = overall.map(|_| Instant::now()); - if c.level_params().has_duplicate_level_params() { - return Err(TcError::Other("duplicate universe level parameter".into())); - } - let dup_elapsed = dup_start.map(|s| s.elapsed()); - - let mut validation_timing = ValidationTiming::default(); - let validate_start = overall.map(|_| Instant::now()); - if phase_timing { - self.validate_const_well_scoped_timed(c, Some(&mut validation_timing))?; - } else { - self.validate_const_well_scoped(c)?; - } - let validate_elapsed = validate_start.map(|s| s.elapsed()); - - match &c { - KConst::Axio { ty, is_unsafe, .. } => { - // Under the safety tier a safe axiom, like every safe declaration, - // may not mention a partial or unsafe constant. - if !is_unsafe && self.prims.follows_safety_tier() { - self.check_no_unsafe_refs(ty, DefinitionSafety::Safe)?; - } - if !is_unsafe && !self.prims.axiom_declarable(&id.addr) { - return Err(TcError::Other(format!( - "axiom {} is not a standard axiom of the checked theory", - id.addr.hex() - ))); - } - let t = self.infer(ty)?; - self.ensure_sort(&t)?; - Ok(()) - }, - - KConst::Defn { ty, val, safety, kind, .. } => { - let t_infer_ty_start = overall.map(|_| Instant::now()); - let t = self.infer(ty)?; - let lvl = self.ensure_sort(&t)?; - let infer_ty_elapsed = t_infer_ty_start.map(|s| s.elapsed()); - - // Theorems must have types in Prop (Sort 0) - if *kind == DefKind::Theorem && !univ_eq(&lvl, &KUniv::zero()) { - return Err(TcError::Other( - "theorem type must be a proposition (Sort 0)".into(), - )); - } - - let t_infer_val_start = overall.map(|_| Instant::now()); - let val_ty = self.infer(val)?; - let infer_val_elapsed = t_infer_val_start.map(|s| s.elapsed()); - - let t_def_eq_start = overall.map(|_| Instant::now()); - let def_eq_ok = self.is_def_eq(&val_ty, ty)?; - let def_eq_elapsed = t_def_eq_start.map(|s| s.elapsed()); - - if !def_eq_ok { - if *IX_DECL_DIFF && self.debug_label_matches_env() { - // Post-whnf forms on both sides so we can see where - // reduction terminates and hence which reduction rule - // (delta, iota, native, ...) is missing for convergence. - let val_ty_whnf = self.whnf(&val_ty); - let ty_whnf = self.whnf(ty); - log::info!("[decl diff] DeclTypeMismatch"); - log::info!(" val_ty: {val_ty}"); - log::info!(" ty: {ty}"); - match &val_ty_whnf { - Ok(w) => log::info!(" val_ty whnf: {w}"), - Err(e) => log::info!(" val_ty whnf: ERR {e}"), - } - match &ty_whnf { - Ok(w) => log::info!(" ty whnf: {w}"), - Err(e) => log::info!(" ty whnf: ERR {e}"), - } - } - return Err(TcError::DeclTypeMismatch); - } - - // #9: Safety level checking — safe/partial defs must not reference unsafe/partial constants - let t_safety_start = overall.map(|_| Instant::now()); - let mut safety_ty_elapsed = None; - let mut safety_val_elapsed = None; - // Under the safety tier an opaque is checked as safe whatever its - // own safety, as Lean's kernel adds opaques. - let fence = - if *kind == DefKind::Opaque && self.prims.follows_safety_tier() { - DefinitionSafety::Safe - } else { - *safety - }; - if fence != DefinitionSafety::Unsafe { - let t_safety_ty_start = overall.map(|_| Instant::now()); - self.check_no_unsafe_refs(ty, fence)?; - safety_ty_elapsed = t_safety_ty_start.map(|s| s.elapsed()); - - let t_safety_val_start = overall.map(|_| Instant::now()); - self.check_no_unsafe_refs(val, fence)?; - safety_val_elapsed = t_safety_val_start.map(|s| s.elapsed()); - } - let safety_elapsed = t_safety_start.map(|s| s.elapsed()); - - if let Some(t0) = overall - && self.phase_timing_label_matches(id) - { - 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(), - dup_elapsed.unwrap_or_default(), - validate_elapsed.unwrap_or_default(), - validation_timing.ty, - validation_timing.val, - validation_timing.rules, - validation_timing.univ, - infer_ty_elapsed.unwrap_or_default(), - infer_val_elapsed.unwrap_or_default(), - def_eq_elapsed.unwrap_or_default(), - safety_elapsed.unwrap_or_default(), - safety_ty_elapsed.unwrap_or_default(), - safety_val_elapsed.unwrap_or_default(), - ); - } - Ok(()) - }, - - KConst::Quot { ty, kind, lvls, .. } => { - // Reject a forged reserved primitive before invoking inference or - // reduction on attacker-controlled syntax. - self.check_quot(id, *kind, *lvls, ty)?; - let t = self.infer(ty)?; - self.ensure_sort(&t)?; - Ok(()) - }, - - KConst::Recr { ty, .. } => { - let t = self.infer(ty)?; - self.ensure_sort(&t)?; - // `check_recursor` runs the full kernel-driven verification: - // coherence (major inductive passes A1–A4, K-target flag matches), - // plus generated-canonical-vs-stored rule comparison via - // `is_def_eq`. How nested auxiliaries are opened is selected by - // `KEnv::recursor_aux_order`, so the generated auxiliaries line up - // with the stored ones in an env produced by `ixon_ingress`. - self.check_recursor_member(id)?; - Ok(()) - }, - - KConst::Indc { ty, .. } => { - let t = self.infer(ty)?; - self.ensure_sort(&t)?; - self.check_inductive_member(id)?; - Ok(()) - }, - - KConst::Ctor { ty, induct, .. } => { - let t = self.infer(ty)?; - self.ensure_sort(&t)?; - // Validate against the parent inductive (A1–A4 checks). - // This ensures standalone ctorInfo is rejected if it doesn't - // match its declared inductive. - let induct = induct.clone(); - self.check_ctor_against_inductive_member(id, &induct)?; - Ok(()) - }, - } - } - - fn coordinated_block_for( - &mut self, - c: &KConst, - ) -> Result>, TcError> { - match c { - KConst::Defn { block, .. } => { - self.coordinated_block_if_kind(block, CheckBlockKind::Defn) - }, - KConst::Indc { block, .. } => { - self.coordinated_block_if_kind(block, CheckBlockKind::Inductive) - }, - KConst::Ctor { induct, .. } => { - let Some(parent) = self.try_get_const(induct)? else { - return Ok(None); - }; - match parent { - KConst::Indc { block, .. } => { - self.coordinated_block_if_kind(&block, CheckBlockKind::Inductive) - }, - _ => Ok(None), - } - }, - KConst::Recr { block, .. } => { - self.coordinated_block_if_kind(block, CheckBlockKind::Recursor) - }, - KConst::Axio { .. } | KConst::Quot { .. } => Ok(None), - } - } - - fn coordinated_block_if_kind( - &mut self, - block: &KId, - expected: CheckBlockKind, - ) -> Result>, TcError> { - let Some(members) = self.try_get_block(block)? else { - return Ok(None); - }; - match self.classify_block(&members) { - Ok(kind) if kind == expected => Ok(Some(block.clone())), - Ok(_) | Err(_) => Ok(None), - } - } - - fn classify_block( - &mut self, - members: &[KId], - ) -> Result> { - if members.is_empty() { - return Err(TcError::Other("empty check block".into())); - } - - let mut saw_defn = false; - let mut saw_recr = false; - let mut saw_inductive_like = false; - for member in members { - match self.get_const(member)? { - KConst::Defn { .. } => saw_defn = true, - KConst::Recr { .. } => saw_recr = true, - KConst::Indc { .. } | KConst::Ctor { .. } => { - saw_inductive_like = true; - }, - KConst::Axio { .. } | KConst::Quot { .. } => { - return Err(TcError::Other(format!( - "unsupported check block {member}: axiom/quotient member" - ))); - }, - } - } - - match (saw_defn, saw_inductive_like, saw_recr) { - (true, false, false) => Ok(CheckBlockKind::Defn), - (false, true, false) => Ok(CheckBlockKind::Inductive), - (false, false, true) => Ok(CheckBlockKind::Recursor), - (false, true, true) => Ok(CheckBlockKind::Family), - _ => Err(TcError::Other( - "unsupported mixed check block: definitions cannot mix with an inductive/recursor family" - .into(), - )), - } - } - - fn check_block_body( - &mut self, - block: &KId, - requested: &KId, - ) -> Result<(), TcError> - where - M::MField>: CheckDupLevelParams, - { - let phase_timing = *IX_PHASE_TIMING; - let overall = if phase_timing { Some(Instant::now()) } else { None }; - - let get_members_start = overall.map(|_| Instant::now()); - let members = - self.try_get_block(block)?.unwrap_or_else(|| vec![requested.clone()]); - let get_members_elapsed = get_members_start.map(|s| s.elapsed()); - - let classify_start = overall.map(|_| Instant::now()); - let kind = self.classify_block(&members)?; - let classify_elapsed = classify_start.map(|s| s.elapsed()); - - let mut validation_timing = ValidationTiming::default(); - let prevalidate_start = overall.map(|_| Instant::now()); - if kind != CheckBlockKind::Defn { - for member in &members { - let c = self.get_const(member)?; - if c.level_params().has_duplicate_level_params() { - return Err(TcError::Other( - "duplicate universe level parameter".into(), - )); - } - if phase_timing { - self.validate_const_well_scoped_timed( - &c, - Some(&mut validation_timing), - )?; - } else { - self.validate_const_well_scoped(&c)?; - } - } - } - let prevalidate_elapsed = prevalidate_start.map(|s| s.elapsed()); - - let body_start = overall.map(|_| Instant::now()); - let result = match kind { - CheckBlockKind::Defn => { - let mut peak = 0; - for member in &members { - self.check_const_member_fresh(member)?; - peak = peak.max(self.def_eq_peak); - } - self.def_eq_peak = peak; - Ok(()) - }, - CheckBlockKind::Inductive => self.check_inductive_block(block, &members), - CheckBlockKind::Recursor => self.check_recursor_block(block, &members), - CheckBlockKind::Family => { - let mut inductives = Vec::new(); - let mut recursors = Vec::new(); - for member in &members { - match self.get_const(member)? { - KConst::Indc { .. } | KConst::Ctor { .. } => { - inductives.push(member.clone()) - }, - KConst::Recr { .. } => recursors.push(member.clone()), - _ => { - return Err(TcError::Other( - "invalid serialized family member".into(), - )); - }, - } - } - self.check_inductive_block(block, &inductives)?; - self.check_recursor_block(block, &recursors) - }, - }; - let body_elapsed = body_start.map(|s| s.elapsed()); - - if let Some(t0) = overall - && self.phase_timing_label_matches(block) - { - 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, - members.len(), - t0.elapsed(), - get_members_elapsed.unwrap_or_default(), - prevalidate_elapsed.unwrap_or_default(), - validation_timing.ty, - validation_timing.val, - validation_timing.rules, - validation_timing.univ, - classify_elapsed.unwrap_or_default(), - body_elapsed.unwrap_or_default(), - ); - } - - result - } - - // ----------------------------------------------------------------------- - // #5: Quotient type validation - // ----------------------------------------------------------------------- - - /// Validate declaration expressions before inference. - /// - /// This is the Ix equivalent of Lean's declaration-admission closure and - /// universe-param checks: declarations must be closed at the top level, and - /// every `Param(idx)` in their type/value/rules must refer to one of the - /// declaration's own universe parameters. - pub(crate) fn validate_const_well_scoped( - &mut self, - c: &KConst, - ) -> Result<(), TcError> { - self.validate_const_well_scoped_timed(c, None) - } - - fn validate_const_well_scoped_timed( - &mut self, - c: &KConst, - mut timing: Option<&mut ValidationTiming>, - ) -> Result<(), TcError> { - let lvl_bound = u64_to_usize::(c.lvls())?; - let ty_start = timing.as_ref().map(|_| Instant::now()); - self.validate_expr_well_scoped( - c.ty(), - 0, - lvl_bound, - timing.as_deref_mut(), - )?; - if let (Some(t), Some(start)) = (timing.as_deref_mut(), ty_start) { - t.ty += start.elapsed(); - } - match c { - KConst::Defn { val, .. } => { - let val_start = timing.as_ref().map(|_| Instant::now()); - self.validate_expr_well_scoped( - val, - 0, - lvl_bound, - timing.as_deref_mut(), - )?; - if let (Some(t), Some(start)) = (timing.as_deref_mut(), val_start) { - t.val += start.elapsed(); - } - self.check_definition_dependencies(c)?; - }, - KConst::Recr { rules, .. } => { - let rules_start = timing.as_ref().map(|_| Instant::now()); - for rule in rules { - self.validate_expr_well_scoped( - &rule.rhs, - 0, - lvl_bound, - timing.as_deref_mut(), - )?; - } - if let (Some(t), Some(start)) = (timing, rules_start) { - t.rules += start.elapsed(); - } - }, - KConst::Axio { .. } - | KConst::Quot { .. } - | KConst::Indc { .. } - | KConst::Ctor { .. } => {}, - } - Ok(()) - } - - /// A reference's declared type does not justify a circular definition. - /// Follow every reachable definition, while treating declarations with their - /// own admission rules (axioms, inductives, constructors, recursors) as leaves. - fn check_definition_dependencies( - &mut self, - c: &KConst, - ) -> Result<(), TcError> { - // Ixon ingress admits all local Rec edges before publishing a block. - // External references are content addressed and cannot form back edges. - // The full traversal remains necessary for arbitrary, mutable raw KEnvs. - if self.ixon_ingress { - return Ok(()); - } - if !matches!(c, KConst::Defn { safety: DefinitionSafety::Safe, .. }) { - return Ok(()); - } - let mut pending: Vec<_> = definition_dependencies(c) - .into_iter() - .rev() - .map(DefinitionDependencyTask::Enter) - .collect(); - let mut active: FxHashSet
= FxHashSet::default(); - let mut finished: FxHashSet
= FxHashSet::default(); - for _ in 0..MAX_DEFINITION_DEPENDENCY_STEPS { - let Some(task) = pending.pop() else { return Ok(()) }; - match task { - DefinitionDependencyTask::Enter(id) => { - if finished.contains(&id.addr) { - continue; - } - if active.contains(&id.addr) { - return Err(TcError::Other(format!( - "cyclic definition dependency at {id}" - ))); - } - let declaration = self.get_const(&id)?; - let dependencies = definition_dependencies(&declaration); - active.insert(id.addr.clone()); - pending.push(DefinitionDependencyTask::Finish(id, declaration)); - pending.extend( - dependencies.into_iter().rev().map(DefinitionDependencyTask::Enter), - ); - }, - DefinitionDependencyTask::Finish(id, declaration) => { - if finished.contains(&id.addr) - || !definition_dependencies(&declaration) - .iter() - .all(|dependency| finished.contains(&dependency.addr)) - { - return Err(TcError::Other(format!( - "incomplete definition dependencies at {id}" - ))); - } - active.remove(&id.addr); - finished.insert(id.addr); - }, - } - } - Err(TcError::MaxRecDepth) - } - - fn phase_timing_label_matches(&self, id: &KId) -> bool { - match crate::env_var("IX_KERNEL_DEBUG_CONST") { - Ok(filter) if filter.is_empty() => true, - Ok(filter) => { - id.to_string().contains(&filter) - || self - .debug_label - .as_ref() - .is_some_and(|label| label.contains(&filter)) - }, - Err(_) => true, - } - } - - fn validate_expr_well_scoped( - &mut self, - root: &KExpr, - root_depth: u64, - lvl_bound: usize, - mut timing: Option<&mut ValidationTiming>, - ) -> Result<(), TcError> { - let mut stack: Vec<(&KExpr, u64)> = vec![(root, root_depth)]; - let mut seen_exprs: FxHashSet<(Addr, u64)> = FxHashSet::default(); - let mut seen_univs: FxHashSet = FxHashSet::default(); - while let Some((e, depth)) = stack.pop() { - if !seen_exprs.insert((e.hash_key(), depth)) { - continue; - } - match e.data() { - ExprData::Var(idx, _, _) => { - if *idx >= depth { - let ctx_len = usize::try_from(depth).unwrap_or(usize::MAX); - return Err(TcError::VarOutOfRange { idx: *idx, ctx_len }); - } - }, - ExprData::Sort(u, _) => { - let univ_start = timing.as_ref().map(|_| Instant::now()); - self.validate_univ_params_seen(u, lvl_bound, &mut seen_univs)?; - if let (Some(t), Some(start)) = (timing.as_deref_mut(), univ_start) { - t.univ += start.elapsed(); - } - }, - ExprData::Const(id, us, _) => { - let c = self.get_const(id)?; - if u64_to_usize::(c.lvls())? != us.len() { - return Err(TcError::UnivParamMismatch { - expected: c.lvls(), - got: us.len(), - }); - } - for u in us { - let univ_start = timing.as_ref().map(|_| Instant::now()); - self.validate_univ_params_seen(u, lvl_bound, &mut seen_univs)?; - if let (Some(t), Some(start)) = (timing.as_deref_mut(), univ_start) - { - t.univ += start.elapsed(); - } - } - }, - ExprData::App(f, a, _) => { - stack.push((f, depth)); - stack.push((a, depth)); - }, - ExprData::Lam(_, _, _, ty, body, _) - | ExprData::All(_, _, _, _, ty, body, _) => { - stack.push((ty, depth)); - let body_depth = depth.checked_add(1).ok_or_else(|| { - TcError::Other("binder depth overflow during validation".into()) - })?; - stack.push((body, body_depth)); - }, - ExprData::Let(_, ty, val, body, _, _) => { - stack.push((ty, depth)); - stack.push((val, depth)); - let body_depth = depth.checked_add(1).ok_or_else(|| { - TcError::Other("binder depth overflow during validation".into()) - })?; - stack.push((body, body_depth)); - }, - ExprData::Prj(id, _, val, _) => { - if !self.has_const(id)? { - return Err(TcError::UnknownConst(id.addr.clone())); - } - stack.push((val, depth)); - }, - // FVars carry no de Bruijn index, so the depth check does not apply. - // They are leaves with no further children to traverse. - ExprData::FVar(..) | ExprData::Nat(..) | ExprData::Str(..) => {}, - } - } - Ok(()) - } - - fn validate_univ_params_seen( - &self, - root: &KUniv, - bound: usize, - seen: &mut FxHashSet, - ) -> Result<(), TcError> { - let mut stack = vec![root]; - while let Some(u) = stack.pop() { - if !seen.insert(*u.addr()) { - continue; - } - match u.data() { - UnivData::Zero(_) => {}, - UnivData::Succ(inner, _) => stack.push(inner), - UnivData::Max(a, b, _) | UnivData::IMax(a, b, _) => { - stack.push(a); - stack.push(b); - }, - UnivData::Param(idx, _, _) => { - if u64_to_usize::(*idx)? >= bound { - return Err(TcError::UnivParamOutOfRange { idx: *idx, bound }); - } - }, - } - } - Ok(()) - } - - /// Validate quotient constant structure. - /// - /// Checks: - /// - Correct address matches the expected QuotKind - /// - Correct universe parameter count per variant - /// - The complete type is the canonical type installed by `addQuot` - /// - Eq and Eq.refl have exact canonical metadata/types for Quot.lift - fn check_quot( - &mut self, - id: &KId, - kind: QuotKind, - lvls: u64, - ty: &KExpr, - ) -> Result<(), TcError> { - // Validate address ↔ kind consistency - let expected_kind = if id.addr == self.primitives()?.quot_type.addr { - QuotKind::Type - } else if id.addr == self.primitives()?.quot_ctor.addr { - QuotKind::Ctor - } else if id.addr == self.primitives()?.quot_lift.addr { - QuotKind::Lift - } else if id.addr == self.primitives()?.quot_ind.addr { - QuotKind::Ind - } else { - return Err(TcError::Other(format!( - "check_quot: unknown quot address {}", - &id.addr.hex()[..8] - ))); - }; - - if kind != expected_kind { - return Err(TcError::Other(format!( - "check_quot: kind mismatch: declared {:?} but address matches {:?}", - kind, expected_kind - ))); - } - - // Validate universe parameter count per variant - // Quot: 1 (u), Quot.mk: 1 (u), Quot.lift: 2 (u,v), Quot.ind: 1 (u) - let expected_lvls = match kind { - QuotKind::Lift => 2, - QuotKind::Type | QuotKind::Ctor | QuotKind::Ind => 1, - }; - if lvls != expected_lvls { - return Err(TcError::Other(format!( - "check_quot: {:?} expects {} universe params, got {}", - kind, expected_lvls, lvls - ))); - } - - let expected_ty = canonical_quot_type(self.primitives()?, kind); - if ty != &expected_ty { - return Err(TcError::Other(format!( - "check_quot: {:?} type is not canonical", - kind - ))); - } - - // For Quot.lift (the main eliminator), verify Eq is properly formed. - // This is a prerequisite for the quot reduction rule to be sound. - if kind == QuotKind::Lift { - self.check_eq_type()?; - } - - Ok(()) - } - - /// Verify the exact Eq/Eq.refl prerequisite checked by Lean before it - /// installs the quotient primitives. - fn check_eq_type(&self) -> Result<(), TcError> { - let primitives = self.primitives()?; - // Find Eq inductive in the environment by address. - // Search all constants for one matching the Eq address. - let eq_const = self - .env - .iter() - .find(|(id, _)| id.addr == primitives.eq.addr) - .map(|(id, c)| (id.clone(), c.clone())); - let (_eq_id, eq_c) = eq_const.ok_or_else(|| { - TcError::Other("check_eq_type: Eq not found in environment".into()) - })?; - match &eq_c { - KConst::Indc { lvls, params, indices, is_unsafe, ty, ctors, .. } => { - if *lvls != 1 { - return Err(TcError::Other(format!( - "check_eq_type: Eq expects 1 universe param, got {}", - lvls - ))); - } - // Eq : {α : Sort u} → α → α → Prop - // numParams = 2 (α, a are uniform across Eq.refl), numIndices = 1 (b) - if *params != 2 { - return Err(TcError::Other(format!( - "check_eq_type: Eq expects 2 params (α, a), got {}", - params - ))); - } - if *indices != 1 { - return Err(TcError::Other(format!( - "check_eq_type: Eq expects 1 index, got {}", - indices - ))); - } - if *is_unsafe { - return Err(TcError::Other("check_eq_type: Eq must be safe".into())); - } - if ctors.len() != 1 { - return Err(TcError::Other(format!( - "check_eq_type: Eq expects 1 constructor, got {}", - ctors.len() - ))); - } - // Verify the constructor is Eq.refl - if ctors[0].addr != self.primitives()?.eq_refl.addr { - return Err(TcError::Other( - "check_eq_type: Eq's constructor is not Eq.refl".into(), - )); - } - if ty != &canonical_eq_type() { - return Err(TcError::Other( - "check_eq_type: Eq type is not canonical".into(), - )); - } - }, - _ => { - return Err(TcError::Other( - "check_eq_type: Eq not found or not inductive".into(), - )); - }, - } - - let refl_c = self - .env - .iter() - .find(|(id, _)| id.addr == primitives.eq_refl.addr) - .map(|(_, c)| c.clone()) - .ok_or_else(|| { - TcError::Other("check_eq_type: Eq.refl not found".into()) - })?; - match refl_c { - KConst::Ctor { - is_unsafe, - lvls, - induct, - cidx, - params, - fields, - ty, - .. - } => { - if is_unsafe - || lvls != 1 - || induct.addr != self.primitives()?.eq.addr - || cidx != 0 - || params != 2 - || fields != 0 - { - return Err(TcError::Other( - "check_eq_type: Eq.refl metadata is not canonical".into(), - )); - } - if ty != canonical_eq_refl_type(self.primitives()?) { - return Err(TcError::Other( - "check_eq_type: Eq.refl type is not canonical".into(), - )); - } - }, - _ => { - return Err(TcError::Other( - "check_eq_type: Eq.refl not found or not a constructor".into(), - )); - }, - } - Ok(()) - } - - // ----------------------------------------------------------------------- - // #9: Safety level checking - // ----------------------------------------------------------------------- - - /// Verify that an expression does not reference constants with weaker safety. - /// `caller_safety` is the safety level of the definition being checked. - /// - Safe defs cannot reference unsafe or partial constants - /// - Partial defs cannot reference unsafe constants - pub(crate) fn check_no_unsafe_refs( - &mut self, - e: &KExpr, - caller_safety: DefinitionSafety, - ) -> Result<(), TcError> { - self.walk_for_unsafe(e, caller_safety) - } - - /// Iterative (stack-based) walk — immune to stack overflow on deeply nested input. - fn walk_for_unsafe( - &mut self, - root: &KExpr, - caller_safety: DefinitionSafety, - ) -> Result<(), TcError> { - let mut stack: Vec<&KExpr> = vec![root]; - let mut seen_exprs: FxHashSet = FxHashSet::default(); - let mut seen_consts: FxHashSet
= FxHashSet::default(); - while let Some(e) = stack.pop() { - if !seen_exprs.insert(e.hash_key()) { - continue; - } - match e.data() { - ExprData::Var(..) - | ExprData::FVar(..) - | ExprData::Sort(..) - | ExprData::Nat(..) - | ExprData::Str(..) => {}, - ExprData::Const(id, _, _) => { - if !seen_consts.insert(id.addr.clone()) { - continue; - } - match self.try_get_const(id)? { - Some(KConst::Axio { is_unsafe: true, .. }) => { - return Err(TcError::Other(format!( - "safe definition references unsafe axiom {}", - &id.addr.hex()[..8] - ))); - }, - Some(KConst::Defn { safety: DefinitionSafety::Unsafe, .. }) => { - return Err(TcError::Other(format!( - "safe definition references unsafe definition {}", - &id.addr.hex()[..8] - ))); - }, - Some(KConst::Defn { - safety: DefinitionSafety::Partial, .. - }) if caller_safety == DefinitionSafety::Safe => { - return Err(TcError::Other(format!( - "safe definition references partial definition {}", - &id.addr.hex()[..8] - ))); - }, - Some(KConst::Recr { is_unsafe: true, .. }) => { - return Err(TcError::Other(format!( - "safe definition references unsafe recursor {}", - &id.addr.hex()[..8] - ))); - }, - Some(KConst::Indc { is_unsafe: true, .. }) => { - return Err(TcError::Other(format!( - "safe definition references unsafe inductive {}", - &id.addr.hex()[..8] - ))); - }, - Some(KConst::Ctor { is_unsafe: true, .. }) => { - return Err(TcError::Other(format!( - "safe definition references unsafe constructor {}", - &id.addr.hex()[..8] - ))); - }, - _ => {}, - } - }, - ExprData::App(f, a, _) => { - stack.push(f); - stack.push(a); - }, - ExprData::Lam(_, _, _, ty, body, _) - | ExprData::All(_, _, _, _, ty, body, _) => { - stack.push(ty); - stack.push(body); - }, - ExprData::Let(_, ty, val, body, _, _) => { - stack.push(ty); - stack.push(val); - stack.push(body); - }, - ExprData::Prj(id, _, val, _) => { - if seen_consts.insert(id.addr.clone()) - && let Some(KConst::Indc { is_unsafe: true, .. }) = - self.try_get_const(id)? - { - return Err(TcError::Other(format!( - "safe definition references unsafe inductive {}", - &id.addr.hex()[..8] - ))); - } - stack.push(val); - }, - } - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - - use super::super::constant::KConst; - use super::super::env::KEnv; - use super::super::error::TcError; - use super::super::expr::KExpr; - use super::super::id::KId; - use super::super::level::KUniv; - use super::super::mode::Anon; - use super::super::primitive::Primitives; - use super::super::tc::TypeChecker; - use ix_common::address::Address; - use ix_common::env::{DefinitionSafety, QuotKind, ReducibilityHints}; - use ixon::constant::DefKind; - - /// The certified checker bounds its untyped inferer's fuel so that a - /// non-terminating head β in a declared type declines. Inference here is - /// typed, so the same type, `(fun x : Prop => x x) (fun x : Prop => x x)`, - /// declines as ill-typed rather than reducing. - #[test] - fn non_terminating_head_beta_in_a_declared_type_declines() { - use crate::testing as t; - let omega = || t::lam(t::sort0(), t::app(t::var(0), t::var(0))); - let (id, axiom) = t::mk_axiom("loops", 0, vec![], t::app(omega(), omega())); - let mut env = KEnv::new(); - env.insert(id.clone(), axiom); - t::check_rejects(&mut env, &id); - } - - #[test] - fn profile_sink_records_delta_edge_and_fuel() { - use crate::mode::Meta; - use crate::profile::ProfileSink; - use crate::testing as t; - - // g : Sort 2 := Sort 1 — a Definition, delta-reducible to Sort 1. - let (g_id, g) = t::mk_defn( - "g", - 0, - vec![], - t::sort(t::usucc(t::usucc(t::uzero()))), - t::sort1(), - ReducibilityHints::Regular(5), - ); - // f : g := Sort 0 — checking f must whnf (delta-unfold) g → Sort 1 to match - // infer(Sort 0) = Sort 1, so the recorder must capture the edge f→g. - let (f_id, f) = t::mk_defn( - "f", - 0, - vec![], - t::cnst("g", &[]), - t::sort0(), - ReducibilityHints::Regular(5), - ); - - let mut env = KEnv::::new(); - env.insert(g_id.clone(), g); - env.insert(f_id.clone(), f); - env.profile_sink = Some(ProfileSink::new(true)); - - { - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&g_id).unwrap(); - tc.check_const(&f_id).unwrap(); - tc.finish_constant_accounting(); // flush the last constant's record - } - - let sink = env.profile_sink.as_ref().unwrap(); - let f_rec = sink.records.get(&f_id.addr).expect("f should be recorded"); - assert!( - f_rec.producers.contains(&g_id.addr), - "checking f must record a delta-unfold of g" - ); - assert!(f_rec.fuel > 0, "checking f consumes heartbeats"); - // g unfolds nothing of its own. - if let Some(g_rec) = sink.records.get(&g_id.addr) { - assert!(!g_rec.producers.contains(&g_id.addr)); - } - } - - type AE = KExpr; - type AU = KUniv; - - fn mk_addr(s: &str) -> Address { - Address::hash(s.as_bytes()) - } - fn mk_id(s: &str) -> KId { - KId::new(mk_addr(s), ()) - } - fn sort0() -> AE { - AE::sort(AU::zero()) - } - fn sort1() -> AE { - AE::sort(AU::succ(AU::zero())) - } - - fn canonical_quot_env() -> (KEnv, Primitives) { - let mut env = KEnv::::new(); - let prims = Primitives::from_env(&env); - - env.insert( - prims.eq.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 1, - params: 2, - indices: 1, - is_unsafe: false, - block: prims.eq.clone(), - member_idx: 0, - ty: super::canonical_eq_type(), - ctors: vec![prims.eq_refl.clone()], - lean_all: (), - }, - ); - env.insert( - prims.eq_refl.clone(), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: prims.eq.clone(), - cidx: 0, - params: 2, - fields: 0, - ty: super::canonical_eq_refl_type(&prims), - }, - ); - - for (id, kind, lvls) in [ - (prims.quot_type.clone(), QuotKind::Type, 1), - (prims.quot_ctor.clone(), QuotKind::Ctor, 1), - (prims.quot_lift.clone(), QuotKind::Lift, 2), - (prims.quot_ind.clone(), QuotKind::Ind, 1), - ] { - env.insert( - id, - KConst::Quot { - name: (), - level_params: (), - kind, - lvls, - ty: super::canonical_quot_type(&prims, kind), - }, - ); - } - (env, prims) - } - - fn replace_quot_type(env: &mut KEnv, id: &KId, ty: AE) { - let KConst::Quot { kind, lvls, .. } = env.get(id).expect("quot fixture") - else { - panic!("expected quotient fixture") - }; - env.insert( - id.clone(), - KConst::Quot { name: (), level_params: (), kind, lvls, ty }, - ); - } - - fn replace_quot_metadata( - env: &mut KEnv, - id: &KId, - kind: QuotKind, - lvls: u64, - ) { - let KConst::Quot { ty, .. } = env.get(id).expect("quot fixture") else { - panic!("expected quotient fixture") - }; - env.insert( - id.clone(), - KConst::Quot { name: (), level_params: (), kind, lvls, ty }, - ); - } - - fn replace_eq_type(env: &mut KEnv, prims: &Primitives, ty: AE) { - env.insert( - prims.eq.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 1, - params: 2, - indices: 1, - is_unsafe: false, - block: prims.eq.clone(), - member_idx: 0, - ty, - ctors: vec![prims.eq_refl.clone()], - lean_all: (), - }, - ); - } - - fn replace_eq_refl_type( - env: &mut KEnv, - prims: &Primitives, - ty: AE, - ) { - env.insert( - prims.eq_refl.clone(), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: prims.eq.clone(), - cidx: 0, - params: 2, - fields: 0, - ty, - }, - ); - } - - /// A well-typed type with the old minimum number of leading foralls but no - /// quotient semantics. Every variant below was accepted by the former - /// arity-only gate when installed directly at a reserved primitive KId. - fn forged_forall_type(n: usize) -> AE { - (0..n).fold(sort0(), |body, _| AE::all((), (), sort0(), body)) - } - - fn forged_eq_type() -> AE { - let u = AU::param(0, ()); - AE::all( - (), - (), - AE::sort(u), - AE::all((), (), AE::var(0, ()), AE::all((), (), AE::var(1, ()), sort1())), - ) - } - - fn assert_not_canonical(err: TcError, label: &str) { - match err { - TcError::Other(s) => assert!( - s.contains("not canonical"), - "expected canonicality error for {label}, got {s}" - ), - other => panic!("expected canonicality error for {label}, got {other:?}"), - } - } - - #[test] - fn canonical_quotient_bundle_is_accepted() { - let (mut env, prims) = canonical_quot_env(); - for id in - [prims.quot_type, prims.quot_ctor, prims.quot_lift, prims.quot_ind] - { - TypeChecker::new(&mut env).check_const(&id).unwrap(); - } - } - - #[test] - fn reject_quot_kind_address_mismatch() { - let (mut env, prims) = canonical_quot_env(); - replace_quot_metadata(&mut env, &prims.quot_type, QuotKind::Ctor, 1); - let err = - TypeChecker::new(&mut env).check_const(&prims.quot_type).unwrap_err(); - match err { - TcError::Other(s) => assert!(s.contains("kind mismatch"), "got {s}"), - other => panic!("expected kind mismatch, got {other:?}"), - } - } - - #[test] - fn reject_quot_universe_count_mismatch() { - let (mut env, prims) = canonical_quot_env(); - replace_quot_metadata(&mut env, &prims.quot_lift, QuotKind::Lift, 3); - let err = - TypeChecker::new(&mut env).check_const(&prims.quot_lift).unwrap_err(); - match err { - TcError::Other(s) => { - assert!(s.contains("expects 2 universe params"), "got {s}") - }, - other => panic!("expected universe-count mismatch, got {other:?}"), - } - } - - #[test] - fn reject_forged_quot_type_with_two_foralls() { - let (mut env, prims) = canonical_quot_env(); - replace_quot_type(&mut env, &prims.quot_type, forged_forall_type(2)); - let err = - TypeChecker::new(&mut env).check_const(&prims.quot_type).unwrap_err(); - assert_not_canonical(err, "Quot"); - } - - #[test] - fn reject_forged_quot_mk_type_with_three_foralls() { - let (mut env, prims) = canonical_quot_env(); - replace_quot_type(&mut env, &prims.quot_ctor, forged_forall_type(3)); - let err = - TypeChecker::new(&mut env).check_const(&prims.quot_ctor).unwrap_err(); - assert_not_canonical(err, "Quot.mk"); - } - - #[test] - fn reject_forged_quot_lift_type_with_six_foralls() { - let (mut env, prims) = canonical_quot_env(); - replace_quot_type(&mut env, &prims.quot_lift, forged_forall_type(6)); - let err = - TypeChecker::new(&mut env).check_const(&prims.quot_lift).unwrap_err(); - assert_not_canonical(err, "Quot.lift"); - } - - #[test] - fn reject_forged_quot_ind_type_with_five_foralls() { - let (mut env, prims) = canonical_quot_env(); - replace_quot_type(&mut env, &prims.quot_ind, forged_forall_type(5)); - let err = - TypeChecker::new(&mut env).check_const(&prims.quot_ind).unwrap_err(); - assert_not_canonical(err, "Quot.ind"); - } - - #[test] - fn reject_quot_lift_when_eq_type_is_not_canonical() { - let (mut env, prims) = canonical_quot_env(); - replace_eq_type(&mut env, &prims, forged_eq_type()); - let err = - TypeChecker::new(&mut env).check_const(&prims.quot_lift).unwrap_err(); - assert_not_canonical(err, "Eq"); - } - - #[test] - fn reject_quot_lift_when_eq_refl_type_is_not_canonical() { - let (mut env, prims) = canonical_quot_env(); - replace_eq_refl_type(&mut env, &prims, forged_forall_type(2)); - let err = - TypeChecker::new(&mut env).check_const(&prims.quot_lift).unwrap_err(); - assert_not_canonical(err, "Eq.refl"); - } - - #[test] - fn reject_quot_lift_when_eq_refl_metadata_is_not_canonical() { - let (mut env, prims) = canonical_quot_env(); - env.insert( - prims.eq_refl.clone(), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: prims.eq.clone(), - cidx: 0, - params: 2, - fields: 1, - ty: super::canonical_eq_refl_type(&prims), - }, - ); - let err = - TypeChecker::new(&mut env).check_const(&prims.quot_lift).unwrap_err(); - match err { - TcError::Other(s) => { - assert!(s.contains("Eq.refl metadata is not canonical"), "got {s}") - }, - other => panic!("expected Eq.refl metadata mismatch, got {other:?}"), - } - } - - fn test_env() -> KEnv { - let mut env = KEnv::new(); - // Axiom: Nat : Sort 1 - env.insert( - mk_id("Nat"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: sort1(), - }, - ); - // Definition: id : Sort 0 → Sort 0 := λ x. x - let id_ty = AE::all((), (), sort0(), sort0()); - let id_val = AE::lam((), (), sort0(), AE::var(0, ())); - env.insert( - mk_id("id"), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Abbrev, - lvls: 0, - ty: id_ty, - val: id_val, - lean_all: (), - block: mk_id("id"), - }, - ); - // Bad definition: wrong_id : Sort 0 → Sort 0 := Sort 1 (type mismatch) - let wrong_ty = AE::all((), (), sort0(), sort0()); - let wrong_val = sort1(); // Sort 1, but declared type says Sort 0 → Sort 0 - env.insert( - mk_id("wrong"), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(0), - lvls: 0, - ty: wrong_ty, - val: wrong_val, - lean_all: (), - block: mk_id("wrong"), - }, - ); - env - } - - #[test] - fn check_axiom() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - assert!(tc.check_const(&mk_id("Nat")).is_ok()); - } - - #[test] - fn check_defn_ok() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - assert!(tc.check_const(&mk_id("id")).is_ok()); - } - - #[test] - fn check_defn_mismatch() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - assert!(tc.check_const(&mk_id("wrong")).is_err()); - } - - #[test] - fn check_unknown_const() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - assert!(tc.check_const(&mk_id("nonexistent")).is_err()); - } - - #[test] - fn check_clears_caches() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("Nat")).unwrap(); - // def_eq_depth should be reset - assert_eq!(tc.def_eq_depth, 0); - assert_eq!(tc.def_eq_peak, 0); - } - - #[test] - fn bounded_capacity_reset_matches_release_across_success_and_failure() { - for capacity in [0, 16, 4_096] { - let mut retained = KEnv::::new(); - let mut released = KEnv::::new(); - // Reuse the same fixture names after both successful and failed checks. - // Reset must not turn either a cached success or a cached error into the - // next check's result, including when empty bucket storage survives. - for name in ["id", "wrong", "Nat", "nonexistent", "wrong", "id"] { - retained.clear_with_capacity_limit(capacity); - released.clear_releasing_memory(); - for env in [&mut retained, &mut released] { - for (id, c) in test_env().iter() { - env.insert(id.clone(), c.clone()); - } - } - let run = |env: &mut KEnv| { - let mut tc = TypeChecker::new(env); - let result = tc.check_const(&mk_id(name)).map_err(|e| e.to_string()); - (result, tc.fuel_used()) - }; - let expected = run(&mut released); - assert_eq!(expected.0.is_ok(), matches!(name, "id" | "Nat")); - assert_eq!(run(&mut retained), expected); - } - } - } - - // ========================================================================= - // Theorem must land in Prop - // ========================================================================= - - #[test] - fn check_theorem_with_type_in_prop_ok() { - let mut env = KEnv::::new(); - // Axiom P : Prop. - env.insert( - mk_id("P"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: sort0(), - }, - ); - // Axiom p : P. - env.insert( - mk_id("p"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: AE::cnst(mk_id("P"), Box::new([])), - }, - ); - // Theorem thm : P := p. - env.insert( - mk_id("thm"), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Theorem, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Opaque, - lvls: 0, - ty: AE::cnst(mk_id("P"), Box::new([])), - val: AE::cnst(mk_id("p"), Box::new([])), - lean_all: (), - block: mk_id("thm"), - }, - ); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("thm")).unwrap(); - } - - #[test] - fn check_theorem_with_non_prop_type_rejected() { - let mut env = KEnv::::new(); - // Theorem claiming to inhabit Sort 1 (not Prop) — must be rejected. - env.insert( - mk_id("thm_bad"), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Theorem, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Opaque, - lvls: 0, - ty: sort1(), // Type, not Prop - val: sort0(), - lean_all: (), - block: mk_id("thm_bad"), - }, - ); - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&mk_id("thm_bad")) { - Err(TcError::Other(s)) => { - assert!(s.contains("theorem type must be a proposition")); - }, - other => panic!("expected theorem-must-be-Prop error, got {other:?}"), - } - } - - // ========================================================================= - // Axiom type must be a Sort - // ========================================================================= - - #[test] - fn check_axiom_with_non_sort_type_rejected() { - // Axiom whose declared type is `id` (a definition, not a Sort) → error. - let mut env = test_env(); - // Add an axiom with a bogus type — the type expression is valid, but its - // _inferred type_ (the type of its type) is `Sort 0 → Sort 0`'s type, - // which is a Sort. To actually hit `TypeExpected` we need a type that - // infers to something non-Sort — take a projection into a non-struct. - // Easier: declare a type that's a Var in an empty context (out-of-range). - env.insert( - mk_id("bad_ax"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - // Var(0) in the empty context — infer will return VarOutOfRange. - ty: AE::var(0, ()), - }, - ); - let mut tc = TypeChecker::new(&mut env); - assert!(tc.check_const(&mk_id("bad_ax")).is_err()); - } - - // ========================================================================= - // Duplicate level-param names - // ========================================================================= - - #[test] - fn check_duplicate_level_params_rejected() { - use crate::mode::Meta; - type ME = KExpr; - type MU = KUniv; - - let mut env = KEnv::::new(); - let dup_name = - ix_common::env::Name::str(ix_common::env::Name::anon(), "u".into()); - let id = KId::new(mk_addr("T"), dup_name.clone()); - env.insert( - id.clone(), - KConst::Axio { - name: dup_name.clone(), - level_params: vec![dup_name.clone(), dup_name.clone()], - is_unsafe: false, - lvls: 2, - ty: ME::sort(MU::succ(MU::zero())), - }, - ); - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&id) { - Err(TcError::Other(s)) => { - assert!(s.contains("duplicate universe level parameter")); - }, - other => panic!("expected duplicate-level-param error, got {other:?}"), - } - } - - #[test] - fn check_loose_var_in_decl_rejected_before_infer() { - let mut env = KEnv::::new(); - env.insert( - mk_id("bad_loose"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: AE::all((), (), sort0(), AE::var(1, ())), - }, - ); - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&mk_id("bad_loose")) { - Err(TcError::VarOutOfRange { idx: 1, ctx_len: 1 }) => {}, - other => panic!("expected closure VarOutOfRange, got {other:?}"), - } - } - - #[test] - fn check_out_of_range_universe_param_rejected() { - let mut env = KEnv::::new(); - env.insert( - mk_id("bad_univ"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - ty: AE::sort(AU::param(1, ())), - }, - ); - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&mk_id("bad_univ")) { - Err(TcError::UnivParamOutOfRange { idx: 1, bound: 1 }) => {}, - other => panic!("expected universe-param range error, got {other:?}"), - } - } - - // ========================================================================= - // Caching: check_const is idempotent - // ========================================================================= - - #[test] - fn check_const_idempotent() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("id")).unwrap(); - tc.check_const(&mk_id("id")).unwrap(); - tc.check_const(&mk_id("id")).unwrap(); - } - - #[test] - fn safe_definition_rejects_unsafe_inductive_ref() { - let mut env = KEnv::::new(); - let unsafe_ty = mk_id("UnsafeTy"); - env.insert( - unsafe_ty.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: true, - block: unsafe_ty.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![], - lean_all: (), - }, - ); - - let unsafe_expr = AE::cnst(unsafe_ty, Box::new([])); - env.insert( - mk_id("useUnsafe"), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(0), - lvls: 0, - ty: AE::all((), (), unsafe_expr.clone(), unsafe_expr.clone()), - val: AE::lam((), (), unsafe_expr, AE::var(0, ())), - lean_all: (), - block: mk_id("useUnsafe"), - }, - ); - - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&mk_id("useUnsafe")) { - Err(TcError::Other(s)) => assert!(s.contains("unsafe inductive")), - other => { - panic!("expected unsafe-inductive reference error, got {other:?}") - }, - } - } - - fn insert_id_def(env: &mut KEnv, id: KId, block: KId) { - env.insert( - id, - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Abbrev, - lvls: 0, - ty: AE::all((), (), sort0(), sort0()), - val: AE::lam((), (), sort0(), AE::var(0, ())), - lean_all: (), - block, - }, - ); - } - - fn dependency_def( - id: &KId, - ty: AE, - val: AE, - safety: DefinitionSafety, - kind: DefKind, - ) -> KConst { - KConst::Defn { - name: (), - level_params: (), - kind, - safety, - hints: ReducibilityHints::Regular(0), - lvls: 0, - ty, - val, - lean_all: (), - block: id.clone(), - } - } - - fn insert_proposition(env: &mut KEnv) -> AE { - let id = mk_id("P"); - env.insert( - id.clone(), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: sort0(), - }, - ); - AE::cnst(id, Box::new([])) - } - - #[test] - fn definition_dependency_rejects_self_justifying_theorem() { - let mut env = KEnv::::new(); - let prop = insert_proposition(&mut env); - let id = mk_id("loop"); - env.insert( - id.clone(), - dependency_def( - &id, - prop, - AE::cnst(id.clone(), Box::new([])), - DefinitionSafety::Safe, - DefKind::Theorem, - ), - ); - let error = TypeChecker::new(&mut env).check_const(&id).unwrap_err(); - assert!(error.to_string().contains("cyclic definition dependency")); - } - - #[test] - fn definition_dependency_rejects_mutual_cycles_in_every_safe_kind() { - for kind in [DefKind::Definition, DefKind::Theorem, DefKind::Opaque] { - let mut env = KEnv::::new(); - let prop = insert_proposition(&mut env); - let left = mk_id("left"); - let right = mk_id("right"); - for (id, target) in [(&left, &right), (&right, &left)] { - env.insert( - id.clone(), - dependency_def( - id, - prop.clone(), - AE::cnst(target.clone(), Box::new([])), - DefinitionSafety::Safe, - kind, - ), - ); - } - let mut tc = TypeChecker::new(&mut env); - for id in [&left, &right] { - let error = tc.check_const(id).unwrap_err(); - assert!(error.to_string().contains("cyclic definition dependency")); - } - } - } - - #[test] - fn definition_dependency_checks_types_and_hidden_values() { - let id = mk_id("hidden_cycle"); - let self_ref = AE::cnst(id.clone(), Box::new([])); - let cases = [ - (self_ref.clone(), sort0()), - (sort0(), AE::lam((), (), sort0(), self_ref.clone())), - (sort0(), AE::let_((), sort0(), self_ref.clone(), sort0(), true)), - (sort0(), AE::all((), (), self_ref.clone(), sort0())), - (sort0(), AE::app(self_ref.clone(), self_ref)), - ]; - for (ty, val) in cases { - let mut env = KEnv::::new(); - env.insert( - id.clone(), - dependency_def( - &id, - ty, - val, - DefinitionSafety::Safe, - DefKind::Definition, - ), - ); - let error = TypeChecker::new(&mut env).check_const(&id).unwrap_err(); - assert!(error.to_string().contains("cyclic definition dependency")); - } - } - - #[test] - fn definition_dependency_preserves_partial_and_unsafe_policy() { - for safety in [DefinitionSafety::Partial, DefinitionSafety::Unsafe] { - let mut env = KEnv::::new(); - let prop = insert_proposition(&mut env); - let id = mk_id("nonlogical_loop"); - env.insert( - id.clone(), - dependency_def( - &id, - prop, - AE::cnst(id.clone(), Box::new([])), - safety, - DefKind::Definition, - ), - ); - TypeChecker::new(&mut env).check_const(&id).unwrap(); - } - } - - #[test] - fn definition_dependency_accepts_acyclic_forward_shared_dependencies() { - let mut env = KEnv::::new(); - let prop = insert_proposition(&mut env); - let witness = mk_id("p"); - env.insert( - witness.clone(), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: prop.clone(), - }, - ); - let left = mk_id("left"); - let right = mk_id("right"); - let shared = mk_id("shared"); - let block = mk_id("acyclic_block"); - for (id, value) in [ - (&left, AE::cnst(right.clone(), Box::new([]))), - (&right, AE::cnst(shared.clone(), Box::new([]))), - (&shared, AE::cnst(witness, Box::new([]))), - ] { - let mut declaration = dependency_def( - id, - prop.clone(), - value, - DefinitionSafety::Safe, - DefKind::Theorem, - ); - if let KConst::Defn { block: target, .. } = &mut declaration { - *target = block.clone(); - } - env.insert(id.clone(), declaration); - } - env.insert_block(block, vec![left.clone(), right.clone(), shared.clone()]); - let mut tc = TypeChecker::new(&mut env); - for id in [&left, &shared, &right, &left] { - tc.check_const(id).unwrap(); - } - } - - #[test] - fn definition_dependency_collects_projection_heads() { - let id = mk_id("root"); - let head = mk_id("projection_head"); - let major = mk_id("major"); - let declaration = dependency_def( - &id, - sort0(), - AE::prj(head.clone(), 0, AE::cnst(major.clone(), Box::new([]))), - DefinitionSafety::Safe, - DefKind::Definition, - ); - let refs = super::definition_dependencies(&declaration); - assert_eq!(refs, vec![head, major]); - } - - #[test] - fn checking_one_definition_checks_sibling_block() { - let mut env = KEnv::::new(); - let block = mk_id("def_block"); - let good = mk_id("good"); - let bad = mk_id("bad"); - insert_id_def(&mut env, good.clone(), block.clone()); - env.insert( - bad.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(0), - lvls: 0, - ty: AE::all((), (), sort0(), sort0()), - val: sort1(), - lean_all: (), - block: block.clone(), - }, - ); - env.insert_block(block.clone(), vec![good.clone(), bad.clone()]); - - let first = { - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&good).unwrap_err() - }; - let second = { - let mut tc2 = TypeChecker::new(&mut env); - tc2.check_const(&bad).unwrap_err() - }; - - assert_eq!(format!("{first}"), format!("{second}")); - assert!(env.block_check_results.get(&block).is_some_and(|r| r.is_err())); - } - - // Note: the previous `concurrent_definition_block_checks_share_result` - // test exercised cross-thread block-check coordination via the old - // `Arc` + `Mutex/Condvar` machinery. With the per-worker - // single-threaded `KEnv` design, there is no shared block-check - // coordination to test — each worker owns its env and the - // `block_check_results` cache is purely a within-worker memo. - - // ========================================================================= - // Axiom with unknown referent in its type errors - // ========================================================================= - - #[test] - fn check_axiom_referencing_unknown_const_errors() { - let mut env = KEnv::::new(); - env.insert( - mk_id("x"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: AE::cnst(mk_id("UnknownType"), Box::new([])), - }, - ); - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&mk_id("x")) { - Err(TcError::UnknownConst(_)) => {}, - other => panic!("expected UnknownConst, got {other:?}"), - } - } - - // ========================================================================= - // Native evaluation is outside the checked theory - // ========================================================================= - - fn native_evaluation_id(name: &str) -> KId { - let (_, addr) = super::super::primitive::native_evaluation_addrs() - .iter() - .find(|(n, _)| *n == name) - .expect("known native-evaluation constant"); - KId::new(addr.clone(), ()) - } - - fn prop_axiom(env: &mut KEnv, id: &KId) { - env.insert( - id.clone(), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: sort0(), - }, - ); - } - - fn prop_definition(env: &mut KEnv, id: &KId, val: AE) { - env.insert( - id.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(0), - lvls: 0, - ty: sort0(), - val, - lean_all: (), - block: id.clone(), - }, - ); - } - - /// Each of the five declarations is declined by its own address, even - /// when, like `Lean.trustCompiler : True`, its statement mentions none of - /// the others. The same axiom at another address is accepted. - #[test] - fn native_evaluation_declarations_decline() { - for (name, addr) in super::super::primitive::native_evaluation_addrs() { - let mut env = KEnv::::new(); - let id = KId::new(addr.clone(), ()); - prop_axiom(&mut env, &id); - let control = mk_id("ordinary axiom"); - prop_axiom(&mut env, &control); - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&id) { - Err(TcError::NativeEvaluation(declined)) => { - assert_eq!(declined, *addr, "{name}") - }, - other => { - panic!("{name}: expected a native-evaluation decline, got {other:?}") - }, - } - tc.check_const(&control).expect("an ordinary axiom is accepted"); - } - } - - /// A declaration that refers to a native-evaluation constant is declined, - /// as the certified checker blocks it; inference without validation still - /// reads the declared type. - #[test] - fn reference_to_native_evaluation_declines() { - let mut env = KEnv::::new(); - let trust = native_evaluation_id("Lean.trustCompiler"); - let other = mk_id("ordinary axiom"); - prop_axiom(&mut env, &trust); - prop_axiom(&mut env, &other); - let user = mk_id("user"); - let control = mk_id("control"); - prop_definition(&mut env, &user, AE::cnst(trust.clone(), Box::new([]))); - prop_definition(&mut env, &control, AE::cnst(other, Box::new([]))); - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&user) { - Err(TcError::NativeEvaluation(addr)) => assert_eq!(addr, trust.addr), - other => panic!("expected a native-evaluation decline, got {other:?}"), - } - tc.check_const(&control).expect("the same shape over an ordinary axiom"); - let reference = AE::cnst(trust, Box::new([])); - let ty = tc - .with_infer_only(|tc| tc.infer(&reference)) - .expect("inference without validation is unaffected"); - assert_eq!(ty, sort0()); - } - - #[test] - fn safe_definition_rejects_projection_of_unsafe_inductive() { - let mut env = KEnv::::new(); - let ind = mk_id("unsafe_struct"); - let ctor = mk_id("unsafe_struct.mk"); - env.insert( - ind.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - member_idx: 0, - is_unsafe: true, - ty: sort0(), - ctors: vec![ctor], - lean_all: (), - block: ind.clone(), - }, - ); - let expr = AE::prj(ind, 0, sort0()); - let mut tc = TypeChecker::new(&mut env); - let err = tc - .check_no_unsafe_refs(&expr, DefinitionSafety::Safe) - .expect_err("projection from unsafe inductive must be rejected"); - assert!( - err.to_string().contains("safe definition references unsafe inductive"), - "unexpected error: {err}" - ); - } -} diff --git a/crates/kernel/src/claim.rs b/crates/kernel/src/claim.rs deleted file mode 100644 index 5898b9c44..000000000 --- a/crates/kernel/src/claim.rs +++ /dev/null @@ -1,298 +0,0 @@ -//! High-level claim builders that combine kernel results with the -//! transitive-dep walker and the merkle root. -//! -//! `TypeChecker::check_const` stays pure (`Result<(), TcError>`); these -//! builders sit above it. They take an `Env` so they can compute the -//! `assumptions` merkle root over the constant's transitive deps. -//! -//! These env-derived builders intentionally use the *canonical* merkle builder -//! (sorted + deduped leaves). `Claim::CheckEnv` itself treats its root as an -//! opaque Merkle commitment and already accepts free-form roots produced by -//! recursive `merkle_join`; there is no canonical-root validator on claims. - -use ix_common::address::Address; -use ixon::env::Env; -use ixon::merkle::merkle_root_canonical; -use ixon::proof::{Claim, ClaimContext}; - -/// Canonical merkle root over the env's `consts.keys()`. Also called -/// from the env serializer. Returns `None` for an empty const set. -pub fn env_merkle_root(env: &Env) -> Option
{ - let mut addrs: Vec
= - env.consts.iter().map(|e| e.key().clone()).collect(); - addrs.sort_unstable(); - merkle_root_canonical(&addrs) -} - -/// Build a check claim for the constant at `const_addr` in `env`. -/// -/// Sets `assumptions: None` when the constant has no transitive deps, -/// else `Some(root)` where `root` is the canonical merkle root over -/// those deps. -pub fn build_check_claim( - ctx: &ClaimContext, - env: &Env, - const_addr: Address, -) -> Claim { - let deps = env.transitive_deps_excl(&const_addr); - let assumptions = merkle_root_canonical(&deps); - Claim::Check { ctx: ctx.clone(), const_addr, assumptions } -} - -/// Build an eval claim for the pair `(input, output)` in `env`. -/// -/// Only the input's dependency closure needs typing assumptions. The output -/// definition's type and body are established by the evaluation claim. -pub fn build_eval_claim( - ctx: &ClaimContext, - env: &Env, - input: Address, - output: Address, -) -> Claim { - let deps = env.transitive_deps_excl(&input); - let assumptions = merkle_root_canonical(&deps); - Claim::Eval { ctx: ctx.clone(), input, output, assumptions } -} - -/// Build a shard `CheckEnv` claim under the SHARD PROTOCOL convention: -/// root over the owned set, assumptions over the THIN frontier (direct -/// out-of-owned walk edges). Delegates to [`ixon::shard_claim`] — the -/// single source of truth shared with the Aiur witness builders, so -/// digests reproduce across the Rust and Aiur pipelines. The -/// claim-exact check set (asm-cutoff walk + strict owned-membership) -/// is [`ixon::shard_claim::walk_check_set`]; a native checker -/// validating such a claim must check exactly that set — checking the -/// whole env eagerly is a sound superset for DEV use but does not -/// certify the claim (it misses the strict-membership coverage rule). -pub fn build_shard_check_env_claim( - ctx: &ClaimContext, - env: &Env, - owned: &[Address], -) -> Option<(Claim, Vec
)> { - ixon::shard_claim::shard_check_env_claim(ctx, env, owned) -} - -// --------------------------------------------------------------------------- -// Tests -// --------------------------------------------------------------------------- - -#[cfg(test)] -mod tests { - use super::*; - use ix_common::env::DefinitionSafety; - use ixon::constant::{Axiom, Constant, ConstantInfo, DefKind, Definition}; - use ixon::expr::Expr; - use ixon::merkle::leaf_hash; - use std::sync::Arc; - - fn ctx() -> ClaimContext { - ClaimContext { - spec: ixon::proof::spec_digest(), - binding: Address::hash(b"binding"), - axioms: None, - binding_assumed: false, - literal_class: false, - } - } - - fn axiom_const(refs: Vec
) -> Constant { - Constant::with_tables( - ConstantInfo::Axio(Axiom { - is_unsafe: false, - lvls: 0, - typ: Arc::new(Expr::Sort(0)), - }), - Vec::new(), - refs, - Vec::new(), - ) - } - - /// A definition whose value NAMES every one of its `refs` entries via a - /// `Ref` node. The claim walk admits an index only when some Expr uses - /// it as a constant, so a fixture whose Exprs ignore `refs` produces no - /// edges and every frontier comes back empty. - fn defn_const(refs: Vec
) -> Constant { - let mut value = Expr::var(0); - for i in 0..refs.len() { - value = Expr::app(value, Expr::reference(i as u64, Vec::new())); - } - Constant::with_tables( - ConstantInfo::Defn(Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: Expr::sort(0), - value, - }), - Vec::new(), - refs, - Vec::new(), - ) - } - - #[test] - fn check_no_deps_assumptions_none() { - let env = Env::new(); - let a = Address::hash(b"a"); - env.store_const(a.clone(), defn_const(vec![])); - match build_check_claim(&ctx(), &env, a.clone()) { - Claim::Check { const_addr, assumptions: None, .. } => { - assert_eq!(const_addr, a); - }, - other => panic!("expected Check {{ assumptions: None }}, got {other:?}"), - } - } - - #[test] - fn check_with_one_axiom_dep_assumptions_some() { - let env = Env::new(); - let a = Address::hash(b"a"); - let ax = Address::hash(b"ax"); - env.store_const(a.clone(), defn_const(vec![ax.clone()])); - env.store_const(ax.clone(), axiom_const(vec![])); - match build_check_claim(&ctx(), &env, a.clone()) { - Claim::Check { const_addr, assumptions: Some(asm), .. } => { - assert_eq!(const_addr, a); - assert_eq!(asm, leaf_hash(&ax)); - }, - other => panic!("expected Check Some, got {other:?}"), - } - } - - #[test] - fn check_excludes_subject_from_assumptions() { - // a -> b -> a (cycle). Subject `a` must not be in its own assumption set. - let env = Env::new(); - let a = Address::hash(b"a"); - let b = Address::hash(b"b"); - env.store_const(a.clone(), defn_const(vec![b.clone()])); - env.store_const(b.clone(), defn_const(vec![a.clone()])); - match build_check_claim(&ctx(), &env, a.clone()) { - Claim::Check { const_addr, assumptions: Some(asm), .. } => { - assert_eq!(const_addr, a); - assert_eq!(asm, leaf_hash(&b)); - }, - other => panic!("expected Check Some, got {other:?}"), - } - } - - #[test] - fn eval_no_deps_assumptions_none() { - let env = Env::new(); - let i = Address::hash(b"i"); - let o = Address::hash(b"o"); - env.store_const(i.clone(), defn_const(vec![])); - env.store_const(o.clone(), defn_const(vec![])); - match build_eval_claim(&ctx(), &env, i.clone(), o.clone()) { - Claim::Eval { input, output, assumptions: None, .. } => { - assert_eq!(input, i); - assert_eq!(output, o); - }, - other => panic!("expected Eval None, got {other:?}"), - } - } - - #[test] - fn eval_assumptions_come_only_from_input() { - // Output-only dependencies are not input typing assumptions. - let env = Env::new(); - let i = Address::hash(b"i"); - let o = Address::hash(b"o"); - let d = Address::hash(b"d"); - env.store_const(i.clone(), defn_const(vec![d.clone()])); - env.store_const(o.clone(), defn_const(vec![Address::hash(b"output-only")])); - env.store_const(d.clone(), defn_const(vec![])); - match build_eval_claim(&ctx(), &env, i.clone(), o.clone()) { - Claim::Eval { input, output, assumptions: Some(asm), .. } => { - assert_eq!(input, i); - assert_eq!(output, o); - assert_eq!(asm, leaf_hash(&d)); - }, - other => panic!("expected Eval Some, got {other:?}"), - } - } - - #[test] - fn eval_keeps_output_when_it_is_an_input_dependency() { - let env = Env::new(); - let input = Address::hash(b"input"); - let output = Address::hash(b"output"); - env.store_const(input.clone(), defn_const(vec![output.clone()])); - env.store_const(output.clone(), defn_const(vec![])); - assert_eq!( - build_eval_claim(&ctx(), &env, input.clone(), output.clone()), - Claim::Eval { - ctx: ctx(), - input, - assumptions: Some(leaf_hash(&output)), - output - } - ); - } - - #[test] - fn shard_check_env_claim_is_thin_frontier() { - // a -> b -> c: owned {a} gives frontier {b} only — c is below the - // first layer and must NOT enter the asm set. - let env = Env::new(); - let a = Address::hash(b"a"); - let b = Address::hash(b"b"); - let c = Address::hash(b"c"); - env.store_const(a.clone(), defn_const(vec![b.clone()])); - env.store_const(b.clone(), defn_const(vec![c.clone()])); - env.store_const(c.clone(), defn_const(vec![])); - let (claim, frontier) = - build_shard_check_env_claim(&ctx(), &env, std::slice::from_ref(&a)) - .unwrap(); - assert_eq!(frontier, vec![b.clone()]); - match claim { - Claim::CheckEnv { root, assumptions: Some(asm), .. } => { - assert_eq!(root, leaf_hash(&a)); - assert_eq!(asm, leaf_hash(&b)); - }, - other => panic!("expected CheckEnv Some, got {other:?}"), - } - } - - #[test] - fn env_merkle_root_invariant_under_insertion_order() { - let env1 = Env::new(); - let env2 = Env::new(); - let a = Address::hash(b"a"); - let b = Address::hash(b"b"); - let c = Address::hash(b"c"); - env1.store_const(a.clone(), defn_const(vec![])); - env1.store_const(b.clone(), defn_const(vec![])); - env1.store_const(c.clone(), defn_const(vec![])); - // Insert in reverse order. - env2.store_const(c, defn_const(vec![])); - env2.store_const(b, defn_const(vec![])); - env2.store_const(a, defn_const(vec![])); - assert_eq!(env_merkle_root(&env1), env_merkle_root(&env2)); - } - - #[test] - fn env_merkle_root_changes_with_extra_const() { - let env = Env::new(); - let a = Address::hash(b"a"); - env.store_const(a.clone(), defn_const(vec![])); - let root1 = env_merkle_root(&env).unwrap(); - let b = Address::hash(b"b"); - env.store_const(b, defn_const(vec![])); - let root2 = env_merkle_root(&env).unwrap(); - assert_ne!(root1, root2); - } - - #[test] - fn build_claims_are_deterministic() { - let env = Env::new(); - let a = Address::hash(b"a"); - let b = Address::hash(b"b"); - env.store_const(a.clone(), defn_const(vec![b.clone()])); - env.store_const(b, defn_const(vec![])); - let c1 = build_check_claim(&ctx(), &env, a.clone()); - let c2 = build_check_claim(&ctx(), &env, a); - assert_eq!(c1, c2); - } -} diff --git a/crates/kernel/src/congruence.rs b/crates/kernel/src/congruence.rs deleted file mode 100644 index 9a2d1fc25..000000000 --- a/crates/kernel/src/congruence.rs +++ /dev/null @@ -1,1012 +0,0 @@ -//! Congruence checks between Lean-side `ix::env` types and zero kernel types. -//! -//! Validates that Ixon ingress in Anon mode produces structurally correct -//! constants by comparing the Lean `ConstantInfo` against the loaded `KConst`. - -use ix_common::address::Address; -use ix_common::env::{self as lean, ConstantInfo as LeanCI, Literal, Name}; - -use super::constant::KConst; -use super::expr::{ExprData, KExpr}; -use super::level::{KUniv, UnivData}; -use super::mode::Anon; - -/// Name-to-address resolver, built from the Ixon named map. -pub struct NameResolver { - map: rustc_hash::FxHashMap, -} - -impl NameResolver { - pub fn from_ixon_env(ixon_env: &ixon::env::Env) -> Self { - let mut map = rustc_hash::FxHashMap::default(); - for entry in ixon_env.named.iter() { - map.insert(entry.key().clone(), entry.value().addr.clone()); - } - NameResolver { map } - } - - pub fn resolve(&self, name: &Name) -> Option<&Address> { - self.map.get(name) - } -} - -/// Check that a Lean-side Level matches a zero Univ structurally. -pub fn level_congruent( - lean_lvl: &lean::Level, - zero_univ: &KUniv, - _nr: &NameResolver, -) -> Result<(), String> { - use lean::LevelData as LD; - match (lean_lvl.as_data(), zero_univ.data()) { - (LD::Zero(_), UnivData::Zero(_)) => Ok(()), - (LD::Succ(a, _), UnivData::Succ(b, _)) => level_congruent(a, b, _nr), - (LD::Max(a1, a2, _), UnivData::Max(b1, b2, _)) - | (LD::Imax(a1, a2, _), UnivData::IMax(b1, b2, _)) => { - level_congruent(a1, b1, _nr)?; - level_congruent(a2, b2, _nr) - }, - (LD::Param(_, _), UnivData::Param(_, _, _)) => { - // Lean uses named params, zero uses positional indices. - // Can't check correspondence without level_params list. - Ok(()) - }, - _ => Err(format!( - "level mismatch: lean={} vs zero={}", - lean_lvl_tag(lean_lvl), - zero_univ_tag(zero_univ), - )), - } -} - -/// Check that a Lean-side Expr matches a zero Expr structurally. -pub fn expr_congruent( - lean_expr: &lean::Expr, - zero_expr: &KExpr, - nr: &NameResolver, -) -> Result<(), String> { - use lean::ExprData as LE; - let mut source = lean_expr; - let mut layers = Vec::new(); - while let LE::Mdata(data, inner, _) = source.as_data() { - layers.push(data.clone()); - source = inner; - } - let frame = ixon::contract::source::source_layers(source, &layers) - .map_err(|e| e.to_string())?; - let actual = zero_expr.source_contract(); - if frame.is_some_and(|expected| Some(expected) != actual) - || (frame.is_none() && actual.is_some_and(|c| !c.is_default())) - { - return Err("source/kernel binder contract mismatch".into()); - } - let lean_expr = source; - match (lean_expr.as_data(), zero_expr.data()) { - (LE::Bvar(n, _), ExprData::Var(m, _, _)) => { - let n = n.to_u64().unwrap_or(u64::MAX); - if n == *m { - Ok(()) - } else { - Err(format!("var mismatch: lean={n} vs zero={m}")) - } - }, - - (LE::Sort(l, _), ExprData::Sort(u, _)) => level_congruent(l, u, nr), - - (LE::Const(name, levels, _), ExprData::Const(id, univs, _)) => { - match nr.resolve(name) { - Some(expected) if expected == &id.addr => {}, - Some(expected) => { - return Err(format!( - "const address mismatch for {name}: expected {}, got {}", - expected.hex(), - id.addr.hex() - )); - }, - None => { - return Err(format!("const name not found in resolver: {name}")); - }, - } - if levels.len() != univs.len() { - return Err(format!( - "const {name}: level count mismatch: {} vs {}", - levels.len(), - univs.len() - )); - } - for (l, u) in levels.iter().zip(univs.iter()) { - level_congruent(l, u, nr)?; - } - Ok(()) - }, - - (LE::App(f1, a1, _), ExprData::App(f2, a2, _)) => { - expr_congruent(f1, f2, nr)?; - expr_congruent(a1, a2, nr) - }, - - (LE::Lam(_, ty1, body1, _, _), ExprData::Lam(_, _, _, ty2, body2, _)) - | ( - LE::ForallE(_, ty1, body1, _, _), - ExprData::All(_, _, _, _, ty2, body2, _), - ) => { - expr_congruent(ty1, ty2, nr)?; - expr_congruent(body1, body2, nr) - }, - - ( - LE::LetE(_, ty1, val1, body1, non_dep, _), - ExprData::Let(_, ty2, val2, body2, contract, _), - ) => { - if *non_dep != contract.non_dep { - return Err("source/kernel let nonDep mismatch".into()); - } - expr_congruent(ty1, ty2, nr)?; - expr_congruent(val1, val2, nr)?; - expr_congruent(body1, body2, nr) - }, - - (LE::Lit(Literal::NatVal(_), _), ExprData::Nat(_, _, _)) - | (LE::Lit(Literal::StrVal(_), _), ExprData::Str(_, _, _)) => Ok(()), - - (LE::Proj(name, idx, struct_expr, _), ExprData::Prj(id, field, val, _)) => { - match nr.resolve(name) { - Some(expected) if expected == &id.addr => {}, - Some(expected) => { - return Err(format!( - "proj type mismatch for {name}: expected {}, got {}", - expected.hex(), - id.addr.hex() - )); - }, - None => return Err(format!("proj type name not found: {name}")), - } - if idx.to_u64().unwrap_or(u64::MAX) != *field { - return Err(format!( - "proj field mismatch: lean={} vs zero={field}", - idx.to_u64().unwrap_or(u64::MAX) - )); - } - expr_congruent(struct_expr, val, nr) - }, - - // Lean Mdata wraps an inner expr — zero strips it in Anon mode. - (LE::Mdata(_, inner, _), _) => expr_congruent(inner, zero_expr, nr), - - (LE::Fvar(..) | LE::Mvar(..), _) => { - Err("unexpected Fvar/Mvar in constant".to_string()) - }, - - _ => Err(format!( - "expr shape mismatch: lean={} vs zero={}", - lean_expr_tag(lean_expr), - zero_expr_tag(zero_expr), - )), - } -} - -/// Check that a Lean `ConstantInfo` matches a `KConst` structurally. -pub fn const_congruent( - lean_ci: &LeanCI, - zero_const: &KConst, - nr: &NameResolver, -) -> Result<(), String> { - // Check type congruence - let lean_type = lean_ci.get_type(); - let zero_type = zero_const.ty(); - expr_congruent(lean_type, zero_type, nr).map_err(|e| format!("type: {e}"))?; - - // Check lvls count - let lean_lvls = lean_ci.get_level_params().len() as u64; - let zero_lvls = zero_const.lvls(); - if lean_lvls != zero_lvls { - return Err(format!("lvls: lean={lean_lvls} vs zero={zero_lvls}")); - } - - // Variant-specific checks - match (lean_ci, zero_const) { - (LeanCI::AxiomInfo(_), KConst::Axio { .. }) - | (LeanCI::QuotInfo(_), KConst::Quot { .. }) => Ok(()), - - (LeanCI::DefnInfo(v), KConst::Defn { val, .. }) => { - expr_congruent(&v.value, val, nr).map_err(|e| format!("value: {e}")) - }, - - (LeanCI::ThmInfo(v), KConst::Defn { val, .. }) => { - expr_congruent(&v.value, val, nr).map_err(|e| format!("value: {e}")) - }, - - (LeanCI::OpaqueInfo(v), KConst::Defn { val, .. }) => { - expr_congruent(&v.value, val, nr).map_err(|e| format!("value: {e}")) - }, - - (LeanCI::InductInfo(v), KConst::Indc { params, indices, ctors, .. }) => { - let lp = v.num_params.to_u64().unwrap_or(u64::MAX); - let li = v.num_indices.to_u64().unwrap_or(u64::MAX); - if lp != *params { - return Err(format!("params: lean={lp} vs zero={params}")); - } - if li != *indices { - return Err(format!("indices: lean={li} vs zero={indices}")); - } - if v.ctors.len() != ctors.len() { - return Err(format!( - "ctor count: lean={} vs zero={}", - v.ctors.len(), - ctors.len() - )); - } - Ok(()) - }, - - (LeanCI::CtorInfo(v), KConst::Ctor { cidx, params, fields, .. }) => { - let lc = v.cidx.to_u64().unwrap_or(u64::MAX); - let lp = v.num_params.to_u64().unwrap_or(u64::MAX); - let lf = v.num_fields.to_u64().unwrap_or(u64::MAX); - if lc != *cidx { - return Err(format!("cidx: lean={lc} vs zero={cidx}")); - } - if lp != *params { - return Err(format!("params: lean={lp} vs zero={params}")); - } - if lf != *fields { - return Err(format!("fields: lean={lf} vs zero={fields}")); - } - Ok(()) - }, - - ( - LeanCI::RecInfo(v), - KConst::Recr { params, indices, motives, minors, rules, k, .. }, - ) => { - let lp = v.num_params.to_u64().unwrap_or(u64::MAX); - let li = v.num_indices.to_u64().unwrap_or(u64::MAX); - let lm = v.num_motives.to_u64().unwrap_or(u64::MAX); - let ln = v.num_minors.to_u64().unwrap_or(u64::MAX); - if lp != *params { - return Err(format!("params: lean={lp} vs zero={params}")); - } - if li != *indices { - return Err(format!("indices: lean={li} vs zero={indices}")); - } - if lm != *motives { - return Err(format!("motives: lean={lm} vs zero={motives}")); - } - if ln != *minors { - return Err(format!("minors: lean={ln} vs zero={minors}")); - } - if v.rules.len() != rules.len() { - return Err(format!( - "rule count: lean={} vs zero={}", - v.rules.len(), - rules.len() - )); - } - if v.k != *k { - return Err(format!("k: lean={} vs zero={k}", v.k)); - } - for (i, (lean_rule, zero_rule)) in - v.rules.iter().zip(rules.iter()).enumerate() - { - expr_congruent(&lean_rule.rhs, &zero_rule.rhs, nr) - .map_err(|e| format!("rule[{i}].rhs: {e}"))?; - } - Ok(()) - }, - - _ => Err(format!( - "variant mismatch: lean={} vs zero={}", - lean_ci_tag(lean_ci), - zero_const_tag(zero_const), - )), - } -} - -fn lean_lvl_tag(l: &lean::Level) -> &'static str { - use lean::LevelData as LD; - match l.as_data() { - LD::Zero(_) => "Zero", - LD::Succ(..) => "Succ", - LD::Max(..) => "Max", - LD::Imax(..) => "IMax", - LD::Param(..) => "Param", - LD::Mvar(..) => "Mvar", - } -} - -fn zero_univ_tag(u: &KUniv) -> &'static str { - match u.data() { - UnivData::Zero(_) => "Zero", - UnivData::Succ(..) => "Succ", - UnivData::Max(..) => "Max", - UnivData::IMax(..) => "IMax", - UnivData::Param(..) => "Param", - } -} - -fn lean_expr_tag(e: &lean::Expr) -> &'static str { - use lean::ExprData as LE; - match e.as_data() { - LE::Bvar(..) => "Bvar", - LE::Fvar(..) => "Fvar", - LE::Mvar(..) => "Mvar", - LE::Sort(..) => "Sort", - LE::Const(..) => "Const", - LE::App(..) => "App", - LE::Lam(..) => "Lam", - LE::ForallE(..) => "ForallE", - LE::LetE(..) => "LetE", - LE::Lit(..) => "Lit", - LE::Mdata(..) => "Mdata", - LE::Proj(..) => "Proj", - } -} - -fn zero_expr_tag(e: &KExpr) -> &'static str { - match e.data() { - ExprData::Var(..) => "Var", - ExprData::FVar(..) => "FVar", - ExprData::Sort(..) => "Sort", - ExprData::Const(..) => "Const", - ExprData::App(..) => "App", - ExprData::Lam(..) => "Lam", - ExprData::All(..) => "All", - ExprData::Let(..) => "Let", - ExprData::Prj(..) => "Prj", - ExprData::Nat(..) => "Nat", - ExprData::Str(..) => "Str", - } -} - -fn lean_ci_tag(ci: &LeanCI) -> &'static str { - match ci { - LeanCI::AxiomInfo(_) => "Axiom", - LeanCI::DefnInfo(_) => "Defn", - LeanCI::ThmInfo(_) => "Thm", - LeanCI::OpaqueInfo(_) => "Opaque", - LeanCI::QuotInfo(_) => "Quot", - LeanCI::InductInfo(_) => "Induct", - LeanCI::CtorInfo(_) => "Ctor", - LeanCI::RecInfo(_) => "Rec", - } -} - -fn zero_const_tag(c: &KConst) -> &'static str { - match c { - KConst::Defn { .. } => "Defn", - KConst::Recr { .. } => "Recr", - KConst::Axio { .. } => "Axio", - KConst::Quot { .. } => "Quot", - KConst::Indc { .. } => "Indc", - KConst::Ctor { .. } => "Ctor", - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::constant::KConst; - use crate::id::KId; - use crate::mode::Anon; - use ix_common::address::Address; - use ix_common::env::{ - self, AxiomVal, BinderInfo, ConstantVal, ConstructorVal, DefinitionSafety, - DefinitionVal, InductiveVal, Level as LL, Name, OpaqueVal, QuotKind, - QuotVal, RecursorRule as LeanRule, RecursorVal, ReducibilityHints, - TheoremVal, - }; - use ixon::env::{Env as IxonEnv, Named}; - - /// `Nat` from a u64 via the public `From` impl. - /// (The `Nat` type itself is a private re-export in `env.rs`.) - fn n(x: u64) -> bignat::Nat { - bignat::Nat::from(x) - } - - // ---- test helpers ---- - - fn mk_name(s: &str) -> Name { - let mut n = Name::anon(); - for part in s.split('.') { - n = Name::str(n, part.to_string()); - } - n - } - - fn mk_addr(s: &str) -> Address { - Address::hash(s.as_bytes()) - } - - fn empty_resolver() -> NameResolver { - NameResolver::from_ixon_env(&IxonEnv::new()) - } - - fn resolver_with(entries: &[(Name, Address)]) -> NameResolver { - let env = IxonEnv::new(); - for (n, a) in entries { - env.register_name(n.clone(), Named::with_addr(a.clone())); - } - NameResolver::from_ixon_env(&env) - } - - // ---- level_congruent ---- - - #[test] - fn level_zero_matches() { - let r = empty_resolver(); - let ll = LL::zero(); - let lu = KUniv::::zero(); - level_congruent(&ll, &lu, &r).unwrap(); - } - - #[test] - fn level_succ_matches() { - let r = empty_resolver(); - let ll = LL::succ(LL::zero()); - let lu = KUniv::::succ(KUniv::zero()); - level_congruent(&ll, &lu, &r).unwrap(); - } - - #[test] - fn level_max_matches() { - // KUniv::max / ::imax simplify at construction (e.g. `max(0, a) → a`), - // so use two params so neither side is reducible at the Zero case. - let r = empty_resolver(); - let u_name = Name::str(Name::anon(), "u".to_string()); - let v_name = Name::str(Name::anon(), "v".to_string()); - let ll = LL::max(LL::param(u_name), LL::param(v_name)); - let lu = KUniv::::max(KUniv::param(0, ()), KUniv::param(1, ())); - level_congruent(&ll, &lu, &r).unwrap(); - } - - #[test] - fn level_imax_matches() { - let r = empty_resolver(); - let u_name = Name::str(Name::anon(), "u".to_string()); - let v_name = Name::str(Name::anon(), "v".to_string()); - let ll = LL::imax(LL::param(u_name), LL::param(v_name)); - let lu = KUniv::::imax(KUniv::param(0, ()), KUniv::param(1, ())); - level_congruent(&ll, &lu, &r).unwrap(); - } - - #[test] - fn level_param_matches() { - // Lean Param has a name; zero Param has a positional index. Without a - // level_params list the check must pass (see module comment). - let r = empty_resolver(); - let ll = LL::param(mk_name("u")); - let lu = KUniv::::param(0, ()); - level_congruent(&ll, &lu, &r).unwrap(); - } - - #[test] - fn level_zero_vs_succ_fails() { - let r = empty_resolver(); - let ll = LL::zero(); - let lu = KUniv::::succ(KUniv::zero()); - let e = level_congruent(&ll, &lu, &r).unwrap_err(); - assert!(e.contains("Zero")); - assert!(e.contains("Succ")); - } - - #[test] - fn level_max_vs_imax_fails() { - let r = empty_resolver(); - let u_name = Name::str(Name::anon(), "u".to_string()); - let v_name = Name::str(Name::anon(), "v".to_string()); - let ll = LL::max(LL::param(u_name), LL::param(v_name)); - let lu = KUniv::::imax(KUniv::param(0, ()), KUniv::param(1, ())); - let e = level_congruent(&ll, &lu, &r).unwrap_err(); - assert!(e.contains("Max")); - assert!(e.contains("IMax")); - } - - #[test] - fn level_succ_inner_propagates_error() { - let r = empty_resolver(); - // Succ(Zero) vs Succ(Succ(Zero)) — outer shape matches, inner differs. - let ll = LL::succ(LL::zero()); - let lu = KUniv::::succ(KUniv::succ(KUniv::zero())); - let e = level_congruent(&ll, &lu, &r).unwrap_err(); - assert!(e.contains("Zero")); - assert!(e.contains("Succ")); - } - - // ---- expr_congruent ---- - - #[test] - fn expr_bvar_matches() { - let r = empty_resolver(); - let lean_e = env::Expr::bvar(n(3)); - let zero_e = KExpr::::var(3, ()); - expr_congruent(&lean_e, &zero_e, &r).unwrap(); - } - - #[test] - fn expr_bvar_idx_mismatch_fails() { - let r = empty_resolver(); - let lean_e = env::Expr::bvar(n(3)); - let zero_e = KExpr::::var(5, ()); - let e = expr_congruent(&lean_e, &zero_e, &r).unwrap_err(); - assert!(e.contains("var mismatch")); - } - - #[test] - fn expr_sort_matches() { - let r = empty_resolver(); - let lean_e = env::Expr::sort(LL::zero()); - let zero_e = KExpr::::sort(KUniv::zero()); - expr_congruent(&lean_e, &zero_e, &r).unwrap(); - } - - #[test] - fn expr_const_matches_by_address() { - let name = mk_name("Nat"); - let addr = mk_addr("Nat"); - let r = resolver_with(&[(name.clone(), addr.clone())]); - - let lean_e = env::Expr::cnst(name.clone(), vec![]); - let zero_e = KExpr::::cnst(KId::new(addr, ()), Box::new([])); - expr_congruent(&lean_e, &zero_e, &r).unwrap(); - } - - #[test] - fn expr_const_addr_mismatch_fails() { - let name = mk_name("Nat"); - let r = resolver_with(&[(name.clone(), mk_addr("Nat"))]); - - let lean_e = env::Expr::cnst(name.clone(), vec![]); - // Wrong address in zero_e - let zero_e = - KExpr::::cnst(KId::new(mk_addr("Bogus"), ()), Box::new([])); - let e = expr_congruent(&lean_e, &zero_e, &r).unwrap_err(); - assert!(e.contains("address mismatch")); - } - - #[test] - fn expr_const_name_missing_from_resolver_fails() { - let r = empty_resolver(); - let lean_e = env::Expr::cnst(mk_name("Nat"), vec![]); - let zero_e = - KExpr::::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let e = expr_congruent(&lean_e, &zero_e, &r).unwrap_err(); - assert!(e.contains("not found")); - } - - #[test] - fn expr_const_level_count_mismatch_fails() { - let name = mk_name("Nat"); - let addr = mk_addr("Nat"); - let r = resolver_with(&[(name.clone(), addr.clone())]); - - let lean_e = env::Expr::cnst(name.clone(), vec![LL::zero()]); - let zero_e = KExpr::::cnst(KId::new(addr, ()), Box::new([])); - let e = expr_congruent(&lean_e, &zero_e, &r).unwrap_err(); - assert!(e.contains("level count mismatch")); - } - - #[test] - fn expr_app_matches_recursively() { - let r = empty_resolver(); - let lean_e = - env::Expr::app(env::Expr::sort(LL::zero()), env::Expr::bvar(n(0))); - let zero_e = - KExpr::::app(KExpr::sort(KUniv::zero()), KExpr::var(0, ())); - expr_congruent(&lean_e, &zero_e, &r).unwrap(); - } - - #[test] - fn expr_lam_matches() { - let r = empty_resolver(); - let lean_e = env::Expr::lam( - mk_name("x"), - env::Expr::sort(LL::zero()), - env::Expr::bvar(n(0)), - BinderInfo::Default, - ); - let zero_e = - KExpr::::lam((), (), KExpr::sort(KUniv::zero()), KExpr::var(0, ())); - expr_congruent(&lean_e, &zero_e, &r).unwrap(); - } - - #[test] - fn expr_forall_matches() { - let r = empty_resolver(); - let lean_e = env::Expr::all( - mk_name("x"), - env::Expr::sort(LL::zero()), - env::Expr::bvar(n(0)), - BinderInfo::Default, - ); - let zero_e = - KExpr::::all((), (), KExpr::sort(KUniv::zero()), KExpr::var(0, ())); - expr_congruent(&lean_e, &zero_e, &r).unwrap(); - } - - #[test] - fn expr_let_matches() { - let r = empty_resolver(); - let lean_e = env::Expr::letE( - mk_name("x"), - env::Expr::sort(LL::zero()), - env::Expr::bvar(n(0)), - env::Expr::bvar(n(0)), - false, - ); - let zero_e = KExpr::::let_( - (), - KExpr::sort(KUniv::zero()), - KExpr::var(0, ()), - KExpr::var(0, ()), - false, - ); - expr_congruent(&lean_e, &zero_e, &r).unwrap(); - } - - #[test] - fn expr_mdata_is_transparent() { - let r = empty_resolver(); - // Lean Mdata(_, Sort 0) must match the bare zero Sort 0. - let inner = env::Expr::sort(LL::zero()); - let lean_e = env::Expr::mdata(vec![], inner); - let zero_e = KExpr::::sort(KUniv::zero()); - expr_congruent(&lean_e, &zero_e, &r).unwrap(); - } - - #[test] - fn expr_nat_lit_matches() { - let r = empty_resolver(); - let lean_e = env::Expr::lit(Literal::NatVal(n(42))); - // Nat expr construction for the zero kernel. - let zero_e = KExpr::::nat(n(42), mk_addr("any")); - expr_congruent(&lean_e, &zero_e, &r).unwrap(); - } - - #[test] - fn expr_str_lit_matches() { - let r = empty_resolver(); - let lean_e = env::Expr::lit(Literal::StrVal("hi".into())); - let zero_e = KExpr::::str("hi".into(), mk_addr("any")); - expr_congruent(&lean_e, &zero_e, &r).unwrap(); - } - - #[test] - fn expr_proj_matches() { - let name = mk_name("MyStruct"); - let addr = mk_addr("MyStruct"); - let r = resolver_with(&[(name.clone(), addr.clone())]); - - let lean_e = env::Expr::proj(name.clone(), n(1), env::Expr::bvar(n(0))); - let zero_e = KExpr::::prj(KId::new(addr, ()), 1, KExpr::var(0, ())); - expr_congruent(&lean_e, &zero_e, &r).unwrap(); - } - - #[test] - fn expr_proj_field_mismatch_fails() { - let name = mk_name("MyStruct"); - let addr = mk_addr("MyStruct"); - let r = resolver_with(&[(name.clone(), addr.clone())]); - - let lean_e = env::Expr::proj(name.clone(), n(2), env::Expr::bvar(n(0))); - let zero_e = KExpr::::prj(KId::new(addr, ()), 1, KExpr::var(0, ())); - let e = expr_congruent(&lean_e, &zero_e, &r).unwrap_err(); - assert!(e.contains("proj field mismatch")); - } - - #[test] - fn expr_fvar_unexpected() { - let r = empty_resolver(); - let lean_e = env::Expr::fvar(mk_name("x")); - let zero_e = KExpr::::var(0, ()); - let e = expr_congruent(&lean_e, &zero_e, &r).unwrap_err(); - assert!(e.contains("Fvar") || e.contains("unexpected")); - } - - #[test] - fn expr_shape_mismatch_fails() { - let r = empty_resolver(); - let lean_e = env::Expr::sort(LL::zero()); - let zero_e = KExpr::::var(0, ()); - let e = expr_congruent(&lean_e, &zero_e, &r).unwrap_err(); - assert!(e.contains("shape mismatch")); - } - - // ---- const_congruent ---- - - fn lean_axio( - name: &str, - lvls: Vec, - typ: env::Expr, - ) -> env::ConstantInfo { - env::ConstantInfo::AxiomInfo(AxiomVal { - cnst: ConstantVal { name: mk_name(name), level_params: lvls, typ }, - is_unsafe: false, - }) - } - - fn zero_axio(lvls: u64, ty: KExpr) -> KConst { - KConst::Axio { name: (), level_params: (), is_unsafe: false, lvls, ty } - } - - #[test] - fn const_axio_matches() { - let r = empty_resolver(); - let ltyp = env::Expr::sort(LL::zero()); - let ztyp = KExpr::::sort(KUniv::zero()); - let lci = lean_axio("A", vec![], ltyp); - let kc = zero_axio(0, ztyp); - const_congruent(&lci, &kc, &r).unwrap(); - } - - #[test] - fn const_variant_mismatch_fails() { - // Axiom on the Lean side, Defn on the zero side → variant mismatch error. - let r = empty_resolver(); - let lci = lean_axio("A", vec![], env::Expr::sort(LL::zero())); - let kc = KConst::::Defn { - name: (), - level_params: (), - kind: ixon::constant::DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Opaque, - lvls: 0, - ty: KExpr::sort(KUniv::zero()), - val: KExpr::sort(KUniv::zero()), - lean_all: (), - block: KId::new(mk_addr("A"), ()), - }; - let e = const_congruent(&lci, &kc, &r).unwrap_err(); - assert!(e.contains("variant mismatch")); - } - - #[test] - fn const_lvls_count_mismatch_fails() { - let r = empty_resolver(); - let lci = lean_axio( - "A", - vec![mk_name("u"), mk_name("v")], - env::Expr::sort(LL::zero()), - ); - let kc = zero_axio(1, KExpr::sort(KUniv::zero())); // claims 1 lvl - let e = const_congruent(&lci, &kc, &r).unwrap_err(); - assert!(e.contains("lvls")); - } - - #[test] - fn const_defn_value_mismatch_propagates() { - let r = empty_resolver(); - let lci = env::ConstantInfo::DefnInfo(DefinitionVal { - cnst: ConstantVal { - name: mk_name("f"), - level_params: vec![], - typ: env::Expr::sort(LL::zero()), - }, - value: env::Expr::sort(LL::zero()), // value is Sort 0 - hints: ReducibilityHints::Opaque, - safety: DefinitionSafety::Safe, - all: vec![], - }); - let kc = KConst::::Defn { - name: (), - level_params: (), - kind: ixon::constant::DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Opaque, - lvls: 0, - ty: KExpr::sort(KUniv::zero()), - // mismatched value: Var(0) instead of Sort 0 - val: KExpr::var(0, ()), - lean_all: (), - block: KId::new(mk_addr("f"), ()), - }; - let e = const_congruent(&lci, &kc, &r).unwrap_err(); - assert!(e.contains("value")); - } - - #[test] - fn const_quot_matches_kind_free() { - // QuotInfo ↔ Quot must succeed regardless of the QuotKind variant. - let r = empty_resolver(); - let lci = env::ConstantInfo::QuotInfo(QuotVal { - cnst: ConstantVal { - name: mk_name("Quot"), - level_params: vec![mk_name("u")], - typ: env::Expr::sort(LL::succ(LL::zero())), - }, - kind: QuotKind::Type, - }); - let kc = KConst::::Quot { - name: (), - level_params: (), - kind: QuotKind::Type, - lvls: 1, - ty: KExpr::sort(KUniv::succ(KUniv::zero())), - }; - const_congruent(&lci, &kc, &r).unwrap(); - } - - #[test] - fn const_induct_param_count_mismatch_fails() { - let r = empty_resolver(); - let lci = env::ConstantInfo::InductInfo(InductiveVal { - cnst: ConstantVal { - name: mk_name("A"), - level_params: vec![], - typ: env::Expr::sort(LL::zero()), - }, - num_params: n(2), - num_indices: n(0), - all: vec![mk_name("A")], - ctors: vec![], - is_rec: false, - is_reflexive: false, - num_nested: bignat::Nat::from(0), - is_unsafe: false, - }); - let kc = KConst::::Indc { - name: (), - level_params: (), - params: 5, // wrong - indices: 0, - ctors: vec![], - lvls: 0, - ty: KExpr::sort(KUniv::zero()), - lean_all: (), - block: KId::new(mk_addr("A"), ()), - is_unsafe: false, - member_idx: 0, - }; - let e = const_congruent(&lci, &kc, &r).unwrap_err(); - assert!(e.contains("params")); - } - - #[test] - fn const_ctor_field_count_mismatch_fails() { - let r = empty_resolver(); - let lci = env::ConstantInfo::CtorInfo(ConstructorVal { - cnst: ConstantVal { - name: mk_name("A.mk"), - level_params: vec![], - typ: env::Expr::sort(LL::zero()), - }, - induct: mk_name("A"), - cidx: n(0), - num_params: n(0), - num_fields: n(3), - is_unsafe: false, - }); - let kc = KConst::::Ctor { - name: (), - level_params: (), - induct: KId::new(mk_addr("A"), ()), - cidx: 0, - params: 0, - fields: 7, // wrong - lvls: 0, - ty: KExpr::sort(KUniv::zero()), - is_unsafe: false, - }; - let e = const_congruent(&lci, &kc, &r).unwrap_err(); - assert!(e.contains("fields")); - } - - #[test] - fn const_rec_rule_count_mismatch_fails() { - let r = empty_resolver(); - let lci = env::ConstantInfo::RecInfo(RecursorVal { - cnst: ConstantVal { - name: mk_name("A.rec"), - level_params: vec![], - typ: env::Expr::sort(LL::zero()), - }, - all: vec![mk_name("A")], - num_params: n(0), - num_indices: n(0), - num_motives: n(1), - num_minors: n(1), - rules: vec![LeanRule { - ctor: mk_name("A.mk"), - n_fields: n(0), - rhs: env::Expr::sort(LL::zero()), - }], - k: false, - is_unsafe: false, - }); - let kc = KConst::::Recr { - name: (), - level_params: (), - params: 0, - indices: 0, - motives: 1, - minors: 1, - rules: vec![], // wrong: empty - k: false, - lvls: 0, - ty: KExpr::sort(KUniv::zero()), - block: KId::new(mk_addr("A"), ()), - member_idx: 0, - lean_all: (), - is_unsafe: false, - }; - let e = const_congruent(&lci, &kc, &r).unwrap_err(); - assert!(e.contains("rule count")); - } - - #[test] - fn const_rec_k_mismatch_fails() { - let r = empty_resolver(); - let lci = env::ConstantInfo::RecInfo(RecursorVal { - cnst: ConstantVal { - name: mk_name("A.rec"), - level_params: vec![], - typ: env::Expr::sort(LL::zero()), - }, - all: vec![], - num_params: n(0), - num_indices: n(0), - num_motives: n(1), - num_minors: n(0), - rules: vec![], - k: true, // lean says k - is_unsafe: false, - }); - let kc = KConst::::Recr { - name: (), - level_params: (), - params: 0, - indices: 0, - motives: 1, - minors: 0, - rules: vec![], - k: false, // zero says !k - lvls: 0, - ty: KExpr::sort(KUniv::zero()), - block: KId::new(mk_addr("A.rec"), ()), - member_idx: 0, - lean_all: (), - is_unsafe: false, - }; - let e = const_congruent(&lci, &kc, &r).unwrap_err(); - assert!(e.contains("k:")); - } - - #[test] - fn const_thm_and_opaque_match_via_defn_side() { - // Both ThmInfo and OpaqueInfo compare against KConst::Defn. - let r = empty_resolver(); - - let lthm = env::ConstantInfo::ThmInfo(TheoremVal { - cnst: ConstantVal { - name: mk_name("t"), - level_params: vec![], - typ: env::Expr::sort(LL::zero()), - }, - value: env::Expr::sort(LL::zero()), - all: vec![], - }); - let k = KConst::::Defn { - name: (), - level_params: (), - kind: ixon::constant::DefKind::Theorem, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Opaque, - lvls: 0, - ty: KExpr::sort(KUniv::zero()), - val: KExpr::sort(KUniv::zero()), - lean_all: (), - block: KId::new(mk_addr("t"), ()), - }; - const_congruent(<hm, &k, &r).unwrap(); - - let lop = env::ConstantInfo::OpaqueInfo(OpaqueVal { - cnst: ConstantVal { - name: mk_name("o"), - level_params: vec![], - typ: env::Expr::sort(LL::zero()), - }, - value: env::Expr::sort(LL::zero()), - is_unsafe: false, - all: vec![], - }); - const_congruent(&lop, &k, &r).unwrap(); - } -} diff --git a/crates/kernel/src/constant.rs b/crates/kernel/src/constant.rs deleted file mode 100644 index 5247954fd..000000000 --- a/crates/kernel/src/constant.rs +++ /dev/null @@ -1,247 +0,0 @@ -//! Constant declarations parameterized by `KernelMode`. -//! -//! Each variant carries structural fields plus metadata fields -//! (`name`, `level_params`, `lean_all`) for roundtrip fidelity in Meta mode. - -use ix_common::env::{DefinitionSafety, Name, QuotKind, ReducibilityHints}; -use ixon::constant::DefKind; - -use super::expr::KExpr; -use super::id::KId; -use super::mode::KernelMode; - -/// A recursor computation rule. -/// -/// `ctor` carries the Lean name of the constructor this rule dispatches on. -/// The kernel doesn't use it for dispatch (the positional `cidx` on -/// `KConst::Ctor` does), but we preserve it as a metadata field so LEON -/// ingress ↔ egress roundtrips the full `RecursorRule { ctor, n_fields, -/// rhs }` shape. In `Anon` mode the field is `()` and does not participate -/// in hashing or equality. -#[derive(Clone, Debug)] -pub struct RecRule { - pub ctor: M::MField, - pub fields: u64, - pub rhs: KExpr, -} - -/// A loaded constant. -/// -/// `member_idx` is representation metadata, not a reduction index. Inductive -/// ingress derives it from physical block traversal, anonymous recursor ingress -/// intentionally records zero, and kernel checking/WHNF do not branch on the -/// stored value. Constructor dispatch instead uses the validated `cidx`. -#[derive(Clone, Debug)] -pub enum KConst { - Defn { - name: M::MField, - level_params: M::MField>, - kind: DefKind, - safety: DefinitionSafety, - hints: ReducibilityHints, - lvls: u64, - ty: KExpr, - val: KExpr, - lean_all: M::MField>>, - block: KId, - }, - Recr { - name: M::MField, - level_params: M::MField>, - k: bool, - is_unsafe: bool, - lvls: u64, - params: u64, - indices: u64, - motives: u64, - minors: u64, - block: KId, - member_idx: u64, - ty: KExpr, - rules: Vec>, - lean_all: M::MField>>, - }, - Axio { - name: M::MField, - level_params: M::MField>, - is_unsafe: bool, - lvls: u64, - ty: KExpr, - }, - Quot { - name: M::MField, - level_params: M::MField>, - kind: QuotKind, - lvls: u64, - ty: KExpr, - }, - Indc { - name: M::MField, - level_params: M::MField>, - lvls: u64, - params: u64, - indices: u64, - is_unsafe: bool, - block: KId, - member_idx: u64, - ty: KExpr, - ctors: Vec>, - lean_all: M::MField>>, - }, - Ctor { - name: M::MField, - level_params: M::MField>, - is_unsafe: bool, - lvls: u64, - induct: KId, - cidx: u64, - params: u64, - fields: u64, - ty: KExpr, - }, -} - -impl KConst { - pub fn ty(&self) -> &KExpr { - match self { - KConst::Defn { ty, .. } - | KConst::Recr { ty, .. } - | KConst::Axio { ty, .. } - | KConst::Quot { ty, .. } - | KConst::Indc { ty, .. } - | KConst::Ctor { ty, .. } => ty, - } - } - - pub fn lvls(&self) -> u64 { - match self { - KConst::Defn { lvls, .. } - | KConst::Recr { lvls, .. } - | KConst::Axio { lvls, .. } - | KConst::Quot { lvls, .. } - | KConst::Indc { lvls, .. } - | KConst::Ctor { lvls, .. } => *lvls, - } - } - - pub fn name(&self) -> &M::MField { - match self { - KConst::Defn { name, .. } - | KConst::Recr { name, .. } - | KConst::Axio { name, .. } - | KConst::Quot { name, .. } - | KConst::Indc { name, .. } - | KConst::Ctor { name, .. } => name, - } - } - - pub fn level_params(&self) -> &M::MField> { - #[allow(unreachable_patterns)] - match self { - KConst::Defn { level_params, .. } - | KConst::Recr { level_params, .. } - | KConst::Axio { level_params, .. } - | KConst::Quot { level_params, .. } - | KConst::Indc { level_params, .. } - | KConst::Ctor { level_params, .. } => level_params, - } - } -} - -#[cfg(test)] -mod tests { - use super::super::expr::KExpr; - use super::super::id::KId; - use super::super::level::KUniv; - use super::super::mode::Anon; - use super::*; - use ix_common::address::Address; - use ix_common::env::{DefinitionSafety, QuotKind, ReducibilityHints}; - use ixon::constant::DefKind; - - fn sort0() -> KExpr { - KExpr::sort(KUniv::zero()) - } - fn mk_addr(s: &str) -> Address { - Address::hash(s.as_bytes()) - } - - #[test] - fn axio_accessors() { - let c = KConst::::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 2, - ty: sort0(), - }; - assert_eq!(c.lvls(), 2); - assert_eq!(*c.name(), ()); - assert_eq!(*c.level_params(), ()); - assert!(matches!(c.ty().data(), super::super::expr::ExprData::Sort(..))); - } - - #[test] - fn defn_accessors() { - let c = KConst::::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(5), - lvls: 1, - ty: sort0(), - val: sort0(), - lean_all: (), - block: KId::new(mk_addr("block"), ()), - }; - assert_eq!(c.lvls(), 1); - } - - #[test] - fn quot_accessors() { - let c = KConst::::Quot { - name: (), - level_params: (), - kind: QuotKind::Type, - lvls: 1, - ty: sort0(), - }; - assert_eq!(c.lvls(), 1); - } - - #[test] - fn ctor_accessors() { - let c = KConst::::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: KId::new(mk_addr("Nat"), ()), - cidx: 0, - params: 0, - fields: 0, - ty: sort0(), - }; - assert_eq!(c.lvls(), 0); - } - - #[test] - fn indc_accessors() { - let c = KConst::::Indc { - name: (), - level_params: (), - lvls: 0, - params: 2, - indices: 0, - is_unsafe: false, - block: KId::new(mk_addr("block"), ()), - member_idx: 0, - ty: sort0(), - ctors: vec![], - lean_all: (), - }; - assert_eq!(c.lvls(), 0); - assert!(matches!(c, KConst::Indc { params: 2, .. })); - } -} diff --git a/crates/kernel/src/contract_tests.rs b/crates/kernel/src/contract_tests.rs deleted file mode 100644 index a339cdf3d..000000000 --- a/crates/kernel/src/contract_tests.rs +++ /dev/null @@ -1,377 +0,0 @@ -//! Host conversion and reconstruction checks; resource validity is separate. - -use crate::env::{InternTable, KEnv}; -use crate::expr::{ExprData, FVarId, KExpr}; -use crate::level::KUniv; -use crate::mode::{Anon, KernelMode, Meta}; -use crate::subst::{ - abstract_fvars, instantiate_rev, lift, simul_subst, subst, subst_no_intern, -}; -use crate::tc::TypeChecker; -use ix_common::env::{BinderInfo, Name}; -use ixon::contract::{BinderContract, LetContract, LetKind, ValueContract}; -use ixon::expr::Uses; - -fn name() -> M::MField { - M::meta_field(Name::anon()) -} -fn bi() -> M::MField { - M::meta_field(BinderInfo::Default) -} -fn input(code: u8) -> BinderContract { - BinderContract::from_bits(code).unwrap() -} -fn sort() -> KExpr { - KExpr::sort(KUniv::zero()) -} -fn var(i: u64) -> KExpr { - KExpr::var(i, name::()) -} -fn lam(c: BinderContract, body: KExpr) -> KExpr { - KExpr::lam_contract(name::(), bi::(), c, sort(), body) -} -fn all( - c: BinderContract, - r: ValueContract, - body: KExpr, -) -> KExpr { - KExpr::all_contract(name::(), bi::(), c, r, sort(), body) -} - -fn identity_and_conversion() { - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - let lambdas: Vec<_> = - (0..16).map(|c| tc.intern(lam(input(c), var(0)))).collect(); - let pis: Vec<_> = (0..16) - .map(|c| { - (0..4) - .map(|r| { - tc.intern(all(input(c), ValueContract::from_bits(r).unwrap(), sort())) - }) - .collect::>() - }) - .collect(); - for a in 0..16 { - for b in 0..16 { - assert_eq!(lambdas[a] == lambdas[b], a == b); - assert_eq!(lambdas[a].addr() == lambdas[b].addr(), a == b); - assert_eq!(tc.is_def_eq(&lambdas[a], &lambdas[b]).unwrap(), a == b); - for r in 0..4 { - assert_eq!(pis[a][0] == pis[b][r], a == b && r == 0); - assert_eq!(pis[a][0].addr() == pis[b][r].addr(), a == b && r == 0); - for (left, right) in - [(&pis[a][0], &pis[b][r]), (&pis[b][r], &pis[a][0])] - { - assert_eq!(tc.is_def_eq(left, right).unwrap(), a == b); - } - } - } - } - // Canonical reuse of a default constructor still meets the explicit payload. - let default_lam = - tc.intern(KExpr::lam(name::(), bi::(), sort(), var(0))); - assert_eq!(default_lam.addr(), lambdas[7].addr()); - let default_pi = - tc.intern(KExpr::all(name::(), bi::(), sort(), sort())); - assert_eq!(default_pi.addr(), pis[7][1].addr()); - let mut lets = Vec::new(); - for non_dep in [false, true] { - for kind in [LetKind::Value, LetKind::BorrowShared] { - for c in 0..16 { - lets.push(tc.intern(KExpr::let_contract( - name::(), - LetContract { non_dep, kind, binder: input(c) }, - sort(), - sort(), - var(0), - ))); - } - } - } - // Structural identity covers every wire scalar, independently of whether - // a separate resource check would admit a particular borrow contract. - for (i, a) in lets.iter().enumerate() { - for (j, b) in lets.iter().enumerate() { - assert_eq!(a == b, i == j); - assert_eq!(a.addr() == b.addr(), i == j); - } - } -} - -#[test] -fn contract_identity_and_conversion_all_scalar_codes_both_modes() { - identity_and_conversion::(); - identity_and_conversion::(); -} - -fn telescope( - contracts: &[BinderContract], - lambda: bool, - result: ValueContract, -) -> KExpr { - let mut out = if lambda { var(0) } else { sort() }; - for c in contracts.iter().rev() { - out = if lambda { lam(*c, out) } else { all(*c, result, out) }; - } - out -} - -fn inference_and_batches() { - for n in [1, 2, 3, 4, 5] { - let contracts: Vec<_> = (0..n).map(|i| input((i * 3 + 1) % 16)).collect(); - let value = telescope::(&contracts, true, ValueContract::unique()); - let expected = telescope::(&contracts, false, ValueContract::shared()); - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - assert_eq!(tc.infer(&value).unwrap(), expected); - assert_eq!(tc.infer(&value).unwrap(), expected); - assert!(tc.lctx.is_empty()); - for single in [false, true] { - for lambda in [false, true] { - for index in [0, usize::from(n - 1)] { - let mut changed = contracts.clone(); - changed[index] = input(changed[index].to_bits() ^ 4); - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - tc.in_binder_batch = single; - let original = - tc.intern(telescope(&contracts, lambda, ValueContract::shared())); - let mismatch = - tc.intern(telescope(&changed, lambda, ValueContract::unique())); - assert!(!tc.is_def_eq(&original, &mismatch).unwrap()); - assert!(!tc.is_def_eq(&mismatch, &original).unwrap()); - if !lambda { - let result_only = - tc.intern(telescope(&contracts, false, ValueContract::unique())); - assert!(tc.is_def_eq(&original, &result_only).unwrap()); - } - assert!(tc.lctx.is_empty()); - } - } - } - } -} - -#[test] -fn inference_and_single_batched_conversion_preserve_input_contracts() { - inference_and_batches::(); - inference_and_batches::(); -} - -fn eta() { - for r in 0..4 { - for c in [5, 7, 13] { - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - let ty = all(input(c), ValueContract::from_bits(r).unwrap(), sort()); - let (_, f, _) = - tc.open_binder_with_fv(name::(), bi::(), ty, &var(0)); - let body = KExpr::app(f.clone(), var(0)); - let good = lam(input(c), body.clone()); - let bad = lam(input(c ^ 4), body); - for (left, right) in [(&f, &good), (&good, &f)] { - assert!(tc.is_def_eq(left, right).unwrap()); - } - for (left, right) in [(&f, &bad), (&bad, &f)] { - assert!(!tc.is_def_eq(left, right).unwrap()); - } - } - } -} - -#[test] -fn eta_checks_input_and_ignores_result_both_directions() { - eta::(); - eta::(); -} - -// An outer free variable occurs at every binder depth, forcing all rebuilders. -fn nested(leaf: impl Fn(u64) -> KExpr) -> KExpr { - KExpr::lam_contract( - name::(), - bi::(), - input(5), - leaf(0), - KExpr::all_contract( - name::(), - bi::(), - input(9), - ValueContract::local_unique(), - leaf(1), - KExpr::let_contract( - name::(), - LetContract::borrow(true, Uses::Affine), - leaf(2), - leaf(2), - leaf(3), - ), - ), - ) -} - -fn rebuilds() { - let mut intern = InternTable::::new(); - let open = nested(var); - let expected = nested(|_| sort()); - assert_eq!(subst(&mut intern, &open, &sort(), 0), expected); - assert_eq!(subst_no_intern(&open, &sort(), 0), expected); - assert_eq!(simul_subst(&mut intern, &open, &[sort()], 0), expected); - assert_eq!(instantiate_rev(&mut intern, &open, &[sort()]), expected); - assert_eq!(lift(&mut intern, &open, 2, 0), nested(|i| var(i + 2))); - let id = FVarId(1234); - let free = nested(|_| KExpr::fvar(id, name::())); - assert_eq!(abstract_fvars(&mut intern, &free, &[id]), open); - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - let param = nested(|_| KExpr::sort(KUniv::param(0, name::()))); - assert_eq!( - tc.instantiate_univ_params(¶m, &[KUniv::zero()]).unwrap(), - expected - ); - // The closure machine must read back an unapplied Lam/All with its payload. - for lambda in [false, true] { - let inner = if lambda { - lam(input(5), var(1)) - } else { - all(input(9), ValueContract::local_unique(), var(1)) - }; - let applied = KExpr::app(lam(input(7), inner), sort()); - let reduced = tc.whnf(&applied).unwrap(); - let expected = if lambda { - lam(input(5), sort()) - } else { - all(input(9), ValueContract::local_unique(), sort()) - }; - assert_eq!(reduced, expected); - } -} - -#[test] -fn substitutions_universes_and_closure_readback_preserve_contracts() { - rebuilds::(); - rebuilds::(); -} - -fn locals() { - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - let (a, a_id) = tc.open_binder_contract( - name::(), - bi::(), - input(9), - ValueContract::local_unique(), - sort(), - &var(0), - ); - let (x, x_id) = tc.open_binder_contract( - name::(), - bi::(), - input(5), - ValueContract::unique(), - a.clone(), - &var(0), - ); - let contract = LetContract::borrow(true, Uses::Linear); - let (body, y_id) = tc.open_let_contract(name::(), contract, a, x, &var(0)); - let ids = [a_id, x_id, y_id]; - for lambda in [false, true] { - let closed = if lambda { - tc.lctx.mk_lambda(&mut tc.env.intern, &ids, &body) - } else { - tc.lctx.mk_pi(&mut tc.env.intern, &ids, &body) - }; - assert!(!closed.has_fvars()); - let inner = - KExpr::let_contract(name::(), contract, var(1), var(0), var(0)); - let expected = if lambda { - KExpr::lam_contract( - name::(), - bi::(), - input(9), - sort(), - KExpr::lam_contract(name::(), bi::(), input(5), var(0), inner), - ) - } else { - KExpr::all_contract( - name::(), - bi::(), - input(9), - ValueContract::local_unique(), - sort(), - KExpr::all_contract( - name::(), - bi::(), - input(5), - ValueContract::unique(), - var(0), - inner, - ), - ) - }; - assert_eq!(closed, expected); - } - let plain = tc.intern(KExpr::let_contract( - name::(), - LetContract::plain(true), - sort(), - sort(), - var(0), - )); - let borrowed = tc.intern(KExpr::let_contract( - name::(), - contract, - sort(), - sort(), - var(0), - )); - assert_ne!(plain.addr(), borrowed.addr()); - assert_ne!(plain, borrowed); - assert!(tc.is_def_eq(&plain, &borrowed).unwrap()); - assert!( - matches!(borrowed.data(), ExprData::Let(_, _, _, _, c, _) if *c == contract) - ); -} - -#[test] -fn local_open_close_preserves_dependent_domains_results_and_let_payload() { - locals::(); - locals::(); -} - -#[test] -fn direct_source_ingress_preserves_frames_and_refuses_wrong_attachment() { - use crate::ingress::lean_expr_to_zexpr_with_kenv; - use ix_common::env::{Expr as Source, Level}; - use ixon::contract::source::{decode, egress_layers}; - let mut env = KEnv::::new(); - let prop = Source::sort(Level::zero()); - let binder = - Source::all(Name::anon(), prop.clone(), prop.clone(), BinderInfo::Default); - for code in 0..64 { - let contract = decode(1, code).unwrap(); - let framed = contract.attach(binder.clone()); - let kernel = - lean_expr_to_zexpr_with_kenv(&framed, &[], &mut env, None, None).unwrap(); - assert_eq!(kernel.source_contract(), Some(contract)); - assert_eq!( - egress_layers(kernel.source_contract(), kernel.mdata()).unwrap(), - vec![contract.metadata()] - ); - let warm = - lean_expr_to_zexpr_with_kenv(&framed, &[], &mut env, None, None).unwrap(); - assert_eq!(warm.addr(), kernel.addr()); - } - let frame = decode(1, 21).unwrap(); - for wrong in [prop, frame.attach(binder)] { - let error = lean_expr_to_zexpr_with_kenv( - &frame.attach(wrong), - &[], - &mut env, - None, - None, - ) - .unwrap_err(); - assert!(error.contains("not attached to its original binder"), "{error}"); - } -} diff --git a/crates/kernel/src/def_eq.rs b/crates/kernel/src/def_eq.rs deleted file mode 100644 index 1bf5d102e..000000000 --- a/crates/kernel/src/def_eq.rs +++ /dev/null @@ -1,2951 +0,0 @@ -//! Definitional equality checking. -//! -//! Multi-tier strategy following lean4lean: -//! 1. Quick structural (same constructor, same children) -//! 2. WHNF without delta, quick structural -//! 3. Proof irrelevance (before delta) -//! 4. Iterative lazy delta with same-head-spine optimization -//! 5. Full WHNF, structural comparison, eta, struct eta - -use ixon::constant::DefKind; - -use super::constant::KConst; -use super::env::Addr; -use super::error::{TcError, u64_to_usize}; -use super::expr::{ExprData, KExpr}; -use super::id::KId; -use super::lctx::LocalDecl; -use super::level::{KUniv, univ_eq}; -use super::mode::KernelMode; -use super::subst::{instantiate_rev, lift}; -use super::tc::{ - MAX_DEF_EQ_DEPTH, MAX_WHNF_FUEL, TypeChecker, app_head, borrow_app_spine, - collect_app_spine, -}; -use super::whnf::PrimFamily; - -mod application; -mod binders; -mod speculation; -pub(crate) use speculation::SameHeadBackoff; -#[cfg(test)] -mod same_head_tests; - -/// 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` -/// to watch all `Int.bmod`-involving comparisons). Prints `[deq] a b` -/// before entering `is_def_eq_inner`, then the boolean outcome. Useful for -/// pinning down which sub-expression of an App-spine is stuck. -static IX_DEF_EQ_TRACE: crate::EnvString = - crate::EnvString::new(|| crate::env_var("IX_DEF_EQ_TRACE").ok()); - -/// Global perf counter: total `is_def_eq` entries across all checks. -/// When `IX_DEF_EQ_COUNT_LOG=1`, logs every 1M calls. Useful for -/// detecting checks that explode into millions of recursive -/// comparisons \u2014 a signal that some caching optimization is -/// mis-firing or some reduction is looping. -static IX_DEF_EQ_COUNT_LOG: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_DEF_EQ_COUNT_LOG").is_ok()); - -/// Dump the expression pair when `is_def_eq` hits its recursion/fuel guard. -/// The optional env var value is used as a substring filter over the two head -/// constants; an empty value dumps every guard hit. -static IX_DEF_EQ_MAX_DUMP: crate::EnvString = - crate::EnvString::new(|| crate::env_var("IX_DEF_EQ_MAX_DUMP").ok()); - -/// Print at most 96 cache-missing pairs near the depth guard. Identity and -/// mode/context fields distinguish a repeated state from a long descent. -static IX_DEF_EQ_NEAR_GUARD: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_DEF_EQ_NEAR_GUARD").is_ok()); -static NEAR_GUARD_COUNT: std::sync::atomic::AtomicUsize = - std::sync::atomic::AtomicUsize::new(0); - -static IX_ETA_TRACE: crate::EnvString = - crate::EnvString::new(|| crate::env_var("IX_ETA_TRACE").ok()); - -static IX_PROJ_DELTA_TRACE: crate::EnvString = - crate::EnvString::new(|| crate::env_var("IX_PROJ_DELTA_TRACE").ok()); - -static DEF_EQ_COUNT: std::sync::atomic::AtomicUsize = - std::sync::atomic::AtomicUsize::new(0); - -/// Same-head comparisons are speculative for every reducibility hint: a miss -/// must not consume the entire constant-check budget before ordinary -/// delta reduction gets its turn. -const SAME_HEAD_SPECULATION_ATTEMPT_FUEL: u64 = 4_096; -/// Regular congruence can profitably compare larger arguments (a Mathlib -/// regression needs about 76k fuel). Leave headroom without allowing a single -/// speculative comparison to consume the entire 100M check allowance. -const SAME_HEAD_REGULAR_ATTEMPT_FUEL: u64 = 131_072; -/// Only non-Regular probes have a per-constant startup window. Regular -/// congruence remains useful throughout a check, but each attempt is bounded. -const SAME_HEAD_SPECULATION_START_FUEL: u64 = 16_384; - -/// Try comparing the requested fields before comparing whole records, without -/// letting an unsuccessful probe starve the ordinary conversion algorithm. -const PROJECTION_PROBE_FUEL: u64 = 4_096; - -/// Step journal (`IX_STEP_TRACE=1`): one `[deq] ~ ` line -/// per `is_def_eq` entry (plus `[whnf+]` lines in whnf.rs), mirroring the -/// Lean kernel's `IX_TC_STEP_TRACE` journal (`Ix.Tc` / `TcM.stepTrace`). -/// Diffing the two sequences localizes a behavioral divergence at the -/// first fork (workflow in `Ix/Tc/ParCheck.lean`). Unscoped by design — -/// pair with a seeded single-constant run (`--consts `). -pub(crate) static IX_STEP_TRACE: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_STEP_TRACE").is_ok()); - -impl TypeChecker<'_, M> { - /// Check definitional equality of two expressions. - pub fn is_def_eq( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result> { - self.check_input_context()?; - 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 { - log::info!("[is_def_eq] count={n}"); - } - } - crate::profile::bump_def_eq(); - if *IX_STEP_TRACE { - eprintln!( - "[deq] {} {} ~ {}", - self.fuel_used(), - a.hash_key(), - b.hash_key(), - ); - } - if a.ptr_eq(b) { - return Ok(true); - } - if a.hash_key() == b.hash_key() { - // Hashes are alpha-invariant in both `Anon` and `Meta` modes — see - // `KExpr::lam_hash` etc., which deliberately omit binder `name`/ - // `bi`/`mdata` from the content hash. So hash equality is the only - // structural alpha-equivalence fast-path we need; an earlier - // additional `compare_kexpr` call here was redundant. - return Ok(true); - } - - // Diagnostic trace: emit a `[deq]` line when either side's head - // constant name contains the configured substring. Keeps output - // manageable — a naive unconditional trace blows out the log. - let trace_active = if let Some(prefix) = IX_DEF_EQ_TRACE.as_ref() { - 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 { - log::info!( - "[deq] depth={} a={}", - self.def_eq_depth, - compact_def_eq_expr(a) - ); - log::info!( - "[deq] depth={} b={}", - self.def_eq_depth, - compact_def_eq_expr(b) - ); - true - } else { - false - } - } else { - false - }; - if trace_active { - self.def_eq_trace_depth += 1; - } - - // Context-aware EquivManager/cache. Closed pairs use the empty context; - // open pairs use only the context suffix reachable from the compared - // expressions. This matches the WHNF/infer cache shape and avoids - // rechecking the same small open pair under many irrelevant outer - // binders in large proof terms. - // - // Build `a_key` and `b_key` ONCE and reuse them throughout. - // `is_equiv` and `find_root_key` take by reference (see - // `src/ix/kernel/equiv.rs`), so no additional key construction is paid - // per method call. Any true result moves the originals into `add_equiv` - // before returning. - let eq_lbr = a.lbr().max(b.lbr()); - let eq_ctx = self.def_eq_ctx_key(a, b); - let a_key = crate::equiv::EqKey::new(a.hash_key(), eq_ctx, eq_lbr, a.lbr()); - let b_key = crate::equiv::EqKey::new(b.hash_key(), eq_ctx, eq_lbr, b.lbr()); - - if self.equiv_manager.is_equiv(&a_key, &b_key) { - return Ok(true); - } - - let (lo, hi) = canonical_pair(a.hash_key(), b.hash_key()); - let cache_key = (lo, hi, eq_ctx); - let cheap_mode = self.cheap_recursion_depth > 0; - if let Some(cached) = self.env.def_eq_cache.get(&cache_key).copied() { - if cheap_mode { - self.env.def_eq_cheap_cache.insert(cache_key, cached); - } - if cached { - self.equiv_manager.add_equiv(a_key, b_key); - } - self.env.perf.record_def_eq_hit(); - return Ok(cached); - } - if cheap_mode - && let Some(cached) = self.env.def_eq_cheap_cache.get(&cache_key).copied() - { - if cached { - self.env.def_eq_cache.insert(cache_key, true); - self.equiv_manager.add_equiv(a_key, b_key); - } - self.env.perf.record_def_eq_hit(); - return Ok(cached); - } - - // Equiv-root second-chance: if (a,b) not cached, try (root(a), root(b)). - if let (Some(a_root), Some(b_root)) = ( - self.equiv_manager.find_root_key(&a_key), - self.equiv_manager.find_root_key(&b_key), - ) && (a_root != a_key || b_root != b_key) - && crate::equiv::EqKey::root_cache_scope_matches( - &a_root, &b_root, eq_ctx, eq_lbr, - ) - { - let (rlo, rhi) = canonical_pair(a_root.expr_addr, b_root.expr_addr); - let root_cache_key = (rlo, rhi, eq_ctx); - let mut cached = - self.env.def_eq_cache.get(&root_cache_key).map(|v| (*v, false)); - if cached.is_none() && cheap_mode { - cached = - self.env.def_eq_cheap_cache.get(&root_cache_key).map(|v| (*v, true)); - } - if let Some((cached, from_cheap_cache)) = cached { - if from_cheap_cache { - self.env.def_eq_cheap_cache.insert(cache_key, cached); - if cached { - self.env.def_eq_cache.insert(cache_key, true); - } - } else { - self.env.def_eq_cache.insert(cache_key, cached); - if cheap_mode { - self.env.def_eq_cheap_cache.insert(cache_key, cached); - } - } - if cached { - self.equiv_manager.add_equiv(a_key, b_key); - } - self.env.perf.record_def_eq_hit(); - return Ok(cached); - } - } - // Both probes missed. - self.env.perf.record_def_eq_miss(); - self.record_hot_def_eq_miss(a, b); - - if *IX_DEF_EQ_NEAR_GUARD - && self.def_eq_depth >= MAX_DEF_EQ_DEPTH.saturating_sub(32) - && self.debug_label_matches_env() - && NEAR_GUARD_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed) - < 96 - { - eprintln!( - "[deq near guard] depth={} local={} ctx={} cheap={} infer_only={} eager={} a={} {} b={} {}", - self.def_eq_depth, - self.depth(), - eq_ctx, - self.cheap_recursion_depth, - self.infer_only, - self.eager_reduce, - a.hash_key(), - compact_def_eq_expr(a), - b.hash_key(), - compact_def_eq_expr(b), - ); - } - - // Charge recursive fuel only after the O(1) exits above. Large proof - // terms can perform hundreds of thousands of pointer/equiv/cache hits; - // those should not consume the same budget as an actual comparison. - if self.rec_fuel == 0 && IX_DEF_EQ_MAX_DUMP.is_some() { - self.dump_def_eq_rec_fuel(a, b); - } - self.tick()?; - - self.def_eq_depth += 1; - if self.def_eq_depth > self.def_eq_peak { - self.def_eq_peak = self.def_eq_depth; - } - if self.def_eq_depth > MAX_DEF_EQ_DEPTH { - self.dump_guard_stack("def-eq-depth"); - self.def_eq_depth -= 1; - self.dump_def_eq_max("depth", a, b, None, None); - return Err(TcError::MaxRecDepth); - } - - let result = self.is_def_eq_inner(a, b); - self.def_eq_depth -= 1; - - let ok = result?; - // Some optional reducers treat an inner error as a miss. Do not let an - // exhausted speculative slice escape that way as a cached inequality. - if !ok && self.rec_fuel == 0 { - return Err(TcError::MaxRecFuel); - } - if trace_active { - log::info!( - "[deq] depth={} -> {} ({})", - self.def_eq_depth, - ok, - if ok { "OK" } else { "FAIL" } - ); - // 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 { - 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); - } - if ok { - // Move the up-front `a_key` / `b_key` directly into `add_equiv`. - // - // SOUNDNESS: cheap-mode `true` is monotone (cheap-equal implies - // FULL-equal), so it may be recorded as a local equivalence. WHNF - // caches deliberately do not consult these equivalence roots; they are - // only a def-eq shortcut. - self.equiv_manager.add_equiv(a_key, b_key); - } - // SOUNDNESS: cheap-mode WHNF can leave projections stuck where FULL - // would reduce, causing `is_def_eq` to return `false` - // for terms FULL would judge equal. Caching such a cheap-mode `false` - // would let a later FULL-mode caller hit the poisoned key and - // short-circuit before doing the actual comparison. - // - // Cheap-mode `true` is monotone-sound to cache: cheap WHNF leaves - // terms less-reduced, so any pair found equal at the cheap level is - // also equal at the FULL level (further reduction preserves equality). - // Caching cheap `true` is also performance-critical — without it, - // heavy proof terms recompute the same comparisons inside lazy delta - // and blow past `MAX_DEF_EQ_DEPTH`. - // - // The depth counter is bumped by the def-eq WHNF helpers in `whnf.rs`. - // Any `is_def_eq` call inside a cheap reduction observes `cheap_mode` - // and records cheap `false` only in `def_eq_cheap_cache`. - if cheap_mode { - self.env.def_eq_cheap_cache.insert(cache_key, ok); - if ok { - self.env.def_eq_cache.insert(cache_key, true); - } - } else { - self.env.def_eq_cache.insert(cache_key, ok); - } - Ok(ok) - } - - fn is_def_eq_inner( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result> { - // Tier 1: quick structural - if self.quick_def_eq(a, b)? { - return Ok(true); - } - - // Tier 1b: Eager Bool reduction (lean4 type_checker.cpp:1066) - // If one side is Bool.true and the other has no free variables (or - // eagerReduce is active), try full WHNF. Critical for Decidable/decide-based - // definitions. - if self.is_bool_true(b) && (!a.has_fvars() || self.eager_reduce) { - let wa = self.whnf(a)?; - if self.is_bool_true(&wa) { - return Ok(true); - } - } else if self.is_bool_true(a) && (!b.has_fvars() || self.eager_reduce) { - let wb = self.whnf(b)?; - if self.is_bool_true(&wb) { - return Ok(true); - } - } - - // Tier 1c: String literal expansion (before any WHNF). - // Expand string literals to String.ofList [Char.ofNat c₁, ...] form so - // both sides can reduce in lockstep through lazy delta. Must happen before - // WHNF to avoid committing the other side to a structural form that - // diverges from the expansion. - if matches!(a.data(), ExprData::Str(..)) - || matches!(b.data(), ExprData::Str(..)) - { - if self.try_string_lit_expansion(a, b)? { - return Ok(true); - } - if self.try_string_lit_expansion(b, a)? { - return Ok(true); - } - } - - // Tier 1d: Lean-style structural WHNF for def-eq. This uses cheap - // projections so `a.i =?= b.i` first has a chance to compare `a =?= b` - // before unfolding definitions hidden behind each projection. - let ca = self.whnf_core_for_def_eq(a)?; - let cb = self.whnf_core_for_def_eq(b)?; - if ca.ptr_eq(&cb) { - return Ok(true); - } - if self.quick_def_eq(&ca, &cb)? { - return Ok(true); - } - // Ix's no-delta layer also contains primitive/native reductions needed - // by the existing kernel model. Keep cheap projection behavior here, but - // do not expose this as a public WHNF mode. - let mut wa = self.whnf_no_delta_for_def_eq(a)?; - let mut wb = self.whnf_no_delta_for_def_eq(b)?; - if wa.ptr_eq(&wb) { - return Ok(true); - } - if self.quick_def_eq(&wa, &wb)? { - return Ok(true); - } - - // Tier 3: proof irrelevance (before delta) - if self.try_proof_irrel(&wa, &wb)? { - return Ok(true); - } - - if let Some(result) = self.def_eq_lazy_delta(a, b, &mut wa, &mut wb)? { - return Ok(result); - } - - if self.def_eq_trace_depth > 0 { - 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) - if self.try_structural_congruence(&wa, &wb)? { - return Ok(true); - } - - // Tier 4c: second structural pass (lean4lean:683-686, lean4 - // type_checker.cpp:1109-1110). This is deliberately `whnfCore`, not full - // `whnf`: full WHNF would delta-unfold stuck open primitives such as - // `Nat.ble` and can literally walk enormous Nat literals in their - // recursive logical models. - let wa_core = self.whnf_core(&wa)?; - let wb_core = self.whnf_core(&wb)?; - let wa_changed = - !wa_core.ptr_eq(&wa) && wa_core.hash_key() != wa.hash_key(); - let wb_changed = - !wb_core.ptr_eq(&wb) && wb_core.hash_key() != wb.hash_key(); - if wa_changed || wb_changed { - return self.is_def_eq(&wa_core, &wb_core); - } - let wa = wa_core; - let wb = wb_core; - if wa.ptr_eq(&wb) { - return Ok(true); - } - if self.quick_def_eq(&wa, &wb)? { - return Ok(true); - } - - // Tier 4d: app spine comparison (lean4lean isDefEqApp, lean4 type_checker.cpp:1115) - if self.try_def_eq_app(&wa, &wb)? { - return Ok(true); - } - - let result = self.is_def_eq_whnf(&wa, &wb); - - // Tier 5 final-fail trace: when IX_DEF_EQ_TIER5_DUMP is set and the - // pair's head names contain the configured substring, dump the - // post-whnfCore wa/wb. This is where lazy-delta + Tier 4c gave up. - if let Ok(prefix) = crate::env_var("IX_DEF_EQ_TIER5_DUMP") - && let Ok(false) = result.as_ref() - { - 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 { - log::info!("[deq tier5 fail] depth={}", self.def_eq_depth); - log::info!(" wa: {wa}"); - log::info!(" wb: {wb}"); - } - } - - result - } - - /// Keep lazy-delta temporaries out of the native frame retained while - /// recursively comparing irreducible applications. In unoptimized builds - /// those temporaries otherwise occupy many KiB at EVERY application level, - /// even when neither head can unfold. Reduction order/fuel are unchanged. - #[inline] - fn def_eq_lazy_delta( - &mut self, - a: &KExpr, - b: &KExpr, - wa: &mut KExpr, - wb: &mut KExpr, - ) -> Result, TcError> { - // Tier 4: iterative lazy delta (lean4lean lazyDeltaReduction) - let mut fuel = MAX_WHNF_FUEL; - loop { - if fuel == 0 { - self.dump_guard_stack("def-eq-lazy-delta-fuel"); - self.dump_def_eq_max("fuel", a, b, Some(wa), Some(wb)); - return Err(TcError::MaxRecDepth); - } - fuel -= 1; - - // M2: Nat offset reduction at top of loop (lean4lean isDefEqOffset) - if let Some(result) = self.try_def_eq_offset(wa, wb)? { - return Ok(Some(result)); - } - - // Nat primitive reduction inside lazy delta. Mirrors lean4 - // (`refs/lean4/src/kernel/type_checker.cpp:978-984`) and lean4lean - // (`refs/lean4lean/Lean4Lean/TypeChecker.lean:619`): skip Nat - // 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 fam_a == PrimFamily::Nat - && let Some(wa2) = self.try_reduce_nat(wa)? - { - return self.is_def_eq(&wa2, wb).map(Some); - } - if fam_b == PrimFamily::Nat - && let Some(wb2) = self.try_reduce_nat(wb)? - { - return self.is_def_eq(wa, &wb2).map(Some); - } - } - - // Reference order is `is_def_eq_offset → reduce_nat (gated) → delta` - // (lean4 `type_checker.cpp:986-991`, lean4lean - // `TypeChecker.lean:625-628`), without their native evaluation, which - // is outside the checked theory. Ix-specific `try_reduce_decidable` - // runs after the Nat step to keep the reference-aligned segment tight. - if fam_a == PrimFamily::Decidable - && let Some(wa2) = self.try_reduce_decidable(wa)? - { - return self.is_def_eq(&wa2, wb).map(Some); - } - if fam_b == PrimFamily::Decidable - && let Some(wb2) = self.try_reduce_decidable(wb)? - { - return self.is_def_eq(wa, &wb2).map(Some); - } - - let a_head = head_const_id(wa); - let b_head = head_const_id(wb); - let a_delta = match &a_head { - Some(h) => self.is_delta(h)?, - None => false, - }; - let b_delta = match &b_head { - Some(h) => self.is_delta(h)?, - None => false, - }; - - if !a_delta && !b_delta { - break; - } - - // C6: Before unfolding a definition, try reducing projection apps - // on the non-definition side (lean4lean tryUnfoldProjApp). - if a_delta && !b_delta { - if let Some(wb2) = self.try_unfold_proj_app(wb)? { - *wb = wb2; - continue; - } - } else if b_delta - && !a_delta - && let Some(wa2) = self.try_unfold_proj_app(wa)? - { - *wa = wa2; - continue; - } - - if a_delta && b_delta { - // Both `a_delta` and `b_delta` already imply a present head, so the - // `map_or` defaults are dead code in practice. We keep the - // "missing-head ranks above all real ranks" semantic by mapping the - // None case to `(u8::MAX, u32::MAX)` — preserving the old `u32::MAX` - // sentinel under the new tuple-based comparator. - let wa_w = match &a_head { - Some(h) => self.def_rank_id(h)?, - None => (u8::MAX, u32::MAX), - }; - let wb_w = match &b_head { - Some(h) => self.def_rank_id(h)?, - None => (u8::MAX, u32::MAX), - }; - - if wa_w == wb_w { - // H2: Same-head congruence is sound for every hint, but it is only - // a sufficient condition: unfolding may erase unequal arguments. - // Bound speculation and cache only inconclusive/rejected probes, - // never an inequality of the enclosing applications. - if let (Some(ah), Some(bh)) = (&a_head, &b_head) - && ah.addr == bh.addr - { - let (lo, hi) = canonical_pair(wa.hash_key(), wb.hash_key()); - let failure_key = (lo, hi, self.def_eq_ctx_key(wa, wb)); - if !self.env.def_eq_failure.contains(&failure_key) { - let regular = self.is_regular(ah)?; - let result = - self.try_same_head_spine_speculative(wa, wb, ah, regular)?; - if let Some(result) = result { - return Ok(Some(result)); - } - // This pair did not yield a useful probe (possibly skipped or - // resource-limited). The cache only skips future speculation. - self.env.def_eq_failure.insert(failure_key); - self.env.perf.record_def_eq_failure_insert(); - } else { - self.env.perf.record_def_eq_failure_hit(); - crate::perf::same_head::skip( - wa_w.0 == 1, - crate::perf::same_head::Skip::FailureCache, - ); - } - } - // H1: Equal height — unfold BOTH sides (lean4lean:596) - let ua = self.delta_unfold_one(wa)?; - let ub = self.delta_unfold_one(wb)?; - match (ua, ub) { - (Some(ua), Some(ub)) => { - *wa = self.whnf_no_delta_for_def_eq(&ua)?; - *wb = self.whnf_no_delta_for_def_eq(&ub)?; - }, - (Some(ua), None) => { - *wa = self.whnf_no_delta_for_def_eq(&ua)?; - }, - (None, Some(ub)) => { - *wb = self.whnf_no_delta_for_def_eq(&ub)?; - }, - (None, None) => break, - } - } else if wa_w > wb_w { - // a is heavier — unfold a first - if let Some(ua) = self.delta_unfold_one(wa)? { - *wa = self.whnf_no_delta_for_def_eq(&ua)?; - } else { - break; - } - } else { - // b is heavier — unfold b first - if let Some(ub) = self.delta_unfold_one(wb)? { - *wb = self.whnf_no_delta_for_def_eq(&ub)?; - } else { - break; - } - } - } else if a_delta { - if let Some(ua) = self.delta_unfold_one(wa)? { - *wa = self.whnf_no_delta_for_def_eq(&ua)?; - } else { - break; - } - } else if let Some(ub) = self.delta_unfold_one(wb)? { - *wb = self.whnf_no_delta_for_def_eq(&ub)?; - } else { - break; - } - - if wa.ptr_eq(wb) { - return Ok(Some(true)); - } - if self.quick_def_eq(wa, wb)? { - return Ok(Some(true)); - } - } - - Ok(None) - } - - /// Quick structural: same constructor, recursively same children (no WHNF). - fn quick_def_eq( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result> { - match (a.data(), b.data()) { - (ExprData::Sort(u1, _), ExprData::Sort(u2, _)) => Ok(univ_eq(u1, u2)), - (ExprData::Lam(..), ExprData::Lam(..)) - | (ExprData::All(..), ExprData::All(..)) => self.def_eq_binders(a, b), - _ => Ok(false), - } - } - - /// Same-head constant: if both are `C us args`, compare spines without unfolding. - fn try_same_head_spine( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result, TcError> { - let (a_head, a_args) = borrow_app_spine(a); - let (b_head, b_args) = borrow_app_spine(b); - let (a_id, a_us) = match a_head.data() { - ExprData::Const(id, us, _) => (id, us), - _ => return Ok(None), - }; - let (b_id, b_us) = match b_head.data() { - ExprData::Const(id, us, _) => (id, us), - _ => return Ok(None), - }; - if a_id.addr != b_id.addr || a_args.len() != b_args.len() { - return Ok(None); - } - if a_us.len() != b_us.len() - || !a_us.iter().zip(b_us.iter()).all(|(u, v)| univ_eq(u, v)) - { - return Ok(None); - } - if self.try_app_congruence(a, b)? { - return Ok(Some(true)); - } - for (ai, bi) in a_args.iter().zip(b_args.iter()) { - if !self.is_def_eq(ai, bi)? { - return Ok(None); - } - } - Ok(Some(true)) - } - - /// Measurement wraps only a real attempt, inside any speculative fuel slice. - /// Nested inclusive counts overlap; the diagnostic also reports exclusive - /// and root fuel. The result and all accounting of actual checking work are - /// unchanged, including errors that a speculative caller may swallow. - fn try_same_head_spine_measured( - &mut self, - a: &KExpr, - b: &KExpr, - head: &KId, - regular: bool, - ) -> Result, TcError> { - use crate::perf::same_head::{self, Outcome}; - if !same_head::enabled() { - return self.try_same_head_spine(a, b); - } - let ticket = same_head::begin(&head.addr, regular); - let before = self.rec_fuel; - let result = self.try_same_head_spine(a, b); - let outcome = match &result { - Ok(Some(true)) => Outcome::Success, - Ok(_) => Outcome::Miss, - Err(TcError::MaxRecFuel) => Outcome::FuelAbort, - Err(TcError::MaxRecDepth) => Outcome::DepthAbort, - Err(_) => Outcome::Error, - }; - let consumed = before.saturating_sub(self.rec_fuel); - same_head::finish(ticket, consumed, outcome); - if let Some(sample) = same_head::take_root_trace(ticket, consumed) { - eprintln!( - "[same-head-root] sample={sample} head=#{} regular={regular} outcome={outcome:?} fuel={consumed} pair={},{} legacy_ctx={} lbr={},{} depth={} def_eq_depth={} cheap={} infer_only={} a={} b={}", - head.addr.hex(), - a.hash_key(), - b.hash_key(), - self.ctx_id, - a.lbr(), - b.lbr(), - self.depth(), - self.def_eq_depth, - self.cheap_recursion_depth, - self.infer_only, - compact_def_eq_expr(a), - compact_def_eq_expr(b) - ); - } - result - } - - /// Give a same-head attempt a small local slice of recursive - /// fuel. Nested attempts inherit the remaining slice; an exhausted slice is - /// a speculative miss, after which the caller follows the ordinary delta - /// path with the consumed work charged to the enclosing check. - fn try_same_head_spine_speculative( - &mut self, - a: &KExpr, - b: &KExpr, - head: &KId, - regular: bool, - ) -> Result, TcError> { - let saved_fuel = self.rec_fuel; - // Preserve the existing non-Regular policy inside small inherited slices. - // A Regular slice is larger: its temporarily withheld fuel is NOT work - // already performed, and must not prematurely close the startup window. - let nested = saved_fuel <= SAME_HEAD_SPECULATION_ATTEMPT_FUEL; - let used = self.fuel_used().saturating_sub(self.same_head_fuel_reserve); - if !regular && !nested && used >= SAME_HEAD_SPECULATION_START_FUEL { - crate::perf::same_head::skip(false, crate::perf::same_head::Skip::Window); - return Ok(None); - } - if self.same_head_backoff.should_skip(regular) { - crate::perf::same_head::skip( - regular, - crate::perf::same_head::Skip::Backoff, - ); - return Ok(None); - } - let allowance = if regular { - SAME_HEAD_REGULAR_ATTEMPT_FUEL - } else { - SAME_HEAD_SPECULATION_ATTEMPT_FUEL - }; - let local_fuel = saved_fuel.min(allowance); - let saved_reserve = self.same_head_fuel_reserve; - self.same_head_fuel_reserve += saved_fuel - local_fuel; - self.rec_fuel = local_fuel; - self.same_head_backoff.enter(); - let result = self.try_same_head_spine_measured(a, b, head, regular); - let consumed = local_fuel.saturating_sub(self.rec_fuel); - self.rec_fuel = saved_fuel.saturating_sub(consumed); - self.same_head_fuel_reserve = saved_reserve; - let unsuccessful = matches!( - result, - Ok(None | Some(false)) | Err(TcError::MaxRecDepth | TcError::MaxRecFuel) - ); - self.same_head_backoff.leave(regular, unsuccessful, consumed); - match result { - Err(TcError::MaxRecDepth | TcError::MaxRecFuel) => Ok(None), - other => other, - } - } - - /// Full structural comparison after WHNF. - fn is_def_eq_whnf( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result> { - // First try purely structural comparison - let structural = match (a.data(), b.data()) { - (ExprData::Sort(u1, _), ExprData::Sort(u2, _)) => { - return Ok(univ_eq(u1, u2)); - }, - (ExprData::Var(i, _, _), ExprData::Var(j, _, _)) if i == j => { - return Ok(true); - }, - (ExprData::Const(id1, us1, _), ExprData::Const(id2, us2, _)) => { - if id1.addr == id2.addr - && us1.len() == us2.len() - && us1.iter().zip(us2.iter()).all(|(u, v)| univ_eq(u, v)) - { - return Ok(true); - } - false - }, - (ExprData::App(f1, a1, _), ExprData::App(f2, a2, _)) => { - if self.is_def_eq(f1, f2)? && self.is_def_eq(a1, a2)? { - return Ok(true); - } - false - }, - (ExprData::Lam(..), ExprData::Lam(..)) - | (ExprData::All(..), ExprData::All(..)) => { - if self.def_eq_binders(a, b)? { - return Ok(true); - } - false - }, - ( - ExprData::Let(name, ty1, v1, body1, contract, _), - ExprData::Let(_, ty2, v2, body2, _, _), - ) => { - // H3: Let should be zeta-reduced by whnf_core before reaching this - // point. Push as LDecl so the let-bound value is available for - // FVar zeta-reduction in body comparison, in case this branch IS - // reached. - if self.is_def_eq(ty1, ty2)? && self.is_def_eq(v1, v2)? { - let r = self.with_lctx_scope(|tc| { - let fv_id = tc.fresh_fvar_id(); - let fv = tc.intern(KExpr::fvar(fv_id, name.clone())); - tc.lctx.push( - fv_id, - LocalDecl::LDecl { - name: name.clone(), - ty: ty1.clone(), - val: v1.clone(), - contract: *contract, - }, - ); - let b1_open = instantiate_rev( - &mut tc.env.intern, - body1, - std::slice::from_ref(&fv), - ); - let b2_open = instantiate_rev(&mut tc.env.intern, body2, &[fv]); - tc.is_def_eq(&b1_open, &b2_open) - })?; - if r { - return Ok(true); - } - } - false - }, - (ExprData::Nat(v1, _, _), ExprData::Nat(v2, _, _)) => { - self.primitives()?; - return Ok(v1 == v2); - }, - (ExprData::Str(v1, _, _), ExprData::Str(v2, _, _)) => { - self.primitives()?; - return Ok(v1 == v2); - }, - _ => false, - }; - - if structural { - return Ok(true); - } - - // Nat literal ↔ constructor: 0 ≡ Nat.zero, succ(n) ≡ n+1 - if self.is_nat_like(a) && self.is_nat_like(b) { - return self.is_def_eq_nat(a, b); - } - - // Eta expansion: try both directions - if matches!(a.data(), ExprData::Lam(..)) - || matches!(b.data(), ExprData::Lam(..)) - { - if self.try_eta_expansion(a, b)? { - return Ok(true); - } - if self.try_eta_expansion(b, a)? { - return Ok(true); - } - } - - // String literal expansion - if matches!(a.data(), ExprData::Str(..)) - || matches!(b.data(), ExprData::Str(..)) - { - if self.try_string_lit_expansion(a, b)? { - return Ok(true); - } - if self.try_string_lit_expansion(b, a)? { - return Ok(true); - } - } - - // Struct eta + unit-like + proof irrelevance fallback - if self.try_eta_struct(a, b)? { - return Ok(true); - } - if self.try_eta_struct(b, a)? { - return Ok(true); - } - if self.try_def_eq_unit(a, b)? { - return Ok(true); - } - self.try_proof_irrel(a, b) - } - - /// Proof irrelevance: if both are proofs of propositions (types in Prop), - /// they're def-eq. We check type(type(a)) = Sort(0), meaning type(a) : Prop. - /// - /// The "is `a_ty` propositional?" question is delegated to - /// [`Self::is_prop_type`], which caches by the type's content hash so a - /// repeat probe on the same proposition skips the recursive - /// `infer ∘ whnf` chain entirely. Without that cache, every successful - /// proof-irrelevance call paid 2× `infer` + 1× `whnf` of overhead, even - /// when the inner caches were warm — empirically the dominant cost on - /// mathlib proof-heavy blocks. - fn try_proof_irrel( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result> { - if self.known_non_proof(a) { - return Ok(false); - } - let a_ty = match self.with_infer_only(|tc| tc.infer(a)) { - Ok(ty) => ty, - Err(_) => return Ok(false), - }; - if !self.is_prop_type(&a_ty) { - return Ok(false); - } - let b_ty = match self.with_infer_only(|tc| tc.infer(b)) { - Ok(ty) => ty, - Err(_) => return Ok(false), - }; - self.is_def_eq(&a_ty, &b_ty) - } - - /// Returns true iff `ty` is a propositional type — i.e. its sort is - /// `Sort 0`. Memoized on `(ty.hash_key(), ctx_hash)` because the answer - /// is a pure function of the type and the relevant context suffix. - /// - /// On a hit this is one `FxHashMap` probe; on a miss it pays the - /// existing `infer ∘ whnf` chain and stores the result. Errors from - /// the inner chain are treated as a miss, but are NOT cached: in particular, - /// exhausting a speculative fuel slice must not permanently classify a - /// proposition as non-propositional. - pub(crate) fn is_prop_type(&mut self, ty: &KExpr) -> bool { - let cache_key = (ty.hash_key(), self.ctx_addr_for_lbr(ty.lbr())); - if let Some(&cached) = self.env.is_prop_cache.get(&cache_key) { - self.env.perf.record_is_prop_hit(); - return cached; - } - self.env.perf.record_is_prop_miss(); - self.record_hot_miss("is-prop", ty); - - // infer(ty) returns the Sort that classifies `ty`. WHNF is needed because - // the inferred sort may be wrapped in `mdata` or a let-bound sort - // synonym before being structurally `Sort u`. - let result = match self.with_infer_only(|tc| tc.infer(ty)) { - Ok(sort) => match self.whnf(&sort) { - Ok(reduced) => match reduced.data() { - ExprData::Sort(u, _) => u.is_semantic_zero(), - _ => false, - }, - Err(_) => return false, - }, - Err(_) => return false, - }; - self.env.is_prop_cache.insert(cache_key, result); - result - } - - /// Unit-like type: non-recursive, 0 indices, 1 ctor with 0 fields. - /// If both values inhabit the same unit-like type, they're def-eq. - fn try_def_eq_unit( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result> { - let a_ty = match self.with_infer_only(|tc| tc.infer(a)) { - Ok(ty) => ty, - Err(_) => return Ok(false), - }; - let a_ty_w = match self.whnf(&a_ty) { - Ok(w) => w, - Err(_) => return Ok(false), - }; - let a_head = app_head(&a_ty_w); - let a_ind = match a_head.data() { - ExprData::Const(id, _, _) => id.clone(), - _ => return Ok(false), - }; - // Check unit-like: non-recursive, 0 indices, 1 ctor with 0 fields - let is_unit = match self.try_get_const(&a_ind)? { - Some(KConst::Indc { indices, ctors, .. }) => { - if indices != 0 || ctors.len() != 1 { - false - } else { - match self.try_get_const(&ctors[0])? { - Some(KConst::Ctor { fields, .. }) => fields == 0, - _ => false, - } - } - }, - _ => return Ok(false), - }; - if !is_unit { - return Ok(false); - } - // Both must have the same type - let b_ty = match self.with_infer_only(|tc| tc.infer(b)) { - Ok(ty) => ty, - Err(_) => return Ok(false), - }; - self.is_def_eq(&a_ty_w, &b_ty) - } - - // ----------------------------------------------------------------------- - // Nat literal ↔ constructor comparison - // ----------------------------------------------------------------------- - - /// Check if an expression is a nat-like value (literal, Nat.zero, Nat.succ _). - fn is_nat_like(&self, e: &KExpr) -> bool { - let Some(primitives) = self.optional_primitives() else { - return false; - }; - match e.data() { - ExprData::Nat(..) => true, - ExprData::Const(id, _, _) => id.addr == primitives.nat_zero.addr, - ExprData::App(f, _, _) => { - matches!(f.data(), ExprData::Const(id, _, _) if id.addr == primitives.nat_succ.addr) - }, - _ => false, - } - } - - /// Check if expression is nat zero (literal 0 or Nat.zero constructor). - fn is_nat_zero(&self, e: &KExpr) -> bool { - let Some(primitives) = self.optional_primitives() else { - return false; - }; - match e.data() { - ExprData::Nat(v, _, _) => v.0 == num_bigint::BigUint::ZERO, - ExprData::Const(id, _, _) => id.addr == primitives.nat_zero.addr, - _ => false, - } - } - - /// 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 Some(primitives) = self.optional_primitives() else { - return false; - }; - let mut cur = e; - loop { - match cur.data() { - ExprData::Nat(..) => return true, - ExprData::Const(id, _, _) => { - let p = primitives; - 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> { - match e.data() { - ExprData::Nat(v, _, _) => { - if v.0 == num_bigint::BigUint::ZERO { - return None; - } - let pred = bignat::Nat(&v.0 - num_bigint::BigUint::from(1u64)); - let pred_addr = ix_common::address::Address::hash(&pred.to_le_bytes()); - Some(self.env.intern.intern_expr(KExpr::nat(pred, pred_addr))) - }, - ExprData::App(f, arg, _) => match f.data() { - ExprData::Const(id, _, _) - if id.addr == self.optional_primitives()?.nat_succ.addr => - { - Some(arg.clone()) - }, - _ => None, - }, - _ => None, - } - } - - /// Def-eq for nat-like values: handles mixed literal/constructor comparison. - /// Fast-path: two Nat literals are compared directly by value (O(1) instead of - /// O(n) recursion depth that would blow the def_eq_depth limit). - fn is_def_eq_nat( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result> { - self.primitives()?; - // Fast path: both literals — compare by value directly - if let (ExprData::Nat(va, _, _), ExprData::Nat(vb, _, _)) = - (a.data(), b.data()) - { - return Ok(va == vb); - } - if self.is_nat_zero(a) && self.is_nat_zero(b) { - return Ok(true); - } - match (self.nat_succ_of(a), self.nat_succ_of(b)) { - (Some(a_pred), Some(b_pred)) => self.is_def_eq(&a_pred, &b_pred), - _ => Ok(false), - } - } - - /// 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, - b: &KExpr, - ) -> Result, TcError> { - if self.optional_primitives().is_none() { - return Ok(None); - } - // Fast path: both literals — compare by value directly - if let (ExprData::Nat(va, _, _), ExprData::Nat(vb, _, _)) = - (a.data(), b.data()) - { - return Ok(Some(va == vb)); - } - if self.is_nat_zero(a) && self.is_nat_zero(b) { - return Ok(Some(true)); - } - // 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)?)) - } - - // ----------------------------------------------------------------------- - // String literal expansion - // ----------------------------------------------------------------------- - - /// String literal expansion (C++ kernel: try_string_lit_expansion_core). - /// - /// When `t` is a string literal, expand it to constructor form via - /// `str_lit_to_ctor_app` — `String.ofList [Char.ofNat c₁, ...]` with - /// one delta step past `String.ofList`, exposing the canonical - /// `String.ofByteArray ...` form — then compare with `s`. The delta - /// step matters: whnf of the raw `String.ofList` application would - /// natively collapse straight back to the `Str` literal - /// (`try_reduce_string`), and the comparison would never reach the - /// structural constructor form. - fn try_string_lit_expansion( - &mut self, - t: &KExpr, - s: &KExpr, - ) -> Result> { - if self.optional_primitives().is_none() { - return Ok(false); - } - let str_val = match t.data() { - ExprData::Str(v, _, _) => v.clone(), - _ => return Ok(false), - }; - // Literal vs literal: equal literals are one interned node (caught - // long before this point); distinct literals are never defeq — their - // constructor expansions differ in the codepoint payloads. Decide - // directly instead of expanding both sides structurally. - if let ExprData::Str(s_val, _, _) = s.data() { - return Ok(*s_val == str_val); - } - let expanded = self.str_lit_to_ctor_app(&str_val)?; - self.is_def_eq(&expanded, s) - } - - /// Convert a string literal to constructor form: - /// `"abc"` → `String.ofList (List.cons (Char.ofNat 97) (List.cons (Char.ofNat 98) (... List.nil)))` - /// - /// Uses `Char.ofNat` (not `Char.mk`) matching lean4lean/C++ kernel. - /// Uses `String.ofList` (= `String.mk` in our env) matching lean4lean/C++ kernel. - pub(super) fn str_lit_to_constructor( - &mut self, - s: &str, - ) -> Result, TcError> { - // A string literal denotes `String.ofList` of its `Char.ofNat` list. - for role in [ - ixon::role_table::Role::StringOfList, - ixon::role_table::Role::CharOfNat, - ixon::role_table::Role::List, - ixon::role_table::Role::Nat, - ] { - self.require_role(role)?; - } - Ok({ - let char_const = self.intern(KExpr::cnst( - self.primitives()?.char_type.clone(), - Box::new([]), - )); - let char_of_nat = self.intern(KExpr::cnst( - self.primitives()?.char_of_nat.clone(), - Box::new([]), - )); - let string_mk = self.intern(KExpr::cnst( - self.primitives()?.string_of_list.clone(), - Box::new([]), - )); - - // List.nil.{0} Char - let list_nil_z = self.intern(KExpr::cnst( - self.primitives()?.list_nil.clone(), - Box::new([KUniv::zero()]), - )); - let nil = self.intern(KExpr::app(list_nil_z, char_const.clone())); - - // List.cons.{0} Char - let list_cons_z = self.intern(KExpr::cnst( - self.primitives()?.list_cons.clone(), - Box::new([KUniv::zero()]), - )); - let cons = self.intern(KExpr::app(list_cons_z, char_const)); - - // Build list right-to-left: foldr - let mut list = nil; - for c in s.chars().rev() { - let nat_val = bignat::Nat::from(c as u64); - let nat_addr = - ix_common::address::Address::hash(&nat_val.to_le_bytes()); - let nat_lit = self.intern(KExpr::nat(nat_val, nat_addr)); - let char_val = self.intern(KExpr::app(char_of_nat.clone(), nat_lit)); - let partial = self.intern(KExpr::app(cons.clone(), char_val)); - list = self.intern(KExpr::app(partial, list)); - } - - // String.mk list - self.intern(KExpr::app(string_mk, list)) - }) - } - - /// String literal → structure-constructor application. - /// - /// `str_lit_to_constructor` produces `String.ofList [Char.ofNat c…]`; - /// with the native collapse rule in `try_reduce_string`, a whnf of - /// that application folds straight back into the `Str` literal. So - /// callers that need the actual constructor form — iota majors, - /// projections, literal-vs-structural def-eq — take one delta step - /// past `String.ofList` here: in the compiled environment that - /// exposes `String.ofByteArray (List.utf8Encode [chars]) proof` - /// (fields stay lazy). In environments where the `String.ofList` - /// address IS the constructor (List-Char-model test envs), the delta - /// step is a no-op and the expansion is already ctor-headed. - pub(super) fn str_lit_to_ctor_app( - &mut self, - s: &str, - ) -> Result, TcError> { - let of_list_app = self.str_lit_to_constructor(s)?; - match self.delta_unfold_one(&of_list_app)? { - // Beta the unfolded body against the char list. `whnf_core` stops - // at the constructor head and runs no string-primitive - // reductions, so it cannot re-collapse the expansion. - Some(unfolded) => self.whnf_core(&unfolded), - None => Ok(of_list_app), - } - } - - // ----------------------------------------------------------------------- - // Eta expansion - // ----------------------------------------------------------------------- - - /// Lambda eta expansion (lean4lean style): if `t` is a lambda and `s` is not, - /// infer `s`'s type, WHNF to get a forall, wrap `s` as `λ(ty). s #0`, compare with `t`. - fn try_eta_expansion( - &mut self, - t: &KExpr, - s: &KExpr, - ) -> Result> { - if !matches!(t.data(), ExprData::Lam(..)) - || matches!(s.data(), ExprData::Lam(..)) - { - return Ok(false); - } - // Infer s's type, WHNF to forall to get the binder type - let s_ty = match self.with_infer_only(|tc| tc.infer(s)) { - Ok(ty) => ty, - Err(_) => return Ok(false), - }; - let s_ty_whnf = match self.whnf(&s_ty) { - Ok(w) => w, - Err(_) => return Ok(false), - }; - let (name, bi, contract, ty) = match s_ty_whnf.data() { - ExprData::All(name, bi, contract, _, ty, _, _) => { - (name.clone(), bi.clone(), *contract, ty.clone()) - }, - _ => return Ok(false), - }; - if let ExprData::Lam(_, _, input, ..) = t.data() - && *input != contract - { - return Ok(false); - } - // Wrap s as λ(ty). s #0 - let s_lifted = lift(&mut self.env.intern, s, 1, 0); - let v0 = - self.intern(KExpr::var(0, M::meta_field(ix_common::env::Name::anon()))); - let body = self.intern(KExpr::app(s_lifted, v0)); - let s_lam = self.intern(KExpr::lam_contract(name, bi, contract, ty, body)); - self.is_def_eq(t, &s_lam) - } - - /// Struct eta (lean4lean style): if `s` is a fully-applied constructor of a - /// struct-like type, check `proj(i, t) ≡ s.args[params+i]` for each field. - /// Tries `tryEtaStructCore(t, s)` — caller should try both directions. - fn try_eta_struct( - &mut self, - t: &KExpr, - s: &KExpr, - ) -> Result> { - let t_norm = self.whnf_no_delta(t).unwrap_or_else(|_| t.clone()); - - // s must be a constructor application - let (s_head, s_args) = borrow_app_spine(s); - let ctor_id = match s_head.data() { - ExprData::Const(id, _, _) => id.clone(), - _ => { - self.dump_eta_trace("rhs-not-ctor-head", None, 0, &t_norm, s); - return Ok(false); - }, - }; - - // Head must be a constructor - let (induct_id, num_params, num_fields) = match self - .try_get_const(&ctor_id)? - { - Some(KConst::Ctor { induct, params, fields, .. }) => { - (induct.clone(), u64_to_usize::(params)?, u64_to_usize::(fields)?) - }, - _ => { - self.dump_eta_trace("rhs-head-not-ctor", Some(&ctor_id), 0, &t_norm, s); - return Ok(false); - }, - }; - - // Must be fully applied - if s_args.len() != num_params + num_fields { - self.dump_eta_trace( - "ctor-arity", - Some(&ctor_id), - s_args.len(), - &t_norm, - s, - ); - return Ok(false); - } - - // Inductive must be struct-like (non-recursive, 0 indices, 1 ctor) - if !self.is_struct_like(&induct_id)? { - self.dump_eta_trace("not-struct-like", Some(&induct_id), 0, &t_norm, s); - return Ok(false); - } - - // Types must be def-eq (lean4lean tryEtaStructCore, line 515). - // No Prop guard here — struct eta in def-eq is safe even for Prop types - // because we're checking equality, not constructing terms. The Prop guard - // is only needed in iota's toCtorWhenStruct (whnf.rs try_struct_eta_iota) - // where eta-expanding creates projections that would be unsound for Prop. - let s_ty = match self.with_infer_only(|tc| tc.infer(s)) { - Ok(ty) => ty, - Err(_) => { - self.dump_eta_trace("infer-rhs-type", Some(&induct_id), 0, t, s); - return Ok(false); - }, - }; - let t_ty = match self.with_infer_only(|tc| tc.infer(&t_norm)) { - Ok(ty) => ty, - Err(_) => { - self.dump_eta_trace("infer-lhs-type", Some(&induct_id), 0, &t_norm, s); - return Ok(false); - }, - }; - if !self.is_def_eq(&t_ty, &s_ty)? { - self.dump_eta_trace("type-mismatch", Some(&induct_id), 0, &t_norm, s); - return Ok(false); - } - - if let Some(base) = - self.eta_expansion_base(&induct_id, num_params, num_fields, &s_args)? - && self.is_def_eq(&t_norm, &base)? - { - self.dump_eta_trace( - "eta-base", - Some(&induct_id), - num_fields, - &t_norm, - &base, - ); - return Ok(true); - } - - // Compare each field: proj(induct, i, t) ≡ s_args[params + i] - for i in 0..num_fields { - let proj = - self.intern(KExpr::prj(induct_id.clone(), i as u64, t_norm.clone())); - if !self.is_def_eq(&proj, s_args[num_params + i])? { - self.dump_eta_trace( - "field-mismatch", - Some(&induct_id), - i, - &proj, - s_args[num_params + i], - ); - return Ok(false); - } - } - - self.dump_eta_trace("ok", Some(&induct_id), num_fields, &t_norm, s); - Ok(true) - } - - fn eta_expansion_base( - &mut self, - induct_id: &KId, - num_params: usize, - num_fields: usize, - args: &[&KExpr], - ) -> Result>, TcError> { - let mut base: Option> = None; - for i in 0..num_fields { - let field = &args[num_params + i]; - let field = self.whnf_no_delta(field)?; - let ExprData::Prj(id, idx, val, _) = field.data() else { - return Ok(None); - }; - if id.addr != induct_id.addr || *idx != i as u64 { - return Ok(None); - } - let val = self.whnf_no_delta(val).unwrap_or_else(|_| val.clone()); - match &base { - Some(base) if base.hash_key() != val.hash_key() => return Ok(None), - Some(_) => {}, - None => base = Some(val), - } - } - Ok(base) - } - - /// App spine comparison (lean4lean isDefEqApp): decompose both sides into - /// head + args and compare componentwise. Handles multi-arg apps. - fn try_def_eq_app( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result> { - if !matches!(a.data(), ExprData::App(..)) - || !matches!(b.data(), ExprData::App(..)) - { - return Ok(false); - } - let (a_head, a_args) = borrow_app_spine(a); - let (b_head, b_args) = borrow_app_spine(b); - if a_args.len() != b_args.len() { - return Ok(false); - } - if !self.is_def_eq(a_head, b_head)? { - return Ok(false); - } - if self.try_app_congruence(a, b)? { - return Ok(true); - } - for (ai, bi) in a_args.iter().zip(b_args.iter()) { - if !self.is_def_eq(ai, bi)? { - return Ok(false); - } - } - Ok(true) - } - - /// Check if expression is the Bool.true constant. - fn is_bool_true(&self, e: &KExpr) -> bool { - let Some(primitives) = self.optional_primitives() else { - return false; - }; - match e.data() { - ExprData::Const(id, us, _) => { - us.is_empty() && id.addr == primitives.bool_true.addr - }, - _ => false, - } - } - - /// Check if a constant is delta-reducible. - fn is_delta(&mut self, id: &KId) -> Result> { - Ok(matches!( - self.try_get_const(id)?, - Some(KConst::Defn { kind, safety, .. }) if self.unfolds(kind, safety) - )) - } - - /// Check if a constant has Regular reducibility hints. - fn is_regular(&mut self, id: &KId) -> Result> { - use ix_common::env::ReducibilityHints; - Ok(matches!( - self.try_get_const(id)?, - Some(KConst::Defn { hints: ReducibilityHints::Regular(_), .. }) - )) - } - - /// Reducibility rank by id. Higher rank = unfold first. - /// - /// Returns a `(class, height)` tuple compared lexicographically, so that - /// `Abbrev` strictly dominates every `Regular(h)` regardless of `h`. The - /// previous `u32` encoding mapped `Abbrev` to `u32::MAX - 1` and saturated - /// `Regular(h)` to `h.saturating_add(1)`, which collapsed at `h ≥ u32::MAX-2` - /// — flipping delta direction in the rare case of an `Abbrev` paired with - /// a maximally heavy regular definition. The structured tuple matches - /// Lean's `compare(d_t->get_hints(), d_s->get_hints())` - /// (`type_checker.cpp:910`): - /// - /// - `Opaque` / `Theorem` / unknown → `(0, 0)` - /// - `Regular(h)` → `(1, h)` (ordered by height within the class) - /// - `Abbrev` → `(2, 0)` (strictly greater than every `Regular(h)`) - fn def_rank_id(&mut self, id: &KId) -> Result<(u8, u32), TcError> { - use ix_common::env::ReducibilityHints; - Ok(match self.try_get_const(id)? { - Some(KConst::Defn { kind, safety, .. }) - if !self.unfolds(kind, safety) => - { - (0, 0) - }, - Some(KConst::Defn { kind, hints, .. }) => match kind { - DefKind::Opaque | DefKind::Theorem => (0, 0), - DefKind::Definition => match hints { - ReducibilityHints::Opaque => (0, 0), - ReducibilityHints::Regular(h) => (1, h), - ReducibilityHints::Abbrev => (2, 0), - }, - }, - _ => (0, 0), - }) - } - - // ----------------------------------------------------------------------- - // Post-delta congruence and projection unfolding (C5, C6) - // ----------------------------------------------------------------------- - - /// Structural congruence after lazy delta exhaustion (lean4lean isDefEqConst/Proj). - /// Checks Const-Const, Var-Var, Prj-Prj without further reduction. - fn try_structural_congruence( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result> { - match (a.data(), b.data()) { - (ExprData::Const(id1, us1, _), ExprData::Const(id2, us2, _)) => Ok( - id1.addr == id2.addr - && us1.len() == us2.len() - && us1.iter().zip(us2.iter()).all(|(u, v)| univ_eq(u, v)), - ), - (ExprData::Var(i, _, _), ExprData::Var(j, _, _)) => Ok(i == j), - (ExprData::Prj(id1, f1, v1, _), ExprData::Prj(id2, f2, v2, _)) => { - if id1.addr != id2.addr || f1 != f2 { - return Ok(false); - } - if self.try_projected_def_eq(a, b)? { - return Ok(true); - } - let mut v1 = v1.clone(); - let mut v2 = v2.clone(); - self.lazy_delta_proj_reduction(id1, *f1, &mut v1, &mut v2) - }, - _ => Ok(false), - } - } - - /// A positive-only shortcut: compare reduced fields before traversing the - /// other fields/arguments of their records. Failure (including local budget - /// exhaustion) leaves the ORIGINAL record-congruence path available. Never - /// turn an interrupted record comparison into a negative conversion result. - fn try_projected_def_eq( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result> { - if self.in_projection_probe { - return Ok(false); - } - let saved_fuel = self.rec_fuel; - let local_fuel = saved_fuel.min(PROJECTION_PROBE_FUEL); - self.rec_fuel = local_fuel; - self.in_projection_probe = true; - let result = (|| { - let pa = self.whnf_core(a)?; - let pb = self.whnf_core(b)?; - if pa.hash_key() == a.hash_key() && pb.hash_key() == b.hash_key() { - return Ok(false); - } - self.is_def_eq(&pa, &pb) - })(); - self.in_projection_probe = false; - let consumed = local_fuel.saturating_sub(self.rec_fuel); - self.rec_fuel = saved_fuel.saturating_sub(consumed); - match result { - Err(TcError::MaxRecDepth | TcError::MaxRecFuel) => Ok(false), - other => other, - } - } - - fn lazy_delta_proj_reduction( - &mut self, - struct_id: &KId, - field: u64, - a: &mut KExpr, - b: &mut KExpr, - ) -> Result> { - let mut fuel = MAX_WHNF_FUEL; - loop { - if fuel == 0 { - self.dump_guard_stack("def-eq-projection-delta-fuel"); - self.dump_def_eq_max("proj-delta-fuel", a, b, None, None); - return Err(TcError::MaxRecDepth); - } - fuel -= 1; - match self.lazy_delta_reduction_step(a, b)? { - LazyDeltaStep::Equal => return Ok(true), - LazyDeltaStep::Continue => {}, - LazyDeltaStep::Unknown => { - self.dump_proj_delta_trace("stuck", struct_id, field, a, b); - let pa = self.try_project_core(struct_id, field, a)?; - let pb = self.try_project_core(struct_id, field, b)?; - return match (pa, pb) { - (Some(pa), Some(pb)) => { - self.dump_proj_delta_trace( - "projected", - struct_id, - field, - &pa, - &pb, - ); - self.is_def_eq(&pa, &pb) - }, - _ => { - self.dump_proj_delta_trace("fallback", struct_id, field, a, b); - self.is_def_eq(a, b) - }, - }; - }, - } - } - } - - fn lazy_delta_reduction_step( - &mut self, - a: &mut KExpr, - b: &mut KExpr, - ) -> Result> { - let a_head = head_const_id(a); - let b_head = head_const_id(b); - let a_delta = match &a_head { - Some(h) => self.is_delta(h)?, - None => false, - }; - let b_delta = match &b_head { - Some(h) => self.is_delta(h)?, - None => false, - }; - - if !a_delta && !b_delta { - return Ok(LazyDeltaStep::Unknown); - } - - if a_delta && !b_delta { - if let Some(b2) = self.try_unfold_proj_app(b)? { - *b = b2; - } else if let Some(a2) = self.delta_unfold_one(a)? { - *a = self.whnf_core(&a2)?; - } else { - return Ok(LazyDeltaStep::Unknown); - } - } else if !a_delta && b_delta { - if let Some(a2) = self.try_unfold_proj_app(a)? { - *a = a2; - } else if let Some(b2) = self.delta_unfold_one(b)? { - *b = self.whnf_core(&b2)?; - } else { - return Ok(LazyDeltaStep::Unknown); - } - } else { - let a_id = a_head.as_ref().expect("a_delta implies head"); - let b_id = b_head.as_ref().expect("b_delta implies head"); - let cmp = self.def_rank_id(a_id)?.cmp(&self.def_rank_id(b_id)?); - if cmp.is_gt() { - if let Some(a2) = self.delta_unfold_one(a)? { - *a = self.whnf_core(&a2)?; - } else { - return Ok(LazyDeltaStep::Unknown); - } - } else if cmp.is_lt() { - if let Some(b2) = self.delta_unfold_one(b)? { - *b = self.whnf_core(&b2)?; - } else { - return Ok(LazyDeltaStep::Unknown); - } - } else { - if a_id.addr == b_id.addr { - let regular = self.is_regular(a_id)?; - let result = - self.try_same_head_spine_speculative(a, b, a_id, regular)?; - if let Some(true) = result { - return Ok(LazyDeltaStep::Equal); - } - } - let a2 = self.delta_unfold_one(a)?; - let b2 = self.delta_unfold_one(b)?; - match (a2, b2) { - (Some(a2), Some(b2)) => { - *a = self.whnf_core(&a2)?; - *b = self.whnf_core(&b2)?; - }, - (Some(a2), None) => *a = self.whnf_core(&a2)?, - (None, Some(b2)) => *b = self.whnf_core(&b2)?, - (None, None) => return Ok(LazyDeltaStep::Unknown), - } - } - } - - if a.ptr_eq(b) || self.quick_def_eq(a, b)? { - Ok(LazyDeltaStep::Equal) - } else { - Ok(LazyDeltaStep::Continue) - } - } - - fn try_project_core( - &mut self, - struct_id: &KId, - field: u64, - e: &KExpr, - ) -> Result>, TcError> { - self.try_proj_reduce(struct_id, field, e) - } - - fn dump_proj_delta_trace( - &self, - phase: &str, - id: &KId, - field: u64, - a: &KExpr, - b: &KExpr, - ) { - let Some(filter) = IX_PROJ_DELTA_TRACE.as_ref() else { - return; - }; - if !self.debug_label_matches_env() { - return; - } - let id_s = id.to_string(); - if !filter.is_empty() && !id_s.contains(filter) { - return; - } - log::info!( - "[proj-delta] const={} depth={} phase={} proj={}.{} a={} b={}", - self.debug_label.as_deref().unwrap_or(""), - self.def_eq_depth, - phase, - id, - field, - compact_def_eq_expr(a), - compact_def_eq_expr(b) - ); - } - - /// If the head of `e` is a projection, try reducing it via whnf_no_delta. - /// Returns the reduced form if it changed, None otherwise (lean4lean tryUnfoldProjApp). - fn try_unfold_proj_app( - &mut self, - e: &KExpr, - ) -> Result>, TcError> { - let head = app_head(e); - if !matches!(head.data(), ExprData::Prj(..)) { - return Ok(None); - } - let reduced = self.whnf_no_delta(e)?; - if reduced.ptr_eq(e) { Ok(None) } else { Ok(Some(reduced)) } - } - - fn dump_eta_trace( - &self, - reason: &str, - id: Option<&KId>, - idx: usize, - a: &KExpr, - b: &KExpr, - ) { - let Some(filter) = IX_ETA_TRACE.as_ref() else { - return; - }; - if !self.debug_label_matches_env() { - return; - } - let id_s = id.map_or_else(|| "".into(), |id| id.to_string()); - if !filter.is_empty() && !id_s.contains(filter) { - return; - } - log::info!( - "[eta] const={} depth={} reason={} id={} idx={} a={} b={}", - self.debug_label.as_deref().unwrap_or(""), - self.def_eq_depth, - reason, - id_s, - idx, - compact_def_eq_expr(a), - compact_def_eq_expr(b) - ); - } -} - -enum LazyDeltaStep { - Equal, - Unknown, - Continue, -} - -fn compact_def_eq_expr(e: &KExpr) -> String { - let (head, args) = collect_app_spine(e); - let base = match head.data() { - ExprData::Var(i, _, _) => format!("#{i}"), - ExprData::FVar(id, _, _) => format!("{id}"), - ExprData::Sort(u, _) => format!("Sort({u})"), - ExprData::Const(id, us, _) => format!("{id}.{{{}}}", us.len()), - ExprData::App(..) => "app".to_string(), - ExprData::Lam(..) => "lam".to_string(), - ExprData::All(..) => "forall".to_string(), - ExprData::Let(..) => "let".to_string(), - ExprData::Prj(id, field, val, _) => { - format!("Prj({id}.{field}, {})", compact_def_eq_expr(val)) - }, - ExprData::Nat(v, _, _) => format!("Nat({})", v.0), - ExprData::Str(v, _, _) => format!("Str(len={})", v.len()), - }; - if args.is_empty() { - format!("{base}@{}", short_def_eq_addr(e)) - } else { - let shown = args - .iter() - .take(6) - .map(compact_def_eq_head) - .collect::>() - .join(", "); - let more = if args.len() > 6 { ", ..." } else { "" }; - format!("{base}/{} [{shown}{more}]@{}", args.len(), short_def_eq_addr(e)) - } -} - -fn compact_def_eq_head(e: &KExpr) -> String { - let (head, args) = collect_app_spine(e); - let base = match head.data() { - ExprData::Var(i, _, _) => format!("#{i}"), - ExprData::FVar(id, _, _) => format!("{id}"), - ExprData::Sort(u, _) => format!("Sort({u})"), - ExprData::Const(id, us, _) => format!("{id}.{{{}}}", us.len()), - ExprData::App(..) => "app".to_string(), - ExprData::Lam(..) => "lam".to_string(), - ExprData::All(..) => "forall".to_string(), - ExprData::Let(..) => "let".to_string(), - ExprData::Prj(id, field, _, _) => format!("Prj({id}.{field})"), - ExprData::Nat(v, _, _) => format!("Nat({})", v.0), - ExprData::Str(v, _, _) => format!("Str(len={})", v.len()), - }; - if args.is_empty() { base } else { format!("{base}/{}", args.len()) } -} - -fn short_def_eq_addr(e: &KExpr) -> String { - format!("uid{}", e.addr()) -} - -/// Canonical ordering for cache keys: (min, max) by uid. -fn canonical_pair(a: Addr, b: Addr) -> (Addr, Addr) { - if a <= b { (a, b) } else { (b, a) } -} - -/// Extract head constant KId from expression or app spine. -fn head_const_id(e: &KExpr) -> Option> { - match e.data() { - ExprData::Const(id, _, _) => Some(id.clone()), - ExprData::App(..) => { - let head = app_head(e); - match head.data() { - ExprData::Const(id, _, _) => Some(id.clone()), - _ => None, - } - }, - _ => None, - } -} - -/// Extract head constant's display form as a string, for diagnostic -/// prefix matching. Uses `{kid}`'s Display impl (which is defined for -/// every `KernelMode`), not the inner `Name` which only has Display in -/// Meta mode. Returns `None` if the head isn't a `Const`. -fn head_const_name(e: &KExpr) -> Option { - let id = head_const_id(e)?; - Some(format!("{id}")) -} - -impl TypeChecker<'_, M> { - fn dump_def_eq_max( - &self, - kind: &str, - a: &KExpr, - b: &KExpr, - wa: Option<&KExpr>, - wb: Option<&KExpr>, - ) { - let Some(filter) = IX_DEF_EQ_MAX_DUMP.as_ref() else { - return; - }; - if !self.debug_label_matches_env() { - return; - } - let a_head = head_const_name(a).unwrap_or_else(|| "".to_string()); - let b_head = head_const_name(b).unwrap_or_else(|| "".to_string()); - let wa_head = - wa.and_then(head_const_name).unwrap_or_else(|| "".to_string()); - let wb_head = - wb.and_then(head_const_name).unwrap_or_else(|| "".to_string()); - if !filter.is_empty() - && !a_head.contains(filter) - && !b_head.contains(filter) - && !wa_head.contains(filter) - && !wb_head.contains(filter) - { - return; - } - // `ix check-rs` does not currently install a `log` subscriber. Emit the - // compact, explicitly requested guard record directly so a local-fuel - // failure cannot masquerade as generic recursion depth in CLI runs. The - // full expressions remain behind `log::info!` below. - eprintln!( - "[deq max] {kind} const={} depth={} peak={} used={} a={} b={} a_head={} b_head={} wa_head={} wb_head={}", - self.debug_label.as_deref().unwrap_or(""), - self.def_eq_depth, - self.def_eq_peak, - self.fuel_used(), - a.hash_key(), - b.hash_key(), - a_head, - b_head, - wa_head, - wb_head - ); - 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 - ); - log::info!(" a: {a}"); - log::info!(" b: {b}"); - if let Some(wa) = wa { - log::info!(" wa: {wa}"); - } - if let Some(wb) = wb { - log::info!(" wb: {wb}"); - } - } - - fn dump_def_eq_rec_fuel(&self, a: &KExpr, b: &KExpr) { - let Some(filter) = IX_DEF_EQ_MAX_DUMP.as_ref() else { - return; - }; - if !self.debug_label_matches_env() { - return; - } - let a_head = head_const_name(a).unwrap_or_else(|| "".to_string()); - let b_head = head_const_name(b).unwrap_or_else(|| "".to_string()); - if !filter.is_empty() - && !a_head.contains(filter) - && !b_head.contains(filter) - { - return; - } - eprintln!( - "[deq max] rec-fuel depth={} a={} b={}", - self.def_eq_depth, - compact_def_eq_expr(a), - compact_def_eq_expr(b) - ); - } -} - -#[cfg(test)] -#[path = "def_eq/projection_tests.rs"] -mod projection_tests; - -#[cfg(test)] -mod tests { - - use super::super::constant::KConst; - use super::super::env::KEnv; - use super::super::expr::KExpr; - use super::super::id::KId; - use super::super::level::KUniv; - use super::super::mode::{Anon, Meta}; - use super::super::tc::TypeChecker; - use ix_common::address::Address; - use ix_common::env::{DataValue, DefinitionSafety, Name, ReducibilityHints}; - use ixon::constant::DefKind; - - type AE = KExpr; - type ME = KExpr; - type AU = KUniv; - - fn mk_addr(s: &str) -> Address { - Address::hash(s.as_bytes()) - } - fn mk_id(s: &str) -> KId { - KId::new(mk_addr(s), ()) - } - fn mk_meta_name(s: &str) -> Name { - let mut name = Name::anon(); - for part in s.split('.') { - name = Name::str(name, part.to_string()); - } - name - } - fn sort0() -> AE { - AE::sort(AU::zero()) - } - - fn sort1() -> AE { - AE::sort(AU::succ(AU::zero())) - } - - #[test] - fn declaration_summary_skips_only_non_proof_irrelevance() { - let mut env = env_with_same_head_hint(ReducibilityHints::Abbrev); - let a = AE::cnst(mk_id("same_head.c"), Box::new([])); - let b = AE::cnst(mk_id("same_head.d"), Box::new([])); - let mut tc = TypeChecker::new(&mut env); - assert!(!tc.try_proof_irrel(&a, &b).unwrap()); - assert!(!tc.env.decl_summary_cache.is_empty()); - assert!(tc.env.infer_cache.is_empty()); - assert!(tc.env.infer_only_cache.is_empty()); - // Other conversion rules can still infer (e.g. structure eta). The - // summary bypasses only this probe, and never establishes equality. - assert!(!tc.is_def_eq(&a, &b).unwrap()); - } - - fn env_with_id() -> KEnv { - let mut env = KEnv::new(); - let id_ty = AE::all((), (), sort0(), sort0()); - let id_val = AE::lam((), (), sort0(), AE::var(0, ())); - env.insert( - mk_id("id"), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Abbrev, - lvls: 0, - ty: id_ty, - val: id_val, - lean_all: (), - block: mk_id("id"), - }, - ); - env - } - - /// `A : Type`, `c d : A`, and `head : A → A := fun x => x`, with the - /// requested reducibility hint on `head`. - fn env_with_same_head_hint(hints: ReducibilityHints) -> KEnv { - let mut env = KEnv::new(); - let a_id = mk_id("same_head.A"); - let a = AE::cnst(a_id.clone(), Box::new([])); - env.insert( - a_id, - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: sort1(), - }, - ); - for name in ["same_head.c", "same_head.d"] { - env.insert( - mk_id(name), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: a.clone(), - }, - ); - } - let head_id = mk_id("same_head.head"); - env.insert( - head_id.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints, - lvls: 0, - ty: AE::all((), (), a.clone(), a.clone()), - val: AE::lam((), (), a, AE::var(0, ())), - lean_all: (), - block: head_id, - }, - ); - env - } - - fn assert_same_head_congruence_does_not_unfold(hints: ReducibilityHints) { - let mut env = env_with_same_head_hint(hints); - let a = AE::cnst(mk_id("same_head.A"), Box::new([])); - let c = AE::cnst(mk_id("same_head.c"), Box::new([])); - let head = AE::cnst(mk_id("same_head.head"), Box::new([])); - let head_key = head.hash_key(); - let beta_arg = AE::app(AE::lam((), (), a, AE::var(0, ())), c.clone()); - let left = AE::app(head.clone(), beta_arg); - let right = AE::app(head, c); - - { - let mut tc = TypeChecker::new(&mut env); - assert!(tc.is_def_eq(&left, &right).unwrap()); - } - assert!( - !env.unfold_cache.contains_key(&head_key), - "same-head congruence should decide equality before unfolding the head" - ); - } - - #[test] - fn def_eq_same_head_abbrev_uses_congruence_before_delta() { - assert_same_head_congruence_does_not_unfold(ReducibilityHints::Abbrev); - } - - #[test] - fn def_eq_same_head_regular_still_uses_congruence_before_delta() { - assert_same_head_congruence_does_not_unfold(ReducibilityHints::Regular(7)); - } - - #[test] - fn def_eq_same_head_opaque_hint_uses_congruence_before_delta() { - assert_same_head_congruence_does_not_unfold(ReducibilityHints::Opaque); - } - - #[test] - fn def_eq_same_head_abbrev_falls_back_after_speculation_window() { - let mut env = env_with_same_head_hint(ReducibilityHints::Abbrev); - let a = AE::cnst(mk_id("same_head.A"), Box::new([])); - let c = AE::cnst(mk_id("same_head.c"), Box::new([])); - let head = AE::cnst(mk_id("same_head.head"), Box::new([])); - let head_key = head.hash_key(); - let beta_arg = AE::app(AE::lam((), (), a, AE::var(0, ())), c.clone()); - let left = AE::app(head.clone(), beta_arg); - let right = AE::app(head, c); - - { - let mut tc = TypeChecker::new(&mut env); - tc.rec_fuel = super::super::tc::max_rec_fuel() - .saturating_sub(super::SAME_HEAD_SPECULATION_START_FUEL); - assert!(tc.is_def_eq(&left, &right).unwrap()); - } - assert!( - env.unfold_cache.contains_key(&head_key), - "late non-Regular comparisons must resume through ordinary delta" - ); - } - - #[test] - fn def_eq_same_head_argument_miss_falls_back_to_delta() { - let mut env = env_with_same_head_hint(ReducibilityHints::Abbrev); - let head = AE::cnst(mk_id("same_head.head"), Box::new([])); - let head_key = head.hash_key(); - let left = - AE::app(head.clone(), AE::cnst(mk_id("same_head.c"), Box::new([]))); - let right = AE::app(head, AE::cnst(mk_id("same_head.d"), Box::new([]))); - - { - let mut tc = TypeChecker::new(&mut env); - assert!(!tc.is_def_eq(&left, &right).unwrap()); - } - assert!(!env.def_eq_failure.is_empty()); - assert!( - env.unfold_cache.contains_key(&head_key), - "a rejected same-head comparison must continue with ordinary delta" - ); - } - - #[test] - fn same_head_profile_preserves_success_result_and_fuel() { - use crate::perf::same_head; - let run = |measured: bool| { - let mut env = env_with_same_head_hint(ReducibilityHints::Regular(7)); - let a = AE::cnst(mk_id("same_head.A"), Box::new([])); - let c = AE::cnst(mk_id("same_head.c"), Box::new([])); - let id = mk_id("same_head.head"); - let head = AE::cnst(id.clone(), Box::new([])); - let beta_arg = AE::app(AE::lam((), (), a, AE::var(0, ())), c.clone()); - let left = AE::app(head.clone(), beta_arg); - let right = AE::app(head, c); - let mut tc = TypeChecker::new(&mut env); - same_head::reset(); - let result = if measured { - tc.try_same_head_spine_measured(&left, &right, &id, true) - } else { - tc.try_same_head_spine(&left, &right) - } - .unwrap(); - if measured && same_head::enabled() { - let report = same_head::summary(); - assert!(report.contains("regular outcome=success calls=1"), "{report}"); - assert!( - report.contains(&format!("root_fuel={} ", tc.fuel_used())), - "{report}" - ); - assert!(report.contains("active=0 "), "{report}"); - assert!(report.contains("accounting_errors=0"), "{report}"); - } - (result, tc.fuel_used()) - }; - assert_eq!(run(false), run(true)); - } - - #[test] - fn same_head_profile_distinguishes_window_skip_from_fuel_abort() { - use crate::perf::same_head; - let mut env = env_with_same_head_hint(ReducibilityHints::Abbrev); - let a = AE::cnst(mk_id("same_head.A"), Box::new([])); - let c = AE::cnst(mk_id("same_head.c"), Box::new([])); - let id = mk_id("same_head.head"); - let head = AE::cnst(id.clone(), Box::new([])); - let beta_arg = AE::app(AE::lam((), (), a, AE::var(0, ())), c.clone()); - let left = AE::app(head.clone(), beta_arg); - let right = AE::app(head, c); - let mut tc = TypeChecker::new(&mut env); - tc.rec_fuel = crate::tc::max_rec_fuel() - .saturating_sub(super::SAME_HEAD_SPECULATION_START_FUEL); - let before = tc.rec_fuel; - same_head::reset(); - assert_eq!( - tc.try_same_head_spine_speculative(&left, &right, &id, false).unwrap(), - None - ); - assert_eq!(tc.rec_fuel, before); - tc.rec_fuel = 1; - assert_eq!( - tc.try_same_head_spine_speculative(&left, &right, &id, false).unwrap(), - None - ); - assert_eq!(tc.rec_fuel, 0); - if same_head::enabled() { - let report = same_head::summary(); - assert!(report.contains("non_regular skipped_window=1"), "{report}"); - assert!( - report.contains("non_regular outcome=fuel_abort calls=1"), - "{report}" - ); - assert!(report.contains("root_fuel=1 "), "{report}"); - assert!(report.contains("active=0 "), "{report}"); - assert!(report.contains("accounting_errors=0"), "{report}"); - } - } - - /// Insert a `Defn` with the given reducibility hints under `name`, returning - /// its `KId`. Used by `def_rank_id` ordering tests. - fn insert_rank_def( - env: &mut KEnv, - name: &str, - hints: ReducibilityHints, - ) -> KId { - let id = mk_id(name); - env.insert( - id.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints, - lvls: 0, - ty: sort1(), - val: sort0(), - lean_all: (), - block: id.clone(), - }, - ); - id - } - - /// `Abbrev` must outrank a `Regular(u32::MAX)` — the saturation collision - /// the `def_weight_id : u32` encoding admitted (audit Tier 1 #3). - #[test] - fn def_rank_abbrev_above_saturated_regular() { - let mut env = KEnv::new(); - let abbrev = insert_rank_def(&mut env, "abbrev", ReducibilityHints::Abbrev); - let regular = insert_rank_def( - &mut env, - "regular", - ReducibilityHints::Regular(u32::MAX), - ); - let mut tc = TypeChecker::new(&mut env); - - assert!( - tc.def_rank_id(&abbrev).unwrap() > tc.def_rank_id(®ular).unwrap() - ); - } - - /// Within the `Regular` class, height orders rank monotonically. - #[test] - fn def_rank_regular_orders_by_height() { - let mut env = KEnv::new(); - let low = insert_rank_def(&mut env, "low", ReducibilityHints::Regular(1)); - let high = - insert_rank_def(&mut env, "high", ReducibilityHints::Regular(10)); - let mut tc = TypeChecker::new(&mut env); - - assert!(tc.def_rank_id(&high).unwrap() > tc.def_rank_id(&low).unwrap()); - } - - #[test] - fn def_eq_ptr_eq() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - let e = sort0(); - assert!(tc.is_def_eq(&e, &e).unwrap()); - } - - #[test] - fn def_eq_sort_same() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - let s1 = AE::sort(AU::zero()); - let s2 = AE::sort(AU::zero()); - assert!(tc.is_def_eq(&s1, &s2).unwrap()); - } - - #[test] - fn def_eq_sort_diff() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - let s0 = AE::sort(AU::zero()); - let s1 = AE::sort(AU::succ(AU::zero())); - assert!(!tc.is_def_eq(&s0, &s1).unwrap()); - } - - #[test] - fn def_eq_const_same() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - let c1 = AE::cnst(mk_id("id"), Box::new([])); - let c2 = AE::cnst(mk_id("id"), Box::new([])); - assert!(tc.is_def_eq(&c1, &c2).unwrap()); - } - - #[test] - fn def_eq_ignores_meta_mdata() { - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - let id = KId::new(mk_addr("C"), mk_meta_name("C")); - let tagged = ME::cnst_mdata( - id.clone(), - Box::new([]), - vec![vec![( - mk_meta_name("tag"), - DataValue::OfString("ignored".to_string()), - )]], - ); - 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()); - } - - #[test] - fn def_eq_const_diff_addr() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - let c1 = AE::cnst(mk_id("a"), Box::new([])); - let c2 = AE::cnst(mk_id("b"), Box::new([])); - assert!(!tc.is_def_eq(&c1, &c2).unwrap()); - } - - #[test] - fn def_eq_lam_structural() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - let l1 = AE::lam((), (), sort0(), AE::var(0, ())); - let l2 = AE::lam((), (), sort0(), AE::var(0, ())); - assert!(tc.is_def_eq(&l1, &l2).unwrap()); - } - - #[test] - fn def_eq_all_structural() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - let a1 = AE::all((), (), sort0(), sort0()); - let a2 = AE::all((), (), sort0(), sort0()); - assert!(tc.is_def_eq(&a1, &a2).unwrap()); - } - - #[test] - fn def_eq_beta() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - // (λ x. x)(Sort 0) ≡ Sort 0 - let lam = AE::lam((), (), sort0(), AE::var(0, ())); - let app = AE::app(lam, sort0()); - assert!(tc.is_def_eq(&app, &sort0()).unwrap()); - } - - #[test] - fn def_eq_delta_unfold() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - // id(Sort 0) ≡ Sort 0 (via delta + beta) - let id_app = AE::app(AE::cnst(mk_id("id"), Box::new([])), sort0()); - assert!(tc.is_def_eq(&id_app, &sort0()).unwrap()); - } - - #[test] - fn def_eq_cache_hit() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - let a = sort0(); - let b = AE::sort(AU::zero()); - assert!(tc.is_def_eq(&a, &b).unwrap()); - // Second call should hit cache - assert!(tc.is_def_eq(&a, &b).unwrap()); - } - - #[test] - fn def_eq_closed_cache_ignores_context_across_checkers() { - let mut env = env_with_id(); - let a = AE::app(AE::cnst(mk_id("id"), Box::new([])), sort0()); - let b = sort0(); - - let mut tc1 = TypeChecker::new(&mut env); - assert!(tc1.is_def_eq(&a, &b).unwrap()); - let cache_len = env.def_eq_cache.len(); - - let mut tc2 = TypeChecker::new(&mut env); - tc2.push_local(sort1()); - assert!(tc2.is_def_eq(&a, &b).unwrap()); - assert_eq!(env.def_eq_cache.len(), cache_len); - } - - #[test] - fn def_eq_open_cache_uses_relevant_context_suffix() { - let mut env = env_with_id(); - let id = AE::cnst(mk_id("id"), Box::new([])); - let v0 = AE::var(0, ()); - let id_v0 = AE::app(id, v0.clone()); - - { - let mut tc1 = TypeChecker::new(&mut env); - tc1.push_local(sort0()); // irrelevant outer frame - tc1.push_local(sort0()); // relevant innermost frame - assert!(tc1.is_def_eq(&id_v0, &v0).unwrap()); - } - let cache_len = env.def_eq_cache.len(); - - { - let mut tc2 = TypeChecker::new(&mut env); - tc2.push_local(sort1()); // different irrelevant outer frame - tc2.push_local(sort0()); // same relevant innermost suffix - assert!(tc2.is_def_eq(&id_v0, &v0).unwrap()); - } - - assert_eq!( - env.def_eq_cache.len(), - cache_len, - "open def-eq cache should ignore irrelevant outer context frames" - ); - } - - // ========================================================================= - // Tier 3: proof irrelevance - // - // Two terms whose types live in Prop (Sort 0) are definitionally equal - // regardless of their value structure. Terms whose types live in Type - // (Sort ≥ 1) must match structurally. - // ========================================================================= - - /// Env with `P : Prop`, `p1 p2 : P`, `T : Type`, `a1 a2 : T`. - fn env_with_prop_and_type_axioms() -> KEnv { - let mut env = KEnv::new(); - - // P : Prop - env.insert( - mk_id("P"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: sort0(), // Sort 0 = Prop - }, - ); - // T : Type - env.insert( - mk_id("T"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: AE::sort(AU::succ(AU::zero())), // Sort 1 = Type - }, - ); - // p1, p2 : P - for name in ["p1", "p2"] { - env.insert( - mk_id(name), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: AE::cnst(mk_id("P"), Box::new([])), - }, - ); - } - // a1, a2 : T - for name in ["a1", "a2"] { - env.insert( - mk_id(name), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: AE::cnst(mk_id("T"), Box::new([])), - }, - ); - } - env - } - - #[test] - fn def_eq_proof_irrelevance_prop() { - // Two structurally distinct proofs of the same Prop type are def-eq. - let mut env = env_with_prop_and_type_axioms(); - let mut tc = TypeChecker::new(&mut env); - let p1 = AE::cnst(mk_id("p1"), Box::new([])); - let p2 = AE::cnst(mk_id("p2"), Box::new([])); - assert!(tc.is_def_eq(&p1, &p2).unwrap()); - } - - #[test] - fn def_eq_proof_irrelevance_symmetric() { - let mut env = env_with_prop_and_type_axioms(); - let mut tc = TypeChecker::new(&mut env); - let p1 = AE::cnst(mk_id("p1"), Box::new([])); - let p2 = AE::cnst(mk_id("p2"), Box::new([])); - assert!(tc.is_def_eq(&p1, &p2).unwrap()); - assert!(tc.is_def_eq(&p2, &p1).unwrap()); - } - - #[test] - fn def_eq_no_irrelevance_for_type_level() { - // Proof irrelevance must NOT apply to Type-valued terms. - let mut env = env_with_prop_and_type_axioms(); - let mut tc = TypeChecker::new(&mut env); - let a1 = AE::cnst(mk_id("a1"), Box::new([])); - let a2 = AE::cnst(mk_id("a2"), Box::new([])); - assert!(!tc.is_def_eq(&a1, &a2).unwrap()); - } - - // ========================================================================= - // Tier 5: unit-like types - // - // An inductive with 0 indices, 1 constructor with 0 fields, and `is_rec - // = false` is a "unit-like" type. Any two values of such a type are - // def-eq (both reduce to the unique constructor). - // ========================================================================= - - /// Env with `Unit : Sort 0` (0 indices, 1 ctor Unit.mk with 0 fields). - fn env_with_unit_like() -> KEnv { - let mut env = KEnv::new(); - - // Unit.mk : Unit - env.insert( - mk_id("Unit.mk"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Unit"), - cidx: 0, - params: 0, - fields: 0, - ty: AE::cnst(mk_id("Unit"), Box::new([])), - }, - ); - // Unit : Prop (make it a Prop inductive so proof irrelevance is out of the - // picture and we exercise try_def_eq_unit specifically) - env.insert( - mk_id("Unit"), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: mk_id("Unit"), - member_idx: 0, - ty: AE::sort(AU::succ(AU::zero())), - ctors: vec![mk_id("Unit.mk")], - lean_all: (), - }, - ); - // Two different proof-style terms of Unit, both reducing to Unit.mk. - for name in ["u1", "u2"] { - env.insert( - mk_id(name), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: AE::cnst(mk_id("Unit"), Box::new([])), - }, - ); - } - env - } - - #[test] - fn def_eq_unit_like_distinct_values() { - // Two distinct inhabitants of a unit-like inductive are def-eq. - let mut env = env_with_unit_like(); - let mut tc = TypeChecker::new(&mut env); - let u1 = AE::cnst(mk_id("u1"), Box::new([])); - let u2 = AE::cnst(mk_id("u2"), Box::new([])); - assert!(tc.is_def_eq(&u1, &u2).unwrap()); - } - - #[test] - fn def_eq_unit_like_ctor_and_opaque() { - // The explicit constructor and an opaque axiom of the same unit-like - // type are def-eq. - let mut env = env_with_unit_like(); - let mut tc = TypeChecker::new(&mut env); - let mk = AE::cnst(mk_id("Unit.mk"), Box::new([])); - let u1 = AE::cnst(mk_id("u1"), Box::new([])); - assert!(tc.is_def_eq(&mk, &u1).unwrap()); - } - - // ========================================================================= - // Tier 5: eta expansion for lambdas - // - // `f` def-eq `λ x, f x` when `f`'s type is a forall. - // ========================================================================= - - /// Env with `A : Type 0`, `B : Type 0`, `f : A → B`. - fn env_with_fun() -> KEnv { - let mut env = KEnv::new(); - env.insert( - mk_id("A"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: AE::sort(AU::succ(AU::zero())), - }, - ); - env.insert( - mk_id("B"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: AE::sort(AU::succ(AU::zero())), - }, - ); - let a_cnst = AE::cnst(mk_id("A"), Box::new([])); - let b_cnst = AE::cnst(mk_id("B"), Box::new([])); - // A → B = ∀ (_ : A), B (since the body doesn't mention the bound var, - // using Var(1) in codomain would be wrong; Var-free B is correct). - let arrow_ab = AE::all((), (), a_cnst, b_cnst); - env.insert( - mk_id("f"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: arrow_ab, - }, - ); - env - } - - #[test] - fn def_eq_eta_lambda_wraps_function() { - // f ≡ λ (x : A), f x - let mut env = env_with_fun(); - let mut tc = TypeChecker::new(&mut env); - let f = AE::cnst(mk_id("f"), Box::new([])); - // Lifting `f` by 1 is a no-op because it's closed. - let eta = AE::lam( - (), - (), - AE::cnst(mk_id("A"), Box::new([])), - AE::app(f.clone(), AE::var(0, ())), - ); - assert!(tc.is_def_eq(&f, &eta).unwrap()); - } - - #[test] - fn def_eq_eta_lambda_symmetric() { - // λ x, f x ≡ f (reverse direction) - let mut env = env_with_fun(); - let mut tc = TypeChecker::new(&mut env); - let f = AE::cnst(mk_id("f"), Box::new([])); - let eta = AE::lam( - (), - (), - AE::cnst(mk_id("A"), Box::new([])), - AE::app(f.clone(), AE::var(0, ())), - ); - assert!(tc.is_def_eq(&eta, &f).unwrap()); - } - - #[test] - fn def_eq_eta_lambda_fails_on_non_function() { - // `a : A` is not a function — η-expanding makes no sense, must NOT fire. - let mut env = env_with_fun(); - env.insert( - mk_id("a"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: AE::cnst(mk_id("A"), Box::new([])), - }, - ); - let mut tc = TypeChecker::new(&mut env); - let a = AE::cnst(mk_id("a"), Box::new([])); - // A bogus "eta-like" wrapping of a non-function. - let bogus = AE::lam( - (), - (), - AE::cnst(mk_id("A"), Box::new([])), - AE::app(a.clone(), AE::var(0, ())), - ); - assert!(!tc.is_def_eq(&a, &bogus).unwrap()); - } - - // ========================================================================= - // Tier 5: struct eta - // - // For a struct-like inductive (non-recursive, 0 indices, single 0-field - // constructor? — here use a 2-field struct), a term `t` is def-eq to - // `Mk (t.1) (t.2)` via struct-eta. - // ========================================================================= - - /// Env with `Pair : Type 0` whose only ctor `Pair.mk : A → B → Pair`. - fn env_with_pair_struct() -> KEnv { - let mut env = KEnv::new(); - - env.insert( - mk_id("A"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: AE::sort(AU::succ(AU::zero())), - }, - ); - env.insert( - mk_id("B"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: AE::sort(AU::succ(AU::zero())), - }, - ); - // Pair : Type (non-recursive, 0 indices, 1 ctor) - env.insert( - mk_id("Pair"), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: mk_id("Pair"), - member_idx: 0, - ty: AE::sort(AU::succ(AU::zero())), - ctors: vec![mk_id("Pair.mk")], - lean_all: (), - }, - ); - let a_cnst = AE::cnst(mk_id("A"), Box::new([])); - let b_cnst = AE::cnst(mk_id("B"), Box::new([])); - let pair_cnst = AE::cnst(mk_id("Pair"), Box::new([])); - // Pair.mk : A → B → Pair - env.insert( - mk_id("Pair.mk"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Pair"), - cidx: 0, - params: 0, - fields: 2, - ty: AE::all((), (), a_cnst, AE::all((), (), b_cnst, pair_cnst)), - }, - ); - // a : A, b : B, p : Pair - env.insert( - mk_id("a"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: AE::cnst(mk_id("A"), Box::new([])), - }, - ); - env.insert( - mk_id("b"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: AE::cnst(mk_id("B"), Box::new([])), - }, - ); - env.insert( - mk_id("p"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: AE::cnst(mk_id("Pair"), Box::new([])), - }, - ); - env - } - - #[test] - fn def_eq_struct_eta_via_projections() { - // p ≡ Pair.mk p.1 p.2 - let mut env = env_with_pair_struct(); - let mut tc = TypeChecker::new(&mut env); - let p = AE::cnst(mk_id("p"), Box::new([])); - let proj0 = AE::prj(mk_id("Pair"), 0, p.clone()); - let proj1 = AE::prj(mk_id("Pair"), 1, p.clone()); - let mk_app = - AE::app(AE::app(AE::cnst(mk_id("Pair.mk"), Box::new([])), proj0), proj1); - assert!(tc.is_def_eq(&p, &mk_app).unwrap()); - } -} diff --git a/crates/kernel/src/def_eq/application.rs b/crates/kernel/src/def_eq/application.rs deleted file mode 100644 index a0682f29a..000000000 --- a/crates/kernel/src/def_eq/application.rs +++ /dev/null @@ -1,159 +0,0 @@ -//! Positive-only application congruence with an explicit postorder worklist. -//! -//! Invariant: a Finish frame is reached ONLY after both child comparisons -//! succeeded in the frame's original context. Thus every published equality -//! follows from application congruence, not from a pending/visited-pair guess. -//! See Ix/Tc/Verify/DefEq/SpineArguments.lean (TrAppSpine.defEq_of_zip) for the -//! corresponding semantic rule. That theorem does not certify this Rust loop. -//! -//! No binders are opened here. All non-App pairs use ordinary conversion, -//! with recursive worklist entry disabled. A failed child abandons the probe; -//! it does NOT imply inequality of its applications (functions may ignore -//! arguments). The caller retains the original conversion path on every miss. - -use super::*; -use crate::env::CtxAddr; -use crate::equiv::EqKey; - -const APP_CONGRUENCE_FUEL: u64 = 4_096; -// Ordinary conversion is usually cheaper than speculative congruence. Use -// the worklist as a near-guard fallback, not a general structural fast path: -// a shallow trigger can spend much more work on arguments that reduction -// would erase. Reserve stack headroom for the ordinary leaf comparisons. -// This scheduling choice changes neither equality rules nor the depth cap. -const APP_CONGRUENCE_MIN_DEPTH: u32 = MAX_DEF_EQ_DEPTH - 512; - -/// Compute these in the original context, before calling any leaf reducer. -/// Only positive entries are published, so FULL may also consume cheap-mode -/// successes, just as in is_def_eq. Negative results are never published here. -struct CompletedAppKeys { - cache: (Addr, Addr, CtxAddr), - left: EqKey, - right: EqKey, - cheap: bool, -} - -enum Task { - Compare(KExpr, KExpr), - Finish(CompletedAppKeys), -} - -impl TypeChecker<'_, M> { - #[inline] - pub(super) fn try_app_congruence( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result> { - if self.def_eq_depth < APP_CONGRUENCE_MIN_DEPTH { - return Ok(false); - } - self.app_congruence_probe(a, b) - } - - #[cold] - #[inline(never)] - fn app_congruence_probe( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result> { - if self.in_app_congruence - || !matches!((a.data(), b.data()), (ExprData::App(..), ExprData::App(..))) - { - return Ok(false); - } - // Bound failed speculation, including leaf reductions, without changing - // the constant's global allowance. Pending frames consume O(local_fuel) - // space: each expansion is ticked before pushing at most three tasks. - let saved_fuel = self.rec_fuel; - let local_fuel = saved_fuel.min(APP_CONGRUENCE_FUEL); - self.rec_fuel = local_fuel; - self.in_app_congruence = true; - let result = self.app_congruence_worklist(a, b); - self.in_app_congruence = false; - let consumed = local_fuel.saturating_sub(self.rec_fuel); - self.rec_fuel = saved_fuel.saturating_sub(consumed); - match result { - Err(TcError::MaxRecDepth | TcError::MaxRecFuel) => Ok(false), - other => other, - } - } - - fn app_congruence_worklist( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result> { - let mut work = vec![Task::Compare(a.clone(), b.clone())]; - while let Some(task) = work.pop() { - let (a, b) = match task { - Task::Finish(keys) => { - // Both children completed. No pending parent can take this path - // after a miss/error, because those return from the entire loop. - self.env.def_eq_cache.insert(keys.cache, true); - if keys.cheap { - self.env.def_eq_cheap_cache.insert(keys.cache, true); - } - self.equiv_manager.add_equiv(keys.left, keys.right); - continue; - }, - Task::Compare(a, b) => (a, b), - }; - if a.ptr_eq(&b) || a.hash_key() == b.hash_key() { - continue; - } - let (ExprData::App(af, aa, _), ExprData::App(bf, ba, _)) = - (a.data(), b.data()) - else { - if !self.is_def_eq(&a, &b)? { - return Ok(false); - } - continue; - }; - - crate::profile::bump_def_eq(); - let lbr = a.lbr().max(b.lbr()); - let ctx = self.def_eq_ctx_key(&a, &b); - let (lo, hi) = canonical_pair(a.hash_key(), b.hash_key()); - let keys = CompletedAppKeys { - cache: (lo, hi, ctx), - left: EqKey::new(a.hash_key(), ctx, lbr, a.lbr()), - right: EqKey::new(b.hash_key(), ctx, lbr, b.lbr()), - cheap: self.cheap_recursion_depth > 0, - }; - // Completed equality is enough to skip a shared DAG branch. In-flight - // frames are deliberately absent from both caches and the union-find. - if self.equiv_manager.is_equiv(&keys.left, &keys.right) - || self.env.def_eq_cache.get(&keys.cache) == Some(&true) - || (keys.cheap - && self.env.def_eq_cheap_cache.get(&keys.cache) == Some(&true)) - { - self.env.perf.record_def_eq_hit(); - continue; - } - // A cached inequality makes this congruence attempt unproductive. - // Abandon the probe, NOT its enclosing application comparison: a - // surrounding function can still ignore this unequal child. - if self.env.def_eq_cache.get(&keys.cache) == Some(&false) - || (keys.cheap - && self.env.def_eq_cheap_cache.get(&keys.cache) == Some(&false)) - { - self.env.perf.record_def_eq_hit(); - return Ok(false); - } - self.env.perf.record_def_eq_miss(); - self.tick()?; - // LIFO order: function first, then argument, then completion. Each - // argument is checked after its function prefix, preserving dependent - // application order. The caller-held roots keep descendants alive. - work.push(Task::Finish(keys)); - work.push(Task::Compare(aa.clone(), ba.clone())); - work.push(Task::Compare(af.clone(), bf.clone())); - } - Ok(true) - } -} - -#[cfg(test)] -mod tests; diff --git a/crates/kernel/src/def_eq/application/tests.rs b/crates/kernel/src/def_eq/application/tests.rs deleted file mode 100644 index 3f99dcddf..000000000 --- a/crates/kernel/src/def_eq/application/tests.rs +++ /dev/null @@ -1,552 +0,0 @@ -use super::*; -use crate::{ - constant::KConst, - env::KEnv, - mode::{Anon, Meta}, -}; -use ix_common::{ - address::Address, - env::{BinderInfo, DefinitionSafety, Name, ReducibilityHints}, -}; - -fn id(s: &str) -> KId { - KId::new( - Address::hash(s.as_bytes()), - M::meta_field(Name::str(Name::anon(), s.to_owned())), - ) -} - -fn cnst(s: &str) -> KExpr { - KExpr::cnst(id(s), Box::new([])) -} - -fn arrow(a: KExpr, b: KExpr) -> KExpr { - KExpr::all( - M::meta_field(Name::anon()), - M::meta_field(BinderInfo::Default), - a, - b, - ) -} - -fn lam(body: KExpr) -> KExpr { - KExpr::lam( - M::meta_field(Name::anon()), - M::meta_field(BinderInfo::Default), - cnst("A"), - body, - ) -} - -fn axiom(env: &mut KEnv, name: &str, ty: KExpr) { - env.insert( - id(name), - KConst::Axio { - name: M::meta_field(Name::anon()), - level_params: M::meta_field(vec![]), - is_unsafe: false, - lvls: 0, - ty, - }, - ); -} - -fn defn( - env: &mut KEnv, - name: &str, - ty: KExpr, - val: KExpr, -) { - env.insert( - id(name), - KConst::Defn { - name: M::meta_field(Name::anon()), - level_params: M::meta_field(vec![]), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(7), - lvls: 0, - ty, - val, - lean_all: M::meta_field(vec![]), - block: id(name), - }, - ); -} - -fn setup() -> KEnv { - let mut env = KEnv::new(); - axiom(&mut env, "A", KExpr::sort(KUniv::succ(KUniv::zero()))); - axiom(&mut env, "a", cnst("A")); - axiom(&mut env, "b", cnst("A")); - axiom(&mut env, "F", arrow(cnst("A"), cnst("A"))); - axiom(&mut env, "G", arrow(cnst("A"), arrow(cnst("A"), cnst("A")))); - defn(&mut env, "alias", cnst("A"), cnst("a")); - defn( - &mut env, - "id", - arrow(cnst("A"), cnst("A")), - lam(KExpr::var(0, M::meta_field(Name::anon()))), - ); - defn(&mut env, "ignore", arrow(cnst("A"), cnst("A")), lam(cnst("a"))); - env -} - -fn app( - tc: &mut TypeChecker<'_, M>, - f: KExpr, - a: KExpr, -) -> KExpr { - tc.intern(KExpr::app(f, a)) -} - -fn pair_key( - tc: &mut TypeChecker<'_, M>, - a: &KExpr, - b: &KExpr, -) -> (Addr, Addr, CtxAddr) { - let (lo, hi) = canonical_pair(a.hash_key(), b.hash_key()); - (lo, hi, tc.def_eq_ctx_key(a, b)) -} - -fn chain( - tc: &mut TypeChecker<'_, M>, - n: usize, -) -> (KExpr, KExpr) { - let f = tc.intern(cnst("F")); - let mut a = tc.intern(cnst("alias")); - let mut b = tc.intern(cnst("a")); - for _ in 0..n { - a = app(tc, f.clone(), a); - b = app(tc, f.clone(), b); - } - (a, b) -} - -fn deep_chain() { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - let (a, b) = chain(&mut tc, MAX_DEF_EQ_DEPTH as usize + 100); - assert!(tc.is_def_eq(&a, &b).unwrap()); - assert!( - tc.def_eq_peak < APP_CONGRUENCE_MIN_DEPTH + 20, - "depth {}", - tc.def_eq_peak - ); - assert_eq!(tc.def_eq_depth, 0); - assert!(!tc.in_app_congruence); - - // Fresh caches and the original path: same well-typed finite chain still - // reaches the old guard. Large stack matches the real dedicated worker. - let mut old_env = setup::(); - let mut old = TypeChecker::new(&mut old_env); - old.in_app_congruence = true; - let (a, b) = chain(&mut old, MAX_DEF_EQ_DEPTH as usize + 100); - assert!(matches!(old.is_def_eq(&a, &b), Err(TcError::MaxRecDepth))); - assert_eq!(old.def_eq_depth, 0); -} - -#[test] -fn long_application_chain_uses_worklist_not_def_eq_stack() { - std::thread::Builder::new() - .stack_size(256 * 1024 * 1024) - .spawn(|| { - deep_chain::(); - deep_chain::(); - }) - .unwrap() - .join() - .unwrap(); -} - -fn partial_success() { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - let left_inner = app(&mut tc, cnst("F"), cnst("alias")); - let right_inner = app(&mut tc, cnst("F"), cnst("a")); - let left_fn = app(&mut tc, cnst("G"), left_inner); - let right_fn = app(&mut tc, cnst("G"), right_inner); - let a = app(&mut tc, left_fn.clone(), cnst("a")); - let b = app(&mut tc, right_fn.clone(), cnst("b")); - let root_key = pair_key(&mut tc, &a, &b); - let fn_key = pair_key(&mut tc, &left_fn, &right_fn); - assert!(!tc.app_congruence_probe(&a, &b).unwrap()); - assert_eq!(tc.env.def_eq_cache.get(&fn_key), Some(&true)); - assert!(!tc.env.def_eq_cache.contains_key(&root_key)); - assert!(!tc.equiv_manager.is_equiv( - &EqKey::new(a.hash_key(), root_key.2, 0, 0), - &EqKey::new(b.hash_key(), root_key.2, 0, 0), - )); - assert!(!tc.is_def_eq(&a, &b).unwrap()); - assert!(!tc.is_def_eq(&b, &a).unwrap()); -} - -#[test] -fn failed_child_never_completes_pending_parent() { - partial_success::(); - partial_success::(); -} - -fn non_injective() { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - tc.def_eq_depth = APP_CONGRUENCE_MIN_DEPTH; - let a = app(&mut tc, cnst("ignore"), cnst("a")); - let b = app(&mut tc, cnst("ignore"), cnst("b")); - let key = pair_key(&mut tc, &a, &b); - assert!(!tc.app_congruence_probe(&a, &b).unwrap()); - assert!(!tc.env.def_eq_cache.contains_key(&key)); - assert!(tc.is_def_eq(&a, &b).unwrap()); -} - -#[test] -fn unequal_arguments_of_ignoring_function_fall_back_to_reduction() { - non_injective::(); - non_injective::(); -} - -fn diamond() { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - let g = tc.intern(cnst("G")); - let (mut a, mut b) = (tc.intern(cnst("alias")), tc.intern(cnst("a"))); - for _ in 0..64 { - let af = app(&mut tc, g.clone(), a.clone()); - let bf = app(&mut tc, g.clone(), b.clone()); - a = app(&mut tc, af, a); - b = app(&mut tc, bf, b); - } - tc.rec_fuel = 1_000; - assert!(tc.app_congruence_probe(&a, &b).unwrap()); - assert!(tc.rec_fuel > 700, "completed DAG branches must not be revisited"); -} - -#[test] -fn shared_diamond_uses_only_completed_equalities() { - diamond::(); - diamond::(); -} - -fn budgets() { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - let (a, b) = chain(&mut tc, 4); - let key = pair_key(&mut tc, &a, &b); - tc.rec_fuel = 1; - assert!(!tc.app_congruence_probe(&a, &b).unwrap()); - assert_eq!(tc.rec_fuel, 0); - assert!(!tc.in_app_congruence); - assert!(!tc.env.def_eq_cache.contains_key(&key)); - assert_eq!(tc.def_eq_depth, 0); - - tc.rec_fuel = 10_000; - tc.def_eq_depth = MAX_DEF_EQ_DEPTH; - assert!(!tc.app_congruence_probe(&a, &b).unwrap()); - assert_eq!(tc.def_eq_depth, MAX_DEF_EQ_DEPTH); - assert!(tc.rec_fuel < 10_000); - assert!(!tc.in_app_congruence); - assert!(!tc.env.def_eq_cache.contains_key(&key)); - tc.def_eq_depth = 0; - assert!(tc.is_def_eq(&a, &b).unwrap()); - - let (a, b) = - chain(&mut tc, usize::try_from(APP_CONGRUENCE_FUEL).unwrap() + 100); - let key = pair_key(&mut tc, &a, &b); - tc.rec_fuel = 20_000; - assert!(!tc.app_congruence_probe(&a, &b).unwrap()); - assert_eq!(tc.rec_fuel, 20_000 - APP_CONGRUENCE_FUEL); - assert!(!tc.in_app_congruence); - assert!(!tc.env.def_eq_cache.contains_key(&key)); -} - -#[test] -fn budget_and_depth_misses_restore_state_without_refunds() { - std::thread::Builder::new() - .stack_size(256 * 1024 * 1024) - .spawn(|| { - budgets::(); - budgets::(); - }) - .unwrap() - .join() - .unwrap(); -} - -fn malformed() { - let mut env = setup::(); - defn( - &mut env, - "bad", - cnst("A"), - KExpr::sort(KUniv::param(1, M::meta_field(Name::anon()))), - ); - let mut tc = TypeChecker::new(&mut env); - let a = - app(&mut tc, cnst("F"), KExpr::cnst(id("bad"), Box::new([KUniv::zero()]))); - let b = app(&mut tc, cnst("F"), cnst("a")); - let key = pair_key(&mut tc, &a, &b); - let fuel = tc.rec_fuel; - assert!(matches!( - tc.app_congruence_probe(&a, &b), - Err(TcError::UnivParamOutOfRange { .. }) - )); - assert!(!tc.in_app_congruence); - assert_eq!(tc.def_eq_depth, 0); - assert!(tc.rec_fuel < fuel); - assert!(!tc.env.def_eq_cache.contains_key(&key)); -} - -#[test] -fn non_budget_leaf_error_propagates_without_parent_cache_entry() { - malformed::(); - malformed::(); -} - -fn contexts() { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - let a = app(&mut tc, cnst("F"), KExpr::var(0, M::meta_field(Name::anon()))); - let b = app(&mut tc, cnst("F"), cnst("a")); - tc.push_let(cnst("A"), cnst("a")); - let first = pair_key(&mut tc, &a, &b); - assert!(tc.app_congruence_probe(&a, &b).unwrap()); - assert_eq!(tc.depth(), 1); - tc.pop_local(); - tc.push_let(cnst("A"), cnst("b")); - let second = pair_key(&mut tc, &a, &b); - assert_ne!(first, second); - assert!(!tc.app_congruence_probe(&a, &b).unwrap()); - assert!(!tc.env.def_eq_cache.contains_key(&second)); - assert!(!tc.is_def_eq(&a, &b).unwrap()); - assert_eq!(tc.depth(), 1); -} - -#[test] -fn completed_open_pair_is_not_reused_in_another_context() { - contexts::(); - contexts::(); -} - -fn modes() { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - let (a, b) = chain(&mut tc, 4); - let key = pair_key(&mut tc, &a, &b); - // A failed cheap attempt is not a full-mode inequality certificate. - tc.env.def_eq_cheap_cache.insert(key, false); - assert!(tc.app_congruence_probe(&a, &b).unwrap()); - assert_eq!(tc.env.def_eq_cache.get(&key), Some(&true)); - tc.env.clear_reduction_caches(); - tc.equiv_manager.clear(); - tc.cheap_recursion_depth = 1; - tc.infer_only = true; - tc.eager_reduce = true; - assert!(tc.app_congruence_probe(&a, &b).unwrap()); - assert_eq!(tc.env.def_eq_cheap_cache.get(&key), Some(&true)); - assert_eq!(tc.env.def_eq_cache.get(&key), Some(&true)); - assert_eq!(tc.cheap_recursion_depth, 1); - assert!(tc.infer_only && tc.eager_reduce); - assert_eq!(tc.depth(), 0); -} - -#[test] -fn mode_boundaries_and_positive_cache_promotion_are_preserved() { - modes::(); - modes::(); -} - -fn dependent() { - let mut env = setup::(); - axiom( - &mut env, - "B", - arrow(cnst("A"), KExpr::sort(KUniv::succ(KUniv::zero()))), - ); - let b_a = KExpr::app(cnst("B"), cnst("a")); - axiom(&mut env, "v", b_a); - let b_alias = KExpr::app(cnst("B"), cnst("alias")); - defn(&mut env, "vAlias", b_alias, cnst("v")); - axiom( - &mut env, - "dep", - arrow( - cnst("A"), - arrow( - KExpr::app(cnst("B"), KExpr::var(0, M::meta_field(Name::anon()))), - cnst("A"), - ), - ), - ); - let mut tc = TypeChecker::new(&mut env); - let af = app(&mut tc, cnst("dep"), cnst("alias")); - let bf = app(&mut tc, cnst("dep"), cnst("a")); - let a = app(&mut tc, af, cnst("vAlias")); - let b = app(&mut tc, bf, cnst("v")); - tc.infer(&a).unwrap(); - tc.infer(&b).unwrap(); - assert!(tc.app_congruence_probe(&a, &b).unwrap()); - assert!(tc.is_def_eq(&a, &b).unwrap()); -} - -#[test] -fn dependent_spine_compares_prefix_before_later_arguments() { - dependent::(); - dependent::(); -} - -#[test] -fn probe_does_not_nest_and_reset_clears_its_flag() { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - let (a, b) = chain(&mut tc, 4); - tc.in_app_congruence = true; - let fuel = tc.rec_fuel; - assert!(!tc.app_congruence_probe(&a, &b).unwrap()); - assert!(tc.in_app_congruence); - assert_eq!(tc.rec_fuel, fuel); - tc.reset(); - assert!(!tc.in_app_congruence); -} - -#[test] -fn shallow_comparisons_do_not_pay_for_speculative_work() { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - let (a, b) = chain(&mut tc, 4); - tc.def_eq_depth = APP_CONGRUENCE_MIN_DEPTH - 1; - let fuel = tc.rec_fuel; - assert!(!tc.try_app_congruence(&a, &b).unwrap()); - assert_eq!(tc.rec_fuel, fuel); - assert!(tc.env.def_eq_cache.is_empty()); - tc.def_eq_depth += 1; - assert!(tc.try_app_congruence(&a, &b).unwrap()); - assert_eq!(tc.def_eq_depth, APP_CONGRUENCE_MIN_DEPTH); -} - -fn below_guard_matches_original() { - for n in [1, 64, 128] { - let check = |old: bool| { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - tc.in_app_congruence = old; - let (a, b) = chain(&mut tc, n); - assert!(tc.is_def_eq(&a, &b).unwrap()); - assert!(tc.def_eq_peak < APP_CONGRUENCE_MIN_DEPTH); - (tc.fuel_used(), tc.def_eq_peak) - }; - assert_eq!(check(false), check(true), "chain length {n}"); - } -} - -#[test] -fn ordinary_comparisons_preserve_recursive_work_and_depth() { - // Depth 64 was enough to activate the earlier eager variant. These - // successful ordinary checks should do exactly the original work now. - below_guard_matches_original::(); - below_guard_matches_original::(); -} - -#[test] -fn cached_unequal_child_only_abandons_the_probe() { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - let a = app(&mut tc, cnst("F"), cnst("a")); - let b = app(&mut tc, cnst("F"), cnst("b")); - assert!(!tc.is_def_eq(&a, &b).unwrap()); - let left = app(&mut tc, cnst("ignore"), a); - let right = app(&mut tc, cnst("ignore"), b); - let fuel = tc.rec_fuel; - let key = pair_key(&mut tc, &left, &right); - assert!(!tc.app_congruence_probe(&left, &right).unwrap()); - assert_eq!(tc.rec_fuel, fuel - 1, "the cached child must not be expanded"); - assert!(!tc.env.def_eq_cache.contains_key(&key)); - assert!(tc.is_def_eq(&left, &right).unwrap()); -} - -fn generated( - tc: &mut TypeChecker<'_, M>, - seed: u64, - depth: u64, -) -> KExpr { - if depth == 0 { - return tc.intern(cnst(["a", "b", "alias"][(seed % 3) as usize])); - } - let child = generated(tc, seed / 3 + 1, depth - 1); - match seed % 4 { - 0 => app(tc, cnst("F"), child), - 1 => app(tc, cnst("id"), child), - 2 => app(tc, cnst("ignore"), child), - _ => { - let f = app(tc, cnst("G"), child.clone()); - app(tc, f, child) - }, - } -} - -fn differential() { - for n in 0..96 { - let check = |old: bool| { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - tc.in_app_congruence = old; - // Exercise the worklist even on these small terms. Both variants - // start at the same depth; only the old path disables the probe. - tc.def_eq_depth = APP_CONGRUENCE_MIN_DEPTH; - let a = generated(&mut tc, n * 17 + 3, 5); - let b = generated(&mut tc, n * 13 + 3, 5); - tc.is_def_eq(&a, &b).unwrap() - }; - assert_eq!(check(false), check(true), "seed {n}"); - } -} - -#[test] -fn small_well_typed_terms_match_original_recursive_conversion() { - differential::(); - differential::(); -} - -fn universes_and_arity() { - let mut env = setup::(); - // An unused universe parameter leaves both applications well typed, but - // different instantiations of an irreducible axiom are not def-eq heads. - env.insert( - id("poly"), - KConst::Axio { - name: M::meta_field(Name::anon()), - level_params: M::meta_field(vec![Name::str( - Name::anon(), - "u".to_owned(), - )]), - is_unsafe: false, - lvls: 1, - ty: arrow(cnst("A"), cnst("A")), - }, - ); - let mut tc = TypeChecker::new(&mut env); - tc.def_eq_depth = APP_CONGRUENCE_MIN_DEPTH; - let a = - app(&mut tc, KExpr::cnst(id("poly"), Box::new([KUniv::zero()])), cnst("a")); - let b = app( - &mut tc, - KExpr::cnst(id("poly"), Box::new([KUniv::succ(KUniv::zero())])), - cnst("a"), - ); - tc.infer(&a).unwrap(); - tc.infer(&b).unwrap(); - assert_eq!(tc.try_same_head_spine(&a, &b).unwrap(), None); - assert!(!tc.app_congruence_probe(&a, &b).unwrap()); - assert!(!tc.is_def_eq(&a, &b).unwrap()); - - let partial = app(&mut tc, cnst("G"), cnst("a")); - let full = app(&mut tc, partial.clone(), cnst("b")); - tc.infer(&partial).unwrap(); - tc.infer(&full).unwrap(); - assert_eq!(tc.try_same_head_spine(&partial, &full).unwrap(), None); -} - -#[test] -fn same_head_is_not_enough_without_matching_universes_and_arity() { - universes_and_arity::(); - universes_and_arity::(); -} diff --git a/crates/kernel/src/def_eq/binders.rs b/crates/kernel/src/def_eq/binders.rs deleted file mode 100644 index d07f7c261..000000000 --- a/crates/kernel/src/def_eq/binders.rs +++ /dev/null @@ -1,222 +0,0 @@ -//! Positive-only batched lambda/forall congruence. -//! -//! Domains are compared under the already-accepted prefix, before adding -//! the SAME fresh local to both sides. The terminal bodies are opened once. -//! Each accepted binder therefore follows the existing single-binder rule. -//! See Ix/Tc/Verify/DefEq/Structural.lean (quickBinder_wf) and the lamDF / -//! forallEDF rules in lean4lean. Those proofs do not certify this Rust loop. -//! -//! This is not a complete conversion procedure for binder terms: skipping -//! intermediate conversion can miss reduction/proof-irrelevance/cache wins. -//! A false or exhausted probe falls back to the original single-binder path, -//! with batching disabled throughout that fallback. No pending/suffix pair -//! is published equal, and no failed probe is published as an inequality. - -use super::*; -use ix_common::env::{BinderInfo, Name}; -use ixon::contract::{BinderContract, ValueContract}; - -const BINDER_BATCH_MIN_LENGTH: usize = 4; -const BINDER_BATCH_FUEL: u64 = 4_096; - -struct BinderPair<'a, M: KernelMode> { - name: &'a M::MField, - bi: &'a M::MField, - left_contract: BinderContract, - right_contract: BinderContract, - left_result: ValueContract, - left_ty: &'a KExpr, - right_ty: &'a KExpr, - left_body: &'a KExpr, - right_body: &'a KExpr, -} - -fn binder_pair<'a, M: KernelMode>( - a: &'a KExpr, - b: &'a KExpr, -) -> Option> { - match (a.data(), b.data()) { - ( - ExprData::Lam(name, bi, left_contract, at, ab, _), - ExprData::Lam(_, _, right_contract, bt, bb, _), - ) => Some(BinderPair { - name, - bi, - left_contract: *left_contract, - right_contract: *right_contract, - left_result: ValueContract::shared(), - left_ty: at, - right_ty: bt, - left_body: ab, - right_body: bb, - }), - ( - ExprData::All(name, bi, left_contract, result, at, ab, _), - ExprData::All(_, _, right_contract, _, bt, bb, _), - ) => Some(BinderPair { - name, - bi, - left_contract: *left_contract, - right_contract: *right_contract, - left_result: *result, - left_ty: at, - right_ty: bt, - left_body: ab, - right_body: bb, - }), - _ => None, - } -} - -fn has_batch_prefix<'a, M: KernelMode>( - mut a: &'a KExpr, - mut b: &'a KExpr, -) -> bool { - for _ in 0..BINDER_BATCH_MIN_LENGTH { - let Some(pair) = binder_pair(a, b) else { return false }; - a = pair.left_body; - b = pair.right_body; - } - true -} - -impl TypeChecker<'_, M> { - pub(super) fn def_eq_binders( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result> { - if self.in_binder_batch || !has_batch_prefix(a, b) { - return self.def_eq_one_binder(a, b); - } - self.in_binder_batch = true; - let saved_fuel = self.rec_fuel; - let local_fuel = saved_fuel.min(BINDER_BATCH_FUEL); - self.rec_fuel = local_fuel; - let result = self.with_lctx_scope(|tc| tc.def_eq_binder_telescope(a, b)); - let consumed = local_fuel.saturating_sub(self.rec_fuel); - self.rec_fuel = saved_fuel.saturating_sub(consumed); - let result = match result { - Ok(false) | Err(TcError::MaxRecDepth | TcError::MaxRecFuel) => { - // Do not start another failed probe at every recursive suffix. - self.def_eq_one_binder(a, b) - }, - other => other, - }; - self.in_binder_batch = false; - result - } - - /// Original binder comparison, also the reference path for differential - /// tests. Scope cleanup occurs on success, false, and every returned error. - fn def_eq_one_binder( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result> { - let Some(pair) = binder_pair(a, b) else { return Ok(false) }; - if pair.left_contract != pair.right_contract { - return Ok(false); - } - if !self.is_def_eq(pair.left_ty, pair.right_ty)? { - return Ok(false); - } - self.with_lctx_scope(|tc| { - let id = tc.fresh_fvar_id(); - let fv = tc.intern(KExpr::fvar(id, pair.name.clone())); - tc.lctx.push( - id, - LocalDecl::CDecl { - name: pair.name.clone(), - bi: pair.bi.clone(), - ty: pair.left_ty.clone(), - contract: pair.left_contract, - result: pair.left_result, - }, - ); - let left = instantiate_rev( - &mut tc.env.intern, - pair.left_body, - std::slice::from_ref(&fv), - ); - let right = instantiate_rev(&mut tc.env.intern, pair.right_body, &[fv]); - tc.is_def_eq(&left, &right) - }) - } - - fn def_eq_binder_telescope<'a>( - &mut self, - mut a: &'a KExpr, - mut b: &'a KExpr, - ) -> Result> { - let mut fvars = Vec::new(); - loop { - // Opening the same raw expression under the same prefix yields the - // same expression. This is structural identity, not an in-flight pair. - if a.ptr_eq(b) || a.hash_key() == b.hash_key() { - return Ok(true); - } - // Closed suffixes are unchanged by opening: honor existing conversion - // caches without materializing suffixes merely to look them up. - if !fvars.is_empty() - && a.lbr() == 0 - && b.lbr() == 0 - && self.has_closed_binder_result(a, b) - { - return self.is_def_eq(a, b); - } - let Some(pair) = binder_pair(a, b) else { break }; - if pair.left_contract != pair.right_contract { - return Ok(false); - } - if !fvars.is_empty() { - // Corresponds to entering another nontrivial binder comparison. - // Keeps the iterative walk fuel-bounded without growing the stack. - self.tick()?; - crate::profile::bump_def_eq(); - } - // Each substitution has its own memo: distinct prefixes must never - // share a semantic substitution result just to reuse scratch storage. - let left_ty = instantiate_rev(&mut self.env.intern, pair.left_ty, &fvars); - let right_ty = - instantiate_rev(&mut self.env.intern, pair.right_ty, &fvars); - if !self.is_def_eq(&left_ty, &right_ty)? { - return Ok(false); - } - let id = self.fresh_fvar_id(); - let fv = self.intern(KExpr::fvar(id, pair.name.clone())); - self.lctx.push( - id, - LocalDecl::CDecl { - name: pair.name.clone(), - bi: pair.bi.clone(), - ty: left_ty, - contract: pair.left_contract, - result: pair.left_result, - }, - ); - fvars.push(fv); - a = pair.left_body; - b = pair.right_body; - } - let left = instantiate_rev(&mut self.env.intern, a, &fvars); - let right = instantiate_rev(&mut self.env.intern, b, &fvars); - self.is_def_eq(&left, &right) - } - - fn has_closed_binder_result(&mut self, a: &KExpr, b: &KExpr) -> bool { - let ctx = self.def_eq_ctx_key(a, b); - let (lo, hi) = canonical_pair(a.hash_key(), b.hash_key()); - let key = (lo, hi, ctx); - self.env.def_eq_cache.contains_key(&key) - || (self.cheap_recursion_depth > 0 - && self.env.def_eq_cheap_cache.contains_key(&key)) - || self.equiv_manager.is_equiv( - &crate::equiv::EqKey::new(a.hash_key(), ctx, 0, 0), - &crate::equiv::EqKey::new(b.hash_key(), ctx, 0, 0), - ) - } -} - -#[cfg(test)] -mod tests; diff --git a/crates/kernel/src/def_eq/binders/tests.rs b/crates/kernel/src/def_eq/binders/tests.rs deleted file mode 100644 index 9c7a81277..000000000 --- a/crates/kernel/src/def_eq/binders/tests.rs +++ /dev/null @@ -1,516 +0,0 @@ -use super::*; -use crate::env::KEnv; -use crate::level::KUniv; -use crate::mode::{Anon, Meta}; -use crate::profile::{OpCounts, take_op_counts}; -use ix_common::address::Address; -use ix_common::env::{DefinitionSafety, ReducibilityHints}; - -fn name(s: &str) -> M::MField { - M::meta_field(Name::str(Name::anon(), s.to_owned())) -} - -fn id(s: &str) -> KId { - KId::new(Address::hash(s.as_bytes()), name::(s)) -} - -fn cnst(s: &str) -> KExpr { - KExpr::cnst(id(s), Box::new([])) -} - -fn var(i: u64) -> KExpr { - KExpr::var(i, name::("original-var")) -} - -fn sort() -> KExpr { - KExpr::sort(KUniv::succ(KUniv::zero())) -} - -fn binder( - is_lam: bool, - ty: KExpr, - body: KExpr, -) -> KExpr { - let n = name::(if is_lam { "lambda" } else { "forall" }); - let bi = M::meta_field(BinderInfo::Implicit); - if is_lam { KExpr::lam(n, bi, ty, body) } else { KExpr::all(n, bi, ty, body) } -} - -fn axiom(env: &mut KEnv, s: &str, ty: KExpr) { - env.insert( - id(s), - KConst::Axio { - name: name::(s), - level_params: M::meta_field(vec![]), - is_unsafe: false, - lvls: 0, - ty, - }, - ); -} - -fn defn( - env: &mut KEnv, - s: &str, - ty: KExpr, - val: KExpr, -) { - env.insert( - id(s), - KConst::Defn { - name: name::(s), - level_params: M::meta_field(vec![]), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(7), - lvls: 0, - ty, - val, - lean_all: M::meta_field(vec![]), - block: id(s), - }, - ); -} - -fn setup() -> KEnv { - let mut env = KEnv::new(); - axiom(&mut env, "A", sort()); - axiom(&mut env, "B", sort()); - axiom(&mut env, "a", cnst("A")); - axiom(&mut env, "b", cnst("A")); - axiom( - &mut env, - "G", - binder(false, sort(), binder(false, var(0), binder(false, var(1), var(2)))), - ); - axiom(&mut env, "P", binder(false, sort(), binder(false, var(0), sort()))); - defn(&mut env, "Alias", sort(), cnst("A")); - defn(&mut env, "alias", cnst("A"), cnst("a")); - defn( - &mut env, - "id", - binder(false, cnst("A"), cnst("A")), - binder(true, cnst("A"), var(0)), - ); - defn( - &mut env, - "ignore", - binder(false, cnst("A"), cnst("A")), - binder(true, cnst("A"), cnst("a")), - ); - env -} - -fn telescope( - n: usize, - is_lam: bool, - mut body: KExpr, -) -> KExpr { - for _ in 0..n { - body = binder(is_lam, cnst("A"), body); - } - body -} - -fn compare( - a: &KExpr, - b: &KExpr, - old: bool, -) -> (bool, u64, u32, OpCounts) { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - tc.in_binder_batch = old; - let a = tc.intern(a.clone()); - let b = tc.intern(b.clone()); - take_op_counts(); - let verdict = tc.is_def_eq(&a, &b).unwrap(); - assert!(tc.lctx.is_empty()); - assert_eq!(tc.def_eq_depth, 0); - assert_eq!(tc.in_binder_batch, old); - (verdict, tc.fuel_used(), tc.def_eq_peak, take_op_counts()) -} - -fn long_positive( - count: u64, - compare_reference: bool, - report: bool, -) { - for is_lam in [false, true] { - // (T : Type) (x1 : T) ... (x_{count-1} : T). Unlike closed suffixes, - // these domains really must be traversed by repeated single opening. - let make = |alias: bool| { - // Every value binder is used, so each single opening must revisit - // the terminal DAG; merely depending on T would only traverse once. - let g = KExpr::app(cnst("G"), var(count - 1)); - let mut body = var(0); - for i in 1..count - 1 { - body = KExpr::app(KExpr::app(g.clone(), var(i)), body); - } - if !is_lam { - body = KExpr::app(KExpr::app(cnst("P"), var(count - 1)), body); - } - if alias { - let ty = if is_lam { var(count - 1) } else { sort() }; - body = KExpr::let_(name::("alias"), ty, body, var(0), false); - } - for i in (0..count).rev() { - let ty = if i == 0 { sort() } else { var(i - 1) }; - body = binder(is_lam, ty, body); - } - body - }; - let a = make(false); - let b = make(true); - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - tc.infer(&a).unwrap(); - tc.infer(&b).unwrap(); - let new = compare::(&a, &b, false); - assert!(new.0); - assert!(new.2 < 10, "batch peak {}", new.2); - if !compare_reference { - continue; - } - let old = compare::(&a, &b, true); - if report { - println!( - "mode={} lambda={is_lam} subst={}->{} intern={}->{} fuel={}->{} peak={}->{}", - std::any::type_name::(), - old.3.subst_nodes, - new.3.subst_nodes, - old.3.intern_nodes, - new.3.intern_nodes, - old.1, - new.1, - old.2, - new.2, - ); - } - assert!(old.0); - assert!(u64::from(old.2) >= count); - assert!( - new.3.subst_nodes * 4 < old.3.subst_nodes, - "subst {} vs {}", - new.3.subst_nodes, - old.3.subst_nodes - ); - } -} - -#[test] -fn long_telescope_opens_terminal_bodies_once() { - // The deliberately recursive reference needs one native frame per - // binder. Keep this differential/work-counter fixture within the normal - // test stack; exercise the full deep case on the production path below. - long_positive::(32, true, false); - long_positive::(32, true, false); -} - -#[test] -fn deep_batched_telescope_uses_default_stack() { - // Do not spawn a large-stack worker or require RUST_MIN_STACK here. - long_positive::(128, false, false); - long_positive::(128, false, false); -} - -#[test] -#[ignore = "manual paired work-counter report; not a wall-time benchmark"] -fn report_dependent_telescope_work() { - long_positive::(32, true, true); - long_positive::(32, true, true); -} - -fn dependent() { - let mut env = setup::(); - // idPoly : (T : Type) -> T -> T - let id_ty = binder(false, sort(), binder(false, var(0), var(1))); - let id_val = binder(true, sort(), binder(true, var(0), var(0))); - defn(&mut env, "idPoly", id_ty, id_val); - for is_lam in [false, true] { - let make = |alias: bool| { - // (T : Type) (x : T) (y : T) (z : T), x / T. - let terminal = if is_lam { var(2) } else { var(3) }; - let terminal = if alias && is_lam { - KExpr::app(KExpr::app(cnst("idPoly"), var(3)), terminal) - } else { - terminal - }; - let domain_z = if alias { - // A local type alias reduces to T; both domains are well typed. - KExpr::let_(name::("D"), sort(), var(2), var(0), false) - } else { - var(2) - }; - binder( - is_lam, - sort(), - binder( - is_lam, - var(0), - binder(is_lam, var(1), binder(is_lam, domain_z, terminal)), - ), - ) - }; - let mut tc = TypeChecker::new(&mut env); - let a = tc.intern(make(false)); - let b = tc.intern(make(true)); - tc.infer(&a).unwrap(); - tc.infer(&b).unwrap(); - assert!(tc.is_def_eq(&a, &b).unwrap()); - assert!(tc.lctx.is_empty()); - assert!(!tc.in_binder_batch); - } -} - -#[test] -fn dependent_domains_and_shared_fresh_locals_are_preserved() { - dependent::(); - dependent::(); -} - -fn negative_cases() { - for n in [4, 8, 17] { - // Same domains but unequal terminal values/types. - for is_lam in [false, true] { - let (a, b) = - if is_lam { (cnst("a"), cnst("b")) } else { (cnst("A"), cnst("B")) }; - let a = telescope(n, is_lam, a); - let b = telescope(n, is_lam, b); - assert!(!compare::(&a, &b, true).0); - assert!(!compare::(&a, &b, false).0); - } - let a = telescope(n, false, binder(false, cnst("A"), cnst("A"))); - let b = telescope(n, false, binder(false, cnst("B"), cnst("A"))); - assert!(!compare::(&a, &b, true).0); - assert!(!compare::(&a, &b, false).0); - } -} - -#[test] -fn failed_domains_and_terminal_comparisons_match_original() { - negative_cases::(); - negative_cases::(); -} - -fn diverging() { - let a = telescope(4, true, binder(true, cnst("A"), var(0))); - let b = telescope(4, true, cnst("id")); - assert!(compare::(&a, &b, true).0); - assert!(compare::(&a, &b, false).0); - let a = telescope(4, false, cnst("A")); - let b = telescope(3, false, cnst("A")); - assert!(!compare::(&a, &b, true).0); - assert!(!compare::(&a, &b, false).0); -} - -#[test] -fn divergent_suffix_uses_normal_conversion_including_eta() { - diverging::(); - diverging::(); -} - -fn contexts() { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - // The same loose outer Var means different values in different legacy - // let contexts. Opening four binders must shift it down by exactly four. - let a = tc.intern(telescope(4, true, var(4))); - let b = tc.intern(telescope(4, true, cnst("a"))); - tc.push_let(cnst("A"), cnst("a")); - let first = tc.def_eq_ctx_key(&a, &b); - assert!(tc.is_def_eq(&a, &b).unwrap()); - assert_eq!(tc.depth(), 1); - assert!(tc.lctx.is_empty()); - tc.pop_local(); - tc.push_let(cnst("A"), cnst("b")); - let second = tc.def_eq_ctx_key(&a, &b); - assert_ne!(first, second); - assert!(!tc.is_def_eq(&a, &b).unwrap()); - assert!(tc.lctx.is_empty()); - assert_eq!(tc.depth(), 1); -} - -#[test] -fn batched_results_are_scoped_to_the_original_outer_context() { - contexts::(); - contexts::(); -} - -#[test] -fn resource_failure_restores_scope_flag_and_does_not_cache_false() { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - let a = tc.intern(telescope(16, true, cnst("a"))); - let b = tc.intern(telescope(16, true, cnst("alias"))); - let (lo, hi) = canonical_pair(a.hash_key(), b.hash_key()); - let key = (lo, hi, tc.def_eq_ctx_key(&a, &b)); - let before = tc.fresh_fvar_id(); - tc.rec_fuel = 2; - assert!(matches!(tc.is_def_eq(&a, &b), Err(TcError::MaxRecFuel))); - assert_eq!(tc.rec_fuel, 0); - assert!(tc.lctx.is_empty()); - assert!(!tc.in_binder_batch); - assert_eq!(tc.def_eq_depth, 0); - assert!(!tc.env.def_eq_cache.contains_key(&key)); - let after = tc.fresh_fvar_id(); - assert_ne!(before, after); - tc.rec_fuel = 100_000; - assert!(tc.is_def_eq(&a, &b).unwrap()); -} - -#[test] -fn non_resource_errors_propagate_and_restore_scope() { - let mut env = setup::(); - defn(&mut env, "bad", cnst("A"), KExpr::sort(KUniv::param(1, ()))); - let mut tc = TypeChecker::new(&mut env); - let a = tc.intern(telescope( - 4, - true, - KExpr::cnst(id("bad"), Box::new([KUniv::zero()])), - )); - let b = tc.intern(telescope(4, true, cnst("a"))); - assert!(matches!( - tc.is_def_eq(&a, &b), - Err(TcError::UnivParamOutOfRange { .. }) - )); - assert!(tc.lctx.is_empty()); - assert!(!tc.in_binder_batch); - assert_eq!(tc.def_eq_depth, 0); -} - -#[test] -fn short_binders_keep_original_work_and_reset_clears_flag() { - for n in 1..BINDER_BATCH_MIN_LENGTH { - let a = telescope(n, true, cnst::("a")); - let b = telescope(n, true, cnst::("alias")); - let old = compare(&a, &b, true); - let new = compare(&a, &b, false); - assert_eq!((old.0, old.1, old.2), (new.0, new.1, new.2)); - assert_eq!(old.3.subst_nodes, new.3.subst_nodes); - assert_eq!(old.3.intern_nodes, new.3.intern_nodes); - } - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - tc.in_binder_batch = true; - tc.reset(); - assert!(!tc.in_binder_batch); -} - -#[test] -fn generated_telescope_pairs_match_the_original_checker() { - fn check() { - for n in 0..64 { - let body = |k| { - let a = cnst(["a", "b", "alias"][k % 3]); - if k % 2 == 0 { a } else { KExpr::app(cnst("ignore"), a) } - }; - let a = telescope(4 + n % 5, true, body(n * 7)); - let b = telescope(4 + n % 5, true, body(n * 11)); - assert_eq!( - compare::(&a, &b, true).0, - compare::(&a, &b, false).0, - "seed {n}" - ); - } - } - check::(); - check::(); -} - -#[test] -fn closed_suffix_cache_is_honored_without_reopening_it() { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - let left_suffix = tc.intern(telescope(12, true, cnst("a"))); - let right_suffix = tc.intern(telescope(12, true, cnst("alias"))); - assert!(tc.is_def_eq(&left_suffix, &right_suffix).unwrap()); - assert!(tc.has_closed_binder_result(&left_suffix, &right_suffix)); - let a = tc.intern(telescope(4, true, left_suffix)); - let b = tc.intern(telescope(4, true, right_suffix)); - let fuel = tc.rec_fuel; - assert!(tc.is_def_eq(&a, &b).unwrap()); - assert!(fuel - tc.rec_fuel <= 4); - assert!(tc.lctx.is_empty()); -} - -#[test] -fn cheap_negative_cache_does_not_poison_full_binder_comparison() { - fn check() { - let mut env = setup::(); - let mut tc = TypeChecker::new(&mut env); - let a = tc.intern(telescope(4, true, cnst("a"))); - let b = tc.intern(telescope(4, true, cnst("alias"))); - let (lo, hi) = canonical_pair(a.hash_key(), b.hash_key()); - let key = (lo, hi, tc.def_eq_ctx_key(&a, &b)); - tc.env.def_eq_cheap_cache.insert(key, false); - assert!(tc.is_def_eq(&a, &b).unwrap()); - assert_eq!(tc.env.def_eq_cache.get(&key), Some(&true)); - tc.env.clear_reduction_caches(); - tc.equiv_manager.clear(); - tc.cheap_recursion_depth = 1; - tc.infer_only = true; - tc.eager_reduce = true; - assert!(tc.is_def_eq(&a, &b).unwrap()); - assert_eq!(tc.env.def_eq_cheap_cache.get(&key), Some(&true)); - assert_eq!(tc.env.def_eq_cache.get(&key), Some(&true)); - assert_eq!(tc.cheap_recursion_depth, 1); - assert!(tc.infer_only && tc.eager_reduce); - assert!(!tc.in_binder_batch); - assert!(tc.lctx.is_empty()); - } - check::(); - check::(); -} - -#[test] -fn local_budget_miss_falls_back_without_refunding_work() { - let mut env = setup::(); - let mut next = cnst("a"); - // Finite beta+delta chain: productive betas exceed the batch slice. - // Bare aliases alone use the separate local delta-loop guard and do not - // necessarily consume recursive fuel once their structural forms hit. - for i in 0..3000 { - let s = format!("step{i}"); - defn(&mut env, &s, cnst("A"), KExpr::app(cnst("id"), next)); - next = cnst(&s); - } - let mut tc = TypeChecker::new(&mut env); - let a = tc.intern(telescope(4, true, next)); - let b = tc.intern(telescope(4, true, cnst("a"))); - let before = tc.fresh_fvar_id().0; - let fuel = tc.rec_fuel; - assert!(tc.is_def_eq(&a, &b).unwrap()); - let after = tc.fresh_fvar_id().0; - assert!( - after - before >= 9, - "expected batch+fallback: new fvars={}, fuel consumed={}", - after - before, - fuel - tc.rec_fuel, - ); - assert!(fuel - tc.rec_fuel > BINDER_BATCH_FUEL); - assert!(!tc.in_binder_batch); - assert!(tc.lctx.is_empty()); -} - -#[test] -fn proof_irrelevance_inside_a_telescope_remains_available() { - fn check(old: bool) { - let mut env = setup::(); - axiom(&mut env, "PropP", KExpr::sort(KUniv::zero())); - axiom(&mut env, "proof1", cnst("PropP")); - axiom(&mut env, "proof2", cnst("PropP")); - let mut tc = TypeChecker::new(&mut env); - tc.in_binder_batch = old; - let a = tc.intern(telescope(8, true, cnst("proof1"))); - let b = tc.intern(telescope(8, true, cnst("proof2"))); - tc.infer(&a).unwrap(); - tc.infer(&b).unwrap(); - assert!(tc.is_def_eq(&a, &b).unwrap()); - assert!(tc.lctx.is_empty()); - assert_eq!(tc.in_binder_batch, old); - } - for old in [false, true] { - check::(old); - check::(old); - } -} diff --git a/crates/kernel/src/def_eq/projection_tests.rs b/crates/kernel/src/def_eq/projection_tests.rs deleted file mode 100644 index b0e339691..000000000 --- a/crates/kernel/src/def_eq/projection_tests.rs +++ /dev/null @@ -1,320 +0,0 @@ -//! Projection-first probes may establish equality, never assume it on a miss. - -use super::*; -use crate::env::KEnv; -use crate::mode::{Anon, Meta}; -use ix_common::address::Address; -use ix_common::env::{BinderInfo, DefinitionSafety, Name, ReducibilityHints}; - -fn id(s: &str) -> KId { - KId::new( - Address::hash(s.as_bytes()), - M::meta_field(Name::str(Name::anon(), s.to_owned())), - ) -} - -fn cnst(s: &str) -> KExpr { - KExpr::cnst(id(s), Box::new([])) -} - -fn axiom(env: &mut KEnv, s: &str, ty: KExpr) { - env.insert( - id(s), - KConst::Axio { - name: M::meta_field(Name::anon()), - level_params: M::meta_field(vec![]), - is_unsafe: false, - lvls: 0, - ty, - }, - ); -} - -fn defn( - env: &mut KEnv, - s: &str, - ty: KExpr, - val: KExpr, -) { - env.insert( - id(s), - KConst::Defn { - name: M::meta_field(Name::anon()), - level_params: M::meta_field(vec![]), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(7), - lvls: 0, - ty, - val, - lean_all: M::meta_field(vec![]), - block: id(s), - }, - ); -} - -/// A : Type; a b : A; Box : Type; Box.mk : A → Box. -/// `pack x` either stores x or ignores x and stores a. `neutral` is an axiom. -fn setup(keep_arg: bool) -> KEnv { - let mut env = KEnv::new(); - let type0 = KExpr::sort(KUniv::succ(KUniv::zero())); - axiom(&mut env, "A", type0.clone()); - axiom(&mut env, "a", cnst("A")); - axiom(&mut env, "b", cnst("A")); - env.insert( - id("Box"), - KConst::Indc { - name: M::meta_field(Name::anon()), - level_params: M::meta_field(vec![]), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: id("Box"), - member_idx: 0, - ty: type0, - ctors: vec![id("Box.mk")], - lean_all: M::meta_field(vec![]), - }, - ); - let fun_ty = KExpr::all( - M::meta_field(Name::anon()), - M::meta_field(BinderInfo::Default), - cnst("A"), - cnst("Box"), - ); - env.insert( - id("Box.mk"), - KConst::Ctor { - name: M::meta_field(Name::anon()), - level_params: M::meta_field(vec![]), - is_unsafe: false, - lvls: 0, - induct: id("Box"), - cidx: 0, - params: 0, - fields: 1, - ty: fun_ty.clone(), - }, - ); - let field = if keep_arg { - KExpr::var(0, M::meta_field(Name::anon())) - } else { - cnst("a") - }; - let body = KExpr::lam( - M::meta_field(Name::anon()), - M::meta_field(BinderInfo::Default), - cnst("A"), - KExpr::app(cnst("Box.mk"), field), - ); - defn(&mut env, "pack", fun_ty.clone(), body); - axiom(&mut env, "neutral", fun_ty); - env -} - -fn proj(head: &str, arg: KExpr) -> KExpr { - KExpr::prj(id("Box"), 0, KExpr::app(cnst(head), arg)) -} - -fn ignored_expensive_argument() { - let make = || { - let mut env = setup::(false); - let mut expensive = cnst("a"); - for i in 0..256 { - let s = format!("expensive.{i}"); - let step = KExpr::app( - KExpr::lam( - M::meta_field(Name::anon()), - M::meta_field(BinderInfo::Default), - cnst("A"), - KExpr::var(0, M::meta_field(Name::anon())), - ), - expensive, - ); - defn(&mut env, &s, cnst("A"), step); - expensive = cnst(&s); - } - (env, expensive) - }; - let (mut env, expensive) = make(); - let mut tc = TypeChecker::new(&mut env); - tc.rec_fuel = 128; - assert!( - tc.is_def_eq(&proj("pack", expensive.clone()), &proj("pack", cnst("b"))) - .unwrap() - ); - assert!(tc.rec_fuel > 0); - assert!(!tc.in_projection_probe); - assert_eq!(tc.def_eq_depth, 0); - assert_eq!(tc.depth(), 0); - - // The original whole-record-first path exhausts the SAME total budget. - let (mut old_env, expensive) = make(); - let mut old = TypeChecker::new(&mut old_env); - old.rec_fuel = 128; - assert!(matches!( - old.lazy_delta_proj_reduction( - &id("Box"), - 0, - &mut KExpr::app(cnst("pack"), expensive), - &mut KExpr::app(cnst("pack"), cnst("b")), - ), - Err(TcError::MaxRecFuel) - )); -} - -#[test] -fn projection_probe_avoids_ignored_argument_work() { - ignored_expensive_argument::(); - ignored_expensive_argument::(); -} - -fn unequal_fields() { - let mut env = setup::(true); - let mut tc = TypeChecker::new(&mut env); - let (a, b) = (proj("pack", cnst("a")), proj("pack", cnst("b"))); - assert!(!tc.try_projected_def_eq(&a, &b).unwrap()); - assert!(!tc.is_def_eq(&a, &b).unwrap()); - assert!(!tc.is_def_eq(&b, &a).unwrap()); - assert!(!tc.in_projection_probe); -} - -#[test] -fn projection_probe_rejects_unequal_fields() { - unequal_fields::(); - unequal_fields::(); -} - -fn neutral_fallback() { - let mut env = setup::(true); - let beta = KExpr::app( - KExpr::lam( - M::meta_field(Name::anon()), - M::meta_field(BinderInfo::Default), - cnst("A"), - KExpr::var(0, M::meta_field(Name::anon())), - ), - cnst("a"), - ); - let (a, b) = (proj("neutral", beta), proj("neutral", cnst("a"))); - let mut tc = TypeChecker::new(&mut env); - assert!(!tc.try_projected_def_eq(&a, &b).unwrap()); - assert!(tc.is_def_eq(&a, &b).unwrap()); -} - -#[test] -fn projection_probe_miss_retains_record_congruence() { - neutral_fallback::(); - neutral_fallback::(); -} - -fn guard_restoration() { - let mut env = setup::(true); - let mut tc = TypeChecker::new(&mut env); - let (a, b) = (proj("pack", cnst("a")), proj("pack", cnst("b"))); - tc.rec_fuel = 1; - assert!(!tc.try_projected_def_eq(&a, &b).unwrap()); - assert_eq!(tc.rec_fuel, 0, "probe work must not be refunded"); - assert!(!tc.in_projection_probe); - assert!(tc.env.def_eq_cache.is_empty()); - assert!(tc.env.def_eq_cheap_cache.is_empty()); - - tc.rec_fuel = 10_000; - tc.def_eq_depth = MAX_DEF_EQ_DEPTH; - assert!(!tc.try_projected_def_eq(&a, &b).unwrap()); - assert_eq!(tc.def_eq_depth, MAX_DEF_EQ_DEPTH); - assert!(tc.rec_fuel < 10_000); - assert!(!tc.in_projection_probe); - assert!(tc.env.def_eq_cache.is_empty()); - tc.def_eq_depth = 0; - assert!(!tc.is_def_eq(&a, &b).unwrap()); -} - -#[test] -fn projection_probe_guards_do_not_cache_verdicts_or_refund_work() { - guard_restoration::(); - guard_restoration::(); -} - -fn malformed_field() { - let mut env = setup::(true); - // Deliberately malformed declaration: its body references universe 1, - // while the supplied substitution contains only universe 0. - defn( - &mut env, - "bad", - cnst("Box"), - KExpr::sort(KUniv::param(1, M::meta_field(Name::anon()))), - ); - let mut tc = TypeChecker::new(&mut env); - let result = tc.try_projected_def_eq( - &KExpr::prj( - id("Box"), - 0, - KExpr::cnst(id("bad"), Box::new([KUniv::zero()])), - ), - &proj("pack", cnst("b")), - ); - assert!( - matches!(result, Err(TcError::UnivParamOutOfRange { .. })), - "{result:?}" - ); - assert!(!tc.in_projection_probe); - assert!(tc.rec_fuel < crate::tc::max_rec_fuel()); -} - -#[test] -fn projection_probe_propagates_non_budget_errors() { - malformed_field::(); - malformed_field::(); -} - -#[test] -fn projection_probe_does_not_nest_or_reset_enclosing_state() { - let mut env = setup::(false); - let mut tc = TypeChecker::new(&mut env); - tc.in_projection_probe = true; - let fuel = tc.rec_fuel; - assert!( - !tc - .try_projected_def_eq(&proj("pack", cnst("a")), &proj("pack", cnst("b"))) - .unwrap() - ); - assert_eq!(tc.rec_fuel, fuel); - assert!(tc.in_projection_probe); - tc.reset(); - assert!(!tc.in_projection_probe); -} - -fn exhausted_proposition_probe() { - let mut env = setup::(true); - let type0 = KExpr::sort(KUniv::succ(KUniv::zero())); - defn( - &mut env, - "propSort", - type0.clone(), - KExpr::app( - KExpr::lam( - M::meta_field(Name::anon()), - M::meta_field(BinderInfo::Default), - type0, - KExpr::var(0, M::meta_field(Name::anon())), - ), - KExpr::sort(KUniv::zero()), - ), - ); - axiom(&mut env, "P", cnst("propSort")); - let mut tc = TypeChecker::new(&mut env); - tc.rec_fuel = 0; - assert!(!tc.is_prop_type(&cnst("P"))); - assert!(tc.env.is_prop_cache.is_empty()); - tc.rec_fuel = 10_000; - assert!(tc.is_prop_type(&cnst("P"))); -} - -#[test] -fn projection_probe_fuel_misses_do_not_poison_proposition_cache() { - exhausted_proposition_probe::(); - exhausted_proposition_probe::(); -} diff --git a/crates/kernel/src/def_eq/same_head_tests.rs b/crates/kernel/src/def_eq/same_head_tests.rs deleted file mode 100644 index 6d937a52e..000000000 --- a/crates/kernel/src/def_eq/same_head_tests.rs +++ /dev/null @@ -1,392 +0,0 @@ -use super::*; -use crate::{ - env::KEnv, - mode::{Anon, Meta}, -}; -use ix_common::{ - address::Address, - env::{BinderInfo, DefinitionSafety, Name, ReducibilityHints}, -}; - -fn id(name: &str) -> KId { - KId::new(Address::hash(name.as_bytes()), M::meta_field(Name::anon())) -} - -fn cnst(name: &str) -> KExpr { - KExpr::cnst(id(name), Box::new([])) -} - -fn defn( - env: &mut KEnv, - name: &str, - ty: KExpr, - val: KExpr, -) { - env.insert( - id(name), - KConst::Defn { - name: M::meta_field(Name::anon()), - level_params: M::meta_field(vec![]), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(7), - lvls: 0, - ty, - val, - lean_all: M::meta_field(vec![]), - block: id(name), - }, - ); -} - -fn setup() -> KEnv { - let mut env = KEnv::new(); - for (name, ty) in [ - ("A", KExpr::sort(KUniv::succ(KUniv::zero()))), - ("a", cnst("A")), - ("b", cnst("A")), - ] { - env.insert( - id(name), - KConst::Axio { - name: M::meta_field(Name::anon()), - level_params: M::meta_field(vec![]), - is_unsafe: false, - lvls: 0, - ty, - }, - ); - } - for (name, body) in - [("ignore", cnst("a")), ("id", KExpr::var(0, M::meta_field(Name::anon())))] - { - let ty = KExpr::all( - M::meta_field(Name::anon()), - M::meta_field(BinderInfo::Default), - cnst("A"), - cnst("A"), - ); - let val = KExpr::lam( - M::meta_field(Name::anon()), - M::meta_field(BinderInfo::Default), - cnst("A"), - body, - ); - defn(&mut env, name, ty, val); - } - env -} - -/// A well-typed, finite alias chain with beta steps per link. Bare constant -/// aliases do not consume recursive fuel in the lazy-delta loop. Each body -/// is shallow, avoiding a deep expression/drop stack or enlarged thread stack. -fn beta_alias_chain( - env: &mut KEnv, - links: u64, - betas: usize, -) -> KExpr { - let mut val = cnst("a"); - let identity = KExpr::lam( - M::meta_field(Name::anon()), - M::meta_field(BinderInfo::Default), - cnst("A"), - KExpr::var(0, M::meta_field(Name::anon())), - ); - for i in 0..links { - let name = format!("alias{i}"); - for _ in 0..betas { - val = KExpr::app(identity.clone(), val); - } - defn(env, &name, cnst("A"), val); - val = cnst(&name); - } - val -} - -fn expensive(env: &mut KEnv) -> KExpr { - // Exceed the Regular allowance without reaching the separate 10k-iteration - // lazy-delta guard or constructing a deeply nested expression body. - beta_alias_chain(env, SAME_HEAD_SPECULATION_ATTEMPT_FUEL, 64) -} - -fn assert_not_cached( - tc: &mut TypeChecker<'_, M>, - a: &KExpr, - b: &KExpr, -) { - let (lo, hi) = canonical_pair(a.hash_key(), b.hash_key()); - let key = (lo, hi, tc.def_eq_ctx_key(a, b)); - assert!(!tc.env.def_eq_cache.contains_key(&key)); - assert!(!tc.env.def_eq_cheap_cache.contains_key(&key)); -} - -fn fallback(head: &str, expected: bool) { - let mut env = setup::(); - let arg = expensive(&mut env); - let head_expr = cnst(head); - let other_arg = cnst("b"); - let a = KExpr::app(head_expr.clone(), arg.clone()); - let b = KExpr::app(head_expr.clone(), other_arg.clone()); - let mut tc = TypeChecker::new(&mut env); - let before = tc.rec_fuel; - assert_eq!( - tc.try_same_head_spine_speculative(&a, &b, &id(head), true).unwrap(), - None - ); - assert_eq!(before - tc.rec_fuel, SAME_HEAD_REGULAR_ATTEMPT_FUEL); - assert_eq!(tc.same_head_fuel_reserve, 0); - assert!(!tc.same_head_backoff.should_skip(true)); - assert_not_cached(&mut tc, &a, &b); - assert_not_cached(&mut tc, &arg, &other_arg); - assert_eq!(tc.def_eq_depth, 0); - assert_eq!(tc.is_def_eq(&a, &b).unwrap(), expected); - // Confirm delta rather than only a result rescued by congruence. - assert!(tc.env.unfold_cache.contains_key(&head_expr.hash_key())); - assert!(tc.fuel_used() > SAME_HEAD_REGULAR_ATTEMPT_FUEL); -} - -#[test] -fn expensive_regular_probe_falls_back_without_accepting_unequal_terms() { - fallback::("ignore", true); - fallback::("ignore", true); - fallback::("id", false); - fallback::("id", false); -} - -fn nested(cheap: bool) { - let mut env = setup::(); - let arg = expensive(&mut env); - let other_arg = cnst("a"); - let inner_a = KExpr::app(cnst("id"), arg.clone()); - let inner_b = KExpr::app(cnst("id"), other_arg.clone()); - let a = KExpr::app(cnst("ignore"), inner_a.clone()); - let b = KExpr::app(cnst("ignore"), inner_b.clone()); - let mut tc = TypeChecker::new(&mut env); - tc.cheap_recursion_depth = u32::from(cheap); - // Simulate an enclosing probe with less than one fresh allowance. - tc.rec_fuel = 1_000; - crate::perf::same_head::reset(); - assert_eq!( - tc.try_same_head_spine_speculative(&a, &b, &id("ignore"), true).unwrap(), - None - ); - assert_eq!(tc.rec_fuel, 0, "nested probes must not replenish fuel"); - assert_eq!(tc.same_head_fuel_reserve, 0); - assert_not_cached(&mut tc, &a, &b); - assert_not_cached(&mut tc, &inner_a, &inner_b); - assert_not_cached(&mut tc, &arg, &other_arg); - assert_eq!(tc.def_eq_depth, 0); - if crate::perf::same_head::enabled() { - let report = crate::perf::same_head::summary(); - assert!(report.contains("active=0 max_depth=2 "), "{report}"); - assert!(report.contains("root_fuel=1000 "), "{report}"); - assert!(report.contains("accounting_errors=0"), "{report}"); - } - // Resuming the same environment must not observe poisoned negative caches. - tc.rec_fuel = crate::tc::max_rec_fuel(); - tc.cheap_recursion_depth = 0; - assert!(tc.is_def_eq(&inner_a, &inner_b).unwrap()); - assert!(tc.is_def_eq(&a, &b).unwrap()); -} - -#[test] -fn nested_regular_probes_share_remaining_fuel_without_cache_poisoning() { - for cheap in [false, true] { - nested::(cheap); - nested::(cheap); - } -} - -#[test] -fn productive_regular_probe_has_more_than_the_non_regular_allowance() { - let mut env = setup::(); - let arg = beta_alias_chain(&mut env, 128, 64); - let head = cnst("id"); - let a = KExpr::app(head.clone(), arg); - let b = KExpr::app(head.clone(), cnst("a")); - let mut tc = TypeChecker::new(&mut env); - assert!(tc.is_def_eq(&a, &b).unwrap()); - assert!(tc.fuel_used() > SAME_HEAD_SPECULATION_ATTEMPT_FUEL); - assert!(tc.fuel_used() < SAME_HEAD_REGULAR_ATTEMPT_FUEL); - assert!(!tc.env.unfold_cache.contains_key(&head.hash_key())); - assert_eq!(tc.same_head_fuel_reserve, 0); -} - -#[test] -fn late_regular_probe_still_uses_successful_congruence() { - let mut env = setup::(); - defn(&mut env, "alias", cnst("A"), cnst("a")); - let head = cnst("id"); - let a = KExpr::app(head.clone(), cnst("alias")); - let b = KExpr::app(head.clone(), cnst("a")); - let mut tc = TypeChecker::new(&mut env); - tc.rec_fuel -= SAME_HEAD_SPECULATION_START_FUEL; - let before = tc.rec_fuel; - assert!(tc.is_def_eq(&a, &b).unwrap()); - assert!(before - tc.rec_fuel < SAME_HEAD_SPECULATION_ATTEMPT_FUEL); - assert!(!tc.env.unfold_cache.contains_key(&head.hash_key())); -} - -#[test] -fn regular_reservation_preserves_non_regular_startup_window() { - for used in [0, SAME_HEAD_SPECULATION_START_FUEL] { - let mut env = setup::(); - defn(&mut env, "alias", cnst("A"), cnst("a")); - let mut abbrev = env.get(&id("id")).unwrap(); - if let KConst::Defn { hints, block, .. } = &mut abbrev { - *hints = ReducibilityHints::Abbrev; - *block = id("abbrev"); - } - env.insert(id("abbrev"), abbrev); - let head = cnst("abbrev"); - let a = KExpr::app(cnst("id"), KExpr::app(head.clone(), cnst("alias"))); - let b = KExpr::app(cnst("id"), KExpr::app(head.clone(), cnst("a"))); - let mut tc = TypeChecker::new(&mut env); - tc.rec_fuel -= used; - assert_eq!( - tc.try_same_head_spine_speculative(&a, &b, &id("id"), true).unwrap(), - Some(true) - ); - // The outer Regular allowance neither closes the early window nor - // reopens a late one. Only the latter case must delta-unfold the Abbrev. - assert_eq!(tc.env.unfold_cache.contains_key(&head.hash_key()), used > 0); - assert_eq!(tc.same_head_fuel_reserve, 0); - } -} - -#[test] -fn alternate_lazy_delta_path_falls_back_after_probe_abort() { - let mut env = setup::(); - let arg = expensive(&mut env); - let mut a = KExpr::app(cnst("ignore"), arg); - let mut b = KExpr::app(cnst("ignore"), cnst("b")); - let mut tc = TypeChecker::new(&mut env); - assert!(matches!( - tc.lazy_delta_reduction_step(&mut a, &mut b).unwrap(), - LazyDeltaStep::Equal - )); - assert!(tc.fuel_used() >= SAME_HEAD_REGULAR_ATTEMPT_FUEL); -} - -#[test] -fn resource_abort_is_unknown_but_other_errors_propagate() { - let mut env = setup::(); - defn(&mut env, "alias", cnst("A"), cnst("a")); - defn(&mut env, "bad", cnst("A"), KExpr::sort(KUniv::param(1, ()))); - let a = KExpr::app(cnst("id"), cnst("alias")); - let b = KExpr::app(cnst("id"), cnst("a")); - let mut tc = TypeChecker::new(&mut env); - tc.rec_fuel = 0; - assert_eq!( - tc.try_same_head_spine_speculative(&a, &b, &id("id"), true).unwrap(), - None - ); - assert!(matches!(tc.is_def_eq(&a, &b), Err(TcError::MaxRecFuel))); - assert_not_cached(&mut tc, &a, &b); - - tc.rec_fuel = 20_000; - tc.def_eq_depth = MAX_DEF_EQ_DEPTH; - assert_eq!( - tc.try_same_head_spine_speculative(&a, &b, &id("id"), true).unwrap(), - None - ); - assert_eq!(tc.def_eq_depth, MAX_DEF_EQ_DEPTH); - assert_eq!(tc.same_head_fuel_reserve, 0); - assert!( - tc.rec_fuel < 20_000 - && tc.rec_fuel > 20_000 - SAME_HEAD_SPECULATION_ATTEMPT_FUEL - ); - tc.def_eq_depth = 0; - assert!(tc.is_def_eq(&a, &b).unwrap()); - - let bad = - KExpr::app(cnst("id"), KExpr::cnst(id("bad"), Box::new([KUniv::zero()]))); - tc.rec_fuel = 1_000_000; - let before = tc.rec_fuel; - assert!(matches!( - tc.try_same_head_spine_speculative(&bad, &b, &id("id"), true), - Err(TcError::UnivParamOutOfRange { .. }) - )); - assert!( - tc.rec_fuel < before - && tc.rec_fuel > before - SAME_HEAD_SPECULATION_ATTEMPT_FUEL - ); - assert_eq!(tc.def_eq_depth, 0); - assert_eq!(tc.same_head_fuel_reserve, 0); - assert_not_cached(&mut tc, &bad, &b); -} - -fn seed_backoff(tc: &mut TypeChecker<'_, M>) { - tc.same_head_backoff.enter(); - tc.same_head_backoff.leave(true, true, speculation::FAILED_FUEL_TOTAL); -} - -fn backoff_fallback(head: &str, expected: bool, cheap: bool) { - let mut env = setup::(); - let a = KExpr::app(cnst(head), cnst("a")); - let b = KExpr::app(cnst(head), cnst("b")); - let mut tc = TypeChecker::new(&mut env); - seed_backoff(&mut tc); - tc.cheap_recursion_depth = u32::from(cheap); - let before = tc.rec_fuel; - crate::perf::same_head::reset(); - assert_eq!( - tc.try_same_head_spine_speculative(&a, &b, &id(head), true).unwrap(), - None - ); - assert_eq!(tc.rec_fuel, before, "a skip does not spend or refund fuel"); - assert_eq!(tc.same_head_fuel_reserve, 0); - assert_not_cached(&mut tc, &a, &b); - if crate::perf::same_head::enabled() { - let report = crate::perf::same_head::summary(); - assert!(report.contains("skipped_backoff=1"), "{report}"); - assert!(report.contains("active=0 max_depth=0"), "{report}"); - } - tc.cheap_recursion_depth = 0; - assert_eq!(tc.is_def_eq(&a, &b).unwrap(), expected); -} - -#[test] -fn cumulative_backoff_never_publishes_equality_or_inequality() { - for cheap in [false, true] { - backoff_fallback::("ignore", true, cheap); - backoff_fallback::("ignore", true, cheap); - backoff_fallback::("id", false, cheap); - backoff_fallback::("id", false, cheap); - } -} - -#[test] -fn cumulative_backoff_applies_to_both_lazy_delta_paths() { - let mut env = setup::(); - let mut a = KExpr::app(cnst("ignore"), cnst("a")); - let mut b = KExpr::app(cnst("ignore"), cnst("b")); - let mut tc = TypeChecker::new(&mut env); - seed_backoff(&mut tc); - assert!(matches!( - tc.lazy_delta_reduction_step(&mut a, &mut b).unwrap(), - LazyDeltaStep::Equal - )); - assert!(tc.fuel_used() < SAME_HEAD_SPECULATION_ATTEMPT_FUEL); -} - -#[test] -fn reset_restores_productive_regular_probes_without_reusing_fvar_ids() { - let mut env = setup::(); - let arg = beta_alias_chain(&mut env, 128, 64); - let a = KExpr::app(cnst("id"), arg); - let b = KExpr::app(cnst("id"), cnst("a")); - let mut tc = TypeChecker::new(&mut env); - seed_backoff(&mut tc); - assert!(tc.same_head_backoff.should_skip(true)); - let old_fvar = tc.fresh_fvar_id(); - tc.reset(); - assert_ne!(old_fvar, tc.fresh_fvar_id()); - assert!(!tc.same_head_backoff.should_skip(true)); - assert_eq!( - tc.try_same_head_spine_speculative(&a, &b, &id("id"), true).unwrap(), - Some(true) - ); - assert!(tc.fuel_used() > SAME_HEAD_SPECULATION_ATTEMPT_FUEL); - assert!(tc.fuel_used() < SAME_HEAD_REGULAR_ATTEMPT_FUEL); -} diff --git a/crates/kernel/src/def_eq/speculation.rs b/crates/kernel/src/def_eq/speculation.rs deleted file mode 100644 index 48acf3022..000000000 --- a/crates/kernel/src/def_eq/speculation.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! Admission backoff for repeatedly unproductive same-head comparisons. -//! -//! This is scheduling history, never a semantic equality/inequality cache. -//! Only an outermost unsuccessful Regular attempt charges the history; -//! its fuel includes its descendants, which must not charge again. Admitted -//! attempts keep their normal allowance. Nested attempts still share the -//! outer fuel slice and are not independently denied by history. - -/// Preserve productive speculation in substantial checks while reserving -/// most of the default 100M fuel for ordinary conversion. Early backoff -/// regressed FLT: unsuccessful earlier pairs do not tell us whether -/// later comparisons of the same definition will be useful. -pub(super) const FAILED_FUEL_TOTAL: u64 = 33_554_432; - -/// The threshold stops *new* root admissions. The last admitted attempt may -/// cross it by at most its allowance; it is never refunded or -/// prematurely truncated just because its eventual result might be a miss. -#[derive(Default)] -pub(crate) struct SameHeadBackoff { - active: usize, - failed_fuel: u64, -} - -impl SameHeadBackoff { - #[inline] - pub(super) fn should_skip(&self, regular: bool) -> bool { - self.active == 0 && regular && self.failed_fuel >= FAILED_FUEL_TOTAL - } - - #[inline] - pub(super) fn enter(&mut self) { - self.active += 1; - } - - pub(super) fn leave( - &mut self, - regular: bool, - unsuccessful: bool, - consumed: u64, - ) { - debug_assert!(self.active > 0); - self.active -= 1; - if self.active != 0 || !regular || !unsuccessful || consumed == 0 { - return; - } - self.failed_fuel = self.failed_fuel.saturating_add(consumed); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn charge(s: &mut SameHeadBackoff, fuel: u64) { - s.enter(); - s.leave(true, true, fuel); - } - - #[test] - fn backoff_only_blocks_new_regular_roots_after_the_threshold() { - let mut s = SameHeadBackoff::default(); - charge(&mut s, FAILED_FUEL_TOTAL - 1); - assert!(!s.should_skip(true)); - charge(&mut s, 1); - assert!(s.should_skip(true)); - assert!(!s.should_skip(false)); - s.enter(); - assert!(!s.should_skip(true)); - s.leave(true, false, 100); - assert_eq!(s.failed_fuel, FAILED_FUEL_TOTAL); - } - - #[test] - fn nested_cost_is_charged_once_and_successes_do_not_charge() { - let mut s = SameHeadBackoff::default(); - s.enter(); - s.enter(); - s.leave(true, true, 90); - assert_eq!(s.failed_fuel, 0); - s.leave(true, true, 100); - assert_eq!(s.failed_fuel, 100); - s.enter(); - s.enter(); - s.leave(true, true, 90); - s.leave(true, false, 100); - assert_eq!(s.failed_fuel, 100); - assert_eq!(s.active, 0); - } - - #[test] - fn a_complete_admitted_attempt_can_cross_the_threshold_without_refund() { - let mut s = SameHeadBackoff::default(); - charge(&mut s, FAILED_FUEL_TOTAL - 1); - assert!(!s.should_skip(true)); - charge(&mut s, 131_072); - assert_eq!(s.failed_fuel, FAILED_FUEL_TOTAL + 131_071); - assert!(s.should_skip(true)); - } - - #[test] - fn non_regular_and_zero_cost_attempts_leave_no_history() { - let mut s = SameHeadBackoff::default(); - s.enter(); - s.leave(false, true, 1_000); - charge(&mut s, 0); - assert_eq!(s.failed_fuel, 0); - assert_eq!(s.active, 0); - } -} diff --git a/crates/kernel/src/env.rs b/crates/kernel/src/env.rs deleted file mode 100644 index 52eea4c59..000000000 --- a/crates/kernel/src/env.rs +++ /dev/null @@ -1,1378 +0,0 @@ -//! Zero kernel environment. -//! -//! `KEnv` maps `KId` to `KConst`, and owns all shared kernel state: -//! the intern table, type-checking caches, and resolved primitives. -//! -//! The environment is single-threaded. Worker pools own one `KEnv` per worker -//! and move parallelism above the kernel state boundary. - -use std::collections::BTreeSet; -use std::collections::hash_map::Entry; - -use rustc_hash::{FxHashMap, FxHashSet}; -use std::cell::OnceCell; - -use ix_common::address::Address; -use ix_common::env::{BinderInfo, Name}; -use ixon::contract::{BinderContract, LetContract, ValueContract}; - -use super::constant::{KConst, RecRule}; -use super::error::TcError; -use super::expr::{FVarId, KExpr, UnivDecor}; -use super::id::KId; -use super::level::KUniv; -use super::mode::KernelMode; -use super::perf::PerfCounters; -use super::primitive::Primitives; - -mod scratch; -use scratch::ScratchMap; - -/// 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). -/// -/// 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. -/// -/// The intern table assigns identity from shallow structural keys -/// ([`ExprKey`]/[`UnivKey`]), avoiding a content hash for every node. -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 what the -/// historical per-node content hash covered — display names, binder info, -/// and mdata are excluded — so interning semantics are unchanged for -/// them. Children are identified by uid: within one table, equal keys ⇔ -/// structurally equal nodes (children canonical by induction). -/// Binder input/result contracts and complete let contracts are included. -/// Result-only conversion equivalence is never used as an intern key. -/// -/// `Sort`/`Const` additionally carry the level-spelling decoration -/// ([`UnivDecor`], Meta mode only — always `None` in Anon mode): unlike -/// names/mdata, the decoration feeds egress OUTPUT (the rebuilt anon univ -/// tables), so spelling twins must intern to distinct nodes or the -/// first-interned spelling would be replayed for both (the Lean twin gets -/// the same distinction by folding decorations into `metaAddr`). -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub enum ExprKey { - Var(u64), - FVar(u64), - Sort(Addr, Option), - Const(Address, Box<[Addr]>, Option), - App(Addr, Addr), - Lam(BinderContract, Addr, Addr), - All(BinderContract, ValueContract, Addr, Addr), - Let(LetContract, Addr, Addr, Addr), - 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 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 -/// call. Allocating these as `FxHashMap::default()` per call shows up in -/// profiles for big mathlib blocks where beta/zeta reductions fire -/// millions of times; threading the scratch through the `&mut InternTable` -/// 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>, - /// 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` / binder opening and closing, - /// keyed by `(addr, depth)`. Cleared on entry; retained allocation adapts - /// to recent occupancy without sharing logical entries between calls. - pub(crate) subst_scratch: ScratchMap<(Addr, u64), KExpr>, - /// Scratch buffer for `lift` per-call memoization, keyed by - /// `(addr, cutoff)`. Cleared on entry. Separate from `subst_scratch` - /// 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: ScratchMap<(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>>, -} - -/// Original input allocation -> completed canonical result, for ONE public -/// interning call. The caller retains its input root, and recursive helpers -/// borrow only descendants of that root, so these pointer keys cannot be reused -/// while the memo is alive. Rebuilt temporaries must never become input keys. -/// -/// Exact pointers (not semantic uids) distinguish metadata-bearing occurrences. -/// Only changed nodes need entries: unchanged nodes hit the canonical-uid sets. -/// Dropping the memo on return also avoids retaining a second DAG across calls. -struct InternMemo { - exprs: FxHashMap>, - univs: FxHashMap>, -} - -impl Default for InternMemo { - fn default() -> Self { - Self { exprs: FxHashMap::default(), univs: FxHashMap::default() } - } -} - -impl Default for InternTable { - fn default() -> Self { - Self::new() - } -} - -/// 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, i) => { - ExprKey::Sort(*u.addr(), M::meta_get(&i.univ_decor).cloned().flatten()) - }, - ExprData::Const(id, us, i) => ExprKey::Const( - id.addr.clone(), - us.iter().map(|u| *u.addr()).collect(), - M::meta_get(&i.univ_decor).cloned().flatten(), - ), - ExprData::App(f, a, _) => ExprKey::App(*f.addr(), *a.addr()), - ExprData::Lam(_, _, c, t, b, _) => ExprKey::Lam(*c, *t.addr(), *b.addr()), - ExprData::All(_, _, c, r, t, b, _) => { - ExprKey::All(*c, *r, *t.addr(), *b.addr()) - }, - ExprData::Let(_, t, v, b, c, _) => { - ExprKey::Let(*c, *t.addr(), *v.addr(), *b.addr()) - }, - 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: ScratchMap::default(), - lift_scratch: ScratchMap::default(), - clo_scratch_pool: Vec::new(), - } - } - - /// Read-only fast path: return the canonical interned universe for - /// `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, 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, key: &ExprKey) -> Option> { - self.exprs.get(key).cloned() - } - - /// Construct an application only if its canonical parent is absent. - /// Equivalent to `intern_expr(KExpr::app(f, a))`, including child traversal - /// order and first-insert-wins metadata. Inputs need not be canonical. - pub(crate) fn intern_app(&mut self, f: &KExpr, a: &KExpr) -> KExpr { - crate::profile::bump_intern_nodes(); - let mut memo = InternMemo::default(); - // Keep both input roots alive until the call-local pointer memo is gone. - let cf = self.intern_expr_cached(f, &mut memo); - let ca = self.intern_expr_cached(a, &mut memo); - self.intern_expr_with(ExprKey::App(*cf.addr(), *ca.addr()), || { - KExpr::app(cf, ca) - }) - } - - /// Allocate-on-miss counterpart of the ordinary-Lean `KExpr::all`. - /// Rebuilding a supplied Pi uses `intern_all_contract` instead. - #[cfg(test)] - pub(crate) fn intern_all( - &mut self, - name: M::MField, - bi: M::MField, - ty: &KExpr, - body: &KExpr, - ) -> KExpr { - self.intern_all_contract( - name, - bi, - BinderContract::default(), - ValueContract::shared(), - ty, - body, - ) - } - - /// Preserve both Pi contracts in structural identity. Conversion's - /// result-only equivalence must never select an egress representative. - pub(crate) fn intern_all_contract( - &mut self, - name: M::MField, - bi: M::MField, - contract: BinderContract, - result: ValueContract, - ty: &KExpr, - body: &KExpr, - ) -> KExpr { - crate::profile::bump_intern_nodes(); - let mut memo = InternMemo::default(); - let ct = self.intern_expr_cached(ty, &mut memo); - let cb = self.intern_expr_cached(body, &mut memo); - self.intern_expr_with( - ExprKey::All(contract, result, *ct.addr(), *cb.addr()), - || KExpr::all_contract(name, bi, contract, result, ct, cb), - ) - } - - /// Private constructor gate: `key` MUST describe `make()` exactly, with - /// children already canonical in this table. Never expose arbitrary keys - /// to callers. Entry lookup avoids a second parent probe on misses. - fn intern_expr_with( - &mut self, - key: ExprKey, - make: impl FnOnce() -> KExpr, - ) -> KExpr { - match self.exprs.entry(key) { - Entry::Occupied(entry) => entry.get().clone(), - Entry::Vacant(entry) => { - let e = make(); - debug_assert_eq!(entry.key(), &expr_key(&e)); - self.canon_exprs.insert(*e.addr()); - entry.insert(e).clone() - }, - } - } - - /// 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 { - crate::profile::bump_intern_nodes(); - if self.canon_univs.contains(u.addr()) { - return u; - } - // The root cannot recur below itself. Only descendants need memo entries. - self.intern_univ_node(&u, &mut InternMemo::default()) - } - - #[inline] - fn intern_univ_cached( - &mut self, - input: &KUniv, - memo: &mut InternMemo, - ) -> KUniv { - crate::profile::bump_intern_nodes(); - if self.canon_univs.contains(input.addr()) { - return input.clone(); - } - let ptr = std::ptr::from_ref(input.data()).addr(); - if let Some(canonical) = memo.univs.get(&ptr) { - return canonical.clone(); - } - let canonical = self.intern_univ_node(input, memo); - if !canonical.ptr_eq(input) { - memo.univs.insert(ptr, canonical.clone()); - } - canonical - } - - fn intern_univ_node( - &mut self, - input: &KUniv, - memo: &mut InternMemo, - ) -> KUniv { - use super::level::UnivData; - // Canonicalize children first; rebuild only if any child changed. - let u = match input.data() { - UnivData::Succ(inner, _) => { - let ci = self.intern_univ_cached(inner, memo); - if ci.ptr_eq(inner) { - input.clone() - } else { - KUniv::new(UnivData::Succ(ci, super::expr::fresh_uid())) - } - }, - UnivData::Max(a, b, _) => { - let ca = self.intern_univ_cached(a, memo); - let cb = self.intern_univ_cached(b, memo); - if ca.ptr_eq(a) && cb.ptr_eq(b) { - input.clone() - } else { - KUniv::new(UnivData::Max(ca, cb, super::expr::fresh_uid())) - } - }, - UnivData::IMax(a, b, _) => { - let ca = self.intern_univ_cached(a, memo); - let cb = self.intern_univ_cached(b, memo); - if ca.ptr_eq(a) && cb.ptr_eq(b) { - input.clone() - } else { - KUniv::new(UnivData::IMax(ca, cb, super::expr::fresh_uid())) - } - }, - UnivData::Zero(_) | UnivData::Param(..) => input.clone(), - }; - let key = univ_key(&u); - if let Some(existing) = self.univs.get(&key) { - existing.clone() - } else { - self.canon_univs.insert(*u.addr()); - self.univs.insert(key, u.clone()); - u - } - } - - /// 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 { - crate::profile::bump_intern_nodes(); - if self.canon_exprs.contains(e.addr()) { - return e; - } - // Retain the entire input DAG until this call-local pointer memo is gone. - // Do not memoize the root: this avoids allocating for the common case of - // a new node whose children are already canonical. - self.intern_expr_node(&e, &mut InternMemo::default()) - } - - #[inline] - fn intern_expr_cached( - &mut self, - input: &KExpr, - memo: &mut InternMemo, - ) -> KExpr { - crate::profile::bump_intern_nodes(); - // Keep this before the pointer memo: already-canonical uids retain the - // historical fast path, including their occurrence metadata. - if self.canon_exprs.contains(input.addr()) { - return input.clone(); - } - let ptr = std::ptr::from_ref(input.data()).addr(); - if let Some(canonical) = memo.exprs.get(&ptr) { - return canonical.clone(); - } - let canonical = self.intern_expr_node(input, memo); - // Do not mark the original uid canonical when it was replaced. Future - // edges to this input must return the actual canonical OUTPUT, not input. - if !canonical.ptr_eq(input) { - memo.exprs.insert(ptr, canonical.clone()); - } - canonical - } - - fn intern_expr_node( - &mut self, - input: &KExpr, - memo: &mut InternMemo, - ) -> KExpr { - use super::expr::ExprData; - let e = match input.data() { - ExprData::Sort(un, _) => { - let cu = self.intern_univ_cached(un, memo); - if cu.ptr_eq(un) { - input.clone() - } else { - // Child canonicalization only — same semantic level, same - // occurrence: the spelling decoration rides along. - KExpr::sort_full( - cu, - input.mdata().clone(), - input.univ_decor().clone(), - ) - } - }, - ExprData::Const(id, us, _) => { - // Most generated constants already have canonical levels. Delay the - // replacement buffer until the first changed child, without changing - // traversal order or re-interning the unchanged prefix. - let mut changed: Option>> = None; - for (i, un) in us.iter().enumerate() { - let cu = self.intern_univ_cached(un, memo); - if let Some(cus) = &mut changed { - cus.push(cu); - } else if !cu.ptr_eq(un) { - let mut cus = Vec::with_capacity(us.len()); - cus.extend(us[..i].iter().cloned()); - cus.push(cu); - changed = Some(cus); - } - } - if let Some(cus) = changed { - KExpr::cnst_full( - id.clone(), - cus.into_boxed_slice(), - input.mdata().clone(), - input.univ_decor().clone(), - ) - } else { - input.clone() - } - }, - ExprData::App(f, a, _) => { - let cf = self.intern_expr_cached(f, memo); - let ca = self.intern_expr_cached(a, memo); - if cf.ptr_eq(f) && ca.ptr_eq(a) { - input.clone() - } else { - KExpr::app_mdata(cf, ca, input.mdata().clone()) - } - }, - ExprData::Lam(n, bi, contract, t, b, _) => { - let ct = self.intern_expr_cached(t, memo); - let cb = self.intern_expr_cached(b, memo); - if ct.ptr_eq(t) && cb.ptr_eq(b) { - input.clone() - } else { - KExpr::lam_contract_mdata( - n.clone(), - bi.clone(), - *contract, - ct, - cb, - input.mdata().clone(), - ) - } - }, - ExprData::All(n, bi, contract, result, t, b, _) => { - let ct = self.intern_expr_cached(t, memo); - let cb = self.intern_expr_cached(b, memo); - if ct.ptr_eq(t) && cb.ptr_eq(b) { - input.clone() - } else { - KExpr::all_contract_mdata( - n.clone(), - bi.clone(), - *contract, - *result, - ct, - cb, - input.mdata().clone(), - ) - } - }, - ExprData::Let(n, t, v, b, contract, _) => { - let ct = self.intern_expr_cached(t, memo); - let cv = self.intern_expr_cached(v, memo); - let cb = self.intern_expr_cached(b, memo); - if ct.ptr_eq(t) && cv.ptr_eq(v) && cb.ptr_eq(b) { - input.clone() - } else { - KExpr::let_contract_mdata( - n.clone(), - *contract, - ct, - cv, - cb, - input.mdata().clone(), - ) - } - }, - ExprData::Prj(id, f, v, _) => { - let cv = self.intern_expr_cached(v, memo); - if cv.ptr_eq(v) { - input.clone() - } else { - KExpr::prj_mdata(id.clone(), *f, cv, input.mdata().clone()) - } - }, - ExprData::Var(..) - | ExprData::FVar(..) - | ExprData::Nat(..) - | ExprData::Str(..) => input.clone(), - }; - 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"); - existing.clone() - } else { - self.canon_exprs.insert(*e.addr()); - self.exprs.insert(key, e.clone()); - e - } - } -} - -#[cfg(test)] -#[path = "intern_reference.rs"] -mod intern_reference; - -#[cfg(test)] -#[path = "intern_tests.rs"] -mod intern_tests; - -/// Generated recursor, cached after inductive validation. -#[derive(Clone, Debug)] -pub struct GeneratedRecursor { - pub ind_addr: Address, - /// Canonical recursor arities. These are derived from the validated flat - /// inductive block, never from the stored `Recr` metadata. - pub lvls: u64, - pub params: u64, - pub motives: u64, - pub minors: u64, - pub indices: u64, - pub is_unsafe: bool, - pub ty: KExpr, - pub rules: Vec>, -} - -/// How generated recursor validation opens a nested occurrence `I As` of an -/// external inductive `I`. Either way the auxiliaries come in the order a walk -/// over the block's constructors discovers them. -/// -/// - `Canonical`, for an environment ingressed from Ixon: the occurrence opens -/// `I`'s whole stored block, one auxiliary per member, in stored member -/// order. The compiler stores each block in Lean's member order, so this is -/// the order of the stored (Lean's) auxiliary recursors. -/// - `Source`, for Lean's environment read directly (`lean_ingress`): the -/// occurrence opens `I` alone. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum RecursorAuxOrder { - Source, - Canonical, -} - -/// Snapshot of all `KEnv` cache sizes at a point in time. -/// -/// Used by the parallel kernel-check diagnostic mode (gated on -/// `IX_KERNEL_CHECK_DIAG=1`) to surface which scheduled blocks ratchet -/// per-worker cache memory. Each field is the entry count of one of -/// `KEnv`'s `FxHashMap`/`FxHashSet` caches at the moment of snapshotting. -#[derive(Clone, Copy, Debug, Default)] -pub struct KEnvCacheSizes { - pub consts: usize, - pub blocks: usize, - pub intern_exprs: usize, - pub intern_univs: usize, - pub whnf: usize, - pub whnf_no_delta: usize, - pub whnf_no_delta_cheap: usize, - pub whnf_core: usize, - pub whnf_core_cheap: usize, - pub infer: usize, - pub infer_only: usize, - pub def_eq: usize, - pub def_eq_cheap: usize, - pub def_eq_failure: usize, - pub unfold: usize, - pub ingress: usize, - pub is_prop: usize, - pub decl_summary: usize, - pub is_rec: usize, - pub recursor: usize, - pub rec_majors: usize, - pub block_peer_agreement: usize, - pub block_check_results: usize, -} - -impl KEnvCacheSizes { - /// Largest single cache size. Cheap proxy for "how big did this block - /// get" without summing. (Sum is misleading because the same content - /// hash can appear in multiple caches.) - pub fn max(&self) -> usize { - [ - self.consts, - self.blocks, - self.intern_exprs, - self.intern_univs, - self.whnf, - self.whnf_no_delta, - self.whnf_no_delta_cheap, - self.whnf_core, - self.whnf_core_cheap, - self.infer, - self.infer_only, - self.def_eq, - self.def_eq_cheap, - self.def_eq_failure, - self.unfold, - self.ingress, - self.is_prop, - self.decl_summary, - self.is_rec, - self.recursor, - self.rec_majors, - self.block_peer_agreement, - self.block_check_results, - ] - .into_iter() - .max() - .unwrap_or(0) - } -} - -impl std::fmt::Display for KEnvCacheSizes { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "consts={} intern_exprs={} intern_univs={} whnf={}/{}/{}/{}/{} infer={}/{} def_eq={}/{}/{} unfold={} ingress={} is_prop={} decl_summary={}", - self.consts, - self.intern_exprs, - self.intern_univs, - self.whnf, - self.whnf_no_delta, - self.whnf_no_delta_cheap, - self.whnf_core, - self.whnf_core_cheap, - self.infer, - self.infer_only, - self.def_eq, - self.def_eq_cheap, - self.def_eq_failure, - self.unfold, - self.ingress, - self.is_prop, - self.decl_summary, - ) - } -} - -/// The global zero kernel environment. -/// -/// Single-threaded: one worker owns one environment at a time. Contains all -/// kernel state for that worker: constants, intern table, and type-checking -/// caches. -/// -/// `get()` returns owned `KConst`/`Vec` (cheap Arc clones) to avoid -/// tying callers to internal map borrows. -pub struct KEnv { - // -- Constants -- - /// Loaded constants keyed by `KId`. - pub consts: FxHashMap, KConst>, - /// Block membership: block id → ordered member ids. - pub blocks: FxHashMap, Vec>>, - - // -- Intern table (hash-consing for pointer dedup) -- - pub intern: InternTable, - - // -- Primitives (resolved lazily from consts) -- - prims: OnceCell>, - - // -- Global caches (grow monotonically, stored in FxHashMap keyed by intern UID) -- - // All cache keys use `Addr` (`u64` monotonic intern-table UIDs) in `FxHashMap` - // rather 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, CtxAddr), KExpr>, - /// WHNF cache (no delta): (expr_hash, ctx_hash)-keyed. - 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 - /// because cheap projections leave projection-of-non-ctor terms stuck where - /// FULL would unfold the underlying definition. Reads and writes here are - /// 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, 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, 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, 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, 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, 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, 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, CtxAddr), bool>, - /// Failed def-eq pairs in lazy delta: canonical ordering by hash. - 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 - /// already content-addresses `(id, us)` (the head's address derives from id + - /// universe args). Mirrors lean4 C++ `m_unfold` cache. Cross-call sharing of - /// universe-substituted bodies eliminates O(body) walks on every unfold. - pub unfold_cache: FxHashMap>, - /// Memo of `try_reduce_nat_succ_iter` STUCK outcomes: succ-chain args - /// whose base never collapses to a literal. Keyed like `whnf_cache` - /// ((expr_hash, ctx_hash)). The succ-collapse loop runs its inner WHNF - /// in `NatSuccMode::Stuck`, which bypasses the WHNF caches, so without - /// 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, 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<(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` - /// invocation, and its uncached path costs `infer ∘ infer ∘ whnf` — - /// two type-inference runs and one full WHNF — to decide whether the - /// term's type is `Prop`. Because the answer depends only on the - /// *type* (not on the term whose type was inferred), caching by the - /// type's content hash + suffix-aware context lets every subsequent - /// 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, CtxAddr), bool>, - /// Conservative proof-eligibility summaries keyed by the exact Const - /// expression UID (including its instantiated universes). Types in this - /// declaration environment are the same assumptions ordinary inference - /// consults; no argument or declaration validation is replaced by a hit. - /// Clear on environment resets and declaration replacement, including - /// replacements of dependencies consulted while constructing a summary. - pub(crate) decl_summary_cache: - FxHashMap, - /// Computed `is_rec` per inductive, keyed by content address - pub is_rec_cache: FxHashMap, - /// Generated recursors, keyed by inductive Muts block id. - pub recursor_cache: FxHashMap, Vec>>, - /// Nested-auxiliary order expected by stored recursors in this environment. - pub recursor_aux_order: RecursorAuxOrder, - /// Maps the set of major inductive KIds to the inductive block id. - pub rec_majors_cache: FxHashMap>, KId>, - /// Mutual-block peer-agreement cache: records block ids whose peers have - /// already been verified to share the same universe (S3) and parameter - /// prefix (S3b). Populated by `check_inductive` after the per-peer loop - /// succeeds; collapses the naturally O(N²) per-peer iteration to O(N) - /// total work per block across all the peers' individual checks. - pub block_peer_agreement_cache: FxHashSet>, - /// Whole-block type-check results. Both successes and failures are cached, - /// 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 - /// ids must also be allocated from the shared environment. Otherwise two - /// checker instances could both mint `fv$0` and reuse an `infer(fv$0)` cache - /// entry under different local contexts. - next_fvar_id: u64, - - // -- Performance counters (audit §10) -- - /// Cache hit/miss and fuel-consumption counters, gated by - /// `IX_PERF_COUNTERS=1`. When the env var is unset the counters are - /// no-ops; when set, the totals are dumped from the `Drop` impl below. - pub perf: PerfCounters, - - /// Out-of-circuit profile recorder for sharding (see `plans/sharding.md`). - /// `Some` enables per-constant heartbeat + delta-unfold recording on this - /// worker; `None` (the default) has zero overhead. Deliberately preserved - /// across `clear`/`clear_releasing_memory` so recording survives scheduled - /// block boundaries within a run. - pub profile_sink: Option, -} - -impl Default for KEnv { - fn default() -> Self { - Self::new() - } -} - -/// Dump performance counters when the env is dropped, but only when -/// `IX_PERF_COUNTERS=1` is set. Serial `FxHashMap` teardown is left to -/// normal Rust drop order. -impl Drop for KEnv { - fn drop(&mut self) { - if super::perf::enabled() { - let summary = self.perf.summary(); - if !summary.is_empty() { - log::info!("{summary}"); - } - } - } -} - -impl KEnv { - pub fn new() -> Self { - Self::new_with_recursor_aux_order(RecursorAuxOrder::Canonical) - } - - pub fn new_with_recursor_aux_order( - recursor_aux_order: RecursorAuxOrder, - ) -> Self { - KEnv { - consts: FxHashMap::default(), - blocks: FxHashMap::default(), - intern: InternTable::new(), - prims: OnceCell::new(), - whnf_cache: FxHashMap::default(), - whnf_no_delta_cache: FxHashMap::default(), - whnf_no_delta_cheap_cache: FxHashMap::default(), - whnf_core_cache: FxHashMap::default(), - whnf_core_cheap_cache: FxHashMap::default(), - infer_cache: FxHashMap::default(), - infer_only_cache: FxHashMap::default(), - def_eq_cache: FxHashMap::default(), - def_eq_cheap_cache: FxHashMap::default(), - def_eq_failure: FxHashSet::default(), - unfold_cache: FxHashMap::default(), - nat_succ_stuck: FxHashSet::default(), - ingress_cache: FxHashMap::default(), - is_prop_cache: FxHashMap::default(), - decl_summary_cache: FxHashMap::default(), - is_rec_cache: FxHashMap::default(), - recursor_cache: FxHashMap::default(), - recursor_aux_order, - 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, - } - } - - pub fn fresh_fvar_id(&mut self) -> FVarId { - let id = self.next_fvar_id; - self.next_fvar_id = self.next_fvar_id.checked_add(1).expect( - "KEnv::fresh_fvar_id: u64 counter overflow (more than 2^64 fvars in one environment)", - ); - FVarId(id) - } - - /// Resolve primitives from the environment (cached via `OnceCell`). - pub fn prims(&self) -> &Primitives { - self.prims.get_or_init(|| Primitives::from_env(self)) - } - - /// Pre-initialize the primitives cache with an externally-resolved - /// `Primitives`. Returns `Ok(())` on success, `Err(p)` if `prims()` - /// has already been called (the `OnceCell` is full). - /// - /// Used by `TypeChecker::new_with_lazy_ixon` to install primitives - /// resolved from the IxonIngressLookups address→name map *before* any - /// constants have been faulted into the local KEnv — without this - /// seeding, `prims()` would derive primitives from an empty env and - /// return synthetic `@` KIds that wouldn't match the real names - /// later faulted in. - /// - /// `Primitives` is large (~2 KB), so the error path is allowed to - /// be big — the caller hands ownership in and only retrieves it on - /// failure. - #[allow(clippy::result_large_err)] - pub fn set_prims(&mut self, p: Primitives) -> Result<(), Primitives> { - self.prims.set(p) - } - - pub fn has_prims(&self) -> bool { - self.prims.get().is_some() - } - - pub fn get(&self, id: &KId) -> Option> { - self.consts.get(id).cloned() - } - - pub fn insert(&mut self, id: KId, c: KConst) { - if let Some(marker) = super::primitive::reserved_marker_name(&id.addr) { - panic!( - "attempted to insert {id} at reserved kernel marker address {marker} ({})", - id.addr.hex() - ); - } - if self.consts.insert(id, c).is_some() { - self.decl_summary_cache.clear(); - } - } - - pub fn len(&self) -> usize { - self.consts.len() - } - - pub fn is_empty(&self) -> bool { - self.consts.is_empty() - } - - pub fn contains_key(&self, id: &KId) -> bool { - self.consts.contains_key(id) - } - - /// Iterate over all constants. Returns owned (KId, KConst) pairs. - pub fn iter(&self) -> impl Iterator, KConst)> + '_ { - self.consts.iter().map(|(id, c)| (id.clone(), c.clone())) - } - - /// Get block members. Returns owned Vec (cheap KId clones). - pub fn get_block(&self, id: &KId) -> Option>> { - self.blocks.get(id).cloned() - } - - /// Insert a block membership entry. - pub fn insert_block(&mut self, id: KId, members: Vec>) { - self.blocks.insert(id, members); - } - - /// Clear all worker-local kernel state before checking another scheduled - /// block or when a caller needs a fresh environment. - pub fn clear(&mut self) { - self.consts.clear(); - 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(); - self.whnf_no_delta_cheap_cache.clear(); - self.whnf_core_cache.clear(); - self.whnf_core_cheap_cache.clear(); - self.infer_cache.clear(); - self.infer_only_cache.clear(); - self.def_eq_cache.clear(); - self.def_eq_cheap_cache.clear(); - self.def_eq_failure.clear(); - self.unfold_cache.clear(); - self.nat_succ_stuck.clear(); - self.ingress_cache.clear(); - self.is_prop_cache.clear(); - self.decl_summary_cache.clear(); - self.recursor_cache.clear(); - 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; - } - - /// Snapshot of all per-worker cache sizes. Cheap (each `len()` is O(1)); - /// useful as diagnostic input to identify which blocks blow up - /// individual caches before `clear_releasing_memory` reclaims them. - pub fn cache_sizes(&self) -> KEnvCacheSizes { - KEnvCacheSizes { - consts: self.consts.len(), - blocks: self.blocks.len(), - intern_exprs: self.intern.exprs.len(), - intern_univs: self.intern.univs.len(), - whnf: self.whnf_cache.len(), - whnf_no_delta: self.whnf_no_delta_cache.len(), - whnf_no_delta_cheap: self.whnf_no_delta_cheap_cache.len(), - whnf_core: self.whnf_core_cache.len(), - whnf_core_cheap: self.whnf_core_cheap_cache.len(), - infer: self.infer_cache.len(), - infer_only: self.infer_only_cache.len(), - def_eq: self.def_eq_cache.len(), - def_eq_cheap: self.def_eq_cheap_cache.len(), - def_eq_failure: self.def_eq_failure.len(), - unfold: self.unfold_cache.len(), - ingress: self.ingress_cache.len(), - is_prop: self.is_prop_cache.len(), - decl_summary: self.decl_summary_cache.len(), - is_rec: self.is_rec_cache.len(), - recursor: self.recursor_cache.len(), - rec_majors: self.rec_majors_cache.len(), - block_peer_agreement: self.block_peer_agreement_cache.len(), - block_check_results: self.block_check_results.len(), - } - } - - /// Clear worker-local state and drop backing allocations. - /// - /// `clear()` preserves `HashMap` capacity, which is useful for reuse but - /// problematic for full-env checking: one very large block can permanently - /// ratchet a worker's retained cache allocation. This variant is for - /// scheduled-block boundaries where memory pressure matters more than - /// preserving buckets for the next unrelated block. - pub fn clear_releasing_memory(&mut self) { - self.consts = FxHashMap::default(); - self.blocks = FxHashMap::default(); - self.intern = InternTable::new(); - self.prims = OnceCell::new(); - self.whnf_cache = FxHashMap::default(); - self.whnf_no_delta_cache = FxHashMap::default(); - self.whnf_no_delta_cheap_cache = FxHashMap::default(); - self.whnf_core_cache = FxHashMap::default(); - self.whnf_core_cheap_cache = FxHashMap::default(); - self.infer_cache = FxHashMap::default(); - self.infer_only_cache = FxHashMap::default(); - self.def_eq_cache = FxHashMap::default(); - self.def_eq_cheap_cache = FxHashMap::default(); - self.def_eq_failure = FxHashSet::default(); - self.unfold_cache = FxHashMap::default(); - self.nat_succ_stuck = FxHashSet::default(); - self.ingress_cache = FxHashMap::default(); - self.is_prop_cache = FxHashMap::default(); - self.decl_summary_cache = FxHashMap::default(); - self.recursor_cache = FxHashMap::default(); - 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; - } - - /// Clear the same logical state as [`Self::clear`], retaining only modest - /// backing tables for the next scheduled check. - /// - /// `max_capacity` is an entry-capacity bound on EACH cleared collection, - /// not a byte budget for the environment or process. Oversized allocations - /// are discarded, not shrunk/reallocated. Expression references, canonical - /// uid sets, scope-sensitive memo entries and block results are all cleared - /// before free-variable ids can be reused. The existing address-keyed - /// `is_rec_cache`, profile sink and configuration survive just as they do - /// with `clear` and `clear_releasing_memory`. - pub fn clear_with_capacity_limit(&mut self, max_capacity: usize) { - // Keep logical reset centralized: capacity reuse must never accidentally - // retain an entry when the ordinary reset gains another cache. - self.clear(); - macro_rules! release_oversized { - ($($table:expr),+ $(,)?) => { - $(if $table.capacity() > max_capacity { - $table = Default::default(); - })+ - }; - } - release_oversized!( - self.consts, - self.blocks, - self.intern.univs, - self.intern.exprs, - self.intern.canon_exprs, - self.intern.canon_univs, - self.intern.subst_scratch, - self.intern.lift_scratch, - self.intern.clo_scratch_pool, - self.whnf_cache, - self.whnf_no_delta_cache, - self.whnf_no_delta_cheap_cache, - self.whnf_core_cache, - self.whnf_core_cheap_cache, - self.infer_cache, - self.infer_only_cache, - self.def_eq_cache, - self.def_eq_cheap_cache, - self.def_eq_failure, - self.unfold_cache, - self.nat_succ_stuck, - self.ingress_cache, - self.is_prop_cache, - self.decl_summary_cache, - self.recursor_cache, - self.rec_majors_cache, - self.block_peer_agreement_cache, - self.block_check_results, - self.prim_family_cache, - ); - } - - /// Clear only the reduction-memo caches (whnf / infer / def-eq / unfold / - /// is-prop). Structural caches (`consts`, `blocks`, `intern`, recursor - /// caches, `block_check_results`) and the profile sink are preserved. - /// - /// Used by the profile recorder's per-constant isolation mode: clearing the - /// cross-constant memo between constants forces every delta-unfold to - /// re-execute (sound delta recording) and makes recorded heartbeats reflect - /// the in-circuit cost, which has no cross-constant memoization. Clearing a - /// pure memo never affects correctness — only performance. - pub fn clear_reduction_caches(&mut self) { - self.decl_summary_cache.clear(); - self.whnf_cache.clear(); - self.whnf_no_delta_cache.clear(); - self.whnf_no_delta_cheap_cache.clear(); - self.whnf_core_cache.clear(); - self.whnf_core_cheap_cache.clear(); - self.infer_cache.clear(); - self.infer_only_cache.clear(); - self.def_eq_cache.clear(); - self.def_eq_cheap_cache.clear(); - self.def_eq_failure.clear(); - self.unfold_cache.clear(); - self.nat_succ_stuck.clear(); - self.is_prop_cache.clear(); - } -} - -#[cfg(test)] -mod tests { - use super::super::mode::Anon; - use super::super::primitive::PrimAddrs; - use super::*; - use ix_common::address::Address; - - fn mk_addr(s: &str) -> Address { - Address::hash(s.as_bytes()) - } - - fn mk_id(s: &str) -> KId { - KId::new(mk_addr(s), ()) - } - - fn mk_axio(_s: &str) -> KConst { - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: KExpr::sort(KUniv::zero()), - } - } - - #[test] - fn new_env_is_empty() { - let env = KEnv::::new(); - assert!(env.is_empty()); - assert_eq!(env.len(), 0); - } - - #[test] - fn insert_and_get() { - let mut env = KEnv::::new(); - let id = mk_id("Nat"); - env.insert(id.clone(), mk_axio("Nat")); - assert_eq!(env.len(), 1); - assert!(env.get(&id).is_some()); - } - - #[test] - #[should_panic(expected = "reserved kernel marker")] - fn insert_reserved_marker_panics() { - let mut env = KEnv::::new(); - let id = KId::new(PrimAddrs::new().eager_reduce, ()); - env.insert(id, mk_axio("eager_reduce")); - } - - #[test] - fn contains_key_works() { - let mut env = KEnv::::new(); - let id = mk_id("Nat"); - assert!(!env.contains_key(&id)); - env.insert(id.clone(), mk_axio("Nat")); - assert!(env.contains_key(&id)); - } - - #[test] - fn get_missing_returns_none() { - let env = KEnv::::new(); - assert!(env.get(&mk_id("missing")).is_none()); - } - - #[test] - fn bounded_clear_reuses_small_tables_but_not_logical_entries() { - let mut env = KEnv::::new(); - let id = mk_id("old"); - let ctx = blake3::hash(b"old context"); - let old = env.intern.intern_expr(KExpr::var(0, ())); - let key = (old.hash_key(), ctx); - env.insert(id.clone(), mk_axio("old")); - env.blocks.insert(id.clone(), vec![id.clone()]); - env.whnf_cache.insert(key, old.clone()); - env.infer_cache.insert(key, old.clone()); - env.infer_only_cache.insert(key, old.clone()); - env.def_eq_cache.insert((key.0, key.0, ctx), true); - env.block_check_results.insert(id.clone(), Ok(())); - env - .intern - .subst_scratch - .restore_after_call(FxHashMap::from_iter([((key.0, 0), old.clone())])); - env - .intern - .lift_scratch - .restore_after_call(FxHashMap::from_iter([((key.0, 0), old.clone())])); - env - .intern - .clo_scratch_pool - .push(FxHashMap::from_iter([((key.0, 0), old.clone())])); - assert_eq!(env.fresh_fvar_id(), FVarId(0)); - assert_eq!(env.fresh_fvar_id(), FVarId(1)); - let capacities = ( - env.consts.capacity(), - env.intern.exprs.capacity(), - env.whnf_cache.capacity(), - ); - - env.clear_with_capacity_limit(128); - - assert_eq!(env.cache_sizes().max(), 0); - assert!(env.intern.canon_exprs.is_empty()); - assert!(env.intern.canon_univs.is_empty()); - assert!(env.intern.subst_scratch.is_empty()); - assert!(env.intern.lift_scratch.is_empty()); - assert!(env.intern.clo_scratch_pool.is_empty()); - assert_eq!(env.fresh_fvar_id(), FVarId(0)); - assert_eq!( - capacities, - ( - env.consts.capacity(), - env.intern.exprs.capacity(), - env.whnf_cache.capacity(), - ) - ); - let new = env.intern.intern_expr(KExpr::var(0, ())); - assert!(!old.ptr_eq(&new), "reset must release the old canonical entry"); - } - - #[test] - fn bounded_clear_discards_oversized_allocations() { - let mut env = KEnv::::new(); - env.consts.reserve(256); - env.intern.exprs.reserve(256); - env.intern.canon_exprs.reserve(256); - env.whnf_cache.reserve(256); - let mut scratch = env.intern.subst_scratch.take_for_call(); - scratch.reserve(256); - env.intern.subst_scratch.restore_after_call(scratch); - env.intern.clo_scratch_pool.reserve(256); - env.infer_cache.reserve(16); - let small_capacity = env.infer_cache.capacity(); - - env.clear_with_capacity_limit(128); - - assert_eq!(env.consts.capacity(), 0); - assert_eq!(env.intern.exprs.capacity(), 0); - assert_eq!(env.intern.canon_exprs.capacity(), 0); - assert_eq!(env.whnf_cache.capacity(), 0); - assert_eq!(env.intern.subst_scratch.capacity(), 0); - assert_eq!(env.intern.clo_scratch_pool.capacity(), 0); - assert_eq!(env.infer_cache.capacity(), small_capacity); - - env.clear_with_capacity_limit(0); - assert_eq!(env.infer_cache.capacity(), 0); - } - - #[test] - fn get_by_id_works() { - let mut env = KEnv::::new(); - let id = mk_id("Nat"); - env.insert(id.clone(), mk_axio("Nat")); - assert!(env.get(&id).is_some()); - assert!(env.get(&mk_id("missing")).is_none()); - } - - #[test] - fn intern_univ_dedup() { - let mut it = InternTable::::new(); - let z1 = KUniv::zero(); - let z2 = KUniv::zero(); - // Before interning, same hash but different Arcs - assert!(!z1.ptr_eq(&z2)); - let i1 = it.intern_univ(z1); - let i2 = it.intern_univ(z2); - assert!(i1.ptr_eq(&i2)); - } - - #[test] - fn intern_univ_different() { - let mut it = InternTable::::new(); - let z = it.intern_univ(KUniv::zero()); - let s = it.intern_univ(KUniv::succ(KUniv::zero())); - assert!(!z.ptr_eq(&s)); - } - - #[test] - fn intern_expr_dedup() { - let mut it = InternTable::::new(); - let v1 = KExpr::var(0, ()); - let v2 = KExpr::var(0, ()); - assert!(!v1.ptr_eq(&v2)); - let i1 = it.intern_expr(v1); - let i2 = it.intern_expr(v2); - assert!(i1.ptr_eq(&i2)); - } - - #[test] - fn intern_expr_different() { - let mut it = InternTable::::new(); - let v0 = it.intern_expr(KExpr::var(0, ())); - let v1 = it.intern_expr(KExpr::var(1, ())); - assert!(!v0.ptr_eq(&v1)); - } - - #[test] - fn iter_all_entries() { - let mut env = KEnv::::new(); - env.insert(mk_id("A"), mk_axio("A")); - env.insert(mk_id("B"), mk_axio("B")); - assert_eq!(env.iter().count(), 2); - } -} diff --git a/crates/kernel/src/env/scratch.rs b/crates/kernel/src/env/scratch.rs deleted file mode 100644 index ca26e3d54..000000000 --- a/crates/kernel/src/env/scratch.rs +++ /dev/null @@ -1,190 +0,0 @@ -//! Reuse allocation, never logical memo entries, between independent calls. - -use rustc_hash::FxHashMap; - -// Entry-capacity floor, not a bound on a live traversal's memory. These are -// storage heuristics only: a call may grow its memo without limit as before. -const SMALL_CAPACITY: usize = 4_096; -const SPARSE_RATIO: usize = 16; -const SPARSE_USES_BEFORE_RELEASE: u8 = 2; - -/// A per-call memo whose backing allocation follows recent occupancy. -/// -/// A bulk traversal can grow a table that subsequent tiny calls spend most -/// of their time clearing: HashMap::clear scans control bytes across its -/// capacity, even when few entries remain. Release an oversized allocation -/// after two consecutive uses below 1/16 occupancy. Keeping one sparse use -/// tolerates alternating large/small calls without repeatedly regrowing the -/// large table. Small and well-used tables retain their allocation. -/// -/// Entries from the previous call are retained solely to observe occupancy -/// and are ALWAYS removed before handing the map to the next call. Neither -/// keys nor values nor within-call memoization semantics depend on sizing. -pub(crate) struct ScratchMap { - map: FxHashMap, - sparse_uses: u8, -} - -impl Default for ScratchMap { - fn default() -> Self { - Self { map: FxHashMap::default(), sparse_uses: 0 } - } -} - -impl ScratchMap { - /// Borrow an EMPTY memo for one traversal. The owner keeps only sizing - /// history and an empty placeholder until `restore_after_call`. - pub(crate) fn take_for_call(&mut self) -> FxHashMap { - let capacity = self.map.capacity(); - if capacity > SMALL_CAPACITY && self.map.len() < capacity / SPARSE_RATIO { - self.sparse_uses += 1; - if self.sparse_uses == SPARSE_USES_BEFORE_RELEASE { - // Drop directly: clearing first would scan the oversized allocation - // once more just to discard it. A fresh map allocates only on insert. - self.map = FxHashMap::default(); - self.sparse_uses = 0; - } - } else { - self.sparse_uses = 0; - } - let mut map = std::mem::take(&mut self.map); - map.clear(); - map - } - - pub(crate) fn restore_after_call(&mut self, map: FxHashMap) { - debug_assert_eq!(self.map.capacity(), 0, "scratch is already restored"); - self.map = map; - } - - /// Ordinary environment reset preserves capacity but not sizing history. - pub(crate) fn clear(&mut self) { - self.map.clear(); - self.sparse_uses = 0; - } - - pub(crate) fn capacity(&self) -> usize { - self.map.capacity() - } - - #[cfg(test)] - pub(crate) fn is_empty(&self) -> bool { - self.map.is_empty() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::sync::Arc; - - fn large_scratch() -> ScratchMap { - let mut scratch = ScratchMap::default(); - scratch.map.reserve(SMALL_CAPACITY * 4); - scratch - } - - fn fill(scratch: &mut ScratchMap, len: usize) { - assert!(scratch.map.is_empty()); - scratch.map.extend((0..len).map(|i| (i, i))); - } - - #[test] - fn scratch_discards_only_after_two_sparse_uses() { - let mut scratch = large_scratch(); - let large = scratch.capacity(); - fill(&mut scratch, 1); - let mut map = scratch.take_for_call(); - assert!(map.is_empty()); - assert_eq!(map.capacity(), large, "one sparse use retains capacity"); - map.insert(2, 3); - scratch.restore_after_call(map); - let map = scratch.take_for_call(); - assert!(map.is_empty()); - assert_eq!(map.capacity(), 0, "sustained sparse use releases capacity"); - assert_eq!(scratch.sparse_uses, 0); - } - - #[test] - fn scratch_retains_small_tables() { - let mut scratch = ScratchMap::::default(); - scratch.map.reserve(128); - let capacity = scratch.capacity(); - assert!(capacity <= SMALL_CAPACITY); - for _ in 0..8 { - fill(&mut scratch, 1); - let map = scratch.take_for_call(); - assert!(map.is_empty()); - assert_eq!(map.capacity(), capacity); - scratch.restore_after_call(map); - } - } - - #[test] - fn scratch_retains_well_used_large_tables_at_ratio_boundary() { - let mut scratch = large_scratch(); - let capacity = scratch.capacity(); - for _ in 0..8 { - fill(&mut scratch, capacity / SPARSE_RATIO); - let map = scratch.take_for_call(); - assert!(map.is_empty()); - assert_eq!(map.capacity(), capacity); - assert_eq!(scratch.sparse_uses, 0); - scratch.restore_after_call(map); - } - } - - #[test] - fn scratch_alternating_large_small_uses_do_not_churn() { - let mut scratch = large_scratch(); - let capacity = scratch.capacity(); - for _ in 0..8 { - for len in [1, capacity / 2] { - fill(&mut scratch, len); - let map = scratch.take_for_call(); - assert!(map.is_empty()); - assert_eq!(map.capacity(), capacity); - scratch.restore_after_call(map); - } - } - } - - #[test] - fn scratch_empty_large_table_also_releases() { - let mut scratch = large_scratch(); - let map = scratch.take_for_call(); - scratch.restore_after_call(map); - assert_eq!(scratch.take_for_call().capacity(), 0); - } - - #[test] - fn scratch_clear_resets_history_and_releases_values() { - let mut scratch = large_scratch(); - let capacity = scratch.capacity(); - fill(&mut scratch, 1); - let mut map = scratch.take_for_call(); - map.insert(1, 2); - scratch.restore_after_call(map); - assert_eq!(scratch.sparse_uses, 1); - scratch.clear(); - assert!(scratch.is_empty()); - assert_eq!(scratch.sparse_uses, 0); - assert_eq!(scratch.capacity(), capacity); - assert_eq!(scratch.take_for_call().capacity(), capacity); - } - - #[test] - fn scratch_never_returns_old_entries_or_retains_their_values() { - let mut scratch = ScratchMap::>::default(); - scratch.map.reserve(SMALL_CAPACITY * 4); - for key in 0..4 { - let value = Arc::new(key); - let weak = Arc::downgrade(&value); - scratch.map.insert(key, value); - let map = scratch.take_for_call(); - assert!(map.is_empty()); - assert!(weak.upgrade().is_none(), "old memo value survived reset"); - scratch.restore_after_call(map); - } - } -} diff --git a/crates/kernel/src/equiv.rs b/crates/kernel/src/equiv.rs deleted file mode 100644 index e39a1c013..000000000 --- a/crates/kernel/src/equiv.rs +++ /dev/null @@ -1,266 +0,0 @@ -//! Union-find (disjoint set) for context-aware definitional equality caching. -//! -//! Provides O(α(n)) amortized equivalence checks via weighted quick-union -//! with path halving. Keys retain the expression hash, context hash, the -//! context-suffix radius used to construct that hash, and the expression's -//! intrinsic local-binder radius. - -use rustc_hash::FxHashMap; - -use super::env::{Addr, CtxAddr}; - -/// One expression in one context-suffix interpretation. -/// -/// The radius is semantically load-bearing even if two suffix calculations -/// emit the same digest: DefEq transport is justified only at a common -/// requested radius. Keeping it in the union-find key prevents transitive -/// components from joining equality results established at different radii. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct EqKey { - pub expr_addr: Addr, - pub ctx_addr: CtxAddr, - /// Radius at which `ctx_addr` was computed for this comparison. - pub lbr: u64, - /// Intrinsic local-binder radius of the expression at `expr_addr`. - pub expr_lbr: u64, -} - -impl EqKey { - pub fn new( - expr_addr: Addr, - ctx_addr: CtxAddr, - lbr: u64, - expr_lbr: u64, - ) -> Self { - Self { expr_addr, ctx_addr, lbr, expr_lbr } - } - - /// Whether two representatives can safely reuse a DefEq cache context. - /// Their intrinsic expression radii must reconstruct the requested radius - /// at which the context digest was computed. - pub fn root_cache_scope_matches( - left: &Self, - right: &Self, - ctx_addr: CtxAddr, - lbr: u64, - ) -> bool { - left.ctx_addr == ctx_addr - && right.ctx_addr == ctx_addr - && left.lbr == lbr - && right.lbr == lbr - && left.expr_lbr.max(right.expr_lbr) == lbr - } -} - -/// Union-find structure for tracking definitional equality between -/// context-aware expression keys. -#[derive(Debug, Clone)] -pub struct EquivManager { - /// Map from composite key to union-find node index. - key_to_node: FxHashMap, - /// `parent[i]` = parent of node `i`. Root if `parent[i] == i`. - parent: Vec, - /// `rank[i]` = upper bound on height of subtree rooted at `i`. - rank: Vec, - /// Reverse map: node index → composite key. - node_to_key: Vec, -} - -impl Default for EquivManager { - fn default() -> Self { - Self::new() - } -} - -impl EquivManager { - pub fn new() -> Self { - EquivManager { - key_to_node: FxHashMap::default(), - parent: Vec::new(), - rank: Vec::new(), - node_to_key: Vec::new(), - } - } - - /// Reset all equivalence information. - pub fn clear(&mut self) { - self.key_to_node.clear(); - self.parent.clear(); - self.rank.clear(); - self.node_to_key.clear(); - } - - /// Get or create a node index for a composite key. - fn node_for_key(&mut self, key: EqKey) -> usize { - if let Some(&node) = self.key_to_node.get(&key) { - return node; - } - let node = self.parent.len(); - self.parent.push(node); - self.rank.push(0); - self.node_to_key.push(key); - self.key_to_node.insert(key, node); - node - } - - /// Find root with path halving (every other node → grandparent). - fn find(&mut self, mut node: usize) -> usize { - while self.parent[node] != node { - self.parent[node] = self.parent[self.parent[node]]; - node = self.parent[node]; - } - node - } - - /// Union by rank. Returns true if sets were different. - fn union(&mut self, a: usize, b: usize) -> bool { - let ra = self.find(a); - let rb = self.find(b); - if ra == rb { - return false; - } - if self.rank[ra] < self.rank[rb] { - self.parent[ra] = rb; - } else if self.rank[ra] > self.rank[rb] { - self.parent[rb] = ra; - } else { - self.parent[rb] = ra; - self.rank[ra] += 1; - } - true - } - - /// Check if two composite keys are equivalent. - /// - /// Takes keys by reference — callers in the `is_def_eq` hot path - /// already hold `EqKey` tuples as local bindings, and forcing them to - /// pass by value would require an Arc-clone on each component. With - /// by-ref we avoid that clone entirely (see `src/ix/kernel/def_eq.rs` - /// for the caller pattern). - pub fn is_equiv(&mut self, k1: &EqKey, k2: &EqKey) -> bool { - if k1 == k2 { - return true; - } - let n1 = match self.key_to_node.get(k1) { - Some(&n) => n, - None => return false, - }; - let n2 = match self.key_to_node.get(k2) { - Some(&n) => n, - None => return false, - }; - self.find(n1) == self.find(n2) - } - - /// Find the root representative key for a given composite key. - /// Returns None if the key is not in the union-find. - /// - /// Like `is_equiv`, takes the lookup key by reference so callers can - /// reuse a single `EqKey` binding across multiple queries without - /// cloning it for each call. - pub fn find_root_key(&mut self, key: &EqKey) -> Option { - let node = *self.key_to_node.get(key)?; - let root = self.find(node); - Some(self.node_to_key[root]) - } - - /// Record that two composite keys are definitionally equal. - /// - /// Kept by-value because `node_for_key` inserts the key into the - /// internal `key_to_node` map on first observation, requiring - /// ownership transfer. Callers that have already consumed their - /// `EqKey`s should clone at the call site, not here. - pub fn add_equiv(&mut self, k1: EqKey, k2: EqKey) { - let n1 = self.node_for_key(k1); - let n2 = self.node_for_key(k2); - self.union(n1, n2); - } -} - -#[cfg(test)] -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 = ctx(0); - let a = EqKey::new(addr(100), zero, 0, 0); - let b = EqKey::new(addr(200), zero, 0, 0); - assert!(!em.is_equiv(&a, &b)); - em.add_equiv(a, b); - assert!(em.is_equiv(&a, &b)); - assert!(em.is_equiv(&b, &a)); - } - - #[test] - fn test_transitivity() { - let mut em = EquivManager::new(); - let zero = ctx(0); - let a = EqKey::new(addr(100), zero, 0, 0); - let b = EqKey::new(addr(200), zero, 0, 0); - let c = EqKey::new(addr(300), zero, 0, 0); - em.add_equiv(a, b); - em.add_equiv(b, c); - assert!(em.is_equiv(&a, &c)); - } - - #[test] - fn test_context_isolation() { - let mut em = EquivManager::new(); - let ctx1 = ctx(1); - let ctx2 = ctx(2); - let a1 = EqKey::new(addr(100), ctx1, 1, 1); - let b1 = EqKey::new(addr(200), ctx1, 1, 1); - let a2 = EqKey::new(addr(100), ctx2, 1, 1); - let b2 = EqKey::new(addr(200), ctx2, 1, 1); - em.add_equiv(a1, b1); - assert!(em.is_equiv(&a1, &b1)); - assert!(!em.is_equiv(&a2, &b2)); - } - - #[test] - fn test_radius_isolation() { - let mut em = EquivManager::new(); - let zero = ctx(0); - let a1 = EqKey::new(addr(100), zero, 1, 1); - let b1 = EqKey::new(addr(200), zero, 1, 1); - let a2 = EqKey::new(addr(100), zero, 2, 1); - let b2 = EqKey::new(addr(200), zero, 2, 1); - em.add_equiv(a1, b1); - assert!(em.is_equiv(&a1, &b1)); - assert!(!em.is_equiv(&a2, &b2)); - } - - #[test] - fn test_intrinsic_radius_isolation() { - let mut em = EquivManager::new(); - let zero = ctx(0); - let a1 = EqKey::new(addr(100), zero, 2, 1); - let b1 = EqKey::new(addr(200), zero, 2, 2); - let a2 = EqKey::new(addr(100), zero, 2, 2); - let b2 = EqKey::new(addr(200), zero, 2, 2); - em.add_equiv(a1, b1); - assert!(em.is_equiv(&a1, &b1)); - assert!(!em.is_equiv(&a2, &b2)); - } - - #[test] - fn test_root_cache_scope_requires_intrinsic_radius() { - let scope = ctx(0); - let low_left = EqKey::new(addr(100), scope, 2, 1); - let low_right = EqKey::new(addr(200), scope, 2, 1); - let high_right = EqKey::new(addr(200), scope, 2, 2); - - assert!(!EqKey::root_cache_scope_matches(&low_left, &low_right, scope, 2)); - assert!(EqKey::root_cache_scope_matches(&low_left, &high_right, scope, 2)); - } -} diff --git a/crates/kernel/src/error.rs b/crates/kernel/src/error.rs deleted file mode 100644 index c74647116..000000000 --- a/crates/kernel/src/error.rs +++ /dev/null @@ -1,230 +0,0 @@ -//! Type checker error types. - -use ix_common::address::Address; - -use super::expr::KExpr; -use super::mode::KernelMode; - -/// Convert `u64` to `usize`, returning `TcError` if the value exceeds -/// the platform's pointer width (relevant for 32-bit targets). -#[inline(always)] -pub fn u64_to_usize(val: u64) -> Result> { - usize::try_from(val) - .map_err(|_e| TcError::Other(format!("{val} exceeds usize::MAX"))) -} - -#[derive(Clone, Debug)] -pub enum TcError { - TypeExpected, - FunExpected { - e: KExpr, - whnf: KExpr, - }, - AppTypeMismatch { - a_ty: KExpr, - dom: KExpr, - depth: usize, - }, - DeclTypeMismatch, - UnknownConst(Address), - UnivParamMismatch { - expected: u64, - got: usize, - }, - /// An interior universe substitution hit `Param(idx)` where `idx` was - /// out of range for the supplied universe list. Distinct from - /// `UnivParamMismatch` which is the arity gate at Const-infer time; - /// this variant fires from `subst_univ` as defense-in-depth against - /// any code path that reaches substitution without the arity check. - UnivParamOutOfRange { - idx: u64, - bound: usize, - }, - VarOutOfRange { - idx: u64, - ctx_len: usize, - }, - DefEqFailed, - MaxRecDepth, - MaxRecFuel, - /// The check's recursion came within the checker's stack reserve (see - /// [`crate::tc::STACK_RESERVE_MAX`]) of the end of its thread's stack, and - /// declined instead of overflowing. - StackExhausted, - /// The declaration is, or refers to, one of Lean's native-evaluation - /// constants (`Lean.trustCompiler` and `Lean.reduceBool`, - /// `Lean.reduceNat`, `Lean.ofReduceBool`, `Lean.ofReduceNat`, which are - /// built on it). Evaluation by the compiler is outside the checked - /// theory, so the kernel declines them as the certified checker does. - NativeEvaluation(Address), - Other(String), -} - -impl std::fmt::Display for TcError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TcError::TypeExpected => write!(f, "type expected"), - TcError::FunExpected { e, whnf } => { - write!(f, "function expected, got {e} (whnf: {whnf})") - }, - TcError::AppTypeMismatch { a_ty, dom, depth } => { - write!( - f, - "app type mismatch at depth {depth}: arg has type {a_ty}, domain is {dom}" - ) - }, - TcError::DeclTypeMismatch => write!(f, "declaration type mismatch"), - TcError::UnknownConst(addr) => { - write!(f, "unknown constant {:.12}", addr.hex()) - }, - TcError::UnivParamMismatch { expected, got } => { - write!(f, "universe param count: expected {expected}, got {got}") - }, - TcError::UnivParamOutOfRange { idx, bound } => { - write!( - f, - "universe Param({idx}) out of range: only {bound} universes supplied" - ) - }, - TcError::VarOutOfRange { idx, ctx_len } => { - write!(f, "variable #{idx} out of range (context depth {ctx_len})") - }, - TcError::DefEqFailed => write!(f, "definitional equality check failed"), - TcError::MaxRecDepth => write!(f, "max recursion depth exceeded"), - TcError::MaxRecFuel => write!(f, "recursive fuel exhausted"), - TcError::StackExhausted => { - write!(f, "recursion too deep for the native stack") - }, - TcError::NativeEvaluation(addr) => write!( - f, - "native-evaluation constant {:.12}: evaluation by the compiler is \ - outside the checked theory", - addr.hex() - ), - TcError::Other(s) => write!(f, "{s}"), - } - } -} - -#[cfg(test)] -mod tests { - use super::super::expr::KExpr; - use super::super::level::KUniv; - use super::super::mode::Anon; - use super::*; - - fn sort0() -> KExpr { - KExpr::sort(KUniv::zero()) - } - - #[test] - fn u64_to_usize_small_value() { - let r: Result> = u64_to_usize::(42u64); - assert_eq!(r.unwrap(), 42); - } - - #[test] - fn u64_to_usize_zero() { - let r: Result> = u64_to_usize::(0u64); - assert_eq!(r.unwrap(), 0); - } - - #[test] - fn display_type_expected() { - let e: TcError = TcError::TypeExpected; - assert_eq!(format!("{e}"), "type expected"); - } - - #[test] - fn display_fun_expected() { - let e: TcError = TcError::FunExpected { e: sort0(), whnf: sort0() }; - let s = format!("{e}"); - // Must contain the "function expected" header; the expression format - // isn't frozen so we only sniff for the leading text. - assert!(s.starts_with("function expected")); - } - - #[test] - fn display_app_type_mismatch() { - let e: TcError = - TcError::AppTypeMismatch { a_ty: sort0(), dom: sort0(), depth: 7 }; - let s = format!("{e}"); - assert!(s.contains("app type mismatch")); - assert!(s.contains("depth 7")); - } - - #[test] - fn display_decl_type_mismatch() { - let e: TcError = TcError::DeclTypeMismatch; - assert_eq!(format!("{e}"), "declaration type mismatch"); - } - - #[test] - fn display_unknown_const() { - let addr = Address::hash(b"some-constant"); - let e: TcError = TcError::UnknownConst(addr.clone()); - let s = format!("{e}"); - assert!(s.starts_with("unknown constant")); - // The display uses `{:.12}` — precision truncates the hex. Verify the - // first 12 chars of the hex appear. - let hex = addr.hex(); - assert!(s.contains(&hex[..12])); - } - - #[test] - fn display_univ_param_mismatch() { - let e: TcError = TcError::UnivParamMismatch { expected: 2, got: 3 }; - let s = format!("{e}"); - assert!(s.contains("universe param count")); - assert!(s.contains("expected 2")); - assert!(s.contains("got 3")); - } - - #[test] - fn display_univ_param_out_of_range() { - let e: TcError = TcError::UnivParamOutOfRange { idx: 5, bound: 2 }; - let s = format!("{e}"); - assert!(s.contains("Param(5)")); - assert!(s.contains("only 2 universes supplied")); - } - - #[test] - fn display_var_out_of_range() { - let e: TcError = TcError::VarOutOfRange { idx: 7, ctx_len: 3 }; - let s = format!("{e}"); - assert!(s.contains("#7")); - assert!(s.contains("depth 3")); - } - - #[test] - fn display_def_eq_failed() { - let e: TcError = TcError::DefEqFailed; - assert_eq!(format!("{e}"), "definitional equality check failed"); - } - - #[test] - fn display_max_rec_depth() { - let e: TcError = TcError::MaxRecDepth; - assert_eq!(format!("{e}"), "max recursion depth exceeded"); - } - - #[test] - fn display_max_rec_fuel() { - let e: TcError = TcError::MaxRecFuel; - assert_eq!(format!("{e}"), "recursive fuel exhausted"); - } - - #[test] - fn display_other_passthrough() { - let e: TcError = TcError::Other("custom diagnostic".into()); - assert_eq!(format!("{e}"), "custom diagnostic"); - } - - #[test] - fn debug_is_implemented() { - // Regression guard: TcError must remain Debug for `?` propagation - // through test assertions. - let e: TcError = TcError::TypeExpected; - let _ = format!("{e:?}"); - } -} diff --git a/crates/kernel/src/expr.rs b/crates/kernel/src/expr.rs deleted file mode 100644 index 2a0209eae..000000000 --- a/crates/kernel/src/expr.rs +++ /dev/null @@ -1,1362 +0,0 @@ -//! Expressions with optional metadata. -//! -//! `KExpr` is an Arc-wrapped expression. Each variant carries an `ExprInfo` -//! with its internal UID, substitution annotations, and mdata. Binder and let -//! contracts are semantic payload, separate from display metadata. - -use std::fmt; -use std::sync::Arc; - -use bignat::Nat; -use ix_common::address::Address; -use ix_common::env::{BinderInfo, DataValue, Name}; -use ixon::contract::{BinderContract, LetContract, ValueContract}; -use ixon::univ::Univ as IxonUniv; - -use super::env::Addr; -use super::id::KId; -use super::level::KUniv; -use super::mode::{KernelMode, MetaDisplay, MetaHash}; - -/// Expression. Thin Arc wrapper — cheap to clone, O(1) identity via `Arc::ptr_eq`. -#[derive(Clone, Debug)] -pub struct KExpr(Arc>); - -/// A single mdata layer: key-value pairs from Lean's `Expr.mdata`. -pub type MData = Vec<(Name, DataValue)>; - -/// Original level-spelling decoration for a Meta-mode `Sort`/`Const` -/// occurrence (canonicity §10.6). Carried when the occurrence's stored -/// spelling differs from the kernel-held `mk*`-normal form, so egress can -/// replay the source syntax. Spellings are `ixon::univ::Univ` (positional -/// `Var` indices — language-neutral vs the Lean twin's `Ixon.Univ`). -/// -/// Presentation-only: never part of semantic equality (`structural_eq` -/// ignores it) and never read by checking. Unlike names/binder-info/mdata, -/// it DOES enter the intern identity ([`super::env::ExprKey`]): spelling -/// twins must stay distinct nodes, because the ixon-egress output (the -/// rebuilt anon univ table) depends on the decoration — a first-wins -/// collapse would replay the wrong spelling. Decorations live on -/// occurrence (expr) nodes, NOT on `KUniv` nodes — universe interning is -/// semantic and would collapse spelling twins. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum UnivDecor { - /// Original spelling of a `Sort` occurrence's level. - Sort(Arc), - /// Original spellings of a `Const` occurrence's full level-arg list. - Const(Box<[Arc]>), -} - -/// Structural fold of an `IxonUniv` into a hasher (tag byte per node, -/// LE index bytes for `Var`) — deterministic, injective per tree. -fn hash_ixon_univ(u: &IxonUniv, hasher: &mut blake3::Hasher) { - match u { - IxonUniv::Zero => { - hasher.update(&[0]); - }, - IxonUniv::Succ(inner) => { - hasher.update(&[1]); - hash_ixon_univ(inner, hasher); - }, - IxonUniv::Max(a, b) => { - hasher.update(&[2]); - hash_ixon_univ(a, hasher); - hash_ixon_univ(b, hasher); - }, - IxonUniv::IMax(a, b) => { - hasher.update(&[3]); - hash_ixon_univ(a, hasher); - hash_ixon_univ(b, hasher); - }, - IxonUniv::Var(idx) => { - hasher.update(&[4]); - hasher.update(&idx.to_le_bytes()); - }, - } -} - -impl MetaHash for UnivDecor { - fn meta_hash(&self, hasher: &mut blake3::Hasher) { - match self { - UnivDecor::Sort(u) => { - hasher.update(&[1]); - hash_ixon_univ(u, hasher); - }, - UnivDecor::Const(us) => { - hasher.update(&[2]); - hasher.update(&(us.len() as u64).to_le_bytes()); - for u in us.iter() { - hash_ixon_univ(u, hasher); - } - }, - } - } -} - -impl MetaDisplay for UnivDecor { - fn has_meta(&self) -> bool { - true - } - fn meta_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - UnivDecor::Sort(u) => write!(f, "origUniv {u:?}"), - UnivDecor::Const(us) => write!(f, "origUnivs {us:?}"), - } - } -} - -/// Per-expression metadata: internal UID, substitution annotations, and mdata. -#[derive(Clone, Debug)] -pub struct ExprInfo { - /// Internal construction/intern identity. This is never a serialized - /// content address; the intern key includes every binder/let contract. - pub addr: Addr, - /// Loose bound variable range: upper bound on free de Bruijn indices. - pub lbr: u64, - /// Count of free `Var(0)` occurrences. - pub count_0: u64, - /// Whether any [`ExprData::FVar`] occurrence is reachable in this expression. - /// - /// FVars (free variables) are leaves carrying a unique [`FVarId`]; they are - /// introduced when a binder is opened during inference / def-eq, and - /// re-abstracted into de Bruijn binders before the result escapes the - /// open scope. The flag lets callers (substitution, `abstract_fvars`, - /// soundness assertions) skip walks when no fvars are reachable. - pub has_fvars: bool, - /// Lean mdata annotations. Semantically transparent, erased in Anon mode. - pub mdata: M::MField>, - /// Original level-spelling decoration (Meta-mode `Sort`/`Const` only; - /// `None` everywhere else and when the stored spelling is already - /// `mk*`-normal). See [`UnivDecor`]. Erased in Anon mode. - pub univ_decor: M::MField>, -} - -/// Per-`TypeChecker` unique identifier for a free variable. Embedded into the -/// blake3 content hash of [`ExprData::FVar`] nodes, so that two distinct fvars -/// hash distinctly. This is the soundness lever that lets cache keys be the -/// expression hash alone (no separate local-context key) — see the kernel -/// fvar plan and `refs/lean4/src/kernel/type_checker.h:27`. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub struct FVarId(pub u64); - -impl fmt::Display for FVarId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "fv${}", self.0) - } -} - -/// Expression data. Each variant carries its [`ExprInfo`]. -#[derive(Clone, Debug)] -pub enum ExprData { - Var(u64, M::MField, ExprInfo), - /// Free variable: opaque identity from the active local context. - /// `FVarId` participates in the content hash; the user-facing `Name` is - /// preserved (in Meta mode) for diagnostics. The looked-up type lives in - /// the active [`crate::lctx::LocalContext`], not on the node. - FVar(FVarId, M::MField, ExprInfo), - Sort(KUniv, ExprInfo), - Const(KId, Box<[KUniv]>, ExprInfo), - App(KExpr, KExpr, ExprInfo), - /// Lambda input contract; a lambda has no result contract. - Lam( - M::MField, - M::MField, - BinderContract, - KExpr, - KExpr, - ExprInfo, - ), - /// Pi input and result contracts. Both are identity data; conversion - /// compares only the input contract. - All( - M::MField, - M::MField, - BinderContract, - ValueContract, - KExpr, - KExpr, - ExprInfo, - ), - /// Let binding: name, type, value, body, complete let contract. - Let(M::MField, KExpr, KExpr, KExpr, LetContract, ExprInfo), - /// Projection: struct type id, field index, struct value. - Prj(KId, u64, KExpr, ExprInfo), - Nat(Nat, Address, ExprInfo), - Str(String, Address, ExprInfo), -} - -impl ExprData { - pub fn info(&self) -> &ExprInfo { - match self { - ExprData::Var(.., i) - | ExprData::FVar(.., i) - | ExprData::Sort(.., i) - | ExprData::Const(.., i) - | ExprData::App(.., i) - | ExprData::Lam(.., i) - | ExprData::All(.., i) - | ExprData::Let(.., i) - | ExprData::Prj(.., i) - | ExprData::Nat(.., i) - | ExprData::Str(.., i) => i, - } - } -} - -impl KExpr { - pub fn new(data: ExprData) -> Self { - KExpr(Arc::new(data)) - } - - pub fn data(&self) -> &ExprData { - &self.0 - } - - pub fn info(&self) -> &ExprInfo { - self.data().info() - } - - pub fn addr(&self) -> &Addr { - &self.info().addr - } - - pub fn lbr(&self) -> u64 { - self.info().lbr - } - - pub fn count_0(&self) -> u64 { - self.info().count_0 - } - - /// Whether any [`ExprData::FVar`] occurrence is reachable. Computed at - /// construction time and propagated via OR through composite nodes, so - /// the check is O(1) per call. - pub fn has_fvars(&self) -> bool { - self.info().has_fvars - } - - pub fn mdata(&self) -> &M::MField> { - &self.info().mdata - } - - /// The authoritative source-frame payload of a binder, independent of its - /// optional display metadata. A lambda has no result contract. - pub fn source_contract(&self) -> Option { - use ixon::contract::{ - LetKind, - source::{Contract, Kind}, - }; - Some(match self.data() { - ExprData::Lam(_, _, binder, ..) => Contract { - kind: Kind::Lam, - binder: *binder, - result: ValueContract::shared(), - let_kind: LetKind::Value, - }, - ExprData::All(_, _, binder, result, ..) => Contract { - kind: Kind::All, - binder: *binder, - result: *result, - let_kind: LetKind::Value, - }, - ExprData::Let(_, _, _, _, contract, _) => Contract { - kind: Kind::Let, - binder: contract.binder, - result: ValueContract::shared(), - let_kind: contract.kind, - }, - _ => return None, - }) - } - - /// Apply an already validated direct-source frame. Rebuilding takes a fresh - /// UID; this never mutates the identity of an existing kernel node. - pub fn with_source_contract( - &self, - contract: ixon::contract::source::Contract, - ) -> Result { - use ixon::contract::source::Kind; - match (contract.kind, self.data()) { - (Kind::Lam, ExprData::Lam(n, bi, _, ty, body, _)) => { - Ok(Self::lam_contract_mdata( - n.clone(), - bi.clone(), - contract.binder, - ty.clone(), - body.clone(), - self.mdata().clone(), - )) - }, - (Kind::All, ExprData::All(n, bi, _, _, ty, body, _)) => { - Ok(Self::all_contract_mdata( - n.clone(), - bi.clone(), - contract.binder, - contract.result, - ty.clone(), - body.clone(), - self.mdata().clone(), - )) - }, - (Kind::Let, ExprData::Let(n, ty, value, body, prior, _)) => { - Ok(Self::let_contract_mdata( - n.clone(), - LetContract { - binder: contract.binder, - kind: contract.let_kind, - ..*prior - }, - ty.clone(), - value.clone(), - body.clone(), - self.mdata().clone(), - )) - }, - _ => { - Err("semantic contract is not attached to its original binder".into()) - }, - } - } - - /// Level-spelling decoration of this node (Meta-mode `Sort`/`Const` - /// occurrences only; see [`UnivDecor`]). - pub fn univ_decor(&self) -> &M::MField> { - &self.info().univ_decor - } - - /// 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() - } - - pub fn ptr_eq(&self, other: &KExpr) -> bool { - Arc::ptr_eq(&self.0, &other.0) - } - - /// 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 payload equality: display names, binder info, mdata and - /// universe-spelling decorations are not compared. Intern identity is - /// finer for decorations so egress can retain their spelling. Equal UIDs - /// prune a subtree; all binder/result/let contracts are compared here. - /// Result-only conversion equivalence belongs to the checker, not this - /// structural relation. - 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(_, _, c1, t1, b1, _), - ExprData::Lam(_, _, c2, t2, b2, _), - ) => c1 == c2 && t1.structural_eq(t2) && b1.structural_eq(b2), - ( - ExprData::All(_, _, c1, r1, t1, b1, _), - ExprData::All(_, _, c2, r2, t2, b2, _), - ) => c1 == c2 && r1 == r2 && t1.structural_eq(t2) && b1.structural_eq(b2), - ( - ExprData::Let(_, t1, v1, b1, c1, _), - ExprData::Let(_, t2, v2, b2, c2, _), - ) => { - c1 == c2 - && 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.structural_eq(other) - } -} - -impl Eq for KExpr {} - -fn no_mdata() -> M::MField> { - M::meta_field(vec![]) -} - -/// The absent level-spelling decoration, mode-erased. -pub fn no_univ_decor() -> M::MField> { - M::meta_field(None) -} - -fn mk_info( - addr: Addr, - lbr: u64, - count_0: u64, - has_fvars: bool, - mdata: M::MField>, -) -> ExprInfo { - ExprInfo { - addr, - lbr, - count_0, - has_fvars, - mdata, - univ_decor: no_univ_decor::(), - } -} - -/// `mk_info` with a level-spelling decoration (`Sort`/`Const` nodes only). -fn mk_info_decor( - addr: Addr, - lbr: u64, - count_0: u64, - has_fvars: bool, - mdata: M::MField>, - univ_decor: M::MField>, -) -> ExprInfo { - ExprInfo { addr, lbr, count_0, has_fvars, mdata, univ_decor } -} - -/// 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. -/// -/// Assigning identity with one atomic increment avoids hashing each node's -/// content during term construction. -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 - }) -} - -// ============================================================================= -// 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 { - pub fn var(idx: u64, name: M::MField) -> Self { - Self::var_mdata(idx, name, no_mdata::()) - } - - /// Construct a bound variable expression with a pre-allocated intern [`Addr`]. - fn var_mdata_with_addr( - idx: u64, - name: M::MField, - mdata: M::MField>, - addr: Addr, - ) -> Self { - // The loose-bvar range of `Var(idx)` is `idx + 1`; at `idx = u64::MAX` it - // saturates (no expression has 2^64 binders, so every reachable offset - // compares below it). A wrapping add made such a variable look closed - // (range 0) in release builds and overflowed in debug builds. - let info = mk_info::( - addr, - idx.saturating_add(1), - if idx == 0 { 1 } else { 0 }, - false, - mdata, - ); - KExpr::new(ExprData::Var(idx, name, info)) - } - - pub fn fvar(id: FVarId, name: M::MField) -> Self { - Self::fvar_mdata(id, name, no_mdata::()) - } - - fn fvar_mdata_with_addr( - id: FVarId, - name: M::MField, - mdata: M::MField>, - addr: Addr, - ) -> Self { - // FVars are leaves: no loose bvars (lbr = 0), no Var(0) occurrences, - // and `has_fvars` is true since this node *is* an fvar. - let info = mk_info::(addr, 0, 0, true, mdata); - KExpr::new(ExprData::FVar(id, name, info)) - } - - pub fn fvar_mdata( - id: FVarId, - name: M::MField, - mdata: M::MField>, - ) -> Self { - let addr = fresh_uid(); - Self::fvar_mdata_with_addr(id, name, mdata, addr) - } - - pub fn var_mdata( - idx: u64, - name: M::MField, - mdata: M::MField>, - ) -> Self { - let addr = fresh_uid(); - Self::var_mdata_with_addr(idx, name, mdata, addr) - } - - pub fn sort(u: KUniv) -> Self { - Self::sort_mdata(u, no_mdata::()) - } - - fn sort_mdata_with_addr( - u: KUniv, - mdata: M::MField>, - addr: Addr, - ) -> Self { - KExpr::new(ExprData::Sort(u, mk_info::(addr, 0, 0, false, mdata))) - } - - pub fn sort_mdata(u: KUniv, mdata: M::MField>) -> Self { - let addr = fresh_uid(); - Self::sort_mdata_with_addr(u, mdata, addr) - } - - /// `sort_mdata` plus a level-spelling decoration (ingress only; rebuild - /// sites that change the level go through `sort`/`sort_mdata` and drop - /// it, which is correct — the decoration describes the ingressed - /// occurrence's stored spelling). - pub fn sort_full( - u: KUniv, - mdata: M::MField>, - univ_decor: M::MField>, - ) -> Self { - KExpr::new(ExprData::Sort( - u, - mk_info_decor::(fresh_uid(), 0, 0, false, mdata, univ_decor), - )) - } - - pub fn cnst(id: KId, univs: Box<[KUniv]>) -> Self { - Self::cnst_mdata(id, univs, no_mdata::()) - } - - fn cnst_mdata_with_addr( - id: KId, - univs: Box<[KUniv]>, - mdata: M::MField>, - addr: Addr, - ) -> Self { - KExpr::new(ExprData::Const( - id, - univs, - mk_info::(addr, 0, 0, false, mdata), - )) - } - - pub fn cnst_mdata( - id: KId, - univs: Box<[KUniv]>, - mdata: M::MField>, - ) -> Self { - let addr = fresh_uid(); - Self::cnst_mdata_with_addr(id, univs, mdata, addr) - } - - /// `cnst_mdata` plus a level-spelling decoration (see `sort_full`). - pub fn cnst_full( - id: KId, - univs: Box<[KUniv]>, - mdata: M::MField>, - univ_decor: M::MField>, - ) -> Self { - KExpr::new(ExprData::Const( - id, - univs, - mk_info_decor::(fresh_uid(), 0, 0, false, mdata, univ_decor), - )) - } - - pub fn app(f: KExpr, a: KExpr) -> Self { - Self::app_mdata(f, a, no_mdata::()) - } - - fn app_mdata_with_addr( - f: KExpr, - a: KExpr, - mdata: M::MField>, - addr: Addr, - ) -> Self { - let info = mk_info::( - addr, - f.lbr().max(a.lbr()), - f.count_0() + a.count_0(), - f.has_fvars() || a.has_fvars(), - mdata, - ); - KExpr::new(ExprData::App(f, a, info)) - } - - pub fn app_mdata( - f: KExpr, - a: KExpr, - mdata: M::MField>, - ) -> Self { - let addr = fresh_uid(); - Self::app_mdata_with_addr(f, a, mdata, addr) - } - - pub fn lam( - name: M::MField, - bi: M::MField, - ty: KExpr, - body: KExpr, - ) -> Self { - Self::lam_contract(name, bi, BinderContract::default(), ty, body) - } - - /// Construct a lambda with an explicit input contract and a fresh UID. - pub fn lam_contract( - name: M::MField, - bi: M::MField, - contract: BinderContract, - ty: KExpr, - body: KExpr, - ) -> Self { - Self::lam_contract_mdata(name, bi, contract, ty, body, no_mdata::()) - } - - fn lam_contract_mdata_with_addr( - name: M::MField, - bi: M::MField, - contract: BinderContract, - ty: KExpr, - body: KExpr, - mdata: M::MField>, - addr: Addr, - ) -> Self { - let info = mk_info::( - addr, - ty.lbr().max(body.lbr().saturating_sub(1)), - ty.count_0(), - ty.has_fvars() || body.has_fvars(), - mdata, - ); - KExpr::new(ExprData::Lam(name, bi, contract, ty, body, info)) - } - - pub fn lam_mdata( - name: M::MField, - bi: M::MField, - ty: KExpr, - body: KExpr, - mdata: M::MField>, - ) -> Self { - Self::lam_contract_mdata( - name, - bi, - BinderContract::default(), - ty, - body, - mdata, - ) - } - - /// Rebuilders pass the existing contract explicitly. This constructor - /// always allocates a fresh UID, including when the contract changes. - pub fn lam_contract_mdata( - name: M::MField, - bi: M::MField, - contract: BinderContract, - ty: KExpr, - body: KExpr, - mdata: M::MField>, - ) -> Self { - let addr = fresh_uid(); - Self::lam_contract_mdata_with_addr( - name, bi, contract, ty, body, mdata, addr, - ) - } - - pub fn all( - name: M::MField, - bi: M::MField, - ty: KExpr, - body: KExpr, - ) -> Self { - Self::all_contract( - name, - bi, - BinderContract::default(), - ValueContract::shared(), - ty, - body, - ) - } - - /// Construct a Pi, retaining its result contract even though conversion - /// ignores that field. Inferred lambda types use a shared result contract. - pub fn all_contract( - name: M::MField, - bi: M::MField, - contract: BinderContract, - result: ValueContract, - ty: KExpr, - body: KExpr, - ) -> Self { - Self::all_contract_mdata( - name, - bi, - contract, - result, - ty, - body, - no_mdata::(), - ) - } - - pub fn all_mdata( - name: M::MField, - bi: M::MField, - ty: KExpr, - body: KExpr, - mdata: M::MField>, - ) -> Self { - Self::all_contract_mdata( - name, - bi, - BinderContract::default(), - ValueContract::shared(), - ty, - body, - mdata, - ) - } - - pub fn all_contract_mdata( - name: M::MField, - bi: M::MField, - contract: BinderContract, - result: ValueContract, - ty: KExpr, - body: KExpr, - mdata: M::MField>, - ) -> Self { - let addr = fresh_uid(); - let info = mk_info::( - addr, - ty.lbr().max(body.lbr().saturating_sub(1)), - ty.count_0(), - ty.has_fvars() || body.has_fvars(), - mdata, - ); - KExpr::new(ExprData::All(name, bi, contract, result, ty, body, info)) - } - - pub fn let_( - name: M::MField, - ty: KExpr, - val: KExpr, - body: KExpr, - non_dep: bool, - ) -> Self { - Self::let_contract(name, LetContract::plain(non_dep), ty, val, body) - } - - pub fn let_contract( - name: M::MField, - contract: LetContract, - ty: KExpr, - val: KExpr, - body: KExpr, - ) -> Self { - Self::let_contract_mdata(name, contract, ty, val, body, no_mdata::()) - } - - fn let_contract_mdata_with_addr( - name: M::MField, - contract: LetContract, - ty: KExpr, - val: KExpr, - body: KExpr, - mdata: M::MField>, - addr: Addr, - ) -> Self { - let info = mk_info::( - addr, - ty.lbr().max(val.lbr()).max(body.lbr().saturating_sub(1)), - ty.count_0() + val.count_0(), - ty.has_fvars() || val.has_fvars() || body.has_fvars(), - mdata, - ); - KExpr::new(ExprData::Let(name, ty, val, body, contract, info)) - } - - pub fn let_mdata( - name: M::MField, - ty: KExpr, - val: KExpr, - body: KExpr, - non_dep: bool, - mdata: M::MField>, - ) -> Self { - Self::let_contract_mdata( - name, - LetContract::plain(non_dep), - ty, - val, - body, - mdata, - ) - } - - pub fn let_contract_mdata( - name: M::MField, - contract: LetContract, - ty: KExpr, - val: KExpr, - body: KExpr, - mdata: M::MField>, - ) -> Self { - let addr = fresh_uid(); - Self::let_contract_mdata_with_addr( - name, contract, ty, val, body, mdata, addr, - ) - } - - pub fn prj(id: KId, field: u64, val: KExpr) -> Self { - Self::prj_mdata(id, field, val, no_mdata::()) - } - - fn prj_mdata_with_addr( - id: KId, - field: u64, - val: KExpr, - mdata: M::MField>, - addr: Addr, - ) -> Self { - let info = - mk_info::(addr, val.lbr(), val.count_0(), val.has_fvars(), mdata); - KExpr::new(ExprData::Prj(id, field, val, info)) - } - - pub fn prj_mdata( - id: KId, - field: u64, - val: KExpr, - mdata: M::MField>, - ) -> Self { - let addr = fresh_uid(); - Self::prj_mdata_with_addr(id, field, val, mdata, addr) - } - - pub fn nat(val: Nat, blob_addr: Address) -> Self { - Self::nat_mdata(val, blob_addr, no_mdata::()) - } - - fn nat_mdata_with_addr( - val: Nat, - blob_addr: Address, - mdata: M::MField>, - addr: Addr, - ) -> Self { - KExpr::new(ExprData::Nat( - val, - blob_addr, - mk_info::(addr, 0, 0, false, mdata), - )) - } - - pub fn nat_mdata( - val: Nat, - blob_addr: Address, - mdata: M::MField>, - ) -> Self { - let addr = fresh_uid(); - Self::nat_mdata_with_addr(val, blob_addr, mdata, addr) - } - - pub fn str(val: String, blob_addr: Address) -> Self { - Self::str_mdata(val, blob_addr, no_mdata::()) - } - - fn str_mdata_with_addr( - val: String, - blob_addr: Address, - mdata: M::MField>, - addr: Addr, - ) -> Self { - KExpr::new(ExprData::Str( - val, - blob_addr, - mk_info::(addr, 0, 0, false, mdata), - )) - } - - pub fn str_mdata( - val: String, - blob_addr: Address, - mdata: M::MField>, - ) -> Self { - let addr = fresh_uid(); - Self::str_mdata_with_addr(val, blob_addr, mdata, addr) - } -} - -/// Meta mode: shows names when available. Anon mode: positional/hash fallbacks. -impl fmt::Display for KExpr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt_expr(self, f, 0) - } -} - -fn fmt_expr( - e: &KExpr, - f: &mut fmt::Formatter<'_>, - depth: usize, -) -> fmt::Result { - if depth > 20 { - return write!(f, "..."); - } - match e.data() { - ExprData::Var(idx, name, _) => { - if name.has_meta() { - name.meta_fmt(f) - } else { - write!(f, "#{idx}") - } - }, - ExprData::FVar(id, name, _) => { - if name.has_meta() { - name.meta_fmt(f)?; - write!(f, "@{id}") - } else { - write!(f, "{id}") - } - }, - ExprData::Sort(u, _) => write!(f, "Sort {u}"), - ExprData::Const(id, us, _) => { - write!(f, "{id}")?; - if !us.is_empty() { - write!(f, ".{{")?; - for (i, u) in us.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{u}")?; - } - write!(f, "}}")?; - } - Ok(()) - }, - ExprData::App(..) => { - let (head, args) = collect_spine(e); - write!(f, "(")?; - fmt_expr(&head, f, depth + 1)?; - for a in &args { - write!(f, " ")?; - fmt_expr(a, f, depth + 1)?; - } - write!(f, ")") - }, - ExprData::Lam(name, _, _, ty, body, _) => { - write!(f, "(fun (")?; - if name.has_meta() { - name.meta_fmt(f)?; - } else { - write!(f, "_")?; - } - write!(f, " : ")?; - fmt_expr(ty, f, depth + 1)?; - write!(f, ") => ")?; - fmt_expr(body, f, depth + 1)?; - write!(f, ")") - }, - ExprData::All(name, _, _, _, ty, body, _) => { - write!(f, "((")?; - if name.has_meta() { - name.meta_fmt(f)?; - } else { - write!(f, "_")?; - } - write!(f, " : ")?; - fmt_expr(ty, f, depth + 1)?; - write!(f, ") -> ")?; - fmt_expr(body, f, depth + 1)?; - write!(f, ")") - }, - ExprData::Let(name, ty, val, body, _, _) => { - write!(f, "(let ")?; - if name.has_meta() { - name.meta_fmt(f)?; - } else { - write!(f, "_")?; - } - write!(f, " : ")?; - fmt_expr(ty, f, depth + 1)?; - write!(f, " := ")?; - fmt_expr(val, f, depth + 1)?; - write!(f, " in ")?; - fmt_expr(body, f, depth + 1)?; - write!(f, ")") - }, - ExprData::Prj(id, field, val, _) => { - fmt_expr(val, f, depth + 1)?; - write!(f, ".{field}@{id}") - }, - ExprData::Nat(val, _, _) => write!(f, "{val}"), - ExprData::Str(val, _, _) => write!(f, "{val:?}"), - } -} - -fn collect_spine(e: &KExpr) -> (KExpr, Vec>) { - let mut args = Vec::new(); - let mut cur = e.clone(); - while let ExprData::App(func, arg, _) = cur.data() { - args.push(arg.clone()); - cur = func.clone(); - } - args.reverse(); - (cur, args) -} - -#[cfg(test)] -mod tests { - use super::super::mode::{Anon, Meta}; - use super::*; - use ix_common::address::Address; - use ix_common::env::BinderInfo; - - type ME = KExpr; - type AE = KExpr; - type MU = KUniv; - type AU = KUniv; - - fn mk_name(s: &str) -> Name { - let mut name = Name::anon(); - for part in s.split('.') { - name = Name::str(name, part.to_string()); - } - name - } - - fn mk_addr(s: &str) -> Address { - Address::hash(s.as_bytes()) - } - - // ---- Constructors & hashing ---- - - #[test] - fn var_hash_deterministic() { - // Identity is intern-assigned now: independently built nodes carry - // distinct uids but remain structurally equal (`==`). - assert_eq!(AE::var(0, ()), AE::var(0, ())); - } - - #[test] - fn var_different_indices() { - assert_ne!(AE::var(0, ()).addr(), AE::var(1, ()).addr()); - } - - #[test] - fn var_meta_name_does_not_affect_hash() { - // 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")), ME::var(0, mk_name("y"))); - } - - #[test] - fn sort_hash() { - assert_ne!( - AE::sort(AU::zero()).addr(), - AE::sort(AU::succ(AU::zero())).addr() - ); - } - - #[test] - fn const_hash() { - let c = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - assert_eq!(c.lbr(), 0); - assert_eq!(c.count_0(), 0); - } - - #[test] - fn const_meta_name_does_not_affect_hash() { - // `id.name` is display-only metadata. Two `Const` nodes with the same - // `id.addr` (the actual identity) are content-equal regardless of - // 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, b); - } - - /// The loose-bvar range of the largest index saturates instead of wrapping - /// to 0 (which made the variable look closed). - #[test] - fn var_max_index_is_not_closed() { - let v = AE::var(u64::MAX, ()); - assert_eq!(v.lbr(), u64::MAX); - assert_eq!(AE::var(u64::MAX - 1, ()).lbr(), u64::MAX); - assert_eq!(AE::var(0, ()).lbr(), 1); - } - - #[test] - fn app_hash_and_lbr() { - let a = AE::app(AE::var(0, ()), AE::var(1, ())); - assert_eq!(a.lbr(), 2); - assert_eq!(a.count_0(), 1); - } - - #[test] - fn app_order_matters() { - let v0 = AE::var(0, ()); - let v1 = AE::var(1, ()); - assert_ne!(AE::app(v0.clone(), v1.clone()).addr(), AE::app(v1, v0).addr()); - } - - #[test] - fn lam_meta_name_does_not_affect_hash() { - // Binder names are alpha-equivalent metadata; two lambdas differing - // only in binder name hash identically (true alpha-invariance even - // in `Meta` mode). - let ty = ME::sort(MU::zero()); - let body = ME::var(0, mk_name("x")); - 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, b); - } - - #[test] - fn lam_binder_info_does_not_affect_hash() { - // Binder info (implicit / instance / etc.) is elaborator-facing - // metadata; the kernel does not distinguish lambdas that differ only - // in binder info. - let ty = ME::sort(MU::zero()); - let body = ME::var(0, mk_name("x")); - 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, b); - } - - #[test] - fn lam_lbr() { - let e = AE::lam((), (), AE::sort(AU::zero()), AE::var(1, ())); - assert_eq!(e.lbr(), 1); - let e2 = AE::lam((), (), AE::var(0, ()), AE::var(0, ())); - assert_eq!(e2.lbr(), 1); - } - - #[test] - fn all_hash_differs_from_lam() { - let ty = AE::sort(AU::zero()); - let body = AE::var(0, ()); - assert_ne!( - AE::lam((), (), ty.clone(), body.clone()).addr(), - AE::all((), (), ty, body).addr() - ); - } - - #[test] - fn let_hash() { - let e = - AE::let_((), AE::sort(AU::zero()), AE::var(0, ()), AE::var(1, ()), true); - assert_eq!(e.lbr(), 1); - assert_eq!(e.count_0(), 1); - } - - #[test] - fn let_non_dep_distinguishes_hash() { - let ty = AE::sort(AU::zero()); - let val = AE::var(0, ()); - let body = AE::var(0, ()); - let a = AE::let_((), ty.clone(), val.clone(), body.clone(), true); - let b = AE::let_((), ty, val, body, false); - assert_ne!(a.addr(), b.addr()); - } - - #[test] - fn prj_hash() { - let p = AE::prj(KId::new(mk_addr("Prod"), ()), 0, AE::var(0, ())); - assert_eq!(p.lbr(), 1); - } - - #[test] - fn nat_str_hash() { - let n = AE::nat(Nat::from(42u64), mk_addr("42")); - let s = AE::str("hello".into(), mk_addr("hello")); - assert_ne!(n.addr(), s.addr()); - assert_eq!(n.lbr(), 0); - } - - // ---- mdata accessor ---- - - #[test] - fn mdata_default_empty() { - let e = ME::var(0, mk_name("x")); - assert!(e.mdata().is_empty()); - } - - // ---- PartialEq ---- - - #[test] - fn eq_by_hash() { - let a = AE::app(AE::var(0, ()), AE::var(1, ())); - let b = AE::app(AE::var(0, ()), AE::var(1, ())); - assert_eq!(a, b); - assert_ne!(a, AE::var(0, ())); - } - - // ---- Display ---- - - #[test] - fn display_var_anon() { - assert_eq!(format!("{}", AE::var(0, ())), "#0"); - } - - #[test] - fn display_var_meta_named() { - assert_eq!(format!("{}", ME::var(0, mk_name("x"))), "x"); - } - - #[test] - fn display_sort() { - assert_eq!(format!("{}", AE::sort(AU::zero())), "Sort 0"); - } - - #[test] - fn display_const_anon() { - let c = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let s = format!("{c}"); - assert_eq!(s.len(), 8, "got '{s}'"); // 8 hex chars (hash only) - } - - #[test] - fn display_const_meta() { - let c = ME::cnst(KId::new(mk_addr("Nat"), mk_name("Nat")), Box::new([])); - assert!(format!("{c}").starts_with("Nat@")); - } - - #[test] - fn display_const_with_univs() { - let c = - AE::cnst(KId::new(mk_addr("List"), ()), Box::new([AU::param(0, ())])); - let s = format!("{c}"); - assert!(s.contains(".{u0}"), "got '{s}'"); - } - - #[test] - fn display_app() { - assert_eq!( - format!("{}", AE::app(AE::var(0, ()), AE::var(1, ()))), - "(#0 #1)" - ); - } - - #[test] - fn display_app_spine() { - let e = AE::app(AE::app(AE::var(0, ()), AE::var(1, ())), AE::var(2, ())); - assert_eq!(format!("{e}"), "(#0 #1 #2)"); - } - - #[test] - fn display_lam_meta() { - let e = ME::lam( - mk_name("x"), - BinderInfo::Default, - ME::sort(MU::zero()), - ME::var(0, mk_name("x")), - ); - assert_eq!(format!("{e}"), "(fun (x : Sort 0) => x)"); - } - - #[test] - fn display_all_anon() { - let e = AE::all((), (), AE::sort(AU::zero()), AE::var(0, ())); - assert_eq!(format!("{e}"), "((_ : Sort 0) -> #0)"); - } - - #[test] - fn display_let() { - let e = - AE::let_((), AE::sort(AU::zero()), AE::var(0, ()), AE::var(0, ()), true); - assert_eq!(format!("{e}"), "(let _ : Sort 0 := #0 in #0)"); - } - - #[test] - fn display_nat() { - assert_eq!(format!("{}", AE::nat(Nat::from(42u64), mk_addr("42"))), "42"); - } - - #[test] - fn display_str() { - assert_eq!( - format!("{}", AE::str("hello".into(), mk_addr("hello"))), - "\"hello\"" - ); - } -} diff --git a/crates/kernel/src/extra_reductions.rs b/crates/kernel/src/extra_reductions.rs deleted file mode 100644 index ed7e14dcb..000000000 --- a/crates/kernel/src/extra_reductions.rs +++ /dev/null @@ -1,106 +0,0 @@ -//! Optional reductions enabled by checked role specifications. -//! -//! A bound source can enable the String and BitVec families after semantic -//! admission. Each reducer also checks admission of its own head and stays -//! within the input shapes covered by that head's equations. Decision-procedure -//! proof construction and the Nat offset/linear-recursion shortcuts remain -//! disabled in production. Unbound checking retains ordinary delta and iota. - -/// One group of optional reductions. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ExtraReduction { - /// `Nat.decEq`, `Nat.decLe` and `Nat.decLt` on literals, which build - /// `Decidable.isTrue`/`isFalse` from four `Nat` lemmas, and the literal - /// normalisation of `Int.decEq`, `Int.decLe` and `Int.decLt` arguments. - Decidable, - /// `BitVec.toNat (BitVec.ofNat w n)` and `BitVec.ult` on literals. - BitVec, - /// `String` operations on literals: the `String.ofList` collapse, - /// `append`, `decEq`, `back`, `utf8ByteSize`, and `toByteArray ""`. - String, - /// The `Nat` offset forms, one unit: `x + k`, `x / k` and `x % k` kept - /// stuck in WHNF, the recursor-major cleanup of `x + (k+1)` to - /// `Nat.succ (x + k)` that the stuck forms rely on, and the - /// definitional-equality strip of a shared `+ k`. The stuck forms without - /// the cleanup leave recursors stuck on offset majors. - NatOffsets, - /// The collapse of `succ`-step `Nat.rec` applications on a literal major. - NatRecLinear, -} - -impl ExtraReduction { - const fn bit(self) -> u8 { - match self { - ExtraReduction::Decidable => 1, - ExtraReduction::BitVec => 1 << 1, - ExtraReduction::String => 1 << 2, - ExtraReduction::NatOffsets => 1 << 3, - ExtraReduction::NatRecLinear => 1 << 4, - } - } -} - -/// The groups of [`ExtraReduction`] a type checker takes. -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] -pub struct ExtraReductions(u8); - -impl ExtraReductions { - /// No optional reductions, including the unbound production setting. - pub const NONE: Self = ExtraReductions(0); - - /// Every group, for unit tests of the reducers. - #[cfg(test)] - pub(crate) const ALL: Self = ExtraReductions(0b1_1111); - - #[cfg(not(target_arch = "riscv64"))] - pub(crate) fn from_admitted( - admitted: impl Fn(ixon::role_table::Role) -> bool, - ) -> Self { - use ixon::role_table::Role; - let mut bits = 0; - if admitted(Role::BitVecToNat) { - bits |= ExtraReduction::BitVec.bit(); - } - if [ - Role::StringOfList, - Role::StringAppend, - Role::StringDecEq, - Role::StringBack, - Role::StringUtf8ByteSize, - Role::StringToByteArray, - ] - .into_iter() - .any(admitted) - { - bits |= ExtraReduction::String.bit(); - } - Self(bits) - } - - pub fn allows(self, group: ExtraReduction) -> bool { - self.0 & group.bit() != 0 - } -} - -#[cfg(test)] -mod tests { - use super::{ExtraReduction, ExtraReductions}; - use ixon::role_table::Role; - - #[test] - fn string_of_list_enables_string_extra_reduction_group() { - let none = ExtraReductions::from_admitted(|_| false); - assert!(!none.allows(ExtraReduction::String)); - assert!(!none.allows(ExtraReduction::BitVec)); - - let only_of_list = - ExtraReductions::from_admitted(|role| role == Role::StringOfList); - assert!(only_of_list.allows(ExtraReduction::String)); - assert!(!only_of_list.allows(ExtraReduction::BitVec)); - - let only_bv = - ExtraReductions::from_admitted(|role| role == Role::BitVecToNat); - assert!(!only_bv.allows(ExtraReduction::String)); - assert!(only_bv.allows(ExtraReduction::BitVec)); - } -} diff --git a/crates/kernel/src/id.rs b/crates/kernel/src/id.rs deleted file mode 100644 index 36316984c..000000000 --- a/crates/kernel/src/id.rs +++ /dev/null @@ -1,181 +0,0 @@ -use std::fmt; -use std::hash::{Hash, Hasher}; - -use ix_common::address::Address; -use ix_common::env::Name; - -use super::mode::{KernelMode, MetaDisplay, MetaHash}; - -/// Kernel identifier: bundles a content address with a metadata name. -/// In Meta mode, both fields participate in equality/hashing. -/// In Anon mode, the name is `()` so only the address matters. -#[derive(Clone, Debug)] -pub struct KId { - pub addr: Address, - pub name: M::MField, -} - -impl KId { - pub fn new(addr: Address, name: M::MField) -> Self { - KId { addr, name } - } -} - -impl PartialEq for KId { - fn eq(&self, other: &Self) -> bool { - self.addr == other.addr && self.name == other.name - } -} - -impl Eq for KId {} - -impl PartialOrd for KId { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for KId { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.addr.cmp(&other.addr).then_with(|| meta_cmp(&self.name, &other.name)) - } -} - -/// Derive ordering from MetaHash: hash both values and compare the digests. -/// For `()` (Anon mode), the hash is empty so all units compare equal. -fn meta_cmp(a: &T, b: &T) -> std::cmp::Ordering { - let hash = |v: &T| { - let mut h = blake3::Hasher::new(); - v.meta_hash(&mut h); - h.finalize() - }; - hash(a).as_bytes().cmp(hash(b).as_bytes()) -} - -impl Hash for KId { - fn hash(&self, state: &mut H) { - self.addr.hash(state); - self.name.hash(state); - } -} - -impl MetaHash for KId { - fn meta_hash(&self, hasher: &mut blake3::Hasher) { - hasher.update(self.addr.as_bytes()); - self.name.meta_hash(hasher); - } -} - -impl MetaDisplay for KId { - fn has_meta(&self) -> bool { - self.name.has_meta() - } - fn meta_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let hex = self.addr.hex(); - let short = &hex[..8.min(hex.len())]; - if self.name.has_meta() { - self.name.meta_fmt(f)?; - write!(f, "@{short}") - } else { - write!(f, "{short}") - } - } -} - -/// Meta mode: `Nat.add@a1b2c3d4`. Anon mode: `a1b2c3d4`. -impl fmt::Display for KId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let hex = self.addr.hex(); - let short = &hex[..8.min(hex.len())]; - if self.name.has_meta() { - self.name.meta_fmt(f)?; - write!(f, "@{short}") - } else { - write!(f, "{short}") - } - } -} - -#[cfg(test)] -mod tests { - use super::super::mode::{Anon, Meta}; - use super::*; - - fn mk_name(s: &str) -> Name { - let mut name = Name::anon(); - for part in s.split('.') { - name = Name::str(name, part.to_string()); - } - name - } - - fn mk_addr(s: &str) -> Address { - Address::hash(s.as_bytes()) - } - - #[test] - fn meta_named_shows_name_and_hash() { - let id = KId::::new(mk_addr("test"), mk_name("Nat.add")); - let s = format!("{id}"); - assert!(s.starts_with("Nat.add@"), "expected 'Nat.add@...', got '{s}'"); - assert_eq!(s.len(), "Nat.add@".len() + 8); - } - - #[test] - fn meta_anonymous_shows_hash_only() { - // Anonymous names have no displayable metadata, so KId falls back to hash. - let id = KId::::new(mk_addr("test"), Name::anon()); - let s = format!("{id}"); - assert_eq!(s.len(), 8, "expected 8-char hash, got '{s}'"); - assert!(!s.contains('@'), "anonymous should not contain '@', got '{s}'"); - } - - #[test] - fn meta_nested_name() { - let id = KId::::new(mk_addr("x"), mk_name("Lean.Parser.Term.app")); - let s = format!("{id}"); - assert!(s.starts_with("Lean.Parser.Term.app@"), "got '{s}'"); - } - - #[test] - fn meta_single_component_name() { - let id = KId::::new(mk_addr("x"), mk_name("Nat")); - let s = format!("{id}"); - assert!(s.starts_with("Nat@"), "got '{s}'"); - } - - #[test] - fn anon_shows_hash_only() { - let id = KId::::new(mk_addr("test"), ()); - let s = format!("{id}"); - assert_eq!(s.len(), 8); - assert!(!s.contains('@'), "anon mode should not contain '@', got '{s}'"); - } - - #[test] - fn anon_same_display_regardless_of_addr() { - let id1 = KId::::new(mk_addr("foo"), ()); - let id2 = KId::::new(mk_addr("bar"), ()); - // Different addresses produce different hashes - assert_ne!(format!("{id1}"), format!("{id2}")); - } - - #[test] - fn meta_equality_includes_name() { - let addr = mk_addr("test"); - let a = KId::::new(addr.clone(), mk_name("Foo")); - let b = KId::::new(addr.clone(), mk_name("Bar")); - let c = KId::::new(addr.clone(), mk_name("Foo")); - assert_ne!(a, b); - assert_eq!(a, c); - } - - #[test] - fn anon_equality_ignores_erased_name() { - let a = KId::::new(mk_addr("test"), ()); - let b = KId::::new(mk_addr("test"), ()); - let c = KId::::new(mk_addr("other"), ()); - assert_eq!(a, b); - assert_ne!(a, c); - } -} diff --git a/crates/kernel/src/inductive.rs b/crates/kernel/src/inductive.rs deleted file mode 100644 index d04b01e27..000000000 --- a/crates/kernel/src/inductive.rs +++ /dev/null @@ -1,8173 +0,0 @@ -//! Inductive type validation and recursor generation. -//! -//! Validates inductive declarations (parameter agreement, positivity, universe -//! constraints, return types) and generates canonical recursors following -//! lean4lean's constructive approach, then compares with provided recursors. - -use ix_common::address::Address; - -use super::constant::KConst; -use super::env::{GeneratedRecursor, RecursorAuxOrder}; -use super::error::{TcError, u64_to_usize}; -use super::expr::{ExprData, KExpr}; -use super::id::KId; -use super::level::{KUniv, univ_eq, univ_geq}; -use super::mode::KernelMode; -use super::subst::{instantiate_rev, lift, simul_subst, subst}; -use super::tc::{TypeChecker, collect_app_spine, expr_mentions_any_addr}; - -/// Emit the `[type diff]` walk from `check_recursor`'s mismatch path. -/// Off by default — every inductive over ~100k constants in an alpha-collapse -/// regime or a mutual block with near-identical peers triggers a fresh diff, -/// turning a normal compile into a wall of stderr. Set `IX_TYPE_DIFF=1` to -/// enable when investigating a specific mismatch. -static IX_TYPE_DIFF: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_TYPE_DIFF").is_ok()); - -/// Emit nested-aux recursor ordering/selection diagnostics for names whose -/// display form starts with the configured prefix. Example: -/// `IX_RECURSOR_DUMP=Lean.Doc.Block`. -static IX_RECURSOR_DUMP: crate::EnvString = crate::EnvString::new(|| { - crate::env_var("IX_RECURSOR_DUMP").ok().filter(|s| !s.is_empty()) -}); - -/// Sum attacker-controlled declaration arities without permitting `u64` -/// wraparound. Several reducer and recursor-generation paths use the combined -/// value as a binder or major-premise index; accepting a wrapped total would -/// make those consumers observe a different declaration layout than the -/// individual metadata fields describe. -fn checked_metadata_sum( - label: &str, - parts: &[u64], -) -> Result> { - parts.iter().try_fold(0u64, |sum, part| { - sum - .checked_add(*part) - .ok_or_else(|| TcError::Other(format!("{label} metadata sum overflow"))) - }) -} - -fn checked_binder_sum( - label: &str, - left: usize, - right: usize, -) -> Result> { - left - .checked_add(right) - .ok_or_else(|| TcError::Other(format!("{label} binder sum overflow"))) -} - -fn checked_usize_to_u64( - label: &str, - value: usize, -) -> Result> { - u64::try_from(value).map_err(|error| { - TcError::Other(format!("{label} does not fit in u64: {error}")) - }) -} - -/// A member of the "flat" mutual block used for recursor generation. -/// For non-nested inductives, this is just the original inductive. -/// For nested occurrences (e.g., `Array Syntax` in Syntax's ctor fields), -/// an auxiliary entry is created mirroring the external inductive's structure. -#[derive(Clone)] -pub struct FlatBlockMember { - /// For original: the inductive's KId. For auxiliary: the external inductive's KId. - pub id: KId, - /// True if this is an auxiliary member created for a nested occurrence. - pub is_aux: bool, - /// Specialized param values for this member. - /// For original: Var refs to the recursor's shared params. - /// For auxiliary: the concrete specialized exprs (e.g., `[Syntax]` for `Array Syntax`). - /// These are in terms of the recursor's param binders (depth = n_rec_params). - pub spec_params: Vec>, - /// Number of params this member's inductive has (may differ from shared for nested). - pub own_params: u64, - /// Number of indices. - pub n_indices: u64, - /// Constructor ids (from env). - pub ctors: Vec>, - /// Universe param count. - pub lvls: u64, - /// Universe args for internal processing (abstract shifted params). - /// Used for ctor type instantiation and nesting detection. - pub ind_us: Box<[KUniv]>, - /// Universe args from the actual nested occurrence (concrete). - /// For original members: same as ind_us. - /// For auxiliaries: the concrete args from the ctor field (e.g., [Succ(Zero)]). - /// Used for the final output type (motives, major, ctor apps). - pub occurrence_us: Box<[KUniv]>, -} - -/// One mutually-recursive family whose applications may occur positively in -/// the constructor field currently being checked. The root declaration uses -/// symbolic universe parameters (`concrete_univs = None`); a nested external -/// family records the concrete universe and parameter specialization at which -/// it was encountered. -#[derive(Clone)] -struct PositivityGroup { - addrs: Vec
, - params: Vec>, - concrete_univs: Option]>>, -} - -/// Exact identity shared by flat-block auxiliary deduplication and the -/// positivity recursion stack. Definitional equality is deliberately too -/// broad here: two syntactically distinct specializations receive distinct -/// generated auxiliaries even when their parameters happen to reduce to the -/// same term. -fn same_nested_specialization( - left_family: &Address, - left_universes: &[KUniv], - left_parameters: &[KExpr], - right_family: &Address, - right_universes: &[KUniv], - right_parameters: &[KExpr], -) -> bool { - left_family == right_family - && left_universes.len() == right_universes.len() - && left_universes - .iter() - .zip(right_universes) - .all(|(left, right)| left == right) - && left_parameters.len() == right_parameters.len() - && left_parameters - .iter() - .zip(right_parameters) - .all(|(left, right)| left == right) -} - -impl TypeChecker<'_, M> { - /// Validate an inductive block. Pure inductive blocks are coordinated - /// through `KEnv`; legacy mixed source blocks fall back to the member check - /// to avoid caching a partial result under a mixed block id. - pub fn check_inductive(&mut self, id: &KId) -> Result<(), TcError> { - let block = match self.get_const(id)? { - KConst::Indc { block, .. } => block.clone(), - _ => { - return Err(TcError::Other("check_inductive: not an inductive".into())); - }, - }; - let Some(members) = self.try_get_block(&block)? else { - return self.check_inductive_member(id); - }; - for member in &members { - if !matches!( - self.try_get_const(member)?, - Some(KConst::Indc { .. } | KConst::Ctor { .. }) - ) { - return self.check_inductive_member(id); - } - } - if let Some(result) = self.env.block_check_results.get(&block).cloned() { - return result; - } - let result = self.check_inductive_block(&block, &members); - self.env.block_check_results.insert(block, result.clone()); - result - } - - /// Validate every inductive and constructor in an inductive block. - pub(crate) fn check_inductive_block( - &mut self, - block: &KId, - members: &[KId], - ) -> Result<(), TcError> { - let mut ind_ids = Vec::new(); - let mut ctor_ids = Vec::new(); - - // SECURITY INVARIANT (Lean #14576): infer each original stored member - // type, including every constructor type, before building or consulting - // any flattened/nested-inductive representation. A lossy nested rewrite - // can erase phantom parameter arguments; checking only that rewritten - // form would let an ill-typed argument disappear. Keep this pass in full - // inference mode and over the untouched `ty` values. - for member in members { - self.reset(); - self.begin_const(member); - let c = self.get_const(member)?; - self.validate_const_well_scoped(&c)?; - match c { - KConst::Indc { ty, is_unsafe, .. } => { - self.fence_safe_type(&ty, is_unsafe)?; - let t = self.infer(&ty)?; - self.ensure_sort(&t)?; - ind_ids.push(member.clone()); - }, - KConst::Ctor { ty, is_unsafe, .. } => { - self.fence_safe_type(&ty, is_unsafe)?; - let t = self.infer(&ty)?; - self.ensure_sort(&t)?; - ctor_ids.push(member.clone()); - }, - _ => { - return Err(TcError::Other(format!( - "check_inductive_block: non-inductive member {member} in block {block}" - ))); - }, - } - } - - for ind_id in &ind_ids { - self.reset(); - self.begin_const(ind_id); - self.check_inductive_member(ind_id)?; - } - for ctor_id in &ctor_ids { - let induct = match self.get_const(ctor_id)? { - KConst::Ctor { induct, .. } => induct, - _ => continue, - }; - self.reset(); - self.begin_const(ctor_id); - self.check_ctor_against_inductive_member(ctor_id, &induct)?; - } - Ok(()) - } - - /// Validate the constructor header fields that Lean derives from its parent - /// inductive declaration. These fields are operational metadata in Ix: - /// `cidx` selects an iota rule, `params`/`fields` drive eta and projection - /// logic, `lvls` fixes constant-application arity, and `is_unsafe` feeds the - /// definition-safety lattice. They therefore cannot be accepted merely - /// because the stored constructor type is well-typed. - fn check_ctor_metadata_against_parent( - &mut self, - ctor_id: &KId, - induct_id: &KId, - expected_cidx: usize, - ind_params: usize, - ind_lvls: u64, - ind_is_unsafe: bool, - ) -> Result<(KExpr, usize), TcError> { - let ( - ctor_ty, - ctor_induct, - ctor_cidx, - ctor_params, - ctor_fields, - ctor_lvls, - ctor_is_unsafe, - ) = match self.get_const(ctor_id)? { - KConst::Ctor { - ty, - induct, - cidx, - params, - fields, - lvls, - is_unsafe, - .. - } => ( - ty.clone(), - induct, - u64_to_usize(cidx)?, - u64_to_usize(params)?, - u64_to_usize(fields)?, - lvls, - is_unsafe, - ), - _ => { - return Err(TcError::Other( - "check_inductive: constructor not found".into(), - )); - }, - }; - - if ctor_induct != *induct_id { - return Err(TcError::Other( - "check_inductive: ctor parent mismatch".into(), - )); - } - if ctor_lvls != ind_lvls { - return Err(TcError::Other(format!( - "check_inductive: ctor universe arity mismatch: expected {ind_lvls}, got {ctor_lvls}" - ))); - } - if ctor_is_unsafe != ind_is_unsafe { - return Err(TcError::Other(format!( - "check_inductive: ctor safety mismatch: expected {ind_is_unsafe}, got {ctor_is_unsafe}" - ))); - } - if ctor_params != ind_params { - return Err(TcError::Other(format!( - "check_inductive: ctor params mismatch: expected {ind_params}, got {ctor_params}" - ))); - } - if ctor_cidx != expected_cidx { - return Err(TcError::Other(format!( - "check_inductive: ctor cidx mismatch: expected {expected_cidx}, got {ctor_cidx}" - ))); - } - - Ok((ctor_ty, ctor_fields)) - } - - /// Validate an inductive type and its constructors. - pub fn check_inductive_member( - &mut self, - id: &KId, - ) -> Result<(), TcError> { - let (params, indices, lvls, ctors, block, is_unsafe, ty) = match self - .get_const(id)? - { - KConst::Indc { - params, - indices, - lvls, - ctors, - block, - is_unsafe, - ty, - .. - } => ( - params, - indices, - lvls, - ctors.clone(), - block.clone(), - is_unsafe, - ty.clone(), - ), - _ => { - return Err(TcError::Other("check_inductive: not an inductive".into())); - }, - }; - let ind_arity = checked_metadata_sum::( - "inductive params + indices", - &[params, indices], - )?; - - // Discover all inductives in the mutual block - let block_inds = self.discover_block_inductives(&block)?; - let block_addrs: Vec
= - block_inds.iter().map(|id| id.addr.clone()).collect(); - - // Inductive type must reduce to a Sort after peeling params+indices. - // This must be checked even for inductives with no constructors. - let ind_level = - self.get_result_sort_level(&ty, u64_to_usize(ind_arity)?)?; - - // S3 + S3b: Peer-agreement invariants for mutual inductives. - // - // S3: all peers live in the same result universe. - // S3b: all peers share the same parameter count and parameter-domain - // types. Without S3b, `build_rec_type` — which takes the shared - // param prefix uniformly from `ind_infos[0]` — would produce a - // generated recursor whose param binders misalign with a peer's - // ctor arguments, yielding de-Bruijn-shifted iota reductions and, - // in the limit, ill-typed stored terms. Enforcing agreement - // kernel-side removes the implicit compiler trust. - // - // References: lean4 `src/kernel/inductive.cpp:211–262 check_inductive_types` - // (line 230–231: "parameters of all inductive datatypes must match") - // and lean4lean `Lean4Lean/Inductive/Add.lean:80–82`. - // - // Memoization: the check is invariant across all peers of the block — - // if peer[0] agrees with each of peer[1..N], then by transitivity all - // pairs agree. Running this loop from *every* peer in the block yields - // redundant O(N²) work, which becomes significant on large Mathlib - // mutual families. We memo on successful completion, so subsequent - // peer checks of the same block skip the loop. Failure is not cached - // (the loop re-runs and re-reports on the next peer's check). Block - // ids are content-addressed, so cache entries are stable across the - // TypeChecker's lifetime. - if !self.env.block_peer_agreement_cache.contains(&block) { - for peer_id in &block_inds { - if peer_id.addr == id.addr { - continue; - } - let (peer_params, peer_indices, peer_lvls, peer_is_unsafe, peer_ty) = - match self.get_const(peer_id)? { - KConst::Indc { - params: pp, - indices: pi, - lvls: pl, - is_unsafe: pu, - ty: pty, - .. - } => (pp, pi, pl, pu, pty.clone()), - _ => continue, - }; - // S3: universe agreement. - let peer_arity = checked_metadata_sum::( - "inductive params + indices", - &[peer_params, peer_indices], - )?; - let peer_level = - self.get_result_sort_level(&peer_ty, u64_to_usize(peer_arity)?)?; - if !univ_eq(&ind_level, &peer_level) { - return Err(TcError::Other( - "mutually inductive types must live in the same universe".into(), - )); - } - if peer_lvls != lvls { - return Err(TcError::Other(format!( - "mutual peers must declare the same universe arity: self={lvls}, peer={peer_lvls}" - ))); - } - if peer_is_unsafe != is_unsafe { - return Err(TcError::Other( - "mutual inductives must share the same safety flag".into(), - )); - } - // S3b: parameter-count agreement. - if peer_params != params { - return Err(TcError::Other(format!( - "mutual peers must declare the same number of parameters: \ - self={params}, peer={peer_params}" - ))); - } - // S3b: parameter-domain agreement. Walks the first `n_params` - // foralls of both types and `is_def_eq`s the domains. - self.check_param_agreement(&ty, &peer_ty, u64_to_usize(params)?)?; - } - self.env.block_peer_agreement_cache.insert(block.clone()); - } - - // Validate each constructor - for (expected_cidx, ctor_id) in ctors.iter().enumerate() { - let ind_params = u64_to_usize(params)?; - let (ctor_ty, ctor_fields) = self.check_ctor_metadata_against_parent( - ctor_id, - id, - expected_cidx, - ind_params, - lvls, - is_unsafe, - )?; - - // A1: Parameter domain agreement - self.check_param_agreement(&ty, &ctor_ty, ind_params)?; - - // A3: Strict positivity. Lean skips positivity for unsafe inductives; - // those declarations are admitted only as unsafe constants. - if !is_unsafe { - self.check_positivity(&ctor_ty, ind_params, &block_addrs)?; - } - - // A4: Universe constraints - self.check_field_universes(&ctor_ty, ind_params, &ind_level)?; - - // A2: Constructor return type - self.check_ctor_return_type( - &ctor_ty, - ind_params, - u64_to_usize(indices)?, - ctor_fields, - &id.addr, - lvls, - &block_addrs, - )?; - } - - // Trigger recursor generation for the block (fatal — ZK context cannot tolerate silent failure) - if !self.env.recursor_cache.contains_key(&block) { - self.generate_block_recursors(&block)?; - } - - Ok(()) - } - - /// Validate a standalone constructor by checking its parent inductive block. - pub fn check_ctor_against_inductive( - &mut self, - ctor_id: &KId, - induct_id: &KId, - ) -> Result<(), TcError> { - let block = match self.try_get_const(induct_id)? { - Some(KConst::Indc { block, .. }) => block.clone(), - _ => { - return self.check_ctor_against_inductive_member(ctor_id, induct_id); - }, - }; - let Some(members) = self.try_get_block(&block)? else { - return self.check_ctor_against_inductive_member(ctor_id, induct_id); - }; - for member in &members { - if !matches!( - self.try_get_const(member)?, - Some(KConst::Indc { .. } | KConst::Ctor { .. }) - ) { - return self.check_ctor_against_inductive_member(ctor_id, induct_id); - } - } - - if let Some(result) = self.env.block_check_results.get(&block).cloned() { - return result; - } - let result = self.check_inductive_block(&block, &members); - self.env.block_check_results.insert(block, result.clone()); - result - } - - /// Validate a standalone constructor against its parent inductive. - /// Runs the same A1–A4 checks that `check_inductive_member` runs per-ctor. - pub fn check_ctor_against_inductive_member( - &mut self, - ctor_id: &KId, - induct_id: &KId, - ) -> Result<(), TcError> { - let ( - ind_params, - ind_indices, - ind_lvls, - ind_block, - ind_is_unsafe, - ind_ty, - ind_ctors, - ) = match self.get_const(induct_id)? { - KConst::Indc { - params, - indices, - lvls, - block, - is_unsafe, - ty, - ctors, - .. - } => ( - params, - indices, - lvls, - block.clone(), - is_unsafe, - ty.clone(), - ctors.clone(), - ), - _ => { - return Err(TcError::Other( - "check_ctor: parent inductive not found".into(), - )); - }, - }; - - let mut expected_cidx = None; - for (idx, listed_id) in ind_ctors.iter().enumerate() { - if listed_id == ctor_id { - if expected_cidx.is_some() { - return Err(TcError::Other( - "check_inductive: ctor listed more than once by parent".into(), - )); - } - expected_cidx = Some(idx); - } - } - let Some(expected_cidx) = expected_cidx else { - return Err(TcError::Other( - "check_inductive: ctor not listed by parent".into(), - )); - }; - - let ind_params_usize = u64_to_usize(ind_params)?; - let (ctor_ty, ctor_fields) = self.check_ctor_metadata_against_parent( - ctor_id, - induct_id, - expected_cidx, - ind_params_usize, - ind_lvls, - ind_is_unsafe, - )?; - - let block_inds = self.discover_block_inductives(&ind_block)?; - let block_addrs: Vec
= - block_inds.iter().map(|id| id.addr.clone()).collect(); - - let ind_arity = checked_metadata_sum::( - "inductive params + indices", - &[ind_params, ind_indices], - )?; - let ind_level = - self.get_result_sort_level(&ind_ty, u64_to_usize(ind_arity)?)?; - - // A1: Parameter domain agreement - self.check_param_agreement(&ind_ty, &ctor_ty, ind_params_usize)?; - - // A3: Strict positivity. Match Lean: unsafe inductives bypass this check. - if !ind_is_unsafe { - self.check_positivity(&ctor_ty, ind_params_usize, &block_addrs)?; - } - - // A4: Universe constraints - self.check_field_universes(&ctor_ty, ind_params_usize, &ind_level)?; - - // A2: Constructor return type - self.check_ctor_return_type( - &ctor_ty, - ind_params_usize, - u64_to_usize(ind_indices)?, - ctor_fields, - &induct_id.addr, - ind_lvls, - &block_addrs, - )?; - - Ok(()) - } - - /// Discover all inductives in a mutual block. - fn discover_block_inductives( - &mut self, - block_id: &KId, - ) -> Result>, TcError> { - let Some(members) = self.try_get_block(block_id)? else { - return Ok(vec![]); - }; - let mut inds = Vec::new(); - for id in members { - if matches!(self.try_get_const(&id)?, Some(KConst::Indc { .. })) { - inds.push(id); - } - } - Ok(inds) - } - - pub fn computed_is_rec(&mut self, ind: &KId) -> Result> { - if let Some(&v) = self.env.is_rec_cache.get(&ind.addr) { - return Ok(v); - } - let (params, ctors, block) = match self.get_const(ind)? { - KConst::Indc { params, ctors, block, .. } => { - (params, ctors.clone(), block.clone()) - }, - _ => { - return Err(TcError::Other("computed_is_rec: not an inductive".into())); - }, - }; - // provisional true entry. compute_is_rec can hit the is_rec_cache via - // whnf ~> try_struct_eta_iota ~> is_struct_like ~> cache - // if and only if the inductive is recursive, so not observable otherwise - self.env.is_rec_cache.insert(ind.addr.clone(), true); - let block_inds = self.discover_block_inductives(&block)?; - let block_addrs: Vec
= - block_inds.iter().map(|id| id.addr.clone()).collect(); - match self.compute_is_rec(&ctors, u64_to_usize::(params)?, &block_addrs) - { - Ok(v) => { - self.env.is_rec_cache.insert(ind.addr.clone(), v); - Ok(v) - }, - Err(e) => { - // ensure the provisional entry doesn't outlive an error - self.env.is_rec_cache.remove(&ind.addr); - Err(e) - }, - } - } - - /// H1: Compute `is_rec` constructively by scanning constructor fields for - /// references to any inductive in the mutual block. This verifies the declared - /// `is_rec` flag rather than trusting it from Ixon input. - /// - /// An inductive is recursive if any constructor field (after parameters) mentions - /// any inductive in the mutual block. - fn compute_is_rec( - &mut self, - ctors: &[KId], - n_params: usize, - block_addrs: &[Address], - ) -> Result> { - for ctor_id in ctors { - let ctor_ty = match self.try_get_const(ctor_id)? { - Some(KConst::Ctor { ty, .. }) => ty.clone(), - _ => continue, - }; - let saved = self.save_depth(); - let found = (|| -> Result> { - // Skip params, retaining each binder while normalizing the dependent - // constructor remainder. Without these frames an open Var could be - // zeta-reduced through an unrelated caller let-binding. - let mut ty = ctor_ty; - for _ in 0..n_params { - let w = self.whnf(&ty)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - self.push_local(dom.clone()); - ty = body.clone(); - }, - _ => break, - } - } - // Check each remaining field domain for block inductive mentions. - loop { - let w = self.whnf(&ty)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - if expr_mentions_any_addr(dom, block_addrs) { - return Ok(true); - } - self.push_local(dom.clone()); - ty = body.clone(); - }, - _ => return Ok(false), - } - } - })(); - self.restore_depth(saved); - if found? { - return Ok(true); - } - } - Ok(false) - } - - /// Build the "flat" block for recursor generation, detecting nested occurrences. - /// - /// Mirrors lean4lean's `ElimNestedInductive.run`: walks constructor fields, - /// detects `ExtInd(block_member_ref)` patterns, and adds auxiliary entries - /// for each nested external inductive. Queue-based for transitive nesting. - fn build_flat_block( - &mut self, - block_inds: &[KId], - n_rec_params: u64, - univ_offset: u64, - ) -> Result>, TcError> { - let anon = || M::meta_field(ix_common::env::Name::anon()); - let all_block_addrs: Vec
= - block_inds.iter().map(|id| id.addr.clone()).collect(); - - let mut flat: Vec> = Vec::new(); - // Complete nested-application identity for dedup. Universe arguments are - // load-bearing: Lean emits distinct auxiliaries when an otherwise - // phantom universe parameter differs between two occurrences with the - // same external family and term parameters. - let mut aux_seen: Vec<(Address, Box<[KUniv]>, Vec>)> = - Vec::new(); - - // Seed with original block inductives. - for ind_id in block_inds { - let (own_params, n_indices, ctors, lvls) = match self.get_const(ind_id)? { - KConst::Indc { params, indices, ctors, lvls, .. } => { - (params, indices, ctors.clone(), lvls) - }, - _ => continue, - }; - let ind_us = self.mk_ind_univs(lvls, univ_offset)?; - let spec_params: Vec> = (0..n_rec_params) - .map(|j| KExpr::var(n_rec_params - 1 - j, anon())) - .collect(); - flat.push(FlatBlockMember { - id: ind_id.clone(), - is_aux: false, - spec_params, - own_params, - n_indices, - ctors, - lvls, - ind_us: ind_us.clone(), - occurrence_us: ind_us, - }); - } - - // Queue-based processing: scan each member's ctors for nested occurrences. - let mut qi = 0; - while qi < flat.len() { - let member = flat[qi].clone(); - qi += 1; - - for ctor_id in &member.ctors { - let ctor_ty = match self.try_get_const(ctor_id)? { - Some(KConst::Ctor { ty, .. }) => ty.clone(), - _ => continue, - }; - - // Instantiate ctor type with occurrence universe args (concrete) so that - // transitively-detected nested occurrences get concrete universe args too. - let ctor_ty_inst = - self.instantiate_univ_params(&ctor_ty, &member.occurrence_us)?; - - // Walk past own_params, substituting with spec_params. - let saved = self.lctx.len(); - let mut cur = ctor_ty_inst; - for j in 0..member.own_params { - let w = self.whnf(&cur)?; - match w.data() { - ExprData::All(_, _, _, _, _, body, _) => { - let p = if u64_to_usize::(j)? < member.spec_params.len() { - member.spec_params[u64_to_usize::(j)?].clone() - } else { - KExpr::var(n_rec_params - 1 - j, anon()) - }; - cur = subst(&mut self.env.intern, body, &p, 0); - }, - _ => break, - } - } - - // Follow expansion's complete constructor-syntax preorder without - // reducing definition heads. The walk opens binders only to mark - // field-local references, which cannot be specialization parameters. - self.try_detect_nested( - &cur, - &all_block_addrs, - &mut flat, - &mut aux_seen, - univ_offset, - saved, - n_rec_params, - )?; - self.lctx.truncate(saved); - } - } - - Ok(flat) - } - - /// Check if a field domain is a nested inductive occurrence and, if so, - /// add an auxiliary entry to the flat block. - /// - /// A nested occurrence is: after peeling foralls, the result is `ExtInd Ds is` - /// where `ExtInd` is a previously-declared inductive (not in our block) and - /// some param arg `Ds[i]` mentions a block inductive. - /// - /// **Important: do not WHNF the domain here.** Compile-side - /// `replace_if_nested` (and Lean's C++ `is_nested_inductive_app`, - /// `inductive.cpp:920`) checks the head literally — if the head is a - /// definition like `IO.Ref`, it is *not* a nested-inductive occurrence. - /// WHNF would unfold `IO.Ref α` to `ST.Ref IO.RealWorld α`, which IS an - /// inductive — the kernel would then synthesize auxiliaries (e.g. - /// `_nested.ST_Ref_*`) that the compile side never generates, and - /// `populate_recursor_rules_from_block` would fail with `rec_ids/flat - /// count mismatch`. Walk syntax beneath an unmatched head instead. - fn try_detect_nested( - &mut self, - dom: &KExpr, - block_addrs: &[Address], - flat: &mut Vec>, - aux_seen: &mut Vec<(Address, Box<[KUniv]>, 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) - ) -> Result<(), TcError> { - let mut work = vec![(dom.clone(), dom.clone(), 0usize)]; - let mut visited = rustc_hash::FxHashSet::default(); - while let Some((source, current, depth)) = work.pop() { - // Valid specializations do not use opened local markers, so the - // original syntax and binder depth determine this discovery decision. - if !visited.insert((source.hash_key(), depth)) { - continue; - } - if self.try_detect_nested_at( - ¤t, - block_addrs, - flat, - aux_seen, - univ_offset, - param_depth, - n_rec_params, - )? { - continue; - } - match (source.data(), current.data()) { - (ExprData::App(..), ExprData::App(..)) => { - // A failed complete application has no matching function prefix: - // recognition uses the head and its first `params` arguments, and - // shorter prefixes only remove trailing arguments. Visit the head - // and each actual argument once, in the same left-to-right order, - // instead of recollecting every prefix's entire application spine. - let mut src = &source; - let mut cur = ¤t; - while let (ExprData::App(sf, sa, _), ExprData::App(f, a, _)) = - (src.data(), cur.data()) - { - work.push((sa.clone(), a.clone(), depth)); - src = sf; - cur = f; - } - work.push((src.clone(), cur.clone(), depth)); - }, - ( - ExprData::Lam(_, _, _, st, sb, _), - ExprData::Lam(_, _, _, ty, body, _), - ) - | ( - ExprData::All(_, _, _, _, st, sb, _), - ExprData::All(_, _, _, _, ty, body, _), - ) => { - let body = self.open_nested_scan_body(body); - work.push((sb.clone(), body, depth + 1)); - work.push((st.clone(), ty.clone(), depth)); - }, - ( - ExprData::Let(_, st, sv, sb, _, _), - ExprData::Let(_, ty, value, body, _, _), - ) => { - let body = self.open_nested_scan_body(body); - work.push((sb.clone(), body, depth + 1)); - work.push((sv.clone(), value.clone(), depth)); - work.push((st.clone(), ty.clone(), depth)); - }, - (ExprData::Prj(_, _, sv, _), ExprData::Prj(_, _, value, _)) => { - work.push((sv.clone(), value.clone(), depth)); - }, - (ExprData::Var(..), ExprData::Var(..) | ExprData::FVar(..)) - | (ExprData::FVar(..), ExprData::FVar(..)) - | (ExprData::Sort(..), ExprData::Sort(..)) - | (ExprData::Const(..), ExprData::Const(..)) - | (ExprData::Nat(..), ExprData::Nat(..)) - | (ExprData::Str(..), ExprData::Str(..)) => {}, - _ => { - return Err(TcError::Other( - "nested discovery: binder substitution changed expression shape" - .into(), - )); - }, - } - } - Ok(()) - } - - /// A fresh, untyped scope marker suffices: discovery never infers its - /// type and rejects every candidate parameter containing the marker. - fn open_nested_scan_body(&mut self, body: &KExpr) -> KExpr { - let id = self.fresh_fvar_id(); - let marker = - self.intern(KExpr::fvar(id, M::meta_field(ix_common::env::Name::anon()))); - instantiate_rev(&mut self.env.intern, body, &[marker]) - } - - /// Recognize one application. Already-seen specializations return true - /// too, so repeated occurrences prune their arguments as expansion does. - fn try_detect_nested_at( - &mut self, - dom: &KExpr, - block_addrs: &[Address], - flat: &mut Vec>, - aux_seen: &mut Vec<(Address, Box<[KUniv]>, Vec>)>, - univ_offset: u64, - param_depth: usize, - n_rec_params: u64, - ) -> Result> { - let saved_lctx = self.lctx.len(); - let result = (|| -> Result> { - let (head, args) = collect_app_spine(dom); - let head_id = match head.data() { - ExprData::Const(id, _, _) => id.clone(), - _ => return Ok(false), - }; - - // Skip if head is already a block member (direct recursive, not nested). - if block_addrs.contains(&head_id.addr) { - return Ok(false); - } - // Also skip if head is already a flat block member (already detected). - if flat.iter().any(|m| m.id.addr == head_id.addr && !m.is_aux) { - return Ok(false); - } - - // Check if head is an external inductive. - let (ext_params, ext_indices, ext_ctors, ext_lvls) = - match self.try_get_const(&head_id)? { - Some(KConst::Indc { params, indices, ctors, lvls, .. }) => { - (params, indices, ctors.clone(), lvls) - }, - _ => return Ok(false), - }; - - let ext_n_params = u64_to_usize::(ext_params)?; - if args.len() < ext_n_params { - return Ok(false); - } - - // Check if any param arg mentions a block original. Match Lean's - // `is_nested_inductive_app` (`inductive.cpp:920`) and compile-side - // `replace_if_nested`, which check INTERNAL identity (block originals - // by name / aux internal names like `_nested.Array_4`). The kernel - // doesn't carry internal aux names, only `flat[i].id.addr` — but for an - // aux that's the EXTERNAL inductive's address (e.g., `Array`'s addr). - // Including those flat addresses here would falsely match unrelated - // occurrences such as `Option (Array LazyStep)` (which mentions - // `Array`'s addr because `Array_4` shares it, even though `LazyStep` - // is not in this block). Originals only. - let has_nested_ref = args - .iter() - .take(ext_n_params) - .any(|a| expr_mentions_any_addr(a, block_addrs)); - if !has_nested_ref { - return Ok(false); - } - - // Extract spec_params (the first ext_n_params args). Field and - // domain-local binders are opened as fvars in this path, while valid - // block parameters remain Var refs in the recursor parameter context. - let spec_params: Vec> = - args.iter().take(ext_n_params).cloned().collect(); - - // S7: Reject nested occurrences whose parameter args contain local - // variables. FVars are field-local or domain-local binders opened by - // this pass. Loose Vars above the shared parameter range are legacy - // local de Bruijn refs. Either case means the would-be aux parameter - // depends on a constructor field, so it is not a valid nested inductive - // parameter. Allow Var(0)..Var(n_rec_params-1) as shared parameter refs. - // (lean4lean: isNestedInductiveApp? checks looseBVars on param args.) - let param_depth = - checked_usize_to_u64::("nested parameter depth", param_depth)?; - let param_bound = checked_metadata_sum::( - "nested parameter scope", - &[param_depth, n_rec_params], - )?; - for sp in spec_params.iter() { - if sp.has_fvars() { - return Ok(false); - } - if sp.lbr() > param_bound { - return Ok(false); // param arg depends on field-local variables — not a valid nesting - } - } - - // Retain the concrete universe spine before deduplication. It is part - // of Lean's nested application identity independently of the term - // parameter spine. - let occurrence_us: Box<[KUniv]> = match head.data() { - ExprData::Const(_, us, _) => us.clone(), - _ => Box::new([]), - }; - - // Dedup the exact (external family, universes, parameters) - // specialization. Structural comparison uses the uid fast path plus - // recursive fallback, so separately-built equal inputs still collapse. - let spec_hashes: Vec> = spec_params.clone(); - if aux_seen.iter().any(|(a, seen_us, s)| { - same_nested_specialization( - a, - seen_us, - s, - &head_id.addr, - &occurrence_us, - &spec_hashes, - ) - }) { - return Ok(true); - } - // A new occurrence `I As` opens I's whole mutual group, one auxiliary - // per member `J`, in group order, each `J As` registered as seen - // (Lean's `elim_nested_inductive_fn`). Over a compiled environment - // (`Canonical`) the group is I's stored block in its stored member - // order, which is Lean's: the auxiliaries are then in discovery order, - // the order the stored recursor block follows, with no sort. In - // `Source` mode (Lean's own recursors read directly) the group is the - // head alone. - let group: Vec> = - if self.env.recursor_aux_order == RecursorAuxOrder::Canonical { - match self.try_get_const(&head_id)? { - Some(KConst::Indc { block, .. }) => { - let members = self.discover_block_inductives(&block)?; - if members.iter().any(|j| j.addr == head_id.addr) { - members - } else { - vec![head_id.clone()] - } - }, - _ => vec![head_id.clone()], - } - } else { - vec![head_id.clone()] - }; - for j_id in group { - let (j_id, j_params, j_indices, j_ctors, j_lvls) = - if j_id.addr == head_id.addr { - ( - head_id.clone(), - ext_params, - ext_indices, - ext_ctors.clone(), - ext_lvls, - ) - } else { - match self.try_get_const(&j_id)? { - Some(KConst::Indc { params, indices, ctors, lvls, .. }) - if params == ext_params => - { - (j_id, params, indices, ctors.clone(), lvls) - }, - _ => continue, - } - }; - if aux_seen.iter().any(|(a, seen_us, s)| { - same_nested_specialization( - a, - seen_us, - s, - &j_id.addr, - &occurrence_us, - &spec_hashes, - ) - }) { - continue; - } - aux_seen.push(( - j_id.addr.clone(), - occurrence_us.clone(), - spec_hashes.clone(), - )); - - // Abstract shifted universe params for internal processing (dedup, - // ctor walking). - let aux_us = self.mk_ind_univs(j_lvls, univ_offset)?; - - flat.push(FlatBlockMember { - id: j_id, - is_aux: true, - spec_params: spec_params.clone(), - own_params: j_params, - n_indices: j_indices, - ctors: j_ctors, - lvls: j_lvls, - ind_us: aux_us, - occurrence_us: occurrence_us.clone(), - }); - } - Ok(true) - })(); - self.lctx.truncate(saved_lctx); - result - } - - fn recursor_dump_matches_id(&self, id: &KId) -> bool { - IX_RECURSOR_DUMP - .as_ref() - .is_some_and(|prefix| format!("{id}").starts_with(prefix)) - } - - fn recursor_dump_matches_block( - &self, - block_id: &KId, - flat: &[FlatBlockMember], - ) -> bool { - IX_RECURSOR_DUMP.as_ref().is_some_and(|prefix| { - format!("{block_id}").starts_with(prefix) - || flat.iter().any(|m| format!("{}", m.id).starts_with(prefix)) - }) - } - - fn dump_flat_aux_order( - &self, - label: &str, - block_id: &KId, - flat: &[FlatBlockMember], - n_originals: usize, - ) { - if !self.recursor_dump_matches_block(block_id, flat) { - return; - } - eprintln!( - "[recursor.dump] {label} flat aux order for {block_id}: originals={} aux={}", - n_originals, - flat.len().saturating_sub(n_originals) - ); - for (aux_i, member) in flat.iter().skip(n_originals).enumerate() { - let spec = - member.spec_params.iter().map(|e| format!("{e}")).collect::>(); - eprintln!( - " aux[{aux_i:2}] id={} own_params={} indices={} spec={spec:?}", - member.id, member.own_params, member.n_indices - ); - } - } - - fn dump_rule_rhs_first_diff( - &mut self, - lhs: &KExpr, - rhs: &KExpr, - path: &str, - depth: u64, - ) -> Result> { - if self.is_def_eq(lhs, rhs)? { - return Ok(false); - } - if depth > 80 { - eprintln!("[rule rhs diff] first diff {path}: recursion limit"); - eprintln!(" gen: {lhs}"); - eprintln!(" sto: {rhs}"); - return Ok(true); - } - - let lw = self.whnf(lhs)?; - let rw = self.whnf(rhs)?; - match (lw.data(), rw.data()) { - ( - ExprData::Lam(_, _, _, lty, lbody, _), - ExprData::Lam(_, _, _, rty, rbody, _), - ) - | ( - ExprData::All(_, _, _, _, lty, lbody, _), - 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}"); - return Ok(true); - } - let saved = self.lctx.len(); - let (lbody_open, fv, _) = - self.open_binder_anon_with_fv(lty.clone(), lbody); - let rbody_open = instantiate_rev(&mut self.env.intern, rbody, &[fv]); - let found = self.dump_rule_rhs_first_diff( - &lbody_open, - &rbody_open, - &format!("{path}.body"), - depth + 1, - ); - self.lctx.truncate(saved); - found - }, - (ExprData::App(lf, la, _), ExprData::App(rf, ra, _)) => { - if self.dump_rule_rhs_first_diff( - lf, - rf, - &format!("{path}.fn"), - depth + 1, - )? { - return Ok(true); - } - 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}"); - Ok(true) - }, - } - } - - /// A1: Check that the first `n_params` forall domains of ind_ty and ctor_ty agree. - fn check_param_agreement( - &mut self, - ind_ty: &KExpr, - ctor_ty: &KExpr, - n_params: usize, - ) -> Result<(), TcError> { - let saved = self.lctx.len(); - let mut it = ind_ty.clone(); - let mut ct = ctor_ty.clone(); - - for _ in 0..n_params { - let wi = self.whnf(&it)?; - let wc = self.whnf(&ct)?; - match (wi.data(), wc.data()) { - ( - ExprData::All(_, _, _, _, i_dom, i_body, _), - ExprData::All(_, _, _, _, c_dom, c_body, _), - ) => { - if !self.is_def_eq(i_dom, c_dom)? { - self.lctx.truncate(saved); - return Err(TcError::Other("param domain mismatch".into())); - } - let (i_open, fv, _) = - self.open_binder_anon_with_fv(i_dom.clone(), i_body); - let c_open = instantiate_rev(&mut self.env.intern, c_body, &[fv]); - it = i_open; - ct = c_open; - }, - _ => { - self.lctx.truncate(saved); - return Err(TcError::Other( - "expected forall in param agreement".into(), - )); - }, - } - } - - self.lctx.truncate(saved); - Ok(()) - } - - /// A3: Strict positivity — block inductives must not appear in negative position. - fn check_positivity( - &mut self, - ctor_ty: &KExpr, - n_params: usize, - block_addrs: &[Address], - ) -> Result<(), TcError> { - let saved = self.lctx.len(); - let result = (|| -> Result<(), TcError> { - // Open rather than merely dropping the shared parameter binders. This - // gives every recursive occurrence a stable set of fvars against which - // uniformity can be checked, including under dependent field binders. - let mut ty = ctor_ty.clone(); - let mut param_fvars = Vec::with_capacity(n_params); - for _ in 0..n_params { - let w = self.whnf(&ty)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - let (open, fv, _) = - self.open_binder_anon_with_fv(dom.clone(), body); - param_fvars.push(fv); - ty = open; - }, - _ => return Ok(()), // A1/A2 report malformed telescopes precisely. - } - } - - let groups = vec![PositivityGroup { - addrs: block_addrs.to_vec(), - params: param_fvars, - concrete_univs: None, - }]; - - // Check each field domain while retaining earlier field binders. - loop { - let w = self.whnf(&ty)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - self.check_positivity_domain(dom, &groups, block_addrs)?; - let (open, _) = self.open_binder_anon(dom.clone(), body); - ty = open; - }, - _ => break, - } - } - Ok(()) - })(); - self.lctx.truncate(saved); - result - } - - /// Validate a direct application of an active recursive family. This is the - /// field-occurrence analogue of `check_ctor_return_type`: arity, universe - /// specialization, uniform parameters, and index independence are all - /// checked before the occurrence is admitted as positive. - fn check_positive_recursive_application( - &mut self, - id: &KId, - us: &[KUniv], - args: &[KExpr], - groups: &[PositivityGroup], - root_addrs: &[Address], - ) -> Result<(), TcError> { - let group = groups - .iter() - .find(|group| group.addrs.contains(&id.addr)) - .cloned() - .ok_or_else(|| { - TcError::Other("positivity: missing recursive-family context".into()) - })?; - let (n_params, n_indices, lvls) = match self.get_const(id)? { - KConst::Indc { params, indices, lvls, .. } => { - (u64_to_usize(params)?, u64_to_usize(indices)?, u64_to_usize(lvls)?) - }, - _ => { - return Err(TcError::Other( - "positivity: recursive head is not an inductive".into(), - )); - }, - }; - - let app_arity = checked_binder_sum::( - "positivity recursive application", - n_params, - n_indices, - )?; - if args.len() != app_arity { - return Err(TcError::Other(format!( - "positivity: recursive occurrence has wrong argument count: expected {}, got {}", - app_arity, - args.len() - ))); - } - if us.len() != lvls { - return Err(TcError::Other(format!( - "positivity: recursive occurrence has wrong universe count: expected {lvls}, got {}", - us.len() - ))); - } - match group.concrete_univs.as_deref() { - Some(expected) => { - if expected.len() != us.len() - || !expected.iter().zip(us).all(|(a, b)| univ_eq(a, b)) - { - return Err(TcError::Other( - "positivity: recursive occurrence has non-uniform universe arguments" - .into(), - )); - } - }, - None => { - for (i, u) in us.iter().enumerate() { - let expected = - KUniv::param(i as u64, M::meta_field(ix_common::env::Name::anon())); - if !univ_eq(u, &expected) { - return Err(TcError::Other( - "positivity: recursive occurrence has non-uniform universe arguments" - .into(), - )); - } - } - }, - } - - if group.params.len() != n_params { - return Err(TcError::Other( - "positivity: recursive occurrence parameter arity disagrees with its family" - .into(), - )); - } - for (i, (actual, expected)) in - args[..n_params].iter().zip(&group.params).enumerate() - { - if !self.is_def_eq(actual, expected)? { - return Err(TcError::Other(format!( - "positivity: recursive occurrence {id} has non-uniform parameter {i}: expected {expected}, got {actual}" - ))); - } - } - for index in &args[n_params..] { - if expr_mentions_any_addr(index, root_addrs) { - return Err(TcError::Other( - "positivity: recursive occurrence index mentions an active inductive" - .into(), - )); - } - } - Ok(()) - } - - /// Whether an application has the exact universe/parameter specialization - /// represented by one nested-family group. Multiple groups may share an - /// inductive address: nested flattening keys auxiliaries by - /// `(inductive, specialization)`, not by inductive address alone. - fn positivity_group_matches( - group: &PositivityGroup, - family: &Address, - us: &[KUniv], - args: &[KExpr], - n_params: usize, - ) -> bool { - let Some(expected_universes) = group.concrete_univs.as_deref() else { - return false; - }; - same_nested_specialization( - family, - expected_universes, - &group.params, - family, - us, - &args[..n_params], - ) - } - - /// Check that a field domain doesn't have block inductives in negative position. - /// Follows Lean's `check_positivity` (`inductive.cpp`) and lean4lean's - /// `checkPositivity`: whnf the domain; accept it if the block no longer - /// occurs in it (a nonrecursive field, even when the unreduced domain - /// mentioned the block, as `K True (Box T)` with `K a _ := a`); otherwise - /// recurse through foralls, reject if inductive in domain (negative), accept - /// if result is a valid inductive app (direct or nested). - /// - /// For nested inductives `J Ds is` where `J` is external and `Ds` mention block - /// inductives, we recursively verify that `J`'s constructors (with `Ds` substituted - /// for parameters) are strictly positive in the augmented address set. This prevents - /// smuggling negative occurrences through an external inductive's parameter position. - fn check_positivity_domain( - &mut self, - dom: &KExpr, - groups: &[PositivityGroup], - active_addrs: &[Address], - ) -> Result<(), TcError> { - // Only occurrences containing the original block are relevant to this - // positivity traversal. Helper families may recur elsewhere at unrelated - // specializations (for example `Option Syntax` while traversing - // `Option (SnapshotTask TacticParsedSnapshot)`); their head address alone - // does not make that occurrence recursive for the root declaration. - let root_addrs = - groups.first().map(|group| group.addrs.as_slice()).ok_or_else(|| { - TcError::Other("positivity: missing root-family context".into()) - })?; - if !expr_mentions_any_addr(dom, root_addrs) { - return Ok(()); // no inductive mention at all — fine - } - - let w = self.whnf(dom)?; - // Lean tests the occurrence after whnf: reduction can remove the block - // (a definition dropping the argument that mentions it), never add it, - // so the test above is only a fast path. - if !expr_mentions_any_addr(&w, root_addrs) { - return Ok(()); - } - match w.data() { - ExprData::All(_, _, _, _, inner_dom, inner_body, _) => { - // Inductive in domain of a Pi = negative position → reject - if expr_mentions_any_addr(inner_dom, root_addrs) { - return Err(TcError::Other("strict positivity violation".into())); - } - // H4: Open binder with fvar so WHNF works correctly on dependent - // types (lean4lean Add.lean:187-189 uses withLocalDecl). - let saved = self.lctx.len(); - let (inner_open, _) = - self.open_binder_anon(inner_dom.clone(), inner_body); - let result = - self.check_positivity_domain(&inner_open, groups, active_addrs); - self.lctx.truncate(saved); - result - }, - _ => { - // Must be either: - // 1. A direct block inductive application: `I_k params args` - // 2. A nested inductive application: `J Ds is` where J is a previously - // declared inductive and Ds contain block inductives - let (head, args) = collect_app_spine(&w); - match head.data() { - ExprData::Const(id, us, _) if root_addrs.contains(&id.addr) => self - .check_positive_recursive_application( - id, us, &args, groups, root_addrs, - ), - ExprData::Const(id, us, _) => { - // Check if this is a nested inductive: head is an inductive type - // (not in our block) and its params contain block inductives. - let (n_params, n_indices, lvls, block, ctors) = match self - .get_const(id)? - { - KConst::Indc { params, indices, lvls, block, ctors, .. } => ( - u64_to_usize(params)?, - u64_to_usize(indices)?, - u64_to_usize(lvls)?, - block.clone(), - ctors.clone(), - ), - _ => { - return Err(TcError::Other( - "positivity: not a valid inductive app".into(), - )); - }, - }; - - let app_arity = checked_binder_sum::( - "positivity nested application", - n_params, - n_indices, - )?; - if args.len() != app_arity || us.len() != lvls { - return Err(TcError::Other( - "positivity: malformed nested inductive application".into(), - )); - } - - // An exact repeated specialization is the recursive edge of an - // already-validated synthetic auxiliary. A different - // specialization of the same external family is a distinct - // auxiliary (for example both `Array (ListItem (Block i b))` - // and `Array (Block i b)` in `Lean.Doc.Block`). - let existing_groups: Vec> = groups - .iter() - .filter(|group| group.addrs.contains(&id.addr)) - .cloned() - .collect(); - for group in &existing_groups { - if Self::positivity_group_matches( - group, &id.addr, us, &args, n_params, - ) { - for index in args.iter().skip(n_params) { - if expr_mentions_any_addr(index, root_addrs) { - return Err(TcError::Other( - "positivity: recursive occurrence index mentions an active inductive" - .into(), - )); - } - } - return Ok(()); - } - } - - // Verify params contain active inductive refs (that's what makes it nested) - let has_nested_ref = args - .iter() - .take(n_params) - .any(|a| expr_mentions_any_addr(a, root_addrs)); - if !has_nested_ref { - return Err(TcError::Other( - "positivity: not a valid inductive app".into(), - )); - } - - // Index args (after params) must not mention block inductives - for arg in args.iter().skip(n_params) { - if expr_mentions_any_addr(arg, root_addrs) { - return Err(TcError::Other( - "positivity: index mentions block inductive".into(), - )); - } - } - - // Add the external mutual family at this concrete specialization. - let mut augmented_addrs: Vec
= active_addrs.to_vec(); - let ext_block_inductives = - self.discover_block_inductives(&block)?; - for ext_id in &ext_block_inductives { - if !augmented_addrs.contains(&ext_id.addr) { - augmented_addrs.push(ext_id.addr.clone()); - } - } - - // Collect param args and universe args for substitution - let param_args: Vec> = - args.iter().take(n_params).cloned().collect(); - let us = us.clone(); - let mut augmented_groups = groups.to_vec(); - augmented_groups.push(PositivityGroup { - addrs: ext_block_inductives - .iter() - .map(|ext_id| ext_id.addr.clone()) - .collect(), - params: param_args.clone(), - concrete_univs: Some(us.clone()), - }); - - // For each constructor, strip params, substitute actual param args, - // and recursively check positivity of each field domain - for ctor_id in &ctors { - let ctor_ty = match self.get_const(ctor_id)? { - KConst::Ctor { ty, .. } => ty.clone(), - _ => { - return Err(TcError::Other( - "positivity: nested ctor not found".into(), - )); - }, - }; - self.check_nested_ctor_fields( - &ctor_ty, - n_params, - ¶m_args, - &us, - &augmented_groups, - &augmented_addrs, - )?; - } - - Ok(()) - }, - _ => { - Err(TcError::Other("positivity: not a valid inductive app".into())) - }, - } - }, - } - } - - /// Check positivity of a nested inductive's constructor fields. - /// - /// Strips `n_params` forall binders from `ctor_ty`, substitutes the actual - /// `param_args` (with universe instantiation via `us`), then checks each - /// remaining field domain for positivity against `augmented_addrs`. - fn check_nested_ctor_fields( - &mut self, - ctor_ty: &KExpr, - n_params: usize, - param_args: &[KExpr], - us: &[KUniv], - groups: &[PositivityGroup], - active_addrs: &[Address], - ) -> Result<(), TcError> { - // Instantiate universe params - let mut ty = self.instantiate_univ_params(ctor_ty, us)?; - - // Strip param foralls - for _ in 0..n_params { - let w = self.whnf(&ty)?; - match w.data() { - ExprData::All(_, _, _, _, _, body, _) => ty = body.clone(), - _ => { - return Err(TcError::Other( - "positivity: nested constructor has fewer parameter binders than declared" - .into(), - )); - }, - } - } - - // Simultaneously substitute param_args for the param binders. - // After stripping n_params foralls, Var(0)..Var(n_params-1) in the body - // refer to the params (Var(0) = innermost = last param). - // simul_subst replaces Var(depth+i) with substs[i], so at depth=0: - // Var(0) -> substs[0], Var(1) -> substs[1], ... - // The params were bound outermost-first, so after stripping: - // Var(n_params-1) = first param (outermost) - // Var(0) = last param (innermost) - // We need substs[i] = param_args[n_params-1-i] to reverse the order. - let reversed_params: Vec> = - param_args.iter().rev().cloned().collect(); - ty = simul_subst(&mut self.env.intern, &ty, &reversed_params, 0); - - // Now check each remaining field domain - self.check_nested_ctor_fields_loop(&ty, groups, active_addrs) - } - - /// Walk the remaining forall binders of a nested constructor type and check - /// each field domain for positivity against the augmented address set. - fn check_nested_ctor_fields_loop( - &mut self, - ty: &KExpr, - groups: &[PositivityGroup], - active_addrs: &[Address], - ) -> Result<(), TcError> { - let w = self.whnf(ty)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - self.check_positivity_domain(dom, groups, active_addrs)?; - let saved = self.lctx.len(); - let (open, _) = self.open_binder_anon(dom.clone(), body); - let result = - self.check_nested_ctor_fields_loop(&open, groups, active_addrs); - self.lctx.truncate(saved); - result - }, - _ => Ok(()), // base case: return type — no more fields to check - } - } - - /// A4: Universe constraints — field sort levels must be ≤ inductive result level. - fn check_field_universes( - &mut self, - ctor_ty: &KExpr, - n_params: usize, - ind_level: &KUniv, - ) -> Result<(), TcError> { - // Skip if inductive is Prop (Sort 0) — any universe is allowed - if ind_level.is_semantic_zero() { - return Ok(()); - } - - let saved = self.lctx.len(); - let mut ty = ctor_ty.clone(); - - // Skip params - for _ in 0..n_params { - let w = self.whnf(&ty)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - let (open, _) = self.open_binder_anon(dom.clone(), body); - ty = open; - }, - _ => break, - } - } - - // Check each field - loop { - let w = self.whnf(&ty)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - let dom_ty = self.infer(dom)?; - let field_level = self.ensure_sort(&dom_ty)?; - if !univ_geq(ind_level, &field_level) { - self.lctx.truncate(saved); - return Err(TcError::Other( - "field universe exceeds inductive level".into(), - )); - } - let (open, _) = self.open_binder_anon(dom.clone(), body); - ty = open; - }, - _ => break, - } - } - - self.lctx.truncate(saved); - Ok(()) - } - - /// A2: Validate constructor return type. - fn check_ctor_return_type( - &mut self, - ctor_ty: &KExpr, - n_params: usize, - n_indices: usize, - n_fields: usize, - ind_addr: &Address, - ind_lvls: u64, - block_addrs: &[Address], - ) -> Result<(), TcError> { - let saved = self.lctx.len(); - let mut ty = ctor_ty.clone(); - - // Skip params + fields. Track the param fvars so we can verify the - // return type's first n_params args are exactly the param fvars by - // FVar identity (replaces the legacy de Bruijn `Var(expected_idx)` - // match after the fvar transition). - let total_binders = checked_binder_sum::( - "constructor params + fields", - n_params, - n_fields, - )?; - let result_arity = checked_binder_sum::( - "constructor params + indices", - n_params, - n_indices, - )?; - let mut param_fvars: Vec> = Vec::with_capacity(n_params); - for i in 0..total_binders { - let w = self.whnf(&ty)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - let (open, fv, _) = self.open_binder_anon_with_fv(dom.clone(), body); - if i < n_params { - param_fvars.push(fv); - } - ty = open; - }, - _ => { - self.lctx.truncate(saved); - return Err(TcError::Other( - "ctor return type: not enough binders".into(), - )); - }, - } - } - let _ = total_binders; - - // Now ty should be the return type: I params... indices... - // Important: do NOT whnf here. The constructor return type must be - // syntactically `I args...` (possibly with App nodes), not something - // that only reduces to `I args...`. This prevents accepting ctor types - // like `id I` that reduce to `I` but aren't manifest applications. - let (head, args) = collect_app_spine(&ty); - - // Head must be the inductive with correct universe params - match head.data() { - ExprData::Const(id, us, _) if id.addr == *ind_addr => { - // Universe args must be Param(0), Param(1), ..., Param(lvls-1) in order - if us.len() as u64 != ind_lvls { - self.lctx.truncate(saved); - return Err(TcError::Other(format!( - "ctor return type: expected {} universe args, got {}", - ind_lvls, - us.len() - ))); - } - for (i, u) in us.iter().enumerate() { - let expected = - KUniv::param(i as u64, M::meta_field(ix_common::env::Name::anon())); - if !univ_eq(u, &expected) { - self.lctx.truncate(saved); - return Err(TcError::Other(format!( - "ctor return type: universe arg {i} is not Param({i})" - ))); - } - } - }, - _ => { - self.lctx.truncate(saved); - return Err(TcError::Other( - "ctor return type: head is not the inductive".into(), - )); - }, - } - - // S2: Total args must equal n_params + n_indices exactly. - if args.len() != result_arity { - self.lctx.truncate(saved); - return Err(TcError::Other(format!( - "ctor return type: expected {} args (params={} + indices={}), got {}", - result_arity, - n_params, - n_indices, - args.len() - ))); - } - - // First n_params args should be exactly the param fvars (FVar - // identity replaces legacy de Bruijn `Var(expected_idx)` matching). - for i in 0..n_params { - if i >= args.len() { - self.lctx.truncate(saved); - return Err(TcError::Other( - "ctor return type: not enough args for params".into(), - )); - } - if !args[i].hash_eq(¶m_fvars[i]) { - self.lctx.truncate(saved); - return Err(TcError::Other( - "ctor return type: param arg not the param fvar".into(), - )); - } - } - - // Index args should not mention block inductives - for arg in &args[n_params..] { - if expr_mentions_any_addr(arg, block_addrs) { - self.lctx.truncate(saved); - return Err(TcError::Other( - "ctor return type: index mentions block inductive".into(), - )); - } - } - - self.lctx.truncate(saved); - Ok(()) - } - - /// Get the result sort level of a type after peeling `n` foralls. - pub fn get_result_sort_level( - &mut self, - ty: &KExpr, - n: usize, - ) -> Result, TcError> { - let saved = self.save_depth(); - let result = (|| -> Result, TcError> { - let mut t = ty.clone(); - for i in 0..n { - let w = self.whnf(&t)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - self.push_local(dom.clone()); - t = body.clone(); - }, - _ => { - return Err(TcError::Other(format!( - "get_result_sort_level: expected {n} foralls, only found {i}" - ))); - }, - } - } - let w = self.whnf(&t)?; - match w.data() { - ExprData::Sort(u, _) => Ok(u.clone()), - _ => Err(TcError::Other("get_result_sort_level: not a sort".into())), - } - })(); - self.restore_depth(saved); - result - } - - /// Determine whether the recursor for this block is a large eliminator - /// (can target any universe). Follows lean4lean's isLargeEliminator. - /// - /// Returns true if: - /// 1. The inductive is NOT in Prop, OR - /// 2. Single inductive with 0 constructors (e.g. Empty), OR - /// 3. Single inductive with exactly 1 constructor where all non-param - /// fields either live in Prop or appear in the return type args. - pub fn is_large_eliminator( - &mut self, - result_level: &KUniv, - ind_infos: &[(KId, u64, u64, Vec>, KExpr)], - ) -> Result> { - // Case 1: non-Prop → always large. - // Use is_never_zero() (not !is_zero()) so that Param(u) — which CAN be - // Prop when u=0 — falls through to the single-constructor check. - if result_level.is_never_zero() { - return Ok(true); - } - // Must be a single inductive for large elimination from Prop - if ind_infos.len() != 1 { - return Ok(false); - } - let (_, n_params, _, ref ctors, _) = ind_infos[0]; - let n_params = u64_to_usize::(n_params)?; - match ctors.len() { - // Case 2: 0 constructors → large (Empty/False) - 0 => Ok(true), - // Case 3: 1 constructor → check fields - 1 => { - let (ctor_ty, ctor_fields) = match self.try_get_const(&ctors[0])? { - Some(KConst::Ctor { ty, fields, .. }) => { - (ty.clone(), u64_to_usize(fields)?) - }, - _ => return Ok(false), - }; - // 0 non-param fields → trivially large (e.g. Eq.refl) - if ctor_fields == 0 { - return Ok(true); - } - // Walk ctor type, collecting non-trivial field positions and the - // fvars opened for the field binders. We later check that each - // non-trivial field's fvar appears among the return-type args - // (FVar identity replaces the legacy de Bruijn match). - let saved = self.lctx.len(); - let mut ty = ctor_ty; - let mut non_trivial: Vec = Vec::new(); // field index (0-based among fields) - let mut field_fvars: Vec> = Vec::with_capacity(ctor_fields); - let ctor_arity = checked_binder_sum::( - "constructor params + fields", - n_params, - ctor_fields, - )?; - for i in 0..ctor_arity { - let w = self.whnf(&ty)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - if i >= n_params { - // Check if this field's sort level is non-zero (semantically) - let dom_ty = self.with_infer_only(|tc| tc.infer(dom))?; - if let Ok(sort_lvl) = self.ensure_sort(&dom_ty) - && !univ_eq(&sort_lvl, &KUniv::zero()) - { - non_trivial.push(i - n_params); - } - } - let (open, fv, _) = - self.open_binder_anon_with_fv(dom.clone(), body); - if i >= n_params { - field_fvars.push(fv); - } - ty = open; - }, - _ => break, - } - } - // ty is now the return type: I params args... - let (_, ret_args) = collect_app_spine(&ty); - let result = non_trivial.iter().all(|&fi| { - let target = &field_fvars[fi]; - ret_args.iter().any( - |arg| matches!(arg.data(), ExprData::FVar(_, _, _) if arg.hash_eq(target)), - ) - }); - self.lctx.truncate(saved); - Ok(result) - }, - // 2+ constructors → never large for Prop - _ => Ok(false), - } - } - - /// Generate recursors for all inductives in a block (lean4lean-style). - /// - /// Detects nested occurrences (à la `ElimNestedInductive`), builds a flat - /// block with auxiliary entries, and generates canonical recursor types for - /// all block members (original + auxiliary). - pub fn generate_block_recursors( - &mut self, - block_id: &KId, - ) -> Result<(), TcError> { - // Collect block inductives - let block_inds = self.discover_block_inductives(block_id)?; - if block_inds.is_empty() { - self.env.recursor_cache.insert(block_id.clone(), vec![]); - return Ok(()); - } - - // Extract basic info for is_large_eliminator check. - let mut ind_infos: Vec<(KId, u64, u64, Vec>, KExpr)> = - Vec::new(); - let mut n_params: u64 = 0; - let mut block_lvls: u64 = 0; - let mut block_is_unsafe: Option = None; - for (i, ind_id) in block_inds.iter().enumerate() { - match self.get_const(ind_id)? { - KConst::Indc { - params, indices, ctors, lvls, is_unsafe, ty, .. - } => { - if i == 0 { - n_params = params; - block_lvls = lvls; - } - match block_is_unsafe { - Some(expected) if expected != is_unsafe => { - return Err(TcError::Other( - "mutual inductives must share the same safety flag".into(), - )); - }, - None => block_is_unsafe = Some(is_unsafe), - _ => {}, - } - ind_infos.push(( - ind_id.clone(), - params, - indices, - ctors.clone(), - ty.clone(), - )); - }, - _ => { - return Err(TcError::Other( - "generate_block_recursors: not an inductive".into(), - )); - }, - } - } - - // Compute elimination level. - let first_ind_arity = checked_metadata_sum::( - "inductive params + indices", - &[ind_infos[0].1, ind_infos[0].2], - )?; - let result_level = self - .get_result_sort_level(&ind_infos[0].4, u64_to_usize(first_ind_arity)?)?; - let mut is_large = self.is_large_eliminator(&result_level, &ind_infos)?; - let mut univ_offset: u64 = if is_large { 1 } else { 0 }; - let mut rec_lvls = checked_metadata_sum::( - "generated recursor universe arity", - &[block_lvls, univ_offset], - )?; - - // Build flat block (detects nested occurrences). - // - // Prop elimination is decided on the complete expanded block. A single - // source member with a proof-valued nested field can pass the ordinary - // singleton test while its nested auxiliaries make the block mutual. - // Rebuild at offset zero if discovery retracts the provisional large - // eliminator; keeping the offset-one specializations would corrupt the - // recursor's universe arguments. Provably nonzero sorts stay large. The - // retraction reads the discovered block, which is the flat block the - // recursor is generated from. - let mut flat = self.build_flat_block(&block_inds, n_params, univ_offset)?; - if is_large && !result_level.is_never_zero() && flat.len() != 1 { - is_large = false; - univ_offset = 0; - rec_lvls = block_lvls; - flat = self.build_flat_block(&block_inds, n_params, univ_offset)?; - } - let rec_is_unsafe = block_is_unsafe.unwrap_or(false); - let elim_level = if is_large { - KUniv::param(0, M::meta_field(ix_common::env::Name::anon())) - } else { - KUniv::zero() - }; - let n_originals = block_inds.len(); - self.dump_flat_aux_order("discovery", block_id, &flat, n_originals); - - // The flat block is in discovery order, which is the order the stored - // recursor block follows: over a compiled environment - // (`RecursorAuxOrder::Canonical`), `build_flat_block` walks the stored - // members and opens each external group as its stored block, in Lean's - // member order, so no sort is needed. Lean's original recursors - // (`RecursorAuxOrder::Source`, `lean_ingress`) use the same walk with - // each external group reduced to its head. - - // Convert flat block to ind_infos format for existing build_motive_type / build_rec_type. - // For auxiliary members, we need their type from the environment. - let mut flat_ind_infos: Vec<(KId, u64, u64, Vec>, KExpr)> = - Vec::with_capacity(flat.len()); - for m in &flat { - let c = self.get_const(&m.id)?; - let ty = c.ty().clone(); - flat_ind_infos.push(( - m.id.clone(), - m.own_params, - m.n_indices, - m.ctors.clone(), - ty, - )); - } - let flat_ids: Vec> = flat.iter().map(|m| m.id.clone()).collect(); - - // Build motive types for ALL flat block members. - let mut motive_types: Vec> = Vec::new(); - for member in flat.iter() { - let motive_ty = self.build_motive_type_flat( - member, - u64_to_usize(n_params)?, - &elim_level, - univ_offset, - )?; - motive_types.push(motive_ty); - } - - let n_motives = flat.len() as u64; - let n_minors = flat.iter().try_fold(0u64, |sum, member| { - checked_metadata_sum::( - "generated recursor minors", - &[sum, member.ctors.len() as u64], - ) - })?; - - // Generate recursor type for each ORIGINAL inductive (not auxiliaries). - // The recursor type spans all flat block members (motives, minors). - let mut generated = Vec::new(); - for di in 0..n_originals { - let rec_type = self.build_rec_type( - di, - &flat_ind_infos, - &flat_ids, - &flat, - &elim_level, - &motive_types, - univ_offset, - )?; - generated.push(GeneratedRecursor { - ind_addr: flat[di].id.addr.clone(), - lvls: rec_lvls, - params: n_params, - motives: n_motives, - minors: n_minors, - indices: flat[di].n_indices, - is_unsafe: rec_is_unsafe, - ty: rec_type, - // Rules are populated later from the recursor block by - // `populate_recursor_rules_from_block`. - rules: vec![], - }); - } - - // Generate recursor types for auxiliary members too. - for di in n_originals..flat.len() { - let rec_type = self.build_rec_type( - di, - &flat_ind_infos, - &flat_ids, - &flat, - &elim_level, - &motive_types, - univ_offset, - )?; - generated.push(GeneratedRecursor { - ind_addr: flat[di].id.addr.clone(), - lvls: rec_lvls, - params: n_params, - motives: n_motives, - minors: n_minors, - indices: flat[di].n_indices, - is_unsafe: rec_is_unsafe, - ty: rec_type, - // Rules are populated later from the recursor block by - // `populate_recursor_rules_from_block`. - rules: vec![], - }); - } - - if self.recursor_dump_matches_block(block_id, &flat) { - eprintln!( - "[recursor.dump] generated recursors for {block_id}: count={}", - generated.len() - ); - for (gi, g) in generated.iter().enumerate() { - eprintln!( - " gen[{gi:2}] ind_addr={} type={}", - &g.ind_addr.hex()[..8], - g.ty - ); - } - } - - // Find peer recursor KIds for rule RHS generation. - // Each flat member needs its corresponding recursor constant for IH values. - let peer_recs = self.find_peer_recursors(block_id, &flat)?; - // Generate rules for each recursor. - if let Some(ref peers) = peer_recs { - for (gi, generated_rec) in generated.iter_mut().enumerate() { - let member = &flat[gi]; - let mut rules = Vec::new(); - for (ci, ctor_id) in member.ctors.iter().enumerate() { - let ctor_fields = match self.get_const(ctor_id)? { - KConst::Ctor { fields, .. } => fields, - _ => { - return Err(TcError::Other( - "generate_block_recursors: ctor not found".into(), - )); - }, - }; - let generated_rec_ty = generated_rec.ty.clone(); - match self.build_rule_rhs( - gi, - ci, - ctor_id, - member, - &flat, - peers, - &generated_rec_ty, - u64_to_usize(n_params)?, - is_large, - univ_offset, - ) { - Ok(rhs) => rules.push(Some(super::constant::RecRule { - ctor: ctor_id.name.clone(), - fields: ctor_fields, - rhs, - })), - Err(_) => { - rules.push(None); - }, - } - } - // Only set rules if ALL constructors succeeded. - if rules.iter().all(|r| r.is_some()) { - generated_rec.rules = rules.into_iter().map(|r| r.unwrap()).collect(); - } - } - } - - // Populate the majors cache: set of all flat block member KIds → block_id. - let majors_key: std::collections::BTreeSet> = - flat.iter().map(|m| m.id.clone()).collect(); - self.env.rec_majors_cache.insert(majors_key, block_id.clone()); - - self.env.recursor_cache.insert(block_id.clone(), generated); - Ok(()) - } - - /// Build motive type for a flat block member, handling spec_params. - /// - /// For original members: walks ind type past shared params (as binders), - /// collects indices, builds `∀ indices (t : I params indices), Sort u`. - /// For auxiliary members: walks ind type, substituting own_params with - /// spec_params (lifted), collects indices, builds `∀ indices (t : I spec_params indices), Sort u`. - pub fn build_motive_type_flat( - &mut self, - member: &FlatBlockMember, - n_rec_params: usize, - elim_level: &KUniv, - _univ_offset: u64, - ) -> Result, TcError> { - let anon = || M::meta_field(ix_common::env::Name::anon()); - let bi_default = || M::meta_field(ix_common::env::BinderInfo::Default); - - // Get inductive type and instantiate with occurrence universe args - // (concrete for auxiliaries, same as ind_us for originals). - let ind_ty = self.get_const(&member.id)?.ty().clone(); - let ind_ty_inst = - self.instantiate_univ_params(&ind_ty, &member.occurrence_us)?; - - // Walk past own_params, substituting with spec_params or recursor-param - // Var refs. No ctx pushes are needed here — `subst` handles the binder - // peel + Var(0) substitution structurally. - let mut ty = ind_ty_inst; - for j in 0..member.own_params { - let w = self.whnf(&ty)?; - match w.data() { - ExprData::All(_, _, _, _, _dom, body, _) => { - let p = if u64_to_usize::(j)? < member.spec_params.len() { - // spec_params live in the recursor-param context (depth = - // n_rec_params). We're at depth 0 here (no ctx pushes), so no - // lift is needed. - member.spec_params[u64_to_usize::(j)?].clone() - } else { - KExpr::var(n_rec_params as u64 - 1 - j, anon()) - }; - ty = subst(&mut self.env.intern, body, &p, 0); - }, - _ => break, - } - } - - // Collect index domains. No ctx push: track the index count in a local - // counter and use Var refs against it when building the major's args. - // The result is wrapped in `∀ indices major. Sort` afterwards, so the - // Var refs end up bound by those wrap binders. - let mut index_doms: Vec> = Vec::new(); - for _ in 0..member.n_indices { - let w = self.whnf(&ty)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - index_doms.push(dom.clone()); - ty = body.clone(); - }, - _ => break, - } - } - let n_idx = u64_to_usize::(member.n_indices)?; - - // Build major premise type: I.{us} params/spec_params indices. - // The major binder will sit below `∀ indices`, so internal Var refs - // are computed at depth = n_idx (the depth where major_ty appears as - // the binder type of the major-Pi inside the index-Pi chain). - let mut major_ty = - self.intern(KExpr::cnst(member.id.clone(), member.occurrence_us.clone())); - let depth = - checked_usize_to_u64::("generated motive index depth", n_idx)?; - if !member.is_aux { - // Original: params are loose Var refs that will be bound by the - // recursor's outer param-Pi chain (added by the caller). They sit - // (depth) binders below the major scope. - let n_rec_params_u64 = checked_usize_to_u64::( - "generated motive parameter count", - n_rec_params, - )?; - let param_base = checked_metadata_sum::( - "generated motive parameter depth", - &[n_rec_params_u64, depth], - )?; - for i in 0..n_rec_params { - let v = self.intern(KExpr::var( - param_base - - 1 - - checked_usize_to_u64::("generated motive parameter index", i)?, - anon(), - )); - major_ty = self.intern(KExpr::app(major_ty, v)); - } - } else { - // Auxiliary: lift spec_params from the recursor-param context to the - // major scope. - let lift_by = u64_to_usize::(depth)?; - for sp in member.spec_params.iter() { - let lifted = if lift_by > 0 { - lift(&mut self.env.intern, sp, lift_by as u64, 0) - } else { - sp.clone() - }; - major_ty = self.intern(KExpr::app(major_ty, lifted)); - } - } - // Apply indices (the index binders we're about to wrap around). - for i in 0..n_idx { - let v = self.intern(KExpr::var((n_idx - 1 - i) as u64, anon())); - major_ty = self.intern(KExpr::app(major_ty, v)); - } - - // Build: ∀ (major : major_ty), Sort elim_level - let sort = self.intern(KExpr::sort(elim_level.clone())); - let mut result = - self.intern(KExpr::all(anon(), bi_default(), major_ty, sort)); - - // Wrap with index foralls (from inside out). - for i in (0..n_idx).rev() { - result = self.intern(KExpr::all( - anon(), - bi_default(), - index_doms[i].clone(), - result, - )); - } - - Ok(result) - } - - /// Build minor premise type for a constructor, called while params and motives - /// are already on the context. This makes de Bruijn indices correct. - /// - /// For constructor `C : ∀ params fields, I params indices`: - /// ```text - /// ∀ (f₁ : F₁) ... (fₙ : Fₙ) - /// (ih₁ : ∀ xs, motive(indices(rec_field₁ xs), rec_field₁ xs)) - /// ... - /// (ihₘ : ∀ xs, motive(indices(rec_fieldₘ xs), rec_fieldₘ xs)), - /// motive(ctor_indices, C params f₁...fₙ) - /// ``` - fn build_minor_at_depth( - &mut self, - ind_idx: usize, - ctor_id: &KId, - member: &FlatBlockMember, - n_rec_params: usize, - motive_base: usize, // context level where motives start - flat: &[FlatBlockMember], - block_addrs: &[Address], - _univ_offset: u64, - ) -> Result, TcError> { - let ctor = match self.get_const(ctor_id)? { - KConst::Ctor { ty, lvls, .. } => (ty.clone(), lvls), - _ => { - return Err(TcError::Other( - "build_minor_at_depth: ctor not found".into(), - )); - }, - }; - let (ctor_ty_raw, _ctor_lvls) = ctor; - let anon = || M::meta_field(ix_common::env::Name::anon()); - let bi_default = || M::meta_field(ix_common::env::BinderInfo::Default); - let saved = self.lctx.len(); - - // Instantiate ctor type with occurrence universe args (concrete for output). - let ctor_ty = - self.instantiate_univ_params(&ctor_ty_raw, &member.occurrence_us)?; - - // Walk ctor type past member's own_params, substituting with spec_params. - // For originals: spec_params = Var refs relative to depth 0, need re-indexing - // to point to the recursor's param binders at the current depth. - // For auxiliaries: spec_params = concrete closed exprs (no lifting needed - // since they don't contain Var refs). - let mut ty = ctor_ty; - for j in 0..member.own_params { - let w = self.whnf(&ty)?; - match w.data() { - ExprData::All(_, _, _, _, _, body, _) => { - let p = if !member.is_aux { - // Original member: param j is the j-th recursor param binder. - // It's at context level j, so Var index = depth - 1 - j. - let depth = self.depth(); - KExpr::var(depth - 1 - j, anon()) - } else if u64_to_usize::(j)? < member.spec_params.len() { - // Auxiliary member: spec_params have Var refs relative to the param - // context (depth = n_rec_params). Lift by the difference between - // current depth and n_rec_params. - let sp = member.spec_params[u64_to_usize::(j)?].clone(); - let depth = u64_to_usize::(self.depth())?; - let lift_by = depth.saturating_sub(n_rec_params); - if lift_by > 0 { - lift(&mut self.env.intern, &sp, lift_by as u64, 0) - } else { - sp - } - } else { - let depth = self.depth(); - KExpr::var(depth - 1 - j, anon()) - }; - ty = subst(&mut self.env.intern, body, &p, 0); - }, - _ => break, - } - } - - // Collect fields and push them as locals - let mut field_domains: Vec> = Vec::new(); - let mut rec_field_indices: Vec<(usize, usize)> = Vec::new(); // (field_idx, block_ind_idx) - - let mut fidx = 0; - loop { - let w = self.whnf(&ty)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - field_domains.push(dom.clone()); - // Field args reference block params at current pushed-local - // depth; spec_params live at depth = n_rec_params (shared - // block params = flat[0].own_params). Lift by the difference. - let n_rec_params = flat.first().map_or(0, |m| m.own_params); - let lift_by = self.depth().saturating_sub(n_rec_params); - if let Some(bi) = self.is_rec_field(dom, flat, lift_by)? { - rec_field_indices.push((fidx, bi)); - } - let _ = self.push_fvar_decl_anon(dom.clone()); - ty = body.clone(); - fidx += 1; - }, - _ => break, - } - } - let n_fields = field_domains.len(); - - // Build IH types for recursive fields and push them as locals. - // At this point depth = saved + n_fields. - let mut ih_domains: Vec> = Vec::new(); - for (k, &(field_idx, block_ind_idx)) in rec_field_indices.iter().enumerate() - { - // depth = saved + n_fields + k (k IHs already pushed) - // For IH building, n_params should be the TARGET member's own_params - // (the member that the recursive field targets). - let target_n_params = if block_ind_idx < flat.len() { - u64_to_usize::(flat[block_ind_idx].own_params)? - } else { - n_rec_params - }; - let ih_ty = self.build_direct_ih( - field_idx, - block_ind_idx, - target_n_params, - n_fields, - k, - saved, - motive_base, - &field_domains, - block_addrs, - )?; - ih_domains.push(ih_ty.clone()); - let _ = self.push_fvar_decl_anon(ih_ty); - } - let n_ihs = ih_domains.len(); - let n_binders = checked_binder_sum::( - "generated minor fields + induction hypotheses", - n_fields, - n_ihs, - )?; - - // `ty` is the return type: I params indices - // The constructor always returns its own inductive, so ret_ind_idx = ind_idx. - // We don't search block_addrs because duplicate addresses (same external inductive - // with different spec_params) would return the wrong position. - let (_ret_head, ret_args) = collect_app_spine(&ty); - let ret_indices: Vec> = ret_args - .iter() - .skip(u64_to_usize::(member.own_params)?) - .cloned() - .collect(); - - // Build conclusion: motive[ind_idx](ret_indices, C params fields) - // Motive[ind_idx] is at context level: motive_base + ind_idx - let depth = self.depth(); - let motive_var_idx = - (u64_to_usize::(depth)? - 1 - (motive_base + ind_idx)) as u64; - let mut conclusion = self.intern(KExpr::var(motive_var_idx, anon())); - - // Apply return indices (these are at the old depth, but we pushed IHs since then, - // so we need to lift the indices by n_ihs) - for idx_expr in &ret_indices { - let lifted = if n_ihs > 0 { - lift( - &mut self.env.intern, - idx_expr, - n_ihs as u64, - 0, // lift ALL Var refs, not just those above fields - ) - } else { - idx_expr.clone() - }; - conclusion = self.intern(KExpr::app(conclusion, lifted)); - } - - // Apply C params/spec_params then fields - let mut ctor_app = - self.intern(KExpr::cnst(ctor_id.clone(), member.occurrence_us.clone())); - if !member.is_aux { - // Original: apply Var refs to recursor param binders - for i in 0..u64_to_usize::(member.own_params)? { - let pvar = self.intern(KExpr::var( - (u64_to_usize::(depth)? - 1 - i) as u64, - anon(), - )); - ctor_app = self.intern(KExpr::app(ctor_app, pvar)); - } - } else { - // Auxiliary: lift spec_params from param context to current depth - let lift_by = u64_to_usize::(depth)?.saturating_sub(n_rec_params); - for sp in &member.spec_params { - let lifted = if lift_by > 0 { - lift(&mut self.env.intern, sp, lift_by as u64, 0) - } else { - sp.clone() - }; - ctor_app = self.intern(KExpr::app(ctor_app, lifted)); - } - } - for i in 0..n_fields { - let fvar = self.intern(KExpr::var((n_binders - 1 - i) as u64, anon())); - ctor_app = self.intern(KExpr::app(ctor_app, fvar)); - } - conclusion = self.intern(KExpr::app(conclusion, ctor_app)); - - // Fold: ∀ (ihs...) (fields...), conclusion (from inside out) - // Pop IHs first (innermost) - for i in (0..n_ihs).rev() { - self.lctx.truncate(self.lctx.len() - 1); - conclusion = self.intern(KExpr::all( - anon(), - bi_default(), - ih_domains[i].clone(), - conclusion, - )); - } - // Pop fields - for i in (0..n_fields).rev() { - self.lctx.truncate(self.lctx.len() - 1); - conclusion = self.intern(KExpr::all( - anon(), - bi_default(), - field_domains[i].clone(), - conclusion, - )); - } - - self.lctx.truncate(saved); - Ok(conclusion) - } - - /// Build an IH type for a recursive field. - /// - /// For a direct recursive field (type = `I_bi params idx_args`): - /// IH = `motive_bi(idx_args, field_var)` - /// - /// For a forall-wrapped recursive field (type = `∀ xs, I_bi params idx_args(xs)`): - /// IH = `∀ xs, motive_bi(idx_args(xs), field xs)` - /// - /// Called when depth = minor_saved + n_fields + k (k IHs already pushed). - fn build_direct_ih( - &mut self, - field_idx: usize, - block_ind_idx: usize, - n_params: usize, - n_fields: usize, - k: usize, // number of IHs already pushed before this one - minor_saved: usize, // depth at entry of build_minor_at_depth - motive_base: usize, - field_domains: &[KExpr], - block_addrs: &[Address], - ) -> Result, TcError> { - let anon = || M::meta_field(ix_common::env::Name::anon()); - let bi_default = || M::meta_field(ix_common::env::BinderInfo::Default); - - // Lift the field domain from its original depth (minor_saved + field_idx) - // to the current depth (minor_saved + n_fields + k). - let dom = &field_domains[field_idx]; - let fields_and_ihs = checked_binder_sum::( - "generated minor field + induction-hypothesis depth", - n_fields, - k, - )?; - let shift = fields_and_ihs.checked_sub(field_idx).ok_or_else(|| { - TcError::Other("generated minor field depth underflow".into()) - })?; - let shift = - checked_usize_to_u64::("generated minor field depth", shift)?; - let dom_lifted = lift(&mut self.env.intern, dom, shift, 0); - let wdom = self.whnf(&dom_lifted)?; - - // Check if direct (head is block inductive) or forall-wrapped - match wdom.data() { - ExprData::All(..) => { - // Forall-wrapped: ∀ (xs...), I_bi params idx_args(xs) - // IH = ∀ (xs...), motive_bi(idx_args(xs), field xs) - let ih_saved = self.lctx.len(); - let mut inner_ty = wdom.clone(); - let mut forall_doms: Vec> = Vec::new(); - let inner_whnf; - - loop { - let w = self.whnf(&inner_ty)?; - match w.data() { - ExprData::All(_, _, _, _, inner_dom, inner_body, _) => { - let (h, _) = collect_app_spine(&w); - if matches!(h.data(), ExprData::Const(id, _, _) if block_addrs.contains(&id.addr)) - { - inner_whnf = w; - break; - } - forall_doms.push(inner_dom.clone()); - let _ = self.push_fvar_decl_anon(inner_dom.clone()); - inner_ty = inner_body.clone(); - }, - _ => { - inner_whnf = w; - break; - }, - } - } - let n_xs = forall_doms.len(); - - // inner_whnf = WHNF of the result type = I_bi params idx_args(xs) - let (_h, inner_args) = collect_app_spine(&inner_whnf); - let idx_args: Vec> = - inner_args.iter().skip(n_params).cloned().collect(); - - // Build motive_bi(idx_args, field xs) - let depth = u64_to_usize::(self.depth())?; - let motive_var = (depth - 1 - (motive_base + block_ind_idx)) as u64; - let mut ih_body = KExpr::var(motive_var, anon()); - for idx in &idx_args { - ih_body = self.intern(KExpr::app(ih_body, idx.clone())); - } - // field is at context level minor_saved + field_idx - let field_var = (depth - 1 - (minor_saved + field_idx)) as u64; - let mut field_app = KExpr::var(field_var, anon()); - for i in 0..n_xs { - let xvar = KExpr::var((n_xs - 1 - i) as u64, anon()); - field_app = self.intern(KExpr::app(field_app, xvar)); - } - ih_body = self.intern(KExpr::app(ih_body, field_app)); - - // Fold ∀ xs - for i in (0..n_xs).rev() { - self.lctx.truncate(self.lctx.len() - 1); - ih_body = - KExpr::all(anon(), bi_default(), forall_doms[i].clone(), ih_body); - } - - self.lctx.truncate(ih_saved); - Ok(ih_body) - }, - _ => { - // Direct case: dom_lifted head should be a block inductive - let (_dom_head, dom_args) = collect_app_spine(&wdom); - let idx_args: Vec> = - dom_args.iter().skip(n_params).cloned().collect(); - - let depth = u64_to_usize::(self.depth())?; - let motive_var = (depth - 1 - (motive_base + block_ind_idx)) as u64; - let mut ih_body = KExpr::var(motive_var, anon()); - - for idx in &idx_args { - ih_body = self.intern(KExpr::app(ih_body, idx.clone())); - } - - // field is at context level minor_saved + field_idx - let field_var = (depth - 1 - (minor_saved + field_idx)) as u64; - ih_body = - self.intern(KExpr::app(ih_body, KExpr::var(field_var, anon()))); - - Ok(ih_body) - }, - } - } - - /// Check if a field domain is a recursive occurrence of a flat block member. - /// Returns `Some(block_index)` if, after peeling foralls, the result is - /// `I_k params args` where `I_k` matches a flat member: - /// - /// - **Original** members (`is_aux = false`): head address match is - /// sufficient. - /// - **Auxiliary** members (`is_aux = true`): head address must match - /// AND the first `own_params` args must be definitionally equal to - /// the member's stored `spec_params` (after lifting spec_params to - /// the caller's param-reference frame). The addr check alone can't - /// distinguish two auxiliaries sharing an external inductive (e.g. - /// `List A` vs `List B`). - /// - /// # Depth handling - /// - /// `spec_params` are stored at the param context (depth = - /// `flat[0].own_params`). Callers reference block params via Var - /// indices that may live at different effective depths: - /// - /// - `build_minor_at_depth` pushes field locals as it scans; at the - /// `is_rec_field` call `self.depth() - n_rec_params` gives the - /// offset needed. - /// - `build_rule_rhs` does NOT push locals — it substitutes params - /// with `Var(total_lams - 1 - j)` (virtual positions for the final - /// lambda chain), leaving `self.depth() = 0` regardless of how - /// many virtual binders are open. The correct offset is - /// `total_lams - n_rec_params`. - /// - /// Rather than have the function guess, the caller passes - /// `spec_params_lift_by` explicitly. Comparison uses `is_def_eq` - /// after lifting, which handles alpha equivalence, whnf, and beta — - /// anything a raw `addr()` hash comparison would miss on `Var` - /// parameter references. - /// - /// Historical note: the original implementation used raw `addr()` - /// comparison after spine decomposition, which returned false - /// whenever a spec_param was a bare `Var` (block param). That - /// dropped the IH for any recursive field whose nested type used the - /// block's params directly — e.g. `head : Entry α β (Node α β)` in - /// a nested `List (Entry α β (Node α β))` scan. An interim fix - /// computed lift from `self.depth()`, which worked for - /// `build_minor_at_depth` but silently failed in `build_rule_rhs`. - fn is_rec_field( - &mut self, - dom: &KExpr, - flat: &[FlatBlockMember], - spec_params_lift_by: u64, - ) -> Result, TcError> { - let mut ty = dom.clone(); - loop { - // An exposed flat-member spine is already headed by an inductive - // constant, so full WHNF cannot reveal a different recursive target. - // This matters in rule construction, where the future lambda positions - // are represented by loose de Bruijn variables but are not installed in - // the checker's live local context. Hidden heads and non-member - // constants retain the ordinary WHNF path. - let head_matches_flat = Self::has_flat_member_head(&ty, flat); - let w = if head_matches_flat { ty.clone() } else { self.whnf(&ty)? }; - match w.data() { - ExprData::All(_, _, _, _, _, body, _) => ty = body.clone(), - _ => { - let (head, args) = collect_app_spine(&w); - let head_addr = match head.data() { - ExprData::Const(id, _, _) => &id.addr, - _ => return Ok(None), - }; - - for (idx, m) in flat.iter().enumerate() { - if m.id.addr != *head_addr { - continue; - } - if !m.is_aux { - return Ok(Some(idx)); - } - // Auxiliary: verify the caller's args agree with the - // stored spec_params after lifting them to caller depth. - let own = u64_to_usize::(m.own_params)?; - if args.len() < own || m.spec_params.len() != own { - continue; - } - let mut matches = true; - for (arg, sp) in args.iter().take(own).zip(m.spec_params.iter()) { - let sp_lifted = if spec_params_lift_by > 0 { - lift(&mut self.env.intern, sp, spec_params_lift_by, 0) - } else { - sp.clone() - }; - if !self.is_def_eq(arg, &sp_lifted).unwrap_or(false) { - matches = false; - break; - } - } - if matches { - return Ok(Some(idx)); - } - } - return Ok(None); - }, - } - } - } - - /// Whether `e` already exposes one of the flattened inductive members at - /// the head of its application spine. Such a head is irreducible: WHNF - /// cannot turn it into another forall or recursive target. - fn has_flat_member_head(e: &KExpr, flat: &[FlatBlockMember]) -> bool { - let (head, _) = collect_app_spine(e); - match head.data() { - ExprData::Const(id, _, _) => flat.iter().any(|m| m.id.addr == id.addr), - _ => false, - } - } - - /// Build the full recursor type for inductive `di` in the block. - /// - /// Structure: `∀ (params) (motives) (minors) (indices) (major), motive indices major` - /// - /// All domains are computed by walking the inductive/constructor types under - /// the appropriate binder context, then folding into a forall chain. - fn build_rec_type( - &mut self, - di: usize, - ind_infos: &[(KId, u64, u64, Vec>, KExpr)], - block_inds: &[KId], - flat: &[FlatBlockMember], - _elim_level: &KUniv, - motive_types: &[KExpr], - univ_offset: u64, - ) -> Result, TcError> { - let saved = self.lctx.len(); - let n_params = u64_to_usize::(ind_infos[0].1)?; - let n_motives = ind_infos.len(); - let n_indices = u64_to_usize::(ind_infos[di].2)?; - let block_addrs: Vec
= - block_inds.iter().map(|id| id.addr.clone()).collect(); - let anon = || M::meta_field(ix_common::env::Name::anon()); - let bi_default = || M::meta_field(ix_common::env::BinderInfo::Default); - - // Collect all binder domains in order: params, motives, minors, indices, major - let mut domains: Vec> = Vec::new(); - - // --- Params: walk first inductive's type, with shifted universe instantiation --- - let first_ind_lvls = match self.try_get_const(&block_inds[0])? { - Some(KConst::Indc { lvls, .. }) => lvls, - _ => 0, - }; - let first_ind_univs = self.mk_ind_univs(first_ind_lvls, univ_offset)?; - let pty_inst = - self.instantiate_univ_params(&ind_infos[0].4, &first_ind_univs)?; - let mut pty = pty_inst; - for _ in 0..n_params { - let w = self.whnf(&pty)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - domains.push(dom.clone()); - let _ = self.push_fvar_decl_anon(dom.clone()); - pty = body.clone(); - }, - _ => break, - } - } - - // --- Motives --- - // Each motive was built at depth 0 (standalone). When placed in the forall - // chain, motive j needs its free Vars lifted by j (accounting for the - // j motives already pushed before it). - for (j, mt) in motive_types.iter().enumerate() { - let lifted_mt = if j > 0 { - lift(&mut self.env.intern, mt, j as u64, 0) - } else { - mt.clone() - }; - domains.push(lifted_mt.clone()); - let _ = self.push_fvar_decl_anon(lifted_mt); - } - - // --- Minors: built inline at the correct depth --- - // motive_base = depth after pushing params (motives start here) - let motive_base = u64_to_usize::(self.depth())? - n_motives; - for (j, (_, _, _, j_ctors, _)) in ind_infos.iter().enumerate() { - let j_member = flat[j].clone(); - for ctor_id in j_ctors { - let minor_ty = self.build_minor_at_depth( - j, - ctor_id, - &j_member, - n_params, - motive_base, - flat, - &block_addrs, - univ_offset, - )?; - domains.push(minor_ty.clone()); - let _ = self.push_fvar_decl_anon(minor_ty); - } - } - let params_and_motives = checked_binder_sum::( - "generated recursor params + motives", - n_params, - n_motives, - )?; - let _n_minors = domains.len().checked_sub(params_and_motives) - .ok_or_else(|| TcError::Other(format!( - "build_rec_type: not enough binders: domains={}, params={n_params}, motives={n_motives}", - domains.len() - )))?; - - // --- Indices for THIS inductive (using flat block member info) --- - let di_member = &flat[di]; - let ity_inst = self - .instantiate_univ_params(&ind_infos[di].4, &di_member.occurrence_us)?; - let mut ity = ity_inst; - // Walk past this member's own_params, substituting appropriately. - for j in 0..di_member.own_params { - let w = self.whnf(&ity)?; - match w.data() { - ExprData::All(_, _, _, _, _, body, _) => { - let p = if !di_member.is_aux { - let depth = self.depth(); - KExpr::var(depth - 1 - j, anon()) - } else if u64_to_usize::(j)? < di_member.spec_params.len() { - let sp = di_member.spec_params[u64_to_usize::(j)?].clone(); - let lift_by = - u64_to_usize::(self.depth())?.saturating_sub(n_params); - if lift_by > 0 { - lift(&mut self.env.intern, &sp, lift_by as u64, 0) - } else { - sp - } - } else { - let depth = self.depth(); - KExpr::var(depth - 1 - j, anon()) - }; - ity = subst(&mut self.env.intern, body, &p, 0); - }, - _ => break, - } - } - for _ in 0..n_indices { - let w = self.whnf(&ity)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - domains.push(dom.clone()); - let _ = self.push_fvar_decl_anon(dom.clone()); - ity = body.clone(); - }, - _ => break, - } - } - - // --- Major premise: I spec_params indices --- - let ind_id = &ind_infos[di].0; - let mut major_dom = - self.intern(KExpr::cnst(ind_id.clone(), di_member.occurrence_us.clone())); - let depth = self.depth(); - if !di_member.is_aux { - for i in 0..u64_to_usize::(di_member.own_params)? { - let pvar = self.intern(KExpr::var( - (u64_to_usize::(depth)? - 1 - i) as u64, - anon(), - )); - major_dom = self.intern(KExpr::app(major_dom, pvar)); - } - } else { - let lift_by = u64_to_usize::(depth)?.saturating_sub(n_params); - for sp in &di_member.spec_params { - let lifted = if lift_by > 0 { - lift(&mut self.env.intern, sp, lift_by as u64, 0) - } else { - sp.clone() - }; - major_dom = self.intern(KExpr::app(major_dom, lifted)); - } - } - for i in 0..n_indices { - let ivar = self.intern(KExpr::var((n_indices - 1 - i) as u64, anon())); - major_dom = self.intern(KExpr::app(major_dom, ivar)); - } - domains.push(major_dom.clone()); - let _ = self.push_fvar_decl_anon(major_dom); - - // --- Return type: motive_di indices major --- - let depth = self.depth(); - let motive_var_idx = (u64_to_usize::(depth)? - 1 - n_params - di) as u64; - let mut ret = self.intern(KExpr::var(motive_var_idx, anon())); - for i in 0..n_indices { - let ivar = self.intern(KExpr::var((n_indices - i) as u64, anon())); - ret = self.intern(KExpr::app(ret, ivar)); - } - let major_var = self.intern(KExpr::var(0, anon())); - ret = self.intern(KExpr::app(ret, major_var)); - - // --- Fold into forall chain (from inside out) --- - for i in (0..domains.len()).rev() { - self.lctx.truncate(self.lctx.len() - 1); - ret = - self.intern(KExpr::all(anon(), bi_default(), domains[i].clone(), ret)); - } - - self.lctx.truncate(saved); - Ok(ret) - } - - /// Create shifted universe param args for an inductive in a recursor context. - /// For large eliminators (offset=1): [Param(1), ..., Param(n)]. - /// For small eliminators (offset=0): [Param(0), ..., Param(n-1)]. - fn mk_ind_univs( - &mut self, - ind_lvls: u64, - offset: u64, - ) -> Result]>, TcError> { - checked_metadata_sum::( - "generated recursor universe arity", - &[ind_lvls, offset], - )?; - Ok( - (0..ind_lvls) - .map(|i| { - KUniv::param(i + offset, M::meta_field(ix_common::env::Name::anon())) - }) - .collect::>() - .into_iter() - .map(|u| self.intern_univ(u)) - .collect(), - ) - } - - /// Find peer recursor KIds for each flat block member. - /// Returns None if peer recursors can't be found (block not in env). - fn find_peer_recursors( - &mut self, - block_id: &KId, - flat: &[FlatBlockMember], - ) -> Result>>, TcError> { - // Position-by-position alignment. - // - // `flat` is in discovery order over the stored members - // (`build_flat_block`). The recursor block — when one is - // co-resident with the inductive block — is stored in the same order, - // Lean's. So `flat[fi]` aligns with `rec_ids[fi]` directly. We sanity- - // check the alignment by comparing the major inductive address, and for - // auxiliary entries by comparing the param-portion of the major args - // against the member's `spec_params`. - // - // Returns `None` if any sanity check fails — caller falls back to - // `populate_recursor_rules_from_block`, which performs the same - // positional alignment with a more verbose diagnostic on failure. - let Some(members) = self.try_get_block(block_id)? else { - return Ok(None); - }; - let mut rec_ids: Vec> = Vec::new(); - for id in members { - if matches!(self.try_get_const(&id)?, Some(KConst::Recr { .. })) { - rec_ids.push(id); - } - } - - if rec_ids.len() != flat.len() { - return Ok(None); - } - - let mut result: Vec> = Vec::with_capacity(flat.len()); - for (fi, member) in flat.iter().enumerate() { - let rec_id = &rec_ids[fi]; - let (params, motives, minors, indices, ty) = - match self.try_get_const(rec_id)? { - Some(KConst::Recr { - params, motives, minors, indices, ty, .. - }) => (params, motives, minors, indices, ty.clone()), - _ => return Ok(None), - }; - let skip = checked_metadata_sum::( - "recursor major index", - &[params, motives, minors, indices], - )?; - let major_id = match self.get_major_inductive_id(&ty, skip) { - Ok(id) => id, - Err(TcError::UnknownConst(addr)) => { - return Err(TcError::UnknownConst(addr)); - }, - Err(_) => return Ok(None), - }; - if major_id.addr != member.id.addr { - return Ok(None); - } - if !member.is_aux { - result.push(rec_id.clone()); - continue; - } - // Auxiliary: verify spec_params match the stored major's param args. - let saved = self.lctx.len(); - let mut cur = ty; - for _ in 0..skip { - match self.whnf(&cur) { - Ok(w) => match w.data() { - ExprData::All(_, _, _, _, dom, b, _) => { - let _ = self.push_fvar_decl_anon(dom.clone()); - cur = b.clone(); - }, - _ => break, - }, - _ => break, - } - } - let mut matched = false; - if let Ok(w) = self.whnf(&cur) - && let ExprData::All(_, _, _, _, dom, _, _) = w.data() - { - let (_, major_args) = collect_app_spine(dom); - let n_par = match u64_to_usize::(member.own_params) { - Ok(n) => n, - Err(_) => return Ok(None), - }; - if major_args.len() >= n_par && member.spec_params.len() == n_par { - let n_rec_params = flat.first().map_or(0, |m| m.own_params); - let lift_by = self.depth().saturating_sub(n_rec_params); - matched = true; - for (arg, sp) in - major_args.iter().take(n_par).zip(member.spec_params.iter()) - { - let sp_lifted = if lift_by > 0 { - lift(&mut self.env.intern, sp, lift_by, 0) - } else { - sp.clone() - }; - if !self.is_def_eq(arg, &sp_lifted)? { - matched = false; - break; - } - } - } - } - self.lctx.truncate(saved); - if !matched { - return Ok(None); - } - result.push(rec_id.clone()); - } - - Ok(Some(result)) - } - - /// Populate canonical recursor rules from the actual recursor block peers. - /// - /// `generate_block_recursors` is driven from the inductive block, where the - /// recursor constants are not necessarily block members. With block-level - /// recursor checking, the recursor block is available before comparing any - /// sibling. Build the rule RHSs once from that block and store them back at - /// the generated-recursors indices. This avoids per-member fallback rule - /// generation and, critically, disambiguates duplicate nested auxiliaries by - /// the full major premise signature instead of by inductive address alone. - fn populate_recursor_rules_from_block( - &mut self, - ind_block_id: &KId, - rec_block_id: &KId, - ) -> Result<(), TcError> { - let generated_snapshot = match self.env.recursor_cache.get(ind_block_id) { - Some(g) => g.clone(), - None => return Ok(()), - }; - let generated_with_rules = self.populate_recursor_rules_from_block_core( - ind_block_id, - rec_block_id, - &generated_snapshot, - )?; - - let Some(cached) = self.env.recursor_cache.get(ind_block_id) else { - return Err(TcError::Other( - "populate_recursor_rules_from_block: cache disappeared before return" - .into(), - )); - }; - let metadata_matches = - cached.iter().zip(generated_snapshot.iter()).all(|(actual, expected)| { - actual.ind_addr == expected.ind_addr - && actual.lvls == expected.lvls - && actual.params == expected.params - && actual.motives == expected.motives - && actual.minors == expected.minors - && actual.indices == expected.indices - && actual.is_unsafe == expected.is_unsafe - }); - if cached.len() != generated_snapshot.len() || !metadata_matches { - return Err(TcError::Other( - "populate_recursor_rules_from_block: cache header metadata changed before return".into(), - )); - } - if generated_with_rules.len() != generated_snapshot.len() { - return Err(TcError::Other(format!( - "populate_recursor_rules_from_block: generated rule batch changed length: generated={} expected={}", - generated_with_rules.len(), - generated_snapshot.len() - ))); - } - - // Callback-written types and rules are untrusted. Rebuild the installed - // batch from the immutable ingress headers/types and the rule arrays - // returned by the local construction path. - let installed = generated_snapshot - .into_iter() - .zip(generated_with_rules) - .map(|(mut header, generated)| { - header.rules = generated.rules; - header - }) - .collect(); - self.env.recursor_cache.insert(ind_block_id.clone(), installed); - - Ok(()) - } - - /// Internal rule-population body for an already-snapshotted generated batch. - /// The public wrapper validates the snapshot again on every successful - /// return, including the early exits below. - fn populate_recursor_rules_from_block_core( - &mut self, - ind_block_id: &KId, - rec_block_id: &KId, - generated_snapshot: &[GeneratedRecursor], - ) -> Result>, TcError> { - if generated_snapshot.is_empty() { - return Ok(generated_snapshot.to_vec()); - } - - let members = match self.try_get_block(rec_block_id)? { - Some(m) => m, - None => return Ok(generated_snapshot.to_vec()), - }; - let mut rec_ids: Vec> = Vec::new(); - for id in members { - if matches!(self.try_get_const(&id)?, Some(KConst::Recr { .. })) { - rec_ids.push(id); - } - } - if rec_ids.is_empty() { - return Ok(generated_snapshot.to_vec()); - } - - let block_inds = self.discover_block_inductives(ind_block_id)?; - if block_inds.is_empty() { - return Ok(generated_snapshot.to_vec()); - } - let n_params_u64 = match self.try_get_const(&block_inds[0])? { - Some(KConst::Indc { params, .. }) => params, - _ => return Ok(generated_snapshot.to_vec()), - }; - let ind_lvls = match self.try_get_const(&block_inds[0])? { - Some(KConst::Indc { lvls, .. }) => lvls, - _ => 0, - }; - let univ_offset = match rec_ids.first() { - Some(rid) => match self.try_get_const(rid)? { - Some(KConst::Recr { lvls, .. }) if lvls > ind_lvls => 1u64, - _ => 0, - }, - None => 0, - }; - let flat = self.build_flat_block(&block_inds, n_params_u64, univ_offset)?; - if flat.len() != generated_snapshot.len() { - return Err(TcError::Other(format!( - "populate_recursor_rules_from_block: flat/generated length mismatch: flat={} generated={}", - flat.len(), - generated_snapshot.len() - ))); - } - if generated_snapshot - .iter() - .zip(flat.iter()) - .all(|(g, member)| g.rules.len() == member.ctors.len()) - { - return Ok(generated_snapshot.to_vec()); - } - - // Position-by-position alignment. - // - // Both the kernel-side `flat` (discovery order over the stored members, - // `build_flat_block`) and `rec_ids` (the recursor block - // members in their stored order) follow Lean's order, so generated peer - // `gi` aligns with `rec_ids[gi]` directly: no search, no greedy match. - // - // Verify the alignment with every header field and the complete closed - // recursor type. Peeling forall bodies here would expose dangling de - // Bruijn variables to stateful WHNF/DefEq callbacks. - if rec_ids.len() != flat.len() { - return Err(TcError::Other(format!( - "populate_recursor_rules_from_block: rec_ids/flat count mismatch: rec_ids={} flat={}", - rec_ids.len(), - flat.len() - ))); - } - - let mut peers: Vec> = Vec::with_capacity(flat.len()); - for (gi, gen_rec) in generated_snapshot.iter().enumerate() { - let rid = &rec_ids[gi]; - let (lvls, is_unsafe, params, motives, minors, indices, ty) = match self - .get_const( - rid, - )? { - KConst::Recr { - lvls, - is_unsafe, - params, - motives, - minors, - indices, - ty, - .. - } => (lvls, is_unsafe, params, motives, minors, indices, ty.clone()), - _ => { - return Err(TcError::Other(format!( - "populate_recursor_rules_from_block: rec_ids[{gi}]={rid} is not a recursor" - ))); - }, - }; - if (lvls, is_unsafe, params, motives, minors, indices) - != ( - gen_rec.lvls, - gen_rec.is_unsafe, - gen_rec.params, - gen_rec.motives, - gen_rec.minors, - gen_rec.indices, - ) - { - return Err(TcError::Other(format!( - "populate_recursor_rules_from_block: canonical header mismatch at peer {gi}" - ))); - } - if !self.is_def_eq(&gen_rec.ty, &ty)? { - return Err(TcError::Other(format!( - "populate_recursor_rules_from_block: canonical-order mismatch at peer {gi}: \ -flat[{gi}].id={}, rec_ids[{gi}]={}; complete recursor types differ", - flat[gi].id, rid - ))); - } - peers.push(rid.clone()); - } - - let peer_recs: Vec> = peers; - let is_large = univ_offset > 0; - let n_params = u64_to_usize::(n_params_u64)?; - let mut generated_with_rules = generated_snapshot.to_vec(); - - for gi in 0..flat.len() { - let member = &flat[gi]; - let rec_ty_for_member = generated_with_rules[gi].ty.clone(); - let mut rules = Vec::with_capacity(member.ctors.len()); - for (ci, ctor_id) in member.ctors.iter().enumerate() { - let ctor_fields = match self.get_const(ctor_id)? { - KConst::Ctor { fields, .. } => fields, - _ => { - return Err(TcError::Other( - "populate_recursor_rules_from_block: ctor not found".into(), - )); - }, - }; - let rhs = self.build_rule_rhs( - gi, - ci, - ctor_id, - member, - &flat, - &peer_recs, - &rec_ty_for_member, - n_params, - is_large, - univ_offset, - )?; - rules.push(super::constant::RecRule { - ctor: ctor_id.name.clone(), - fields: ctor_fields, - rhs, - }); - } - generated_with_rules[gi].rules = rules; - } - - Ok(generated_with_rules) - } - - /// Build the rule RHS for a single constructor. - /// - /// The RHS is: `λ (params) (motives) (minors) (fields), minor[idx] fields ihs` - /// where each IH = `λ (xs...), rec[target] params motives minors indices (field xs...)` - fn build_rule_rhs( - &mut self, - member_idx: usize, - ctor_local_idx: usize, - ctor_id: &KId, - member: &FlatBlockMember, - flat: &[FlatBlockMember], - peer_recs: &[KId], - rec_ty_for_member: &KExpr, - n_rec_params: usize, - is_large: bool, - _univ_offset: u64, - ) -> Result, TcError> { - let anon = || M::meta_field(ix_common::env::Name::anon()); - let bi_default = || M::meta_field(ix_common::env::BinderInfo::Default); - - let ctor_ty_raw = match self.get_const(ctor_id)? { - KConst::Ctor { ty, .. } => ty.clone(), - _ => return Err(TcError::Other("build_rule_rhs: ctor not found".into())), - }; - - let saved = self.lctx.len(); - - let n_motives = flat.len(); - let n_minors = flat.iter().try_fold(0usize, |sum, m| { - checked_binder_sum::("generated recursor minors", sum, m.ctors.len()) - })?; - let params_and_motives = checked_binder_sum::( - "generated recursor params + motives", - n_rec_params, - n_motives, - )?; - let pmm = checked_binder_sum::( - "generated recursor params + motives + minors", - params_and_motives, - n_minors, - )?; - - // --- Pass 1: count fields --- - // Walk ctor type past own_params WITHOUT substituting (field count is structural), - // then count remaining foralls. - let ctor_ty_inst = - self.instantiate_univ_params(&ctor_ty_raw, &member.occurrence_us)?; - let mut count_ty = ctor_ty_inst.clone(); - for _ in 0..member.own_params { - let w = self.whnf(&count_ty)?; - match w.data() { - ExprData::All(_, _, _, _, _, body, _) => count_ty = body.clone(), - _ => break, - } - } - let mut n_fields = 0u64; - let mut tmp = count_ty; - loop { - // The constructor result is an exposed application of its flat member. - // It cannot reduce to another field forall, and its arguments may use - // the virtual de Bruijn frame of the rule currently being built. - if Self::has_flat_member_head(&tmp, flat) { - break; - } - let w = self.whnf(&tmp)?; - match w.data() { - ExprData::All(_, _, _, _, _, body, _) => { - n_fields = checked_metadata_sum::( - "generated recursor fields", - &[n_fields, 1], - )?; - tmp = body.clone(); - }, - _ => break, - } - } - - let pmm_u64 = checked_usize_to_u64::( - "generated recursor params + motives + minors", - pmm, - )?; - let total_lams = checked_metadata_sum::( - "generated recursor rule lambdas", - &[pmm_u64, n_fields], - )?; - - // --- Pass 2: build body --- - // Structure: λ (p0..pk) (m0..ml) (min0..minr) (f0..fn), body - // body = minor[global_ctor_idx] f0..fn ih0..ihm - // - // Under total_lams lambdas: - // Var(total_lams - 1) = first param (p0) - // Var(total_lams - 1 - j) = param j - // Var(n_fields + n_minors + n_motives - 1) = first motive - // Var(n_fields + n_minors - 1 - gi) = minor gi - // Var(n_fields - 1) = first field (f0) - // Var(0) = last field (fn-1) - - // Global minor index for this ctor - let earlier_minors = - flat.iter().take(member_idx).try_fold(0usize, |sum, m| { - checked_binder_sum::( - "generated recursor preceding minors", - sum, - m.ctors.len(), - ) - })?; - let global_minor_idx = checked_binder_sum::( - "generated recursor global minor index", - earlier_minors, - ctor_local_idx, - )?; - let trailing_minors = n_minors - .checked_sub(global_minor_idx) - .and_then(|n| n.checked_sub(1)) - .ok_or_else(|| { - TcError::Other( - "generated recursor global minor index out of range".into(), - ) - })?; - let trailing_minors = checked_usize_to_u64::( - "generated recursor trailing minors", - trailing_minors, - )?; - let minor_var_idx = checked_metadata_sum::( - "generated recursor minor variable index", - &[n_fields, trailing_minors], - )?; - let mut body = self.intern(KExpr::var(minor_var_idx, anon())); - - // Apply fields: Var(n_fields - 1) down to Var(0) - for fi in 0..n_fields { - let fvar = self.intern(KExpr::var(n_fields - 1 - fi, anon())); - body = self.intern(KExpr::app(body, fvar)); - } - - // Walk ctor type with param substitution to detect recursive fields. - // - // Aux spec_params live in the param context (depth = - // `n_rec_params` — their Var refs point at param positions - // `Var(n_rec_params - 1)..Var(0)`). We want those Vars to land - // on the rule body's param positions `Var(total_lams - 1).. - // Var(total_lams - n_rec_params)`, so we lift by - // `total_lams - n_rec_params` — NOT by `total_lams`, which would - // push them one past the param slots and out of the body's scope. - // Originals substitute directly to `Var(total_lams - 1 - j)`, - // matching the same positions. - let n_rec_params_u64 = checked_usize_to_u64::( - "generated recursor parameter count", - n_rec_params, - )?; - let aux_sp_lift = - total_lams.checked_sub(n_rec_params_u64).ok_or_else(|| { - TcError::Other("generated recursor parameter depth underflow".into()) - })?; - let mut ty2 = ctor_ty_inst; - for j in 0..member.own_params { - let w = self.whnf(&ty2)?; - match w.data() { - ExprData::All(_, _, _, _, _, body2, _) => { - let p = if !member.is_aux { - KExpr::var(total_lams - 1 - j, anon()) - } else if u64_to_usize::(j)? < member.spec_params.len() { - let sp = member.spec_params[u64_to_usize::(j)?].clone(); - lift(&mut self.env.intern, &sp, aux_sp_lift, 0) - } else { - KExpr::var(total_lams - 1 - j, anon()) - }; - ty2 = subst(&mut self.env.intern, body2, &p, 0); - }, - _ => break, - } - } - - // Detect recursive fields and build IH values. - // - // Field type Var refs point to the final-lambda positions we - // substituted above: params at `Var(total_lams - 1 - j)` (for - // originals) or embedded inside `lift(spec_params, total_lams)` - // (for auxiliaries). Stored aux spec_params in `flat[]` live at - // `n_rec_params` depth — so `is_rec_field` must lift them by - // `total_lams - n_rec_params` to align with the field's frame. - // Without this, Var-containing spec_params (e.g. `α` in - // `Entry α β (Node α β)`) would mis-match and their IHs would be - // silently dropped. - let rec_field_lift = - total_lams.checked_sub(n_rec_params_u64).ok_or_else(|| { - TcError::Other( - "generated recursor recursive-field depth underflow".into(), - ) - })?; - let mut field_idx = 0u64; - loop { - // Stop before sending the exposed inductive result, whose arguments - // inhabit the future lambda frame rather than `self.lctx`, through WHNF. - if Self::has_flat_member_head(&ty2, flat) { - break; - } - let w = self.whnf(&ty2)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body2, _) => { - let dom = dom.clone(); - let body2 = body2.clone(); - - if let Some(target_bi) = - self.is_rec_field(&dom, flat, rec_field_lift)? - { - let ih = self.build_rule_ih( - field_idx, - n_fields, - total_lams, - target_bi, - flat, - peer_recs, - n_rec_params, - n_motives, - n_minors, - is_large, - &dom, - )?; - body = self.intern(KExpr::app(body, ih)); - } - - // Substitute this field with its Var ref for dependent types - let fvar = KExpr::var(n_fields - 1 - field_idx, anon()); - ty2 = subst(&mut self.env.intern, &body2, &fvar, 0); - field_idx += 1; - }, - _ => break, - } - } - - // --- Wrap body in lambda chain (inside-out) --- - // Field lambdas: extract domains from the peer recursor's minor premise. - // The minor for this constructor has type: - // ∀ (field₀ : T₀) ... (fieldₙ : Tₙ) (ih₀ : ...) ..., motive (ctor fields) - // We extract the first n_fields forall domains from the minor. - // These domains already have correct de Bruijn indices relative to the - // recursor's binding context (params, motives, earlier minors are above). - let minor_domain = { - // Walk past params, motives, and earlier minors to reach this ctor's minor - let mut cur = rec_ty_for_member.clone(); - let params_and_motives = checked_binder_sum::( - "generated recursor params + motives", - n_rec_params, - n_motives, - )?; - let skip_to_minor = checked_binder_sum::( - "generated recursor minor position", - params_and_motives, - global_minor_idx, - )?; - for _ in 0..skip_to_minor { - let w = self.whnf(&cur)?; - match w.data() { - ExprData::All(_, _, _, _, _, b, _) => cur = b.clone(), - _ => break, - } - } - // cur should be ∀ (minor_i : T_minor) ..., extract T_minor - let w = self.whnf(&cur)?; - match w.data() { - ExprData::All(_, _, _, _, dom, _, _) => dom.clone(), - _ => KExpr::sort(KUniv::zero()), - } - }; - // Extract field domains from the minor's type (which is a nested forall). - // The minor's domain is at depth `skip_to_minor` in the recursor type. - // The field lambdas in the rule are at depth `n_rec_params + n_motives + n_minors`. - // We lift each domain by the difference to adjust free Var references. - // Cutoff = fi because domain fi is inside fi nested foralls in the minor's - // type, so Var(0)..Var(fi-1) are bound refs to earlier fields, not free. - let field_dom_lift = checked_usize_to_u64::( - "generated recursor field-domain lift", - n_minors.checked_sub(global_minor_idx).ok_or_else(|| { - TcError::Other( - "generated recursor global minor index out of range".into(), - ) - })?, - )?; - let mut field_domains: Vec> = - Vec::with_capacity(u64_to_usize::(n_fields)?); - let mut minor_cur = minor_domain; - for fi in 0..n_fields { - let w = self.whnf(&minor_cur)?; - match w.data() { - ExprData::All(_, _, _, _, dom, b, _) => { - let lifted_dom = if field_dom_lift > 0 { - lift(&mut self.env.intern, dom, field_dom_lift, fi) - } else { - dom.clone() - }; - field_domains.push(lifted_dom); - minor_cur = b.clone(); - }, - _ => break, - } - } - // Wrap in reverse: last field innermost, first field outermost. - // This ensures Var(n_fields-1) = first field, Var(0) = last field, - // matching the body's de Bruijn indexing. - for i in (0..field_domains.len()).rev() { - body = self.intern(KExpr::lam( - anon(), - bi_default(), - field_domains[i].clone(), - body, - )); - } - - // PMM lambdas: extract actual domains from the peer recursor's type. - // The recursor type has the shape: - // ∀ (params...) (motives...) (minors...) (indices...) (major), ret - // We need the first pmm domains for the rule's leading lambdas. - // Do NOT instantiate universe params: the rule RHS and recursor type share - // the same Param references. The stored rule was built by Lean with the same - // Param indices as the recursor type. - let mut pmm_domains: Vec> = Vec::with_capacity(pmm); - let mut rec_ty_cur = rec_ty_for_member.clone(); - for _ in 0..pmm { - let w = self.whnf(&rec_ty_cur)?; - match w.data() { - ExprData::All(_, _, _, _, dom, b, _) => { - pmm_domains.push(dom.clone()); - rec_ty_cur = b.clone(); - }, - _ => { - // Fallback to placeholder if recursor type is shorter than expected - pmm_domains.push(KExpr::sort(KUniv::zero())); - break; - }, - } - } - // Wrap body in PMM lambdas (inside-out: minors, then motives, then params) - // pmm_domains is [p0, ..., pk, m0, ..., ml, min0, ..., minr] - // We wrap inside-out, so we need to reverse through them - for i in (0..pmm).rev() { - let dom = if i < pmm_domains.len() { - pmm_domains[i].clone() - } else { - KExpr::sort(KUniv::zero()) - }; - body = self.intern(KExpr::lam(anon(), bi_default(), dom, body)); - } - - self.lctx.truncate(saved); - Ok(body) - } - - /// Build an IH value for a recursive field in a rule RHS. - /// - /// Direct case (field type = `I_bi params idx_args`): - /// IH = `rec[target] params motives minors idx_args field` - /// - /// Forall-wrapped case (field type = `∀ (xs...), I_bi params idx_args(xs)`): - /// IH = `λ (xs...), rec[target] params motives minors idx_args(xs) (field xs...)` - fn build_rule_ih( - &mut self, - field_idx: u64, - n_fields: u64, - total_lams: u64, - target_bi: usize, - flat: &[FlatBlockMember], - peer_recs: &[KId], - n_rec_params: usize, - n_motives: usize, - n_minors: usize, - is_large: bool, - dom: &KExpr, - ) -> Result, TcError> { - let anon = || M::meta_field(ix_common::env::Name::anon()); - let bi_default = || M::meta_field(ix_common::env::BinderInfo::Default); - - let target_n_params = u64_to_usize::(flat[target_bi].own_params)?; - - // Use the TARGET recursor (the one for the inductive the field recurses on), - // matching lean4lean (Add.lean:427), lean4 C++ (inductive.cpp:738), - // and ix/kernel (recursor.rs:1391). - let peer_rec = &peer_recs[target_bi]; - let peer_rec_lvls = match self.try_get_const(peer_rec)? { - Some(KConst::Recr { lvls, .. }) => lvls, - _ => { - if is_large { - checked_metadata_sum::( - "generated recursor universe arity", - &[flat[target_bi].lvls, 1], - )? - } else { - flat[target_bi].lvls - } - }, - }; - let rec_lvls: Box<[KUniv]> = (0..peer_rec_lvls) - .map(|i| KUniv::param(i, M::meta_field(ix_common::env::Name::anon()))) - .collect(); - - // Peel foralls from the domain to detect wrapping. - // After peeling, the head should be `I_target params idx_args`. - let wdom = if Self::has_flat_member_head(dom, flat) { - dom.clone() - } else { - self.whnf(dom)? - }; - let mut inner = wdom.clone(); - let mut forall_doms: Vec> = Vec::new(); - - while let ExprData::All(_, _, _, _, fd, fb, _) = inner.data() { - // Check if this forall's result type (after peeling) has a block - // inductive as head. If inner itself IS a block inductive app, stop. - let (h, _) = collect_app_spine(&inner); - if matches!(h.data(), ExprData::Const(id, _, _) - if flat.iter().any(|m| m.id.addr == id.addr)) - { - break; - } - forall_doms.push(fd.clone()); - inner = fb.clone(); - } - let n_xs = checked_usize_to_u64::( - "generated recursor wrapped-field binder count", - forall_doms.len(), - )?; - - // Extract index args from the inner application: `I_target params idx_args` - let inner_w = if Self::has_flat_member_head(&inner, flat) { - inner.clone() - } else { - self.whnf(&inner)? - }; - let (_, inner_args) = collect_app_spine(&inner_w); - let idx_args: Vec> = - inner_args.iter().skip(target_n_params).cloned().collect(); - - // Build the IH core: rec[target] params motives minors indices field - // All Var references are relative to total_lams (+ n_xs for forall-wrapped case). - let depth = checked_metadata_sum::( - "generated recursor induction-hypothesis depth", - &[total_lams, n_xs], - )?; - let n_rec_params_u64 = checked_usize_to_u64::( - "generated recursor parameter count", - n_rec_params, - )?; - let n_motives_u64 = - checked_usize_to_u64::("generated recursor motive count", n_motives)?; - - let mut ih = self.intern(KExpr::cnst(peer_rec.clone(), rec_lvls)); - // Apply params - for pi in 0..n_rec_params { - let pi = - checked_usize_to_u64::("generated recursor parameter index", pi)?; - let pvar = self.intern(KExpr::var(depth - 1 - pi, anon())); - ih = self.intern(KExpr::app(ih, pvar)); - } - // Apply motives - for mi in 0..n_motives { - let mi = - checked_usize_to_u64::("generated recursor motive index", mi)?; - let mvar = - self.intern(KExpr::var(depth - 1 - n_rec_params_u64 - mi, anon())); - ih = self.intern(KExpr::app(ih, mvar)); - } - // Apply minors - for mi in 0..n_minors { - let mi = checked_usize_to_u64::("generated recursor minor index", mi)?; - let mvar = self.intern(KExpr::var( - depth - 1 - n_rec_params_u64 - n_motives_u64 - mi, - anon(), - )); - ih = self.intern(KExpr::app(ih, mvar)); - } - // Apply indices. After peeling n_xs foralls from dom, free Var refs in - // idx_args are already shifted by n_xs (standard de Bruijn binder entry), - // placing them at depth = total_lams + n_xs. No additional lift needed. - for idx in &idx_args { - ih = self.intern(KExpr::app(ih, idx.clone())); - } - // Apply the field variable (+ xs for forall-wrapped case) - // Field is at Var(n_fields - 1 - field_idx) relative to total_lams, - // shifted by n_xs under the forall binders. - let field_offset = n_fields - .checked_sub(field_idx) - .and_then(|n| n.checked_sub(1)) - .ok_or_else(|| { - TcError::Other("generated recursor field index out of range".into()) - })?; - let field_base = checked_metadata_sum::( - "generated recursor wrapped-field index", - &[field_offset, n_xs], - )?; - let mut field_app = self.intern(KExpr::var(field_base, anon())); - // Apply forall-bound variables: xs are Var(n_xs-1)..Var(0) under the lambdas - for xi in 0..n_xs { - let xvar = self.intern(KExpr::var(n_xs - 1 - xi, anon())); - field_app = self.intern(KExpr::app(field_app, xvar)); - } - ih = self.intern(KExpr::app(ih, field_app)); - - // Wrap in lambdas for forall-bound variables - for i in (0..u64_to_usize::(n_xs)?).rev() { - ih = self.intern(KExpr::lam( - anon(), - bi_default(), - forall_doms[i].clone(), - ih, - )); - } - - Ok(ih) - } - - /// Validate a recursor block. A pure recursor block is checked once and the - /// result is shared by all sibling recursors. - pub fn check_recursor(&mut self, id: &KId) -> Result<(), TcError> { - let block = match self.get_const(id)? { - KConst::Recr { block, .. } => block.clone(), - _ => return Err(TcError::Other("check_recursor: not a recursor".into())), - }; - let Some(members) = self.try_get_block(&block)? else { - return self.check_recursor_member(id); - }; - for member in &members { - if !matches!(self.try_get_const(member)?, Some(KConst::Recr { .. })) { - return self.check_recursor_member(id); - } - } - - if let Some(result) = self.env.block_check_results.get(&block).cloned() { - return result; - } - let result = self.check_recursor_block(&block, &members); - self.env.block_check_results.insert(block, result.clone()); - result - } - - /// Validate every recursor in a recursor block. - pub(crate) fn check_recursor_block( - &mut self, - block: &KId, - members: &[KId], - ) -> Result<(), TcError> { - for member in members { - self.reset(); - self.begin_const(member); - let c = self.get_const(member)?; - self.validate_const_well_scoped(&c)?; - match c { - KConst::Recr { ty, .. } => { - let t = self.infer(&ty)?; - self.ensure_sort(&t)?; - }, - _ => { - return Err(TcError::Other(format!( - "check_recursor_block: non-recursor member {member} in block {block}" - ))); - }, - } - } - - for member in members { - self.reset(); - self.begin_const(member); - self.check_recursor_member(member)?; - } - Ok(()) - } - - /// Validate a recursor by comparing with generated canonical form. - pub fn check_recursor_member( - &mut self, - id: &KId, - ) -> Result<(), TcError> { - let ( - rec_block, - ty, - declared_k, - declared_lvls, - declared_is_unsafe, - params, - motives, - minors, - indices, - stored_rules, - ) = match self.get_const(id)? { - KConst::Recr { - block, - ty, - k, - lvls, - is_unsafe, - params, - motives, - minors, - indices, - rules, - .. - } => ( - block.clone(), - ty.clone(), - k, - lvls, - is_unsafe, - params, - motives, - minors, - indices, - rules.clone(), - ), - _ => { - return Err(TcError::Other("check_recursor: not a recursor".into())); - }, - }; - // Find the major inductive from this recursor's type. - let skip = checked_metadata_sum::( - "recursor major index", - &[params, motives, minors, indices], - )?; - let ind_id = self.get_major_inductive_id(&ty, skip)?; - - // Coherence gate: the major inductive itself must pass A1–A4. Without - // this, a recursor for a structurally-invalid inductive (bad ctor return - // shape, field-universe violation, strict-positivity violation, …) can - // slip through because recursor generation succeeds syntactically even - // when the inductive is unsound. `check_inductive` is idempotent with - // our own `generate_block_recursors` call below (both guarded by - // `recursor_cache.contains_key`), so re-entering is safe. - // - // Cycle invariant: `check_inductive` never calls back into - // `check_recursor` — it only calls `generate_block_recursors`. Keep it - // that way. - if matches!(self.try_get_const(&ind_id)?, Some(KConst::Indc { .. })) { - self.check_inductive(&ind_id)?; - } - - // Try direct lookup: major ind's own block. - let ind_block = match self.try_get_const(&ind_id)? { - Some(KConst::Indc { block, .. }) => Some(block.clone()), - _ => None, - }; - - // Check if the direct block has generated recursors with the right - // number of motives. For auxiliary recursors (e.g., RCasesPatt.rec_1 - // targeting List), the direct block (List's) has fewer motives than needed. - let resolved_block = if let Some(ref ib) = ind_block { - if let Some(cached) = self.env.recursor_cache.get(ib) { - if cached.len() as u64 >= motives { Some(ib.clone()) } else { None } - } else { - None - } - } else { - None - }; - - // If direct lookup failed, use rec_majors_cache: - // gather all peer recursors' major inductives to form the lookup key. - let resolved_block = match resolved_block { - Some(b) => b, - None => { - let majors_key = self.gather_peer_majors(&rec_block)?; - match self.env.rec_majors_cache.get(&majors_key).cloned() { - Some(block_id) => block_id, - None => { - // Not generated yet — try generating from each peer major's - // inductive block until the majors cache is populated. - for major_id in &majors_key { - if let Some(KConst::Indc { block, .. }) = - self.try_get_const(major_id)? - { - let ib = block.clone(); - if !self.env.recursor_cache.contains_key(&ib) { - let _ = self.generate_block_recursors(&ib); - } - } - } - // Re-check the majors cache. - let majors_key = self.gather_peer_majors(&rec_block)?; - match self.env.rec_majors_cache.get(&majors_key).cloned() { - Some(block_id) => block_id, - None => { - return Err(TcError::Other( - "check_recursor: could not resolve inductive block".into(), - )); - }, - } - }, - } - }, - }; - - // S1: Constructively verify K-target flag. - // K-like reduction is only sound for: single inductive, Prop result level, - // exactly one constructor with zero non-param fields. - let computed_k = self.compute_k_target(&ind_id)?; - if declared_k != computed_k { - return Err(TcError::Other(format!( - "check_recursor: K-target mismatch: declared k={declared_k}, computed k={computed_k}" - ))); - } - - self.populate_recursor_rules_from_block(&resolved_block, &rec_block)?; - - // Find the generated recursor for this inductive. - let generated = match self.env.recursor_cache.get(&resolved_block) { - Some(g) => g.clone(), - None => { - return Err(TcError::Other( - "check_recursor: no generated recursors".into(), - )); - }, - }; - - // Full-type match for aux recursors. - // - // Nested auxiliaries can contain several recursors with the same external - // major head. Complete recursor types disambiguate those specializations - // while remaining closed. Extracting a major domain by peeling forall - // bodies would instead send dangling de Bruijn variables through - // stateful WHNF/DefEq callbacks. - let stored_pos: Option = self - .env - .blocks - .get(&rec_block) - .and_then(|members| members.iter().position(|m| m == id)); - let header_matches = |g: &GeneratedRecursor| { - g.ind_addr == ind_id.addr - && g.params == params - && g.motives == motives - && g.minors == minors - }; - - // Block alignment was already checked position-by-position by - // `populate_recursor_rules_from_block`. Try that position first and stop - // as soon as its complete type matches. Scanning every same-major - // auxiliary before consulting the position is catastrophically expensive - // for nested mutual blocks: their recursor types share a hundred-binder - // prefix, so each *wrong* candidate consumes substantial def-eq fuel before - // diverging near the tail. The positional candidate is hash-identical in - // compiled environments, making the common path O(1). - let mut type_matches: Vec = Vec::new(); - if let Some(p) = stored_pos - && let Some(g) = generated.get(p) - && header_matches(g) - && self.is_def_eq(&g.ty, &ty)? - { - type_matches.push(p); - } - if type_matches.is_empty() { - for (gi, g) in generated.iter().enumerate() { - if Some(gi) == stored_pos || !header_matches(g) { - continue; - } - if self.is_def_eq(&g.ty, &ty)? { - type_matches.push(gi); - } - } - } - let selected_idx = type_matches.first().copied(); - - if self.recursor_dump_matches_id(id) { - eprintln!( - "[recursor.dump] check {} rec_block={} resolved_block={} stored_pos={stored_pos:?} selected_idx={selected_idx:?}", - id, rec_block, resolved_block - ); - eprintln!("[recursor.dump] full_type_matches={type_matches:?}"); - } - - let gen_rec = selected_idx.map(|i| &generated[i]); - match gen_rec { - Some(g) => { - if declared_lvls != g.lvls { - return Err(TcError::Other(format!( - "check_recursor: universe arity mismatch: stored={declared_lvls}, generated={}", - g.lvls - ))); - } - if declared_is_unsafe != g.is_unsafe { - return Err(TcError::Other(format!( - "check_recursor: safety mismatch: stored={declared_is_unsafe}, generated={}", - g.is_unsafe - ))); - } - if (params, motives, minors, indices) - != (g.params, g.motives, g.minors, g.indices) - { - return Err(TcError::Other(format!( - "check_recursor: arity metadata mismatch: \ - stored=(params={params}, motives={motives}, minors={minors}, indices={indices}), \ - generated=(params={}, motives={}, minors={}, indices={})", - g.params, g.motives, g.minors, g.indices - ))); - } - if !self.is_def_eq(&g.ty, &ty)? { - // When `IX_TYPE_DIFF` is set, walk the binder chain to find the - // first divergent binder and print a readable gen/sto diff. Off - // by default: in alpha-collapse regimes or for mutual blocks - // with near-identical peers, every such mismatch ends up in - // `stt.ungrounded` (non-fatal), and printing them all drowns - // stderr under tens of thousands of lines. The walk only runs - // when the env var is set to keep the common path cheap. - // - // Uses `KExpr::Display` (Name.Pretty@shorthex for consts, - // `#idx` / `name` for vars, `(f a b …)` for spines, etc.) — - // the same formatter `TcError::AppTypeMismatch` uses — so the - // output format matches the rest of the kernel's diagnostic - // surface. - if *IX_TYPE_DIFF { - let mut gc = g.ty.clone(); - let mut sc = ty.clone(); - let mut bi = 0u64; - loop { - match (gc.data(), sc.data()) { - ( - ExprData::All(_, _, _, _, gd, gb, _), - ExprData::All(_, _, _, _, sd, sb, _), - ) => { - if !self.is_def_eq(gd, sd).unwrap_or(false) { - let label = if bi < params { - "param" - } else if bi < params + motives { - "motive" - } else if bi < params + motives + minors { - "minor" - } else { - "idx/major" - }; - eprintln!( - "[type diff] binder {bi} ({label}) DIFFERS (p={params} m={motives} min={minors})" - ); - eprintln!(" gen: {gd}"); - eprintln!(" sto: {sd}"); - break; - } - let _ = self.push_fvar_decl_anon(gd.clone()); - gc = gb.clone(); - sc = sb.clone(); - bi += 1; - }, - _ => { - eprintln!("[type diff] return differs at {bi}"); - eprintln!(" gen: {gc}"); - eprintln!(" sto: {sc}"); - break; - }, - } - } - for _ in 0..bi { - self.lctx.truncate(self.lctx.len() - 1); - } - } - return Err(TcError::Other("check_recursor: type mismatch".into())); - } - - let gen_rules = g.rules.clone(); - - // Compare rules. - // - // Correctness invariant: `check_recursor` accepts iff the stored - // rule list matches the canonical one produced by - // `generate_block_recursors` under the element-wise checks below - // (`fields` count + `rhs` defeq). The length-zero case is just a - // vacuous instance of agreement — `Empty.rec`, `False.rec`, - // `PEmpty.rec`, and similar empty inductives canonically have - // zero computation rules, Lean stores zero, and the generator - // produces zero. No extra guard is needed or correct here; an - // earlier guard `both_empty → error` spuriously rejected these, - // conflating "agreement at zero" with "generation failure." - // - // The one-sided `is_empty()` branches below remain as legitimate - // asymmetric mismatches (e.g., generator produced N rules but - // storage has none, or vice versa). - if gen_rules.is_empty() && !stored_rules.is_empty() { - // C1: Generator produced no canonical rules but Lean stored - // some — we cannot verify the stored rules against a missing - // canonical form. MUST NOT accept. - return Err(TcError::Other(format!( - "check_recursor: rule generation failed for {}, cannot verify {} stored rules", - &ind_id.addr.hex()[..8], - stored_rules.len() - ))); - } else if !gen_rules.is_empty() && stored_rules.is_empty() { - // Dual of C1: generator produced N canonical rules but Lean - // stored none. Also a real mismatch. - return Err(TcError::Other(format!( - "check_recursor: stored recursor has no rules (expected {})", - gen_rules.len() - ))); - } else if gen_rules.len() != stored_rules.len() { - return Err(TcError::Other(format!( - "check_recursor: rule count mismatch: gen={} stored={}", - gen_rules.len(), - stored_rules.len() - ))); - } - // Element-wise comparison. Vacuous when both sides are empty - // (zero-constructor inductives), which is the agreement case. - for (ri, (gen_rule, stored_rule)) in - gen_rules.iter().zip(stored_rules.iter()).enumerate() - { - if gen_rule.fields != stored_rule.fields { - return Err(TcError::Other(format!( - "check_recursor: rule {ri} field count mismatch: gen={} stored={}", - gen_rule.fields, stored_rule.fields - ))); - } - if !self.is_def_eq(&gen_rule.rhs, &stored_rule.rhs)? { - if *IX_TYPE_DIFF { - let _ = self.dump_rule_rhs_first_diff( - &gen_rule.rhs, - &stored_rule.rhs, - "rhs", - 0, - ); - eprintln!( - "[rule rhs diff] rule {ri} RHS mismatch (fields={})", - gen_rule.fields - ); - eprintln!(" gen: {}", gen_rule.rhs); - eprintln!(" sto: {}", stored_rule.rhs); - } - return Err(TcError::Other(format!( - "check_recursor: rule {ri} RHS mismatch" - ))); - } - } - Ok(()) - }, - None => { - // C2: No generated recursor found — MUST NOT silently pass. - // If we can't generate a canonical recursor, we can't verify the provided one. - Err(TcError::Other(format!( - "check_recursor: no generated recursor for major {}", - &ind_id.addr.hex()[..8] - ))) - }, - } - } - - /// Gather the set of major inductive KIds from all peer recursors in a - /// recursor block. Used to look up the rec_majors_cache. - fn gather_peer_majors( - &mut self, - rec_block: &KId, - ) -> Result>, TcError> { - let mut majors = std::collections::BTreeSet::new(); - - let peers: Vec> = match self.try_get_block(rec_block)? { - Some(members) => { - let mut peers = Vec::new(); - for id in members { - if matches!(self.try_get_const(&id)?, Some(KConst::Recr { .. })) { - peers.push(id); - } - } - peers - }, - None => vec![], - }; - - for peer_id in &peers { - let (p, mo, mi, ix, peer_ty) = match self.get_const(peer_id)? { - KConst::Recr { params, motives, minors, indices, ty, .. } => { - (params, motives, minors, indices, ty.clone()) - }, - _ => continue, - }; - let skip = - checked_metadata_sum::("recursor major index", &[p, mo, mi, ix])?; - match self.get_major_inductive_id(&peer_ty, skip) { - Ok(major_id) => { - majors.insert(major_id); - }, - Err(TcError::UnknownConst(addr)) => { - return Err(TcError::UnknownConst(addr)); - }, - Err(_) => {}, - } - } - - Ok(majors) - } - - /// S1: Compute K-target flag constructively. - /// K-like reduction is sound iff: - /// 1. Single inductive (not part of a mutual block with >1 inductive) - /// 2. Result universe is Prop (level is zero) - /// 3. Exactly one constructor with zero non-param fields - fn compute_k_target(&mut self, ind_id: &KId) -> Result> { - let (ind_params, ind_indices, ctors, block, ty) = - match self.try_get_const(ind_id)? { - Some(KConst::Indc { params, indices, ctors, block, ty, .. }) => { - (params, indices, ctors.clone(), block.clone(), ty.clone()) - }, - _ => return Ok(false), - }; - - // 1. Must be a single inductive (not mutual) - let block_inds = self.discover_block_inductives(&block)?; - let mut ind_count = 0; - for id in &block_inds { - if matches!(self.try_get_const(id)?, Some(KConst::Indc { .. })) { - ind_count += 1; - } - } - if ind_count != 1 { - return Ok(false); - } - - // 2. Result level must be Prop (semantically zero). - // Use univ_eq instead of is_zero() to handle levels like max(0,0) or imax(0,u) - // that are semantically zero but not syntactically UnivData::Zero. - let ind_arity = checked_metadata_sum::( - "inductive params + indices", - &[ind_params, ind_indices], - )?; - let result_level = - self.get_result_sort_level(&ty, u64_to_usize(ind_arity)?)?; - if !univ_eq(&result_level, &KUniv::zero()) { - return Ok(false); - } - - // 3. Exactly one constructor with zero non-param fields - if ctors.len() != 1 { - return Ok(false); - } - match self.try_get_const(&ctors[0])? { - Some(KConst::Ctor { fields, .. }) => Ok(fields == 0), - _ => Ok(false), - } - } -} - -#[cfg(test)] -mod tests { - - use super::super::constant::KConst; - use super::super::env::KEnv; - use super::super::error::TcError; - use super::super::expr::{ExprData, KExpr}; - use super::super::id::KId; - use super::super::level::{KUniv, univ_eq}; - use super::super::mode::Anon; - use super::super::tc::TypeChecker; - use ix_common::address::Address; - - type AE = KExpr; - type AU = KUniv; - - fn mk_addr(s: &str) -> Address { - Address::hash(s.as_bytes()) - } - fn mk_id(s: &str) -> KId { - KId::new(mk_addr(s), ()) - } - fn _sort0() -> AE { - AE::sort(AU::zero()) - } - fn sort1() -> AE { - AE::sort(AU::succ(AU::zero())) - } - fn param(n: u64) -> AU { - AU::param(n, ()) - } - - /// Helper: build `∀ (_ : a), b` - fn pi(a: AE, b: AE) -> AE { - AE::all((), (), a, b) - } - - /// Helper: build `App(f, a)` - fn app(f: AE, a: AE) -> AE { - AE::app(f, a) - } - - /// Helper: build `λ (_ : a), b` - fn lam(a: AE, b: AE) -> AE { - AE::lam((), (), a, b) - } - - /// Helper: build `Const(name, univs)` - fn cnst(name: &str, us: &[AU]) -> AE { - AE::cnst(mk_id(name), us.to_vec().into_boxed_slice()) - } - - fn var(i: u64) -> AE { - AE::var(i, ()) - } - - /// Build an env with Bool (2 ctors, 0 fields each) and its recursor. - /// Bool : Sort 1 - /// Bool.true : Bool - /// Bool.false : Bool - /// Bool.rec : ∀ (motive : Bool → Sort u) (h₁ : motive Bool.true) (h₂ : motive Bool.false) (t : Bool), motive t - fn bool_env() -> KEnv { - let mut env = KEnv::new(); - let block = mk_id("Bool"); - let rec_block = mk_id("Bool.rec.block"); - - // Bool : Sort 1 - env.insert( - mk_id("Bool"), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![mk_id("Bool.true"), mk_id("Bool.false")], - lean_all: (), - }, - ); - // Bool.true : Bool - env.insert( - mk_id("Bool.true"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Bool"), - cidx: 0, - params: 0, - fields: 0, - ty: cnst("Bool", &[]), - }, - ); - // Bool.false : Bool - env.insert( - mk_id("Bool.false"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Bool"), - cidx: 1, - params: 0, - fields: 0, - ty: cnst("Bool", &[]), - }, - ); - - // Bool.rec type: ∀ (motive : Bool → Sort u) (h₁ : motive true) (h₂ : motive false) (t : Bool), motive t - let motive_ty = pi(cnst("Bool", &[]), AE::sort(param(0))); - let minor_true = app(var(0), cnst("Bool.true", &[])); - let minor_false = app(var(1), cnst("Bool.false", &[])); - let major_ty = cnst("Bool", &[]); - let ret = app(var(3), var(0)); - let rec_ty = pi( - motive_ty.clone(), - pi(minor_true.clone(), pi(minor_false.clone(), pi(major_ty, ret))), - ); - - // Bool.rec rules — use actual domain types from recursor type - let motive_dom = motive_ty; - let h_true_dom = minor_true; - let h_false_dom = minor_false; - // Rule 0 (Bool.true, 0 fields): λ (motive) (h_true) (h_false), h_true - let rule_true_rhs = lam( - motive_dom.clone(), - lam(h_true_dom.clone(), lam(h_false_dom.clone(), var(1))), - ); - // Rule 1 (Bool.false, 0 fields): λ (motive) (h_true) (h_false), h_false - let rule_false_rhs = - lam(motive_dom, lam(h_true_dom, lam(h_false_dom, var(0)))); - - env.insert( - mk_id("Bool.rec"), - KConst::Recr { - name: (), - level_params: (), - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 2, - block: rec_block.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![ - super::super::constant::RecRule { - ctor: (), - fields: 0, - rhs: rule_true_rhs, - }, - super::super::constant::RecRule { - ctor: (), - fields: 0, - rhs: rule_false_rhs, - }, - ], - lean_all: (), - }, - ); - - env.blocks.insert( - block, - vec![mk_id("Bool"), mk_id("Bool.true"), mk_id("Bool.false")], - ); - env.blocks.insert(rec_block, vec![mk_id("Bool.rec")]); - env - } - - #[test] - fn check_bool_inductive() { - let mut env = bool_env(); - let mut tc = TypeChecker::new(&mut env); - assert!(tc.check_const(&mk_id("Bool")).is_ok()); - } - - #[test] - fn check_inductive_rejects_ctor_param_count_mismatch() { - let mut env = bool_env(); - env.insert( - mk_id("Bool.true"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Bool"), - cidx: 0, - params: 1, - fields: 0, - ty: cnst("Bool", &[]), - }, - ); - - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&mk_id("Bool")) { - Err(TcError::Other(s)) => assert!(s.contains("ctor params mismatch")), - other => panic!("expected ctor params mismatch, got {other:?}"), - } - } - - fn replace_bool_true( - env: &mut KEnv, - is_unsafe: bool, - lvls: u64, - induct: KId, - cidx: u64, - params: u64, - fields: u64, - ) { - env.insert( - mk_id("Bool.true"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe, - lvls, - induct, - cidx, - params, - fields, - ty: cnst("Bool", &[]), - }, - ); - } - - fn replace_bool_rec_header( - env: &mut KEnv, - is_unsafe: bool, - lvls: u64, - params: u64, - motives: u64, - minors: u64, - indices: u64, - ) { - let rec_id = mk_id("Bool.rec"); - let KConst::Recr { - name, - level_params, - k, - block, - member_idx, - ty, - rules, - lean_all, - .. - } = env.get(&rec_id).expect("Bool.rec fixture") - else { - panic!("Bool.rec fixture is not a recursor"); - }; - env.insert( - rec_id, - KConst::Recr { - name, - level_params, - k, - is_unsafe, - lvls, - params, - indices, - motives, - minors, - block, - member_idx, - ty, - rules, - lean_all, - }, - ); - } - - /// Adversarial metadata regression: when block coordination is unavailable, - /// the standalone constructor path must still enforce the parent's parameter - /// count. `check_param_agreement` alone cannot see this redundant field. - #[test] - fn reject_standalone_ctor_param_count_mismatch() { - let mut env = bool_env(); - env.blocks.remove(&mk_id("Bool")); - replace_bool_true(&mut env, false, 0, mk_id("Bool"), 0, 1, 0); - - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&mk_id("Bool.true")) { - Err(TcError::Other(s)) => assert!(s.contains("ctor params mismatch")), - other => panic!("expected ctor params mismatch, got {other:?}"), - } - } - - /// `cidx` selects the recursor rule during iota reduction, so a constructor - /// checked outside its coordinated block must be tied to its exact position - /// in the parent's constructor array. - #[test] - fn reject_standalone_ctor_cidx_mismatch() { - let mut env = bool_env(); - env.blocks.remove(&mk_id("Bool")); - replace_bool_true(&mut env, false, 0, mk_id("Bool"), 1, 0, 0); - - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&mk_id("Bool.true")) { - Err(TcError::Other(s)) => assert!(s.contains("ctor cidx mismatch")), - other => panic!("expected ctor cidx mismatch, got {other:?}"), - } - } - - /// Constructor universe parameters are inherited from the inductive in - /// Lean's declaration transaction; unused surplus parameters must not make - /// an otherwise malformed standalone constructor admissible. - #[test] - fn reject_standalone_ctor_universe_arity_mismatch() { - let mut env = bool_env(); - env.blocks.remove(&mk_id("Bool")); - replace_bool_true(&mut env, false, 1, mk_id("Bool"), 0, 0, 0); - - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&mk_id("Bool.true")) { - Err(TcError::Other(s)) => { - assert!(s.contains("ctor universe arity mismatch")); - }, - other => panic!("expected ctor universe arity mismatch, got {other:?}"), - } - } - - /// A physical block must not smuggle in an unlisted constructor. Otherwise - /// its `cidx` is unconstrained by the parent's canonical constructor order. - #[test] - fn reject_unlisted_ctor_in_inductive_block() { - let mut env = bool_env(); - env.insert( - mk_id("Bool"), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: mk_id("Bool"), - member_idx: 0, - ty: sort1(), - ctors: vec![], - lean_all: (), - }, - ); - - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&mk_id("Bool")) { - Err(TcError::Other(s)) => assert!(s.contains("not listed by parent")), - other => panic!("expected unlisted constructor rejection, got {other:?}"), - } - } - - /// Constructor safety is inherited from the inductive. Allowing a safe - /// constructor for an unsafe inductive would let the definition-safety walk - /// miss the unsafe family hidden behind that constructor reference. - #[test] - fn reject_ctor_safety_mismatch() { - let mut env = bool_env(); - replace_bool_true(&mut env, true, 0, mk_id("Bool"), 0, 0, 0); - - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&mk_id("Bool")) { - Err(TcError::Other(s)) => assert!(s.contains("ctor safety mismatch")), - other => panic!("expected ctor safety mismatch, got {other:?}"), - } - } - - /// Negative control for the numeric-field audit: `fields` was already - /// checked exactly by peeling `params + fields` binders before requiring the - /// manifest inductive return head. - #[test] - fn reject_ctor_field_count_mismatch() { - let mut env = nat_env(); - env.blocks.remove(&mk_id("Nat")); - env.insert( - mk_id("Nat.succ"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Nat"), - cidx: 1, - params: 0, - fields: 0, - ty: pi(cnst("Nat", &[]), cnst("Nat", &[])), - }, - ); - - let mut tc = TypeChecker::new(&mut env); - assert!( - tc.check_const(&mk_id("Nat.succ")).is_err(), - "constructor with a false field count must be rejected" - ); - } - - /// Inductive telescope metadata is summed before it is used as a binder - /// count. The sum must be mathematical, not wrapping `u64` arithmetic. - #[test] - fn reject_inductive_arity_sum_overflow() { - let mut env = KEnv::new(); - let id = mk_id("OverflowInd"); - let block = mk_id("OverflowInd.block"); - env.insert( - id.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: u64::MAX, - indices: 1, - is_unsafe: false, - block: block.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![], - lean_all: (), - }, - ); - env.blocks.insert(block, vec![id.clone()]); - - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&id) { - Err(TcError::Other(s)) => { - assert!(s.contains("inductive params + indices metadata sum overflow")); - }, - other => panic!("expected inductive arity overflow, got {other:?}"), - } - } - - /// Large elimination adds one universe parameter to the inductive's own - /// level arity. That derived count is part of canonical recursor metadata - /// and must not wrap back to zero. - #[test] - fn reject_generated_recursor_universe_arity_overflow() { - let mut env = KEnv::new(); - let id = mk_id("OverflowRecLevels"); - env.insert( - id.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: u64::MAX, - params: 0, - indices: 0, - is_unsafe: false, - block: id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![], - lean_all: (), - }, - ); - env.blocks.insert(id.clone(), vec![id.clone()]); - - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&id) { - Err(TcError::Other(s)) => { - assert!( - s.contains("generated recursor universe arity metadata sum overflow") - ); - }, - other => { - panic!("expected generated recursor level overflow, got {other:?}") - }, - } - } - - /// The recursor major index is the sum of four serialized arities. This - /// gate is the executable no-wrap fact consumed by ordinary iota proofs. - #[test] - fn reject_recursor_major_index_overflow() { - let mut env = bool_env(); - let rec_id = mk_id("Bool.rec"); - replace_bool_rec_header(&mut env, false, 1, u64::MAX, 1, 0, 0); - - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&rec_id) { - Err(TcError::Other(s)) => { - assert!(s.contains("recursor major index metadata sum overflow")); - }, - other => panic!("expected recursor major-index overflow, got {other:?}"), - } - } - - #[test] - fn reject_recursor_universe_arity_mismatch() { - let mut env = bool_env(); - replace_bool_rec_header(&mut env, false, 2, 0, 1, 2, 0); - - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&mk_id("Bool.rec")) { - Err(TcError::Other(s)) => { - assert!( - s.contains( - "populate_recursor_rules_from_block: canonical header mismatch" - ), - "unexpected rejection: {s}" - ); - }, - other => { - panic!("expected recursor universe arity mismatch, got {other:?}") - }, - } - } - - #[test] - fn reject_recursor_safety_mismatch() { - let mut env = bool_env(); - replace_bool_rec_header(&mut env, true, 1, 0, 1, 2, 0); - - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&mk_id("Bool.rec")) { - Err(TcError::Other(s)) => { - assert!( - s.contains( - "populate_recursor_rules_from_block: canonical header mismatch" - ), - "unexpected rejection: {s}" - ); - }, - other => panic!("expected recursor safety mismatch, got {other:?}"), - } - } - - #[test] - fn check_bool_constructor_uses_parent_block() { - let mut env = bool_env(); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("Bool.true")).unwrap(); - assert!( - env.block_check_results.get(&mk_id("Bool")).is_some_and(|r| r.is_ok()) - ); - } - - #[test] - fn check_bool_rec() { - let mut env = bool_env(); - let mut tc = TypeChecker::new(&mut env); - // Must check inductive first to trigger recursor generation - tc.check_const(&mk_id("Bool")).unwrap(); - assert!(tc.check_const(&mk_id("Bool.rec")).is_ok(), "Bool.rec should pass"); - } - - /// Build env with Nat (1 recursive ctor) and its recursor. - /// Nat : Sort 1 - /// Nat.zero : Nat - /// Nat.succ : Nat → Nat - /// Nat.rec : ∀ (motive : Nat → Sort u) (zero : motive Nat.zero) - /// (succ : ∀ (n : Nat), motive n → motive (Nat.succ n)) - /// (t : Nat), motive t - fn nat_env() -> KEnv { - let mut env = KEnv::new(); - let block = mk_id("Nat"); - let rec_block = mk_id("Nat.rec.block"); - let nat = || cnst("Nat", &[]); - - env.insert( - mk_id("Nat"), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![mk_id("Nat.zero"), mk_id("Nat.succ")], - lean_all: (), - }, - ); - env.insert( - mk_id("Nat.zero"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Nat"), - cidx: 0, - params: 0, - fields: 0, - ty: nat(), - }, - ); - env.insert( - mk_id("Nat.succ"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Nat"), - cidx: 1, - params: 0, - fields: 1, - ty: pi(nat(), nat()), - }, - ); - - // Nat.rec type - let motive_ty = pi(nat(), AE::sort(param(0))); - // minor_zero: motive Nat.zero (motive is Var(0) here) - let minor_zero = app(var(0), cnst("Nat.zero", &[])); - // minor_succ: ∀ (n : Nat) (ih : motive n), motive (Nat.succ n) - // motive is Var(2) inside the two binders - let minor_succ = pi( - nat(), - pi(app(var(2), var(0)), app(var(3), app(cnst("Nat.succ", &[]), var(1)))), - ); - let major = nat(); - let ret = app(var(3), var(0)); - let rec_ty = pi( - motive_ty.clone(), - pi(minor_zero.clone(), pi(minor_succ.clone(), pi(major, ret))), - ); - - // Nat.rec rules — use actual domain types from recursor type - let motive_dom = motive_ty; - let h_zero_dom = minor_zero; - let h_succ_dom = minor_succ; - let rule_zero_rhs = lam( - motive_dom.clone(), - lam(h_zero_dom.clone(), lam(h_succ_dom.clone(), var(1))), - ); - // Rule 1 (Nat.succ, 1 field): λ (motive) (h_zero) (h_succ) (n), h_succ n (Nat.rec motive h_zero h_succ n) - // Under 4 lambdas: motive=Var(3), h_zero=Var(2), h_succ=Var(1), n=Var(0) - let nat_rec = cnst("Nat.rec", &[param(0)]); - let ih = app(app(app(app(nat_rec, var(3)), var(2)), var(1)), var(0)); - let rule_succ_rhs = lam( - motive_dom, - lam( - h_zero_dom, - lam(h_succ_dom, lam(nat(), app(app(var(1), var(0)), ih))), - ), - ); - - env.insert( - mk_id("Nat.rec"), - KConst::Recr { - name: (), - level_params: (), - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 2, - block: rec_block.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![ - super::super::constant::RecRule { - ctor: (), - fields: 0, - rhs: rule_zero_rhs, - }, - super::super::constant::RecRule { - ctor: (), - fields: 1, - rhs: rule_succ_rhs, - }, - ], - lean_all: (), - }, - ); - - env - .blocks - .insert(block, vec![mk_id("Nat"), mk_id("Nat.zero"), mk_id("Nat.succ")]); - env.blocks.insert(rec_block, vec![mk_id("Nat.rec")]); - env - } - - #[test] - fn check_nat_rec() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("Nat")).unwrap(); - assert!(tc.check_const(&mk_id("Nat.rec")).is_ok(), "Nat.rec should pass"); - } - - #[test] - fn nat_rec_rules() { - // Nat.rec has 2 rules (one per ctor): - // Rule 0 (Nat.zero): fields=0, rhs = λ (motive) (h_zero) (h_succ), h_zero - // Rule 1 (Nat.succ): fields=1, rhs = λ (motive) (h_zero) (h_succ) (n), - // h_succ n (Nat.rec.{Param(0), ...} motive h_zero h_succ n) - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("Nat")).unwrap(); - tc.check_const(&mk_id("Nat.rec")).unwrap(); - - let block = mk_id("Nat"); - let generated = tc.env.recursor_cache.get(&block).unwrap(); - let rules = &generated[0].rules; - - assert_eq!(rules.len(), 2, "Nat.rec should have 2 rules"); - - // Rule 0 (zero): fields=0 - assert_eq!(rules[0].fields, 0); - // rhs = λ (motive) (h_zero) (h_succ), h_zero - // = Lam(_, Lam(_, Lam(_, Var(1)))) - // Var(1) = h_zero (2nd from top: Var(0)=h_succ, Var(1)=h_zero) - let _expected_zero = lam( - pi(cnst("Nat", &[]), AE::sort(param(0))), // motive type (placeholder domain) - lam( - app(var(0), cnst("Nat.zero", &[])), // h_zero type (placeholder) - lam( - KExpr::sort(KUniv::zero()), // h_succ type (placeholder, won't be checked structurally) - var(1), // h_zero - ), - ), - ); - // Just check the BODY structure — the lambda domains don't matter for iota, - // only the body does. Let's check fields and that the rule is well-formed. - // For now, just verify the rule exists and has the right field count. - - // Rule 1 (succ): fields=1 - assert_eq!(rules[1].fields, 1); - // rhs body (after applying 3 pmm + 1 field = 4 lambdas): - // h_succ n (Nat.rec motive h_zero h_succ n) - // Check the rhs has the right lambda count - let count_lams = |e: &AE| -> usize { - let mut n = 0; - let mut c = e.clone(); - while let ExprData::Lam(_, _, _, _, body, _) = c.data() { - n += 1; - c = body.clone(); - } - n - }; - // pmm = 0 params + 1 motive + 2 minors = 3, plus 1 field = 4 lambdas - let n_lams = count_lams(&rules[1].rhs); - assert_eq!( - n_lams, 4, - "Nat.succ rule should have 4 lambdas (0p + 1m + 2min + 1f), got {n_lams}" - ); - } - - /// Build env with List (1 param, 2 ctors including recursive cons). - /// List.{u} : Sort u → Sort u - /// List.nil.{u} : ∀ (α : Sort u), List.{u} α - /// List.cons.{u} : ∀ (α : Sort u), α → List.{u} α → List.{u} α - fn list_env() -> KEnv { - let mut env = KEnv::new(); - let block = mk_id("List"); - - // List : Sort u → Sort u (1 lvl param) - let list_ty = pi(AE::sort(param(0)), AE::sort(param(0))); - env.insert( - mk_id("List"), - KConst::Indc { - name: (), - level_params: (), - lvls: 1, - params: 1, - indices: 0, - is_unsafe: false, - block: block.clone(), - member_idx: 0, - ty: list_ty, - ctors: vec![mk_id("List.nil"), mk_id("List.cons")], - lean_all: (), - }, - ); - - // List.nil : ∀ (α : Sort u), List α - let list_a = app(cnst("List", &[param(0)]), var(0)); // List.{u} α - let nil_ty = pi(AE::sort(param(0)), list_a.clone()); - env.insert( - mk_id("List.nil"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("List"), - cidx: 0, - params: 1, - fields: 0, - ty: nil_ty, - }, - ); - - // List.cons : ∀ (α : Sort u) (head : α) (tail : List α), List α - let cons_ty = pi( - AE::sort(param(0)), // α - pi( - var(0), // head : α - pi( - app(cnst("List", &[param(0)]), var(1)), // tail : List α - app(cnst("List", &[param(0)]), var(2)), // List α - ), - ), - ); - env.insert( - mk_id("List.cons"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("List"), - cidx: 1, - params: 1, - fields: 2, - ty: cons_ty, - }, - ); - - // List.rec type (large eliminator): - // ∀ {α : Sort(Param(1))} (motive : List.{Param(1)} α → Sort(Param(0))) - // (nil : motive (List.nil.{Param(1)} α)) - // (cons : ∀ (head : α) (tail : List.{Param(1)} α), motive tail → motive (List.cons.{Param(1)} α head tail)) - // (t : List.{Param(1)} α), motive t - let u1 = param(1); // shifted inductive univ - let u0 = param(0); // elim univ - let _list_u1_a = app(cnst("List", std::slice::from_ref(&u1)), var(0)); // List.{u1} α, where α=Var(0) - - let _motive_ty = pi( - // inside: α is Var(1) from one binder out - app(cnst("List", std::slice::from_ref(&u1)), var(0)), - AE::sort(u0.clone()), - ); - // under α, motive: motive_is_Var(0) - let _minor_nil = - app(var(0), app(cnst("List.nil", std::slice::from_ref(&u1)), var(1))); - // cons minor: ∀ (head : α) (tail : List α) (ih : motive tail), motive (cons α head tail) - let _cons_minor = pi( - var(1), // head : α (α is Var(1) since motive+nil already bound... wait) - // This is getting complicated with de Bruijn. Let me simplify. - // Actually for the test we just need to check that check_const passes. - // Let me construct the rec_ty by hand more carefully. - // Actually, let's just check that the inductive passes and the generated - // recursor type has the right binder count. - KExpr::sort(KUniv::zero()), // placeholder - we'll verify structurally - ); - - // For now, let's just test that check_inductive works and generates a recursor. - // We'll compare binder counts instead of full def-eq. - // Skip the recursor constant for now. - - env.blocks.insert( - block, - vec![mk_id("List"), mk_id("List.nil"), mk_id("List.cons")], - ); - env - } - - #[test] - fn check_list_inductive() { - let mut env = list_env(); - let mut tc = TypeChecker::new(&mut env); - assert!(tc.check_const(&mk_id("List")).is_ok()); - // Verify recursor was generated with the right structure - let block = mk_id("List"); - let generated = - tc.env.recursor_cache.get(&block).expect("recursor should be cached"); - assert_eq!(generated.len(), 1, "should generate 1 recursor for List"); - assert_eq!(generated[0].ind_addr, mk_addr("List")); - - // Count binders in generated rec type - let mut n = 0; - let mut cur = generated[0].ty.clone(); - while let ExprData::All(_, _, _, _, _, body, _) = cur.data() { - n += 1; - cur = body.clone(); - } - // List.rec should have: 1 param + 1 motive + 2 minors + 0 indices + 1 major = 5 binders - assert_eq!(n, 5, "List.rec should have 5 binders"); - } - - /// Build env with a nested inductive: Tree with a field `List Tree`. - /// Tree : Sort 1 - /// Tree.leaf : Tree - /// Tree.node : List Tree → Tree - /// This should create a flat block [Tree, List] with Tree nesting into List. - fn nested_tree_env() -> KEnv { - let mut env = KEnv::new(); - let tree_block = mk_id("Tree"); - let tree = || cnst("Tree", &[]); - - // Tree : Sort 1 - env.insert( - mk_id("Tree"), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: tree_block.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![mk_id("Tree.leaf"), mk_id("Tree.node")], - lean_all: (), - }, - ); - env.insert( - mk_id("Tree.leaf"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Tree"), - cidx: 0, - params: 0, - fields: 0, - ty: tree(), - }, - ); - // Tree.node : List Tree → Tree - // List.{1} Tree → Tree (List at universe 1 since Tree : Sort 1) - let list_tree = app(cnst("List", &[AU::succ(AU::zero())]), tree()); - env.insert( - mk_id("Tree.node"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Tree"), - cidx: 1, - params: 0, - fields: 1, - ty: pi(list_tree, tree()), - }, - ); - - // We also need List in the environment for the nested detection to work. - let list_ty = pi(AE::sort(param(0)), AE::sort(param(0))); - env.insert( - mk_id("List"), - KConst::Indc { - name: (), - level_params: (), - lvls: 1, - params: 1, - indices: 0, - is_unsafe: false, - block: mk_id("List"), - member_idx: 0, - ty: list_ty, - ctors: vec![mk_id("List.nil"), mk_id("List.cons")], - lean_all: (), - }, - ); - - // List.nil : ∀ (α : Sort u), List α - let nil_ty = pi(AE::sort(param(0)), app(cnst("List", &[param(0)]), var(0))); - env.insert( - mk_id("List.nil"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("List"), - cidx: 0, - params: 1, - fields: 0, - ty: nil_ty, - }, - ); - - // List.cons : ∀ (α : Sort u) (head : α) (tail : List α), List α - let cons_ty = pi( - AE::sort(param(0)), - pi( - var(0), - pi( - app(cnst("List", &[param(0)]), var(1)), - app(cnst("List", &[param(0)]), var(2)), - ), - ), - ); - env.insert( - mk_id("List.cons"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("List"), - cidx: 1, - params: 1, - fields: 2, - ty: cons_ty, - }, - ); - - env.blocks.insert( - tree_block, - vec![mk_id("Tree"), mk_id("Tree.leaf"), mk_id("Tree.node")], - ); - env.blocks.insert( - mk_id("List"), - vec![mk_id("List"), mk_id("List.nil"), mk_id("List.cons")], - ); - env - } - - /// `Box (p : Sort l) : Sort l | mk : p -> Box p`, - /// `Nested : Sort l | mk : Box Nested -> Nested`. - /// At l=0 the ordinary Box has a large eliminator, while Nested's - /// expanded two-member Prop block must have only a small eliminator. - fn nested_singleton_env(is_prop: bool, polymorphic: bool) -> KEnv { - let mut env = KEnv::new(); - let sort = if is_prop { _sort0() } else { sort1() }; - let lvls = u64::from(polymorphic); - let us = if polymorphic { vec![param(0)] } else { vec![] }; - for (name, params, ty, ctor_ty) in [ - ( - "Box", - 1, - pi(sort.clone(), sort.clone()), - pi(sort.clone(), pi(var(0), app(cnst("Box", &us), var(1)))), - ), - ( - "Nested", - 0, - sort, - pi(app(cnst("Box", &us), cnst("Nested", &us)), cnst("Nested", &us)), - ), - ] { - let ctor = format!("{name}.mk"); - env.insert( - mk_id(name), - KConst::Indc { - name: (), - level_params: (), - lvls, - params, - indices: 0, - is_unsafe: false, - block: mk_id(name), - member_idx: 0, - ty, - ctors: vec![mk_id(&ctor)], - lean_all: (), - }, - ); - env.insert( - mk_id(&ctor), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls, - induct: mk_id(name), - cidx: 0, - params, - fields: 1, - ty: ctor_ty, - }, - ); - env.blocks.insert(mk_id(name), vec![mk_id(name), mk_id(&ctor)]); - } - env - } - - #[test] - fn nested_prop_recursors_have_small_elimination() { - let mut env = nested_singleton_env(true, false); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("Nested")).unwrap(); - let generated = tc.env.recursor_cache.get(&mk_id("Nested")).unwrap(); - assert_eq!(generated.len(), 2); - for rec in generated { - assert_eq!(rec.lvls, 0); - assert_eq!(rec.motives, 2); - assert_eq!(rec.minors, 2); - } - } - - #[test] - fn nested_prop_repair_keeps_ordinary_prop_and_nested_type_large() { - for (is_prop, name, motives) in [(true, "Box", 1), (false, "Nested", 2)] { - let mut env = nested_singleton_env(is_prop, false); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id(name)).unwrap(); - let generated = tc.env.recursor_cache.get(&mk_id(name)).unwrap(); - assert_eq!(generated.len(), motives); - for rec in generated { - assert_eq!(rec.lvls, 1); - } - } - } - - #[test] - fn nested_prop_rejects_a_forged_large_recursor_header() { - for (forged, polymorphic) in - [(false, false), (true, false), (false, true), (true, true)] - { - let mut env = nested_singleton_env(true, polymorphic); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("Nested")).unwrap(); - let generated = - tc.env.recursor_cache.get(&mk_id("Nested")).unwrap().clone(); - let rec_block = mk_id("Nested.rec.block"); - let rec_ids = vec![mk_id("Nested.rec"), mk_id("Nested.rec_1")]; - for (i, (id, rec)) in rec_ids.iter().zip(&generated).enumerate() { - tc.env.insert( - id.clone(), - KConst::Recr { - name: (), - level_params: (), - k: false, - is_unsafe: false, - lvls: u64::from(polymorphic) + u64::from(forged && i == 0), - params: rec.params, - indices: rec.indices, - motives: rec.motives, - minors: rec.minors, - block: rec_block.clone(), - member_idx: i as u64, - ty: rec.ty.clone(), - rules: vec![], - lean_all: (), - }, - ); - } - tc.env.blocks.insert(rec_block.clone(), rec_ids); - let result = tc.populate_recursor_rules_from_block_core( - &mk_id("Nested"), - &rec_block, - &generated, - ); - if forged { - assert!( - matches!(result, Err(TcError::Other(ref message)) - if message.contains("canonical header mismatch")), - "{result:?}" - ); - } else { - let populated = result.expect("valid small header"); - for (id, rec) in - [mk_id("Nested.rec"), mk_id("Nested.rec_1")].iter().zip(populated) - { - let Some(KConst::Recr { rules, .. }) = tc.env.consts.get_mut(id) - else { - panic!("missing stored recursor"); - }; - *rules = rec.rules; - } - // Check complete stored recursors too: the polymorphic neighbour - // catches offset-one universe terms left behind after small - // elimination retracts the extra universe parameter. - tc.check_const(&mk_id("Nested.rec")).unwrap(); - tc.check_const(&mk_id("Nested.rec_1")).unwrap(); - } - } - } - - #[test] - fn nested_tree_flat_block_detection() { - let mut env = nested_tree_env(); - let mut tc = TypeChecker::new(&mut env); - - // Check Tree inductive — this triggers flat block building - tc.check_const(&mk_id("Tree")).unwrap(); - - let tree_block = mk_id("Tree"); - let generated = tc - .env - .recursor_cache - .get(&tree_block) - .expect("recursor should be cached for Tree"); - - // Flat block should have 2 members: Tree + List auxiliary - assert_eq!( - generated.len(), - 2, - "flat block should produce 2 recursors (Tree + List aux)" - ); - assert_eq!(generated[0].ind_addr, mk_addr("Tree")); - assert_eq!(generated[1].ind_addr, mk_addr("List")); - } - - fn discover_test_syntax( - expr: &AE, - params: u64, - ) -> Vec> { - let mut env = nested_tree_env(); - let mut tc = TypeChecker::new(&mut env); - let mut flat = vec![]; - let mut seen = vec![]; - tc.try_detect_nested( - expr, - &[mk_addr("Tree")], - &mut flat, - &mut seen, - 1, - 0, - params, - ) - .unwrap(); - assert!( - tc.lctx.is_empty(), - "syntax scope markers must not escape into the typing context" - ); - flat - } - - #[test] - fn nested_syntax_visits_under_non_inductive_heads_and_binders() { - let occurrence = - app(cnst("List", &[AU::succ(AU::zero())]), cnst("Tree", &[])); - let wrapped = app(cnst("NonInductiveWrapper", &[]), occurrence); - let flat = discover_test_syntax(&pi(sort1(), wrapped), 0); - assert_eq!(flat.len(), 1); - assert_eq!(flat[0].id, mk_id("List")); - assert!( - matches!(flat[0].spec_params[0].data(), ExprData::Const(id, levels, _) - if *id == mk_id("Tree") && levels.is_empty()) - ); - } - - #[test] - fn nested_syntax_visits_all_arguments_of_a_wide_application() { - let nested = - || app(cnst("List", &[AU::succ(AU::zero())]), cnst("Tree", &[])); - // One occurrence at the start of the spine, another under a binder in - // the middle, and another in the final argument. None may disappear - // when the non-inductive function prefixes are skipped. - for position in [0, 1024, 2047] { - let mut e = cnst("NonInductiveWrapper", &[]); - for i in 0..2048 { - let argument = - if i == position { pi(sort1(), nested()) } else { sort1() }; - e = app(e, argument); - } - let flat = discover_test_syntax(&e, 0); - assert_eq!(flat.len(), 1); - assert_eq!(flat[0].id, mk_id("List")); - } - } - - #[test] - fn nested_syntax_visit_keys_distinguish_binder_depth() { - // This is a direct S7 scope control. Inside the binder Var(0) is a - // field-local marker; outside it is the allowed block parameter. - let parameter = app(cnst("Tree", &[]), var(0)); - let occurrence = - app(cnst("List", &[AU::succ(AU::zero())]), parameter.clone()); - assert!( - discover_test_syntax(&pi(sort1(), occurrence.clone()), 1).is_empty() - ); - let two_scopes = app(pi(sort1(), occurrence.clone()), occurrence); - let flat = discover_test_syntax(&two_scopes, 1); - assert_eq!( - flat.len(), - 1, - "the rejected local occurrence must not suppress its valid parameter neighbour" - ); - assert!(flat[0].spec_params[0].hash_eq(¶meter)); - } - - #[test] - fn nested_syntax_shared_dag_has_no_tree_size_prepass() { - let mut shared = - app(cnst("List", &[AU::succ(AU::zero())]), cnst("Tree", &[])); - for _ in 0..64 { - shared = pi(shared.clone(), shared); - } - let flat = discover_test_syntax(&shared, 0); - assert_eq!(flat.len(), 1); - } - - /// Lean generates distinct nested auxiliaries when the external family and - /// term parameter agree but a phantom universe argument differs. This is - /// the direct metadata analogue of: - /// - /// `Phantom.{p,q} (A : Sort p) : Prop` - /// `UniverseNested.left : Phantom.{0,u} UniverseNested → UniverseNested` - /// `UniverseNested.right : Phantom.{0,v} UniverseNested → UniverseNested`. - fn universe_specialized_nested_env() -> KEnv { - let mut env = KEnv::new(); - let phantom = mk_id("Phantom"); - let root = mk_id("UniverseNested"); - let root_app = || cnst("UniverseNested", &[param(0), param(1)]); - - env.insert( - phantom.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 2, - params: 1, - indices: 0, - is_unsafe: false, - block: phantom.clone(), - member_idx: 0, - ty: pi(AE::sort(param(0)), _sort0()), - ctors: vec![mk_id("Phantom.mk")], - lean_all: (), - }, - ); - env.insert( - mk_id("Phantom.mk"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 2, - induct: phantom.clone(), - cidx: 0, - params: 1, - fields: 1, - ty: pi( - AE::sort(param(0)), - pi( - AE::sort(param(1)), - app(cnst("Phantom", &[param(0), param(1)]), var(1)), - ), - ), - }, - ); - env - .blocks - .insert(phantom.clone(), vec![phantom.clone(), mk_id("Phantom.mk")]); - - env.insert( - root.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 2, - params: 0, - indices: 0, - is_unsafe: false, - block: root.clone(), - member_idx: 0, - ty: _sort0(), - ctors: vec![ - mk_id("UniverseNested.left"), - mk_id("UniverseNested.right"), - ], - lean_all: (), - }, - ); - for (name, phantom_level, cidx) in [ - ("UniverseNested.left", param(0), 0), - ("UniverseNested.right", param(1), 1), - ] { - let nested = - app(cnst("Phantom", &[AU::zero(), phantom_level]), root_app()); - env.insert( - mk_id(name), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 2, - induct: root.clone(), - cidx, - params: 0, - fields: 1, - ty: pi(nested, root_app()), - }, - ); - } - env.blocks.insert( - root.clone(), - vec![root, mk_id("UniverseNested.left"), mk_id("UniverseNested.right")], - ); - env - } - - #[test] - fn nested_aux_identity_includes_universe_specialization() { - let mut env = universe_specialized_nested_env(); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("UniverseNested")).unwrap(); - - let generated = tc - .env - .recursor_cache - .get(&mk_id("UniverseNested")) - .expect("root recursor generation should be cached"); - assert_eq!( - generated.len(), - 3, - "root plus Phantom.{{0,u}} and Phantom.{{0,v}} must remain distinct" - ); - assert_eq!(generated[0].motives, 3); - assert_eq!(generated[1].ind_addr, mk_addr("Phantom")); - assert_eq!(generated[2].ind_addr, mk_addr("Phantom")); - } - - #[test] - fn semantic_universe_equality_does_not_collapse_nested_specialization() { - let family = mk_addr("CommutedUniverseFamily"); - let u = param(0); - let v = param(1); - let left = AU::max(u.clone(), v.clone()); - let right = AU::max(v, u); - - assert!( - univ_eq(&left, &right), - "commuted maxima should be semantically equal" - ); - assert!( - !super::same_nested_specialization::( - &family, - &[left], - &[], - &family, - &[right], - &[], - ), - "semantic universe equality must not merge exact flat-block keys" - ); - } - - #[test] - fn nested_tree_rec_type_matches() { - // Verify that the generated Tree.rec type matches what lean4 would produce. - // Tree.rec.{u} : ∀ (motive₀ : Tree → Sort u) - // (motive₁ : List.{1} Tree → Sort u) - // (h_leaf : motive₀ Tree.leaf) - // (h_node : ∀ (children : List.{1} Tree), motive₁ children → motive₀ (Tree.node children)) - // (h_nil : motive₁ (List.nil.{1} Tree)) - // (h_cons : ∀ (hd : Tree) (tl : List.{1} Tree), motive₀ hd → motive₁ tl → motive₁ (List.cons.{1} Tree hd tl)) - // (t : Tree), motive₀ t - let mut env = nested_tree_env(); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("Tree")).unwrap(); - - let tree_block = mk_id("Tree"); - let gen_ty = tc.env.recursor_cache.get(&tree_block).unwrap()[0].ty.clone(); - - let u0 = param(0); - let u1 = AU::succ(AU::zero()); - let tree = || cnst("Tree", &[]); - let list_tree = || app(cnst("List", std::slice::from_ref(&u1)), tree()); - - // motive₀ : Tree → Sort u - let mot0_ty = pi(tree(), AE::sort(u0.clone())); - // motive₁ : List.{1} Tree → Sort u - let mot1_ty = pi(list_tree(), AE::sort(u0.clone())); - - // Under [mot0, mot1]: - // h_leaf: mot0 Tree.leaf (mot0 = Var(1), mot1 = Var(0)) - let h_leaf = app(var(1), cnst("Tree.leaf", &[])); - - // h_node: ∀ (children : List.{1} Tree), mot1 children → mot0 (Tree.node children) - // Under [mot0, mot1, h_leaf]: mot0=Var(2), mot1=Var(1) - // Under [mot0, mot1, h_leaf, children]: mot0=Var(3), mot1=Var(2), children=Var(0) - let h_node = pi( - list_tree(), - pi( - app(var(2), var(0)), // mot1 children (mot1=Var(2) under h_leaf+children) - app(var(4), app(cnst("Tree.node", &[]), var(1))), // mot0 (Tree.node children) - ), - ); - - // h_nil: mot1 (List.nil.{1} Tree) - // Under [mot0, mot1, h_leaf, h_node]: mot1=Var(2) - let h_nil = - app(var(2), app(cnst("List.nil", std::slice::from_ref(&u1)), tree())); - - // h_cons: ∀ (hd : Tree) (tl : List.{1} Tree), mot0 hd → mot1 tl → mot1 (List.cons.{1} Tree hd tl) - // Under [mot0, mot1, h_leaf, h_node, h_nil]: - // mot0=Var(4), mot1=Var(3) - // Under [..., hd, tl]: - // mot0=Var(6), mot1=Var(5), hd=Var(1), tl=Var(0) - // Under [..., hd, tl, ih_hd]: - // mot0=Var(7), mot1=Var(6), hd=Var(2), tl=Var(1) - // Under [..., hd, tl, ih_hd, ih_tl]: - // mot0=Var(8), mot1=Var(7), hd=Var(3), tl=Var(2) - let h_cons = pi( - tree(), // hd - pi( - list_tree(), // tl - pi( - app(var(6), var(1)), // ih_hd: mot0 hd - pi( - app(var(6), var(1)), // ih_tl: mot1 tl - app( - var(7), // mot1 - app( - app( - app(cnst("List.cons", std::slice::from_ref(&u1)), tree()), - var(3), - ), - var(2), - ), - ), - ), - ), - ), - ); - - // major : Tree - // Under [mot0, mot1, h_leaf, h_node, h_nil, h_cons]: - // mot0=Var(5) - // Under [..., t]: mot0=Var(6) - let major = tree(); - let ret = app(var(6), var(0)); // mot0 t - - let expected = pi( - mot0_ty, - pi( - mot1_ty, - pi(h_leaf, pi(h_node, pi(h_nil, pi(h_cons, pi(major, ret))))), - ), - ); - - let ok = tc.is_def_eq(&gen_ty, &expected).unwrap_or(false); - assert!(ok, "generated Tree.rec type should match expected"); - } - - #[test] - fn nested_tree_rec_binder_count() { - let mut env = nested_tree_env(); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("Tree")).unwrap(); - - let tree_block = mk_id("Tree"); - let generated = tc.env.recursor_cache.get(&tree_block).unwrap(); - - // Count binders in Tree.rec (member 0) - let count_binders = |e: &AE| -> usize { - let mut n = 0; - let mut c = e.clone(); - while let ExprData::All(_, _, _, _, _, b, _) = c.data() { - n += 1; - c = b.clone(); - } - n - }; - - let tree_rec = &generated[0]; - // Tree.rec: 0 params + 2 motives + (2 + 2) minors + 0 indices + 1 major = 7 - // Minors: Tree.leaf (0 fields, 0 IH), Tree.node (1 field + 1 IH = 2) - // List.nil (0 fields, 0 IH), List.cons (2 fields + 2 IH = 4) - // Wait — minors for Tree.rec include ALL ctors of ALL flat members. - // Tree: leaf (0 binders), node (1 field + 1 IH = 2 binders) - // List(aux): nil (0 binders), cons (2 fields + 2 IH = 4 binders) - // But minors are individual forall types, not nested. Each minor is ONE forall domain. - // So: 2 motives + 4 minors + 1 major = 7 binders total (0 params, 0 indices) - let n = count_binders(&tree_rec.ty); - assert_eq!( - n, 7, - "Tree.rec should have 7 binders (2 motives + 4 minors + 1 major), got {n}" - ); - - // List auxiliary rec (member 1) - let list_rec = &generated[1]; - // List aux rec for List Tree: - // 0 params + 2 motives + 4 minors + 0 indices + 1 major = 7 - let n = count_binders(&list_rec.ty); - assert_eq!(n, 7, "List aux rec should have 7 binders, got {n}"); - } - - /// Polymorphic nested: PTree.{u} : Sort (u+1) → Sort (u+1) - /// Like Tree but with one universe param and one type param. - /// PTree.leaf.{u} : ∀ (α : Sort (u+1)), α → PTree.{u} α - /// PTree.node.{u} : ∀ (α : Sort (u+1)), List.{u+1} (PTree.{u} α) → PTree.{u} α - fn poly_nested_env() -> KEnv { - let mut env = KEnv::new(); - let block = mk_id("PTree"); - let su = || AU::succ(param(0)); // u+1 - - // PTree.{u} : Sort(u+1) → Sort(u+1) - let ptree_ty = pi(AE::sort(su()), AE::sort(su())); - env.insert( - mk_id("PTree"), - KConst::Indc { - name: (), - level_params: (), - lvls: 1, - params: 1, - indices: 0, - is_unsafe: false, - block: block.clone(), - member_idx: 0, - ty: ptree_ty, - ctors: vec![mk_id("PTree.leaf"), mk_id("PTree.node")], - lean_all: (), - }, - ); - - // PTree.leaf : ∀ (α : Sort(u+1)), α → PTree.{u} α - let leaf_ty = - pi(AE::sort(su()), pi(var(0), app(cnst("PTree", &[param(0)]), var(1)))); - env.insert( - mk_id("PTree.leaf"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("PTree"), - cidx: 0, - params: 1, - fields: 1, - ty: leaf_ty, - }, - ); - - // PTree.node : ∀ (α : Sort(u+1)), List.{u+1} (PTree.{u} α) → PTree.{u} α - // Note: List.{u+1} because PTree.{u} α : Sort(u+1), and List.{v} : Sort v → Sort v - let ptree_app = app(cnst("PTree", &[param(0)]), var(0)); - let list_ptree = app(cnst("List", &[su()]), ptree_app); - let node_ty = pi( - AE::sort(su()), - pi(list_ptree, app(cnst("PTree", &[param(0)]), var(1))), - ); - env.insert( - mk_id("PTree.node"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("PTree"), - cidx: 1, - params: 1, - fields: 1, - ty: node_ty, - }, - ); - - let list_ty = pi(AE::sort(param(0)), AE::sort(param(0))); - env.insert( - mk_id("List"), - KConst::Indc { - name: (), - level_params: (), - lvls: 1, - params: 1, - indices: 0, - is_unsafe: false, - block: mk_id("List"), - member_idx: 0, - ty: list_ty, - ctors: vec![mk_id("List.nil"), mk_id("List.cons")], - lean_all: (), - }, - ); - let nil_ty = pi(AE::sort(param(0)), app(cnst("List", &[param(0)]), var(0))); - env.insert( - mk_id("List.nil"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("List"), - cidx: 0, - params: 1, - fields: 0, - ty: nil_ty, - }, - ); - let cons_ty = pi( - AE::sort(param(0)), - pi( - var(0), - pi( - app(cnst("List", &[param(0)]), var(1)), - app(cnst("List", &[param(0)]), var(2)), - ), - ), - ); - env.insert( - mk_id("List.cons"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("List"), - cidx: 1, - params: 1, - fields: 2, - ty: cons_ty, - }, - ); - - env.blocks.insert( - block, - vec![mk_id("PTree"), mk_id("PTree.leaf"), mk_id("PTree.node")], - ); - env.blocks.insert( - mk_id("List"), - vec![mk_id("List"), mk_id("List.nil"), mk_id("List.cons")], - ); - env - } - - #[test] - fn poly_nested_flat_block() { - let mut env = poly_nested_env(); - let mut tc = TypeChecker::new(&mut env); - // Check inductive first (consumes fuel for validation) - tc.check_const(&mk_id("PTree")).unwrap(); - // Reset fuel and generate recursors explicitly - tc.rec_fuel = super::super::tc::max_rec_fuel(); - let block = mk_id("PTree"); - if !tc.env.recursor_cache.contains_key(&block) { - tc.generate_block_recursors(&block).unwrap(); - } - - let generated = - tc.env.recursor_cache.get(&block).expect("recursor should be cached"); - assert_eq!( - generated.len(), - 2, - "flat block should produce 2 recursors (PTree + List aux)" - ); - } - - #[test] - fn poly_nested_rec_binder_count() { - let mut env = poly_nested_env(); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("PTree")).unwrap(); - tc.rec_fuel = super::super::tc::max_rec_fuel(); - let block = mk_id("PTree"); - if !tc.env.recursor_cache.contains_key(&block) { - tc.generate_block_recursors(&block).unwrap(); - } - - let generated = tc.env.recursor_cache.get(&block).unwrap(); - - let count_binders = |e: &AE| -> usize { - let mut n = 0; - let mut c = e.clone(); - while let ExprData::All(_, _, _, _, _, b, _) = c.data() { - n += 1; - c = b.clone(); - } - n - }; - - // PTree.rec: 1 param + 2 motives + 4 minors + 0 indices + 1 major = 8 - let n = count_binders(&generated[0].ty); - assert_eq!(n, 8, "PTree.rec should have 8 binders, got {n}"); - } - - /// Mimics Lean.Syntax structure: a type `Syn` that nests with - /// `List (Pair Name Syn)` — testing multi-level transitive nesting. - /// - /// Syn : Sort 1 - /// Syn.atom : Syn - /// Syn.node : List (Pair Name Syn) → Syn - /// - /// This should create a flat block: - /// [Syn, List (Pair Name Syn), Pair (Name, Syn)] - /// with 3 motives. - fn syntax_like_env() -> KEnv { - let mut env = KEnv::new(); - let block = mk_id("Syn"); - let syn = || cnst("Syn", &[]); - - // Name : Sort 1 (axiom, external) - env.insert( - mk_id("Name"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: sort1(), - }, - ); - - // Pair.{u,v} : Sort u → Sort v → Sort (max u v) - // Pair.mk.{u,v} : ∀ (α : Sort u) (β : Sort v), α → β → Pair.{u,v} α β - let pair_ty = pi( - AE::sort(param(0)), - pi(AE::sort(param(1)), AE::sort(AU::max(param(0), param(1)))), - ); - env.insert( - mk_id("Pair"), - KConst::Indc { - name: (), - level_params: (), - lvls: 2, - params: 2, - indices: 0, - is_unsafe: false, - block: mk_id("Pair"), - member_idx: 0, - ty: pair_ty, - ctors: vec![mk_id("Pair.mk")], - lean_all: (), - }, - ); - // Pair.mk : ∀ (α : Sort u) (β : Sort v) (fst : α) (snd : β), Pair α β - let pair_mk_ty = pi( - AE::sort(param(0)), - pi( - AE::sort(param(1)), - pi( - var(1), - pi( - var(1), - app(app(cnst("Pair", &[param(0), param(1)]), var(3)), var(2)), - ), - ), - ), - ); - env.insert( - mk_id("Pair.mk"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 2, - induct: mk_id("Pair"), - cidx: 0, - params: 2, - fields: 2, - ty: pair_mk_ty, - }, - ); - - // List (reused from previous tests) - let list_ty = pi(AE::sort(param(0)), AE::sort(param(0))); - env.insert( - mk_id("List"), - KConst::Indc { - name: (), - level_params: (), - lvls: 1, - params: 1, - indices: 0, - is_unsafe: false, - block: mk_id("List"), - member_idx: 0, - ty: list_ty, - ctors: vec![mk_id("List.nil"), mk_id("List.cons")], - lean_all: (), - }, - ); - let nil_ty = pi(AE::sort(param(0)), app(cnst("List", &[param(0)]), var(0))); - env.insert( - mk_id("List.nil"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("List"), - cidx: 0, - params: 1, - fields: 0, - ty: nil_ty, - }, - ); - let cons_ty = pi( - AE::sort(param(0)), - pi( - var(0), - pi( - app(cnst("List", &[param(0)]), var(1)), - app(cnst("List", &[param(0)]), var(2)), - ), - ), - ); - env.insert( - mk_id("List.cons"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("List"), - cidx: 1, - params: 1, - fields: 2, - ty: cons_ty, - }, - ); - - // Syn : Sort 1 - env.insert( - mk_id("Syn"), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![mk_id("Syn.atom"), mk_id("Syn.node")], - lean_all: (), - }, - ); - // Syn.atom : Syn - env.insert( - mk_id("Syn.atom"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Syn"), - cidx: 0, - params: 0, - fields: 0, - ty: syn(), - }, - ); - // Syn.node : List.{1} (Pair.{1,1} Name Syn) → Syn - let pair_name_syn = app( - app( - cnst("Pair", &[AU::succ(AU::zero()), AU::succ(AU::zero())]), - cnst("Name", &[]), - ), - syn(), - ); - let list_pair = app(cnst("List", &[AU::succ(AU::zero())]), pair_name_syn); - env.insert( - mk_id("Syn.node"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Syn"), - cidx: 1, - params: 0, - fields: 1, - ty: pi(list_pair, syn()), - }, - ); - - env - .blocks - .insert(block, vec![mk_id("Syn"), mk_id("Syn.atom"), mk_id("Syn.node")]); - env.blocks.insert( - mk_id("List"), - vec![mk_id("List"), mk_id("List.nil"), mk_id("List.cons")], - ); - env.blocks.insert(mk_id("Pair"), vec![mk_id("Pair"), mk_id("Pair.mk")]); - env - } - - #[test] - fn syntax_like_flat_block() { - let mut env = syntax_like_env(); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("Syn")).unwrap(); - tc.rec_fuel = super::super::tc::max_rec_fuel(); - let block = mk_id("Syn"); - if !tc.env.recursor_cache.contains_key(&block) { - tc.generate_block_recursors(&block).unwrap(); - } - - let generated = - tc.env.recursor_cache.get(&block).expect("recursor should be cached"); - - // Flat block: [Syn, List (Pair Name Syn), Pair (Name, Syn)] - // = 3 members → 3 recursors generated - assert_eq!( - generated.len(), - 3, - "flat block should have 3 members (Syn + List aux + Pair aux), got {}", - generated.len() - ); - } - - #[test] - fn syntax_like_false_positive_rec_field() { - // Test that `List OtherType` is NOT detected as recursive when only - // `List (Pair Name Syn)` is a valid auxiliary. This replicates the - // Lean.Syntax.rec binder 6 failure where `List Preresolved` was - // incorrectly matched to the `List Syntax` auxiliary. - let mut env = syntax_like_env(); - - // Add OtherType : Sort 1 (external, non-recursive) - env.insert( - mk_id("Other"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: sort1(), - }, - ); - - // Add a third ctor: Syn.ident : List.{1} Other → Syn - // `List Other` should NOT be recursive (Other doesn't mention Syn) - let list_other = - app(cnst("List", &[AU::succ(AU::zero())]), cnst("Other", &[])); - env.insert( - mk_id("Syn.ident"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Syn"), - cidx: 2, - params: 0, - fields: 1, - ty: pi(list_other, cnst("Syn", &[])), - }, - ); - - // Update Syn to have 3 ctors - if let Some(entry) = env.consts.get_mut(&mk_id("Syn")) - && let KConst::Indc { ctors, .. } = entry - { - ctors.push(mk_id("Syn.ident")); - } - - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("Syn")).unwrap(); - tc.rec_fuel = super::super::tc::max_rec_fuel(); - let block = mk_id("Syn"); - if !tc.env.recursor_cache.contains_key(&block) { - tc.generate_block_recursors(&block).unwrap(); - } - let generated = tc.env.recursor_cache.get(&block).unwrap(); - - // Should still have 3 flat members (Syn, List aux, Pair aux) — NOT 4 - // List Other should NOT create a new auxiliary - assert_eq!( - generated.len(), - 3, - "should have 3 flat members, not more (List Other is not nested)" - ); - - let count_binders = |e: &AE| -> usize { - let mut n = 0; - let mut c = e.clone(); - while let ExprData::All(_, _, _, _, _, b, _) = c.data() { - n += 1; - c = b.clone(); - } - n - }; - - // Total top-level binders: 3 motives + 6 minors + 0 indices + 1 major = 10 - let n = count_binders(&generated[0].ty); - assert_eq!(n, 10, "Syn.rec with ident should have 10 binders, got {n}"); - - // Check the ident minor (binder 5 = 3 motives + 2 earlier minors) - // Its domain should have 1 inner binder (the List Other field) and 0 IHs. - // If is_rec_field falsely matches List Other, it would have 2 inner binders. - let mut cur = generated[0].ty.clone(); - for _ in 0..5 { - // skip to binder 5 - if let ExprData::All(_, _, _, _, _, body, _) = cur.data() { - cur = body.clone(); - } - } - let ident_minor_domain = match cur.data() { - ExprData::All(_, _, _, _, dom, _, _) => dom.clone(), - _ => panic!("expected forall at binder 5"), - }; - let ident_inner_binders = count_binders(&ident_minor_domain); - // Should be 1 (just the List Other field), NOT 2 (field + false IH) - assert_eq!( - ident_inner_binders, 1, - "ident minor should have 1 inner binder (non-rec field), got {} (false positive IH?)", - ident_inner_binders - ); - } - - #[test] - fn syntax_like_rec_binder_count() { - let mut env = syntax_like_env(); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("Syn")).unwrap(); - tc.rec_fuel = super::super::tc::max_rec_fuel(); - let block = mk_id("Syn"); - if !tc.env.recursor_cache.contains_key(&block) { - tc.generate_block_recursors(&block).unwrap(); - } - - let generated = tc.env.recursor_cache.get(&block).unwrap(); - - let count_binders = |e: &AE| -> usize { - let mut n = 0; - let mut c = e.clone(); - while let ExprData::All(_, _, _, _, _, b, _) = c.data() { - n += 1; - c = b.clone(); - } - n - }; - - // Syn.rec binders: - // 0 params - // 3 motives (Syn, List aux, Pair aux) - // minors: Syn.atom(0) + Syn.node(1 field + 1 IH = 2) + List.nil(0) + List.cons(2 fields + 2 IH = 4) - // + Pair.mk(2 fields + 1 IH = 3) - // = 5 minors - // 0 indices - // 1 major - // Total = 3 + 5 + 1 = 9 - let n = count_binders(&generated[0].ty); - assert_eq!(n, 9, "Syn.rec should have 9 binders, got {n}"); - } - - /// Mimics Lean.Doc.Inline: parameterized type with Array nesting. - /// Inl.{u} (i : Sort (u+1)) : Sort (u+1) - /// Inl.text.{u} : ∀ (i : Sort (u+1)), String → Inl.{u} i - /// Inl.emph.{u} : ∀ (i : Sort (u+1)), Array.{u+1} (Inl.{u} i) → Inl.{u} i - /// Inl.other.{u} : ∀ (i : Sort (u+1)), i → Array.{u+1} (Inl.{u} i) → Inl.{u} i - fn inline_like_env() -> KEnv { - let mut env = KEnv::new(); - let block = mk_id("Inl"); - let su = || AU::succ(param(0)); // u+1 - - // String : Sort 1 (external axiom) - env.insert( - mk_id("String"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: sort1(), - }, - ); - - // Array.{v} : Sort v → Sort v (external, 1 univ param, 1 type param) - let arr_ty = pi(AE::sort(param(0)), AE::sort(param(0))); - env.insert( - mk_id("Array"), - KConst::Indc { - name: (), - level_params: (), - lvls: 1, - params: 1, - indices: 0, - is_unsafe: false, - block: mk_id("Array"), - member_idx: 0, - ty: arr_ty, - ctors: vec![mk_id("Array.mk")], - lean_all: (), - }, - ); - // Array.mk : ∀ (α : Sort v), List.{v} α → Array.{v} α - let arr_mk_ty = pi( - AE::sort(param(0)), - pi( - app(cnst("List", &[param(0)]), var(0)), - app(cnst("Array", &[param(0)]), var(1)), - ), - ); - env.insert( - mk_id("Array.mk"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("Array"), - cidx: 0, - params: 1, - fields: 1, - ty: arr_mk_ty, - }, - ); - - // List (reused) - let list_ty = pi(AE::sort(param(0)), AE::sort(param(0))); - env.insert( - mk_id("List"), - KConst::Indc { - name: (), - level_params: (), - lvls: 1, - params: 1, - indices: 0, - is_unsafe: false, - block: mk_id("List"), - member_idx: 0, - ty: list_ty, - ctors: vec![mk_id("List.nil"), mk_id("List.cons")], - lean_all: (), - }, - ); - let nil_ty = pi(AE::sort(param(0)), app(cnst("List", &[param(0)]), var(0))); - env.insert( - mk_id("List.nil"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("List"), - cidx: 0, - params: 1, - fields: 0, - ty: nil_ty, - }, - ); - let cons_ty = pi( - AE::sort(param(0)), - pi( - var(0), - pi( - app(cnst("List", &[param(0)]), var(1)), - app(cnst("List", &[param(0)]), var(2)), - ), - ), - ); - env.insert( - mk_id("List.cons"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("List"), - cidx: 1, - params: 1, - fields: 2, - ty: cons_ty, - }, - ); - - // Inl.{u} : Sort(u+1) → Sort(u+1) (1 lvl, 1 param) - let inl_ty = pi(AE::sort(su()), AE::sort(su())); - env.insert( - mk_id("Inl"), - KConst::Indc { - name: (), - level_params: (), - lvls: 1, - params: 1, - indices: 0, - is_unsafe: false, - block: block.clone(), - member_idx: 0, - ty: inl_ty, - ctors: vec![mk_id("Inl.text"), mk_id("Inl.emph"), mk_id("Inl.other")], - lean_all: (), - }, - ); - - // Inl.text : ∀ (i : Sort(u+1)), String → Inl.{u} i - let text_ty = pi( - AE::sort(su()), - pi(cnst("String", &[]), app(cnst("Inl", &[param(0)]), var(1))), - ); - env.insert( - mk_id("Inl.text"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("Inl"), - cidx: 0, - params: 1, - fields: 1, - ty: text_ty, - }, - ); - - // Inl.emph : ∀ (i : Sort(u+1)), Array.{u+1} (Inl.{u} i) → Inl.{u} i - let inl_i = app(cnst("Inl", &[param(0)]), var(0)); // under i binder - let arr_inl = app(cnst("Array", &[su()]), inl_i); - let emph_ty = - pi(AE::sort(su()), pi(arr_inl, app(cnst("Inl", &[param(0)]), var(1)))); - env.insert( - mk_id("Inl.emph"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("Inl"), - cidx: 1, - params: 1, - fields: 1, - ty: emph_ty, - }, - ); - - // Inl.other : ∀ (i : Sort(u+1)), i → Array.{u+1} (Inl.{u} i) → Inl.{u} i - let inl_i2 = app(cnst("Inl", &[param(0)]), var(0)); // under i binder - let arr_inl2 = app(cnst("Array", &[su()]), inl_i2); - let _other_ty = pi( - AE::sort(su()), - pi( - var(0), // i (the type param) - pi( - arr_inl2, // but arr_inl2 references var(0) which is now var(1) under the i-field binder! - app(cnst("Inl", &[param(0)]), var(2)), - ), - ), - ); - - // Wait — the `arr_inl2` uses `var(0)` for i, but after the `pi(var(0), ...)` binder, - // i is now var(1). The Array arg `Inl.{u} i` should reference i=var(1) not var(0). - // Let me fix: under ∀ (i : Sort(u+1)) (x : i), the Array field needs i=var(1). - let inl_i_shifted = app(cnst("Inl", &[param(0)]), var(1)); // i=var(1) under x binder - let arr_inl_shifted = app(cnst("Array", &[su()]), inl_i_shifted); - let other_ty = pi( - AE::sort(su()), - pi(var(0), pi(arr_inl_shifted, app(cnst("Inl", &[param(0)]), var(2)))), - ); - env.insert( - mk_id("Inl.other"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("Inl"), - cidx: 2, - params: 1, - fields: 2, - ty: other_ty, - }, - ); - - env.blocks.insert( - block, - vec![ - mk_id("Inl"), - mk_id("Inl.text"), - mk_id("Inl.emph"), - mk_id("Inl.other"), - ], - ); - env.blocks.insert(mk_id("Array"), vec![mk_id("Array"), mk_id("Array.mk")]); - env.blocks.insert( - mk_id("List"), - vec![mk_id("List"), mk_id("List.nil"), mk_id("List.cons")], - ); - env - } - - #[test] - fn inline_like_flat_block() { - let mut env = inline_like_env(); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("Inl")).unwrap(); - tc.rec_fuel = super::super::tc::max_rec_fuel(); - let block = mk_id("Inl"); - if !tc.env.recursor_cache.contains_key(&block) { - tc.generate_block_recursors(&block).unwrap(); - } - - let generated = - tc.env.recursor_cache.get(&block).expect("recursor should be cached"); - // Flat block: [Inl, Array, List] = 3 members - assert_eq!( - generated.len(), - 3, - "flat block should have 3 members, got {}", - generated.len() - ); - } - - #[test] - fn inline_like_rec_2_binder_count() { - let mut env = inline_like_env(); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("Inl")).unwrap(); - tc.rec_fuel = super::super::tc::max_rec_fuel(); - let block = mk_id("Inl"); - if !tc.env.recursor_cache.contains_key(&block) { - tc.generate_block_recursors(&block).unwrap(); - } - let generated = tc.env.recursor_cache.get(&block).unwrap(); - - let count_binders = |e: &AE| -> usize { - let mut n = 0; - let mut c = e.clone(); - while let ExprData::All(_, _, _, _, _, b, _) = c.data() { - n += 1; - c = b.clone(); - } - n - }; - - // Inl.rec (member 0): - // 1 param(α) + 3 motives + N minors + 0 indices + 1 major - // Minors: text(1f+0ih), emph(1f+1ih), other(2f+1ih), arr.mk(1f+1ih), nil(0), cons(2f+2ih) - // = 6 minors - // Total = 1 + 3 + 6 + 0 + 1 = 11 - let n0 = count_binders(&generated[0].ty); - assert_eq!(n0, 11, "Inl.rec should have 11 binders, got {n0}"); - - // Inl.rec_2 (member 2 = List aux): - // 1 param + 3 motives + 6 minors + 0 indices + 1 major = 11 - if generated.len() > 2 { - let n2 = count_binders(&generated[2].ty); - assert_eq!( - n2, 11, - "Inl.rec_2 (List aux) should have 11 binders, got {n2}" - ); - } - - // Deeper check: verify the generated Inl.rec_2 type against a manually - // constructed version to catch var-index bugs. - // For this we need the Inl.rec_2 stored as a Recr constant and compare. - // Instead, let's just check that is_def_eq succeeds between rec[0] and - // a hand-constructed Inl.rec. - // This is complex, so let's at least verify that the cons minor inside - // rec_2 has the right structure by inspecting its inner binders. - - // rec_2 = generated[2], binder layout: - // 0: param (i : Sort(u+1)) - // 1: motive_0 (Inl motive) - // 2: motive_1 (Array aux motive) - // 3: motive_2 (List aux motive) - // 4-9: minors (text, emph, other, arr.mk, nil, cons) - // 10: major (List.{u+1} (Inl.{u} i)) - // The cons minor is binder 9 (6th minor) - if generated.len() > 2 { - let mut cur = generated[2].ty.clone(); - // Skip to binder 9 (cons minor) - for _ in 0..9 { - if let ExprData::All(_, _, _, _, _, body, _) = cur.data() { - cur = body.clone(); - } - } - let cons_minor_domain = match cur.data() { - ExprData::All(_, _, _, _, dom, _, _) => dom.clone(), - _ => panic!("expected forall at binder 9 for cons minor"), - }; - // cons minor should have 4 inner binders: - // ∀ (hd : Inl i) (tl : List (Inl i)) (ih_hd : motive_0 hd) (ih_tl : motive_2 tl), motive_2 (cons (Inl i) hd tl) - let inner = count_binders(&cons_minor_domain); - assert_eq!( - inner, 4, - "cons minor should have 4 inner binders (2 fields + 2 IH), got {inner}" - ); - } - } - - /// Mimics Std.DHashMap.Raw.WF: Prop inductive with params, index, recursive ctors. - /// - /// Ok.{u} (α : Sort (u+1)) (n : Nat) : Prop - /// Ok.base.{u} : ∀ (α : Sort (u+1)) (n : Nat), Ok.{u} α n - /// Ok.step.{u} : ∀ (α : Sort (u+1)) (n : Nat), Ok.{u} α n → Ok.{u} α n - /// - /// This has 1 univ param, 1 type param, 1 index (Nat), and is in Prop. - fn wf_like_env() -> KEnv { - let mut env = KEnv::new(); - let block = mk_id("Ok"); - - // Nat : Sort 1 - env.insert( - mk_id("Nat"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: sort1(), - }, - ); - - // Ok.{u} : Sort(u+1) → Nat → Prop - let su = || AU::succ(param(0)); - let ok_ty = - pi(AE::sort(su()), pi(cnst("Nat", &[]), KExpr::sort(KUniv::zero()))); - env.insert( - mk_id("Ok"), - KConst::Indc { - name: (), - level_params: (), - lvls: 1, - params: 1, - indices: 1, - is_unsafe: false, - block: block.clone(), - member_idx: 0, - ty: ok_ty, - ctors: vec![mk_id("Ok.base"), mk_id("Ok.step")], - lean_all: (), - }, - ); - - // Ok.base : ∀ (α : Sort(u+1)) (n : Nat), Ok.{u} α n - let base_ty = pi( - AE::sort(su()), - pi(cnst("Nat", &[]), app(app(cnst("Ok", &[param(0)]), var(1)), var(0))), - ); - env.insert( - mk_id("Ok.base"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("Ok"), - cidx: 0, - params: 1, - fields: 0, - // ctor params = 1 (α), indices absorbed into return type - // fields = 0 (just params + index in return) - // Wait: params=1, but the ctor has 2 foralls (α, n). n is part of the - // return type index, not a field. Lean convention: first `params` foralls - // are params, the rest before the return type are fields. - // Ok.base has type ∀ (α) (n), Ok α n. With params=1: α is param, n is field? No. - // Actually for constructors, `fields` = total_foralls - params. - // Ok.base: 2 foralls, params=1, fields=1 (n is a field). - // But n appears in the return type as an index, so it IS a field. - ty: base_ty, - }, - ); - // Fix: fields should be 1 (n), not 0 - if let Some(entry) = env.consts.get_mut(&mk_id("Ok.base")) - && let KConst::Ctor { fields, .. } = entry - { - *fields = 1; - } - - // Ok.step : ∀ (α : Sort(u+1)) (n : Nat), Ok.{u} α n → Ok.{u} α n - // Ok.step : ∀ (α : Sort(u+1)) (n : Nat) (h : Ok α n), Ok α n - // Under (α, n): Ok α n = Ok Var(1) Var(0) - let ok_an_depth2 = app(app(cnst("Ok", &[param(0)]), var(1)), var(0)); - // Under (α, n, h): Ok α n = Ok Var(2) Var(1) - let ok_an_depth3 = app(app(cnst("Ok", &[param(0)]), var(2)), var(1)); - let step_ty = - pi(AE::sort(su()), pi(cnst("Nat", &[]), pi(ok_an_depth2, ok_an_depth3))); - env.insert( - mk_id("Ok.step"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - induct: mk_id("Ok"), - cidx: 1, - params: 1, - fields: 2, // n + proof - ty: step_ty, - }, - ); - - env - .blocks - .insert(block, vec![mk_id("Ok"), mk_id("Ok.base"), mk_id("Ok.step")]); - env - } - - #[test] - fn wf_like_rec_type() { - let mut env = wf_like_env(); - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("Ok")).unwrap(); - - let block = mk_id("Ok"); - let gen_ty = tc.env.recursor_cache.get(&block).unwrap()[0].ty.clone(); - - let count_binders = |e: &AE| -> usize { - let mut n = 0; - let mut c = e.clone(); - while let ExprData::All(_, _, _, _, _, b, _) = c.data() { - n += 1; - c = b.clone(); - } - n - }; - - // Ok is Prop with 2+ ctors → small eliminator (elim_level = Zero) - // Ok.rec: 1 param + 1 motive + 2 minors + 1 index + 1 major = 6 - let n = count_binders(&gen_ty); - assert_eq!(n, 6, "Ok.rec should have 6 binders, got {n}"); - - // Build expected type and compare via is_def_eq. - // Ok.rec.{u} : ∀ (α : Sort(u+1)) (motive : ∀ (n : Nat), Ok.{u} α n → Prop) - // (base : ∀ (n : Nat), motive n (Ok.base.{u} α n)) - // (step : ∀ (n : Nat) (h : Ok.{u} α n), motive n h → motive n (Ok.step.{u} α n h)) - // (n : Nat) (t : Ok.{u} α n), motive n t - - let su = || AU::succ(param(0)); - let u0 = AU::zero(); - - // Under α binder (Var(0) = α): - let ok_a = |idx_var: u64, alpha_var: u64| { - app(app(cnst("Ok", &[param(0)]), var(alpha_var)), var(idx_var)) - }; - - // motive : ∀ (n : Nat) (_ : Ok α n), Prop - // α = Var(0) from param - let motive_ty = pi(cnst("Nat", &[]), pi(ok_a(0, 1), AE::sort(u0.clone()))); - - // base minor: ∀ (n : Nat), motive n (Ok.base α n) - // Under [α, motive]: α=Var(1), motive=Var(0) - // Under [α, motive, n]: α=Var(2), motive=Var(1), n=Var(0) - let base_minor = pi( - cnst("Nat", &[]), - app( - app(var(1), var(0)), - app(app(cnst("Ok.base", &[param(0)]), var(2)), var(0)), - ), - ); - - // step minor: ∀ (n : Nat) (h : Ok α n) (ih : motive n h), motive n (Ok.step α n h) - // Under [α, motive, base_minor]: α=Var(2), motive=Var(1) - // Under [..., n]: α=Var(3), motive=Var(2), n=Var(0) - // Under [..., n, h]: α=Var(4), motive=Var(3), n=Var(1), h=Var(0) - // Under [..., n, h, ih]: α=Var(5), motive=Var(4), n=Var(2), h=Var(1) - let step_minor = pi( - cnst("Nat", &[]), // n - pi( - ok_a(0, 3), // h : Ok α n - pi( - app(app(var(3), var(1)), var(0)), // ih : motive n h - app( - app(var(4), var(2)), // motive n - app(app(app(cnst("Ok.step", &[param(0)]), var(5)), var(2)), var(1)), - ), // Ok.step α n h - ), - ), - ); - - // index: n : Nat - // Under [α, motive, base, step]: α=Var(3) - let idx = cnst("Nat", &[]); - - // major: Ok α n - // Under [α, motive, base, step, n]: α=Var(4), n=Var(0) - let major = ok_a(0, 4); - - // return: motive n t - // Under [α, motive, base, step, n, t]: motive=Var(4), n=Var(1), t=Var(0) - let ret = app(app(var(4), var(1)), var(0)); - - let expected = pi( - AE::sort(su()), // α - pi(motive_ty, pi(base_minor, pi(step_minor, pi(idx, pi(major, ret))))), - ); - - // Verify each binder domain is well-formed with detailed tracing. - let _count_binders = |e: &AE| -> usize { - let mut n = 0; - let mut c = e.clone(); - while let ExprData::All(_, _, _, _, _, b, _) = c.data() { - n += 1; - c = b.clone(); - } - n - }; - let ok = tc.is_def_eq(&gen_ty, &expected).unwrap_or(false); - assert!(ok, "Ok.rec type should match expected"); - } - - // ----------------------------------------------------------------------- - // Nested positivity tests - // ----------------------------------------------------------------------- - - #[test] - fn reject_nested_constructor_with_short_parameter_telescope() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let result = tc.check_nested_ctor_fields(&sort1(), 1, &[], &[], &[], &[]); - assert!( - matches!(result, Err(TcError::Other(ref msg)) - if msg.contains("fewer parameter binders than declared")), - "short nested constructor telescope must be rejected, got {result:?}" - ); - } - - #[test] - fn reject_fabricated_multi_motive_recursor() { - let mut env = nat_env(); - let rec_id = mk_id("Bad.rec"); - let rec_block = mk_id("Bad.rec.block"); - - // (C : Nat -> Prop) -> (junk : Nat) -> (n : Nat) -> C n. - // With attacker-supplied `motives = 2`, the old canonical-order fallback - // accepted this type without comparing it or its empty rule list with - // generated Nat.rec. - let ty = pi( - pi(cnst("Nat", &[]), _sort0()), - pi(cnst("Nat", &[]), pi(cnst("Nat", &[]), app(var(2), var(0)))), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: (), - level_params: (), - k: false, - is_unsafe: false, - // Nat.rec has one recursor universe parameter. The fabricated motive - // count is deliberately non-canonical and must fail the peer-header - // alignment gate before any rules can be installed. - lvls: 1, - params: 0, - indices: 0, - motives: 2, - minors: 0, - block: rec_block.clone(), - member_idx: 0, - ty, - rules: vec![], - lean_all: (), - }, - ); - env.blocks.insert(rec_block, vec![rec_id.clone()]); - - let mut tc = TypeChecker::new(&mut env); - let result = tc.check_const(&rec_id); - assert!( - matches!(result, Err(TcError::Other(ref msg)) - if msg.contains("populate_recursor_rules_from_block: canonical header mismatch")), - "fabricated multi-motive recursor must be rejected, got {result:?}" - ); - } - - #[test] - fn reject_nonuniform_recursive_field_parameter() { - let mut env = nat_env(); - let block = mk_id("I"); - let ind = mk_id("I"); - let ctor = mk_id("I.mk"); - env.insert( - ind.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 1, - indices: 0, - is_unsafe: false, - block: block.clone(), - member_idx: 0, - ty: pi(sort1(), sort1()), - ctors: vec![ctor.clone()], - lean_all: (), - }, - ); - // I.mk : (A : Type) -> I Nat -> I A. - env.insert( - ctor.clone(), - KConst::Ctor { - name: (), - level_params: (), - lvls: 0, - cidx: 0, - params: 1, - fields: 1, - is_unsafe: false, - induct: ind.clone(), - ty: pi( - sort1(), - pi( - app(cnst("I", &[]), cnst("Nat", &[])), - app(cnst("I", &[]), var(1)), - ), - ), - }, - ); - env.blocks.insert(block, vec![ind.clone(), ctor]); - - let mut tc = TypeChecker::new(&mut env); - let result = tc.check_const(&ind); - assert!( - matches!(result, Err(TcError::Other(ref msg)) - if msg.contains("non-uniform parameter")), - "non-uniform recursive parameter must be rejected, got {result:?}" - ); - } - - #[test] - fn reject_nonuniform_recursive_field_universe() { - let mut env = nat_env(); - let block = mk_id("J"); - let ind = mk_id("J"); - let ctor = mk_id("J.mk"); - env.insert( - ind.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 1, - params: 0, - indices: 0, - is_unsafe: false, - block: block.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![ctor.clone()], - lean_all: (), - }, - ); - // J.mk.{u} : J.{0} -> J.{u}. - env.insert( - ctor.clone(), - KConst::Ctor { - name: (), - level_params: (), - lvls: 1, - cidx: 0, - params: 0, - fields: 1, - is_unsafe: false, - induct: ind.clone(), - ty: pi(cnst("J", &[AU::zero()]), cnst("J", &[param(0)])), - }, - ); - env.blocks.insert(block, vec![ind.clone(), ctor]); - - let mut tc = TypeChecker::new(&mut env); - let result = tc.check_const(&ind); - assert!( - matches!(result, Err(TcError::Other(ref msg)) - if msg.contains("non-uniform universe arguments")), - "non-uniform recursive universes must be rejected, got {result:?}" - ); - } - - #[test] - fn reject_recursive_field_index_mention() { - let mut env = nat_env(); - let block = mk_id("K"); - let ind = mk_id("K"); - let ctor = mk_id("K.mk"); - env.insert( - ind.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 1, - is_unsafe: false, - block: block.clone(), - member_idx: 0, - ty: pi(sort1(), sort1()), - ctors: vec![ctor.clone()], - lean_all: (), - }, - ); - // K.mk : K (K Nat) -> K Nat. - env.insert( - ctor.clone(), - KConst::Ctor { - name: (), - level_params: (), - lvls: 0, - cidx: 0, - params: 0, - fields: 1, - is_unsafe: false, - induct: ind.clone(), - ty: pi( - app(cnst("K", &[]), app(cnst("K", &[]), cnst("Nat", &[]))), - app(cnst("K", &[]), cnst("Nat", &[])), - ), - }, - ); - env.blocks.insert(block, vec![ind.clone(), ctor]); - - let mut tc = TypeChecker::new(&mut env); - let result = tc.check_const(&ind); - assert!( - matches!(result, Err(TcError::Other(ref msg)) - if msg.contains("index mentions an active inductive")), - "recursive occurrence indices must not mention the active block, got {result:?}" - ); - } - - #[test] - fn reject_ill_typed_phantom_nested_argument_before_rewrite() { - let mut env = nat_env(); - - // Phantom : Type -> Type; Phantom.mk : (A : Type) -> Phantom A. - // Its parameter is absent from every constructor field, so a lossy - // nested-inductive rewrite could erase the argument entirely. - let phantom = mk_id("Phantom"); - let phantom_ctor = mk_id("Phantom.mk"); - env.insert( - phantom.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 1, - indices: 0, - is_unsafe: false, - block: phantom.clone(), - member_idx: 0, - ty: pi(sort1(), sort1()), - ctors: vec![phantom_ctor.clone()], - lean_all: (), - }, - ); - env.insert( - phantom_ctor.clone(), - KConst::Ctor { - name: (), - level_params: (), - lvls: 0, - cidx: 0, - params: 1, - fields: 0, - is_unsafe: false, - induct: phantom.clone(), - ty: pi(sort1(), app(cnst("Phantom", &[]), var(0))), - }, - ); - env.blocks.insert(phantom.clone(), vec![phantom.clone(), phantom_ctor]); - - // Bad.mk : Phantom (Nat Nat) -> Bad. `Nat Nat` is ill-typed. The - // original stored constructor must be inferred before nested flattening, - // even though Phantom's parameter is phantom. - let bad = mk_id("BadPhantom"); - let bad_ctor = mk_id("BadPhantom.mk"); - env.insert( - bad.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: bad.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![bad_ctor.clone()], - lean_all: (), - }, - ); - let invalid_arg = app(cnst("Nat", &[]), cnst("Nat", &[])); - env.insert( - bad_ctor.clone(), - KConst::Ctor { - name: (), - level_params: (), - lvls: 0, - cidx: 0, - params: 0, - fields: 1, - is_unsafe: false, - induct: bad.clone(), - ty: pi(app(cnst("Phantom", &[]), invalid_arg), cnst("BadPhantom", &[])), - }, - ); - env.blocks.insert(bad.clone(), vec![bad.clone(), bad_ctor]); - - let mut tc = TypeChecker::new(&mut env); - let result = tc.check_const(&bad); - assert!( - matches!(result, Err(TcError::FunExpected { .. })), - "ill-typed phantom nested argument must be rejected before rewriting, got {result:?}" - ); - } - - /// Build an env with an external inductive `Wrap` that has its type param - /// in a **negative** position: `Wrap.mk : ∀ (α : Type), (α → Bool) → Wrap α`. - /// Then define `Evil : Type` with `Evil.mk : Wrap Evil → Evil`. - /// This must be REJECTED: `Evil` appears negatively inside `Wrap`'s constructor. - fn wrap_evil_env() -> KEnv { - let mut env = bool_env(); - - // Wrap : Type → Type (1 param, 0 indices) - let wrap_ty = pi(sort1(), sort1()); - let wrap_block = mk_id("Wrap"); - env.insert( - mk_id("Wrap"), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 1, - indices: 0, - is_unsafe: false, - block: wrap_block.clone(), - member_idx: 0, - ty: wrap_ty, - ctors: vec![mk_id("Wrap.mk")], - lean_all: (), - }, - ); - - // Wrap.mk : ∀ (α : Type), (α → Bool) → Wrap α - // Under ∀(α : Type): Var(0) = α - let wrap_mk_ty = pi( - sort1(), // α : Type - pi( - pi(var(0), cnst("Bool", &[])), // (α → Bool) - app(cnst("Wrap", &[]), var(1)), // Wrap α - ), - ); - env.insert( - mk_id("Wrap.mk"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Wrap"), - cidx: 0, - params: 1, - fields: 1, - ty: wrap_mk_ty, - }, - ); - - env.blocks.insert(wrap_block, vec![mk_id("Wrap"), mk_id("Wrap.mk")]); - - // Evil : Type (0 params, 0 indices) - let evil_block = mk_id("Evil"); - env.insert( - mk_id("Evil"), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: evil_block.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![mk_id("Evil.mk")], - lean_all: (), - }, - ); - - // Evil.mk : Wrap Evil → Evil - let evil_mk_ty = pi( - app(cnst("Wrap", &[]), cnst("Evil", &[])), // Wrap Evil - cnst("Evil", &[]), // Evil - ); - env.insert( - mk_id("Evil.mk"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Evil"), - cidx: 0, - params: 0, - fields: 1, - ty: evil_mk_ty, - }, - ); - - env.blocks.insert(evil_block, vec![mk_id("Evil"), mk_id("Evil.mk")]); - - env - } - - #[test] - fn reject_nested_negative_via_wrap() { - // Evil.mk has field type `Wrap Evil`. Wrap's constructor puts its param - // in negative position: `(α → Bool) → Wrap α`. So `Evil` appears in - // `(Evil → Bool)` — a negative occurrence smuggled through nesting. - // The positivity checker must reject this. - let mut env = wrap_evil_env(); - let mut tc = TypeChecker::new(&mut env); - let result = tc.check_const(&mk_id("Evil")); - assert!( - result.is_err(), - "Evil should be rejected: negative occurrence through nested Wrap" - ); - } - - fn negative_self_function_env(is_unsafe: bool) -> KEnv { - let mut env = bool_env(); - let block = mk_id("Bad"); - - env.insert( - mk_id("Bad"), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe, - block: block.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![mk_id("Bad.mk")], - lean_all: (), - }, - ); - - // Bad.mk : (Bad -> Bool) -> Bad. The occurrence of Bad in the - // function domain is negative and must be rejected unless Bad is unsafe. - env.insert( - mk_id("Bad.mk"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe, - lvls: 0, - induct: mk_id("Bad"), - cidx: 0, - params: 0, - fields: 1, - ty: pi(pi(cnst("Bad", &[]), cnst("Bool", &[])), cnst("Bad", &[])), - }, - ); - - env.blocks.insert(block, vec![mk_id("Bad"), mk_id("Bad.mk")]); - env - } - - #[test] - fn reject_safe_negative_self_function() { - let mut env = negative_self_function_env(false); - let mut tc = TypeChecker::new(&mut env); - assert!( - tc.check_const(&mk_id("Bad")).is_err(), - "safe negative inductive should be rejected" - ); - } - - #[test] - fn accept_unsafe_negative_self_function() { - let mut env = negative_self_function_env(true); - let mut tc = TypeChecker::new(&mut env); - assert!( - tc.check_const(&mk_id("Bad")).is_ok(), - "unsafe inductive should skip positivity like Lean" - ); - } - - /// Valid nesting: `Tree : Type` with `Tree.node : List Tree → Tree`. - /// List's constructor puts its param in strictly positive position only - /// (as `head : α` and `tail : List α`), so this is fine. - #[test] - fn accept_valid_nested_list_tree() { - let mut env = list_env(); - - // Tree : Type (0 params, 0 indices, recursive via List nesting) - let tree_block = mk_id("Tree"); - env.insert( - mk_id("Tree"), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: tree_block.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![mk_id("Tree.node")], - lean_all: (), - }, - ); - - // Tree.node : List.{1} Tree → Tree - // List.{1} Tree : Sort 1 (List at universe 1, applied to Tree) - let list_tree = - app(cnst("List", &[AU::succ(AU::zero())]), cnst("Tree", &[])); - let tree_node_ty = pi(list_tree, cnst("Tree", &[])); - env.insert( - mk_id("Tree.node"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Tree"), - cidx: 0, - params: 0, - fields: 1, - ty: tree_node_ty, - }, - ); - - env.blocks.insert(tree_block, vec![mk_id("Tree"), mk_id("Tree.node")]); - - let mut tc = TypeChecker::new(&mut env); - let result = tc.check_const(&mk_id("Tree")); - assert!( - result.is_ok(), - "Tree with List nesting should be accepted, got: {:?}", - result.err() - ); - } - - /// A helper family that carries the root may itself use an already-active - /// helper at a specialization unrelated to the root. That unrelated use is - /// not a recursive occurrence. This is the small analogue of - /// `TacticParsedSnapshot`, where traversing - /// `Option (SnapshotTask TacticParsedSnapshot)` later encounters - /// `Option Syntax`. - #[test] - fn accept_distinct_specializations_of_active_nested_helper() { - let mut env = nat_env(); - - // Opt : Type -> Type. - let opt = mk_id("Opt"); - let opt_none = mk_id("Opt.none"); - let opt_some = mk_id("Opt.some"); - env.insert( - opt.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 1, - indices: 0, - is_unsafe: false, - block: opt.clone(), - member_idx: 0, - ty: pi(sort1(), sort1()), - ctors: vec![opt_none.clone(), opt_some.clone()], - lean_all: (), - }, - ); - env.insert( - opt_none.clone(), - KConst::Ctor { - name: (), - level_params: (), - lvls: 0, - cidx: 0, - params: 1, - fields: 0, - is_unsafe: false, - induct: opt.clone(), - ty: pi(sort1(), app(cnst("Opt", &[]), var(0))), - }, - ); - env.insert( - opt_some.clone(), - KConst::Ctor { - name: (), - level_params: (), - lvls: 0, - cidx: 1, - params: 1, - fields: 1, - is_unsafe: false, - induct: opt.clone(), - ty: pi(sort1(), pi(var(0), app(cnst("Opt", &[]), var(1)))), - }, - ); - env.blocks.insert(opt.clone(), vec![opt.clone(), opt_none, opt_some]); - - // Helper A has an unrelated `Opt Nat`, a root-carrying `Opt A`, and a - // positive A field. - let helper = mk_id("Helper"); - let helper_mk = mk_id("Helper.mk"); - env.insert( - helper.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 1, - indices: 0, - is_unsafe: false, - block: helper.clone(), - member_idx: 0, - ty: pi(sort1(), sort1()), - ctors: vec![helper_mk.clone()], - lean_all: (), - }, - ); - env.insert( - helper_mk.clone(), - KConst::Ctor { - name: (), - level_params: (), - lvls: 0, - cidx: 0, - params: 1, - fields: 3, - is_unsafe: false, - induct: helper.clone(), - ty: pi( - sort1(), - pi( - app(cnst("Opt", &[]), cnst("Nat", &[])), - pi( - app(cnst("Opt", &[]), var(1)), - pi(var(2), app(cnst("Helper", &[]), var(3))), - ), - ), - ), - }, - ); - env.blocks.insert(helper.clone(), vec![helper.clone(), helper_mk]); - - // Root.mk : Opt (Helper Root) -> Root. - let root = mk_id("NestedReuseRoot"); - let root_mk = mk_id("NestedReuseRoot.mk"); - env.insert( - root.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: root.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![root_mk.clone()], - lean_all: (), - }, - ); - let nested = app( - cnst("Opt", &[]), - app(cnst("Helper", &[]), cnst("NestedReuseRoot", &[])), - ); - env.insert( - root_mk.clone(), - KConst::Ctor { - name: (), - level_params: (), - lvls: 0, - cidx: 0, - params: 0, - fields: 1, - is_unsafe: false, - induct: root.clone(), - ty: pi(nested, cnst("NestedReuseRoot", &[])), - }, - ); - env.blocks.insert(root.clone(), vec![root.clone(), root_mk]); - - let mut tc = TypeChecker::new(&mut env); - let result = tc.check_const(&root); - assert!( - result.is_ok(), - "distinct helper specializations must not be mistaken for recursive non-uniformity: {result:?}" - ); - } - - // --------------------------------------------------------------------- - // Regression tests for the P1 soundness gaps closed in the 2026-04 - // hardening pass. - // --------------------------------------------------------------------- - - /// P1-1 regression: a recursor with a syntactically well-typed but - /// semantically *swapped* rule RHS must be rejected by `check_recursor` - /// at the `is_def_eq(&gen_rule.rhs, &stored_rule.rhs)` gate - /// (see `inductive.rs:3218`). Without that gate, iota reduction could - /// produce the wrong minor for a given constructor — the P1-1 scenario - /// from the adversarial review. - #[test] - fn reject_bool_rec_with_swapped_rules() { - // Build `bool_env`, then replace `Bool.rec` with a version whose - // rule 0 (for `Bool.true`) has the body of rule 1 (`h_false`) and - // vice-versa. Both RHSes still have the correct type (each minor has - // type `motive (Bool.true/false)` — motive is Var(2) under the λ₃, - // so `var(1)` and `var(0)` both typecheck as the minor premise), but - // iota would produce the wrong value for the given ctor. - let mut env = bool_env(); - let rec_block = mk_id("Bool.rec.block"); - - // Rebuild recursor type and rule-body domains exactly as `bool_env` - // does, then swap which Var is returned in each rule. - let motive_ty = pi(cnst("Bool", &[]), AE::sort(param(0))); - let minor_true = app(var(0), cnst("Bool.true", &[])); - let minor_false = app(var(1), cnst("Bool.false", &[])); - let major_ty = cnst("Bool", &[]); - let ret = app(var(3), var(0)); - let rec_ty = pi( - motive_ty.clone(), - pi(minor_true.clone(), pi(minor_false.clone(), pi(major_ty, ret))), - ); - - // SWAPPED rules: rule 0 returns `h_false` (var 0), rule 1 returns `h_true` (var 1). - // Canonical: rule 0 returns `h_true` (var 1), rule 1 returns `h_false` (var 0). - let motive_dom = motive_ty; - let h_true_dom = minor_true; - let h_false_dom = minor_false; - let rule_true_rhs_swapped = lam( - motive_dom.clone(), - lam( - h_true_dom.clone(), - lam(h_false_dom.clone(), var(0)), // wrong: should be var(1) - ), - ); - let rule_false_rhs_swapped = lam( - motive_dom, - lam( - h_true_dom, - lam(h_false_dom, var(1)), // wrong: should be var(0) - ), - ); - - env.insert( - mk_id("Bool.rec"), - KConst::Recr { - name: (), - level_params: (), - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 2, - block: rec_block, - member_idx: 0, - ty: rec_ty, - rules: vec![ - super::super::constant::RecRule { - ctor: (), - fields: 0, - rhs: rule_true_rhs_swapped, - }, - super::super::constant::RecRule { - ctor: (), - fields: 0, - rhs: rule_false_rhs_swapped, - }, - ], - lean_all: (), - }, - ); - - let mut tc = TypeChecker::new(&mut env); - tc.check_const(&mk_id("Bool")).unwrap(); - let result = tc.check_const(&mk_id("Bool.rec")); - assert!( - result.is_err(), - "Bool.rec with swapped rules must be rejected (P1-1 regression), got: Ok" - ); - } - - /// P1-2 regression: two mutual inductives whose parameter-prefix types - /// disagree must be rejected by `check_inductive` at the S3b gate. - /// Without this, recursor generation (which pulls the shared-param - /// prefix from the first peer) would produce a de-Bruijn mismatch when - /// iota-reducing against a ctor of the second peer. - #[test] - fn reject_mutual_peers_with_mismatched_param_domains() { - let mut env = KEnv::new(); - let block = mk_id("Mut"); - - // Peer 1: `M1 : (α : Sort 1) → Sort 1` (one Type parameter) - let m1_ty = pi(sort1(), sort1()); - env.insert( - mk_id("M1"), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 1, - indices: 0, - is_unsafe: false, - block: block.clone(), - member_idx: 0, - ty: m1_ty, - ctors: vec![], - lean_all: (), - }, - ); - - // Peer 2: `M2 : (α : Sort 0) → Sort 1` (one *Prop* parameter) - // Same param count as M1 so we defeat the arity short-circuit and - // exercise the domain-agreement path specifically. - let m2_ty = pi(AE::sort(AU::zero()), sort1()); - env.insert( - mk_id("M2"), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 1, - indices: 0, - is_unsafe: false, - block: block.clone(), - member_idx: 1, - ty: m2_ty, - ctors: vec![], - lean_all: (), - }, - ); - - env.blocks.insert(block, vec![mk_id("M1"), mk_id("M2")]); - - let mut tc = TypeChecker::new(&mut env); - let result = tc.check_const(&mk_id("M1")); - assert!( - result.is_err(), - "mutual peers with different param domains must be rejected \ - (P1-2 regression), got: Ok" - ); - } - - /// P1-2 sanity: two mutual inductives with matching parameter-prefix - /// types must pass the peer agreement check. - #[test] - fn accept_mutual_peers_with_matching_param_domains() { - let mut env = KEnv::new(); - let block = mk_id("Mut"); - - // Both peers share the param prefix `(α : Sort 1)`. - let shared_ty = pi(sort1(), sort1()); - for (i, name) in ["M1", "M2"].iter().enumerate() { - env.insert( - mk_id(name), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 1, - indices: 0, - is_unsafe: false, - block: block.clone(), - member_idx: i as u64, - ty: shared_ty.clone(), - ctors: vec![], - lean_all: (), - }, - ); - } - env.blocks.insert(block, vec![mk_id("M1"), mk_id("M2")]); - - let mut tc = TypeChecker::new(&mut env); - let result = tc.check_const(&mk_id("M1")); - assert!( - result.is_ok(), - "mutual peers with identical param domains must be accepted \ - (P1-2 sanity), got: {:?}", - result.err() - ); - } - - #[test] - fn reject_mutual_peers_with_mismatched_universe_arity() { - let mut env = KEnv::new(); - let block = mk_id("Mut"); - for (i, (name, lvls)) in [("M1", 0), ("M2", 1)].iter().enumerate() { - env.insert( - mk_id(name), - KConst::Indc { - name: (), - level_params: (), - lvls: *lvls, - params: 0, - indices: 0, - is_unsafe: false, - block: block.clone(), - member_idx: i as u64, - ty: sort1(), - ctors: vec![], - lean_all: (), - }, - ); - } - env.blocks.insert(block, vec![mk_id("M1"), mk_id("M2")]); - - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&mk_id("M1")) { - Err(TcError::Other(s)) => assert!(s.contains("same universe arity")), - other => panic!("expected mutual universe-arity mismatch, got {other:?}"), - } - } - - #[test] - fn reject_mutual_peers_with_mismatched_safety() { - let mut env = KEnv::new(); - let block = mk_id("Mut"); - for (i, (name, is_unsafe)) in - [("M1", false), ("M2", true)].iter().enumerate() - { - env.insert( - mk_id(name), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: *is_unsafe, - block: block.clone(), - member_idx: i as u64, - ty: sort1(), - ctors: vec![], - lean_all: (), - }, - ); - } - env.blocks.insert(block, vec![mk_id("M1"), mk_id("M2")]); - - let mut tc = TypeChecker::new(&mut env); - match tc.check_const(&mk_id("M1")) { - Err(TcError::Other(s)) => assert!(s.contains("same safety flag")), - other => panic!("expected mutual safety mismatch, got {other:?}"), - } - } - - /// P1-2 regression: two mutual inductives with *different* parameter - /// counts must also be rejected — at the explicit `peer_params != params` - /// arm of S3b, prior to reaching domain comparison. - #[test] - fn reject_mutual_peers_with_mismatched_param_count() { - let mut env = KEnv::new(); - let block = mk_id("Mut"); - - // Peer 1: one param. - env.insert( - mk_id("M1"), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 1, - indices: 0, - is_unsafe: false, - block: block.clone(), - member_idx: 0, - ty: pi(sort1(), sort1()), - ctors: vec![], - lean_all: (), - }, - ); - // Peer 2: zero params. - env.insert( - mk_id("M2"), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block.clone(), - member_idx: 1, - ty: sort1(), - ctors: vec![], - lean_all: (), - }, - ); - env.blocks.insert(block, vec![mk_id("M1"), mk_id("M2")]); - - let mut tc = TypeChecker::new(&mut env); - let result = tc.check_const(&mk_id("M1")); - assert!( - result.is_err(), - "mutual peers with different param counts must be rejected, got: Ok" - ); - } - - /// P1-3 regression: universe substitution with fewer universes than - /// the type demands must return `UnivParamOutOfRange` rather than - /// silently producing an orphan `Param` node. - #[test] - fn subst_univ_rejects_out_of_range_param() { - use super::super::error::TcError; - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - // Expression `Sort u` where `u = Param(0)`. Supplying zero universes - // to substitute makes `Param(0)` out of range. - let e = AE::sort(param(0)); - let result = tc.instantiate_univ_params(&e, &[]); - // Empty `us` currently short-circuits with a clone (happy path for - // the overwhelmingly common "no params to substitute" case), so - // call the inner substitution directly with an empty slice. - let _ = result; // ignore the fast-path result - let direct = tc.subst_univ(¶m(0), &[]); - assert!( - matches!(direct, Err(TcError::UnivParamOutOfRange { idx: 0, bound: 0 })), - "subst_univ with empty us must return UnivParamOutOfRange, got: {direct:?}" - ); - - // And in a non-empty-but-still-too-short slice, the error carries - // the correct `idx` and `bound`. - let u = AU::zero(); - let direct2 = tc.subst_univ(¶m(3), std::slice::from_ref(&u)); - assert!( - matches!(direct2, Err(TcError::UnivParamOutOfRange { idx: 3, bound: 1 })), - "subst_univ with too-short us must report correct idx/bound, got: {direct2:?}" - ); - } -} diff --git a/crates/kernel/src/infer.rs b/crates/kernel/src/infer.rs deleted file mode 100644 index 0713199de..000000000 --- a/crates/kernel/src/infer.rs +++ /dev/null @@ -1,1015 +0,0 @@ -//! Type inference. - -use super::constant::KConst; -use super::error::{TcError, u64_to_usize}; -use super::expr::{ExprData, KExpr}; -use super::id::KId; -use super::lctx::LocalDecl; -use super::level::KUniv; -use super::mode::KernelMode; -use super::primitive::is_native_evaluation_addr; -use super::subst::{abstract_fvars, cheap_beta_reduce, instantiate_rev, subst}; -use super::tc::{TypeChecker, collect_app_spine}; - -mod application; -pub(crate) use application::PrefixAdmission; -mod binders; -pub(crate) mod summary; - -/// Emit detailed `[app diff]` trace when `infer`'s App path rejects an -/// argument via `AppTypeMismatch`. Off by default — every rejection in a -/// kernel-check pass would print multiple whnf dumps per failing constant, -/// drowning normal `FAIL` lines. Set `IX_APP_DIFF=1` when investigating -/// why a specific `a_ty` and `dom` don't match after reduction. Pairs -/// with the `a_ty` / `dom` pair already printed by the error display. -static IX_APP_DIFF: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_APP_DIFF").is_ok()); - -/// Dump the full function/type/argument context when App inference fails -/// because the inferred function type is not a forall. Off by default: these -/// terms can be enormous in mathlib and hide the constant-level failure line. -/// Set `IX_INFER_APP_FORALL_DUMP=1`, optionally with -/// `IX_KERNEL_DEBUG_CONST=`, for targeted debugging. -static IX_INFER_APP_FORALL_DUMP: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_INFER_APP_FORALL_DUMP").is_ok()); - -/// When set, log every 100K `infer` entries (total, across cache hits -/// and real calls). A check using millions of infer calls points to a -/// bloated term or a mis-firing cache. Pairs with `IX_DEF_EQ_COUNT_LOG` -/// / `IX_WHNF_COUNT_LOG` for a full picture of per-check hotspots. -static IX_INFER_COUNT_LOG: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_INFER_COUNT_LOG").is_ok()); - -static INFER_COUNT: std::sync::atomic::AtomicUsize = - std::sync::atomic::AtomicUsize::new(0); - -impl TypeChecker<'_, M> { - pub fn infer(&mut self, e: &KExpr) -> Result, TcError> { - self.check_input_context()?; - 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 { - log::info!("[infer] count={n}"); - } - } - let infer_only = self.infer_only; - - let cache_key = self.infer_key(e); - // Full-mode results are validated and may be consumed by either mode. - if let Some(cached) = self.env.infer_cache.get(&cache_key) { - self.env.perf.record_infer_hit(); - return Ok(cached.clone()); - } - self.env.perf.record_infer_miss(); - if !infer_only { - self.record_hot_miss("infer", e); - } - // Infer-only results skipped argument/let validation, so only infer-only - // callers may reuse them. - if infer_only { - if let Some(cached) = self.env.infer_only_cache.get(&cache_key) { - self.env.perf.record_infer_only_hit(); - return Ok(cached.clone()); - } - self.env.perf.record_infer_only_miss(); - self.record_hot_miss("infer-only", e); - } - self.check_stack()?; - - let ty = match e.data() { - // Legacy de Bruijn lookup: still used by inductive validation paths - // that push types via `push_local`/`push_let` rather than opening - // binders into fvars. Keeps the dual-bookkeeping correctness during - // the partial fvar transition (Stage B of the plan). - ExprData::Var(i, _, _) => self.lookup_var(*i)?, - - // Free variable: look up the type stored in the active local - // context. No lift is needed: every type pushed to `lctx` is closed - // under fvar identity (its outer Vars/FVars were already - // instantiate_rev'd or were absent), so the stored type is depth- - // invariant. Mirrors lean4lean's `inferType` `.fvar` branch. - ExprData::FVar(id, _, _) => match self.lctx.find(*id) { - Some(decl) => decl.ty().clone(), - None => { - return Err(TcError::Other(format!( - "infer: unknown FVar({id}); not bound in the active local context" - ))); - }, - }, - - ExprData::Sort(u, _) => { - let u2 = KUniv::succ(u.clone()); - self.intern(KExpr::sort(u2)) - }, - - ExprData::Const(id, us, _) => { - // A checked declaration may not refer to a native-evaluation - // constant: the certified checker blocks such declarations. - // Inference without validation (`infer_only`, which also drives - // auxiliary-definition generation) is unaffected. - if !infer_only && is_native_evaluation_addr(&id.addr) { - return Err(TcError::NativeEvaluation(id.addr.clone())); - } - let c = self.get_const(id)?; - // A quotient record means the quotient only at its primitive - // address: for a bound artifact, once the Quot group is admitted. - if matches!(c, KConst::Quot { .. }) && !self.is_quot_primitive(id)? { - return Err(TcError::Other(format!( - "quotient record {} is not an admitted quotient primitive", - id.addr.hex() - ))); - } - // Unsafe axioms belong to the unsafe tier, which safe declarations - // cannot reference; the policy is for the safe theory. - if !infer_only - && matches!(c, KConst::Axio { is_unsafe: false, .. }) - && !self.prims.axiom_usable(&id.addr) - { - return Err(TcError::Other(format!( - "axiom {} is not an admitted standard axiom", - id.addr.hex() - ))); - } - if u64_to_usize::(c.lvls())? != us.len() { - return Err(TcError::UnivParamMismatch { - expected: c.lvls(), - got: us.len(), - }); - } - let ty = c.ty().clone(); - self.instantiate_univ_params(&ty, us)? - }, - - ExprData::App(f, _, _) if matches!(f.data(), ExprData::App(..)) => { - self.infer_app_spine(e)? - }, - - ExprData::App(f, a, _) => { - 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() { - log::info!("[infer App] ensure_forall FAILED"); - log::info!( - " const: {}", - self.debug_label.as_deref().unwrap_or("") - ); - 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() { - log::info!(" ff: {ff}"); - log::info!(" ff addr: {:?}", ff.addr()); - if let Ok(ff_ty) = self.infer(ff) { - log::info!(" ff_ty: {ff_ty}"); - log::info!(" ff_ty addr: {:?}", ff_ty.addr()); - if let Ok((dom2, cod2)) = self.ensure_forall(&ff_ty) { - log::info!(" ff_ty dom: {dom2}"); - log::info!(" ff_ty cod: {cod2}"); - } - } - log::info!(" fa: {fa}"); - } - } - })?; - self.check_app_argument(f, a, &dom)?; - subst(&mut self.env.intern, &cod, a, 0) - }, - - // Avoid telescope-vector allocation on the existing single-binder - // path. Telescopes can open their terminal body once per batch. - ExprData::Lam(_, _, _, _, body, _) - if matches!(body.data(), ExprData::Lam(..)) => - { - self.infer_lambda_telescope(e)? - }, - - ExprData::All(_, _, _, _, _, body, _) - if matches!(body.data(), ExprData::All(..)) => - { - self.infer_forall_telescope(e)? - }, - - ExprData::Lam(name, bi, contract, ty, body, _) => { - if !infer_only { - let t = self.infer(ty)?; - self.ensure_sort(&t)?; - } - // Open the binder with a fresh fvar. Mirrors lean4lean - // `inferLambda` (TypeChecker.lean:122) and the C++ - // `infer_lambda` (refs/lean4/src/kernel/type_checker.cpp:116). - self.with_lctx_scope(|tc| { - let fv_id = tc.fresh_fvar_id(); - let fv = tc.intern(KExpr::fvar(fv_id, name.clone())); - tc.lctx.push( - fv_id, - LocalDecl::CDecl { - name: name.clone(), - bi: bi.clone(), - ty: ty.clone(), - contract: *contract, - result: ixon::contract::ValueContract::shared(), - }, - ); - let body_open = instantiate_rev(&mut tc.env.intern, body, &[fv]); - let body_ty = tc.infer(&body_open)?; - // Peephole-reduce App(λ.., ..) shapes inside the inferred type - // before wrapping in the Pi. Idempotent in the Pi case, so - // outer frames pay nothing. - let body_ty = cheap_beta_reduce(&mut tc.env.intern, &body_ty); - // Close back: abstract the fvar and wrap in `All` with anonymous - // name + default binder info (matching the pre-fvar legacy shape; - // the Lam's user-facing name does not propagate into the - // inferred Pi type). Recursor coherence relies on this exact - // shape — `lctx.mk_pi` would preserve the Lam's `name`/`bi`, - // diverging from what `inductive.rs::build_recursor_*` produces - // canonically. - let abstracted = - abstract_fvars(&mut tc.env.intern, &body_ty, &[fv_id]); - Ok(tc.intern(KExpr::all_contract( - M::meta_field(ix_common::env::Name::anon()), - M::meta_field(ix_common::env::BinderInfo::Default), - *contract, - ixon::contract::ValueContract::shared(), - ty.clone(), - abstracted, - ))) - })? - }, - - ExprData::All(name, bi, contract, result, ty, body, _) => { - let ty_ty = self.infer(ty)?; - let u1 = self.ensure_sort(&ty_ty)?; - self.with_lctx_scope(|tc| { - let fv_id = tc.fresh_fvar_id(); - let fv = tc.intern(KExpr::fvar(fv_id, name.clone())); - if crate::env_var("IX_FVAR_TRACE").is_ok() { - log::info!( - "[fvar All push] fv={fv_id} ty.addr={:?} ty.lbr={} ctx_len_before_push={} body.lbr={}", - ty.addr(), - ty.lbr(), - tc.ctx.len(), - body.lbr(), - ); - log::info!(" ty data: {:?}", ty.data()); - } - tc.lctx.push( - fv_id, - LocalDecl::CDecl { - name: name.clone(), - bi: bi.clone(), - ty: ty.clone(), - contract: *contract, - result: *result, - }, - ); - let body_open = instantiate_rev(&mut tc.env.intern, body, &[fv]); - let body_ty = tc.infer(&body_open)?; - let u2 = tc.ensure_sort(&body_ty)?; - let u = KUniv::imax(u1, u2); - Ok(tc.intern(KExpr::sort(u))) - })? - }, - - ExprData::Let(name, ty, val, body, contract, _) => { - if !infer_only { - let t = self.infer(ty)?; - self.ensure_sort(&t)?; - let val_ty = self.infer(val)?; - if !self.is_def_eq(&val_ty, ty)? { - return Err(TcError::DeclTypeMismatch); - } - } - // Open with let-bound fvar. Mirrors lean4lean `inferLet` - // (TypeChecker.lean:165). The let value lives in the LDecl so - // WHNF can zeta-reduce on FVar(let) lookup, and so the closing - // step below produces a `Let` wrapper whose body is the - // abstracted body_ty. - self.with_lctx_scope(|tc| { - let fv_id = tc.fresh_fvar_id(); - let fv = tc.intern(KExpr::fvar(fv_id, name.clone())); - tc.lctx.push( - fv_id, - LocalDecl::LDecl { - name: name.clone(), - ty: ty.clone(), - val: val.clone(), - contract: *contract, - }, - ); - let body_open = instantiate_rev(&mut tc.env.intern, body, &[fv]); - let body_ty = tc.infer(&body_open)?; - // Eagerly substitute `val` for the let's fvar in the inferred - // type, then cheap-beta. This matches the pre-fvar behavior of - // `inferLet` (which used a single `subst(body_ty, val, 0)` after - // pop) and avoids leaking a `Let` wrapper into cached infer - // results, which would change cache shapes for downstream - // consumers. Equivalent to `lctx.mk_pi([fv_id], body_ty)` - // followed by zeta — we collapse directly. - let abstracted = - abstract_fvars(&mut tc.env.intern, &body_ty, &[fv_id]); - let r = subst(&mut tc.env.intern, &abstracted, val, 0); - Ok(cheap_beta_reduce(&mut tc.env.intern, &r)) - })? - }, - - ExprData::Prj(struct_id, field, val, _) => { - let struct_id = struct_id.clone(); - let val_ty = self.infer(val)?; - self.infer_proj(&struct_id, *field, val, &val_ty)? - }, - - ExprData::Nat(..) => self.infer_nat_type()?, - ExprData::Str(..) => self.infer_str_type()?, - }; - - if !infer_only { - self.env.infer_cache.insert(cache_key, ty.clone()); - } else { - self.env.infer_only_cache.insert(cache_key, ty.clone()); - } - Ok(ty) - } - - fn infer_proj( - &mut self, - struct_id: &KId, - field: u64, - val: &KExpr, - val_ty: &KExpr, - ) -> Result, TcError> { - use super::level::univ_eq; - use super::tc::collect_app_spine; - - let wty = self.whnf(val_ty)?; - let (head, args) = collect_app_spine(&wty); - - let head_id = match head.data() { - ExprData::Const(id, _, _) => id, - _ => { - return Err(TcError::Other( - "projection: struct type is not a constant".into(), - )); - }, - }; - if head_id.addr != struct_id.addr { - return Err(TcError::Other( - "projection: type mismatch with declared struct".into(), - )); - } - - let (i_levels, num_params, num_indices, ctors) = match self - .try_get_const(head_id)? - { - Some(KConst::Indc { params, indices, ctors, .. }) => { - let levels = match head.data() { - ExprData::Const(_, us, _) => us.clone(), - _ => unreachable!(), - }; - ( - levels, - u64_to_usize::(params)?, - u64_to_usize::(indices)?, - ctors.clone(), - ) - }, - _ => { - return Err(TcError::Other("projection: not an inductive type".into())); - }, - }; - - if ctors.len() != 1 { - return Err(TcError::Other( - "projection: inductive must have exactly one constructor".into(), - )); - } - - // Check if the structure lives in Prop. Do this from the inductive - // declaration's result sort instead of inferring the full applied value - // type: projection-heavy proof terms otherwise re-infer every parameter - // and index argument just to recover a universe that is declaration-local. - let is_prop_struct = self.inductive_app_is_prop( - head_id, - &i_levels, - num_params + num_indices, - )?; - - let ctor_ty = match self.try_get_const(&ctors[0])? { - Some(c) => c.ty().clone(), - None => { - return Err(TcError::Other("projection: constructor not found".into())); - }, - }; - - let mut r = self.instantiate_univ_params(&ctor_ty, &i_levels)?; - - for i in 0..num_params { - let (_, body) = self - .peel_proj_forall(&r, "projection: expected forall in ctor type")?; - if i < args.len() { - r = subst(&mut self.env.intern, &body, &args[i], 0); - } else { - return Err(TcError::Other("projection: not enough params".into())); - } - } - - for i in 0..=field { - let (dom, body) = - self.peel_proj_forall(&r, "projection: not enough fields")?; - if i == field { - // For Prop structures, the projected field must be in Prop. - if is_prop_struct { - let field_sort_ty = self.infer(&dom)?; - let field_level = self.ensure_sort(&field_sort_ty)?; - if !univ_eq(&field_level, &KUniv::zero()) { - return Err(TcError::Other( - "projection: cannot project data field from Prop structure" - .into(), - )); - } - } - return Ok(dom); - } - // For Prop structures, check if this preceding field is a data field - // that subsequent fields depend on. If so, projection is forbidden. - if is_prop_struct { - let field_sort_ty = self.infer(&dom)?; - let field_level = self.ensure_sort(&field_sort_ty)?; - let is_data = !univ_eq(&field_level, &KUniv::zero()); - // body.lbr() > 0 means the body references Var(0), i.e., depends on this field - if is_data && body.lbr() > 0 { - return Err(TcError::Other( - "projection: forbidden after dependent data field in Prop structure" - .into(), - )); - } - } - let proj = self.intern(KExpr::prj(struct_id.clone(), i, val.clone())); - r = subst(&mut self.env.intern, &body, &proj, 0); - } - - Err(TcError::Other("projection: unreachable".into())) - } - - /// Peel the leading `Π` binder from `e`, returning `(domain, body)`. - /// - /// Tries the syntactic fast path first: if `e` is already - /// `ExprData::All(..)`, no WHNF call is made. Only on miss does it fall - /// back to full `whnf` and re-check. This is the audit Tier 1 #2 fix - /// (`infer.rs:218, 281, 299`); the per-iteration full WHNF on a body - /// mutated by `subst` rarely hits the WHNF cache and re-traverses the - /// substituted body each iteration. - /// - /// `err` is the message used when the binder cannot be peeled even after - /// WHNF — distinct messages are useful for callers (e.g. "expected forall - /// in ctor type" vs. "not enough fields") so the helper takes it as a - /// parameter rather than baking one in. - fn peel_proj_forall( - &mut self, - e: &KExpr, - err: &'static str, - ) -> Result<(KExpr, KExpr), TcError> { - if let ExprData::All(_, _, _, _, dom, body, _) = e.data() { - return Ok((dom.clone(), body.clone())); - } - let w = self.whnf(e)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - Ok((dom.clone(), body.clone())) - }, - _ => Err(TcError::Other(err.into())), - } - } - - fn infer_nat_type(&mut self) -> Result, TcError> { - self.require_role(ixon::role_table::Role::Nat)?; - Ok({ - self.intern(KExpr::cnst(self.primitives()?.nat.clone(), Box::new([]))) - }) - } - - fn infer_str_type(&mut self) -> Result, TcError> { - self.require_role(ixon::role_table::Role::String)?; - Ok({ - self.intern(KExpr::cnst(self.primitives()?.string.clone(), Box::new([]))) - }) - } - - fn inductive_app_is_prop( - &mut self, - ind_id: &KId, - levels: &[KUniv], - binders: usize, - ) -> Result> { - use super::level::{KUniv, univ_eq}; - - let ind_ty = match self.try_get_const(ind_id)? { - Some(KConst::Indc { ty, .. }) => ty, - _ => { - return Err(TcError::Other("projection: not an inductive type".into())); - }, - }; - let mut r = self.instantiate_univ_params(&ind_ty, levels)?; - for _ in 0..binders { - let wr = self.whnf(&r)?; - match wr.data() { - ExprData::All(_, _, _, _, _, body, _) => { - r = body.clone(); - }, - _ => { - return Err(TcError::Other( - "projection: expected forall in inductive type".into(), - )); - }, - } - } - let sort_ty = self.whnf(&r)?; - let level = self.ensure_sort(&sort_ty)?; - Ok(univ_eq(&level, &KUniv::zero())) - } -} - -fn compact_expr(e: &KExpr) -> String { - compact_expr_deep(e, 1) -} - -fn compact_expr_deep(e: &KExpr, depth: usize) -> String { - if depth > 0 { - match e.data() { - ExprData::Lam(_, _, _, ty, body, _) => { - return format!( - "lam(ty={}, body={}) @{} lbr={}", - compact_expr_deep(ty, depth - 1), - compact_expr_deep(body, depth - 1), - short_addr(e), - e.lbr() - ); - }, - ExprData::All(_, _, _, _, ty, body, _) => { - return format!( - "forall(ty={}, body={}) @{} lbr={}", - compact_expr_deep(ty, depth - 1), - compact_expr_deep(body, depth - 1), - short_addr(e), - e.lbr() - ); - }, - ExprData::Let(_, ty, val, body, _, _) => { - return format!( - "let(ty={}, val={}, body={}) @{} lbr={}", - compact_expr_deep(ty, depth - 1), - compact_expr_deep(val, depth - 1), - compact_expr_deep(body, depth - 1), - short_addr(e), - e.lbr() - ); - }, - _ => {}, - } - } - let (head, args) = collect_app_spine(e); - let mut out = compact_head(&head); - if !args.is_empty() { - let shown = args - .iter() - .take(8) - .map(|arg| { - if depth == 0 { - compact_head(arg) - } else { - compact_expr_deep(arg, depth - 1) - } - }) - .collect::>() - .join(", "); - let more = if args.len() > 8 { ", ..." } else { "" }; - out = format!("{out}/{} [{shown}{more}]", args.len()); - } - format!("{out} @{} lbr={}", short_addr(e), e.lbr()) -} - -fn compact_head(e: &KExpr) -> String { - let (head, args) = collect_app_spine(e); - let base = match head.data() { - ExprData::Var(i, _, _) => format!("#{i}"), - ExprData::FVar(id, _, _) => format!("{id}"), - ExprData::Sort(u, _) => format!("Sort({u})"), - ExprData::Const(id, us, _) => format!("{id}.{{{}}}", us.len()), - ExprData::App(..) => "app".to_string(), - ExprData::Lam(..) => "lam".to_string(), - ExprData::All(..) => "forall".to_string(), - ExprData::Let(..) => "let".to_string(), - ExprData::Prj(id, field, val, _) => { - format!("Prj({id}.{field}, {})", compact_head(val)) - }, - ExprData::Nat(v, _, _) => format!("Nat({})", v.0), - ExprData::Str(v, _, _) => format!("Str(len={})", v.len()), - }; - if args.is_empty() { base } else { format!("{base}/{}", args.len()) } -} - -fn short_addr(e: &KExpr) -> String { - format!("uid{}", e.addr()) -} - -#[cfg(test)] -mod tests { - - use super::super::constant::KConst; - use super::super::env::KEnv; - use super::super::error::TcError; - use super::super::expr::{ExprData, KExpr}; - use super::super::id::KId; - use super::super::lctx::LocalDecl; - use super::super::level::KUniv; - use super::super::mode::Anon; - use super::super::tc::TypeChecker; - use bignat::Nat; - use ix_common::address::Address; - use ix_common::env::{DefinitionSafety, ReducibilityHints}; - use ixon::constant::DefKind; - - type AE = KExpr; - type AU = KUniv; - - fn mk_addr(s: &str) -> Address { - Address::hash(s.as_bytes()) - } - fn mk_id(s: &str) -> KId { - KId::new(mk_addr(s), ()) - } - fn sort0() -> AE { - AE::sort(AU::zero()) - } - fn sort1() -> AE { - AE::sort(AU::succ(AU::zero())) - } - - /// Env with: Nat (axiom), id (definition) - fn test_env() -> KEnv { - let mut env = KEnv::new(); - // Nat : Sort 1 - env.insert( - mk_id("Nat"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: sort1(), - }, - ); - // id : Sort 0 → Sort 0 := λ x. x - let id_ty = AE::all((), (), sort0(), sort0()); - let id_val = AE::lam((), (), sort0(), AE::var(0, ())); - env.insert( - mk_id("id"), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Abbrev, - lvls: 0, - ty: id_ty, - val: id_val, - lean_all: (), - block: mk_id("id"), - }, - ); - env - } - - #[test] - fn infer_sort() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - // Sort 0 : Sort 1 - let ty = tc.infer(&sort0()).unwrap(); - assert!(matches!(ty.data(), ExprData::Sort(u, _) if !u.is_zero())); - } - - #[test] - fn infer_var() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - tc.push_local(sort0()); - let ty = tc.infer(&AE::var(0, ())).unwrap(); - // Var(0) has type Sort 0 (the type we pushed) - assert_eq!(ty, sort0()); - tc.pop_local(); - } - - #[test] - fn infer_const() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - let nat = AE::cnst(mk_id("Nat"), Box::new([])); - let ty = tc.infer(&nat).unwrap(); - // Nat : Sort 1 - assert_eq!(ty, sort1()); - } - - #[test] - fn infer_lam() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - // λ (x : Sort 0). x : ∀ (x : Sort 0). Sort 0 - let lam = AE::lam((), (), sort0(), AE::var(0, ())); - let ty = tc.infer(&lam).unwrap(); - assert!(matches!(ty.data(), ExprData::All(..))); - } - - #[test] - fn infer_app() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - // Under a binder with x : Sort 0, id(x) : Sort 0 - tc.push_local(sort0()); - let id_const = AE::cnst(mk_id("id"), Box::new([])); - let app = AE::app(id_const, AE::var(0, ())); - let ty = tc.infer(&app).unwrap(); - assert_eq!(ty, sort0()); - tc.pop_local(); - } - - #[test] - fn infer_all() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - // ∀ (x : Sort 0). Sort 0 : Sort 1 - let all = AE::all((), (), sort0(), sort0()); - let ty = tc.infer(&all).unwrap(); - assert!(matches!(ty.data(), ExprData::Sort(..))); - } - - #[test] - fn infer_nat_lit() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - let n = AE::nat(Nat::from(42u64), mk_addr("42")); - let ty = tc.infer(&n).unwrap(); - // Nat literal type = Nat constant - assert!( - matches!(ty.data(), ExprData::Const(id, _, _) if id.addr == tc.primitives().unwrap().nat.addr) - ); - } - - #[test] - fn infer_cache() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - let e = sort0(); - let t1 = tc.infer(&e).unwrap(); - let t2 = tc.infer(&e).unwrap(); - assert_eq!(t1, t2); - } - - #[test] - fn infer_closed_cache_ignores_context() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - let e = sort0(); - let t1 = tc.infer(&e).unwrap(); - let cache_len = tc.env.infer_cache.len(); - - tc.push_local(sort1()); - let t2 = tc.infer(&e).unwrap(); - assert_eq!(t1, t2); - assert_eq!(tc.env.infer_cache.len(), cache_len); - } - - #[test] - fn infer_open_cache_is_context_sensitive() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - let e = AE::var(0, ()); - - tc.push_local(sort0()); - let t1 = tc.infer(&e).unwrap(); - let cache_len = tc.env.infer_cache.len(); - tc.pop_local(); - - tc.push_local(sort1()); - let t2 = tc.infer(&e).unwrap(); - assert_ne!(t1, t2); - assert!(tc.env.infer_cache.len() > cache_len); - } - - // ========================================================================= - // Error paths - // ========================================================================= - - #[test] - fn infer_unknown_const_errors() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - let bogus = AE::cnst(mk_id("DoesNotExist"), Box::new([])); - match tc.infer(&bogus) { - Err(TcError::UnknownConst(addr)) => { - assert_eq!(addr, mk_addr("DoesNotExist")); - }, - other => panic!("expected UnknownConst, got {other:?}"), - } - } - - #[test] - fn infer_univ_param_count_mismatch() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - // `id` has 0 level params; supplying one should error. - let wrong = AE::cnst(mk_id("id"), Box::new([AU::zero()])); - match tc.infer(&wrong) { - Err(TcError::UnivParamMismatch { expected, got }) => { - assert_eq!(expected, 0); - assert_eq!(got, 1); - }, - other => panic!("expected UnivParamMismatch, got {other:?}"), - } - } - - #[test] - fn infer_var_out_of_range() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - // Empty context, Var(0) → out of range. - match tc.infer(&AE::var(0, ())) { - Err(TcError::VarOutOfRange { idx, ctx_len }) => { - assert_eq!(idx, 0); - assert_eq!(ctx_len, 0); - }, - other => panic!("expected VarOutOfRange, got {other:?}"), - } - } - - #[test] - fn infer_binder_errors_restore_lctx_scope() { - let bad_body = AE::cnst(mk_id("MissingBinderBody"), Box::new([])); - let cases = [ - ("lambda", AE::lam((), (), sort0(), bad_body.clone())), - ("forall", AE::all((), (), sort0(), bad_body.clone())), - ("let", AE::let_((), sort1(), sort0(), bad_body, false)), - ]; - - for (kind, expr) in cases { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - let outer_id = tc.fresh_fvar_id(); - tc.lctx.push(outer_id, LocalDecl::cdecl((), (), sort0())); - let saved = tc.lctx.len(); - - assert!(tc.infer(&expr).is_err(), "{kind} body must fail"); - assert_eq!( - tc.lctx.len(), - saved, - "{kind} inference leaked a binder local after error" - ); - assert!( - tc.lctx.find(outer_id).is_some(), - "{kind} inference damaged the enclosing local context" - ); - } - } - - #[test] - fn infer_app_mismatch_errors() { - // Applying `id : Sort 0 → Sort 0` to a Nat (which has type Nat, not - // Sort 0) should error with AppTypeMismatch. - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - let id_const = AE::cnst(mk_id("id"), Box::new([])); - let nat_lit = AE::nat(Nat::from(0u64), mk_addr("0")); - let app = AE::app(id_const, nat_lit); - match tc.infer(&app) { - Err(TcError::AppTypeMismatch { .. }) => {}, - other => panic!("expected AppTypeMismatch, got {other:?}"), - } - } - - #[test] - fn infer_app_of_non_function_errors() { - // Nat is not a function — applying it should fail with FunExpected. - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - let nat_const = AE::cnst(mk_id("Nat"), Box::new([])); - let app = AE::app(nat_const, sort0()); - match tc.infer(&app) { - Err(TcError::FunExpected { .. }) => {}, - other => panic!("expected FunExpected, got {other:?}"), - } - } - - // ========================================================================= - // Structural path coverage - // ========================================================================= - - #[test] - fn infer_all_returns_imax_of_domain_and_codomain_sorts() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - // ∀ (x : Sort 0). Sort 1 → Sort imax(1, 2) = Sort 2 - let all = AE::all((), (), sort0(), sort1()); - let ty = tc.infer(&all).unwrap(); - match ty.data() { - ExprData::Sort(u, _) => { - // imax(succ(0), succ(succ(0))) = succ(succ(0)), which is never-zero - // so imax degenerates to max. Both operands are explicit numerals, - // result is succ(succ(0)) = 2. - assert!(!u.is_zero()); - }, - other => panic!("expected Sort, got {other:?}"), - } - } - - #[test] - fn infer_let_substitutes_value_into_body_type() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - // let x : Sort 0 := Sort 0 in x - let expr = AE::let_( - (), - sort1(), // x : Sort 1 - sort0(), // x := Sort 0 - AE::var(0, ()), - false, - ); - // Inferred type: body's type with value substituted. Body is Var(0) - // with type Sort 1, so the type is Sort 1. - let ty = tc.infer(&expr).unwrap(); - assert_eq!(ty, sort1()); - } - - #[test] - fn infer_let_value_type_mismatch_errors() { - // let x : Sort 0 := 42 in x → DeclTypeMismatch (42 is a Nat, not a Sort). - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - let nat_val = AE::nat(Nat::from(42u64), mk_addr("42")); - let expr = AE::let_((), sort0(), nat_val, AE::var(0, ()), false); - match tc.infer(&expr) { - Err(TcError::DeclTypeMismatch) => {}, - other => panic!("expected DeclTypeMismatch, got {other:?}"), - } - } - - #[test] - fn infer_str_returns_string_type() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - let s = AE::str("hello".into(), mk_addr("hello")); - let ty = tc.infer(&s).unwrap(); - // Type should be `String` — a constant at the canonical string addr. - match ty.data() { - ExprData::Const(id, _, _) => { - assert_eq!(id.addr, tc.primitives().unwrap().string.addr); - }, - other => panic!("expected Const(String), got {other:?}"), - } - } - - #[test] - fn infer_with_infer_only_skips_app_type_check() { - // In infer-only mode, `infer` must skip the arg-type def-eq check, - // so `id(42)` infers cleanly even though 42's type doesn't match - // `id`'s domain (Sort 0). This is the key property infer-only has. - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - let id_const = AE::cnst(mk_id("id"), Box::new([])); - let nat_lit = AE::nat(Nat::from(0u64), mk_addr("0")); - let app = AE::app(id_const, nat_lit); - let r = tc.with_infer_only(|tc| tc.infer(&app)); - // In full mode this would error; in infer-only it succeeds. - assert!(r.is_ok()); - } - - #[test] - fn infer_only_cache_does_not_validate_full_mode() { - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - let id_const = AE::cnst(mk_id("id"), Box::new([])); - let nat_lit = AE::nat(Nat::from(0u64), mk_addr("0")); - let app = AE::app(id_const, nat_lit); - - let key = tc.infer_key(&app); - assert!(tc.with_infer_only(|tc| tc.infer(&app)).is_ok()); - assert!(!tc.env.infer_only_cache.is_empty()); - assert!(!tc.env.infer_cache.contains_key(&key)); - - match tc.infer(&app) { - Err(TcError::AppTypeMismatch { .. }) => {}, - other => panic!("expected full-mode AppTypeMismatch, got {other:?}"), - } - } - - #[test] - fn infer_is_deterministic_across_contexts() { - // Inferring the same closed expression twice should always yield - // the same interned result. - let mut env = test_env(); - let mut tc = TypeChecker::new(&mut env); - let e = AE::all((), (), sort0(), sort0()); - let t1 = tc.infer(&e).unwrap(); - let t2 = tc.infer(&e).unwrap(); - assert!(t1.hash_eq(&t2)); - } -} diff --git a/crates/kernel/src/infer/application.rs b/crates/kernel/src/infer/application.rs deleted file mode 100644 index 3a964f951..000000000 --- a/crates/kernel/src/infer/application.rs +++ /dev/null @@ -1,198 +0,0 @@ -//! Application inference with delayed telescope substitution. -//! -//! At each step, `ty` under `pending` denotes exactly the type obtained by -//! sequential App inference. Check the next instantiated domain, then peel -//! its raw Pi body. Materialize the residual type at a non-Pi boundary or at -//! the end, plus at most one repeated prefix selected for caching. Arguments -//! live in the caller's context, NOT under the -//! peeled binders, so simultaneous substitution must lift them under nested -//! binders; `instantiate_rev`'s FVar-only shortcut is not appropriate here. - -use smallvec::SmallVec; - -use super::*; -use crate::subst::simul_subst; - -mod prefix; -pub(crate) use prefix::PrefixAdmission; - -impl TypeChecker<'_, M> { - pub(super) fn infer_app_spine( - &mut self, - e: &KExpr, - ) -> Result, TcError> { - // Borrow original prefix nodes (no reconstruction just to probe a cache). - // Stop at the nearest cached prefix in the caller's inference mode. - let mut prefixes: SmallVec<[&KExpr; 8]> = SmallVec::new(); - let mut args: SmallVec<[KExpr; 8]> = SmallVec::new(); - let mut hot_prefix = None; - let mut head = e; - let cached = loop { - let ExprData::App(f, a, _) = head.data() else { break None }; - if !args.is_empty() { - let key = self.infer_key(head); - if let Some(ty) = self.env.infer_cache.get(&key) { - self.env.perf.record_infer_hit(); - break Some(ty.clone()); - } - self.env.perf.record_infer_miss(); - if self.infer_only - && let Some(ty) = self.env.infer_only_cache.get(&key) - { - self.env.perf.record_infer_only_hit(); - break Some(ty.clone()); - } - if self.infer_only { - self.env.perf.record_infer_only_miss(); - self.record_hot_miss("infer-only", head); - } else { - self.record_hot_miss("infer", head); - } - // Prefer the longest repeated prefix. At most ONE dependent suffix - // may be materialized solely for caching in this spine inference. - // Merely observing a miss never publishes a type or validates a term. - if self.prefix_admission.observe(key, self.infer_only) - && hot_prefix.is_none() - { - hot_prefix = Some(prefixes.len()); - } - } - prefixes.push(head); - args.push(a.clone()); - head = f; - }; - let mut ty = match cached { - Some(ty) => ty, - None => self.infer(head)?, - }; - - // args is innermost-de-Bruijn-first (reverse application order). At - // position i, args[i+1..end] contains the already-consumed arguments - // since the last materialization. Slicing avoids reversing/copying a - // growing argument vector for every dependent domain. - let mut end = args.len(); - for i in (0..args.len()).rev() { - let ExprData::App(f, _, _) = prefixes[i].data() else { - unreachable!("only application prefixes were collected") - }; - let (dom, cod) = if let ExprData::All(_, _, _, _, dom, cod, _) = ty.data() - { - let dom = - instantiate_pending(&mut self.env.intern, dom, &args[i + 1..end]); - (dom, cod.clone()) - } else { - // A reducible type/let or a substituted type variable hides the - // next Pi. Flush in the ambient context BEFORE normalization. - ty = instantiate_pending(&mut self.env.intern, &ty, &args[i + 1..end]); - end = i + 1; - let result = self.ensure_forall(&ty); - if result.is_err() - && *IX_INFER_APP_FORALL_DUMP - && self.debug_label_matches_env() - { - log::info!( - "[infer App batch] ensure_forall FAILED: f={f}, f_ty={ty}, a={}", - args[i] - ); - } - result? - }; - self.check_app_argument(f, &args[i], &dom)?; - ty = cod; - - // Every preceding argument has now succeeded in the caller's mode. - // Preserve cheap results and, for ONE repeated prefix, materialize the - // residual type with the SAME ambient-context substitution as the final - // result. Store it only under the existing exact key/mode contract. - // Keep `ty` and `end` delayed for dependent results: caching must not - // turn the rest of the spine back into sequential suffix substitution. - if i > 0 && (ty.lbr() == 0 || hot_prefix == Some(i)) { - let cached_ty = if ty.lbr() == 0 { - ty.clone() - } else { - self.env.perf.record_dependent_prefix_insert(); - instantiate_pending(&mut self.env.intern, &ty, &args[i..end]) - }; - let key = self.infer_key(prefixes[i]); - if self.infer_only { - self.env.infer_only_cache.insert(key, cached_ty); - } else { - self.env.infer_cache.insert(key, cached_ty); - } - if ty.lbr() == 0 { - end = i; - } - } - } - Ok(instantiate_pending(&mut self.env.intern, &ty, &args[..end])) - } - - /// Shared by the single-App and batched paths. Validation is never elided - /// by batching; infer-only continues to have a separate cache contract. - pub(super) fn check_app_argument( - &mut self, - f: &KExpr, - a: &KExpr, - dom: &KExpr, - ) -> Result<(), TcError> { - if self.infer_only { - return Ok(()); - } - let a_ty = self.infer(a)?; - let saved_eager = self.eager_reduce; - self.eager_reduce |= self.is_eager_reduce(a); - let eq = self.is_def_eq(&a_ty, dom); - // Restore even on errors and preserve an enclosing eager scope. - self.eager_reduce = saved_eager; - if eq? { - return Ok(()); - } - if *IX_APP_DIFF && self.debug_label_matches_env() { - let a_whnf = self.whnf(&a_ty); - let d_whnf = self.whnf(dom); - let depth = crate::env_var("IX_APP_DIFF_DEPTH") - .ok() - .and_then(|s| s.parse::().ok()) - .unwrap_or(2); - eprintln!( - "[app diff] AppTypeMismatch at depth={} in {}", - self.ctx.len(), - self.debug_label.as_deref().unwrap_or("") - ); - 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()); - match &a_whnf { - Ok(w) => eprintln!(" a_ty whnf: {}", compact_expr_deep(w, depth)), - Err(e) => eprintln!(" a_ty whnf: ERR {e}"), - } - match &d_whnf { - Ok(w) => eprintln!(" dom whnf: {}", compact_expr_deep(w, depth)), - Err(e) => eprintln!(" dom whnf: ERR {e}"), - } - } - Err(TcError::AppTypeMismatch { - a_ty, - dom: dom.clone(), - depth: self.ctx.len(), - }) - } -} - -fn instantiate_pending( - intern: &mut crate::env::InternTable, - ty: &KExpr, - args: &[KExpr], -) -> KExpr { - match args { - [] => ty.clone(), - [arg] => subst(intern, ty, arg, 0), - _ => simul_subst(intern, ty, args, 0), - } -} - -#[cfg(test)] -mod tests; diff --git a/crates/kernel/src/infer/application/prefix.rs b/crates/kernel/src/infer/application/prefix.rs deleted file mode 100644 index f1cd35dd1..000000000 --- a/crates/kernel/src/infer/application/prefix.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Bounded admission history, NOT a cache of types or validity judgments. -//! -//! A repeated fingerprint nominates a prefix for ordinary inference and -//! materialization. Collisions can only nominate extra work (or forget a hot -//! prefix); actual results always use the exact, mode-separated infer caches. - -use std::hash::{Hash, Hasher}; - -use crate::env::{Addr, CtxAddr}; - -const INITIAL_SLOTS: usize = 64; -const MAX_SLOTS: usize = 4096; - -/// A lazily allocated, direct-mapped two-touch filter. It starts at 512 bytes; -/// collision-heavy checks grow up to 32 KiB. No expressions, -/// contexts, or type results are retained. Reset between checked members. -#[derive(Default)] -pub(crate) struct PrefixAdmission { - seen: Vec, - collisions: usize, -} - -impl PrefixAdmission { - pub(crate) fn observe( - &mut self, - key: (Addr, CtxAddr), - infer_only: bool, - ) -> bool { - let mut hasher = rustc_hash::FxHasher::default(); - (key, infer_only).hash(&mut hasher); - // Zero denotes an empty slot. The extremely rare remapping collision is - // harmless: this is only an allocation/work admission heuristic. - self.observe_fingerprint(hasher.finish().max(1)) - } - - fn slot(fingerprint: u64, len: usize) -> usize { - usize::try_from(fingerprint & (len as u64 - 1)) - .expect("masked to at most MAX_SLOTS - 1") - } - - fn observe_fingerprint(&mut self, fingerprint: u64) -> bool { - if self.seen.is_empty() { - self.seen.resize(INITIAL_SLOTS, 0); - } - let mut slot = Self::slot(fingerprint, self.seen.len()); - if self.seen[slot] == fingerprint { - return true; - } - if self.seen[slot] != 0 && self.seen.len() < MAX_SLOTS { - self.collisions += 1; - if self.collisions == self.seen.len() { - let next_len = self.seen.len() * 2; - let old = std::mem::replace(&mut self.seen, vec![0; next_len]); - for saved in old.into_iter().filter(|&f| f != 0) { - let i = Self::slot(saved, self.seen.len()); - self.seen[i] = saved; - } - self.collisions = 0; - slot = Self::slot(fingerprint, self.seen.len()); - } - } - self.seen[slot] = fingerprint; - false - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn repeated_keys_are_mode_and_context_specific() { - let mut history = PrefixAdmission::default(); - let a = (1, blake3::hash(b"a")); - let b = (1, blake3::hash(b"b")); - assert!(history.seen.is_empty()); - assert!(!history.observe(a, false)); - assert!(history.observe(a, false)); - assert!(!history.observe(a, true)); - assert!(history.observe(a, true)); - assert!(!history.observe(b, true)); - assert!(history.observe(b, true)); - } - - #[test] - fn collisions_forget_history_and_growth_is_bounded() { - let mut history = PrefixAdmission::default(); - for i in 1..100_000 { - // All fingerprints collide, even at the maximum size. - let f = i * MAX_SLOTS as u64; - assert!(!history.observe_fingerprint(f)); - assert!(history.observe_fingerprint(f)); - assert!(history.seen.len() <= MAX_SLOTS); - } - assert_eq!(history.seen.len(), MAX_SLOTS); - assert!(!history.observe_fingerprint(MAX_SLOTS as u64)); - } -} diff --git a/crates/kernel/src/infer/application/tests.rs b/crates/kernel/src/infer/application/tests.rs deleted file mode 100644 index ec00cba75..000000000 --- a/crates/kernel/src/infer/application/tests.rs +++ /dev/null @@ -1,727 +0,0 @@ -//! Differential tests against sequential, one-App-at-a-time inference. - -use super::*; -use crate::env::{InternTable, KEnv}; -use crate::mode::{Anon, Meta}; -use crate::profile::take_op_counts; -use ix_common::address::Address; -use ix_common::env::{BinderInfo, DataValue, Name}; - -fn name(s: &str) -> M::MField { - M::meta_field(Name::str(Name::anon(), s.to_owned())) -} -fn var(i: u64) -> KExpr { - KExpr::var(i, name::("variable")) -} -fn sort(n: u64) -> KExpr { - let mut u = KUniv::zero(); - for _ in 0..n { - u = KUniv::succ(u); - } - KExpr::sort(u) -} -fn all(dom: KExpr, cod: KExpr) -> KExpr { - KExpr::all(name::("binder"), M::meta_field(BinderInfo::Implicit), dom, cod) -} -fn app(f: KExpr, a: KExpr) -> KExpr { - KExpr::app_mdata( - f, - a, - M::meta_field(vec![vec![( - Name::str(Name::anon(), "tag".to_owned()), - DataValue::OfString("application differential".to_owned()), - )]]), - ) -} -fn axiom(env: &mut KEnv, s: &str, ty: KExpr) -> KExpr { - let id = KId::new(Address::hash(s.as_bytes()), name::(s)); - env.insert( - id.clone(), - KConst::Axio { - name: name::(s), - level_params: M::meta_field(vec![]), - is_unsafe: false, - lvls: 0, - ty, - }, - ); - KExpr::cnst(id, Box::new([])) -} - -// Independent copy of the former recursive App rule. Other expression -// constructors use the normal checker, so this isolates the changed rule. -fn reference( - tc: &mut TypeChecker<'_, M>, - e: &KExpr, -) -> Result, TcError> { - let key = tc.infer_key(e); - if let Some(ty) = tc.env.infer_cache.get(&key) { - return Ok(ty.clone()); - } - if tc.infer_only - && let Some(ty) = tc.env.infer_only_cache.get(&key) - { - return Ok(ty.clone()); - } - let ExprData::App(f, a, _) = e.data() else { return tc.infer(e) }; - let f_ty = reference(tc, f)?; - let (dom, cod) = tc.ensure_forall(&f_ty)?; - if !tc.infer_only { - let a_ty = reference(tc, a)?; - let eager = tc.eager_reduce; - tc.eager_reduce |= tc.is_eager_reduce(a); - let eq = tc.is_def_eq(&a_ty, &dom); - tc.eager_reduce = eager; - if !eq? { - return Err(TcError::AppTypeMismatch { a_ty, dom, depth: tc.ctx.len() }); - } - } - let ty = subst(&mut tc.env.intern, &cod, a, 0); - if tc.infer_only { - tc.env.infer_only_cache.insert(key, ty.clone()); - } else { - tc.env.infer_cache.insert(key, ty.clone()); - } - Ok(ty) -} - -fn same_type(a: KExpr, b: KExpr) { - let mut pending = vec![(&a, &b)]; - let mut seen = rustc_hash::FxHashSet::default(); - while let Some((a, b)) = pending.pop() { - if !seen.insert((*a.addr(), *b.addr())) { - continue; - } - assert_eq!(a.mdata(), b.mdata()); - assert_eq!(a.univ_decor(), b.univ_decor()); - assert_eq!(a.lbr(), b.lbr()); - match (a.data(), b.data()) { - (ExprData::App(f, x, _), ExprData::App(g, y, _)) => { - pending.extend([(f, g), (x, y)]) - }, - ( - ExprData::All(n, i, _, _, d, c, _), - ExprData::All(m, j, _, _, e, r, _), - ) - | (ExprData::Lam(n, i, _, d, c, _), ExprData::Lam(m, j, _, e, r, _)) => { - assert_eq!(n, m); - assert_eq!(i, j); - pending.extend([(d, e), (c, r)]); - }, - (ExprData::Var(i, n, _), ExprData::Var(j, m, _)) => { - assert_eq!(i, j); - assert_eq!(n, m); - }, - (ExprData::Let(n, t, v, r, nd, _), ExprData::Let(m, u, w, s, md, _)) => { - assert_eq!(n, m); - assert_eq!(nd, md); - pending.extend([(t, u), (v, w), (r, s)]); - }, - (ExprData::Prj(_, _, v, _), ExprData::Prj(_, _, w, _)) => { - pending.push((v, w)) - }, - _ => {}, - } - } - let mut intern = InternTable::new(); - let a = intern.intern_expr(a); - let b = intern.intern_expr(b); - assert!(a.ptr_eq(&b), "different inferred types: {a:?}\n{b:?}"); -} - -fn fixture(env: &mut KEnv) -> (KExpr, Vec>) { - let a = axiom(env, "A", sort(1)); - let b = axiom(env, "B", all(a.clone(), sort(1))); - let x = axiom(env, "x", a.clone()); - let y = axiom(env, "y", app(b.clone(), x.clone())); - // f : (A : Type) -> (B : A -> Type) -> (x : A) -> B x -> B x - let ty = all( - sort(1), - all( - all(var(0), sort(1)), - all(var(1), all(app(var(1), var(0)), app(var(2), var(1)))), - ), - ); - (axiom(env, "f", ty), vec![a, b, x, y]) -} - -fn dependent() { - for infer_only in [false, true] { - for count in 1..=4 { - let mut env_a = KEnv::::new(); - let mut env_b = KEnv::::new(); - let (mut a, args_a) = fixture(&mut env_a); - let (mut b, args_b) = fixture(&mut env_b); - for i in 0..count { - a = app(a, args_a[i].clone()); - b = app(b, args_b[i].clone()); - } - let mut tc_a = TypeChecker::new(&mut env_a); - let mut tc_b = TypeChecker::new(&mut env_b); - tc_a.infer_only = infer_only; - tc_b.infer_only = infer_only; - same_type(reference(&mut tc_a, &a).unwrap(), tc_b.infer(&b).unwrap()); - assert!(tc_b.lctx.is_empty() && tc_b.ctx.is_empty()); - } - } -} - -#[test] -fn dependent_partial_and_full_applications() { - dependent::(); - dependent::(); -} - -fn boundaries() { - for infer_only in [false, true] { - for hidden in [false, true] { - let mut a = KEnv::new(); - let mut b = KEnv::new(); - let mk = |env: &mut KEnv| { - // An applied type variable, or a let, exposes a new Pi only AFTER - // substituting the first argument into the function's codomain. - let result = if hidden { - KExpr::let_(name::("T"), sort(1), var(0), var(0), false) - } else { - var(0) - }; - let f = axiom(env, "dependentFunction", all(sort(1), result)); - app(app(f, all(sort(0), sort(0))), axiom(env, "P", sort(0))) - }; - let ea = mk(&mut a); - let eb = mk(&mut b); - let mut a = TypeChecker::new(&mut a); - let mut b = TypeChecker::new(&mut b); - a.infer_only = infer_only; - b.infer_only = infer_only; - same_type(reference(&mut a, &ea).unwrap(), b.infer(&eb).unwrap()); - } - } -} - -#[test] -fn flushes_pending_arguments_before_revealing_hidden_pi() { - boundaries::(); - boundaries::(); -} - -fn open_args() { - for infer_only in [false, true] { - let mut a = KEnv::new(); - let mut b = KEnv::new(); - let mk = |env: &mut KEnv| { - // f : (A : Type) -> A -> ((z : A) -> A). Return a function so the - // open replacement A must be lifted underneath the residual binder. - let f = axiom( - env, - "openFunction", - all(sort(1), all(var(0), all(var(1), var(2)))), - ); - app(app(f, var(1)), var(0)) - }; - let ea = mk(&mut a); - let eb = mk(&mut b); - let mut a = TypeChecker::new(&mut a); - let mut b = TypeChecker::new(&mut b); - a.infer_only = infer_only; - b.infer_only = infer_only; - a.push_local(sort(1)); - a.push_local(var(0)); - b.push_local(sort(1)); - b.push_local(var(0)); - same_type(reference(&mut a, &ea).unwrap(), b.infer(&eb).unwrap()); - assert_eq!(b.ctx.len(), 2); - } -} - -#[test] -fn lifts_ambient_arguments_under_residual_binders() { - open_args::(); - open_args::(); -} - -#[test] -fn checks_every_argument_and_keeps_infer_only_cache_separate() { - for bad_index in 0..4 { - let mut env = KEnv::::new(); - let (mut f, mut args) = fixture(&mut env); - args[bad_index] = sort(4); - for a in args { - f = app(f, a); - } - let mut tc = TypeChecker::new(&mut env); - let _ = tc.with_infer_only(|tc| tc.infer(&f)); - assert!(tc.env.infer_cache.is_empty()); - let key = tc.infer_key(&f); - assert!(matches!(tc.infer(&f), Err(TcError::AppTypeMismatch { .. }))); - assert!(!tc.env.infer_cache.contains_key(&key)); - } -} - -#[test] -fn overapplication_and_unknown_heads_are_rejected() { - let mut env = KEnv::::new(); - let (mut f, args) = fixture(&mut env); - for a in args { - f = app(f, a); - } - let f = app(f, sort(0)); - let mut tc = TypeChecker::new(&mut env); - assert!(matches!(tc.infer(&f), Err(TcError::FunExpected { .. }))); - let missing = - KExpr::cnst(KId::new(Address::hash(b"absent"), ()), Box::new([])); - assert!(matches!( - tc.infer(&app(app(missing, sort(0)), sort(0))), - Err(TcError::UnknownConst(_)) - )); -} - -#[test] -fn batched_heads_still_validate_universe_arity_and_scope() { - let mut env = KEnv::::new(); - let id = KId::new(Address::hash(b"universeFunction"), ()); - env.insert( - id.clone(), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - ty: all(KExpr::sort(KUniv::param(0, ())), all(var(0), var(1))), - }, - ); - let mut tc = TypeChecker::new(&mut env); - let wrong_arity = - app(app(KExpr::cnst(id.clone(), Box::new([])), sort(0)), sort(0)); - for infer_only in [false, true] { - tc.infer_only = infer_only; - assert!(matches!( - tc.infer(&wrong_arity), - Err(TcError::UnivParamMismatch { .. }) - )); - } - tc.env.insert( - id.clone(), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - ty: all(KExpr::sort(KUniv::param(1, ())), all(var(0), var(1))), - }, - ); - let bad_scope = - app(app(KExpr::cnst(id, Box::new([KUniv::zero()])), sort(0)), sort(0)); - for infer_only in [false, true] { - tc.infer_only = infer_only; - assert!(matches!( - tc.infer(&bad_scope), - Err(TcError::UnivParamOutOfRange { .. }) - )); - } -} - -#[test] -fn application_cache_does_not_cross_local_let_contexts() { - let mut env = KEnv::::new(); - let a = axiom(&mut env, "A", sort(1)); - let b = axiom(&mut env, "B", sort(1)); - let x = axiom(&mut env, "x", a.clone()); - let f = axiom(&mut env, "id", all(sort(1), all(var(0), var(1)))); - let e = app(app(f, var(1)), var(0)); - let mut tc = TypeChecker::new(&mut env); - tc.push_let(sort(1), a.clone()); - tc.push_let(a.clone(), x.clone()); - let key = tc.infer_key(&e); - tc.infer(&e).unwrap(); - tc.pop_local(); - tc.pop_local(); - tc.push_let(sort(1), b); - tc.push_let(a, x); - assert_ne!(key, tc.infer_key(&e)); - assert!(matches!(tc.infer(&e), Err(TcError::AppTypeMismatch { .. }))); -} - -fn fvar_arguments() { - let mut env = KEnv::::new(); - let f = axiom(&mut env, "id", all(sort(1), all(var(0), all(var(1), var(2))))); - let mut tc = TypeChecker::new(&mut env); - tc.with_lctx_scope(|tc| { - let aid = tc.fresh_fvar_id(); - let a = tc.intern(KExpr::fvar(aid, name::("A"))); - tc.lctx.push( - aid, - LocalDecl::cdecl( - name::("A"), - M::meta_field(BinderInfo::Default), - sort(1), - ), - ); - let xid = tc.fresh_fvar_id(); - let x = tc.intern(KExpr::fvar(xid, name::("x"))); - tc.lctx.push( - xid, - LocalDecl::cdecl( - name::("x"), - M::meta_field(BinderInfo::Default), - a.clone(), - ), - ); - let e = app(app(f, a), x); - for infer_only in [false, true] { - tc.infer_only = infer_only; - tc.env.clear_reduction_caches(); - let expected = reference(tc, &e)?; - tc.env.clear_reduction_caches(); - tc.prefix_admission = Default::default(); - let root = tc.infer_key(&e); - let ExprData::App(prefix, _, _) = e.data() else { unreachable!() }; - let key = tc.infer_key(prefix); - for _ in 0..2 { - tc.env.infer_cache.remove(&root); - tc.env.infer_only_cache.remove(&root); - tc.prefix_admission.observe(key, infer_only); - same_type(expected.clone(), tc.infer(&e)?); - } - assert!( - tc.env.infer_cache.contains_key(&key) - || tc.env.infer_only_cache.contains_key(&key) - ); - } - Ok::<(), TcError>(()) - }) - .unwrap(); - assert!(tc.lctx.is_empty()); -} - -#[test] -fn fvar_arguments_match_sequential_substitution_in_both_modes() { - fvar_arguments::(); - fvar_arguments::(); -} - -#[test] -fn honors_cached_dependent_prefixes() { - let mut env = KEnv::::new(); - let (f, args) = fixture(&mut env); - let prefix = app(app(f, args[0].clone()), args[1].clone()); - let mut tc = TypeChecker::new(&mut env); - let ty = tc.infer(&prefix).unwrap(); - let key = tc.infer_key(&prefix); - assert!(tc.env.infer_cache.get(&key).unwrap().ptr_eq(&ty)); - let tail = app(app(prefix, args[2].clone()), args[3].clone()); - // A valid cached prefix should be enough; no inference of the head is - // needed. Keep the declaration environment intact, remove all other - // synthesis results, then verify that the prefix remains reusable. - tc.env.infer_cache.retain(|k, _| k == &key); - let actual = tc.infer(&tail).unwrap(); - same_type(actual, app(args[1].clone(), args[2].clone())); - assert!(tc.env.infer_cache.get(&key).unwrap().ptr_eq(&ty)); -} - -#[test] -fn eager_scope_is_restored_on_errors_and_success() { - let mut env = KEnv::::new(); - let a = axiom(&mut env, "someValue", sort(0)); - let mut tc = TypeChecker::new(&mut env); - for saved in [false, true] { - tc.eager_reduce = saved; - tc.check_app_argument(&a, &a, &sort(0)).unwrap(); - assert_eq!(tc.eager_reduce, saved); - assert!(tc.check_app_argument(&a, &a, &sort(3)).is_err()); - assert_eq!(tc.eager_reduce, saved); - tc.rec_fuel = 0; - assert!(tc.check_app_argument(&a, &a, &sort(5)).is_err()); - assert_eq!(tc.eager_reduce, saved); - tc.rec_fuel = crate::tc::max_rec_fuel(); - } -} - -fn long_application(env: &mut KEnv, count: u64) -> KExpr { - let mut pack_ty = sort(1); - for _ in 0..count { - pack_ty = all(sort(1), pack_ty); - } - let mut ty = axiom(env, "Pack", pack_ty); - // Every argument occurs in the result, so sequential inference revisits - // a growingly-instantiated Pack application at every telescope step. - for i in (0..count).rev() { - ty = app(ty, var(i)); - } - for _ in 0..count { - ty = all(sort(1), ty); - } - let mut f = axiom(env, "longFunction", ty); - for _ in 0..count { - f = app(f, sort(0)); - } - f -} - -#[test] -fn long_telescope_avoids_quadratic_codomain_construction() { - let mut a = KEnv::new(); - let mut b = KEnv::new(); - let ea = long_application(&mut a, 96); - let eb = long_application(&mut b, 96); - let mut a = TypeChecker::new(&mut a); - let mut b = TypeChecker::new(&mut b); - take_op_counts(); - let expected = reference(&mut a, &ea).unwrap(); - let old = take_op_counts(); - let actual = b.infer(&eb).unwrap(); - let new = take_op_counts(); - eprintln!("application work: sequential={old:?}, batched={new:?}"); - assert!(new.intern_nodes * 4 < old.intern_nodes); - same_type(actual, expected); -} - -fn hot_prefixes() { - for infer_only in [false, true] { - let mut env = KEnv::::new(); - let (f, args) = fixture(&mut env); - let result = app(args[1].clone(), args[2].clone()); - let y2 = axiom(&mut env, "anotherY", result.clone()); - let y3 = axiom(&mut env, "thirdY", result.clone()); - let prefix = - app(app(app(f, args[0].clone()), args[1].clone()), args[2].clone()); - let first = app(prefix.clone(), args[3].clone()); - let mut reference_env = KEnv::::new(); - for (id, declaration) in env.iter() { - reference_env.insert(id, declaration); - } - let mut reference_tc = TypeChecker::new(&mut reference_env); - reference_tc.infer_only = infer_only; - let expected = reference(&mut reference_tc, &first).unwrap(); - let expected_prefix = reference(&mut reference_tc, &prefix).unwrap(); - let mut tc = TypeChecker::new(&mut env); - tc.infer_only = infer_only; - let key = tc.infer_key(&prefix); - same_type(tc.infer(&first).unwrap(), expected.clone()); - assert!(!tc.env.infer_cache.contains_key(&key)); - assert!(!tc.env.infer_only_cache.contains_key(&key)); - // The bounded filter is allowed to forget intervening collisions. Seed - // this nomination immediately so the cache-contract test is UID-order - // independent, including under parallel unit-test scheduling. - tc.prefix_admission.observe(key, infer_only); - same_type(tc.infer(&app(prefix.clone(), y2)).unwrap(), expected.clone()); - let cache = - if infer_only { &tc.env.infer_only_cache } else { &tc.env.infer_cache }; - same_type(cache[&key].clone(), expected_prefix); - // A third use needs no synthesis of earlier prefixes or the head. - tc.env.infer_cache.retain(|k, _| k == &key); - tc.env.infer_only_cache.retain(|k, _| k == &key); - same_type(tc.infer(&app(prefix, y3)).unwrap(), expected); - if infer_only { - assert!(tc.env.infer_cache.is_empty()); - } - } -} - -#[test] -fn repeated_dependent_prefixes_are_published_in_the_correct_mode() { - hot_prefixes::(); - hot_prefixes::(); -} - -fn invalid_hot_prefixes() { - for bad_index in 0..4 { - let mut env = KEnv::::new(); - let (mut f, mut args) = fixture(&mut env); - args[bad_index] = sort(4); - let mut prefixes = vec![]; - for arg in args { - f = app(f, arg); - prefixes.push(f.clone()); - } - let mut tc = TypeChecker::new(&mut env); - // Warm unchecked results/admission history first. They must never grant - // full-mode validity, even when the same UID is seen repeatedly. - for _ in 0..3 { - let root = tc.infer_key(&f); - tc.env.infer_only_cache.remove(&root); - let _ = tc.with_infer_only(|tc| tc.infer(&f)); - } - for _ in 0..3 { - assert!(matches!(tc.infer(&f), Err(TcError::AppTypeMismatch { .. }))); - for invalid in &prefixes[bad_index..] { - let key = tc.infer_key(invalid); - assert!(!tc.env.infer_cache.contains_key(&key)); - } - } - } -} - -#[test] -fn repeated_invalid_prefixes_and_infer_only_results_never_validate_arguments() { - invalid_hot_prefixes::(); - invalid_hot_prefixes::(); -} - -fn warm_open_prefix() { - for infer_only in [false, true] { - let mut env = KEnv::::new(); - let a = axiom(&mut env, "A", sort(1)); - let b = axiom(&mut env, "B", sort(1)); - let x = axiom(&mut env, "x", a.clone()); - let f = - axiom(&mut env, "openId", all(sort(1), all(var(0), all(var(1), var(2))))); - let prefix = app(f, var(1)); - let e = app(prefix.clone(), var(0)); - let mut tc = TypeChecker::new(&mut env); - tc.infer_only = infer_only; - tc.push_let(sort(1), a.clone()); - tc.push_let(a.clone(), x.clone()); - let root = tc.infer_key(&e); - let key_a = tc.infer_key(&prefix); - let expected = reference(&mut tc, &e).unwrap(); - let expected_prefix = reference(&mut tc, &prefix).unwrap(); - tc.env.clear_reduction_caches(); - for _ in 0..2 { - tc.env.infer_cache.remove(&root); - tc.env.infer_only_cache.remove(&root); - tc.prefix_admission.observe(key_a, infer_only); - same_type(tc.infer(&e).unwrap(), expected.clone()); - } - // The cached residual binder contains correctly lifted ambient Vars. - let cache = - if infer_only { &tc.env.infer_only_cache } else { &tc.env.infer_cache }; - same_type(cache[&key_a].clone(), expected_prefix); - tc.pop_local(); - tc.pop_local(); - tc.push_let(sort(1), b); - tc.push_let(a, x); - let key_b = tc.infer_key(&prefix); - assert_ne!(key_a, key_b); - assert!(!tc.env.infer_cache.contains_key(&key_b)); - assert!(!tc.env.infer_only_cache.contains_key(&key_b)); - if !infer_only { - assert!(matches!(tc.infer(&e), Err(TcError::AppTypeMismatch { .. }))); - } - } -} - -#[test] -fn warmed_dependent_prefixes_lift_ambient_vars_and_respect_let_contexts() { - warm_open_prefix::(); - warm_open_prefix::(); -} - -#[test] -fn hot_long_telescope_materializes_at_most_one_dependent_suffix() { - let mut env = KEnv::new(); - let e = long_application(&mut env, 96); - let mut tc = TypeChecker::new(&mut env); - // Warm every prefix's admission history WITHOUT precomputing types. This - // is the adversarial case for accidentally restoring quadratic batching. - let mut prefixes = vec![]; - let ExprData::App(p, _, _) = e.data() else { unreachable!() }; - let mut p = p; - while let ExprData::App(f, _, _) = p.data() { - let key = tc.infer_key(p); - tc.prefix_admission.observe(key, false); - prefixes.push(p); - p = f; - } - let longest = tc.infer_key(prefixes[0]); - tc.prefix_admission.observe(longest, false); - take_op_counts(); - let expected = tc.infer(&e).unwrap(); - let work = take_op_counts(); - let published = prefixes - .iter() - .filter(|p| { - let key = tc.infer_key(p); - tc.env.infer_cache.contains_key(&key) - }) - .count(); - assert_eq!(published, 1); - assert!(work.intern_nodes < 2000, "suffixes must stay batched: {work:?}"); - tc.env.clear_reduction_caches(); - tc.prefix_admission = Default::default(); - same_type(tc.infer(&e).unwrap(), expected); -} - -#[test] -fn reset_forgets_admission_and_full_prefixes_remain_usable_by_infer_only() { - let mut env = KEnv::::new(); - let (f, args) = fixture(&mut env); - let prefix = - app(app(app(f, args[0].clone()), args[1].clone()), args[2].clone()); - let e = app(prefix.clone(), args[3].clone()); - let mut tc = TypeChecker::new(&mut env); - let key = tc.infer_key(&prefix); - assert!(!tc.prefix_admission.observe(key, false)); - tc.reset(); - assert!(!tc.prefix_admission.observe(key, false)); - let expected = tc.infer(&e).unwrap(); - assert!(tc.env.infer_cache.contains_key(&key)); - let root = tc.infer_key(&e); - tc.env.infer_cache.remove(&root); - tc.env.infer_only_cache.clear(); - tc.env.infer_cache.retain(|k, _| k == &key); - same_type(tc.with_infer_only(|tc| tc.infer(&e)).unwrap(), expected); - assert!(tc.env.infer_only_cache.contains_key(&root)); - assert!(!tc.env.infer_only_cache.contains_key(&key)); - tc.env.clear_reduction_caches(); - assert!(!tc.env.infer_cache.contains_key(&key)); - assert!(!tc.env.infer_only_cache.contains_key(&key)); -} - -#[test] -fn warm_hidden_pi_boundaries_still_flush_pending_arguments() { - for infer_only in [false, true] { - let mut env = KEnv::::new(); - let ty = all(sort(1), var(0)); - let f = axiom(&mut env, "hiddenPrefix", ty); - let p = axiom(&mut env, "P", sort(0)); - let h = app(f, all(sort(0), all(sort(0), sort(0)))); - let e = app(app(h, p.clone()), p); - let mut tc = TypeChecker::new(&mut env); - tc.infer_only = infer_only; - let expected = reference(&mut tc, &e).unwrap(); - tc.env.clear_reduction_caches(); - let root = tc.infer_key(&e); - for _ in 0..3 { - tc.env.infer_cache.remove(&root); - tc.env.infer_only_cache.remove(&root); - same_type(tc.infer(&e).unwrap(), expected.clone()); - } - } -} - -#[test] -fn hot_prefix_reuse_reduces_checked_argument_work() { - let run = |admit: bool| { - let mut env = KEnv::::new(); - let application = long_application(&mut env, 32); - let ExprData::App(prefix, _, _) = application.data() else { - unreachable!() - }; - let tails: Vec<_> = (0..32) - .map(|i| { - let arg = axiom(&mut env, &format!("tailType{i}"), sort(1)); - app(prefix.clone(), arg) - }) - .collect(); - let mut tc = TypeChecker::new(&mut env); - let key = tc.infer_key(prefix); - take_op_counts(); - for tail in tails { - if !admit { - // A cold two-touch filter never nominates a prefix, reproducing the - // previous batched result-cache policy without a production toggle. - tc.prefix_admission = Default::default(); - } else { - tc.prefix_admission.observe(key, false); - } - tc.infer(&tail).unwrap(); - } - take_op_counts() - }; - let cold = run(false); - let hot = run(true); - eprintln!("reused prefix work: cold={cold:?}, hot={hot:?}"); - assert!(hot.def_eq_calls * 4 < cold.def_eq_calls); - assert!(hot.intern_nodes * 2 < cold.intern_nodes); -} diff --git a/crates/kernel/src/infer/binders.rs b/crates/kernel/src/infer/binders.rs deleted file mode 100644 index 1d61707f9..000000000 --- a/crates/kernel/src/infer/binders.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! Batched opening of consecutive lambdas/foralls during inference. -//! -//! Walk the original telescope, opening each domain under its own prefix of -//! fresh locals, then instantiate the terminal body once. Substitution memos -//! remain call-local: different prefixes MUST NOT share logical entries. -//! The outer `infer` call still owns cache lookup/publication; domains and the -//! terminal body use ordinary, mode-separated cached inference. We omit -//! partially opened suffix entries rather than constructing them just to -//! cache them. An unchanged closed suffix can still use an existing result. - -use super::*; - -impl TypeChecker<'_, M> { - /// A suffix with no loose bvars is unaffected by the accumulated opening. - /// Probe only that case: building other suffixes to probe their cache would - /// reintroduce the repeated DAG traversals this optimization removes. - fn has_closed_infer_result(&mut self, e: &KExpr) -> bool { - if e.lbr() != 0 { - return false; - } - let key = self.infer_key(e); - self.env.infer_cache.contains_key(&key) - || (self.infer_only && self.env.infer_only_cache.contains_key(&key)) - } - - pub(super) fn infer_lambda_telescope( - &mut self, - e: &KExpr, - ) -> Result, TcError> { - self.with_lctx_scope(|tc| { - let mut fvars = Vec::new(); - let mut ids = Vec::new(); - let mut domains = Vec::new(); - let mut contracts = Vec::new(); - let mut body = e; - while let ExprData::Lam(name, bi, contract, ty, rest, _) = body.data() { - if !fvars.is_empty() && tc.has_closed_infer_result(body) { - break; - } - // Check in dependency order, before introducing this binder. In - // infer-only mode, preserve the existing skipped-domain validation. - let domain = instantiate_rev(&mut tc.env.intern, ty, &fvars); - if !tc.infer_only { - let domain_ty = tc.infer(&domain)?; - tc.ensure_sort(&domain_ty)?; - } - let id = tc.fresh_fvar_id(); - let fv = tc.intern(KExpr::fvar(id, name.clone())); - tc.lctx.push( - id, - LocalDecl::CDecl { - name: name.clone(), - bi: bi.clone(), - ty: domain.clone(), - contract: *contract, - result: ixon::contract::ValueContract::shared(), - }, - ); - fvars.push(fv); - ids.push(id); - domains.push(domain); - contracts.push(*contract); - body = rest; - } - - let opened = instantiate_rev(&mut tc.env.intern, body, &fvars); - let body_ty = tc.infer(&opened)?; - // In the recursive implementation only the innermost call can see a - // head beta redex; every outer call sees the newly constructed All. - let body_ty = cheap_beta_reduce(&mut tc.env.intern, &body_ty); - let mut result = abstract_fvars(&mut tc.env.intern, &body_ty, &ids); - for (i, domain) in domains.iter().enumerate().rev() { - // Close a domain over ONLY its earlier binders. Do not reuse the - // original raw domain: opening/closing canonicalizes variable names - // and affected metadata just as the single-binder implementation does. - let domain = abstract_fvars(&mut tc.env.intern, domain, &ids[..i]); - // Inferred Pis deliberately use anonymous/default binder metadata, - // matching the single-binder path and recursor synthesis exactly. - result = tc.env.intern.intern_all_contract( - M::meta_field(ix_common::env::Name::anon()), - M::meta_field(ix_common::env::BinderInfo::Default), - contracts[i], - ixon::contract::ValueContract::shared(), - &domain, - &result, - ); - } - Ok(result) - }) - } - - pub(super) fn infer_forall_telescope( - &mut self, - e: &KExpr, - ) -> Result, TcError> { - self.with_lctx_scope(|tc| { - let mut fvars = Vec::new(); - let mut levels = Vec::new(); - let mut body = e; - while let ExprData::All(name, bi, contract, result, ty, rest, _) = body.data() { - if !fvars.is_empty() && tc.has_closed_infer_result(body) { - break; - } - let domain = instantiate_rev(&mut tc.env.intern, ty, &fvars); - // Foralls validate domain sorts even in infer-only mode. - let domain_ty = tc.infer(&domain)?; - levels.push(tc.ensure_sort(&domain_ty)?); - let id = tc.fresh_fvar_id(); - let fv = tc.intern(KExpr::fvar(id, name.clone())); - if crate::env_var("IX_FVAR_TRACE").is_ok() { - log::info!( - "[fvar All batch push] fv={id} ty.addr={:?} ty.lbr={} ctx_len_before_push={} batch_prefix={}", - domain.addr(), - domain.lbr(), - tc.ctx.len(), - fvars.len(), - ); - log::info!(" ty data: {:?}", domain.data()); - } - tc.lctx.push( - id, - LocalDecl::CDecl { - name: name.clone(), - bi: bi.clone(), - ty: domain, - contract: *contract, - result: *result, - }, - ); - fvars.push(fv); - body = rest; - } - - let opened = instantiate_rev(&mut tc.env.intern, body, &fvars); - let mut result = tc.infer(&opened)?; - for domain_level in levels.into_iter().rev() { - let body_level = tc.ensure_sort(&result)?; - // Preserve the right-associated imax and normalize each intermediate - // sort as before. Prop codomains and symbolic universes need imax, - // not a max over all the domains. - result = tc.intern(KExpr::sort(KUniv::imax(domain_level, body_level))); - } - Ok(result) - }) - } -} - -#[cfg(test)] -mod tests; diff --git a/crates/kernel/src/infer/binders/tests.rs b/crates/kernel/src/infer/binders/tests.rs deleted file mode 100644 index 3607a18a9..000000000 --- a/crates/kernel/src/infer/binders/tests.rs +++ /dev/null @@ -1,517 +0,0 @@ -//! Differential tests against the former one-binder-at-a-time inference. - -use super::*; -use crate::env::{InternTable, KEnv}; -use crate::expr::FVarId; -use crate::mode::{Anon, Meta}; -use crate::profile::{OpCounts, take_op_counts}; -use ix_common::address::Address; -use ix_common::env::{BinderInfo, DataValue, Name}; - -fn name(s: &str) -> M::MField { - M::meta_field(Name::str(Name::anon(), s.to_owned())) -} - -fn var(i: u64) -> KExpr { - KExpr::var(i, name::("source-variable")) -} - -fn sort(n: u64) -> KExpr { - let mut u = KUniv::zero(); - for _ in 0..n { - u = KUniv::succ(u); - } - KExpr::sort(u) -} - -fn lam(ty: KExpr, body: KExpr) -> KExpr { - KExpr::lam(name::("lambda"), M::meta_field(BinderInfo::Implicit), ty, body) -} - -fn all(ty: KExpr, body: KExpr) -> KExpr { - KExpr::all( - name::("forall"), - M::meta_field(BinderInfo::InstImplicit), - ty, - body, - ) -} - -fn app(f: KExpr, a: KExpr) -> KExpr { - KExpr::app_mdata( - f, - a, - M::meta_field(vec![vec![( - Name::str(Name::anon(), "tag".to_owned()), - DataValue::OfString("binder differential".to_owned()), - )]]), - ) -} - -// A test-only copy of the former Lam/All branches, including cache behavior, -// dependent domain validation, one-at-a-time opening/closing, and scope -// restoration. Non-binder operations use the normal checker. This is an -// oracle for telescope handling, not a second implementation of the kernel. -fn reference( - tc: &mut TypeChecker<'_, M>, - e: &KExpr, -) -> Result, TcError> { - let key = tc.infer_key(e); - if let Some(ty) = tc.env.infer_cache.get(&key) { - return Ok(ty.clone()); - } - if tc.infer_only - && let Some(ty) = tc.env.infer_only_cache.get(&key) - { - return Ok(ty.clone()); - } - let result = match e.data() { - ExprData::Lam(name, bi, contract, ty, body, _) => { - if !tc.infer_only { - let domain_ty = reference(tc, ty)?; - tc.ensure_sort(&domain_ty)?; - } - tc.with_lctx_scope(|tc| { - let id = tc.fresh_fvar_id(); - let fv = tc.intern(KExpr::fvar(id, name.clone())); - tc.lctx.push( - id, - LocalDecl::CDecl { - name: name.clone(), - bi: bi.clone(), - ty: ty.clone(), - contract: *contract, - result: ixon::contract::ValueContract::shared(), - }, - ); - let opened = instantiate_rev(&mut tc.env.intern, body, &[fv]); - let body_ty = reference(tc, &opened)?; - let body_ty = cheap_beta_reduce(&mut tc.env.intern, &body_ty); - let closed = abstract_fvars(&mut tc.env.intern, &body_ty, &[id]); - Ok(tc.intern(KExpr::all_contract( - M::meta_field(Name::anon()), - M::meta_field(BinderInfo::Default), - *contract, - ixon::contract::ValueContract::shared(), - ty.clone(), - closed, - ))) - })? - }, - ExprData::All(name, bi, contract, result, ty, body, _) => { - let domain_ty = reference(tc, ty)?; - let u1 = tc.ensure_sort(&domain_ty)?; - tc.with_lctx_scope(|tc| { - let id = tc.fresh_fvar_id(); - let fv = tc.intern(KExpr::fvar(id, name.clone())); - tc.lctx.push( - id, - LocalDecl::CDecl { - name: name.clone(), - bi: bi.clone(), - ty: ty.clone(), - contract: *contract, - result: *result, - }, - ); - let opened = instantiate_rev(&mut tc.env.intern, body, &[fv]); - let body_ty = reference(tc, &opened)?; - let u2 = tc.ensure_sort(&body_ty)?; - Ok(tc.intern(KExpr::sort(KUniv::imax(u1, u2)))) - })? - }, - _ => return tc.infer(e), - }; - if tc.infer_only { - tc.env.infer_only_cache.insert(key, result.clone()); - } else { - tc.env.infer_cache.insert(key, result.clone()); - } - Ok(result) -} - -fn same_shape(a: KExpr, b: KExpr) { - // Check occurrence metadata BEFORE interning together: first-insert-wins - // interning intentionally ignores names, binder info, and mdata. - let mut pending = vec![(&a, &b)]; - let mut seen = rustc_hash::FxHashSet::default(); - while let Some((a, b)) = pending.pop() { - if !seen.insert((*a.addr(), *b.addr())) { - continue; - } - assert_eq!(a.mdata(), b.mdata(), "expression metadata differs"); - assert_eq!(a.univ_decor(), b.univ_decor(), "universe spelling differs"); - assert_eq!(a.lbr(), b.lbr()); - assert_eq!(a.count_0(), b.count_0()); - assert_eq!(a.has_fvars(), b.has_fvars()); - match (a.data(), b.data()) { - (ExprData::Var(i, n, _), ExprData::Var(j, m, _)) => { - assert_eq!(i, j); - assert_eq!(n, m); - }, - (ExprData::FVar(i, n, _), ExprData::FVar(j, m, _)) => { - assert_eq!(i, j); - assert_eq!(n, m); - }, - (ExprData::App(f, x, _), ExprData::App(g, y, _)) => { - pending.extend([(f, g), (x, y)]); - }, - (ExprData::Lam(n, bi, _, t, r, _), ExprData::Lam(m, bj, _, u, s, _)) - | ( - ExprData::All(n, bi, _, _, t, r, _), - ExprData::All(m, bj, _, _, u, s, _), - ) => { - assert_eq!(n, m); - assert_eq!(bi, bj); - pending.extend([(t, u), (r, s)]); - }, - (ExprData::Let(n, t, v, r, nd, _), ExprData::Let(m, u, w, s, md, _)) => { - assert_eq!(n, m); - assert_eq!(nd, md); - pending.extend([(t, u), (v, w), (r, s)]); - }, - (ExprData::Prj(i, f, v, _), ExprData::Prj(j, g, w, _)) => { - assert_eq!(i, j); - assert_eq!(f, g); - pending.push((v, w)); - }, - (ExprData::Sort(..), ExprData::Sort(..)) - | (ExprData::Const(..), ExprData::Const(..)) - | (ExprData::Nat(..), ExprData::Nat(..)) - | (ExprData::Str(..), ExprData::Str(..)) => {}, - _ => panic!("different expression constructors"), - } - } - // The common interner additionally checks semantic payloads and complete - // universe structure/spelling (without depending on separate-env UIDs). - let mut intern = InternTable::new(); - let a = intern.intern_expr(a); - let b = intern.intern_expr(b); - assert!(a.ptr_eq(&b), "different inferred shapes:\n{a:?}\n{b:?}"); -} - -fn differential(e: &KExpr) { - for infer_only in [false, true] { - let mut baseline = KEnv::new(); - let mut batched = KEnv::new(); - let mut a = TypeChecker::new(&mut baseline); - let mut b = TypeChecker::new(&mut batched); - a.infer_only = infer_only; - b.infer_only = infer_only; - let ea = a.intern(e.clone()); - let eb = b.intern(e.clone()); - let expected = reference(&mut a, &ea).unwrap(); - let actual = b.infer(&eb).unwrap(); - assert!(a.lctx.is_empty() && b.lctx.is_empty()); - assert!(!actual.has_fvars(), "batch-local FVars escaped inference"); - same_shape(actual, expected); - } -} - -fn dependent_examples() { - // (A : Type) (B : A -> Type) (x : A) (y : B x), with deliberately - // nonempty binder/Var/App metadata. In particular the returned dependent - // domains must have the same metadata normalization as sequential opening. - let domains = [sort(1), all(var(0), sort(1)), var(1), app(var(1), var(0))]; - let mut lambda = var::(0); - let mut forall = app::(var(2), var(1)); - for domain in domains.into_iter().rev() { - lambda = lam(domain.clone(), lambda); - forall = all(domain, forall); - } - differential(&lambda); - differential(&forall); - - // A let interrupts the lambda telescope; its existing zeta/beta handling - // must still close the inferred type without leaving a Let or local FVar. - let body = KExpr::let_(name::("z"), var::(1), var(0), var(0), false); - differential(&lam(sort(1), lam(var(0), body))); - - // A lambda's inferred body type can be a head beta redex. Full validation - // must accept the dependent annotation and closing must still cheap-beta. - let redex = app(lam(sort(1), var(0)), var(0)); - differential(&lam(sort(1), lam(redex, var::(0)))); -} - -#[test] -fn batch_dependent_domains_and_metadata_match_sequential() { - dependent_examples::(); - dependent_examples::(); -} - -fn universes() { - let p = KUniv::param(0, name::("u")); - let q = KUniv::param(1, name::("v")); - for domain in [sort(0), sort(1), KExpr::sort(p.clone())] { - for inner in [sort(0), sort(2), KExpr::sort(q.clone())] { - differential(&all::(domain.clone(), all(inner, sort(0)))); - } - } - // (P : Prop) -> (A : Sort u) -> P lives in Prop regardless of u. - let e = all(sort(0), all(KExpr::sort(p), var::(1))); - differential(&e); - let mut env = KEnv::new(); - let mut tc = TypeChecker::new(&mut env); - let ty = tc.infer(&e).unwrap(); - assert!(matches!(ty.data(), ExprData::Sort(u, _) if u.is_zero())); -} - -#[test] -fn batch_forall_preserves_imax_prop_and_symbolic_universes() { - universes::(); - universes::(); -} - -fn outer_contexts() { - let mut baseline = KEnv::new(); - let mut batched = KEnv::new(); - let mut a = TypeChecker::new(&mut baseline); - let mut b = TypeChecker::new(&mut batched); - // Preserve both kinds of enclosing local context while opening a batch. - for tc in [&mut a, &mut b] { - tc.push_local(sort(1)); - let id = tc.fresh_fvar_id(); - assert_eq!(id, FVarId(0)); - tc.lctx.push( - id, - LocalDecl::cdecl( - name::("outer"), - M::meta_field(BinderInfo::Default), - sort(1), - ), - ); - } - let legacy = lam(var(0), lam(var(1), var::(0))); - let outer = KExpr::fvar(FVarId(0), name::("outer")); - let free = lam(outer.clone(), lam(outer, var(0))); - for e in [legacy, free] { - let ea = a.intern(e.clone()); - let eb = b.intern(e); - same_shape(reference(&mut a, &ea).unwrap(), b.infer(&eb).unwrap()); - assert_eq!(a.lctx.len(), 1); - assert_eq!(b.lctx.len(), 1); - assert_eq!(b.ctx.len(), 1); - assert!(b.lctx.find(FVarId(0)).is_some()); - } -} - -#[test] -fn batch_preserves_outer_fvars_and_legacy_context() { - outer_contexts::(); - outer_contexts::(); -} - -fn missing(s: &str) -> KExpr { - KExpr::cnst(KId::new(Address::hash(s.as_bytes()), name::(s)), Box::new([])) -} - -fn errors() { - // Every domain, not just the first, must be checked before the body. - let good = [sort::(1), sort(0), var(1)]; - for bad_index in 0..=good.len() { - for forall in [false, true] { - let mut e = missing("bad-body"); - for (i, ty) in good.iter().enumerate().rev() { - let domain = - if i == bad_index { missing("bad-domain") } else { ty.clone() }; - e = if forall { all(domain, e) } else { lam(domain, e) }; - } - for infer_only in [false, true] { - let mut baseline = KEnv::new(); - let mut batched = KEnv::new(); - let mut a = TypeChecker::new(&mut baseline); - let mut b = TypeChecker::new(&mut batched); - a.infer_only = infer_only; - b.infer_only = infer_only; - let expected = reference(&mut a, &e).unwrap_err(); - let actual = b.infer(&e).unwrap_err(); - assert_eq!(expected.to_string(), actual.to_string()); - assert!(b.lctx.is_empty()); - let key = b.infer_key(&e); - assert!(!b.env.infer_cache.contains_key(&key)); - assert!(!b.env.infer_only_cache.contains_key(&key)); - } - } - } - // A domain whose inferred type is not a sort, and an ill-scoped body. - for e in [ - lam(sort(1), lam(var(0), lam(var(0), sort::(0)))), - all(sort(1), all(var(0), all(var(0), sort(0)))), - lam(sort(1), lam(sort(1), var(5))), - all(sort(1), all(sort(1), var(5))), - ] { - let mut baseline = KEnv::new(); - let mut batched = KEnv::new(); - let expected = - reference(&mut TypeChecker::new(&mut baseline), &e).unwrap_err(); - let mut tc = TypeChecker::new(&mut batched); - let actual = tc.infer(&e).unwrap_err(); - assert_eq!(expected.to_string(), actual.to_string()); - assert!(tc.lctx.is_empty()); - } -} - -#[test] -fn batch_errors_preserve_validation_order_and_restore_scope() { - errors::(); - errors::(); -} - -fn caches() { - let mut env = KEnv::new(); - let mut tc = TypeChecker::new(&mut env); - let suffix = tc.intern(lam(sort(1), lam(sort(1), sort::(0)))); - let suffix_ty = tc.infer(&suffix).unwrap(); - let e = tc.intern(lam(sort(1), suffix)); - let before = tc.fresh_fvar_id().0; - let ty = tc.infer(&e).unwrap(); - let after = tc.fresh_fvar_id().0; - assert_eq!(after - before, 2, "cached suffix was opened again"); - let expected = tc.env.intern.intern_all( - M::meta_field(Name::anon()), - M::meta_field(BinderInfo::Default), - &sort(1), - &suffix_ty, - ); - same_shape(ty.clone(), expected); - let before = tc.fresh_fvar_id().0; - let entries = tc.env.infer_cache.len(); - assert!(tc.infer(&e).unwrap().ptr_eq(&ty)); - assert_eq!(tc.fresh_fvar_id().0 - before, 1); - assert_eq!(tc.env.infer_cache.len(), entries); - - // An infer-only suffix must not be accepted as a validated full-mode - // result. This application passes synthesis but has an invalid argument. - let bad_app = app(lam(sort(1), var(0)), sort(1)); - let bad_suffix = tc.intern(lam(sort(1), lam(sort(1), bad_app))); - assert!(tc.with_infer_only(|tc| tc.infer(&bad_suffix)).is_ok()); - let bad_outer = tc.intern(lam(sort(1), bad_suffix)); - assert!(matches!(tc.infer(&bad_outer), Err(TcError::AppTypeMismatch { .. }))); - assert!(tc.lctx.is_empty()); - let key = tc.infer_key(&bad_outer); - assert!(!tc.env.infer_cache.contains_key(&key)); - - // Conversely, a full result is available to infer-only callers too. - let before = tc.fresh_fvar_id().0; - assert!(tc.with_infer_only(|tc| tc.infer(&e)).unwrap().ptr_eq(&ty)); - assert_eq!(tc.fresh_fvar_id().0 - before, 1); -} - -#[test] -fn batch_keeps_outer_and_closed_suffix_cache_hits_mode_safe() { - caches::(); - caches::(); -} - -#[test] -fn batch_forall_reuses_closed_suffix_without_reopening() { - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - let suffix = tc.intern(all(sort(1), all(sort(2), sort(0)))); - let suffix_ty = tc.infer(&suffix).unwrap(); - let e = tc.intern(all(sort(1), suffix)); - let before = tc.fresh_fvar_id().0; - let ty = tc.infer(&e).unwrap(); - assert_eq!(tc.fresh_fvar_id().0 - before, 2); - let body_level = tc.ensure_sort(&suffix_ty).unwrap(); - let domain_level = tc.ensure_sort(&sort(2)).unwrap(); - let expected = tc.intern(KExpr::sort(KUniv::imax(domain_level, body_level))); - same_shape(ty, expected); -} - -#[test] -fn batch_error_preserves_existing_local_and_does_not_recycle_ids() { - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - let (outer_id, outer) = tc.push_fvar_decl_anon(sort(1)); - let e = lam(outer.clone(), lam(outer.clone(), missing("bad-body"))); - assert!(tc.infer(&e).is_err()); - assert_eq!(tc.lctx.len(), 1); - assert!(tc.lctx.find(outer_id).is_some()); - assert_eq!(tc.fresh_fvar_id().0, outer_id.0 + 3); - let ty = tc.infer(&outer).unwrap(); - same_shape(ty, sort(1)); -} - -// A well-typed DAG under a long telescope: -// (A : Type) (x_0 ... x_n : A) (f : A -> A -> A) => balanced f-tree. -// Distinct leaves keep the term wide without making recursive descent deep. -fn wide_telescope(n: u64) -> KExpr { - assert!(n.is_power_of_two()); - let mut domains = vec![sort(1)]; - domains.extend((0..n).map(var)); - domains.push(all(var(n), all(var(n + 1), var(n + 2)))); - let f = var::(0); - let mut layer: Vec<_> = (1..=n).map(var).collect(); - while layer.len() > 1 { - layer = layer - .as_chunks::<2>() - .0 - .iter() - .map(|pair| app(app(f.clone(), pair[0].clone()), pair[1].clone())) - .collect(); - } - let mut body = layer.pop().unwrap(); - for domain in domains.into_iter().rev() { - body = lam(domain, body); - } - body -} - -struct Measurement { - ty: KExpr, - ops: OpCounts, - elapsed: std::time::Duration, - entries: usize, -} - -fn measure(e: &KExpr, batched: bool) -> Measurement { - let mut env = KEnv::new(); - let mut tc = TypeChecker::new(&mut env); - let e = tc.intern(e.clone()); - take_op_counts(); - let start = std::time::Instant::now(); - let ty = if batched { tc.infer(&e) } else { reference(&mut tc, &e) }.unwrap(); - let elapsed = start.elapsed(); - Measurement { - ty, - ops: take_op_counts(), - elapsed, - entries: tc.env.infer_cache.len(), - } -} - -fn work_reduction() { - let e = wide_telescope::(64); - let a = measure(&e, false); - let b = measure(&e, true); - same_shape(a.ty, b.ty); - assert!( - b.ops.subst_nodes * 4 < a.ops.subst_nodes, - "batch did not eliminate repeated opening: {} vs {} visits", - b.ops.subst_nodes, - a.ops.subst_nodes, - ); - assert!(b.entries < a.entries, "intermediate suffixes were still cached"); -} - -#[test] -fn batch_reduces_telescope_node_visits_without_changing_type() { - work_reduction::(); - work_reduction::(); -} - -#[test] -#[ignore = "manual paired release microbenchmark; timings are not a test gate"] -fn batch_binder_microbenchmark() { - let e = wide_telescope::(128); - for round in 0..5 { - for batched in if round % 2 == 0 { [false, true] } else { [true, false] } { - let m = measure(&e, batched); - eprintln!( - "round={round} batched={batched} elapsed={:?} subst={} intern={} infer_entries={}", - m.elapsed, m.ops.subst_nodes, m.ops.intern_nodes, m.entries, - ); - } - } -} diff --git a/crates/kernel/src/infer/summary.rs b/crates/kernel/src/infer/summary.rs deleted file mode 100644 index 144f3fc15..000000000 --- a/crates/kernel/src/infer/summary.rs +++ /dev/null @@ -1,232 +0,0 @@ -//! Conservative declaration summaries for proof-irrelevance eligibility. -//! -//! For a syntactic telescope `c : (x1 : A1) ... (xn : An) -> T`, the -//! classifying sort of T determines whether c, and each of its first n -//! partial applications, can be a proof: `imax a b` is zero iff b is zero. -//! We recognize T's sort ONLY from Sort nodes or syntactic types of its -//! constant/bound-variable head. No delta, reduction probes, fresh locals, -//! or argument validation occur here. Unknown means use ordinary inference. -//! -//! The substitution lemma justifies transferring a known nonzero sort to -//! well-typed arguments. This is NOT a typechecking shortcut: all arguments, -//! declaration types/bodies and universe scopes still undergo normal checks. - -use super::*; -use crate::level::UnivData; - -const MAX_SUMMARY_ARITY: usize = 64; -const MAX_LEVEL_VISITS: usize = 256; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub(crate) enum ProofEligibility { - Unknown, - /// The result's type has sort Prop; still check BOTH proposition types - /// with the existing proof-irrelevance procedure, never accept from here. - ProofEligible, - NonProof, -} - -#[derive(Clone, Copy, Debug)] -pub(crate) struct DeclarationSummary { - pub(crate) arity: usize, - pub(crate) result: ProofEligibility, -} - -const UNKNOWN: DeclarationSummary = - DeclarationSummary { arity: 0, result: ProofEligibility::Unknown }; - -/// A bounded syntactic walk; deeper spines use ordinary inference. -fn head_and_arity( - mut e: &KExpr, -) -> Option<(&KExpr, usize)> { - let mut arity = 0; - while let ExprData::App(f, _, _) = e.data() { - if arity == MAX_SUMMARY_ARITY { - return None; - } - arity += 1; - e = f; - } - Some((e, arity)) -} - -impl TypeChecker<'_, M> { - /// This predicate can only SKIP an inapplicable proof-irrelevance attempt. - /// False includes unknown, malformed, overapplied, and propositional cases. - pub(crate) fn known_non_proof(&mut self, e: &KExpr) -> bool { - let Some((head, arity)) = head_and_arity(e) else { return false }; - if !matches!(head.data(), ExprData::Const(..)) { - return false; - } - let key = head.hash_key(); - let summary = if let Some(summary) = self.env.decl_summary_cache.get(&key) { - self.env.perf.record_decl_summary_hit(); - *summary - } else { - self.env.perf.record_decl_summary_miss(); - // Dependency lookup/arity/substitution errors are not memoized as - // negative facts. A later call with the dependency loaded can retry. - let Ok(summary) = self.summarize_declaration(head) else { return false }; - self.env.decl_summary_cache.insert(key, summary); - summary - }; - let skip = - arity <= summary.arity && summary.result == ProofEligibility::NonProof; - if skip { - self.env.perf.record_non_proof_skip(); - } - skip - } - - fn summarize_declaration( - &mut self, - head: &KExpr, - ) -> Result> { - let ExprData::Const(id, us, _) = head.data() else { return Ok(UNKNOWN) }; - let c = self.get_const(id)?; - if u64_to_usize::(c.lvls())? != us.len() { - return Err(TcError::UnivParamMismatch { - expected: c.lvls(), - got: us.len(), - }); - } - // Ordinary declaration checking enforces these. The summary must also - // decline malformed hand-built/temporarily loaded declarations. - if c.ty().lbr() != 0 || c.ty().has_fvars() { - return Ok(UNKNOWN); - } - let mut domains: smallvec::SmallVec<[&KExpr; 8]> = - smallvec::SmallVec::new(); - let mut ty = c.ty(); - while let ExprData::All(_, _, _, _, dom, cod, _) = ty.data() { - if domains.len() == MAX_SUMMARY_ARITY { - return Ok(UNKNOWN); - } - domains.push(dom); - ty = cod; - } - let Some(result) = self.summary_type_classification(ty, &domains, us)? - else { - return Ok(UNKNOWN); - }; - Ok(DeclarationSummary { arity: domains.len(), result }) - } - - /// Infer only a syntactically evident sort of a telescope's terminal - /// type. For a constant/application, its head type must expose exactly the - /// required Pis followed by Sort. Bound-variable heads use their original - /// domain; no lifting is necessary for the final universe-only result. - fn summary_type_classification( - &mut self, - ty: &KExpr, - domains: &[&KExpr], - outer_us: &[KUniv], - ) -> Result, TcError> { - let mut visits = MAX_LEVEL_VISITS; - if let ExprData::Sort(u, _) = ty.data() { - // Sort u : Sort (u+1), never Prop. Still validate the universe - // substitution within the same bounded walk before recording a fact. - return Ok( - classify_sort(u, &[outer_us], &mut visits)? - .map(|_| ProofEligibility::NonProof), - ); - } - let Some((head, args)) = head_and_arity(ty) else { return Ok(None) }; - let (head_ty, levels) = match head.data() { - ExprData::Var(i, _, _) => { - let Some(pos) = usize::try_from(*i) - .ok() - .and_then(|i| i.checked_add(1)) - .and_then(|n| domains.len().checked_sub(n)) - else { - return Ok(None); - }; - (domains[pos].clone(), None) - }, - ExprData::Const(id, us, _) => { - let c = self.get_const(id)?; - if u64_to_usize::(c.lvls())? != us.len() { - return Err(TcError::UnivParamMismatch { - expected: c.lvls(), - got: us.len(), - }); - } - if c.ty().lbr() != 0 || c.ty().has_fvars() { - return Ok(None); - } - (c.ty().clone(), Some(us)) - }, - _ => return Ok(None), - }; - let mut result = &head_ty; - for _ in 0..args { - let ExprData::All(_, _, _, _, _, cod, _) = result.data() else { - return Ok(None); - }; - result = cod; - } - let ExprData::Sort(u, _) = result.data() else { return Ok(None) }; - // Type-head universes are in this declaration's formal parameters. - // Apply the inner substitution BEFORE the outer one, lazily, without - // building levels or invoking unbounded universe-normalization helpers. - match levels { - Some(us) => classify_sort(u, &[us, outer_us], &mut visits), - None => classify_sort(u, &[outer_us], &mut visits), - } - } -} - -/// Interpret chained universe substitutions without constructing a level. -/// A parameter consumes ONE substitution layer, so an actual universe's -/// parameters cannot accidentally be captured by its own substitution. -/// Validate both branches, even when one determines nonzeroness, so errors -/// are not memoized as facts. None means the bounded analysis ran out of work. -/// A symbolic parameter with no substitution left is Unknown, NOT NonProof. -fn classify_sort( - u: &KUniv, - substitutions: &[&[KUniv]], - remaining: &mut usize, -) -> Result, TcError> { - use ProofEligibility::{NonProof, ProofEligible, Unknown}; - if *remaining == 0 { - return Ok(None); - } - *remaining -= 1; - let result = match u.data() { - UnivData::Zero(_) => Some(ProofEligible), - UnivData::Succ(inner, _) => { - classify_sort(inner, substitutions, remaining)?.map(|_| NonProof) - }, - UnivData::Param(i, _, _) => { - let Some((us, rest)) = substitutions.split_first() else { - return Ok(Some(Unknown)); - }; - let Some(actual) = usize::try_from(*i).ok().and_then(|i| us.get(i)) - else { - return Err(TcError::UnivParamOutOfRange { idx: *i, bound: us.len() }); - }; - return classify_sort(actual, rest, remaining); - }, - UnivData::IMax(a, b, _) | UnivData::Max(a, b, _) => { - let Some(a) = classify_sort(a, substitutions, remaining)? else { - return Ok(None); - }; - let Some(b) = classify_sort(b, substitutions, remaining)? else { - return Ok(None); - }; - Some(if matches!(u.data(), UnivData::IMax(..)) { - b - } else { - match (a, b) { - (NonProof, _) | (_, NonProof) => NonProof, - (ProofEligible, ProofEligible) => ProofEligible, - _ => Unknown, - } - }) - }, - }; - Ok(result) -} - -#[cfg(test)] -mod tests; diff --git a/crates/kernel/src/infer/summary/tests.rs b/crates/kernel/src/infer/summary/tests.rs deleted file mode 100644 index b347c2636..000000000 --- a/crates/kernel/src/infer/summary/tests.rs +++ /dev/null @@ -1,294 +0,0 @@ -use super::*; -use crate::env::KEnv; -use crate::mode::{Anon, Meta}; -use ix_common::address::Address; -use ix_common::env::{BinderInfo, Name}; - -fn name(s: &str) -> M::MField { - M::meta_field(Name::str(Name::anon(), s.to_owned())) -} -fn sort(n: u64) -> KExpr { - let mut u = KUniv::zero(); - for _ in 0..n { - u = KUniv::succ(u); - } - KExpr::sort(u) -} -fn var(i: u64) -> KExpr { - KExpr::var(i, name::("var")) -} -fn all(dom: KExpr, cod: KExpr) -> KExpr { - KExpr::all(name::("binder"), M::meta_field(BinderInfo::Implicit), dom, cod) -} -fn axiom( - env: &mut KEnv, - s: &str, - lvls: u64, - ty: KExpr, -) -> KId { - let id = KId::new(Address::hash(s.as_bytes()), name::(s)); - env.insert( - id.clone(), - KConst::Axio { - name: name::(s), - level_params: M::meta_field( - (0..lvls).map(|i| Name::str(Name::anon(), format!("u{i}"))).collect(), - ), - is_unsafe: false, - lvls, - ty, - }, - ); - id -} -fn cnst(id: &KId, us: &[KUniv]) -> KExpr { - KExpr::cnst(id.clone(), us.to_vec().into_boxed_slice()) -} -fn app(f: KExpr, a: KExpr) -> KExpr { - KExpr::app(f, a) -} - -fn propositions_and_data() { - let mut env = KEnv::::new(); - let prop = axiom(&mut env, "P", 0, sort(0)); - let p = cnst(&prop, &[]); - let proof1 = axiom(&mut env, "p1", 0, p.clone()); - let proof2 = axiom(&mut env, "p2", 0, p.clone()); - let data = axiom(&mut env, "A", 0, sort(1)); - let a = cnst(&data, &[]); - let data1 = axiom(&mut env, "x1", 0, a.clone()); - let data2 = axiom(&mut env, "x2", 0, a.clone()); - let mut tc = TypeChecker::new(&mut env); - assert!(tc.known_non_proof(&p), "a proposition is not itself a proof"); - assert!(tc.known_non_proof(&a)); - assert!(tc.known_non_proof(&cnst(&data1, &[]))); - assert!(!tc.known_non_proof(&cnst(&proof1, &[]))); - assert!(tc.is_def_eq(&cnst(&proof1, &[]), &cnst(&proof2, &[])).unwrap()); - assert!(!tc.is_def_eq(&cnst(&data1, &[]), &cnst(&data2, &[])).unwrap()); -} - -#[test] -fn distinguishes_propositions_proofs_and_data_in_both_modes() { - propositions_and_data::(); - propositions_and_data::(); -} - -fn universes() { - let mut env = KEnv::::new(); - let u = KUniv::param(0, name::("u")); - // id.{u} : (A : Sort u) -> A -> A. For u=0 it is a proof even - // before application, because the remaining Pis are impredicative. - let id = - axiom(&mut env, "identity", 1, all(KExpr::sort(u), all(var(0), var(1)))); - let mut tc = TypeChecker::new(&mut env); - for (u, expected) in [ - (KUniv::zero(), false), - (KUniv::succ(KUniv::zero()), true), - (KUniv::param(7, name::("symbolic")), false), - (KUniv::imax(KUniv::succ(KUniv::zero()), KUniv::zero()), false), - ( - KUniv::max(KUniv::param(2, name::("v")), KUniv::succ(KUniv::zero())), - true, - ), - ] { - let head = cnst(&id, &[u]); - let summary = tc.summarize_declaration(&head).unwrap(); - assert_eq!(summary.arity, 2); - assert_eq!(summary.result == ProofEligibility::NonProof, expected); - let mut term = head; - for _ in 0..=2 { - assert_eq!(tc.known_non_proof(&term), expected); - // Eligibility is independent of argument values; this does NOT assert - // that these deliberately arbitrary arguments are well-typed. - term = app(term, sort(0)); - } - assert!(!tc.known_non_proof(&term), "overapplication is not summarized"); - } - assert_eq!(tc.env.decl_summary_cache.len(), 5); - assert!(tc.env.infer_cache.is_empty()); - assert!(tc.env.infer_only_cache.is_empty()); - assert!(tc.ctx.is_empty() && tc.lctx.is_empty()); -} - -#[test] -fn universe_instantiations_and_partial_applications_stay_separate() { - universes::(); - universes::(); -} - -fn partial_proofs() { - let mut env = KEnv::::new(); - let p_id = axiom(&mut env, "P", 0, sort(0)); - let p = cnst(&p_id, &[]); - let h_id = axiom(&mut env, "hP", 0, p.clone()); - let h = cnst(&h_id, &[]); - let ty = - all(KExpr::sort(KUniv::param(0, name::("u"))), all(var(0), var(1))); - let f = axiom(&mut env, "f", 1, ty.clone()); - let g = axiom(&mut env, "g", 1, ty); - for (u, args, non_proof) in [ - (KUniv::zero(), [p.clone(), h], false), - (KUniv::succ(KUniv::zero()), [sort(0), p], true), - ] { - let mut a = cnst(&f, std::slice::from_ref(&u)); - let mut b = cnst(&g, &[u]); - let mut tc = TypeChecker::new(&mut env); - for applied in 0..=2 { - tc.infer(&a).unwrap(); - tc.infer(&b).unwrap(); - assert_eq!(tc.known_non_proof(&a), non_proof); - assert_eq!(tc.is_def_eq(&a, &b).unwrap(), !non_proof); - if let Some(arg) = args.get(applied) { - a = app(a, arg.clone()); - b = app(b, arg.clone()); - } - } - } -} - -#[test] -fn impredicative_partial_applications_preserve_proof_irrelevance() { - partial_proofs::(); - partial_proofs::(); -} - -fn families() { - let mut env = KEnv::::new(); - let u = KUniv::param(0, name::("u")); - // F.{u} : Type -> Sort u. g.{u} : (A : Type) -> F.{u} A. - let family = axiom(&mut env, "F", 1, all(sort(1), KExpr::sort(u.clone()))); - let result = app(cnst(&family, std::slice::from_ref(&u)), var(0)); - let g = axiom(&mut env, "g", 1, all(sort(1), result)); - // h : (A : Type) -> (B : A -> Type) -> (x : A) -> B x - let h = axiom( - &mut env, - "h", - 0, - all(sort(1), all(all(var(0), sort(1)), all(var(1), app(var(1), var(0))))), - ); - let h = cnst(&h, &[]); - let mut tc = TypeChecker::new(&mut env); - assert!(!tc.known_non_proof(&cnst(&g, &[KUniv::zero()]))); - assert!(tc.known_non_proof(&cnst(&g, &[KUniv::succ(KUniv::zero())]))); - assert!(tc.known_non_proof(&h)); - // The summary does not create/open FVars or depend on the caller's local - // context. Warm queries must remain free of type-inference/fuel work. - tc.push_local(sort(1)); - tc.rec_fuel = 0; - assert!(tc.known_non_proof(&h)); - assert_eq!(tc.rec_fuel, 0); - assert_eq!(tc.ctx.len(), 1); - assert!(tc.lctx.is_empty()); -} - -#[test] -fn composes_universe_substitutions_and_handles_bound_type_families() { - families::(); - families::(); -} - -#[test] -fn unknown_shapes_fall_back_without_unfolding() { - let mut env = KEnv::::new(); - let hidden = KExpr::let_((), sort(1), sort(0), var(0), false); - let f = axiom(&mut env, "hidden", 0, hidden); - let mut deep = sort(1); - for _ in 0..65 { - deep = all(sort(1), deep); - } - let deep = axiom(&mut env, "deep", 0, deep); - let malformed = axiom(&mut env, "loose", 0, var(0)); - let mut tc = TypeChecker::new(&mut env); - for id in [&f, &deep, &malformed] { - assert!(!tc.known_non_proof(&cnst(id, &[]))); - } - assert!(!tc.known_non_proof(&app(var(0), sort(0)))); - assert!(tc.env.whnf_cache.is_empty() && tc.env.unfold_cache.is_empty()); - assert!(tc.env.infer_cache.is_empty() && tc.env.infer_only_cache.is_empty()); - assert_eq!(tc.fuel_used(), 0); -} - -#[test] -fn arity_missing_dependencies_and_universe_errors_are_not_cached() { - let mut env = KEnv::::new(); - let id = axiom(&mut env, "poly", 1, KExpr::sort(KUniv::param(0, ()))); - let missing = KId::new(Address::hash(b"missing"), ()); - let f = axiom(&mut env, "f", 0, cnst(&missing, &[])); - let broken = axiom(&mut env, "broken", 0, KExpr::sort(KUniv::param(1, ()))); - let mut tc = TypeChecker::new(&mut env); - for e in - [cnst(&id, &[]), cnst(&f, &[]), cnst(&broken, &[]), cnst(&missing, &[])] - { - assert!(!tc.known_non_proof(&e)); - } - assert!(tc.env.decl_summary_cache.is_empty()); - let inserted = axiom(tc.env, "missing", 0, sort(1)); - assert_eq!(inserted, missing); - assert!(tc.known_non_proof(&cnst(&f, &[]))); -} - -#[test] -fn declaration_replacement_and_all_reset_paths_invalidate_summaries() { - let mut env = KEnv::::new(); - let t = axiom(&mut env, "T", 0, sort(1)); - let f = axiom(&mut env, "f", 0, cnst(&t, &[])); - let e = cnst(&f, &[]); - assert!(TypeChecker::new(&mut env).known_non_proof(&e)); - assert_eq!(env.cache_sizes().decl_summary, 1); - // Change a dependency, not just f itself. - axiom(&mut env, "T", 0, sort(0)); - assert!(env.decl_summary_cache.is_empty()); - assert!(!TypeChecker::new(&mut env).known_non_proof(&e)); - for reset in 0..4 { - let t = axiom(&mut env, "T", 0, sort(1)); - let f = axiom(&mut env, "f", 0, cnst(&t, &[])); - assert!(TypeChecker::new(&mut env).known_non_proof(&cnst(&f, &[]))); - assert!(!env.decl_summary_cache.is_empty()); - match reset { - 0 => env.clear_reduction_caches(), - 1 => env.clear(), - 2 => env.clear_with_capacity_limit(0), - _ => env.clear_releasing_memory(), - } - assert!(env.decl_summary_cache.is_empty()); - } -} - -#[test] -fn bounded_level_classification_handles_imax_and_symbolic_zero() { - use ProofEligibility::{NonProof, ProofEligible, Unknown}; - let u: KUniv = KUniv::param(0, ()); - for (level, expected) in [ - (u.clone(), Unknown), - (KUniv::zero(), ProofEligible), - (KUniv::succ(u.clone()), NonProof), - (KUniv::imax(KUniv::succ(u.clone()), u.clone()), Unknown), - (KUniv::imax(u.clone(), KUniv::zero()), ProofEligible), - (KUniv::max(u, KUniv::succ(KUniv::zero())), NonProof), - ] { - assert_eq!(classify_sort(&level, &[], &mut 256).unwrap(), Some(expected)); - assert_eq!(classify_sort(&level, &[], &mut 0).unwrap(), None); - } -} - -#[test] -fn universe_analysis_is_bounded_and_does_not_capture_actual_parameters() { - let mut u: KUniv = KUniv::zero(); - for _ in 0..MAX_LEVEL_VISITS + 1 { - u = KUniv::succ(u); - } - let mut visits = MAX_LEVEL_VISITS; - assert_eq!(classify_sort(&u, &[], &mut visits).unwrap(), None); - // Substituting u0 -> u0 leaves a symbolic parameter, not a cycle. - let p = KUniv::::param(0, ()); - assert_eq!( - classify_sort(&p, &[std::slice::from_ref(&p)], &mut 8).unwrap(), - Some(ProofEligibility::Unknown) - ); - let inner = [p.clone()]; - let outer = [KUniv::zero()]; - assert_eq!( - classify_sort(&p, &[&inner, &outer], &mut 8).unwrap(), - Some(ProofEligibility::ProofEligible) - ); -} diff --git a/crates/kernel/src/ingress.rs b/crates/kernel/src/ingress.rs deleted file mode 100644 index 24312c37e..000000000 --- a/crates/kernel/src/ingress.rs +++ /dev/null @@ -1,6295 +0,0 @@ -//! Ingress: convert Ixon environment to zero kernel types. -//! -//! Converts Ixon `Constant`/`ConstantInfo`/`Expr`/`Univ` (alpha-invariant, -//! content-addressed) to `KExpr`/`KUniv`/`KConst` (kernel types with positional -//! universe params and optional metadata). Uses iterative stack-based traversal -//! to avoid stack overflow on deeply nested expressions. - -use std::cell::{Cell, RefCell}; -#[cfg(not(target_arch = "riscv64"))] -use std::hash::{BuildHasher, Hash}; -use std::sync::Arc; -use std::time::{Duration, Instant}; - -#[cfg(not(target_arch = "riscv64"))] -use rayon::iter::{ - IntoParallelIterator, IntoParallelRefIterator, ParallelIterator, -}; -use rustc_hash::{FxHashMap, FxHashSet}; - -use dashmap::DashMap; - -use bignat::Nat; -use ix_common::address::Address; -#[cfg(not(target_arch = "riscv64"))] -use ix_common::env::ConstantInfo as LeanCI; -use ix_common::env::DefinitionSafety; -#[cfg(not(target_arch = "riscv64"))] -use ix_common::env::Env as LeanEnv; -use ix_common::env::{BinderInfo, Name, ReducibilityHints}; -#[cfg(not(target_arch = "riscv64"))] -use ixon::constant::DefKind; -use ixon::constant::{ - Constant, ConstantInfo as IxonCI, MutConst as IxonMutConst, -}; -use ixon::contract::{BinderContract, LetContract, ValueContract, source}; -use ixon::env::Env as IxonEnv; -use ixon::expr::Expr as IxonExpr; -use ixon::metadata::{ConstantMeta, ConstantMetaInfo, ExprMeta, ExprMetaData}; -use ixon::univ::Univ as IxonUniv; - -use super::constant::{KConst, RecRule}; -use super::env::{CtxAddr, InternTable, KEnv}; -use super::expr::{KExpr, MData, UnivDecor}; -use super::id::KId; -use super::level::{KUniv, UnivData}; -use super::mode::{KernelMode, Meta}; -use super::primitive::reserved_marker_name; - -// ============================================================================ -// Lookup tables -// ============================================================================ - -/// Read-only context for converting a single Ixon constant's expressions. -struct Ctx<'a, M: KernelMode> { - sharing: &'a [Arc], - refs: &'a [Address], - univs: &'a [Arc], - /// ZIds of mutual block members (for resolving `Expr::Rec`). - mut_ctx: Vec>, - /// Rec edges for the definition currently being admitted. - local_refs: Option<&'a RefCell>>, - arena: &'a ExprMeta, - names: &'a FxHashMap, - lvls: Vec, - /// Level-spelling patches of this constant, keyed by metadata arena - /// index — the stage-2 decoration source (canonicity §10.6). Values - /// are the patch's univ indices in the VIRTUAL index space - /// `univs ++ meta_univs`. Empty for anon ingress and for constants - /// without patches. - univ_patches: FxHashMap, - /// Extension univ table: original (pre-canonicalization) spellings - /// referenced by `univ_patches` (canonicity §10.6). - meta_univs: &'a [Arc], - /// Counter for generating synthetic unique names when metadata is missing. - synth_counter: Cell, -} - -impl<'a, M: KernelMode> Ctx<'a, M> { - /// The level-spelling patch covering the occurrence whose (post-mdata) - /// arena index is `arena_idx`, if any. - fn univ_patch(&self, arena_idx: u64) -> Option<&'a [u64]> { - self.univ_patches.get(&arena_idx).copied() - } - - /// Resolve a univ index in the VIRTUAL space `univs ++ meta_univs` — - /// the index space `univ_patches` entries are written in - /// (canonicity §10.6). - fn univ_at_virtual(&self, idx: u64) -> Result<&'a Arc, String> { - let i = usize::try_from(idx) - .map_err(|_e| format!("virtual univ index {idx} exceeds usize"))?; - self - .univs - .get(i) - .or_else(|| self.meta_univs.get(i - self.univs.len())) - .ok_or_else(|| { - format!( - "virtual univ index {i} out of bounds (univs {} + meta_univs {})", - self.univs.len(), - self.meta_univs.len() - ) - }) - } -} - -/// Build the arena-index → patch lookup for a constant's -/// `ConstantMeta.univ_patches` (canonicity §10.6). -fn univ_patch_map(meta: &ConstantMeta) -> FxHashMap { - meta - .univ_patches - .iter() - .map(|p| (p.arena_idx, p.univ_idxs.as_slice())) - .collect() -} - -/// Expression conversion cache, keyed on (expr pointer, arena_idx). -type ExprCache = FxHashMap<(usize, u64), KExpr>; -/// Universe conversion cache, scoped to one level-parameter context. -type UnivCache = FxHashMap>; - -#[derive(Clone, Default)] -struct ConvertStats { - enabled: bool, - expr_roots: u64, - expr_process: u64, - expr_cache_hits: u64, - expr_cache_misses: u64, - expr_cache_inserts: u64, - expr_cache_peak: u64, - expr_cache_clears: u64, - expr_cache_entries_cleared: u64, - share_expansions: u64, - mdata_nodes: u64, - mdata_kv_maps: u64, - univ_roots: u64, - univ_cache_hits: u64, - univ_cache_misses: u64, - univ_cache_inserts: u64, - univ_cache_peak: u64, - univ_process: u64, - univ_interns: u64, - sort_nodes: u64, - var_nodes: u64, - ref_nodes: u64, - rec_nodes: u64, - app_nodes: u64, - lam_nodes: u64, - all_nodes: u64, - let_nodes: u64, - prj_nodes: u64, - str_nodes: u64, - nat_nodes: u64, - // ---- Phase-1 timing breakdown (ns), gated by IX_INGRESS_CONVERT_STATS ---- - /// Time spent in the `for kvm in mdata { resolve_kvmap(...) }` loop in - /// `ingress_expr`. Aggregates blob fetches, name lookups, and (for - /// OfSyntax) recursive `deser_syntax` work. - resolve_kvmap_ns: u64, - /// Number of `resolve_kvmap` calls (bumped by `mdata.len()` per Mdata - /// arena node, matching `mdata_kv_maps`). - resolve_kvmap_calls: u64, - /// Time spent walking the `ExprMetaData::Mdata` arena chain (the whole - /// `while let Some(Mdata)` loop including `resolve_kvmap`). - arena_walk_ns: u64, - /// Time spent inside `intern_expr` (sum of fast-path get + slow-path - /// entry). - intern_expr_ns: u64, - /// Number of `intern_expr` calls. - intern_expr_calls: u64, - /// Of those calls, how many were satisfied by the read-locked fast path - /// (vs. falling through to the write-locked entry path). - intern_expr_get_hits: u64, - /// Time spent inside `intern_univ`. - intern_univ_ns: u64, - /// Number of `intern_univ` calls. - intern_univ_calls: u64, - /// Of those, fast-path hits. - intern_univ_get_hits: u64, - /// Time spent on `cache.get(&cache_key)` lookups in `ingress_expr`. - expr_cache_lookup_ns: u64, - /// Time spent on `cache.insert(...)` for `ExprFrame::Cache`. - expr_cache_insert_ns: u64, - /// Time spent in `ixon_env.get_blob` calls from the `Str`/`Nat` arms of - /// `ingress_expr` (does NOT include `resolve_kvmap`'s blob fetches — - /// those live inside `resolve_kvmap_ns`). - get_blob_ns: u64, - /// Number of those `get_blob` calls. - get_blob_calls: u64, - /// Total time spent inside the `ExprFrame::Process` arm body — covers - /// share expansion, cache check, arena walk, `resolve_kvmap`, the - /// per-variant match arms (KExpr constructor calls, stack pushes for - /// continuations), and `intern_expr` invocations from this arm. - /// Subtracting the inner timed sub-stages from this gives the cost of - /// "everything else": KExpr construction, match dispatch, frame - /// allocation, Arc clones, and minor lookups. - process_arm_ns: u64, - /// Total time spent inside continuation arms (`AppDone`, `LamDone`, - /// `AllDone`, `LetDone`, `PrjDone`, `LetVal`, `BinderPush`, `BinderPop`, - /// `AppArg`, `LamBody`, `AllBody`, `LetBody`, `Cache`). These build a - /// new KExpr from already-converted children and then call - /// `intern_expr`. Subtracting `intern_expr_ns` (continuation share) and - /// `expr_cache_insert_ns` (Cache arm) from this gives the cost of the - /// continuation-side KExpr construction + frame manipulation. - continuation_arms_ns: u64, - /// Time spent constructing KExprs at all 13 call sites in - /// `ingress_expr` — covers blake3 hashing, `intern_addr`, and the outer - /// `Arc` allocation. Excludes the subsequent `intern_expr` - /// call (separately timed). Bumped by every `KExpr::*_mdata` / - /// `KExpr::*` constructor we wrap. - kexpr_construct_ns: u64, - /// Number of timed KExpr constructor calls. - kexpr_construct_calls: u64, -} - -impl ConvertStats { - fn new(enabled: bool) -> Self { - ConvertStats { enabled, ..ConvertStats::default() } - } - - fn merge(mut self, other: &Self) -> Self { - self.enabled |= other.enabled; - self.expr_roots += other.expr_roots; - self.expr_process += other.expr_process; - self.expr_cache_hits += other.expr_cache_hits; - self.expr_cache_misses += other.expr_cache_misses; - self.expr_cache_inserts += other.expr_cache_inserts; - self.expr_cache_peak = self.expr_cache_peak.max(other.expr_cache_peak); - self.expr_cache_clears += other.expr_cache_clears; - self.expr_cache_entries_cleared += other.expr_cache_entries_cleared; - self.share_expansions += other.share_expansions; - self.mdata_nodes += other.mdata_nodes; - self.mdata_kv_maps += other.mdata_kv_maps; - self.univ_roots += other.univ_roots; - self.univ_cache_hits += other.univ_cache_hits; - self.univ_cache_misses += other.univ_cache_misses; - self.univ_cache_inserts += other.univ_cache_inserts; - self.univ_cache_peak = self.univ_cache_peak.max(other.univ_cache_peak); - self.univ_process += other.univ_process; - self.univ_interns += other.univ_interns; - self.sort_nodes += other.sort_nodes; - self.var_nodes += other.var_nodes; - self.ref_nodes += other.ref_nodes; - self.rec_nodes += other.rec_nodes; - self.app_nodes += other.app_nodes; - self.lam_nodes += other.lam_nodes; - self.all_nodes += other.all_nodes; - self.let_nodes += other.let_nodes; - self.prj_nodes += other.prj_nodes; - self.str_nodes += other.str_nodes; - self.nat_nodes += other.nat_nodes; - self.resolve_kvmap_ns += other.resolve_kvmap_ns; - self.resolve_kvmap_calls += other.resolve_kvmap_calls; - self.arena_walk_ns += other.arena_walk_ns; - self.intern_expr_ns += other.intern_expr_ns; - self.intern_expr_calls += other.intern_expr_calls; - self.intern_expr_get_hits += other.intern_expr_get_hits; - self.intern_univ_ns += other.intern_univ_ns; - self.intern_univ_calls += other.intern_univ_calls; - self.intern_univ_get_hits += other.intern_univ_get_hits; - self.expr_cache_lookup_ns += other.expr_cache_lookup_ns; - self.expr_cache_insert_ns += other.expr_cache_insert_ns; - self.get_blob_ns += other.get_blob_ns; - self.get_blob_calls += other.get_blob_calls; - self.process_arm_ns += other.process_arm_ns; - self.continuation_arms_ns += other.continuation_arms_ns; - self.kexpr_construct_ns += other.kexpr_construct_ns; - self.kexpr_construct_calls += other.kexpr_construct_calls; - self - } - - fn record_cache_clear(&mut self, cache: &ExprCache) { - if self.enabled { - self.expr_cache_clears += 1; - self.expr_cache_entries_cleared += cache.len() as u64; - } - } -} - -macro_rules! bump_convert_stat { - ($stats:expr, $field:ident) => { - if ($stats).enabled { - ($stats).$field += 1; - } - }; - ($stats:expr, $field:ident, $amount:expr) => { - if ($stats).enabled { - ($stats).$field += $amount as u64; - } - }; -} - -/// Universe counterpart of [`timed_intern_or_build`]. -#[inline] -fn timed_intern_univ( - intern: &mut InternTable, - u: KUniv, - stats: &mut ConvertStats, -) -> KUniv { - if !stats.enabled { - return intern.intern_univ(u); - } - let t0 = Instant::now(); - 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 - } else { - intern.intern_univ(u) - }; - stats.intern_univ_calls += 1; - stats.intern_univ_ns += elapsed_ns(t0); - result -} - -/// 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 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, - key: &super::env::ExprKey, - build: impl FnOnce() -> KExpr, - stats: &mut ConvertStats, -) -> KExpr { - if !stats.enabled { - if let Some(existing) = intern.try_get_expr(key) { - return existing; - } - return intern.intern_expr(build()); - } - let t0 = Instant::now(); - 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 kc_t0 = Instant::now(); - 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 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; - interned -} - -fn resolve_name(addr: &Address, names: &FxHashMap) -> Name { - names.get(addr).cloned().unwrap_or_else(Name::anon) -} - -impl Ctx<'_, M> { - /// Generate a unique synthetic name like `_s0`, `_s1`, etc. - fn synth_name(&self) -> Name { - let n = self.synth_counter.get(); - self.synth_counter.set(n + 1); - Name::str(Name::anon(), format!("_s{n}")) - } -} - -fn resolve_level_params( - lvl_addrs: &[Address], - names: &FxHashMap, -) -> Vec { - lvl_addrs.iter().map(|a| resolve_name(a, names)).collect() -} - -/// Resolve a list of **Lean-name-hash** addresses to `KId` pairs whose -/// `addr` is the **projection-content address** under which the corresponding -/// KConst is actually stored in `KEnv`. -/// -/// The callers (`ingress_muts_inductive`'s `ctor_ids`, and -/// `lean_all` reconstruction in `ingress_defn` / `ingress_recursor` / -/// `ingress_muts_inductive`) pull addresses out of `ConstantMetaInfo::*::{all, -/// ctx, ctors}`. Those fields store **name-hash** addresses (they were written -/// by compile via `compile_name`), but each KConst is stored in `KEnv` under -/// its **projection** address (the content hash of the `IPrj` / `CPrj` / `RPrj` -/// / `DPrj` struct, or `block_addr` for singleton Muts classes). The two -/// address spaces are different, so we have to round-trip through the Lean -/// name to recover the projection address: -/// -/// name-hash-addr → Lean Name → `ixon_env.named[name].addr` → projection -/// -/// If the `name_to_addr` lookup misses, that means the Named entry we expected -/// the compile pipeline to register is missing — bailing with an error is far -/// better than guessing (the prior behavior synthesized a name-hash address as -/// a fallback, which produced **ghost KConsts**: KIds referring to addresses -/// that no KConst was ever stored at, causing obscure downstream lookup -/// failures and alpha-collapse confusion). -fn resolve_all( - all_addrs: &[Address], - names: &FxHashMap, - name_to_addr: &FxHashMap, -) -> Result>, String> { - all_addrs - .iter() - .map(|name_addr| { - let name = resolve_name(name_addr, names); - let addr = name_to_addr.get(&name).cloned().ok_or_else(|| { - format!( - "resolve_all: Named entry for '{name}' missing in ixon_env.named \ - (expected projection or block address for the compiled constant)" - ) - })?; - Ok(KId::new(addr, M::meta_field(name))) - }) - .collect() -} - -// ============================================================================ -// Universe ingress (iterative) -// ============================================================================ - -enum UnivFrame { - Process(Arc), - Succ, - MaxLeft(Arc), - Max, - IMaxLeft(Arc), - IMax, -} - -fn ingress_univ( - root: &Arc, - ctx: &Ctx<'_, M>, - intern: &mut InternTable, - cache: &mut UnivCache, - stats: &mut ConvertStats, -) -> Result, String> { - bump_convert_stat!(stats, univ_roots); - let cache_key = Arc::as_ptr(root) as usize; - if let Some(cached) = cache.get(&cache_key) { - bump_convert_stat!(stats, univ_cache_hits); - return Ok(cached.clone()); - } - bump_convert_stat!(stats, univ_cache_misses); - - let mut stack: Vec = vec![UnivFrame::Process(root.clone())]; - let mut values: Vec> = Vec::new(); - - while let Some(frame) = stack.pop() { - match frame { - UnivFrame::Process(u) => match u.as_ref() { - IxonUniv::Zero => { - bump_convert_stat!(stats, univ_process); - bump_convert_stat!(stats, univ_interns); - values.push(timed_intern_univ(intern, KUniv::zero(), stats)); - }, - IxonUniv::Succ(inner) => { - bump_convert_stat!(stats, univ_process); - stack.push(UnivFrame::Succ); - stack.push(UnivFrame::Process(inner.clone())); - }, - IxonUniv::Max(a, b) => { - bump_convert_stat!(stats, univ_process); - stack.push(UnivFrame::Max); - stack.push(UnivFrame::Process(b.clone())); - stack.push(UnivFrame::MaxLeft(a.clone())); - }, - IxonUniv::IMax(a, b) => { - bump_convert_stat!(stats, univ_process); - stack.push(UnivFrame::IMax); - stack.push(UnivFrame::Process(b.clone())); - stack.push(UnivFrame::IMaxLeft(a.clone())); - }, - IxonUniv::Var(idx) => { - bump_convert_stat!(stats, univ_process); - let pos = usize::try_from(*idx) - .map_err(|_e| format!("univ var index {idx} exceeds usize"))?; - let name = ctx.lvls.get(pos).cloned().unwrap_or_else(Name::anon); - bump_convert_stat!(stats, univ_interns); - values.push(timed_intern_univ( - intern, - KUniv::param(*idx, M::meta_field(name)), - stats, - )); - }, - }, - UnivFrame::Succ => { - let inner = values.pop().unwrap(); - bump_convert_stat!(stats, univ_interns); - values.push(timed_intern_univ(intern, KUniv::succ(inner), stats)); - }, - UnivFrame::MaxLeft(a) | UnivFrame::IMaxLeft(a) => { - stack.push(UnivFrame::Process(a)); - }, - UnivFrame::Max => { - let b = values.pop().unwrap(); - let a = values.pop().unwrap(); - bump_convert_stat!(stats, univ_interns); - values.push(timed_intern_univ(intern, KUniv::max(a, b), stats)); - }, - UnivFrame::IMax => { - let b = values.pop().unwrap(); - let a = values.pop().unwrap(); - bump_convert_stat!(stats, univ_interns); - values.push(timed_intern_univ(intern, KUniv::imax(a, b), stats)); - }, - } - } - - bump_convert_stat!(stats, univ_interns); - let result = timed_intern_univ(intern, values.pop().unwrap(), stats); - cache.insert(cache_key, result.clone()); - if stats.enabled { - stats.univ_cache_inserts += 1; - stats.univ_cache_peak = stats.univ_cache_peak.max(cache.len() as u64); - } - Ok(result) -} - -fn ingress_univ_args( - univ_idxs: &[u64], - ctx: &Ctx<'_, M>, - intern: &mut InternTable, - cache: &mut UnivCache, - stats: &mut ConvertStats, -) -> Result]>, String> { - let mut result = Vec::with_capacity(univ_idxs.len()); - for &idx in univ_idxs { - let i = usize::try_from(idx) - .map_err(|_e| format!("universe index {idx} exceeds usize"))?; - let u = ctx.univs.get(i).ok_or_else(|| { - format!("universe index {i} out of bounds (len {})", ctx.univs.len()) - })?; - result.push(ingress_univ(u, ctx, intern, cache, stats)?); - } - Ok(result.into_boxed_slice()) -} - -/// Does this stored Ixon universe tree match the (`mk*`-reduced) kernel -/// universe node-for-node? `false` means the smart constructors changed -/// it during ingress — the occurrence gets a level-spelling decoration -/// (canonicity §10.6, stage-1 rule). -fn ixon_matches_kuniv(u: &IxonUniv, k: &KUniv) -> bool { - match (u, k.data()) { - (IxonUniv::Zero, UnivData::Zero(_)) => true, - (IxonUniv::Succ(a), UnivData::Succ(b, _)) => ixon_matches_kuniv(a, b), - (IxonUniv::Max(a1, b1), UnivData::Max(a2, b2, _)) - | (IxonUniv::IMax(a1, b1), UnivData::IMax(a2, b2, _)) => { - ixon_matches_kuniv(a1, a2) && ixon_matches_kuniv(b1, b2) - }, - (IxonUniv::Var(i), UnivData::Param(j, _, _)) => i == j, - _ => false, - } -} - -/// Level-spelling decoration for a `Const` occurrence: `Some` of the -/// FULL stored level-arg spelling list when any referenced table entry's -/// `mk*` rebuild differs from its stored tree (already-normal entries -/// ride along unchanged so egress replays the whole list positionally), -/// `None` when every entry is normal. Meta mode only — call inside -/// `M::meta_field_with`; indices were validated by `ingress_univ_args`. -fn univ_args_decor( - univ_idxs: &[u64], - ctx: &Ctx<'_, M>, - ingressed: &[KUniv], -) -> Option { - let stored_at = |idx: u64| -> &Arc { - usize::try_from(idx) - .ok() - .and_then(|i| ctx.univs.get(i)) - .expect("univ index validated by ingress_univ_args") - }; - let changed = univ_idxs - .iter() - .zip(ingressed.iter()) - .any(|(&idx, k)| !ixon_matches_kuniv(stored_at(idx), k)); - if !changed { - return None; - } - let spellings: Box<[Arc]> = - univ_idxs.iter().map(|&idx| stored_at(idx).clone()).collect(); - Some(UnivDecor::Const(spellings)) -} - -/// Stage-2 decoration source for a `Const`-shaped occurrence -/// (canonicity §10.6): a `univ_patches` entry keyed by the occurrence's -/// (post-mdata) arena index carries the FULL original spelling list in -/// the virtual index space — decorate from it. Without a patch, fall -/// back to the stage-1 mk*-rebuild test against the stored primary -/// entries; on canonical tables the fallback never fires (P3), but it -/// keeps hand-built raw-table fixtures exercised. Meta mode only — call -/// inside `M::meta_field_try`. -fn univ_args_decor_at( - arena_idx: u64, - univ_idxs: &[u64], - ctx: &Ctx<'_, M>, - ingressed: &[KUniv], -) -> Result, String> { - let Some(patch_idxs) = ctx.univ_patch(arena_idx) else { - return Ok(univ_args_decor(univ_idxs, ctx, ingressed)); - }; - if patch_idxs.len() != ingressed.len() { - return Err(format!( - "univ patch at arena index {arena_idx} has {} entries but the \ - occurrence has {} level args", - patch_idxs.len(), - ingressed.len() - )); - } - let mut spellings = Vec::with_capacity(patch_idxs.len()); - for &vidx in patch_idxs { - spellings.push(ctx.univ_at_virtual(vidx)?.clone()); - } - let changed = spellings - .iter() - .zip(ingressed.iter()) - .any(|(s, k)| !ixon_matches_kuniv(s, k)); - Ok(if changed { - Some(UnivDecor::Const(spellings.into_boxed_slice())) - } else { - None - }) -} - -// ============================================================================ -// Expression ingress (iterative) -// ============================================================================ - -enum ExprFrame { - Process { - expr: Arc, - arena_idx: u64, - }, - AppArg { - arg: Arc, - arg_arena: u64, - }, - AppDone { - mdata: M::MField>, - }, - LamBody { - body: Arc, - body_arena: u64, - }, - LamDone { - name: M::MField, - bi: M::MField, - contract: BinderContract, - mdata: M::MField>, - }, - AllBody { - body: Arc, - body_arena: u64, - }, - AllDone { - name: M::MField, - bi: M::MField, - contract: BinderContract, - result: ValueContract, - mdata: M::MField>, - }, - LetVal { - val: Arc, - val_arena: u64, - body: Arc, - body_arena: u64, - binder_name: Name, - }, - LetBody { - body: Arc, - body_arena: u64, - }, - LetDone { - name: M::MField, - contract: LetContract, - mdata: M::MField>, - }, - PrjDone { - type_id: KId, - field_idx: u64, - mdata: M::MField>, - }, - Cache { - key: (usize, u64), - }, - /// Push a binder name before processing a body (for BVar name resolution). - BinderPush { - name: Name, - }, - /// Pop a binder name after processing a body. - BinderPop, -} - -/// Default empty arena for constants without metadata. -static DEFAULT_ARENA: ExprMeta = ExprMeta { nodes: Vec::new() }; - -fn ingress_expr( - root_expr: &Arc, - root_arena: u64, - ctx: &Ctx<'_, M>, - intern: &mut InternTable, - ixon_env: crate::input::Input<'_>, - cache: &mut ExprCache, - univ_cache: &mut UnivCache, - stats: &mut ConvertStats, -) -> Result, String> { - bump_convert_stat!(stats, expr_roots); - let mut stack: Vec> = - vec![ExprFrame::Process { expr: root_expr.clone(), arena_idx: root_arena }]; - let mut values: Vec> = Vec::new(); - // Binder name context for resolving BVar names via de Bruijn index. - // Pushed when entering a binder body, popped when leaving. - let mut binder_names: Vec = Vec::new(); - - while let Some(frame) = stack.pop() { - match frame { - ExprFrame::Process { mut expr, arena_idx } => { - bump_convert_stat!(stats, expr_process); - let process_t0 = - if stats.enabled { Some(Instant::now()) } else { None }; - - // `Share` is transparent and keeps the same arena root. Expand it - // before cache/mdata work; the old path walked metadata for the Share - // frame, discarded it, then reprocessed the shared expression. - while let IxonExpr::Share(share_idx) = expr.as_ref() { - bump_convert_stat!(stats, share_expansions); - expr = - ctx - .sharing - .get(usize::try_from(*share_idx).map_err(|_e| { - format!("Share index {share_idx} exceeds usize") - })?) - .ok_or_else(|| format!("invalid Share index {share_idx}"))? - .clone(); - } - - let is_var = matches!(expr.as_ref(), IxonExpr::Var(_)); - - // Check cache before walking mdata. The key includes the original arena - // root, so a hit already includes the resolved metadata layers. - let cache_key = (Arc::as_ptr(&expr) as usize, arena_idx); - if !is_var { - let lookup_t0 = - if stats.enabled { Some(Instant::now()) } else { None }; - let cached = cache.get(&cache_key); - if let Some(t0) = lookup_t0 { - stats.expr_cache_lookup_ns += elapsed_ns(t0); - } - if let Some(cached) = cached { - bump_convert_stat!(stats, expr_cache_hits); - values.push(cached.clone()); - if let Some(t0) = process_t0 { - stats.process_arm_ns += elapsed_ns(t0); - } - continue; - } - bump_convert_stat!(stats, expr_cache_misses); - } - - // Walk mdata chain in arena - let arena_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - let mut current_idx = arena_idx; - let mut mdata_layers: Vec = Vec::new(); - while let Some(ExprMetaData::Mdata { mdata, child }) = - ctx.arena.nodes.get( - usize::try_from(current_idx).map_err(|_e| { - format!("arena index {current_idx} exceeds usize") - })?, - ) - { - bump_convert_stat!(stats, mdata_nodes); - bump_convert_stat!(stats, mdata_kv_maps, mdata.len()); - let kv_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - for kvm in mdata { - let data = ixon_env.mdata(kvm)?; - if M::HAS_META && source::has_metadata(&data) { - return Err( - "semantic contracts cannot come from optional metadata".into(), - ); - } - mdata_layers.push(data); - } - if let Some(t0) = kv_t0 { - stats.resolve_kvmap_ns += elapsed_ns(t0); - stats.resolve_kvmap_calls += mdata.len() as u64; - } - current_idx = *child; - } - if let Some(t0) = arena_t0 { - stats.arena_walk_ns += elapsed_ns(t0); - } - - // BVar early return (no caching needed for leaves) - if let IxonExpr::Var(idx) = expr.as_ref() { - bump_convert_stat!(stats, var_nodes); - // Resolve name from the binder context using de Bruijn index. - let idx_usize = usize::try_from(*idx) - .map_err(|_e| format!("BVar index {idx} exceeds usize"))?; - let name = binder_names - .len() - .checked_sub(1) - .and_then(|top| top.checked_sub(idx_usize)) - .and_then(|i| binder_names.get(i)) - .cloned() - .unwrap_or_else(Name::anon); - let name_field = M::meta_field(name); - let mdata_field = M::meta_field(mdata_layers); - let key = super::env::ExprKey::Var(*idx); - values.push(timed_intern_or_build( - intern, - &key, - || KExpr::var_mdata(*idx, name_field, mdata_field), - stats, - )); - if let Some(t0) = process_t0 { - stats.process_arm_ns += elapsed_ns(t0); - } - continue; - } - - let node = - ctx - .arena - .nodes - .get(usize::try_from(current_idx).map_err(|_e| { - format!("arena index {current_idx} exceeds usize") - })?) - .unwrap_or(&ExprMetaData::Leaf); - - stack.push(ExprFrame::Cache { key: cache_key }); - let mdata = M::meta_field(mdata_layers); - - match expr.as_ref() { - IxonExpr::Sort(idx) => { - bump_convert_stat!(stats, sort_nodes); - let u = - ctx - .univs - .get(usize::try_from(*idx).map_err(|_e| { - format!("Sort univ index {idx} exceeds usize") - })?) - .ok_or_else(|| format!("invalid Sort univ index {idx}"))?; - let zu = ingress_univ(u, ctx, intern, univ_cache, stats)?; - // Spelling decoration (canonicity §10.6): the occurrence's - // `univ_patches` entry (stage 2) is the primary source of - // the original spelling; without one, fall back to the - // stage-1 rule (stored tree vs mk* rebuild — never fires on - // canonical tables, P3). Anon mode skips the closure. - let decor: M::MField> = - M::meta_field_try::, _, String>(|| { - let spelling = match ctx.univ_patch(current_idx) { - Some(idxs) => { - let vidx = *idxs.first().ok_or_else(|| { - format!("empty univ patch at arena index {current_idx}") - })?; - ctx.univ_at_virtual(vidx)? - }, - None => u, - }; - Ok(if ixon_matches_kuniv(spelling, &zu) { - None - } else { - Some(UnivDecor::Sort(spelling.clone())) - }) - })?; - let key = super::env::ExprKey::Sort( - *zu.addr(), - M::meta_get(&decor).cloned().flatten(), - ); - values.push(timed_intern_or_build( - intern, - &key, - || KExpr::sort_full(zu, mdata, decor), - stats, - )); - }, - - IxonExpr::Var(_) | IxonExpr::Share(_) => unreachable!(), - - IxonExpr::Ref(ref_idx, univ_idxs) => { - bump_convert_stat!(stats, ref_nodes); - let addr = ctx - .refs - .get( - usize::try_from(*ref_idx) - .map_err(|_e| format!("Ref index {ref_idx} exceeds usize"))?, - ) - .ok_or_else(|| format!("invalid Ref index {ref_idx}"))? - .clone(); - ixon_env.require_reference(&addr)?; - // Meta mode: walk the arena to find the Ref's Lean name. - // Anon mode: the closure is never invoked — no arena walk, - // no name construction, no resolve_name call. - let name_field: M::MField = M::meta_field_try::< - Name, - _, - String, - >(|| match node { - ExprMetaData::Ref { name: name_addr } => { - Ok(resolve_name(name_addr, ctx.names)) - }, - _ => Err(format!( - "Ref at index {ref_idx} (addr {}) has no metadata name (node={node:?})", - &addr.hex()[..8] - )), - })?; - let univs = - ingress_univ_args(univ_idxs, ctx, intern, univ_cache, stats)?; - let id = KId::new(addr, name_field); - let decor: M::MField> = - M::meta_field_try::, _, String>(|| { - univ_args_decor_at(current_idx, univ_idxs, ctx, &univs) - })?; - let key = super::env::ExprKey::Const( - id.addr.clone(), - univs.iter().map(|u| *u.addr()).collect(), - M::meta_get(&decor).cloned().flatten(), - ); - values.push(timed_intern_or_build( - intern, - &key, - || KExpr::cnst_full(id, univs, mdata, decor), - stats, - )); - }, - - IxonExpr::Rec(rec_idx, univ_idxs) => { - bump_convert_stat!(stats, rec_nodes); - let mid = ctx - .mut_ctx - .get( - usize::try_from(*rec_idx) - .map_err(|_e| format!("Rec index {rec_idx} exceeds usize"))?, - ) - .ok_or_else(|| format!("invalid Rec index {rec_idx}"))? - .clone(); - if let Some(refs) = ctx.local_refs { - refs.borrow_mut().insert(mid.addr.clone()); - } - let univs = - ingress_univ_args(univ_idxs, ctx, intern, univ_cache, stats)?; - let decor: M::MField> = - M::meta_field_try::, _, String>(|| { - univ_args_decor_at(current_idx, univ_idxs, ctx, &univs) - })?; - let key = super::env::ExprKey::Const( - mid.addr.clone(), - univs.iter().map(|u| *u.addr()).collect(), - M::meta_get(&decor).cloned().flatten(), - ); - values.push(timed_intern_or_build( - intern, - &key, - || KExpr::cnst_full(mid, univs, mdata, decor), - stats, - )); - }, - - IxonExpr::App(f, a) => { - bump_convert_stat!(stats, app_nodes); - let (f_arena, a_arena) = match node { - ExprMetaData::App { children } => (children[0], children[1]), - _ => (current_idx, current_idx), - }; - stack.push(ExprFrame::AppDone { mdata }); - stack - .push(ExprFrame::AppArg { arg: a.clone(), arg_arena: a_arena }); - stack - .push(ExprFrame::Process { expr: f.clone(), arena_idx: f_arena }); - }, - - // Input contracts participate in conversion. Resource validity still - // requires the separate addressed validator in `crate::resource`. - IxonExpr::Lam(contract, ty, body) => { - bump_convert_stat!(stats, lam_nodes); - let (name, bi, ty_arena, body_arena) = match node { - ExprMetaData::Binder { name: addr, info, children } => ( - resolve_name(addr, ctx.names), - info.clone(), - children[0], - children[1], - ), - _ => ( - ctx.synth_name(), - BinderInfo::Default, - current_idx, - current_idx, - ), - }; - stack.push(ExprFrame::LamDone { - name: M::meta_field(name.clone()), - bi: M::meta_field(bi), - contract: *contract, - mdata, - }); - stack.push(ExprFrame::BinderPop); - stack.push(ExprFrame::LamBody { body: body.clone(), body_arena }); - stack.push(ExprFrame::BinderPush { name }); - stack.push(ExprFrame::Process { - expr: ty.clone(), - arena_idx: ty_arena, - }); - }, - - IxonExpr::All(contract, result, ty, body) => { - bump_convert_stat!(stats, all_nodes); - let (name, bi, ty_arena, body_arena) = match node { - ExprMetaData::Binder { name: addr, info, children } => ( - resolve_name(addr, ctx.names), - info.clone(), - children[0], - children[1], - ), - _ => ( - ctx.synth_name(), - BinderInfo::Default, - current_idx, - current_idx, - ), - }; - stack.push(ExprFrame::AllDone { - name: M::meta_field(name.clone()), - bi: M::meta_field(bi), - contract: *contract, - result: *result, - mdata, - }); - stack.push(ExprFrame::BinderPop); - stack.push(ExprFrame::AllBody { body: body.clone(), body_arena }); - stack.push(ExprFrame::BinderPush { name }); - stack.push(ExprFrame::Process { - expr: ty.clone(), - arena_idx: ty_arena, - }); - }, - - IxonExpr::Let(contract, ty, val, body) => { - bump_convert_stat!(stats, let_nodes); - let (name, ty_arena, val_arena, body_arena) = match node { - ExprMetaData::LetBinder { name: addr, children } => ( - resolve_name(addr, ctx.names), - children[0], - children[1], - children[2], - ), - _ => (ctx.synth_name(), current_idx, current_idx, current_idx), - }; - stack.push(ExprFrame::LetDone { - name: M::meta_field(name.clone()), - contract: *contract, - mdata, - }); - stack.push(ExprFrame::BinderPop); - stack.push(ExprFrame::LetVal { - val: val.clone(), - val_arena, - body: body.clone(), - body_arena, - binder_name: name, - }); - stack.push(ExprFrame::Process { - expr: ty.clone(), - arena_idx: ty_arena, - }); - }, - - IxonExpr::Prj(type_ref_idx, field_idx, s) => { - bump_convert_stat!(stats, prj_nodes); - let type_addr = ctx - .refs - .get(usize::try_from(*type_ref_idx).map_err(|_e| { - format!("Prj type ref index {type_ref_idx} exceeds usize") - })?) - .ok_or_else(|| { - format!("invalid Prj type ref index {type_ref_idx}") - })? - .clone(); - ixon_env.require_reference(&type_addr)?; - // The arena holds both the struct name and the child arena - // pointer (needed for the projected value's metadata walk). - // Meta mode reads both; Anon mode skips the entire arena - // touch — no name, no child-arena indexing. - let mut child_arena: u64 = 0; - let struct_name_field: M::MField = M::meta_field_try::< - Name, - _, - String, - >( - || match node - { - ExprMetaData::Prj { struct_name: addr, child } => { - child_arena = *child; - Ok(resolve_name(addr, ctx.names)) - }, - _ => Err(format!( - "Prj at ref index {type_ref_idx} (addr {}) has no metadata name (node={node:?})", - &type_addr.hex()[..8] - )), - } - )?; - stack.push(ExprFrame::PrjDone { - type_id: KId::new(type_addr, struct_name_field), - field_idx: *field_idx, - mdata, - }); - stack.push(ExprFrame::Process { - expr: s.clone(), - arena_idx: child_arena, - }); - }, - - IxonExpr::Str(ref_idx) => { - bump_convert_stat!(stats, str_nodes); - let addr = ctx - .refs - .get(usize::try_from(*ref_idx).map_err(|_e| { - format!("Str ref index {ref_idx} exceeds usize") - })?) - .ok_or_else(|| format!("invalid Str ref index {ref_idx}"))?; - let gb_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - let blob = ixon_env.blob(addr)?.ok_or_else(|| { - format!("missing Str blob at addr {}", addr.hex()) - })?; - if let Some(t0) = gb_t0 { - stats.get_blob_ns += elapsed_ns(t0); - stats.get_blob_calls += 1; - } - let s = String::from_utf8(blob).map_err(|e| { - format!("invalid UTF-8 in Str blob at addr {}: {e}", addr.hex()) - })?; - let blob_addr = addr.clone(); - let key = super::env::ExprKey::Str(blob_addr.clone()); - values.push(timed_intern_or_build( - intern, - &key, - || KExpr::str_mdata(s, blob_addr, mdata), - stats, - )); - }, - - IxonExpr::Nat(ref_idx) => { - bump_convert_stat!(stats, nat_nodes); - let addr = ctx - .refs - .get(usize::try_from(*ref_idx).map_err(|_e| { - format!("Nat ref index {ref_idx} exceeds usize") - })?) - .ok_or_else(|| format!("invalid Nat ref index {ref_idx}"))?; - let gb_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - let blob = ixon_env.blob(addr)?.ok_or_else(|| { - format!("missing Nat blob at addr {}", addr.hex()) - })?; - if let Some(t0) = gb_t0 { - stats.get_blob_ns += elapsed_ns(t0); - stats.get_blob_calls += 1; - } - let n = Nat::from_le_bytes(&blob); - let blob_addr = addr.clone(); - let key = super::env::ExprKey::Nat(blob_addr.clone()); - values.push(timed_intern_or_build( - intern, - &key, - || KExpr::nat_mdata(n, blob_addr, mdata), - stats, - )); - }, - } - if let Some(t0) = process_t0 { - stats.process_arm_ns += elapsed_ns(t0); - } - }, - - // Continuation frames - ExprFrame::AppArg { arg, arg_arena } => { - let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - stack.push(ExprFrame::Process { expr: arg, arena_idx: arg_arena }); - if let Some(t0) = cont_t0 { - stats.continuation_arms_ns += elapsed_ns(t0); - } - }, - ExprFrame::AppDone { mdata } => { - let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - let a = values.pop().unwrap(); - let f = values.pop().unwrap(); - let key = super::env::ExprKey::App(*f.addr(), *a.addr()); - values.push(timed_intern_or_build( - intern, - &key, - || KExpr::app_mdata(f, a, mdata), - stats, - )); - if let Some(t0) = cont_t0 { - stats.continuation_arms_ns += elapsed_ns(t0); - } - }, - ExprFrame::LamBody { body, body_arena } => { - let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - // The binder name was already pushed by BinderPush before this frame - stack.push(ExprFrame::Process { expr: body, arena_idx: body_arena }); - if let Some(t0) = cont_t0 { - stats.continuation_arms_ns += elapsed_ns(t0); - } - }, - ExprFrame::LamDone { name, bi, contract, mdata } => { - let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - let body = values.pop().unwrap(); - let ty = values.pop().unwrap(); - let key = super::env::ExprKey::Lam(contract, *ty.addr(), *body.addr()); - values.push(timed_intern_or_build( - intern, - &key, - || KExpr::lam_contract_mdata(name, bi, contract, ty, body, mdata), - stats, - )); - if let Some(t0) = cont_t0 { - stats.continuation_arms_ns += elapsed_ns(t0); - } - }, - ExprFrame::AllBody { body, body_arena } - | ExprFrame::LetBody { body, body_arena } => { - let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - stack.push(ExprFrame::Process { expr: body, arena_idx: body_arena }); - if let Some(t0) = cont_t0 { - stats.continuation_arms_ns += elapsed_ns(t0); - } - }, - ExprFrame::AllDone { name, bi, contract, result, mdata } => { - let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - let body = values.pop().unwrap(); - let ty = values.pop().unwrap(); - let key = - super::env::ExprKey::All(contract, result, *ty.addr(), *body.addr()); - values.push(timed_intern_or_build( - intern, - &key, - || { - KExpr::all_contract_mdata( - name, bi, contract, result, ty, body, mdata, - ) - }, - stats, - )); - if let Some(t0) = cont_t0 { - stats.continuation_arms_ns += elapsed_ns(t0); - } - }, - ExprFrame::LetVal { val, val_arena, body, body_arena, binder_name } => { - let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - stack.push(ExprFrame::LetBody { body, body_arena }); - stack.push(ExprFrame::BinderPush { name: binder_name }); - stack.push(ExprFrame::Process { expr: val, arena_idx: val_arena }); - if let Some(t0) = cont_t0 { - stats.continuation_arms_ns += elapsed_ns(t0); - } - }, - ExprFrame::LetDone { name, contract, mdata } => { - let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - let body = values.pop().unwrap(); - let val = values.pop().unwrap(); - let ty = values.pop().unwrap(); - let key = super::env::ExprKey::Let( - contract, - *ty.addr(), - *val.addr(), - *body.addr(), - ); - values.push(timed_intern_or_build( - intern, - &key, - || KExpr::let_contract_mdata(name, contract, ty, val, body, mdata), - stats, - )); - if let Some(t0) = cont_t0 { - stats.continuation_arms_ns += elapsed_ns(t0); - } - }, - ExprFrame::BinderPush { name } => { - let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - binder_names.push(name); - if let Some(t0) = cont_t0 { - stats.continuation_arms_ns += elapsed_ns(t0); - } - }, - ExprFrame::BinderPop => { - let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - binder_names.pop(); - if let Some(t0) = cont_t0 { - stats.continuation_arms_ns += elapsed_ns(t0); - } - }, - ExprFrame::PrjDone { type_id, field_idx, mdata } => { - let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - let s = values.pop().unwrap(); - let key = - super::env::ExprKey::Prj(type_id.addr.clone(), field_idx, *s.addr()); - values.push(timed_intern_or_build( - intern, - &key, - || KExpr::prj_mdata(type_id, field_idx, s, mdata), - stats, - )); - if let Some(t0) = cont_t0 { - stats.continuation_arms_ns += elapsed_ns(t0); - } - }, - ExprFrame::Cache { key } => { - let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - let result = values.last().unwrap().clone(); - let ins_t0 = if stats.enabled { Some(Instant::now()) } else { None }; - cache.insert(key, result); - if let Some(t0) = ins_t0 { - stats.expr_cache_insert_ns += elapsed_ns(t0); - stats.expr_cache_inserts += 1; - stats.expr_cache_peak = stats.expr_cache_peak.max(cache.len() as u64); - } - if let Some(t0) = cont_t0 { - stats.continuation_arms_ns += elapsed_ns(t0); - } - }, - } - } - - values.pop().ok_or_else(|| "ingress_expr: empty value stack".to_string()) -} - -// ============================================================================ -// Constant ingress -// ============================================================================ - -#[allow(clippy::too_many_arguments)] -fn ingress_defn( - def: &ixon::constant::Definition, - self_id: KId, - meta: &ConstantMeta, - ixon_env: crate::input::Input<'_>, - names: &FxHashMap, - name_to_addr: &FxHashMap, - sharing: &[Arc], - refs: &[Address], - univs: &[Arc], - block: KId, - intern: &mut InternTable, - stats: &mut ConvertStats, - // The semantic targets of `Expr::Rec`: the serialized owner's members - // (see `owner_mut_ctx`), or `[self_id]` for a standalone. Metadata - // never selects them. - mut_ctx: Vec>, - local_dependencies: Option<&mut LocalDefnDependencies>, -) -> Result, KConst)>, String> { - let mut cache: ExprCache = FxHashMap::default(); - let mut univ_cache: UnivCache = FxHashMap::default(); - // Hints live only in `Env::anon_hints`, keyed by the constant's own - // address, in both anon and meta modes. Without an entry the kernel - // falls back to `Regular(0)` — always correct, just more def-eq work. - let hints = - ixon_env.hint(&self_id.addr).unwrap_or(ReducibilityHints::Regular(0)); - let safety = def.safety; - let local_refs = RefCell::new(FxHashSet::default()); - let (level_params, arena, type_root, value_root, all_addrs) = match &meta.info - { - ConstantMetaInfo::Def { - lvls, arena, type_root, value_root, all, .. - } => ( - resolve_level_params(lvls, names), - arena, - *type_root, - *value_root, - all.clone(), - ), - _ => (vec![], &DEFAULT_ARENA, 0, 0, vec![]), - }; - - let ctx = Ctx { - sharing, - refs, - univs, - mut_ctx, - arena, - names, - lvls: level_params.clone(), - univ_patches: univ_patch_map(meta), - meta_univs: &meta.meta_univs, - local_refs: local_dependencies.as_ref().map(|_| &local_refs), - synth_counter: Cell::new(0), - }; - - let typ = ingress_expr( - &def.typ, - type_root, - &ctx, - intern, - ixon_env, - &mut cache, - &mut univ_cache, - stats, - )?; - let value = ingress_expr( - &def.value, - value_root, - &ctx, - intern, - ixon_env, - &mut cache, - &mut univ_cache, - stats, - )?; - let lean_all = resolve_all(&all_addrs, names, name_to_addr)?; - - let name = resolve_name( - match &meta.info { - ConstantMetaInfo::Def { name, .. } => name, - _ => &self_id.addr, - }, - names, - ); - - if let Some(dependencies) = local_dependencies { - dependencies.insert(&self_id.addr, def.safety, local_refs.into_inner()); - } - - Ok(vec![( - self_id, - KConst::Defn { - name: M::meta_field(name), - level_params: M::meta_field(level_params), - kind: def.kind, - safety, - hints, - lvls: def.lvls, - ty: typ, - val: value, - lean_all: M::meta_field(lean_all), - block, - }, - )]) -} - -#[allow(clippy::too_many_arguments)] -fn ingress_recursor( - rec: &ixon::constant::Recursor, - self_id: KId, - meta: &ConstantMeta, - ixon_env: crate::input::Input<'_>, - names: &FxHashMap, - name_to_addr: &FxHashMap, - sharing: &[Arc], - refs: &[Address], - univs: &[Arc], - block: KId, - intern: &mut InternTable, - stats: &mut ConvertStats, - // The semantic targets of `Expr::Rec`, as for `ingress_defn`. - mut_ctx: Vec>, -) -> Result, KConst)>, String> { - let mut cache: ExprCache = FxHashMap::default(); - let mut univ_cache: UnivCache = FxHashMap::default(); - let (level_params, arena, type_root, rule_roots, rule_ctor_addrs, all_addrs) = - match &meta.info { - ConstantMetaInfo::Rec { - lvls, - arena, - type_root, - rule_roots, - rules, - all, - .. - } => ( - resolve_level_params(lvls, names), - arena, - *type_root, - rule_roots.clone(), - rules.clone(), - all.clone(), - ), - _ => (vec![], &DEFAULT_ARENA, 0, vec![], vec![], vec![]), - }; - - let ctx = Ctx { - sharing, - refs, - univs, - mut_ctx, - arena, - names, - lvls: level_params.clone(), - univ_patches: univ_patch_map(meta), - meta_univs: &meta.meta_univs, - local_refs: None, - synth_counter: Cell::new(0), - }; - - let typ = ingress_expr( - &rec.typ, - type_root, - &ctx, - intern, - ixon_env, - &mut cache, - &mut univ_cache, - stats, - )?; - let rules: Result>, String> = rec - .rules - .iter() - .enumerate() - .map(|(i, rule)| { - // If the meta arm above matched `Rec`, we have one `rule_root` per - // Ixon rule (compile emits them in lockstep). The `DEFAULT_ARENA` - // fallback arm supplies an empty `rule_roots` vec, in which case - // falling back to root 0 is fine because the arena is empty — every - // arena index then misses and degrades to `ExprMetaData::Leaf`. - let rhs_root = rule_roots.get(i).copied().unwrap_or(0); - let rhs = ingress_expr( - &rule.rhs, - rhs_root, - &ctx, - intern, - ixon_env, - &mut cache, - &mut univ_cache, - stats, - )?; - // `ConstantMetaInfo::Rec::rules[i]` is the name-hash address of the - // i-th rule's ctor. Resolve it through the names map; fall back to - // anonymous when metadata is absent (recursor compiled without - // meta, e.g. synthetic kernel tests). - let ctor_name = rule_ctor_addrs - .get(i) - .map_or_else(Name::anon, |a| resolve_name(a, names)); - Ok(RecRule { ctor: M::meta_field(ctor_name), fields: rule.fields, rhs }) - }) - .collect(); - let lean_all = resolve_all(&all_addrs, names, name_to_addr)?; - - let name = resolve_name( - match &meta.info { - ConstantMetaInfo::Rec { name, .. } => name, - _ => &self_id.addr, - }, - names, - ); - - Ok(vec![( - self_id, - KConst::Recr { - name: M::meta_field(name), - level_params: M::meta_field(level_params), - k: rec.k, - is_unsafe: rec.is_unsafe, - lvls: rec.lvls, - params: rec.params, - indices: rec.indices, - motives: rec.motives, - minors: rec.minors, - block, - member_idx: 0, // filled in by caller for muts blocks - ty: typ, - rules: rules?, - lean_all: M::meta_field(lean_all), - }, - )]) -} - -#[allow(clippy::too_many_arguments)] -fn ingress_standalone( - const_name: &Name, - addr: &Address, - constant: &Constant, - meta: &ConstantMeta, - ixon_env: crate::input::Input<'_>, - names: &FxHashMap, - name_to_addr: &FxHashMap, - intern: &mut InternTable, - stats: &mut ConvertStats, -) -> Result, KConst)>, String> { - let self_id: KId = - KId::new(addr.clone(), M::meta_field(const_name.clone())); - - match &constant.info { - IxonCI::Defn(def) => { - let mut local_dependencies = LocalDefnDependencies::default(); - let entries = ingress_defn( - def, - self_id.clone(), - meta, - ixon_env, - names, - name_to_addr, - &constant.sharing, - &constant.refs, - &constant.univs, - self_id.clone(), - intern, - stats, - // A standalone is a singleton unit: `Rec(0)` is itself. - vec![self_id], - Some(&mut local_dependencies), - )?; - local_dependencies.validate()?; - Ok(entries) - }, - - IxonCI::Axio(ax) => { - let mut cache: ExprCache = FxHashMap::default(); - let mut univ_cache: UnivCache = FxHashMap::default(); - let (level_params, arena, type_root) = match &meta.info { - ConstantMetaInfo::Axio { lvls, arena, type_root, .. } => { - (resolve_level_params(lvls, names), arena, *type_root) - }, - _ => (vec![], &DEFAULT_ARENA, 0), - }; - let ctx = Ctx { - sharing: &constant.sharing, - refs: &constant.refs, - univs: &constant.univs, - mut_ctx: vec![], - arena, - names, - lvls: level_params.clone(), - univ_patches: univ_patch_map(meta), - meta_univs: &meta.meta_univs, - local_refs: None, - synth_counter: Cell::new(0), - }; - let typ = ingress_expr( - &ax.typ, - type_root, - &ctx, - intern, - ixon_env, - &mut cache, - &mut univ_cache, - stats, - )?; - let name = resolve_name( - match &meta.info { - ConstantMetaInfo::Axio { name, .. } => name, - _ => addr, - }, - names, - ); - Ok(vec![( - self_id, - KConst::Axio { - name: M::meta_field(name), - level_params: M::meta_field(level_params), - is_unsafe: ax.is_unsafe, - lvls: ax.lvls, - ty: typ, - }, - )]) - }, - - IxonCI::Quot(q) => { - let mut cache: ExprCache = FxHashMap::default(); - let mut univ_cache: UnivCache = FxHashMap::default(); - let (level_params, arena, type_root) = match &meta.info { - ConstantMetaInfo::Quot { lvls, arena, type_root, .. } => { - (resolve_level_params(lvls, names), arena, *type_root) - }, - _ => (vec![], &DEFAULT_ARENA, 0), - }; - let ctx = Ctx { - sharing: &constant.sharing, - refs: &constant.refs, - univs: &constant.univs, - mut_ctx: vec![], - arena, - names, - lvls: level_params.clone(), - univ_patches: univ_patch_map(meta), - meta_univs: &meta.meta_univs, - local_refs: None, - synth_counter: Cell::new(0), - }; - let typ = ingress_expr( - &q.typ, - type_root, - &ctx, - intern, - ixon_env, - &mut cache, - &mut univ_cache, - stats, - )?; - let name = resolve_name( - match &meta.info { - ConstantMetaInfo::Quot { name, .. } => name, - _ => addr, - }, - names, - ); - Ok(vec![( - self_id, - KConst::Quot { - name: M::meta_field(name), - level_params: M::meta_field(level_params), - kind: q.kind, - lvls: q.lvls, - ty: typ, - }, - )]) - }, - - IxonCI::Recr(rec) => ingress_recursor( - rec, - self_id.clone(), - meta, - ixon_env, - names, - name_to_addr, - &constant.sharing, - &constant.refs, - &constant.univs, - self_id.clone(), - intern, - stats, - vec![self_id], - ), - - // Projections and Muts are handled in ingress_muts_block - IxonCI::IPrj(_) - | IxonCI::CPrj(_) - | IxonCI::RPrj(_) - | IxonCI::DPrj(_) - | IxonCI::Muts(_) => Ok(vec![]), - } -} - -// ============================================================================ -// Muts block ingress -// ============================================================================ - -#[allow(clippy::too_many_arguments)] -fn ingress_muts_inductive( - ind: &ixon::constant::Inductive, - self_id: &KId, - meta: &ConstantMeta, - ixon_env: crate::input::Input<'_>, - names: &FxHashMap, - name_to_addr: &FxHashMap, - block_constant: &Constant, - block_id: KId, - member_idx: u64, - // The owner's members, the semantic targets of `Expr::Rec`. - mut_ctx: Vec>, - intern: &mut InternTable, - stats: &mut ConvertStats, -) -> Result, KConst)>, String> { - let (level_params, arena, type_root, all_addrs, ctor_addrs) = match &meta.info - { - ConstantMetaInfo::Indc { lvls, arena, type_root, all, ctors, .. } => ( - resolve_level_params(lvls, names), - arena, - *type_root, - all.clone(), - ctors.clone(), - ), - _ => (vec![], &DEFAULT_ARENA, 0, vec![], vec![]), - }; - - let mut cache: ExprCache = FxHashMap::default(); - let mut univ_cache: UnivCache = FxHashMap::default(); - let ctx = Ctx { - sharing: &block_constant.sharing, - refs: &block_constant.refs, - univs: &block_constant.univs, - mut_ctx, - arena, - names, - lvls: level_params.clone(), - univ_patches: univ_patch_map(meta), - meta_univs: &meta.meta_univs, - local_refs: None, - synth_counter: Cell::new(0), - }; - - let typ = ingress_expr( - &ind.typ, - type_root, - &ctx, - intern, - ixon_env, - &mut cache, - &mut univ_cache, - stats, - )?; - let lean_all = resolve_all(&all_addrs, names, name_to_addr)?; - // Metadata selects presentation names; the owner and constructor position - // determine semantic identity. Validate that each named address agrees - // before any payload can be installed under that address. - let ctor_ids: Vec> = resolve_all(&ctor_addrs, names, name_to_addr)?; - if ctor_ids.len() != ind.ctors.len() { - return Err("constructor metadata count differs from its owner".into()); - } - for (index, id) in ctor_ids.iter().enumerate() { - let index = u64::try_from(index).map_err(|error| error.to_string())?; - if id.addr != anon_ctor_proj_addr(&block_id.addr, member_idx, index) { - return Err(format!( - "constructor metadata address differs from owner/index ({member_idx}, {index})" - )); - } - } - - let name = resolve_name( - match &meta.info { - ConstantMetaInfo::Indc { name, .. } => name, - _ => &self_id.addr, - }, - names, - ); - - let mut results = vec![( - self_id.clone(), - KConst::Indc { - name: M::meta_field(name), - level_params: M::meta_field(level_params.clone()), - lvls: ind.lvls, - params: ind.params, - indices: ind.indices, - is_unsafe: ind.is_unsafe, - block: block_id, - member_idx, - ty: typ, - ctors: ctor_ids.clone(), - lean_all: M::meta_field(lean_all), - }, - )]; - - // Emit constructors. For each position `cidx`, `ctor_addrs[cidx]` is the - // name-hash address of the ctor's Lean name; from that we resolve the name - // and then look up its per-ctor ConstantMeta (holding the ctor's own arena - // and type_root). These must be present — the parent inductive's meta - // doesn't carry ctor-specific expression metadata inline, so if the Named - // entry is missing we'd be roundtripping with no arena and synthesize junk - // binder names. Error loudly instead of silently falling back. - for (cidx, ctor) in ind.ctors.iter().enumerate() { - stats.record_cache_clear(&cache); - cache.clear(); - let ctor_id = ctor_ids - .get(cidx) - .cloned() - .ok_or_else(|| format!("missing ctor_id for constructor index {cidx}"))?; - let ctor_name_addr = ctor_addrs.get(cidx).ok_or_else(|| { - format!("missing ctor_addrs entry for constructor index {cidx}") - })?; - let ctor_name = resolve_name(ctor_name_addr, names); - let ctor_named = ixon_env.named(&ctor_name).ok_or_else(|| { - format!( - "missing Named entry for ctor '{ctor_name}' (cidx={cidx}) — \ - per-ctor metadata (arena, type_root, lvls) must be registered \ - for every constructor of this inductive block" - ) - })?; - if ctor_named.addr != ctor_id.addr { - return Err(format!("ctor '{ctor_name}' metadata address changed")); - } - - let ctor_named_meta = ctor_named.meta(); - let (ctor_lvl_params, ctor_arena, ctor_type_root) = - match &ctor_named_meta.info { - ConstantMetaInfo::Ctor { lvls, arena, type_root, .. } => { - (resolve_level_params(lvls, names), arena, *type_root) - }, - other => { - return Err(format!( - "ctor '{ctor_name}' has unexpected meta kind '{}' (expected Ctor)", - other.kind_name() - )); - }, - }; - - let ctor_ctx = Ctx { - sharing: &block_constant.sharing, - refs: &block_constant.refs, - univs: &block_constant.univs, - mut_ctx: ctx.mut_ctx.clone(), - arena: ctor_arena, - names, - lvls: ctor_lvl_params.clone(), - // Constructor patches are scoped to the ctor's own meta — its - // virtual indices extend the BLOCK primary table with the ctor's - // own `meta_univs` (canonicity §10.6). - univ_patches: univ_patch_map(&ctor_named_meta), - meta_univs: &ctor_named_meta.meta_univs, - local_refs: None, - synth_counter: Cell::new(0), - }; - let mut ctor_univ_cache: UnivCache = FxHashMap::default(); - - let ctor_typ = ingress_expr( - &ctor.typ, - ctor_type_root, - &ctor_ctx, - intern, - ixon_env, - &mut cache, - &mut ctor_univ_cache, - stats, - )?; - - results.push(( - ctor_id, - KConst::Ctor { - name: M::meta_field(ctor_name), - level_params: M::meta_field(ctor_lvl_params), - is_unsafe: ctor.is_unsafe, - lvls: ctor.lvls, - induct: self_id.clone(), - cidx: ctor.cidx, - params: ctor.params, - fields: ctor.fields, - ty: ctor_typ, - }, - )); - } - - Ok(results) -} - -/// Serialized mutual units follow the certified reader's shape policy: -/// definitions form their own block; inductives and recursors may share a -/// family block. Direct Lean source grouping has a separate contract. -pub(crate) fn validate_serialized_muts( - members: &[IxonMutConst], -) -> Result<(), String> { - if members.is_empty() { - return Err("empty serialized mutual block".into()); - } - let definitions = - members.iter().filter(|m| matches!(m, IxonMutConst::Defn(_))).count(); - if definitions != 0 && definitions != members.len() { - return Err("unsupported serialized mutual block: definitions mixed with an inductive/recursor family".into()); - } - Ok(()) -} - -#[allow(clippy::too_many_arguments)] -fn ingress_muts_block( - entry_name: &Name, - entry_addr: &Address, - all: &[Vec
], - ixon_env: crate::input::Input<'_>, - names: &FxHashMap, - name_to_addr: &FxHashMap, - intern: &mut InternTable, - stats: &mut ConvertStats, -) -> Result, KConst)>, String> { - let block_id: KId = - KId::new(entry_addr.clone(), M::meta_field(entry_name.clone())); - - let block_constant = ixon_env.constant(entry_addr)?.ok_or_else(|| { - format!("missing Muts block constant {}", entry_addr.hex()) - })?; - let members = match &block_constant.info { - IxonCI::Muts(m) => m, - _ => return Err(format!("constant at {} is not Muts", entry_addr.hex())), - }; - validate_serialized_muts(members)?; - - // The serialized owner defines its members: member `i`'s address is its - // projection from this owner, and `Expr::Rec(i)` denotes it. Metadata only - // names them: `all[i][0]` is the name-hash address of member `i`'s - // canonical Lean name, whose Named entry carries the member's own metadata - // (arena, type root, ...) and must agree with the owner-derived address. - // Error loudly if the Named entry is missing — the Muts-registration pass - // in `compile_mutual` is supposed to emit one per member. - let mut member_ids: Vec> = Vec::with_capacity(members.len()); - let mut member_metas: Vec> = - Vec::with_capacity(members.len()); - for (i, member) in members.iter().enumerate() { - let primary_name_addr = all - .get(i) - .and_then(|cls| cls.first()) - .ok_or_else(|| format!("Muts block member {i} has no name in all"))?; - let member_name = resolve_name(primary_name_addr, names); - - let member_named = ixon_env.named(&member_name).ok_or_else(|| { - format!("Muts member '{member_name}' not found in named entries") - })?; - let index = u64::try_from(i).map_err(|error| error.to_string())?; - let (kind, member_addr) = match member { - IxonMutConst::Defn(_) => ("DPrj", anon_defn_proj_addr(entry_addr, index)), - IxonMutConst::Indc(_) => ("IPrj", anon_indc_proj_addr(entry_addr, index)), - IxonMutConst::Recr(_) => ("RPrj", anon_recr_proj_addr(entry_addr, index)), - }; - if member_named.addr != member_addr { - return Err(format!( - "Muts member '{member_name}' metadata address differs from owner/index {index}" - )); - } - // A carried projection record must be this member's, as in anonymous - // ingress; installing the owner's members must not shadow a corrupted - // one that a later reference would otherwise reach. - verify_proj_addr_in_env( - kind, - &member_addr, - entry_addr, - index, - None, - ixon_env, - )?; - if let IxonMutConst::Indc(ind) = member { - for (cidx, ctor_addr) in - anon_ctor_addrs(entry_addr, index, ind).iter().enumerate() - { - verify_proj_addr_in_env( - "CPrj", - ctor_addr, - entry_addr, - index, - Some(cidx as u64), - ixon_env, - )?; - } - } - member_metas.push(member_named.meta()); - member_ids.push(KId::new(member_addr, M::meta_field(member_name))); - } - let mut_ctx = member_ids.clone(); - - let mut results: Vec<(KId, KConst)> = Vec::new(); - let mut local_dependencies = LocalDefnDependencies::default(); - - for (i, member) in members.iter().enumerate() { - let self_id = member_ids[i].clone(); - let member_meta = &member_metas[i]; - - match member { - IxonMutConst::Indc(ind) => { - results.extend(ingress_muts_inductive( - ind, - &self_id, - member_meta, - ixon_env, - names, - name_to_addr, - &block_constant, - block_id.clone(), - i as u64, - mut_ctx.clone(), - intern, - stats, - )?); - }, - IxonMutConst::Recr(rec) => { - results.extend(ingress_recursor( - rec, - self_id, - member_meta, - ixon_env, - names, - name_to_addr, - &block_constant.sharing, - &block_constant.refs, - &block_constant.univs, - block_id.clone(), - intern, - stats, - mut_ctx.clone(), - )?); - }, - IxonMutConst::Defn(def) => { - results.extend(ingress_defn( - def, - self_id, - member_meta, - ixon_env, - names, - name_to_addr, - &block_constant.sharing, - &block_constant.refs, - &block_constant.univs, - block_id.clone(), - intern, - stats, - mut_ctx.clone(), - Some(&mut local_dependencies), - )?); - }, - } - } - local_dependencies.validate()?; - - // No member-order check: an inductive block is stored in its source's - // order, which shapes its recursors (as constructor order does), so any - // member order is admissible. The recursor block is checked against the - // stored order position by position. - - Ok(results) -} - -// ============================================================================ -// Lightweight LeanExpr → KExpr ingress (compile-side) -// ============================================================================ - -use ix_common::env::{ - Expr as LeanExpr, ExprData as LeanExprData, Level, LevelData, -}; - -/// Convert a Lean Level to KUniv, mapping named params to positional indices. -/// -/// An unknown universe parameter or a level metavariable is an error naming -/// it, never a panic (the texts are those of the panics it replaces). -pub fn lean_level_to_kuniv( - lvl: &Level, - param_names: &[Name], -) -> Result, String> { - Ok(match lvl.as_data() { - LevelData::Succ(l, _) => KUniv::succ(lean_level_to_kuniv(l, param_names)?), - LevelData::Max(a, b, _) => KUniv::max( - lean_level_to_kuniv(a, param_names)?, - lean_level_to_kuniv(b, param_names)?, - ), - LevelData::Imax(a, b, _) => KUniv::imax( - lean_level_to_kuniv(a, param_names)?, - lean_level_to_kuniv(b, param_names)?, - ), - LevelData::Param(name, _) => { - let idx = param_names.iter().position(|n| n == name).ok_or_else(|| { - format!( - "unknown level param `{}` not found in param_names {:?}", - name.pretty(), - param_names.iter().map(|n| n.pretty()).collect::>() - ) - })? as u64; - KUniv::param(idx, name.clone()) - }, - LevelData::Zero(_) => KUniv::zero(), - LevelData::Mvar(name, _) => { - return Err(format!( - "unexpected level metavariable `{}` in elaborated kernel term", - name.pretty() - )); - }, - }) -} - -/// Resolve a Lean Name to an Address, using real Ixon address if available. -/// -/// Checks `name_to_ixon_addr` first (real compiled address), falls back to -/// `Address::from_blake3_hash(*name.get_hash())` for constants not yet compiled. -pub fn resolve_lean_name_addr( - name: &Name, - name_to_ixon_addr: Option<&DashMap>, - aux_n2a: Option<&DashMap>, -) -> Address { - if let Some(map) = name_to_ixon_addr - && let Some(entry) = map.get(name) - { - return entry.value().clone(); - } - if let Some(map) = aux_n2a - && let Some(entry) = map.get(name) - { - return entry.value().clone(); - } - Address::from_blake3_hash(*name.get_hash()) -} - -/// Convert a LeanExpr to KExpr. -/// -/// `param_names` provides the positional mapping for universe level params. -/// `name_to_ixon_addr` maps Lean names to real Ixon addresses for already-compiled -/// constants. Falls back to name hash for constants not yet compiled. -/// 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]) -> CtxAddr { - let mut hasher = blake3::Hasher::new(); - hasher.update(&(param_names.len() as u64).to_le_bytes()); - for n in param_names { - hasher.update(n.get_hash().as_bytes()); - } - hasher.finalize() -} - -pub fn lean_expr_to_zexpr( - expr: &LeanExpr, - param_names: &[Name], - intern: &mut InternTable, - name_to_ixon_addr: Option<&DashMap>, - aux_n2a: Option<&DashMap>, -) -> Result, String> { - // Uncached path — only for callers without KEnv access. Top-level - // expressions start with an empty binder stack. - let mut binder_names: Vec = Vec::new(); - lean_expr_to_zexpr_cached( - expr, - param_names, - &mut binder_names, - intern, - name_to_ixon_addr, - aux_n2a, - None, - None, - ) -} - -/// Cached variant that takes a full `KEnv` reference instead of just `InternTable`. -/// Uses the KEnv's `ingress_cache` to avoid re-converting shared LeanExpr subtrees. -pub fn lean_expr_to_zexpr_with_kenv( - expr: &LeanExpr, - param_names: &[Name], - kenv: &mut KEnv, - n2a: Option<&DashMap>, - aux_n2a: Option<&DashMap>, -) -> Result, String> { - let pn_h = param_names_hash(param_names); - let mut binder_names: Vec = Vec::new(); - lean_expr_to_zexpr_cached( - expr, - param_names, - &mut binder_names, - &mut kenv.intern, - n2a, - aux_n2a, - Some(&mut kenv.ingress_cache), - Some(&pn_h), - ) -} - -enum LeanExprFrame<'a> { - Visit(&'a LeanExpr), - Finish { - node: &'a LeanExprData, - mdata: Vec, - source_contract: Option, - key: Option<(CtxAddr, CtxAddr)>, - projection_id: Option>, - }, - PushBinder(&'a Name), - PopBinder, -} - -/// Cached variant: uses `ingress_cache` (if provided) to avoid re-converting -/// shared LeanExpr subtrees. The cache is keyed by `(expr_hash, pn_hash)` to -/// account for different level param bindings producing different KExprs. -/// -/// `binder_names` is the stack of enclosing binder names (outermost first), -/// pushed/popped around each Lam/All/Let body. It's used to -/// populate `ExprData::Var`'s `name` metadata by de Bruijn lookup — a -/// cosmetic field for pretty-printing that doesn't affect type-checking. -/// Top-level callers pass an empty `Vec`. Mirrors the `binder_names` stack -/// used by the iterative Ixon-side `ingress_expr`. -/// -/// Note: the cache key does not include `binder_names`, so a cache hit -/// returns a `KExpr` whose Var names reflect the FIRST context the subtree -/// was traversed under. The kernel itself never consults Var names (they're -/// erased in Anon mode, ignored in Meta mode by type checking), and egress -/// drops them on the way back to Lean's (nameless) Bvar, so this staleness -/// is benign. Matches the behavior of `ixon_ingress`'s iterative cache. -/// -/// # Errors -/// -/// The whole `lean_expr_to_zexpr*` family returns an error, never a panic or -/// a substituted value, on an unknown universe parameter, a level -/// metavariable, a free variable, an expression metavariable, or a bound -/// variable or projection index that does not fit a `u64`. Only successful -/// conversions enter the cache. The caller's binder stack is restored on -/// both success and error; completed child cache entries survive an error. -#[allow(clippy::too_many_arguments)] -pub fn lean_expr_to_zexpr_cached( - expr: &LeanExpr, - param_names: &[Name], - binder_names: &mut Vec, - intern: &mut InternTable, - n2a: Option<&DashMap>, - aux_n2a: Option<&DashMap>, - mut cache: Option<&mut FxHashMap<(CtxAddr, CtxAddr), KExpr>>, - pn_hash: Option<&CtxAddr>, -) -> Result, String> { - let entry_depth = binder_names.len(); - let result = (|| { - let mut stack = vec![LeanExprFrame::Visit(expr)]; - let mut values: Vec> = Vec::new(); - while let Some(frame) = stack.pop() { - match frame { - LeanExprFrame::Visit(expr) => { - let key = pn_hash.map(|h| (*expr.get_hash(), *h)); - if let (Some(cache), Some(key)) = (cache.as_ref(), key) - && let Some(hit) = cache.get(&key) - { - values.push(hit.clone()); - continue; - } - // Preserve the original cache key and outer-to-inner Mdata order. - // Peeled intermediate wrappers are not separate cache entries. - let mut mdata = Vec::new(); - let mut cur = expr; - while let LeanExprData::Mdata(kv, inner, _) = cur.as_data() { - mdata.push(kv.clone()); - cur = inner; - } - let node = cur.as_data(); - let source_contract = - source::source_layers(cur, &mdata).map_err(|e| e.to_string())?; - // Projection address resolution precedes its operand conversion; - // field-index validation remains after that conversion. - let projection_id = match node { - LeanExprData::Proj(name, ..) => Some(KId::new( - resolve_lean_name_addr(name, n2a, aux_n2a), - name.clone(), - )), - _ => None, - }; - stack.push(LeanExprFrame::Finish { - node, - mdata, - source_contract, - key, - projection_id, - }); - match node { - LeanExprData::App(f, a, _) => { - stack.push(LeanExprFrame::Visit(a)); - stack.push(LeanExprFrame::Visit(f)); - }, - LeanExprData::Lam(name, dom, body, _, _) - | LeanExprData::ForallE(name, dom, body, _, _) => { - stack.push(LeanExprFrame::PopBinder); - stack.push(LeanExprFrame::Visit(body)); - stack.push(LeanExprFrame::PushBinder(name)); - stack.push(LeanExprFrame::Visit(dom)); - }, - LeanExprData::LetE(name, ty, val, body, _, _) => { - stack.push(LeanExprFrame::PopBinder); - stack.push(LeanExprFrame::Visit(body)); - stack.push(LeanExprFrame::PushBinder(name)); - stack.push(LeanExprFrame::Visit(val)); - stack.push(LeanExprFrame::Visit(ty)); - }, - LeanExprData::Proj(_, _, value, _) => { - stack.push(LeanExprFrame::Visit(value)); - }, - _ => {}, - } - }, - LeanExprFrame::PushBinder(name) => binder_names.push(name.clone()), - LeanExprFrame::PopBinder => { - binder_names.pop(); - }, - LeanExprFrame::Finish { - node, - mdata, - source_contract, - key, - projection_id, - } => { - let e = match node { - LeanExprData::Bvar(idx, _) => { - let idx_u64 = idx.to_u64().ok_or_else(|| { - format!("bound variable index {idx} exceeds u64") - })?; - let idx_usize = usize::try_from(idx_u64).unwrap_or(usize::MAX); - let name = binder_names - .len() - .checked_sub(1) - .and_then(|top| top.checked_sub(idx_usize)) - .and_then(|i| binder_names.get(i)) - .cloned() - .unwrap_or_else(Name::anon); - KExpr::var_mdata(idx_u64, name, mdata) - }, - LeanExprData::Sort(lvl, _) => { - KExpr::sort_mdata(lean_level_to_kuniv(lvl, param_names)?, mdata) - }, - LeanExprData::Const(name, us, _) => { - let id = KId::new( - resolve_lean_name_addr(name, n2a, aux_n2a), - name.clone(), - ); - let us = us - .iter() - .map(|u| lean_level_to_kuniv(u, param_names)) - .collect::, _>>()?; - KExpr::cnst_mdata(id, us, mdata) - }, - LeanExprData::App(..) => { - let a = values.pop().unwrap(); - let f = values.pop().unwrap(); - KExpr::app_mdata(f, a, mdata) - }, - LeanExprData::Lam(name, _, _, bi, _) => { - let body = values.pop().unwrap(); - let dom = values.pop().unwrap(); - KExpr::lam_contract_mdata( - name.clone(), - bi.clone(), - source_contract - .map_or_else(BinderContract::default, |c| c.binder), - dom, - body, - mdata, - ) - }, - LeanExprData::ForallE(name, _, _, bi, _) => { - let body = values.pop().unwrap(); - let dom = values.pop().unwrap(); - KExpr::all_contract_mdata( - name.clone(), - bi.clone(), - source_contract - .map_or_else(BinderContract::default, |c| c.binder), - source_contract - .map_or_else(ValueContract::shared, |c| c.result), - dom, - body, - mdata, - ) - }, - LeanExprData::LetE(name, _, _, _, nd, _) => { - let body = values.pop().unwrap(); - let val = values.pop().unwrap(); - let ty = values.pop().unwrap(); - let contract = source_contract.map_or_else( - || LetContract::plain(*nd), - |c| LetContract { - non_dep: *nd, - kind: c.let_kind, - binder: c.binder, - }, - ); - KExpr::let_contract_mdata( - name.clone(), - contract, - ty, - val, - body, - mdata, - ) - }, - LeanExprData::Proj(name, idx, _, _) => { - let value = values.pop().unwrap(); - let idx_u64 = idx.to_u64().ok_or_else(|| { - format!( - "projection index {idx} of {} exceeds u64", - name.pretty() - ) - })?; - KExpr::prj_mdata(projection_id.unwrap(), idx_u64, value, mdata) - }, - LeanExprData::Lit(lit, _) => match lit { - ix_common::env::Literal::NatVal(n) => KExpr::nat_mdata( - n.clone(), - Address::hash(&n.to_le_bytes()), - mdata, - ), - ix_common::env::Literal::StrVal(s) => { - KExpr::str_mdata(s.clone(), Address::hash(s.as_bytes()), mdata) - }, - }, - LeanExprData::Fvar(name, _) => { - return Err(format!( - "unexpected FVar({}) in elaborated kernel term during ingress", - name.pretty() - )); - }, - LeanExprData::Mvar(name, _) => { - return Err(format!( - "unexpected MVar({}) in elaborated kernel term during ingress", - name.pretty() - )); - }, - LeanExprData::Mdata(..) => { - unreachable!("Mdata was peeled on entry") - }, - }; - // Construct and intern in the old postorder, including on intern - // hits, so allocation/first-occurrence metadata behavior is stable. - let e = intern.intern_expr(e); - if let (Some(cache), Some(key)) = (cache.as_deref_mut(), key) { - cache.insert(key, e.clone()); - } - values.push(e); - }, - } - } - Ok(values.pop().unwrap()) - })(); - // A failed body must not leak a pushed binder into its caller. Successful - // child cache/interner writes are retained, exactly as on the old path. - binder_names.truncate(entry_depth); - result -} - -/// Name → Address for KId construction from Lean Names. -pub fn lean_name_to_addr(name: &Name) -> Address { - Address::from_blake3_hash(*name.get_hash()) -} - -/// Incrementally ingress a set of just-compiled constants into a KEnv. -/// -/// Called after each block compiles in the topological compilation loop. -/// `names` are the Lean names of constants in the block. For each name, -/// we look up its Ixon address and constant, convert to KConst, and insert. -/// Build the address → name + name → address lookup tables for -/// `ingress_compiled_names`. Call once at compile start, then pass to each -/// incremental ingress call. -/// -/// Two maps: -/// - `name_map`: `ixon_env.names` inverted — address of a `Lean.Name` → -/// the name itself. Used in Meta mode to recover names from arena -/// metadata. -/// - `addr_map`: `ixon_env.named` — each registered Lean name → the -/// content address at which its compiled `Constant` is stored -/// (projection address for Muts members, or direct block address for -/// singletons). This is the kernel-addressing map: `KId`s for sibling -/// references inside Muts blocks MUST use these addresses (the raw -/// name-hash address is insufficient because an alpha-collapsed block -/// is stored at its content address, not any individual member's name -/// hash). -pub fn build_ingress_lookups( - ixon_env: &IxonEnv, -) -> (FxHashMap, FxHashMap) { - let mut name_map: FxHashMap = FxHashMap::default(); - for entry in ixon_env.names.iter() { - name_map.insert(entry.key().clone(), entry.value().clone()); - } - let mut addr_map: FxHashMap = FxHashMap::default(); - for entry in ixon_env.named.iter() { - addr_map.insert(entry.key().clone(), entry.value().addr.clone()); - } - (name_map, addr_map) -} - -pub fn ingress_compiled_names( - names: &[Name], - ixon_env: &IxonEnv, - zenv: &mut KEnv, - intern: &mut InternTable, - name_map: &FxHashMap, - addr_map: &FxHashMap, -) { - for name in names { - let named = match ixon_env.named.get(name) { - Some(entry) => entry.value().clone(), - None => continue, - }; - let constant = match ixon_env.get_const(&named.addr) { - Some(c) => c, - None => continue, - }; - let mut stats = ConvertStats::default(); - - // Check if this is a Muts entry (mutual block) — handle differently - let named_meta = named.meta(); - if matches!(&named_meta.info, ConstantMetaInfo::Muts { .. }) { - if let ConstantMetaInfo::Muts { all, .. } = &named_meta.info - && let Ok(entries) = ingress_muts_block( - name, - &named.addr, - all, - ixon_env.into(), - name_map, - addr_map, - intern, - &mut stats, - ) - { - let block_id = entries.first().and_then(|(_, zc)| match zc { - KConst::Defn { block, .. } - | KConst::Recr { block, .. } - | KConst::Indc { block, .. } => Some(block.clone()), - _ => None, - }); - let member_ids: Vec> = - entries.iter().map(|(id, _)| id.clone()).collect(); - if let Some(bid) = block_id { - zenv.blocks.insert(bid, member_ids); - } - for (id, zc) in entries { - zenv.insert(id, zc); - } - } - continue; - } - - // Standalone constant (or member of a mutual block handled via Muts) - // Skip projection wrappers — they're handled by the Muts path - match &constant.info { - IxonCI::IPrj(_) | IxonCI::CPrj(_) | IxonCI::RPrj(_) | IxonCI::DPrj(_) => { - continue; - }, - _ => {}, - } - - if let Ok(entries) = ingress_standalone( - name, - &named.addr, - &constant, - &named_meta, - ixon_env.into(), - name_map, - addr_map, - intern, - &mut stats, - ) { - for (id, zc) in entries { - zenv.insert(id, zc); - } - } - } -} - -// ============================================================================ -// Direct Lean env → kernel env (bypasses Ixon) -// ============================================================================ -// -// This path is used by the `kernel-lean-roundtrip` diagnostic -// test (`src/ffi/kernel.rs::rs_kernel_roundtrip_no_compile`) to isolate -// ingress bugs from compile/Ixon bugs. It produces a `KEnv` directly -// from the decoded Lean `Env`, using: -// -// * `lean_name_to_addr` for `KId.addr`s — the same name-hash scheme that -// `resolve_lean_name_addr` falls back to when both maps are `None`, so -// `Const`-reference addresses inside expressions match constant keys. -// * `lean_expr_to_zexpr_with_kenv` for expression ingress. -// * `kenv.intern` is populated in-place (no separate `InternTable` to -// swap in the way `ixon_ingress` requires). - -/// Extract the `all` (mutual siblings) list from a Lean `ConstantInfo`. -/// Returns `None` for variants without a mutual block (Axio, Quot, Ctor, Rec). -/// Ctors/Recs have their own `induct`/`all` but the block identity comes -/// from the inductive, which is what's on the map anyway. -#[cfg(not(target_arch = "riscv64"))] -fn lean_constant_all(ci: &LeanCI) -> Option<&Vec> { - match ci { - LeanCI::DefnInfo(v) => Some(&v.all), - LeanCI::ThmInfo(v) => Some(&v.all), - LeanCI::OpaqueInfo(v) => Some(&v.all), - LeanCI::InductInfo(v) => Some(&v.all), - LeanCI::RecInfo(v) => Some(&v.all), - LeanCI::AxiomInfo(_) | LeanCI::QuotInfo(_) | LeanCI::CtorInfo(_) => None, - } -} - -/// Look up position of `name` in its mutual `all` list, returning 0 for -/// non-mutuals or constants not found in their own `all`. -#[cfg(not(target_arch = "riscv64"))] -fn lean_member_idx(name: &Name, all: Option<&Vec>) -> u64 { - all.and_then(|a| a.iter().position(|n| n == name)).map_or(0, |i| i as u64) -} - -/// Build a `Name → LEON content-hash` map for every constant in the Lean env. -/// -/// The LEON hash is `ConstantInfo::get_hash()` in `src/ix/env.rs` — a Blake3 -/// digest over the serialized original `ConstantInfo` -/// (name, level params, type expression, variant-specific fields). -/// Two constants with the same Lean name but different content get distinct -/// addresses, so a rogue environment can't shadow a primitive just by naming -/// its own declaration `Nat`. -/// -/// The resulting map is the addressing authority for `lean_ingress`: every -/// `KId.addr` in `orig_kenv` and every `Const`-reference address inside -/// `orig_kenv` expressions is drawn from it. Names absent from the env -/// (dangling refs, partial envs) fall through to `lean_name_to_addr` as a -/// best-effort — those cases produce mismatched addresses and will surface -/// as `UnknownConst` in the type checker rather than silently succeeding. -#[cfg(not(target_arch = "riscv64"))] -pub fn build_leon_addr_map(lean_env: &LeanEnv) -> DashMap { - // Build in parallel. Each shard's write lock is contended only when - // distinct names happen to hash into the same shard — with 64 default - // shards and ~199k names, contention is low. Pre-sizing `with_capacity` - // keeps the shards from growing during construction. - // - // The map type stays `DashMap` (rather than `FxHashMap`) because - // downstream signatures (`lean_expr_to_zexpr_cached`, - // `resolve_lean_name_addr`) take their `n2a` and `aux_n2a` name maps as - // `DashMap`s. - let entries: Vec<_> = lean_env.iter().collect(); - let map = DashMap::with_capacity(lean_env.len()); - entries.par_iter().for_each(|(name, ci)| { - map.insert((*name).clone(), Address::from_blake3_hash(ci.get_hash())); - }); - map -} - -/// Resolve a Lean name to its LEON content-hash address, falling back to -/// the name-hash when the name isn't present in `n2a`. -/// -/// The fallback exists for robustness against dangling references — a -/// well-formed Lean env should never trigger it. Callers that need -/// strict resolution (e.g. "does this name exist?") should check -/// `n2a.contains_key` directly. -#[cfg(not(target_arch = "riscv64"))] -fn leon_addr_of(name: &Name, n2a: &DashMap) -> Address { - n2a.get(name).map_or_else(|| lean_name_to_addr(name), |e| e.value().clone()) -} - -/// Build the `block` KId for a constant's mutual block. For singletons -/// (no `all` or `all` length 1), the block id is the constant's own KId. -/// For mutuals, it's the representative (first name in `all`). -#[cfg(not(target_arch = "riscv64"))] -fn lean_block_id( - self_name: &Name, - all: Option<&Vec>, - n2a: &DashMap, -) -> KId { - let rep = all.and_then(|a| a.first()).unwrap_or(self_name); - KId::new(leon_addr_of(rep, n2a), rep.clone()) -} - -/// Build the `lean_all` KId list in Meta mode. -#[cfg(not(target_arch = "riscv64"))] -fn lean_all_ids(all: &[Name], n2a: &DashMap) -> Vec> { - all.iter().map(|n| KId::new(leon_addr_of(n, n2a), n.clone())).collect() -} - -/// Convert one Lean `ConstantInfo` to a `KConst`. Expressions go through -/// `lean_expr_to_zexpr_with_kenv` with the `n2a` map so inner `Const` -/// references resolve to LEON addresses (same scheme used for the KId -/// addresses in this constant's own fields). -/// -/// Every error names the constant (`"{self_name}: ..."`): an expression the -/// expression ingress refuses, or a `Nat` count or index field that does -/// not fit a `u64` (such a field used to become 0). -#[cfg(not(target_arch = "riscv64"))] -fn lean_const_to_kconst( - self_name: &Name, - ci: &LeanCI, - kenv: &mut KEnv, - n2a: &DashMap, -) -> Result, String> { - // Helper: shorthand for expression ingress. `n2a` carries the env-wide - // LEON addressing so `Const` refs inside expressions resolve to the same - // addresses we're using for KId keys — any KId we construct here and any - // Const-ref we ingress agree on where they point. - let mut expr_to_k = - |e: &ix_common::env::Expr, pn: &[Name]| -> Result, String> { - lean_expr_to_zexpr_with_kenv(e, pn, kenv, Some(n2a), None) - .map_err(|e| format!("{}: {e}", self_name.pretty())) - }; - // Helper: a `Nat` count or index field as a `u64`, refused when it does - // not fit. - let nat_field = |n: &Nat, field: &str| -> Result { - n.to_u64() - .ok_or_else(|| format!("{}: {field} {n} exceeds u64", self_name.pretty())) - }; - - Ok(match ci { - LeanCI::AxiomInfo(v) => { - let pn = &v.cnst.level_params; - KConst::Axio { - name: self_name.clone(), - level_params: pn.clone(), - is_unsafe: v.is_unsafe, - lvls: pn.len() as u64, - ty: expr_to_k(&v.cnst.typ, pn)?, - } - }, - LeanCI::DefnInfo(v) => { - let pn = &v.cnst.level_params; - let all = Some(&v.all); - KConst::Defn { - name: self_name.clone(), - level_params: pn.clone(), - kind: DefKind::Definition, - safety: v.safety, - hints: v.hints, - lvls: pn.len() as u64, - ty: expr_to_k(&v.cnst.typ, pn)?, - val: expr_to_k(&v.value, pn)?, - lean_all: lean_all_ids(&v.all, n2a), - block: lean_block_id(self_name, all, n2a), - } - }, - LeanCI::ThmInfo(v) => { - let pn = &v.cnst.level_params; - let all = Some(&v.all); - KConst::Defn { - name: self_name.clone(), - level_params: pn.clone(), - kind: DefKind::Theorem, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Opaque, - lvls: pn.len() as u64, - ty: expr_to_k(&v.cnst.typ, pn)?, - val: expr_to_k(&v.value, pn)?, - lean_all: lean_all_ids(&v.all, n2a), - block: lean_block_id(self_name, all, n2a), - } - }, - LeanCI::OpaqueInfo(v) => { - let pn = &v.cnst.level_params; - let all = Some(&v.all); - KConst::Defn { - name: self_name.clone(), - level_params: pn.clone(), - kind: DefKind::Opaque, - safety: if v.is_unsafe { - DefinitionSafety::Unsafe - } else { - DefinitionSafety::Safe - }, - hints: ReducibilityHints::Opaque, - lvls: pn.len() as u64, - ty: expr_to_k(&v.cnst.typ, pn)?, - val: expr_to_k(&v.value, pn)?, - lean_all: lean_all_ids(&v.all, n2a), - block: lean_block_id(self_name, all, n2a), - } - }, - LeanCI::QuotInfo(v) => { - let pn = &v.cnst.level_params; - KConst::Quot { - name: self_name.clone(), - level_params: pn.clone(), - kind: v.kind, - lvls: pn.len() as u64, - ty: expr_to_k(&v.cnst.typ, pn)?, - } - }, - LeanCI::InductInfo(v) => { - let pn = &v.cnst.level_params; - let all = Some(&v.all); - let ctors = v - .ctors - .iter() - .map(|n| KId::new(leon_addr_of(n, n2a), n.clone())) - .collect(); - KConst::Indc { - name: self_name.clone(), - level_params: pn.clone(), - lvls: pn.len() as u64, - params: nat_field(&v.num_params, "num_params")?, - indices: nat_field(&v.num_indices, "num_indices")?, - is_unsafe: v.is_unsafe, - block: lean_block_id(self_name, all, n2a), - member_idx: lean_member_idx(self_name, all), - ty: expr_to_k(&v.cnst.typ, pn)?, - ctors, - lean_all: lean_all_ids(&v.all, n2a), - } - }, - LeanCI::CtorInfo(v) => { - let pn = &v.cnst.level_params; - KConst::Ctor { - name: self_name.clone(), - level_params: pn.clone(), - is_unsafe: v.is_unsafe, - lvls: pn.len() as u64, - induct: KId::new(leon_addr_of(&v.induct, n2a), v.induct.clone()), - cidx: nat_field(&v.cidx, "cidx")?, - params: nat_field(&v.num_params, "num_params")?, - fields: nat_field(&v.num_fields, "num_fields")?, - ty: expr_to_k(&v.cnst.typ, pn)?, - } - }, - LeanCI::RecInfo(v) => { - let pn = &v.cnst.level_params; - let all = Some(&v.all); - let rules = v - .rules - .iter() - .map(|r| -> Result, String> { - let fields = r.n_fields.to_u64().ok_or_else(|| { - format!( - "{}: n_fields {} of the rule for {} exceeds u64", - self_name.pretty(), - r.n_fields, - r.ctor.pretty() - ) - })?; - Ok(RecRule { - ctor: r.ctor.clone(), - fields, - rhs: expr_to_k(&r.rhs, pn)?, - }) - }) - .collect::>()?; - KConst::Recr { - name: self_name.clone(), - level_params: pn.clone(), - k: v.k, - is_unsafe: v.is_unsafe, - lvls: pn.len() as u64, - params: nat_field(&v.num_params, "num_params")?, - indices: nat_field(&v.num_indices, "num_indices")?, - motives: nat_field(&v.num_motives, "num_motives")?, - minors: nat_field(&v.num_minors, "num_minors")?, - block: lean_block_id(self_name, all, n2a), - member_idx: lean_member_idx(self_name, all), - ty: expr_to_k(&v.cnst.typ, pn)?, - rules, - lean_all: lean_all_ids(&v.all, n2a), - } - }, - }) -} - -/// Lean emits original recursors in `.all` declaration order, followed by -/// nested auxiliaries named `.rec_N` in numeric source-walk order. -/// This is loader layout metadata, not evidence that a recursor is valid: -/// the kernel still checks every peer's full header, type, and rules. -#[cfg(not(target_arch = "riscv64"))] -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -enum LeanRecursorOrder { - Original(usize), - Nested(usize), - Unrecognized, -} - -#[cfg(not(target_arch = "riscv64"))] -fn lean_recursor_order( - name: &Name, - block_name: &Name, - original_positions: &FxHashMap, -) -> LeanRecursorOrder { - use ix_common::env::NameData; - - let NameData::Str(parent, suffix, _) = name.as_data() else { - return LeanRecursorOrder::Unrecognized; - }; - if suffix == "rec" { - return original_positions - .get(parent) - .map_or(LeanRecursorOrder::Unrecognized, |&i| { - LeanRecursorOrder::Original(i) - }); - } - if parent == block_name - && let Some(index) = suffix.strip_prefix("rec_") - && !index.starts_with('0') - && index.bytes().all(|b| b.is_ascii_digit()) - && let Ok(index) = index.parse::() - { - return LeanRecursorOrder::Nested(index); - } - LeanRecursorOrder::Unrecognized -} - -/// Direct ingress: build a `KEnv` from a Lean `Env` without going -/// through Ixon compilation. Used by the `kernel-lean-roundtrip` -/// diagnostic test. -/// -/// # Addressing -/// -/// All `KId.addr`s are derived via `ConstantInfo::get_hash()` — the LEON -/// content hash, Blake3 over the serialized original `ConstantInfo` -/// (name + level params + type + variant-specific fields). `Const` -/// references inside expressions resolve against the same map so -/// constant keys and reference targets line up automatically. -/// -/// LEON addressing has two properties that name-hash addressing lacked: -/// -/// - **Content-distinguishing**: two constants with the same name but -/// different content hash to different addresses, so a rogue env -/// can't silently shadow a primitive by naming its own declaration -/// `Nat`. -/// - **Compatible with `PrimOrigAddrs`**: the hardcoded original-addr -/// table in `src/ix/kernel/primitive.rs` holds LEON hashes, so -/// address-keyed primitive lookup against `orig_kenv` succeeds -/// without a synthetic `@` fallback. -/// -/// # Block entries -/// -/// `kenv.blocks` is populated for every constant: each `KId` is pushed -/// under its block's representative (first name in `all`, or the -/// constant itself for singletons). Constructors follow their parent -/// inductive's block. -/// -/// # Errors -/// -/// A constant whose conversion is refused (see -/// [`lean_expr_to_zexpr_cached`], and a `Nat` count or index field that does -/// not fit a `u64`) makes the whole ingress fail with an error naming that -/// constant. -/// -/// **Meta-only**: the existing `lean_expr_to_zexpr_*` family is Meta-mode -/// only, so this helper is Meta-mode only by extension. Generalizing to -/// `Anon` would require generalizing `lean_expr_to_zexpr_cached` too. -#[cfg(not(target_arch = "riscv64"))] -pub fn lean_ingress(lean_env: &LeanEnv) -> Result, String> { - use std::time::Instant; - let quiet = crate::env_var("IX_VERBOSE").is_err(); - let mut kenv = KEnv::::new_with_recursor_aux_order( - super::env::RecursorAuxOrder::Source, - ); - - // Build the env-wide name → LEON-addr map once. Threaded through every - // KId construction below so all addresses in orig_kenv — whether - // stored as the KEnv key, or referenced from within a KExpr via - // `Const`, or captured in structural fields like `block`, `ctors`, - // `induct`, `lean_all` — come from the same authoritative source. - let t = Instant::now(); - let n2a = build_leon_addr_map(lean_env); - if !quiet { - log::info!( - "[lean_ingress] build_leon_addr_map: {:.2}s ({} names)", - t.elapsed().as_secs_f32(), - n2a.len() - ); - } - - // Pass 1: ingress every constant sequentially into this worker-local env. - let t = Instant::now(); - for (name, ci) in lean_env.iter() { - let kid = KId::new(leon_addr_of(name, &n2a), name.clone()); - // The error already names the constant. - let kc = lean_const_to_kconst(name, &ci, &mut kenv, &n2a)?; - kenv.insert(kid, kc); - } - if !quiet { - log::info!( - "[lean_ingress] pass 1 (serial ingress): {:.2}s", - t.elapsed().as_secs_f32() - ); - } - - // Pass 2: populate `kenv.blocks`. - // - // Each inductive block's entry under `blocks[rep_kid]` must hold - // *every* KId that the kernel's block-traversal paths need: - // - // - The inductives themselves (discovered by - // `discover_block_inductives` during `check_inductive`'s A1–A4 - // pass and during `compute_is_rec`). - // - Their constructors (needed for ctor lookups keyed on the block). - // - Their recursors (needed by `find_peer_recursors` during - // `generate_block_recursors`'s rule generation — without recs in - // the block, rule RHS construction returns None and the stored - // rules can't be verified). - // - // **Order matters for inductives and recursors.** `discover_block_inductives` - // filters the block's member list down to `KConst::Indc` entries - // and the resulting order drives `build_flat_block` → `build_rec_type` - // → motive-binder emission in `generate_block_recursors`. That - // order must match whatever order the *stored* recursor was - // generated against. - // - // For `orig_kenv` (what this function builds), the stored recursor - // is Lean's own — generated against the **declaration order** given - // by each constant's `all` list (the source order the user wrote - // the mutual block in). If `discover_block_inductives` returns - // members in any other order, the generated motive prefix permutes - // relative to Lean's, yielding spurious `check_recursor: type - // mismatch` on every mutual-block recursor (we saw this on - // `Lean.Xml.Content.rec`, `Lean.Compiler.LCNF.Code.rec`, every - // `Grind.Arith.*.*Cnstr*.rec`, etc.). - // - // The compiler stores each block in this same declaration order. - // Iterating `lean_env` directly to push each constant's `self_kid` gave - // random (FxHashMap iteration) order; we now seed each block with - // its `all` list the first time any member is observed, then - // append ctors and source-ordered recursors in a second pass. The - // inductive and recursor subsequences must align positionally with the - // flat block (original `.all` members, then source-walk nested auxes). - // - // `ixon_ingress` builds an analogous list from each block's stored - // `Muts` entry, in stored member order. The two kenvs differ in how - // recursor generation opens a nested occurrence of an external - // inductive (`RecursorAuxOrder`): this one opens the head alone, an - // Ixon-ingressed one the head's whole stored block. - // - // For singleton inductives, the block is keyed at `self_kid`; for - // multi-member mutuals, at the representative (first name in `all`). - let block_rep = |name: &Name, ci: &LeanCI| -> KId { - let all = lean_constant_all(ci); - let rep = - all.and_then(|a| a.first()).cloned().unwrap_or_else(|| name.clone()); - KId::new(leon_addr_of(&rep, &n2a), rep) - }; - - // Phase A: seed each block's initial member list from the constant's - // `all` list (source order), exactly once per block. Constants - // without `all` (axioms, quotients, ctors) seed a singleton block - // under their own KId. - let t = Instant::now(); - let mut seeded: FxHashSet> = FxHashSet::default(); - for (name, ci) in lean_env.iter() { - let block_id = block_rep(name, &ci); - if !seeded.insert(block_id.clone()) { - continue; - } - let all = - lean_constant_all(&ci).cloned().unwrap_or_else(|| vec![name.clone()]); - let members: Vec> = - all.iter().map(|n| KId::new(leon_addr_of(n, &n2a), n.clone())).collect(); - kenv.blocks.insert(block_id, members); - } - if !quiet { - log::info!( - "[lean_ingress] phase A (block seed): {:.2}s", - t.elapsed().as_secs_f32() - ); - } - - // Phase B: append constructors and collect recursors separately. `.all` - // lists inductives even for RecInfo; environment iteration is not the - // recursor order required by positional peer matching in the kernel. - let t = Instant::now(); - let mut recursors: FxHashMap, Vec>> = - FxHashMap::default(); - for (name, ci) in lean_env.iter() { - match &*ci { - LeanCI::InductInfo(v) => { - let block_id = block_rep(name, &ci); - for ctor_name in &v.ctors { - let ctor_kid: KId = - KId::new(leon_addr_of(ctor_name, &n2a), ctor_name.clone()); - kenv.blocks.entry(block_id.clone()).or_default().push(ctor_kid); - } - }, - LeanCI::RecInfo(_) => { - let block_id = block_rep(name, &ci); - let self_kid = KId::new(leon_addr_of(name, &n2a), name.clone()); - recursors.entry(block_id).or_default().push(self_kid); - }, - // Inductives and Defns/Thms/Opaques are already in the Phase-A - // seed via their `all` list; axioms, quotients, and ctors are - // placed as singletons (the latter also get appended above). - _ => {}, - } - } - for (block_id, mut peers) in recursors { - let members = kenv.blocks.entry(block_id.clone()).or_default(); - let original_positions: FxHashMap = members - .iter() - .filter(|id| matches!(kenv.consts.get(id), Some(KConst::Indc { .. }))) - .enumerate() - .map(|(i, id)| (id.name.clone(), i)) - .collect(); - peers.sort_by_cached_key(|id| { - lean_recursor_order(&id.name, &block_id.name, &original_positions) - }); - // Keep unrecognized names too. Dropping malformed/extra peers here - // could hide a recursor-count or type mismatch from kernel validation. - members.extend(peers); - } - if !quiet { - log::info!( - "[lean_ingress] phase B (ctor/rec append): {:.2}s", - t.elapsed().as_secs_f32() - ); - } - - // Pre-cache primitives against the LEON-addressed scheme so - // `TypeChecker::new(orig_kenv)` and any caller of `kenv.prims()` - // resolve primitives through `PrimAddrs::new_orig` (matching KIds in - // this env) instead of the canonical table (which would always miss - // here and produce synthetic `@` KIds). - // - // Returns `Err` only if `prims()` has already been called on this - // KEnv — fresh `KEnv::new()` above guarantees that hasn't happened, - // so we ignore the Result. - let _ = kenv.set_prims(crate::primitive::Primitives::from_env_orig(&kenv)); - - Ok(kenv) -} - -// ============================================================================ -// Top-level entry point -// ============================================================================ - -enum IngressWorkItem { - Standalone(Name), - Muts(Name), -} - -#[derive(Clone, Debug, Default)] -pub struct IxonIngressLookups { - names: FxHashMap, - name_to_addr: FxHashMap, - addr_to_name: FxHashMap, - names_by_addr: FxHashMap>, - muts_by_addr: FxHashMap>)>>, -} - -impl IxonIngressLookups { - pub fn name_for_addr(&self, addr: &Address) -> Option<&Name> { - self.addr_to_name.get(addr) - } - - fn names_for_addr(&self, addr: &Address) -> Option<&[Name]> { - self.names_by_addr.get(addr).map(Vec::as_slice) - } -} - -pub fn build_ixon_ingress_lookups(ixon_env: &IxonEnv) -> IxonIngressLookups { - let mut lookups = IxonIngressLookups::default(); - for entry in ixon_env.names.iter() { - lookups.names.insert(entry.key().clone(), entry.value().clone()); - } - for entry in ixon_env.named.iter() { - let name = entry.key().clone(); - let named = entry.value(); - lookups.name_to_addr.insert(name.clone(), named.addr.clone()); - lookups - .names_by_addr - .entry(named.addr.clone()) - .or_default() - .push(name.clone()); - lookups - .addr_to_name - .entry(named.addr.clone()) - .or_insert_with(|| name.clone()); - if let ConstantMetaInfo::Muts { all, .. } = &named.meta().info { - lookups - .muts_by_addr - .entry(named.addr.clone()) - .or_default() - .push((name, all.clone())); - } - } - lookups -} - -fn projection_block(info: &IxonCI) -> Option<&Address> { - match info { - IxonCI::IPrj(p) => Some(&p.block), - IxonCI::CPrj(p) => Some(&p.block), - IxonCI::RPrj(p) => Some(&p.block), - IxonCI::DPrj(p) => Some(&p.block), - _ => None, - } -} - -enum IngressNeed { - Addr(Address), - ProjectionAliases(Address), -} - -fn insert_addr_aliases( - kenv: &mut KEnv, - lookups: &IxonIngressLookups, - addr: &Address, -) { - let Some(names) = lookups.names_for_addr(addr) else { - return; - }; - let Some(template) = kenv - .consts - .iter() - .find_map(|(id, c)| if &id.addr == addr { Some(c.clone()) } else { None }) - else { - return; - }; - for name in names { - let id = KId::new(addr.clone(), M::meta_field(name.clone())); - if !kenv.contains_key(&id) { - kenv.insert(id, template.clone()); - } - } -} - -pub fn ingress_const_into_kenv( - kenv: &mut KEnv, - ixon_env: &IxonEnv, - name: &Name, -) -> Result, String> { - let lookups = build_ixon_ingress_lookups(ixon_env); - ingress_const_into_kenv_with_lookups(kenv, ixon_env, &lookups, name) -} - -pub fn ingress_const_into_kenv_with_lookups( - kenv: &mut KEnv, - ixon_env: &IxonEnv, - lookups: &IxonIngressLookups, - name: &Name, -) -> Result, String> { - ingress_const_into_kenv_with_lookups_impl( - kenv, - ixon_env.into(), - lookups, - name, - true, - ) -} - -pub fn ingress_const_shallow_into_kenv_with_lookups( - kenv: &mut KEnv, - ixon_env: &IxonEnv, - lookups: &IxonIngressLookups, - name: &Name, -) -> Result, String> { - ingress_const_into_kenv_with_lookups_impl( - kenv, - ixon_env.into(), - lookups, - name, - false, - ) -} - -pub fn ingress_addr_shallow_into_kenv_with_lookups( - kenv: &mut KEnv, - ixon_env: &IxonEnv, - lookups: &IxonIngressLookups, - addr: &Address, -) -> Result { - ingress_addr_set_into_kenv(kenv, ixon_env.into(), lookups, addr, false) -} - -pub(crate) fn ingress_addr_with_input( - kenv: &mut KEnv, - input: crate::input::Input<'_>, - lookups: &IxonIngressLookups, - addr: &Address, -) -> Result { - ingress_addr_set_into_kenv(kenv, input, lookups, addr, false) -} - -pub(crate) fn ingress_name_with_input( - kenv: &mut KEnv, - input: crate::input::Input<'_>, - lookups: &IxonIngressLookups, - name: &Name, -) -> Result, String> { - ingress_const_into_kenv_with_lookups_impl(kenv, input, lookups, name, false) -} - -fn ingress_const_into_kenv_with_lookups_impl( - kenv: &mut KEnv, - ixon_env: crate::input::Input<'_>, - lookups: &IxonIngressLookups, - name: &Name, - follow_refs: bool, -) -> Result, String> { - let requested = ixon_env - .named(name) - .ok_or_else(|| format!("{}: missing Named entry", name.pretty()))?; - let requested_id = - KId::new(requested.addr.clone(), M::meta_field(name.clone())); - - ingress_addr_set_into_kenv( - kenv, - ixon_env, - lookups, - &requested.addr, - follow_refs, - )?; - - if !kenv.contains_key(&requested_id) { - return Err(format!("{}: no ingressed kernel constant", name.pretty())); - } - Ok(requested_id) -} - -fn ingress_addr_set_into_kenv( - kenv: &mut KEnv, - ixon_env: crate::input::Input<'_>, - lookups: &IxonIngressLookups, - seed_addr: &Address, - follow_refs: bool, -) -> Result { - let mut seen: FxHashSet
= FxHashSet::default(); - let mut found_seed = false; - let mut worklist = vec![IngressNeed::Addr(seed_addr.clone())]; - let convert_stats_enabled = ingress_convert_stats_enabled(); - - while let Some(need) = worklist.pop() { - let addr = match need { - IngressNeed::Addr(addr) => addr, - IngressNeed::ProjectionAliases(addr) => { - insert_addr_aliases(kenv, lookups, &addr); - continue; - }, - }; - - if !seen.insert(addr.clone()) { - continue; - } - - let Some(constant) = ixon_env.constant(&addr)? else { - // `Constant.refs` also contains blob addresses for string/nat payloads. - continue; - }; - if &addr == seed_addr { - found_seed = true; - } - - if let Some(block_addr) = projection_block(&constant.info) { - worklist.push(IngressNeed::ProjectionAliases(addr)); - worklist.push(IngressNeed::Addr(block_addr.clone())); - continue; - } - - if follow_refs { - for dep in &constant.refs { - if ixon_env.contains(dep) { - worklist.push(IngressNeed::Addr(dep.clone())); - } - } - } - - match &constant.info { - IxonCI::Muts(_) => { - let Some(block_entries) = lookups.muts_by_addr.get(&addr) else { - return Err(format!("Muts block {} has no named entry", addr.hex())); - }; - for (entry_name, all) in block_entries { - let block_id = - KId::new(addr.clone(), M::meta_field(entry_name.clone())); - if kenv.blocks.contains_key(&block_id) { - continue; - } - let mut convert_stats = ConvertStats::new(convert_stats_enabled); - let entries = ingress_muts_block( - entry_name, - &addr, - all, - ixon_env, - &lookups.names, - &lookups.name_to_addr, - &mut kenv.intern, - &mut convert_stats, - ) - .map_err(|e| format!("{entry_name}: {e}"))?; - insert_muts_entries(kenv, entries); - } - }, - _ => { - let Some(const_names) = lookups.names_for_addr(&addr) else { - return Err(format!("constant {} has no named entry", addr.hex())); - }; - for const_name in const_names { - let kid = KId::new(addr.clone(), M::meta_field(const_name.clone())); - if kenv.contains_key(&kid) { - continue; - } - let named = ixon_env - .named(const_name) - .ok_or_else(|| format!("{const_name}: missing Named entry"))?; - let mut convert_stats = ConvertStats::new(convert_stats_enabled); - let entries = ingress_standalone( - const_name, - &addr, - &constant, - &named.meta(), - ixon_env, - &lookups.names, - &lookups.name_to_addr, - &mut kenv.intern, - &mut convert_stats, - ) - .map_err(|e| format!("{const_name}: {e}"))?; - insert_standalone_entries(kenv, entries); - } - }, - } - } - - Ok(found_seed) -} - -#[derive(Default)] -struct IngressInsertTiming { - blocks_ns: u64, - consts_ns: u64, -} - -#[derive(Default)] -struct IngressStreamTimingSnapshot { - standalone_items: u64, - muts_items: u64, - output_consts: u64, - missing_consts: u64, - lookup_ns: u64, - const_get_ns: u64, - convert_ns: u64, - insert_ns: u64, - insert_blocks_ns: u64, - insert_consts_ns: u64, - convert_stats: ConvertStats, -} - -impl IngressStreamTimingSnapshot { - fn merge(mut self, other: &Self) -> Self { - self.standalone_items += other.standalone_items; - self.muts_items += other.muts_items; - self.output_consts += other.output_consts; - self.missing_consts += other.missing_consts; - self.lookup_ns += other.lookup_ns; - self.const_get_ns += other.const_get_ns; - self.convert_ns += other.convert_ns; - self.insert_ns += other.insert_ns; - self.insert_blocks_ns += other.insert_blocks_ns; - self.insert_consts_ns += other.insert_consts_ns; - self.convert_stats = self.convert_stats.merge(&other.convert_stats); - self - } -} - -#[cfg(not(target_arch = "riscv64"))] -#[derive(Default)] -struct IxonDropTiming { - consts_ns: u64, - named_ns: u64, - names_ns: u64, - blobs_ns: u64, - comms_ns: u64, -} - -#[cfg(not(target_arch = "riscv64"))] -struct LookupDropTiming { - names_ns: u64, - name_to_addr_ns: u64, -} - -fn duration_ns(d: Duration) -> u64 { - u64::try_from(d.as_nanos()).unwrap_or(u64::MAX) -} - -fn elapsed_ns(start: Instant) -> u64 { - duration_ns(start.elapsed()) -} - -#[cfg(not(target_arch = "riscv64"))] -#[allow(clippy::cast_precision_loss)] -fn seconds(ns: u64) -> f64 { - ns as f64 / 1_000_000_000.0 -} - -#[cfg(not(target_arch = "riscv64"))] -#[allow(clippy::cast_precision_loss)] -fn percent(part: u64, total: u64) -> f64 { - if total == 0 { 0.0 } else { (part as f64 * 100.0) / total as f64 } -} - -#[cfg(not(target_arch = "riscv64"))] -fn timed_drop_ns(value: T) -> u64 { - let start = Instant::now(); - drop(value); - elapsed_ns(start) -} - -/// Drop a `DashMap` in parallel across its shards. -/// -/// DashMap's `IntoParallelIterator` impl yields owned `(K, V)` pairs by -/// processing shards as the parallel unit (one rayon task per shard, -/// sequential within a shard). Default shard count is `4 * num_cpus()`, which -/// gives rayon's work-stealing plenty to distribute. -/// -/// Used by `drop_ixon_env` to tear down the five `DashMap`s holding the -/// post-ingress IxonEnv. Concurrent `Arc::drop` is safe by construction -/// (atomic refcount; the last decrementer destroys exactly once), and none -/// of the value types have custom `Drop` impls — so this is a pure -/// parallelisation of the existing teardown. -#[cfg(not(target_arch = "riscv64"))] -fn timed_drop_dashmap_par(map: DashMap) -> u64 -where - K: Eq + Hash + Send, - V: Send, - S: BuildHasher + Clone + Send, -{ - let start = Instant::now(); - map.into_par_iter().for_each(drop); - elapsed_ns(start) -} - -/// Drop an `FxHashMap` (= `std::HashMap` with FxHasher) in parallel. -/// -/// `std::HashMap` only exposes a sequential `into_iter()`, so we drain into -/// a `Vec<(K, V)>` first (a cheap O(n) sequential pass that just moves owned -/// pairs) and then `into_par_iter().for_each(drop)` on the Vec, letting -/// rayon distribute the actual destructor work. -#[cfg(not(target_arch = "riscv64"))] -fn timed_drop_fxmap_par(map: FxHashMap) -> u64 { - let start = Instant::now(); - let entries: Vec<(K, V)> = map.into_iter().collect(); - entries.into_par_iter().for_each(drop); - elapsed_ns(start) -} - -/// Opt-out for the parallel drop path: set `IX_SEQ_IXON_DROP=1` to fall back -/// to single-threaded `drop` for measurement comparisons. -#[cfg(not(target_arch = "riscv64"))] -fn seq_ixon_drop_enabled() -> bool { - crate::env_var_os("IX_SEQ_IXON_DROP").is_some() -} - -#[cfg(not(target_arch = "riscv64"))] -fn ingress_convert_stats_enabled() -> bool { - crate::env_var_os("IX_INGRESS_CONVERT_STATS").is_some() -} -#[cfg(target_arch = "riscv64")] -fn ingress_convert_stats_enabled() -> bool { - false -} - -#[cfg(target_arch = "riscv64")] -fn drop_ingress_lookups( - _names: FxHashMap, - _name_to_addr: FxHashMap, - _quiet: bool, -) { - // Auto-dropped at end of scope. -} - -#[cfg(not(target_arch = "riscv64"))] -fn drop_ingress_lookups( - names: FxHashMap, - name_to_addr: FxHashMap, - quiet: bool, -) { - let total_start = Instant::now(); - let names_len = names.len(); - let name_to_addr_len = name_to_addr.len(); - #[cfg(not(target_arch = "riscv64"))] - let sequential = seq_ixon_drop_enabled(); - #[cfg(target_arch = "riscv64")] - let sequential = true; - - // Drop the two lookup tables in series; each one fully utilises the rayon - // pool internally via `timed_drop_fxmap_par`. Running them in parallel via - // `rayon::scope` would just fight for the same global thread pool and - // entangle per-map timings. - let timing = if sequential { - LookupDropTiming { - names_ns: timed_drop_ns(names), - name_to_addr_ns: timed_drop_ns(name_to_addr), - } - } else { - #[cfg(not(target_arch = "riscv64"))] - { - LookupDropTiming { - names_ns: timed_drop_fxmap_par(names), - name_to_addr_ns: timed_drop_fxmap_par(name_to_addr), - } - } - #[cfg(target_arch = "riscv64")] - unreachable!() - }; - - let total_ns = elapsed_ns(total_start); - if !quiet { - #[cfg(not(target_arch = "riscv64"))] - let threads = rayon::current_num_threads(); - #[cfg(target_arch = "riscv64")] - let threads = 1; - log::info!( - "[ixon_ingress] drop lookups: {:.2}s {} threads={} \ - (names {:.2}s/{} name_to_addr {:.2}s/{})", - seconds(total_ns), - if sequential { "sequential" } else { "parallel" }, - threads, - seconds(timing.names_ns), - names_len, - seconds(timing.name_to_addr_ns), - name_to_addr_len - ); - } -} - -fn insert_standalone_entries( - zenv: &mut KEnv, - entries: Vec<(KId, KConst)>, -) -> IngressInsertTiming { - #[cfg(not(target_arch = "riscv64"))] - let mut timing = IngressInsertTiming::default(); - #[cfg(target_arch = "riscv64")] - let timing = IngressInsertTiming::default(); - - #[cfg(not(target_arch = "riscv64"))] - let phase_start = Instant::now(); - for (id, _) in &entries { - zenv.blocks.entry(id.clone()).or_default().push(id.clone()); - } - #[cfg(not(target_arch = "riscv64"))] - { - timing.blocks_ns = elapsed_ns(phase_start); - } - - #[cfg(not(target_arch = "riscv64"))] - let phase_start = Instant::now(); - for (id, zc) in entries { - zenv.insert(id, zc); - } - #[cfg(not(target_arch = "riscv64"))] - { - timing.consts_ns = elapsed_ns(phase_start); - } - - timing -} - -fn insert_muts_entries( - zenv: &mut KEnv, - entries: Vec<(KId, KConst)>, -) -> IngressInsertTiming { - #[cfg(not(target_arch = "riscv64"))] - let mut timing = IngressInsertTiming::default(); - #[cfg(target_arch = "riscv64")] - let timing = IngressInsertTiming::default(); - - #[cfg(not(target_arch = "riscv64"))] - let phase_start = Instant::now(); - let block_id = entries.first().and_then(|(_, zc)| match zc { - KConst::Defn { block, .. } - | KConst::Recr { block, .. } - | KConst::Indc { block, .. } => Some(block.clone()), - _ => None, - }); - let member_ids: Vec> = - entries.iter().map(|(id, _)| id.clone()).collect(); - if let Some(bid) = block_id { - zenv.blocks.insert(bid, member_ids); - } - #[cfg(not(target_arch = "riscv64"))] - { - timing.blocks_ns = elapsed_ns(phase_start); - } - - #[cfg(not(target_arch = "riscv64"))] - let phase_start = Instant::now(); - for (id, zc) in entries { - zenv.insert(id, zc); - } - #[cfg(not(target_arch = "riscv64"))] - { - timing.consts_ns = elapsed_ns(phase_start); - } - - timing -} - -/// Convert an Ixon environment to a zero kernel environment. -pub fn ixon_ingress( - ixon_env: &IxonEnv, -) -> Result<(KEnv, InternTable), String> { - ixon_ingress_inner(ixon_env) -} - -/// Convert an owned Ixon environment to a zero kernel environment. -/// -/// This is the production path for callers that do not need the compiled Ixon -/// environment after ingress. Taking ownership ensures the Ixon side is dropped -/// before the kernel check loop starts. -pub fn ixon_ingress_owned( - ixon_env: IxonEnv, -) -> Result<(KEnv, InternTable), String> { - let quiet = crate::env_var_os("IX_VERBOSE").is_none(); - let result = ixon_ingress_inner(&ixon_env); - drop_ixon_env(ixon_env, quiet); - result -} - -#[cfg(target_arch = "riscv64")] -fn drop_ixon_env(_ixon_env: IxonEnv, _quiet: bool) { - // Auto-dropped at end of scope. -} - -#[cfg(not(target_arch = "riscv64"))] -fn drop_ixon_env(ixon_env: IxonEnv, quiet: bool) { - let total_start = Instant::now(); - // `anon_hints` is small (one entry per Def from the .ixe's hints - // section); `main`/`assumptions` are a single address and a small - // set. Dropping them inline alongside the bookkeeping below is - // negligible compared to the DashMap dropdance. - // `..` covers the env's private fields. - let IxonEnv { consts, named, blobs, names, comms, .. } = ixon_env; - let consts_len = consts.len(); - let named_len = named.len(); - let names_len = names.len(); - let blobs_len = blobs.len(); - let comms_len = comms.len(); - - // Drop each map sequentially, but parallelise across each map's shards via - // `timed_drop_dashmap_par`. The previous `rayon::scope` 5-task fan-out only - // achieved map-level parallelism — wall-clock was bounded by `consts`, - // which is single-threaded internally and dominates the total. Doing one - // map at a time, fully parallel within, gives clean per-map timing and - // saturates the rayon pool on the work that actually matters. - #[cfg(not(target_arch = "riscv64"))] - let sequential = seq_ixon_drop_enabled(); - #[cfg(target_arch = "riscv64")] - let sequential = true; - let timing = if sequential { - IxonDropTiming { - consts_ns: timed_drop_ns(consts), - named_ns: timed_drop_ns(named), - names_ns: timed_drop_ns(names), - blobs_ns: timed_drop_ns(blobs), - comms_ns: timed_drop_ns(comms), - } - } else { - #[cfg(not(target_arch = "riscv64"))] - { - IxonDropTiming { - consts_ns: timed_drop_dashmap_par(consts), - named_ns: timed_drop_dashmap_par(named), - names_ns: timed_drop_dashmap_par(names), - blobs_ns: timed_drop_dashmap_par(blobs), - comms_ns: timed_drop_dashmap_par(comms), - } - } - #[cfg(target_arch = "riscv64")] - unreachable!() - }; - - let total_ns = elapsed_ns(total_start); - if !quiet { - #[cfg(not(target_arch = "riscv64"))] - let threads = rayon::current_num_threads(); - #[cfg(target_arch = "riscv64")] - let threads = 1; - log::info!( - "[ixon_ingress] drop ixon_env: {:.2}s {} threads={} \ - (consts {:.2}s/{} named {:.2}s/{} names {:.2}s/{} blobs {:.2}s/{} comms {:.2}s/{})", - seconds(total_ns), - if sequential { "sequential" } else { "parallel" }, - threads, - seconds(timing.consts_ns), - consts_len, - seconds(timing.named_ns), - named_len, - seconds(timing.names_ns), - names_len, - seconds(timing.blobs_ns), - blobs_len, - seconds(timing.comms_ns), - comms_len - ); - } -} - -fn ixon_ingress_inner( - ixon_env: &IxonEnv, -) -> Result<(KEnv, InternTable), String> { - #[cfg(not(target_arch = "riscv64"))] - let total_start = Instant::now(); - - #[cfg(not(target_arch = "riscv64"))] - let phase_start = Instant::now(); - validate_no_reserved_marker_addresses(ixon_env)?; - #[cfg(not(target_arch = "riscv64"))] - log::info!( - "[ixon_ingress] validate_reserved: {:.2}s", - phase_start.elapsed().as_secs_f32() - ); - - let mut intern = InternTable::new(); - - // Build the address → Lean-name lookup and the Lean-name → projection- - // address lookup. See `build_ingress_lookups` for the role each plays. - #[cfg(not(target_arch = "riscv64"))] - let phase_start = Instant::now(); - let mut names: FxHashMap = FxHashMap::default(); - for entry in ixon_env.names.iter() { - names.insert(entry.key().clone(), entry.value().clone()); - } - let mut name_to_addr: FxHashMap = FxHashMap::default(); - for entry in ixon_env.named.iter() { - name_to_addr.insert(entry.key().clone(), entry.value().addr.clone()); - } - #[cfg(not(target_arch = "riscv64"))] - log::info!( - "[ixon_ingress] build lookups: {:.2}s ({} names, {} named)", - phase_start.elapsed().as_secs_f32(), - names.len(), - name_to_addr.len() - ); - - // Partition named entries into work items without cloning the `Named` - // metadata payloads. Each worker resolves its current Named entry just - // before conversion. - #[cfg(not(target_arch = "riscv64"))] - let phase_start = Instant::now(); - let mut work_items: Vec = Vec::new(); - - for entry in ixon_env.named.iter() { - let const_name = entry.key().clone(); - let named = entry.value(); - match &named.meta().info { - ConstantMetaInfo::Muts { .. } => { - work_items.push(IngressWorkItem::Muts(const_name)); - }, - ConstantMetaInfo::Indc { .. } - | ConstantMetaInfo::Ctor { .. } - | ConstantMetaInfo::Rec { .. } => { - if let Some(c) = ixon_env.get_const(&named.addr) { - match &c.info { - IxonCI::IPrj(_) - | IxonCI::CPrj(_) - | IxonCI::RPrj(_) - | IxonCI::DPrj(_) => {}, - _ => { - work_items.push(IngressWorkItem::Standalone(const_name)); - }, - } - } - }, - ConstantMetaInfo::Def { .. } => { - if let Some(c) = ixon_env.get_const(&named.addr) { - match &c.info { - IxonCI::DPrj(_) => {}, - _ => { - work_items.push(IngressWorkItem::Standalone(const_name)); - }, - } - } - }, - _ => { - work_items.push(IngressWorkItem::Standalone(const_name)); - }, - } - } - #[cfg(not(target_arch = "riscv64"))] - { - let muts_count = work_items - .iter() - .filter(|w| matches!(w, IngressWorkItem::Muts(_))) - .count(); - log::info!( - "[ixon_ingress] partition work: {:.2}s ({} standalone, {} muts)", - phase_start.elapsed().as_secs_f32(), - work_items.len() - muts_count, - muts_count - ); - } - - // Convert each standalone constant or Muts block sequentially into the - // single-threaded KEnv. - #[cfg(not(target_arch = "riscv64"))] - let phase_start = Instant::now(); - let convert_stats_enabled = ingress_convert_stats_enabled(); - let mut zenv: KEnv = KEnv::new(); - let mut stream = IngressStreamTimingSnapshot::default(); - for work_item in work_items { - let mut timing = IngressStreamTimingSnapshot::default(); - let mut convert_stats = ConvertStats::new(convert_stats_enabled); - match work_item { - IngressWorkItem::Standalone(const_name) => { - timing.standalone_items += 1; - #[cfg(not(target_arch = "riscv64"))] - let lookup_start = Instant::now(); - let named = ixon_env - .lookup_name(&const_name) - .ok_or_else(|| format!("{const_name}: missing Named entry"))?; - #[cfg(not(target_arch = "riscv64"))] - { - timing.lookup_ns += elapsed_ns(lookup_start); - } - - #[cfg(not(target_arch = "riscv64"))] - let const_start = Instant::now(); - let constant = match ixon_env.get_const(&named.addr) { - Some(c) => { - #[cfg(not(target_arch = "riscv64"))] - { - timing.const_get_ns += elapsed_ns(const_start); - } - c - }, - None => { - #[cfg(not(target_arch = "riscv64"))] - { - timing.const_get_ns += elapsed_ns(const_start); - } - timing.missing_consts += 1; - timing.convert_stats = convert_stats; - stream = stream.merge(&timing); - continue; - }, - }; - #[cfg(not(target_arch = "riscv64"))] - let convert_start = Instant::now(); - let entries = ingress_standalone( - &const_name, - &named.addr, - &constant, - &named.meta(), - ixon_env.into(), - &names, - &name_to_addr, - &mut intern, - &mut convert_stats, - ) - .map_err(|e| format!("{const_name}: {e}"))?; - #[cfg(not(target_arch = "riscv64"))] - { - timing.convert_ns += elapsed_ns(convert_start); - } - timing.output_consts += entries.len() as u64; - - #[cfg(not(target_arch = "riscv64"))] - let insert_start = Instant::now(); - let insert_timing = insert_standalone_entries(&mut zenv, entries); - #[cfg(not(target_arch = "riscv64"))] - { - timing.insert_ns += elapsed_ns(insert_start); - } - timing.insert_blocks_ns += insert_timing.blocks_ns; - timing.insert_consts_ns += insert_timing.consts_ns; - }, - IngressWorkItem::Muts(entry_name) => { - timing.muts_items += 1; - #[cfg(not(target_arch = "riscv64"))] - let lookup_start = Instant::now(); - let named = ixon_env - .lookup_name(&entry_name) - .ok_or_else(|| format!("{entry_name}: missing Named entry"))?; - #[cfg(not(target_arch = "riscv64"))] - { - timing.lookup_ns += elapsed_ns(lookup_start); - } - - let named_meta = named.meta(); - let all = match &named_meta.info { - ConstantMetaInfo::Muts { all, .. } => all, - _ => { - timing.convert_stats = convert_stats; - stream = stream.merge(&timing); - continue; - }, - }; - #[cfg(not(target_arch = "riscv64"))] - let convert_start = Instant::now(); - let entries = ingress_muts_block( - &entry_name, - &named.addr, - all, - ixon_env.into(), - &names, - &name_to_addr, - &mut intern, - &mut convert_stats, - ) - .map_err(|e| format!("{entry_name}: {e}"))?; - #[cfg(not(target_arch = "riscv64"))] - { - timing.convert_ns += elapsed_ns(convert_start); - } - timing.output_consts += entries.len() as u64; - - #[cfg(not(target_arch = "riscv64"))] - let insert_start = Instant::now(); - let insert_timing = insert_muts_entries(&mut zenv, entries); - #[cfg(not(target_arch = "riscv64"))] - { - timing.insert_ns += elapsed_ns(insert_start); - } - timing.insert_blocks_ns += insert_timing.blocks_ns; - timing.insert_consts_ns += insert_timing.consts_ns; - }, - } - timing.convert_stats = convert_stats; - stream = stream.merge(&timing); - } - #[cfg(not(target_arch = "riscv64"))] - log::info!( - "[ixon_ingress] stream ingress+insert: {:.2}s", - phase_start.elapsed().as_secs_f32() - ); - #[cfg(not(target_arch = "riscv64"))] - log::info!( - "[ixon_ingress] stream detail (worker-sum): lookup {:.2}s, const_get {:.2}s, convert {:.2}s, insert {:.2}s (blocks {:.2}s, consts {:.2}s), work {} standalone/{} muts, output {} consts, missing {}", - seconds(stream.lookup_ns), - seconds(stream.const_get_ns), - seconds(stream.convert_ns), - seconds(stream.insert_ns), - seconds(stream.insert_blocks_ns), - seconds(stream.insert_consts_ns), - stream.standalone_items, - stream.muts_items, - stream.output_consts, - stream.missing_consts - ); - #[cfg(not(target_arch = "riscv64"))] - let cs = &stream.convert_stats; - #[cfg(not(target_arch = "riscv64"))] - if cs.enabled { - let cache_lookups = cs.expr_cache_hits + cs.expr_cache_misses; - log::info!( - "[ixon_ingress] convert cache: roots {} process {} hits {} misses {} hit {:.1}% inserts {} peak {} clears {} cleared {} shares {}", - cs.expr_roots, - cs.expr_process, - cs.expr_cache_hits, - cs.expr_cache_misses, - percent(cs.expr_cache_hits, cache_lookups), - cs.expr_cache_inserts, - cs.expr_cache_peak, - cs.expr_cache_clears, - cs.expr_cache_entries_cleared, - cs.share_expansions - ); - log::info!( - "[ixon_ingress] convert nodes: sort {} var {} ref {} rec {} app {} lam {} all {} let {} prj {} str {} nat {}", - cs.sort_nodes, - cs.var_nodes, - cs.ref_nodes, - cs.rec_nodes, - cs.app_nodes, - cs.lam_nodes, - cs.all_nodes, - cs.let_nodes, - cs.prj_nodes, - cs.str_nodes, - cs.nat_nodes - ); - log::info!( - "[ixon_ingress] convert metadata/univ: mdata_nodes {} mdata_kv_maps {} univ_roots {} univ_cache_hits {} univ_cache_misses {} univ_hit {:.1}% univ_cache_peak {} univ_process {} univ_interns {}", - cs.mdata_nodes, - cs.mdata_kv_maps, - cs.univ_roots, - cs.univ_cache_hits, - cs.univ_cache_misses, - percent(cs.univ_cache_hits, cs.univ_cache_hits + cs.univ_cache_misses), - cs.univ_cache_peak, - cs.univ_process, - cs.univ_interns - ); - let ie_lookups = cs.intern_expr_calls; - let iu_lookups = cs.intern_univ_calls; - log::info!( - "[ixon_ingress] convert timing (worker-sum): \ - resolve_kvmap {:.2}s/{} arena_walk {:.2}s \ - intern_expr {:.2}s/{} (get_hits {:.1}%) \ - intern_univ {:.2}s/{} (get_hits {:.1}%) \ - expr_cache lookup {:.2}s / insert {:.2}s \ - get_blob {:.2}s/{} \ - kexpr_construct {:.2}s/{} \ - process_arm {:.2}s continuation_arms {:.2}s", - seconds(cs.resolve_kvmap_ns), - cs.resolve_kvmap_calls, - seconds(cs.arena_walk_ns), - seconds(cs.intern_expr_ns), - cs.intern_expr_calls, - percent(cs.intern_expr_get_hits, ie_lookups), - seconds(cs.intern_univ_ns), - cs.intern_univ_calls, - percent(cs.intern_univ_get_hits, iu_lookups), - seconds(cs.expr_cache_lookup_ns), - seconds(cs.expr_cache_insert_ns), - seconds(cs.get_blob_ns), - cs.get_blob_calls, - seconds(cs.kexpr_construct_ns), - cs.kexpr_construct_calls, - seconds(cs.process_arm_ns), - seconds(cs.continuation_arms_ns) - ); - } - #[cfg(not(target_arch = "riscv64"))] - log::info!( - "[ixon_ingress] complete: {:.2}s ({} consts, {} blocks)", - total_start.elapsed().as_secs_f32(), - zenv.len(), - zenv.blocks.len() - ); - - drop_ingress_lookups(names, name_to_addr, false); - - Ok((zenv, intern)) -} - -fn validate_no_reserved_marker_addresses( - ixon_env: &IxonEnv, -) -> Result<(), String> { - for entry in ixon_env.consts.iter() { - if let Some(marker) = reserved_marker_name(entry.key()) { - return Err(format!( - "reserved kernel marker address {marker} ({}) used as an Ixon constant key", - entry.key().hex() - )); - } - let constant = entry.value().get().map_err(|e| { - format!( - "validate_no_reserved_marker_addresses: cannot materialize {}: {e}", - entry.key().hex() - ) - })?; - for (idx, addr) in constant.refs.iter().enumerate() { - if let Some(marker) = reserved_marker_name(addr) { - return Err(format!( - "reserved kernel marker address {marker} ({}) used in refs[{idx}] of Ixon constant {}", - addr.hex(), - entry.key().hex() - )); - } - } - } - - for entry in ixon_env.named.iter() { - if let Some(marker) = reserved_marker_name(&entry.value().addr) { - return Err(format!( - "reserved kernel marker address {marker} ({}) used as the named address for {}", - entry.value().addr.hex(), - entry.key().pretty() - )); - } - } - - Ok(()) -} - -// ============================================================================ -// Anonymous-mode ingress -// ============================================================================ -// -// Companion to the Meta-mode ingress above. Uses only `Constant` data — -// never reads `ConstantMeta`, `Env::named`, `Env::names`, or any other -// metadata. Projection addresses are reconstructed deterministically from -// `(block, idx, [cidx])` via `Constant::commit`, which agrees with the -// addresses stored in `env.consts` (the compiler produces these same -// projection constants). -// -// Intended for use with environments loaded via `Env::get_anon`, which -// discards `named`/`names`/`comms` sections. The helpers below do not -// depend on those sections being empty — they simply never consult them. - -use crate::mode::Anon; - -/// Fetch a constant with an explicit binding to its map key, including sources -/// assembled in memory rather than loaded with deferred address verification. -fn anon_constant( - env: crate::input::Input<'_>, - addr: &Address, -) -> Result>, String> { - env.constant(addr) -} - -/// A content-addressed file cannot encode a cycle between distinct blocks -/// without a hash preimage. Rec slots are the only local back edges. Gather -/// them during conversion and check the block before publishing any members. -#[derive(Default)] -struct LocalDefnDependencies { - edges: FxHashMap>, - safe: Vec
, -} - -impl LocalDefnDependencies { - fn insert( - &mut self, - addr: &Address, - safety: DefinitionSafety, - refs: FxHashSet
, - ) { - // Most declarations have no Rec nodes: keep their admission allocation-free. - if refs.is_empty() { - return; - } - if safety == DefinitionSafety::Safe { - self.safe.push(addr.clone()); - } - self.edges.insert(addr.clone(), refs); - } - - fn validate(&self) -> Result<(), String> { - let mut finished = FxHashSet::default(); - let mut active = FxHashSet::default(); - for root in &self.safe { - let mut pending = vec![(root, false)]; - while let Some((addr, exit)) = pending.pop() { - if exit { - active.remove(addr); - finished.insert(addr); - continue; - } - if finished.contains(addr) { - continue; - } - let Some(refs) = self.edges.get(addr) else { - continue; - }; - if !active.insert(addr) { - return Err(format!( - "cyclic definition dependency at {}", - addr.hex() - )); - } - pending.push((addr, true)); - pending.extend(refs.iter().map(|dependency| (dependency, false))); - } - } - Ok(()) - } -} - -/// Verify that a projection address computed from a block's structure -/// is actually present in the env's consts. Wrapped here so the four -/// dispatch arms in `ingress_anon_block` (DPrj/RPrj/IPrj/CPrj) all -/// produce the same error format. -fn verify_proj_addr_in_env( - kind: &'static str, - proj_addr: &Address, - block_addr: &Address, - member_idx: u64, - ctor_idx: Option, - anon_env: crate::input::Input<'_>, -) -> Result<(), String> { - if let Some(actual) = anon_env.constant(proj_addr)? { - let expected = match kind { - "DPrj" => { - ixon::constant::defn_proj_constant(member_idx, block_addr.clone()) - }, - "RPrj" => { - ixon::constant::recr_proj_constant(member_idx, block_addr.clone()) - }, - "IPrj" => { - ixon::constant::indc_proj_constant(member_idx, block_addr.clone()) - }, - "CPrj" => ixon::constant::ctor_proj_constant( - member_idx, - ctor_idx.ok_or("constructor projection has no constructor index")?, - block_addr.clone(), - ), - _ => return Err("unknown projection kind".into()), - }; - if actual.as_ref() != &expected { - return Err(format!( - "carried {kind} {} differs from its owner/index", - proj_addr.hex() - )); - } - return Ok(()); - } - #[cfg(not(target_arch = "riscv64"))] - if matches!(anon_env, crate::input::Input::Artifact(_)) { - // The authenticated owner defines these semantic members. Absence does - // not fabricate a raw record or authorize lookup of a missing reference. - return Ok(()); - } - match ctor_idx { - None => Err(format!( - "ingress_anon_block: computed {kind} address {} not present in env (block {} idx {})", - proj_addr.hex(), - block_addr.hex(), - member_idx - )), - Some(cidx) => Err(format!( - "ingress_anon_block: computed {kind} address {} not present in env (block {} idx {} cidx {})", - proj_addr.hex(), - block_addr.hex(), - member_idx, - cidx - )), - } -} - -/// Deterministic IPrj content address for member `idx` of `block`. -/// -/// Thin re-export of `ixon::constant::indc_proj_address` — -/// the canonical projection-address helper used by both compile and -/// ingress paths. Keep the `anon_` alias so existing call sites read -/// naturally in the anon-mode pipeline. -pub fn anon_indc_proj_addr(block: &Address, idx: u64) -> Address { - ixon::constant::indc_proj_address(idx, block) -} - -/// Deterministic DPrj content address for member `idx` of `block`. -pub fn anon_defn_proj_addr(block: &Address, idx: u64) -> Address { - ixon::constant::defn_proj_address(idx, block) -} - -/// Deterministic RPrj content address for member `idx` of `block`. -pub fn anon_recr_proj_addr(block: &Address, idx: u64) -> Address { - ixon::constant::recr_proj_address(idx, block) -} - -/// Deterministic CPrj content address for ctor `(idx, cidx)` of `block`. -pub fn anon_ctor_proj_addr(block: &Address, idx: u64, cidx: u64) -> Address { - ixon::constant::ctor_proj_address(idx, cidx, block) -} - -/// Compute deterministic ctor projection addresses for every constructor of -/// an inductive member at position `indc_idx` in a block at `block_addr`. -fn anon_ctor_addrs( - block_addr: &Address, - indc_idx: u64, - ind: &ixon::constant::Inductive, -) -> Vec
{ - (0..ind.ctors.len() as u64) - .map(|cidx| anon_ctor_proj_addr(block_addr, indc_idx, cidx)) - .collect() -} - -/// Anon ingress for a single standalone (non-mutual) constant. -/// -/// For `Defn` and `Recr` the mutual context is the singleton `[self_id]`, -/// so self-recursive standalones can use `Expr::Rec(0, univs)` to refer to -/// themselves. (Singleton-unwrapped self-recursive defs are emitted by the -/// compiler as standalones, with their self-reference encoded as `Rec(0)` — -/// same as a one-member mutual block would have used.) Named ingress uses -/// the same singleton. -/// -/// `Axio` and `Quot` cannot have `Rec`-references so we route them through -/// `ingress_standalone` unchanged. Projections / `Muts` belong to a block -/// and are not valid as standalone entries. -fn ingress_anon_standalone( - kenv: &mut KEnv, - anon_env: crate::input::Input<'_>, - addr: &Address, - constant: &Constant, -) -> Result, String> { - let empty_meta = ConstantMeta::default(); - let empty_names: FxHashMap = FxHashMap::default(); - let empty_n2a: FxHashMap = FxHashMap::default(); - let mut convert_stats = ConvertStats::new(false); - let self_id: KId = KId::new(addr.clone(), ()); - - let mut local_dependencies = LocalDefnDependencies::default(); - let entries = match &constant.info { - IxonCI::Defn(def) => ingress_defn::( - def, - self_id.clone(), - &empty_meta, - anon_env, - &empty_names, - &empty_n2a, - &constant.sharing, - &constant.refs, - &constant.univs, - self_id.clone(), - &mut kenv.intern, - &mut convert_stats, - vec![self_id.clone()], - Some(&mut local_dependencies), - )?, - IxonCI::Recr(rec) => ingress_recursor::( - rec, - self_id.clone(), - &empty_meta, - anon_env, - &empty_names, - &empty_n2a, - &constant.sharing, - &constant.refs, - &constant.univs, - self_id.clone(), - &mut kenv.intern, - &mut convert_stats, - vec![self_id.clone()], - )?, - _ => ingress_standalone::( - &Name::anon(), - addr, - constant, - &empty_meta, - anon_env, - &empty_names, - &empty_n2a, - &mut kenv.intern, - &mut convert_stats, - )?, - }; - local_dependencies.validate()?; - insert_standalone_entries(kenv, entries); - Ok(self_id) -} - -/// Anon ingress for a mutual inductive member. Parallel to -/// `ingress_muts_inductive` but takes ctor projection addresses **directly** -/// (caller computes via `anon_ctor_addrs`) and a pre-computed `mut_ctx` -/// (sibling KIds for `Expr::Rec` resolution) instead of going through -/// metadata. Uses `DEFAULT_ARENA` + `type_root=0` + empty level-params -/// for the inductive and every ctor. -#[allow(clippy::too_many_arguments)] -fn ingress_anon_inductive( - ind: &ixon::constant::Inductive, - self_id: &KId, - anon_env: crate::input::Input<'_>, - block_constant: &Constant, - block_id: KId, - member_idx: u64, - ctor_addrs: &[Address], - mut_ctx: &[KId], - intern: &mut InternTable, - stats: &mut ConvertStats, -) -> Result, KConst)>, String> { - if ctor_addrs.len() != ind.ctors.len() { - return Err(format!( - "ingress_anon_inductive: ctor_addrs.len()={} but ind.ctors.len()={}", - ctor_addrs.len(), - ind.ctors.len() - )); - } - - let empty_names: FxHashMap = FxHashMap::default(); - let level_params: Vec = Vec::new(); - let mut cache: ExprCache = FxHashMap::default(); - let mut univ_cache: UnivCache = FxHashMap::default(); - let ctx = Ctx:: { - sharing: &block_constant.sharing, - refs: &block_constant.refs, - univs: &block_constant.univs, - mut_ctx: mut_ctx.to_vec(), - arena: &DEFAULT_ARENA, - names: &empty_names, - lvls: level_params.clone(), - // Anon ingress never reads decorations (§5.5: anon stays - // metadata-blind), so no patches are threaded here. - univ_patches: FxHashMap::default(), - meta_univs: &[], - local_refs: None, - synth_counter: Cell::new(0), - }; - - let typ = ingress_expr( - &ind.typ, - 0, - &ctx, - intern, - anon_env, - &mut cache, - &mut univ_cache, - stats, - )?; - - let ctor_ids: Vec> = - ctor_addrs.iter().map(|a| KId::::new(a.clone(), ())).collect(); - - let mut results = vec![( - self_id.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: ind.lvls, - params: ind.params, - indices: ind.indices, - is_unsafe: ind.is_unsafe, - block: block_id, - member_idx, - ty: typ, - ctors: ctor_ids.clone(), - lean_all: (), - }, - )]; - - for (cidx, ctor) in ind.ctors.iter().enumerate() { - stats.record_cache_clear(&cache); - cache.clear(); - let ctor_id = ctor_ids[cidx].clone(); - let ctor_ctx = Ctx:: { - sharing: &block_constant.sharing, - refs: &block_constant.refs, - univs: &block_constant.univs, - mut_ctx: mut_ctx.to_vec(), - arena: &DEFAULT_ARENA, - names: &empty_names, - lvls: Vec::new(), - univ_patches: FxHashMap::default(), - meta_univs: &[], - local_refs: None, - synth_counter: Cell::new(0), - }; - let mut ctor_univ_cache: UnivCache = FxHashMap::default(); - - let ctor_typ = ingress_expr( - &ctor.typ, - 0, - &ctor_ctx, - intern, - anon_env, - &mut cache, - &mut ctor_univ_cache, - stats, - )?; - - results.push(( - ctor_id, - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: ctor.is_unsafe, - lvls: ctor.lvls, - induct: self_id.clone(), - cidx: ctor.cidx, - params: ctor.params, - fields: ctor.fields, - ty: ctor_typ, - }, - )); - } - - Ok(results) -} - -/// Anon ingress for an entire Muts block: ingresses every member (and -/// every constructor of every inductive member) under its deterministic -/// projection address. Verifies that each computed address exists in -/// `anon_env.consts` — missing → error (corrupted .ixe). -/// -/// Returns the KIds in member order. The first KId is the block's -/// "primary" — `check_ixon_const` checks the complete serialized unit, -/// including both phases of an inductive/recursor family. -pub fn ingress_anon_block( - kenv: &mut KEnv, - anon_env: &IxonEnv, - block_constant: &Constant, - block_addr: &Address, -) -> Result>, String> { - ingress_anon_block_input(kenv, anon_env.into(), block_constant, block_addr) -} - -pub(crate) fn ingress_anon_block_input( - kenv: &mut KEnv, - anon_env: crate::input::Input<'_>, - block_constant: &Constant, - block_addr: &Address, -) -> Result>, String> { - let IxonCI::Muts(members) = &block_constant.info else { - return Err(format!( - "ingress_anon_block: addr {} is not a Muts block (got variant {:?})", - block_addr.hex(), - block_constant.info.variant() - )); - }; - validate_serialized_muts(members)?; - - convert_anon_muts(kenv, anon_env, block_constant, block_addr, members) -} - -fn convert_anon_muts( - kenv: &mut KEnv, - anon_env: crate::input::Input<'_>, - block_constant: &Constant, - block_addr: &Address, - members: &[IxonMutConst], -) -> Result>, String> { - let block_id = KId::::new(block_addr.clone(), ()); - let mut convert_stats = ConvertStats::new(false); - let empty_meta = ConstantMeta::default(); - let empty_names: FxHashMap = FxHashMap::default(); - let empty_n2a: FxHashMap = FxHashMap::default(); - - // Compute mut_ctx for the entire block: one KId per member, addressed - // by its projection variant. Threaded through every member's ingress - // so `Expr::Rec(idx)` resolves to a sibling correctly. - let mut_ctx: Vec> = members - .iter() - .enumerate() - .map(|(i, m)| { - let i = i as u64; - let addr = match m { - IxonMutConst::Defn(_) => anon_defn_proj_addr(block_addr, i), - IxonMutConst::Indc(_) => anon_indc_proj_addr(block_addr, i), - IxonMutConst::Recr(_) => anon_recr_proj_addr(block_addr, i), - }; - KId::::new(addr, ()) - }) - .collect(); - - let mut local_dependencies = LocalDefnDependencies::default(); - let mut all_entries: Vec<(KId, KConst)> = Vec::new(); - let mut member_kids: Vec> = Vec::with_capacity(members.len()); - - for (i, member) in members.iter().enumerate() { - let idx = i as u64; - match member { - IxonMutConst::Defn(def) => { - let proj_addr = anon_defn_proj_addr(block_addr, idx); - verify_proj_addr_in_env( - "DPrj", &proj_addr, block_addr, idx, None, anon_env, - )?; - let self_id = KId::::new(proj_addr.clone(), ()); - member_kids.push(self_id.clone()); - - let entries = ingress_defn::( - def, - self_id, - &empty_meta, - anon_env, - &empty_names, - &empty_n2a, - &block_constant.sharing, - &block_constant.refs, - &block_constant.univs, - block_id.clone(), - &mut kenv.intern, - &mut convert_stats, - mut_ctx.clone(), - Some(&mut local_dependencies), - )?; - all_entries.extend(entries); - }, - IxonMutConst::Recr(rec) => { - let proj_addr = anon_recr_proj_addr(block_addr, idx); - verify_proj_addr_in_env( - "RPrj", &proj_addr, block_addr, idx, None, anon_env, - )?; - let self_id = KId::::new(proj_addr.clone(), ()); - member_kids.push(self_id.clone()); - - let entries = ingress_recursor::( - rec, - self_id, - &empty_meta, - anon_env, - &empty_names, - &empty_n2a, - &block_constant.sharing, - &block_constant.refs, - &block_constant.univs, - block_id.clone(), - &mut kenv.intern, - &mut convert_stats, - mut_ctx.clone(), - )?; - all_entries.extend(entries); - }, - IxonMutConst::Indc(ind) => { - let proj_addr = anon_indc_proj_addr(block_addr, idx); - verify_proj_addr_in_env( - "IPrj", &proj_addr, block_addr, idx, None, anon_env, - )?; - let self_id = KId::::new(proj_addr.clone(), ()); - member_kids.push(self_id.clone()); - - let ctor_addrs = anon_ctor_addrs(block_addr, idx, ind); - // Verify ctor addresses too — catches corruption early. - for (cidx, c_addr) in ctor_addrs.iter().enumerate() { - verify_proj_addr_in_env( - "CPrj", - c_addr, - block_addr, - idx, - Some(cidx as u64), - anon_env, - )?; - } - - let entries = ingress_anon_inductive( - ind, - &self_id, - anon_env, - block_constant, - block_id.clone(), - idx, - &ctor_addrs, - &mut_ctx, - &mut kenv.intern, - &mut convert_stats, - )?; - all_entries.extend(entries); - }, - } - } - - local_dependencies.validate()?; - insert_muts_entries(kenv, all_entries); - Ok(member_kids) -} - -/// Anon shallow ingress for a single address — used by the lazy fault -/// path in `TypeChecker` (`LazyAnonIngress`). For projections, fetches -/// the parent block and ingresses the relevant member (or the full -/// inductive when a constructor is requested). For standalones, ingresses -/// directly. Errors if asked about a Muts block address (not a valid -/// kernel KId target) or an absent address. -pub fn ingress_anon_addr_shallow( - kenv: &mut KEnv, - anon_env: &IxonEnv, - addr: &Address, -) -> Result { - ingress_anon_addr_shallow_input(kenv, anon_env.into(), addr) -} - -pub(crate) fn ingress_anon_addr_shallow_input( - kenv: &mut KEnv, - anon_env: crate::input::Input<'_>, - addr: &Address, -) -> Result { - let Some(constant) = anon_constant(anon_env, addr)? else { - return Ok(false); - }; - - // Extract the parent block address if this is a projection. The - // kernel sometimes asks for the block's KId directly (block - // coordination during mutual recursion) — in that case we ingress - // the block at its own address. - let block_addr: Option
= match &constant.info { - IxonCI::DPrj(p) => Some(p.block.clone()), - IxonCI::IPrj(p) => Some(p.block.clone()), - IxonCI::RPrj(p) => Some(p.block.clone()), - IxonCI::CPrj(p) => Some(p.block.clone()), - IxonCI::Muts(_) => Some(addr.clone()), - _ => None, - }; - - if let Some(block_addr) = block_addr { - // Block dedup: `insert_muts_entries` always stores the block under - // `KId(block_addr, ())` in `kenv.blocks`. A prior fault on any - // projection of this block populated all members in `kenv.consts`; - // re-ingressing would be wasted work and leaves heap-allocator - // fragmentation that doesn't return to the OS. The - // `LazyAnonIngress::faulted_addrs` set in `tc.rs` only dedupes at the - // projection-address level, so distinct sibling projections of the - // same block each fault separately and reach here. - let block_kid = KId::::new(block_addr.clone(), ()); - if kenv.blocks.contains_key(&block_kid) { - return Ok(true); - } - let block_const = - anon_constant(anon_env, &block_addr)?.ok_or_else(|| { - format!( - "ingress_anon_addr_shallow: block {} (parent of {}) absent", - block_addr.hex(), - addr.hex() - ) - })?; - ingress_anon_block_input(kenv, anon_env, &block_const, &block_addr)?; - return Ok(true); - } - - // Standalone (Defn/Recr/Axio/Quot). - ingress_anon_standalone(kenv, anon_env, addr, &constant)?; - Ok(true) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::expr::ExprData; - use crate::level::UnivData; - use ix_common::env::{self, BinderInfo}; - - fn mk_name(s: &str) -> Name { - let mut n = Name::anon(); - for part in s.split('.') { - n = Name::str(n, part.to_string()); - } - n - } - - fn n_lit(x: u64) -> Nat { - Nat::from(x) - } - - #[cfg(not(target_arch = "riscv64"))] - #[test] - fn lean_recursor_layout_uses_source_then_numeric_order() { - // Layout-only test: the source mutual may split into structural SCCs, - // but direct ingress must keep all three originals in `.all` order. - let head = mk_name("Z"); - let positions = FxHashMap::from_iter([ - (head.clone(), 0), - (mk_name("A"), 1), - (mk_name("C"), 2), - ]); - let mut peers = - ["Z.rec_10", "A.rec", "Z.rec_2", "C.rec", "Z.rec_1", "Z.rec"] - .map(mk_name); - peers - .sort_by_cached_key(|name| lean_recursor_order(name, &head, &positions)); - assert_eq!( - peers.map(|name| name.pretty()), - ["Z.rec", "A.rec", "C.rec", "Z.rec_1", "Z.rec_2", "Z.rec_10"], - ); - } - - #[cfg(not(target_arch = "riscv64"))] - #[test] - fn lean_recursor_layout_does_not_recognize_foreign_or_malformed_names() { - let head = mk_name("Z"); - let positions = - FxHashMap::from_iter([(head.clone(), 0), (mk_name("A"), 1)]); - for name in [ - "Foreign.rec", - "Foreign.rec_1", - "A.rec_1", - "Z.rec_0", - "Z.rec_01", - "Z.rec_", - "Z.rec_+1", - "Z.rec_-1", - "Z.rec_١", - "Z.rec_9999999999999999999999999999999999999999", - ] { - assert_eq!( - lean_recursor_order(&mk_name(name), &head, &positions), - LeanRecursorOrder::Unrecognized, - "{name} must not claim a valid source slot", - ); - } - assert_eq!( - lean_recursor_order( - &Name::num(head.clone(), n_lit(1)), - &head, - &positions - ), - LeanRecursorOrder::Unrecognized, - ); - } - - // ---- lean_level_to_kuniv ---- - - #[test] - fn lean_level_zero_to_kuniv() { - let u = lean_level_to_kuniv(&Level::zero(), &[]).unwrap(); - assert!(matches!(u.data(), UnivData::Zero(_))); - } - - #[test] - fn lean_level_succ_to_kuniv() { - let u = lean_level_to_kuniv(&Level::succ(Level::zero()), &[]).unwrap(); - match u.data() { - UnivData::Succ(inner, _) => { - assert!(matches!(inner.data(), UnivData::Zero(_))) - }, - other => panic!("expected Succ, got {other:?}"), - } - } - - #[test] - fn lean_level_param_by_index() { - let u_name = mk_name("u"); - let v_name = mk_name("v"); - let params = vec![u_name.clone(), v_name.clone()]; - let u = lean_level_to_kuniv(&Level::param(v_name), ¶ms).unwrap(); - match u.data() { - UnivData::Param(i, _, _) => assert_eq!(*i, 1), - other => panic!("expected Param, got {other:?}"), - } - } - - #[test] - fn lean_level_max_to_kuniv() { - let u_name = mk_name("u"); - let v_name = mk_name("v"); - let params = vec![u_name.clone(), v_name.clone()]; - let ll = Level::max(Level::param(u_name), Level::param(v_name)); - let u = lean_level_to_kuniv(&ll, ¶ms).unwrap(); - assert!(matches!(u.data(), UnivData::Max(..))); - } - - #[test] - fn lean_level_param_unknown_is_an_error() { - let err = - lean_level_to_kuniv(&Level::param(mk_name("zzz")), &[mk_name("u")]) - .unwrap_err(); - assert!(err.contains("unknown level param `zzz`"), "{err}"); - // Valid neighbour: the declared parameter converts. - assert!( - lean_level_to_kuniv(&Level::param(mk_name("u")), &[mk_name("u")]).is_ok() - ); - // Nested under succ/max the refusal still propagates. - let nested = - Level::max(Level::zero(), Level::succ(Level::param(mk_name("zzz")))); - assert!(lean_level_to_kuniv(&nested, &[mk_name("u")]).is_err()); - } - - #[test] - fn lean_level_mvar_is_an_error() { - let err = lean_level_to_kuniv(&Level::mvar(mk_name("m")), &[]).unwrap_err(); - assert!(err.contains("unexpected level metavariable `m`"), "{err}"); - } - - // ---- lean_name_to_addr ---- - - #[test] - fn lean_name_to_addr_is_deterministic() { - let a1 = lean_name_to_addr(&mk_name("Nat")); - let a2 = lean_name_to_addr(&mk_name("Nat")); - assert_eq!(a1, a2); - } - - #[test] - fn lean_name_to_addr_different_names_differ() { - let a1 = lean_name_to_addr(&mk_name("Nat")); - let a2 = lean_name_to_addr(&mk_name("Bool")); - assert_ne!(a1, a2); - } - - #[test] - fn lean_name_to_addr_respects_dot_segments() { - let a1 = lean_name_to_addr(&mk_name("Nat.zero")); - let a2 = lean_name_to_addr(&mk_name("Nat.succ")); - assert_ne!(a1, a2); - } - - // ---- param_names_hash ---- - - #[test] - fn param_names_hash_determinism() { - let ps = [mk_name("u"), mk_name("v")]; - let h1 = param_names_hash(&ps); - let h2 = param_names_hash(&ps); - assert_eq!(h1, h2); - } - - #[test] - fn param_names_hash_order_sensitive() { - let h1 = param_names_hash(&[mk_name("u"), mk_name("v")]); - let h2 = param_names_hash(&[mk_name("v"), mk_name("u")]); - assert_ne!(h1, h2); - } - - #[test] - fn param_names_hash_length_sensitive() { - let h1 = param_names_hash(&[mk_name("u")]); - let h2 = param_names_hash(&[mk_name("u"), mk_name("u")]); - assert_ne!(h1, h2); - } - - #[test] - fn param_names_hash_empty_is_stable() { - let h1 = param_names_hash(&[]); - let h2 = param_names_hash(&[]); - assert_eq!(h1, h2); - } - - // ---- resolve_lean_name_addr ---- - - #[test] - fn resolve_lean_name_addr_fallback_uses_name_hash() { - let name = mk_name("Unknown"); - let expected = lean_name_to_addr(&name); - let a = resolve_lean_name_addr(&name, None, None); - assert_eq!(a, expected); - } - - #[test] - fn resolve_lean_name_addr_uses_primary_map() { - let map: DashMap = DashMap::new(); - let name = mk_name("Foo"); - let real = Address::hash(b"custom"); - map.insert(name.clone(), real.clone()); - let got = resolve_lean_name_addr(&name, Some(&map), None); - assert_eq!(got, real); - } - - #[test] - fn resolve_lean_name_addr_falls_through_to_aux() { - let primary: DashMap = DashMap::new(); - let aux: DashMap = DashMap::new(); - let name = mk_name("Aux.name"); - let real = Address::hash(b"aux"); - aux.insert(name.clone(), real.clone()); - let got = resolve_lean_name_addr(&name, Some(&primary), Some(&aux)); - assert_eq!(got, real); - } - - #[test] - fn ixon_ingress_rejects_reserved_marker_named_addr() { - let env = IxonEnv::new(); - let marker = crate::primitive::PrimAddrs::new().eager_reduce; - env.register_name( - mk_name("Evil.marker"), - ixon::env::Named::with_addr(marker), - ); - - let err = match ixon_ingress::(&env) { - Ok(_) => panic!("expected reserved marker rejection"), - Err(err) => err, - }; - assert!(err.contains("eager_reduce"), "{err}"); - assert!(err.contains("named address"), "{err}"); - } - - #[test] - fn ixon_ingress_rejects_reserved_marker_refs() { - let env = IxonEnv::new(); - let marker = crate::primitive::PrimAddrs::new().eager_reduce; - let constant = Constant::with_tables( - ixon::constant::ConstantInfo::Axio(ixon::constant::Axiom { - is_unsafe: false, - lvls: 0, - typ: IxonExpr::sort(0), - }), - vec![], - vec![marker], - vec![], - ); - env.store_const(Address::hash(b"evil-const"), constant); - - let err = match ixon_ingress::(&env) { - Ok(_) => panic!("expected reserved marker rejection"), - Err(err) => err, - }; - assert!(err.contains("eager_reduce"), "{err}"); - assert!(err.contains("refs[0]"), "{err}"); - } - - /// Meta-mode ingress must reject a local definition cycle before any member - /// is published: `TypeChecker::new_with_lazy_ixon` relies on that admission - /// check instead of walking definition dependencies on every check. - #[test] - fn ixon_ingress_meta_rejects_local_definition_cycles() { - use ix_common::env::DefinitionSafety; - use ixon::constant::{ - Constant, ConstantInfo, DefKind, Definition, MutConst, defn_proj_constant, - }; - use ixon::env::Named; - use ixon::metadata::{ConstantMeta, ConstantMetaInfo, ExprMeta}; - use ixon::univ::Univ; - let definition = |value: IxonExpr| Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: Arc::new(IxonExpr::Sort(1)), - value: Arc::new(value), - }; - for cyclic in [false, true] { - let env = IxonEnv::new(); - let mut block = Constant::new(ConstantInfo::Muts(vec![ - MutConst::Defn(definition(IxonExpr::Rec(1, vec![]))), - MutConst::Defn(definition(if cyclic { - IxonExpr::Rec(0, vec![]) - } else { - IxonExpr::Sort(0) - })), - ])); - block.univs = vec![Univ::zero(), Univ::succ(Univ::zero())]; - let block_addr = block.commit().0; - env.store_const(block_addr.clone(), block); - let member_names = [mk_name("Cycle.f"), mk_name("Cycle.g")]; - let name_addrs: Vec
= member_names - .iter() - .map(|n| Address::hash(n.pretty().as_bytes())) - .collect(); - let mut names: FxHashMap = FxHashMap::default(); - let mut name_to_addr: FxHashMap = FxHashMap::default(); - for (i, name) in member_names.iter().enumerate() { - let proj = defn_proj_constant(i as u64, block_addr.clone()); - let proj_addr = proj.commit().0; - env.store_const(proj_addr.clone(), proj); - let meta = ConstantMeta::new(ConstantMetaInfo::Def { - name: name_addrs[i].clone(), - lvls: vec![], - all: name_addrs.clone(), - ctx: name_addrs.clone(), - arena: ExprMeta::default(), - type_root: 0, - value_root: 0, - }); - env.register_name(name.clone(), Named::new(proj_addr.clone(), meta)); - names.insert(name_addrs[i].clone(), name.clone()); - name_to_addr.insert(name.clone(), proj_addr); - } - let all: Vec> = - name_addrs.iter().map(|a| vec![a.clone()]).collect(); - let mut intern = InternTable::::new(); - let mut stats = ConvertStats::new(false); - let result = ingress_muts_block::( - &mk_name("Cycle"), - &block_addr, - &all, - (&env).into(), - &names, - &name_to_addr, - &mut intern, - &mut stats, - ); - if cyclic { - let err = result.unwrap_err(); - assert!(err.contains("cyclic definition dependency"), "{err}"); - } else { - assert!(!result.unwrap().is_empty()); - } - } - } - - // ---- lean_expr_to_zexpr: variant coverage ---- - - fn do_ingress(e: &LeanExpr, pn: &[Name]) -> KExpr { - let mut intern = InternTable::::new(); - lean_expr_to_zexpr(e, pn, &mut intern, None, None).unwrap() - } - - #[test] - fn ingress_bvar() { - let e = LeanExpr::bvar(n_lit(5)); - let k = do_ingress(&e, &[]); - match k.data() { - ExprData::Var(i, _, _) => assert_eq!(*i, 5), - other => panic!("expected Var, got {other:?}"), - } - } - - #[test] - fn ingress_bvar_display_names_with_nonempty_context() { - let prefix = vec![mk_name("outer"), mk_name("inner")]; - let mut binders = prefix.clone(); - for (idx, expected) in [ - (0, mk_name("inner")), - (1, mk_name("outer")), - (2, Name::anon()), - (u64::MAX, Name::anon()), - ] { - let k = lean_expr_to_zexpr_cached( - &LeanExpr::bvar(Nat::from(idx)), - &[], - &mut binders, - &mut InternTable::new(), - None, - None, - None, - None, - ) - .unwrap(); - assert!( - matches!(k.data(), ExprData::Var(i, n, _) if *i == idx && n == &expected) - ); - assert_eq!(binders, prefix); - } - } - - #[test] - fn ixon_bvar_largest_usize_has_anonymous_name() { - let ctx = Ctx:: { - sharing: &[], - refs: &[], - univs: &[], - mut_ctx: vec![], - local_refs: None, - arena: &ExprMeta::default(), - names: &FxHashMap::default(), - lvls: vec![], - univ_patches: FxHashMap::default(), - meta_univs: &[], - synth_counter: Cell::new(0), - }; - let idx = usize::MAX as u64; - let k = ingress_expr( - &Arc::new(IxonExpr::Var(idx)), - 0, - &ctx, - &mut InternTable::new(), - (&IxonEnv::new()).into(), - &mut ExprCache::default(), - &mut UnivCache::default(), - &mut ConvertStats::default(), - ) - .unwrap(); - assert!( - matches!(k.data(), ExprData::Var(i, n, _) if *i == idx && n == &Name::anon()) - ); - } - - #[test] - fn ingress_sort_zero() { - let e = LeanExpr::sort(Level::zero()); - let k = do_ingress(&e, &[]); - assert!(matches!(k.data(), ExprData::Sort(..))); - } - - #[test] - fn ingress_const_without_universe_args() { - let e = LeanExpr::cnst(mk_name("Unit"), vec![]); - let k = do_ingress(&e, &[]); - match k.data() { - ExprData::Const(id, univs, _) => { - assert_eq!(univs.len(), 0); - assert_eq!(id.addr, lean_name_to_addr(&mk_name("Unit"))); - }, - other => panic!("expected Const, got {other:?}"), - } - } - - #[test] - fn ingress_const_with_universe_args() { - let u_name = mk_name("u"); - let e = LeanExpr::cnst(mk_name("List"), vec![Level::param(u_name.clone())]); - let k = do_ingress(&e, &[u_name]); - match k.data() { - ExprData::Const(_id, univs, _) => { - assert_eq!(univs.len(), 1); - assert!(matches!(univs[0].data(), UnivData::Param(0, _, _))); - }, - other => panic!("expected Const, got {other:?}"), - } - } - - #[test] - fn ingress_app() { - let e = - LeanExpr::app(LeanExpr::sort(Level::zero()), LeanExpr::bvar(n_lit(0))); - let k = do_ingress(&e, &[]); - assert!(matches!(k.data(), ExprData::App(..))); - } - - #[test] - fn ingress_lambda() { - let e = LeanExpr::lam( - mk_name("x"), - LeanExpr::sort(Level::zero()), - LeanExpr::bvar(n_lit(0)), - BinderInfo::Default, - ); - let k = do_ingress(&e, &[]); - assert!(matches!(k.data(), ExprData::Lam(..))); - } - - #[test] - fn ingress_forall() { - let e = LeanExpr::all( - mk_name("x"), - LeanExpr::sort(Level::zero()), - LeanExpr::sort(Level::zero()), - BinderInfo::Default, - ); - let k = do_ingress(&e, &[]); - assert!(matches!(k.data(), ExprData::All(..))); - } - - #[test] - fn ingress_let() { - let e = LeanExpr::letE( - mk_name("x"), - LeanExpr::sort(Level::zero()), - LeanExpr::bvar(n_lit(0)), - LeanExpr::bvar(n_lit(0)), - false, - ); - let k = do_ingress(&e, &[]); - assert!(matches!(k.data(), ExprData::Let(..))); - } - - #[test] - fn ingress_nat_literal() { - let e = LeanExpr::lit(env::Literal::NatVal(n_lit(42))); - let k = do_ingress(&e, &[]); - assert!(matches!(k.data(), ExprData::Nat(..))); - } - - #[test] - fn ingress_str_literal() { - let e = LeanExpr::lit(env::Literal::StrVal("hi".into())); - let k = do_ingress(&e, &[]); - assert!(matches!(k.data(), ExprData::Str(..))); - } - - #[test] - fn ingress_proj() { - let e = LeanExpr::proj(mk_name("Prod"), n_lit(0), LeanExpr::bvar(n_lit(0))); - let k = do_ingress(&e, &[]); - match k.data() { - ExprData::Prj(id, field, _, _) => { - assert_eq!(id.addr, lean_name_to_addr(&mk_name("Prod"))); - assert_eq!(*field, 0); - }, - other => panic!("expected Prj, got {other:?}"), - } - } - - #[test] - fn ingress_mdata_passes_through_inner_shape() { - // Mdata is metadata; the shape of the outer expression mirrors the inner. - let inner = LeanExpr::sort(Level::zero()); - let e = LeanExpr::mdata(vec![], inner); - let k = do_ingress(&e, &[]); - assert!(matches!(k.data(), ExprData::Sort(..))); - } - - // ---- Deep nesting: exercises the iterative stack ---- - - /// Drop a left-deep `Arc` spine iteratively so test - /// teardown doesn't recurse once per level. Without this, dropping a - /// chain of N `Expr`s recurses N times regardless of whether ingress - /// itself is iterative (the recursion is in `Arc::drop`). - fn drop_app_spine_iteratively(mut e: LeanExpr) { - while let env::ExprData::App(f, _, _) = e.as_data() { - let next = f.clone(); - drop(e); - e = next; - } - drop(e); - } - - /// Same pattern for forall / lambda body chains. - fn drop_binder_chain_iteratively(mut e: LeanExpr) { - while let env::ExprData::ForallE(_, _, body, _, _) - | env::ExprData::Lam(_, _, body, _, _) = e.as_data() - { - let next = body.clone(); - drop(e); - e = next; - } - drop(e); - } - - #[test] - fn ingress_deep_app_nesting_does_not_overflow() { - // Build a left-deep app spine and verify ingress completes without - // stack overflow. Depth is chosen to exercise the iterative stack - // without tipping the Arc drop chain over thread-stack - // limits (the recursive drop of a deeply nested `LeanExpr` is the - // dominant hazard here — ingress proper is iterative). - let depth = 500; - let mut e = LeanExpr::sort(Level::zero()); - for _ in 0..depth { - e = LeanExpr::app(e, LeanExpr::bvar(n_lit(0))); - } - let _k = do_ingress(&e, &[]); - // Manual teardown: avoid `e`'s recursive Drop. - drop_app_spine_iteratively(e); - } - - #[test] - fn ingress_deep_forall_nesting_does_not_overflow() { - // Body under deeply nested foralls. Binder-name stack must not - // overflow during ingress. - let depth = 500; - let mut e = LeanExpr::bvar(n_lit(0)); - for _ in 0..depth { - e = LeanExpr::all( - mk_name("x"), - LeanExpr::sort(Level::zero()), - e, - BinderInfo::Default, - ); - } - let _k = do_ingress(&e, &[]); - drop_binder_chain_iteratively(e); - } - - #[test] - fn ingress_deep_max_univ_does_not_overflow() { - // Deeply nested Max chain. Level drop is also recursive; keep depth - // conservative. - let mut l = Level::zero(); - for _ in 0..300 { - l = Level::max(l, Level::zero()); - } - let _u = lean_level_to_kuniv(&l, &[]).unwrap(); - } - - // ---- Invalid-input refusals (errors, not panics) ---- - - fn try_ingress(e: &LeanExpr, pn: &[Name]) -> Result, String> { - let mut intern = InternTable::::new(); - lean_expr_to_zexpr(e, pn, &mut intern, None, None) - } - - #[test] - fn ingress_fvar_is_an_error() { - let e = LeanExpr::fvar(mk_name("x")); - let err = try_ingress(&e, &[]).unwrap_err(); - assert!(err.contains("unexpected FVar(x)"), "{err}"); - } - - #[test] - fn ingress_mvar_is_an_error() { - let e = LeanExpr::mvar(mk_name("m")); - let err = try_ingress(&e, &[]).unwrap_err(); - assert!(err.contains("unexpected MVar(m)"), "{err}"); - } - - #[test] - fn ingress_unknown_level_param_under_app_is_an_error() { - // The refusal propagates from a level nested inside an application; - // the valid neighbour with the parameter declared ingresses. - let u = mk_name("u"); - let e = LeanExpr::app( - LeanExpr::cnst(mk_name("f"), vec![Level::param(u.clone())]), - LeanExpr::sort(Level::succ(Level::param(u.clone()))), - ); - let err = try_ingress(&e, &[mk_name("v")]).unwrap_err(); - assert!(err.contains("unknown level param `u`"), "{err}"); - assert!(try_ingress(&e, std::slice::from_ref(&u)).is_ok()); - } - - #[test] - fn ingress_level_mvar_under_sort_is_an_error() { - let e = LeanExpr::sort(Level::succ(Level::mvar(mk_name("m")))); - let err = try_ingress(&e, &[]).unwrap_err(); - assert!(err.contains("unexpected level metavariable `m`"), "{err}"); - } - - /// 2^64, the least `Nat` that does not fit a `u64`. - fn past_u64() -> Nat { - Nat::from_le_bytes(&[0, 0, 0, 0, 0, 0, 0, 0, 1]) - } - - #[test] - fn ingress_bvar_index_past_u64_is_an_error() { - // It used to become `bvar 0`. - let err = try_ingress(&LeanExpr::bvar(past_u64()), &[]).unwrap_err(); - assert!( - err.contains("bound variable index 18446744073709551616 exceeds u64"), - "{err}" - ); - // Under a binder the refusal still propagates. - let lam = LeanExpr::lam( - mk_name("x"), - LeanExpr::sort(Level::zero()), - LeanExpr::bvar(past_u64()), - BinderInfo::Default, - ); - assert!(try_ingress(&lam, &[]).unwrap_err().contains("exceeds u64")); - // Valid neighbour: the largest index that fits is kept as it is. - let k = try_ingress(&LeanExpr::bvar(Nat::from(u64::MAX)), &[]).unwrap(); - assert!(matches!(k.data(), ExprData::Var(i, ..) if *i == u64::MAX)); - } - - #[test] - fn ingress_proj_index_past_u64_is_an_error() { - // It used to become field 0. - let s = LeanExpr::sort(Level::zero()); - let e = LeanExpr::proj(mk_name("Prod"), past_u64(), s.clone()); - let err = try_ingress(&e, &[]).unwrap_err(); - assert!( - err.contains("projection index 18446744073709551616 of Prod exceeds u64"), - "{err}" - ); - // Valid neighbour: the largest index that fits is kept as it is. - let ok = LeanExpr::proj(mk_name("Prod"), Nat::from(u64::MAX), s); - let k = try_ingress(&ok, &[]).unwrap(); - assert!(matches!(k.data(), ExprData::Prj(_, i, ..) if *i == u64::MAX)); - } - - // ---- lean_ingress: refusals name the constant ---- - - /// `T : Type`, `T.mk : T` and `T.rec` with one rule, each Nat field as - /// `edit` leaves it (all fit by default). Ingress does not type-check, so - /// the expressions only need to be well-formed. - #[cfg(not(target_arch = "riscv64"))] - fn unit_like_env( - edit: impl FnOnce( - &mut env::InductiveVal, - &mut env::ConstructorVal, - &mut env::RecursorVal, - ), - ) -> LeanEnv { - let (t, mk, rec) = (mk_name("T"), mk_name("T.mk"), mk_name("T.rec")); - let cv = |name: &Name, typ: LeanExpr| env::ConstantVal { - name: name.clone(), - level_params: vec![], - typ, - }; - let type0 = LeanExpr::sort(Level::succ(Level::zero())); - let mut ind = env::InductiveVal { - cnst: cv(&t, type0.clone()), - num_params: n_lit(0), - num_indices: n_lit(0), - all: vec![t.clone()], - ctors: vec![mk.clone()], - num_nested: n_lit(0), - is_rec: false, - is_unsafe: false, - is_reflexive: false, - }; - let mut ctor = env::ConstructorVal { - cnst: cv(&mk, LeanExpr::cnst(t.clone(), vec![])), - induct: t.clone(), - cidx: n_lit(0), - num_params: n_lit(0), - num_fields: n_lit(0), - is_unsafe: false, - }; - let mut recr = env::RecursorVal { - cnst: cv(&rec, type0.clone()), - all: vec![t.clone()], - num_params: n_lit(0), - num_indices: n_lit(0), - num_motives: n_lit(1), - num_minors: n_lit(1), - rules: vec![env::RecursorRule { - ctor: mk.clone(), - n_fields: n_lit(0), - rhs: LeanExpr::lam( - mk_name("motive"), - type0, - LeanExpr::bvar(n_lit(0)), - BinderInfo::Default, - ), - }], - k: false, - is_unsafe: false, - }; - edit(&mut ind, &mut ctor, &mut recr); - let mut lean_env = LeanEnv::default(); - lean_env.insert(t, LeanCI::InductInfo(ind)); - lean_env.insert(mk, LeanCI::CtorInfo(ctor)); - lean_env.insert(rec, LeanCI::RecInfo(recr)); - lean_env - } - - #[cfg(not(target_arch = "riscv64"))] - fn assert_lean_ingress_refuses(lean_env: &LeanEnv, text: &str) { - match lean_ingress(lean_env) { - Err(err) => assert!(err.contains(text), "{err:?} lacks {text:?}"), - Ok(_) => panic!("expected lean_ingress to refuse with {text:?}"), - } - } - - #[cfg(not(target_arch = "riscv64"))] - #[test] - fn direct_lean_family_retains_member_checking() { - let source = unit_like_env(|_, _, rec| { - let u = mk_name("u"); - let typ = LeanExpr::cnst(mk_name("T"), vec![]); - let ctor = LeanExpr::cnst(mk_name("T.mk"), vec![]); - let var = |i| LeanExpr::bvar(n_lit(i)); - let motive = LeanExpr::all( - mk_name("x"), - typ.clone(), - LeanExpr::sort(Level::param(u.clone())), - BinderInfo::Default, - ); - let minor = LeanExpr::app(var(0), ctor); - rec.cnst.level_params = vec![u]; - rec.cnst.typ = LeanExpr::all( - mk_name("motive"), - motive.clone(), - LeanExpr::all( - mk_name("minor"), - minor.clone(), - LeanExpr::all( - mk_name("major"), - typ, - LeanExpr::app(var(2), var(0)), - BinderInfo::Default, - ), - BinderInfo::Default, - ), - BinderInfo::Default, - ); - rec.rules[0].rhs = LeanExpr::lam( - mk_name("motive"), - motive, - LeanExpr::lam(mk_name("minor"), minor, var(0), BinderInfo::Default), - BinderInfo::Default, - ); - }); - let mut kernel = lean_ingress(&source).unwrap(); - let ids: Vec<_> = ["T", "T.mk", "T.rec"] - .into_iter() - .map(|name| { - kernel - .consts - .keys() - .find(|id| id.name == mk_name(name)) - .unwrap() - .clone() - }) - .collect(); - let mut checker = crate::tc::TypeChecker::new(&mut kernel); - assert!( - checker.coordinated_check_block_for_const(&ids[0]).unwrap().is_none() - ); - for id in ids { - checker.check_const(&id).unwrap(); - } - } - - #[cfg(not(target_arch = "riscv64"))] - #[test] - fn lean_ingress_names_the_constant_of_an_expression_refusal() { - let mut lean_env = unit_like_env(|_, _, _| {}); - let ax = mk_name("Ax"); - lean_env.insert( - ax.clone(), - LeanCI::AxiomInfo(env::AxiomVal { - cnst: env::ConstantVal { - name: ax, - level_params: vec![], - typ: LeanExpr::sort(Level::param(mk_name("u"))), - }, - is_unsafe: false, - }), - ); - assert_lean_ingress_refuses(&lean_env, "Ax: unknown level param `u`"); - let fvar_rhs = unit_like_env(|_, _, r| { - r.rules[0].rhs = LeanExpr::fvar(mk_name("x")); - }); - assert_lean_ingress_refuses(&fvar_rhs, "T.rec: unexpected FVar(x)"); - let mvar_ty = unit_like_env(|_, c, _| { - c.cnst.typ = LeanExpr::mvar(mk_name("m")); - }); - assert_lean_ingress_refuses(&mvar_ty, "T.mk: unexpected MVar(m)"); - // Valid neighbour. - assert!(lean_ingress(&unit_like_env(|_, _, _| {})).is_ok()); - } - - #[cfg(not(target_arch = "riscv64"))] - #[test] - fn lean_ingress_refuses_inductive_counts_past_u64() { - let params = unit_like_env(|i, _, _| i.num_params = past_u64()); - assert_lean_ingress_refuses( - ¶ms, - "T: num_params 18446744073709551616 exceeds u64", - ); - let indices = unit_like_env(|i, _, _| i.num_indices = past_u64()); - assert_lean_ingress_refuses( - &indices, - "T: num_indices 18446744073709551616 exceeds u64", - ); - } - - #[cfg(not(target_arch = "riscv64"))] - #[test] - fn lean_ingress_refuses_constructor_fields_past_u64() { - let cidx = unit_like_env(|_, c, _| c.cidx = past_u64()); - assert_lean_ingress_refuses( - &cidx, - "T.mk: cidx 18446744073709551616 exceeds u64", - ); - let params = unit_like_env(|_, c, _| c.num_params = past_u64()); - assert_lean_ingress_refuses( - ¶ms, - "T.mk: num_params 18446744073709551616 exceeds u64", - ); - let fields = unit_like_env(|_, c, _| c.num_fields = past_u64()); - assert_lean_ingress_refuses( - &fields, - "T.mk: num_fields 18446744073709551616 exceeds u64", - ); - } - - #[cfg(not(target_arch = "riscv64"))] - #[test] - fn lean_ingress_refuses_recursor_counts_past_u64() { - type Edit = fn(&mut env::RecursorVal); - let cases: [(&str, Edit); 4] = [ - ("num_params", |r| r.num_params = past_u64()), - ("num_indices", |r| r.num_indices = past_u64()), - ("num_motives", |r| r.num_motives = past_u64()), - ("num_minors", |r| r.num_minors = past_u64()), - ]; - for (field, edit) in cases { - assert_lean_ingress_refuses( - &unit_like_env(|_, _, r| edit(r)), - &format!("T.rec: {field} 18446744073709551616 exceeds u64"), - ); - } - let rule = unit_like_env(|_, _, r| r.rules[0].n_fields = past_u64()); - assert_lean_ingress_refuses( - &rule, - "T.rec: n_fields 18446744073709551616 of the rule for T.mk exceeds u64", - ); - } - - /// Valid neighbour of the count refusals: every field at `u64::MAX` is - /// kept as it is (not 0), and the default fields convert unchanged. - #[cfg(not(target_arch = "riscv64"))] - #[test] - fn lean_ingress_keeps_count_fields_that_fit() { - let id_of = |kenv: &KEnv, n: &str| { - kenv.consts.keys().find(|id| id.name == mk_name(n)).unwrap().clone() - }; - let kenv = lean_ingress(&unit_like_env(|_, _, _| {})).unwrap(); - match &kenv.consts[&id_of(&kenv, "T.rec")] { - KConst::Recr { params, indices, motives, minors, rules, .. } => { - assert_eq!((*params, *indices, *motives, *minors), (0, 0, 1, 1)); - assert_eq!(rules[0].fields, 0); - }, - other => panic!("expected a recursor, got {other:?}"), - } - let max = Nat::from(u64::MAX); - let kenv = lean_ingress(&unit_like_env(|i, c, r| { - i.num_params = max.clone(); - i.num_indices = max.clone(); - c.cidx = max.clone(); - c.num_params = max.clone(); - c.num_fields = max.clone(); - r.num_params = max.clone(); - r.num_indices = max.clone(); - r.num_motives = max.clone(); - r.num_minors = max.clone(); - r.rules[0].n_fields = max.clone(); - })) - .unwrap(); - match &kenv.consts[&id_of(&kenv, "T")] { - KConst::Indc { params, indices, .. } => { - assert_eq!((*params, *indices), (u64::MAX, u64::MAX)); - }, - other => panic!("expected an inductive, got {other:?}"), - } - match &kenv.consts[&id_of(&kenv, "T.mk")] { - KConst::Ctor { cidx, params, fields, .. } => { - assert_eq!((*cidx, *params, *fields), (u64::MAX, u64::MAX, u64::MAX)); - }, - other => panic!("expected a constructor, got {other:?}"), - } - match &kenv.consts[&id_of(&kenv, "T.rec")] { - KConst::Recr { params, indices, motives, minors, rules, .. } => { - assert_eq!( - (*params, *indices, *motives, *minors, rules[0].fields), - (u64::MAX, u64::MAX, u64::MAX, u64::MAX, u64::MAX) - ); - }, - other => panic!("expected a recursor, got {other:?}"), - } - } - - // ---- Caching ---- - - #[test] - fn ingress_cached_hits_cache_on_second_call() { - let mut env = KEnv::::new(); - let e = LeanExpr::app( - LeanExpr::sort(Level::zero()), - LeanExpr::sort(Level::zero()), - ); - let k1 = - lean_expr_to_zexpr_with_kenv(&e, &[], &mut env, None, None).unwrap(); - let k2 = - lean_expr_to_zexpr_with_kenv(&e, &[], &mut env, None, None).unwrap(); - // Cache hit → same interned result. - assert!(k1.ptr_eq(&k2)); - } - - /// Canonicity §10.6 stage 2: a `univ_patches` entry keyed by the - /// occurrence's arena index decorates the occurrence with the - /// ORIGINAL spelling (resolved through the virtual index space - /// `univs ++ meta_univs`), while the kernel-held level stays the - /// canonical table entry. - #[test] - fn patch_sourced_sort_decoration() { - let canon = IxonUniv::max( - IxonUniv::succ(IxonUniv::var(0)), - IxonUniv::succ(IxonUniv::var(1)), - ); - let orig = - IxonUniv::succ(IxonUniv::max(IxonUniv::var(0), IxonUniv::var(1))); - let univs = vec![canon.clone()]; - let meta_univs = vec![orig.clone()]; - let mut arena = ExprMeta::default(); - let root = arena.alloc(ExprMetaData::Leaf); - // Virtual index 1 = univs.len() (1) + extension slot 0. - let patch_idxs: Vec = vec![1]; - let mut univ_patches: FxHashMap = FxHashMap::default(); - univ_patches.insert(root, patch_idxs.as_slice()); - let names: FxHashMap = FxHashMap::default(); - let ctx = Ctx:: { - sharing: &[], - refs: &[], - univs: &univs, - mut_ctx: vec![], - arena: &arena, - names: &names, - lvls: vec![mk_name("u"), mk_name("v")], - univ_patches, - meta_univs: &meta_univs, - local_refs: None, - synth_counter: Cell::new(0), - }; - let mut intern = InternTable::::new(); - let ixon_env = IxonEnv::new(); - let mut cache = ExprCache::::default(); - let mut univ_cache = UnivCache::::default(); - let mut stats = ConvertStats::default(); - let k = ingress_expr( - &IxonExpr::sort(0), - root, - &ctx, - &mut intern, - (&ixon_env).into(), - &mut cache, - &mut univ_cache, - &mut stats, - ) - .unwrap(); - let decor = Meta::meta_get(k.univ_decor()).cloned().flatten(); - assert_eq!(decor, Some(UnivDecor::Sort(orig.clone()))); - // The kernel-held level is the canonical entry, node-for-node. - let ExprData::Sort(zu, _) = k.data() else { - panic!("expected Sort, got {:?}", k.data()); - }; - assert!(ixon_matches_kuniv(&canon, zu)); - } - - /// P3 at the kernel boundary: `canon_univ`-fixed table entries ingress - /// as identity — the mk* smart constructors change nothing, so no - /// stage-1 fallback decoration fires without a patch. - #[test] - fn canonical_table_ingress_identity_no_decoration() { - use ixon::canon_univ::canon_univ; - let spellings = [ - IxonUniv::zero(), - IxonUniv::succ(IxonUniv::max(IxonUniv::var(0), IxonUniv::var(1))), - IxonUniv::imax(IxonUniv::var(0), IxonUniv::succ(IxonUniv::var(1))), - IxonUniv::max(IxonUniv::var(1), IxonUniv::var(0)), - IxonUniv::imax( - IxonUniv::succ(IxonUniv::var(0)), - IxonUniv::imax(IxonUniv::var(0), IxonUniv::var(1)), - ), - ]; - for spelling in spellings { - let canon = canon_univ(&spelling); - let univs = vec![canon.clone()]; - let mut arena = ExprMeta::default(); - let root = arena.alloc(ExprMetaData::Leaf); - let names: FxHashMap = FxHashMap::default(); - let ctx = Ctx:: { - sharing: &[], - refs: &[], - univs: &univs, - mut_ctx: vec![], - arena: &arena, - names: &names, - lvls: vec![mk_name("u"), mk_name("v")], - univ_patches: FxHashMap::default(), - meta_univs: &[], - local_refs: None, - synth_counter: Cell::new(0), - }; - let mut intern = InternTable::::new(); - let ixon_env = IxonEnv::new(); - let mut cache = ExprCache::::default(); - let mut univ_cache = UnivCache::::default(); - let mut stats = ConvertStats::default(); - let k = ingress_expr( - &IxonExpr::sort(0), - root, - &ctx, - &mut intern, - (&ixon_env).into(), - &mut cache, - &mut univ_cache, - &mut stats, - ) - .unwrap(); - let decor = Meta::meta_get(k.univ_decor()).cloned().flatten(); - assert_eq!( - decor, None, - "canonical entry {canon:?} must ingress without decoration" - ); - let ExprData::Sort(zu, _) = k.data() else { - panic!("expected Sort"); - }; - assert!( - ixon_matches_kuniv(&canon, zu), - "canonical entry {canon:?} must ingress as identity" - ); - } - } - - /// Const-occurrence patches carry the FULL level-arg list: virtual - /// indices resolve extension spellings, primary indices ride along - /// for unchanged args. - #[test] - fn patch_sourced_const_decoration_full_list() { - let canon0 = IxonUniv::max( - IxonUniv::succ(IxonUniv::var(0)), - IxonUniv::succ(IxonUniv::var(1)), - ); - let canon1 = IxonUniv::var(0); - let orig0 = - IxonUniv::succ(IxonUniv::max(IxonUniv::var(0), IxonUniv::var(1))); - let univs = vec![canon0, canon1.clone()]; - let meta_univs = vec![orig0.clone()]; - let ref_name = mk_name("Target"); - let ref_name_addr = lean_name_to_addr(&ref_name); - let ref_addr = Address::hash(b"target-content"); - let mut names = FxHashMap::default(); - names.insert(ref_name_addr.clone(), ref_name.clone()); - let mut arena = ExprMeta::default(); - let root = arena.alloc(ExprMetaData::Ref { name: ref_name_addr }); - // Arg 0 patched (virtual 2 = univs.len() 2 + slot 0), arg 1 - // unchanged (primary 1). - let patch_idxs: Vec = vec![2, 1]; - let mut univ_patches: FxHashMap = FxHashMap::default(); - univ_patches.insert(root, patch_idxs.as_slice()); - let refs = vec![ref_addr.clone()]; - let ctx = Ctx:: { - sharing: &[], - refs: &refs, - univs: &univs, - mut_ctx: vec![], - arena: &arena, - names: &names, - lvls: vec![mk_name("u"), mk_name("v")], - univ_patches, - meta_univs: &meta_univs, - local_refs: None, - synth_counter: Cell::new(0), - }; - let mut intern = InternTable::::new(); - let ixon_env = IxonEnv::new(); - let mut cache = ExprCache::::default(); - let mut univ_cache = UnivCache::::default(); - let mut stats = ConvertStats::default(); - let k = ingress_expr( - &IxonExpr::reference(0, vec![0, 1]), - root, - &ctx, - &mut intern, - (&ixon_env).into(), - &mut cache, - &mut univ_cache, - &mut stats, - ) - .unwrap(); - let decor = Meta::meta_get(k.univ_decor()).cloned().flatten(); - let Some(UnivDecor::Const(spellings)) = decor else { - panic!("expected Const decoration, got {decor:?}"); - }; - assert_eq!(spellings.len(), 2); - assert_eq!(spellings[0], orig0); - assert_eq!(spellings[1], canon1); - } - - #[test] - fn ingress_cache_differentiates_by_param_names() { - let mut env = KEnv::::new(); - // Same Lean expression, but different param names should produce - // different cache keys and (for Param-containing exprs) different - // KExprs. - let u_name = mk_name("u"); - let v_name = mk_name("v"); - let e = LeanExpr::sort(Level::param(u_name.clone())); - let k1 = lean_expr_to_zexpr_with_kenv( - &e, - std::slice::from_ref(&u_name), - &mut env, - None, - None, - ) - .unwrap(); - let k2 = lean_expr_to_zexpr_with_kenv( - &e, - &[v_name, u_name.clone()], - &mut env, - None, - None, - ) - .unwrap(); - // In the first, Param(u) has index 0; in the second, Param(u) has index 1. - let i1 = match k1.data() { - ExprData::Sort(u, _) => match u.data() { - UnivData::Param(i, _, _) => *i, - _ => panic!(), - }, - _ => panic!(), - }; - let i2 = match k2.data() { - ExprData::Sort(u, _) => match u.data() { - UnivData::Param(i, _, _) => *i, - _ => panic!(), - }, - _ => panic!(), - }; - assert_eq!(i1, 0); - assert_eq!(i2, 1); - } - - fn cached_for_frames( - e: &LeanExpr, - binders: &mut Vec, - kenv: &mut KEnv, - ) -> Result, String> { - lean_expr_to_zexpr_cached( - e, - &[], - binders, - &mut kenv.intern, - None, - None, - Some(&mut kenv.ingress_cache), - Some(¶m_names_hash(&[])), - ) - } - - fn frame_binder(kind: u8, dom: LeanExpr, body: LeanExpr) -> LeanExpr { - let name = mk_name("inner"); - let value = LeanExpr::lit(env::Literal::NatVal(n_lit(42))); - match kind { - 0 => LeanExpr::lam(name, dom, body, BinderInfo::Implicit), - 1 => LeanExpr::all(name, dom, body, BinderInfo::Implicit), - _ => LeanExpr::letE(name, dom, value, body, true), - } - } - - #[test] - fn lean_frames_restore_context_and_keep_successful_children() { - let good = LeanExpr::sort(Level::zero()); - let bad = frame_binder(0, good.clone(), LeanExpr::fvar(mk_name("bad"))); - let key = |e: &LeanExpr| (*e.get_hash(), param_names_hash(&[])); - for kind in 0..3 { - for failure in 0..3 { - let mut kenv = KEnv::::new(); - let prefix = vec![mk_name("outer")]; - let mut binders = prefix.clone(); - let dom = if failure == 0 { bad.clone() } else { good.clone() }; - let body = - if failure == 1 { bad.clone() } else { LeanExpr::bvar(n_lit(0)) }; - let e = frame_binder(kind, dom, body); - let result = cached_for_frames(&e, &mut binders, &mut kenv); - assert_eq!(binders, prefix); - if failure < 2 { - assert_eq!( - result.unwrap_err(), - "unexpected FVar(bad) in elaborated kernel term during ingress" - ); - assert!(kenv.ingress_cache.contains_key(&key(&good))); - assert!(!kenv.ingress_cache.contains_key(&key(&e))); - assert!(!kenv.ingress_cache.contains_key(&key(&bad))); - } else { - let k = result.unwrap(); - let body = match k.data() { - ExprData::Lam(n, bi, _, _, b, _) - | ExprData::All(n, bi, _, _, _, b, _) => { - assert_eq!(n, &mk_name("inner")); - assert_eq!(bi, &BinderInfo::Implicit); - b - }, - ExprData::Let(n, _, _, b, nd, _) => { - assert_eq!(n, &mk_name("inner")); - assert!(nd.non_dep); - b - }, - _ => panic!("expected binder"), - }; - assert!( - matches!(body.data(), ExprData::Var(0, n, _) if n == &mk_name("inner")) - ); - } - } - } - let mut binders = vec![mk_name("outer"), mk_name("middle")]; - let prefix = binders.clone(); - let e = LeanExpr::letE( - mk_name("inner"), - LeanExpr::bvar(n_lit(1)), - LeanExpr::bvar(n_lit(0)), - LeanExpr::bvar(n_lit(2)), - true, - ); - let k = cached_for_frames(&e, &mut binders, &mut KEnv::new()).unwrap(); - assert_eq!(binders, prefix); - let ExprData::Let(_, ty, val, body, _, _) = k.data() else { panic!() }; - for (e, expected) in [(ty, "outer"), (val, "middle"), (body, "outer")] { - assert!( - matches!(e.data(), ExprData::Var(_, n, _) if n == &mk_name(expected)) - ); - } - assert!( - ty.addr() < val.addr() - && val.addr() < body.addr() - && body.addr() < k.addr() - ); - } - - #[test] - fn lean_frames_metadata_cache_and_allocation_order() { - let layer = |s| vec![(mk_name(s), env::DataValue::OfBool(true))]; - let child = LeanExpr::mdata( - layer("child"), - LeanExpr::lit(env::Literal::NatVal(n_lit(42))), - ); - let core = - LeanExpr::app(child, LeanExpr::lit(env::Literal::StrVal("tail".into()))); - let inner = LeanExpr::mdata(layer("inner"), core.clone()); - let e = LeanExpr::mdata(layer("outer"), inner.clone()); - let mut kenv = KEnv::new(); - let mut binders = vec![]; - let k = cached_for_frames(&e, &mut binders, &mut kenv).unwrap(); - assert_eq!(k.mdata(), &vec![layer("outer"), layer("inner")]); - let ExprData::App(f, a, _) = k.data() else { panic!() }; - assert_eq!(f.mdata(), &vec![layer("child")]); - assert!(matches!(f.data(), ExprData::Nat(n, _, _) if n == &n_lit(42))); - assert!(matches!(a.data(), ExprData::Str(s, _, _) if s == "tail")); - assert!(f.addr() < a.addr() && a.addr() < k.addr()); - let key = |e: &LeanExpr| (*e.get_hash(), param_names_hash(&[])); - assert_eq!(kenv.ingress_cache.len(), 3); - assert!(!kenv.ingress_cache.contains_key(&key(&core))); - assert!(!kenv.ingress_cache.contains_key(&key(&inner))); - assert!(k.ptr_eq(&cached_for_frames(&e, &mut binders, &mut kenv).unwrap())); - assert_eq!(kenv.ingress_cache.len(), 3); - let var = LeanExpr::bvar(n_lit(0)); - binders.push(mk_name("first")); - let first = cached_for_frames(&var, &mut binders, &mut kenv).unwrap(); - binders[0] = mk_name("second"); - let second = cached_for_frames(&var, &mut binders, &mut kenv).unwrap(); - assert!(first.ptr_eq(&second)); - assert!( - matches!(second.data(), ExprData::Var(0, n, _) if n == &mk_name("first")) - ); - assert_eq!(binders, vec![mk_name("second")]); - } - - #[test] - fn lean_frames_error_precedence() { - let first = LeanExpr::fvar(mk_name("first")); - let second = LeanExpr::fvar(mk_name("second")); - let let_e = - |ty, val, body| LeanExpr::letE(mk_name("x"), ty, val, body, false); - for e in [ - LeanExpr::app(first.clone(), second.clone()), - let_e(first.clone(), second.clone(), second.clone()), - let_e(LeanExpr::sort(Level::zero()), first.clone(), second), - LeanExpr::proj(mk_name("Prod"), past_u64(), first), - ] { - assert_eq!( - try_ingress(&e, &[]).unwrap_err(), - "unexpected FVar(first) in elaborated kernel term during ingress" - ); - } - } - - #[test] - fn lean_frames_deep_bodies_and_staged_teardown() { - for kind in 0..3 { - let mut e = LeanExpr::bvar(n_lit(0)); - for _ in 0..500 { - e = match kind { - 0 => frame_binder(0, LeanExpr::sort(Level::zero()), e), - 1 => frame_binder(2, LeanExpr::sort(Level::zero()), e), - _ => LeanExpr::proj(mk_name("Prod"), n_lit(0), e), - }; - } - let mut kenv = KEnv::new(); - let k = cached_for_frames(&e, &mut vec![], &mut kenv).unwrap(); - drop(k); - drop(kenv); - while let LeanExprData::Lam(_, _, b, _, _) - | LeanExprData::LetE(_, _, _, b, _, _) - | LeanExprData::Proj(_, _, b, _) = e.as_data() - { - let next = b.clone(); - drop(e); - e = next; - } - drop(e); - } - } - - // ---- build_ingress_lookups ---- - - #[test] - fn build_ingress_lookups_on_empty_env() { - let ie = IxonEnv::new(); - let (name_map, addr_map) = build_ingress_lookups(&ie); - assert!(name_map.is_empty()); - assert!(addr_map.is_empty()); - } - - #[test] - fn build_ingress_lookups_inverts_name_table() { - let ie = IxonEnv::new(); - let nat_name = mk_name("Nat"); - let nat_addr = lean_name_to_addr(&nat_name); - ie.names.insert(nat_addr.clone(), nat_name.clone()); - - let list_name = mk_name("List"); - let list_addr = Address::hash(b"arbitrary"); - ie.named.insert( - list_name.clone(), - ixon::env::Named::with_addr(list_addr.clone()), - ); - - let (name_map, addr_map) = build_ingress_lookups(&ie); - assert_eq!(name_map.get(&nat_addr), Some(&nat_name)); - assert_eq!(addr_map.get(&list_name), Some(&list_addr)); - } - - // ---- Anon-mode determinism ---- - - #[test] - fn anon_proj_addrs_are_deterministic() { - let block = Address::hash(b"test-block-deterministic"); - // Same inputs → same outputs. - assert_eq!(anon_defn_proj_addr(&block, 0), anon_defn_proj_addr(&block, 0)); - assert_eq!(anon_indc_proj_addr(&block, 1), anon_indc_proj_addr(&block, 1)); - assert_eq!(anon_recr_proj_addr(&block, 2), anon_recr_proj_addr(&block, 2)); - assert_eq!( - anon_ctor_proj_addr(&block, 3, 4), - anon_ctor_proj_addr(&block, 3, 4) - ); - // Different inputs → different outputs (catches accidental aliasing). - assert_ne!(anon_defn_proj_addr(&block, 0), anon_defn_proj_addr(&block, 1)); - assert_ne!(anon_defn_proj_addr(&block, 0), anon_indc_proj_addr(&block, 0)); - assert_ne!( - anon_ctor_proj_addr(&block, 0, 0), - anon_ctor_proj_addr(&block, 0, 1) - ); - assert_ne!( - anon_ctor_proj_addr(&block, 0, 0), - anon_ctor_proj_addr(&block, 1, 0) - ); - } - - #[test] - fn anon_proj_addr_matches_constant_commit() { - // The helper must agree with the explicit `Constant::commit` over - // the synthesized projection — that's the contract the rest of the - // anon pipeline depends on (verifying the computed address against - // the address actually stored in `env.consts`). - use ixon::constant::{ - Constant, ConstantInfo, ConstructorProj, DefinitionProj, InductiveProj, - RecursorProj, - }; - let b = Address::hash(b"another-block"); - let (defn_addr, _) = Constant::new(ConstantInfo::DPrj(DefinitionProj { - idx: 5, - block: b.clone(), - })) - .commit(); - assert_eq!(defn_addr, anon_defn_proj_addr(&b, 5)); - let (indc_addr, _) = Constant::new(ConstantInfo::IPrj(InductiveProj { - idx: 7, - block: b.clone(), - })) - .commit(); - assert_eq!(indc_addr, anon_indc_proj_addr(&b, 7)); - let (recr_addr, _) = Constant::new(ConstantInfo::RPrj(RecursorProj { - idx: 9, - block: b.clone(), - })) - .commit(); - assert_eq!(recr_addr, anon_recr_proj_addr(&b, 9)); - let (ctor_addr, _) = Constant::new(ConstantInfo::CPrj(ConstructorProj { - idx: 2, - cidx: 3, - block: b.clone(), - })) - .commit(); - assert_eq!(ctor_addr, anon_ctor_proj_addr(&b, 2, 3)); - } -} diff --git a/crates/kernel/src/input.rs b/crates/kernel/src/input.rs deleted file mode 100644 index 8eae13bda..000000000 --- a/crates/kernel/src/input.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! Read-only input used by ingress. A bound artifact never materializes a -//! caller's Constant cache; every semantic read crosses its raw-source gate. - -use std::cell::RefCell; -use std::sync::Arc; - -use ix_common::address::Address; -use ix_common::env::{Name, ReducibilityHints}; -use ixon::constant::Constant; -use ixon::env::{Env, Named}; - -#[derive(Clone, Copy)] -pub(crate) enum Input<'a> { - Legacy(&'a Env), - #[cfg(not(target_arch = "riscv64"))] - Artifact(&'a crate::role_admission::ArtifactSource), -} - -impl<'a> From<&'a Env> for Input<'a> { - fn from(env: &'a Env) -> Self { - Self::Legacy(env) - } -} - -impl Input<'_> { - /// A reference from a declaration being read must name an authentic - /// carried record, not merely a present key (see - /// `ArtifactSource::authenticate_reference`). - pub(crate) fn require_reference( - self, - address: &Address, - ) -> Result<(), String> { - #[cfg(not(target_arch = "riscv64"))] - if let Self::Artifact(source) = self { - return source - .authenticate_reference(address) - .map_err(|error| format!("reference {}: {error}", address.hex())); - } - let _ = address; - Ok(()) - } - pub(crate) fn constant( - self, - address: &Address, - ) -> Result>, String> { - match self { - Self::Legacy(env) => { - env.consts.get(address).map(|entry| entry.get_at(address)).transpose() - }, - #[cfg(not(target_arch = "riscv64"))] - Self::Artifact(source) => match source.record(address) { - Ok(record) => Ok(Some(Arc::new(record.into_constant()))), - Err(crate::role_admission::Error::MissingRecord(_)) => Ok(None), - Err(error) => Err(error.to_string()), - }, - } - } - - pub(crate) fn blob( - self, - address: &Address, - ) -> Result>, String> { - match self { - Self::Legacy(env) => Ok(env.get_blob(address)), - #[cfg(not(target_arch = "riscv64"))] - Self::Artifact(source) => match source.blob(address) { - Ok(blob) => Ok(Some(blob.bytes().to_vec())), - Err(crate::role_admission::Error::MissingBlob(_)) => Ok(None), - Err(error) => Err(error.to_string()), - }, - } - } - - pub(crate) fn contains(self, address: &Address) -> bool { - match self { - Self::Legacy(env) => env.consts.contains_key(address), - #[cfg(not(target_arch = "riscv64"))] - Self::Artifact(source) => source.contains_record(address), - } - } - - pub(crate) fn named(self, name: &Name) -> Option { - match self { - Self::Legacy(env) => env.lookup_name(name), - #[cfg(not(target_arch = "riscv64"))] - Self::Artifact(source) => source.named(name), - } - } - - pub(crate) fn hint(self, address: &Address) -> Option { - match self { - Self::Legacy(env) => env.anon_hints.get(address).map(|entry| *entry), - #[cfg(not(target_arch = "riscv64"))] - Self::Artifact(source) => source.hint(address), - } - } - - pub(crate) fn mdata( - self, - map: &ixon::metadata::KVMap, - ) -> Result { - struct Reader<'a> { - input: Input<'a>, - error: RefCell>, - } - impl ixon::metadata::MetadataSource for Reader<'_> { - fn get_blob(&self, address: &Address) -> Option> { - match self.input.blob(address) { - Ok(bytes) => bytes, - Err(error) => { - self.error.borrow_mut().get_or_insert(error); - None - }, - } - } - fn get_name(&self, address: &Address) -> Option { - match self.input { - Input::Legacy(env) => env.get_name(address), - #[cfg(not(target_arch = "riscv64"))] - Input::Artifact(source) => source.name(address), - } - } - } - let reader = Reader { input: self, error: RefCell::new(None) }; - let data = ixon::metadata::resolve_kvmap(map, &reader); - match reader.error.into_inner() { - Some(error) => Err(error), - None => Ok(data), - } - } -} diff --git a/crates/kernel/src/intern_reference.rs b/crates/kernel/src/intern_reference.rs deleted file mode 100644 index 31b460724..000000000 --- a/crates/kernel/src/intern_reference.rs +++ /dev/null @@ -1,178 +0,0 @@ -//! Frozen pre-memoization interner for differential tests and benchmarks. -//! Keep the traversal, rebuilding, and first-insert-wins behavior unchanged. - -use super::{InternTable, expr_key, univ_key}; -use crate::{expr::KExpr, level::KUniv, mode::KernelMode}; - -impl InternTable { - /// Intern a universe: returns the canonical value for its structural - /// identity, recursively canonicalizing children as needed so the - /// shallow key is meaningful. - pub(super) fn intern_univ_reference(&mut self, u: KUniv) -> KUniv { - use crate::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_reference(inner.clone()); - if ci.ptr_eq(inner) { - u - } else { - KUniv::new(UnivData::Succ(ci, crate::expr::fresh_uid())) - } - }, - UnivData::Max(a, b, _) => { - let ca = self.intern_univ_reference(a.clone()); - let cb = self.intern_univ_reference(b.clone()); - if ca.ptr_eq(a) && cb.ptr_eq(b) { - u - } else { - KUniv::new(UnivData::Max(ca, cb, crate::expr::fresh_uid())) - } - }, - UnivData::IMax(a, b, _) => { - let ca = self.intern_univ_reference(a.clone()); - let cb = self.intern_univ_reference(b.clone()); - if ca.ptr_eq(a) && cb.ptr_eq(b) { - u - } else { - KUniv::new(UnivData::IMax(ca, cb, crate::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.canon_univs.insert(*u.addr()); - self.univs.insert(key, u.clone()); - u - } - - /// 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(super) fn intern_expr_reference(&mut self, e: KExpr) -> KExpr { - use crate::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_reference(un.clone()); - if cu.ptr_eq(un) { - e - } else { - // Child canonicalization only — same semantic level, same - // occurrence: the spelling decoration rides along. - KExpr::sort_full(cu, e.mdata().clone(), e.univ_decor().clone()) - } - }, - ExprData::Const(id, us, _) => { - let cus: Box<[KUniv]> = - us.iter().map(|un| self.intern_univ_reference(un.clone())).collect(); - if cus.iter().zip(us.iter()).all(|(a, b)| a.ptr_eq(b)) { - e - } else { - KExpr::cnst_full( - id.clone(), - cus, - e.mdata().clone(), - e.univ_decor().clone(), - ) - } - }, - ExprData::App(f, a, _) => { - let cf = self.intern_expr_reference(f.clone()); - let ca = self.intern_expr_reference(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, contract, t, b, _) => { - let ct = self.intern_expr_reference(t.clone()); - let cb = self.intern_expr_reference(b.clone()); - if ct.ptr_eq(t) && cb.ptr_eq(b) { - e - } else { - KExpr::lam_contract_mdata( - n.clone(), - bi.clone(), - *contract, - ct, - cb, - e.mdata().clone(), - ) - } - }, - ExprData::All(n, bi, contract, result, t, b, _) => { - let ct = self.intern_expr_reference(t.clone()); - let cb = self.intern_expr_reference(b.clone()); - if ct.ptr_eq(t) && cb.ptr_eq(b) { - e - } else { - KExpr::all_contract_mdata( - n.clone(), - bi.clone(), - *contract, - *result, - ct, - cb, - e.mdata().clone(), - ) - } - }, - ExprData::Let(n, t, v, b, contract, _) => { - let ct = self.intern_expr_reference(t.clone()); - let cv = self.intern_expr_reference(v.clone()); - let cb = self.intern_expr_reference(b.clone()); - if ct.ptr_eq(t) && cv.ptr_eq(v) && cb.ptr_eq(b) { - e - } else { - KExpr::let_contract_mdata( - n.clone(), - *contract, - ct, - cv, - cb, - e.mdata().clone(), - ) - } - }, - ExprData::Prj(id, f, v, _) => { - let cv = self.intern_expr_reference(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.canon_exprs.insert(*e.addr()); - self.exprs.insert(key, e.clone()); - e - } -} diff --git a/crates/kernel/src/intern_tests.rs b/crates/kernel/src/intern_tests.rs deleted file mode 100644 index 61f09321a..000000000 --- a/crates/kernel/src/intern_tests.rs +++ /dev/null @@ -1,611 +0,0 @@ -//! Differential semantics and DAG-work bounds for the kernel interner. - -use super::*; -use crate::expr::{ExprData, FVarId, MData, fresh_uid}; -use crate::level::UnivData; -use crate::mode::{Anon, Meta}; -use crate::profile::take_op_counts; -use bignat::Nat; -use ix_common::env::{BinderInfo, DataValue, Name}; -use ixon::univ::Univ; - -fn name(s: &str) -> Name { - Name::str(Name::anon(), s.to_owned()) -} - -fn metadata(s: &str) -> Vec { - vec![vec![(name("tag"), DataValue::OfString(s.to_owned()))]] -} - -// Unlike KExpr/KUniv equality, these comparisons include ALL metadata and -// cached annotations, ignoring only fresh ephemeral uids. Compare DAGs without -// expanding shared subtrees or formatting an enormous expression on failure. -fn assert_same_univ(a: &KUniv, b: &KUniv) { - let mut pending = vec![(a, b)]; - let mut seen = FxHashSet::default(); - while let Some((a, b)) = pending.pop() { - if !seen.insert(( - std::ptr::from_ref(a.data()).addr(), - std::ptr::from_ref(b.data()).addr(), - )) { - continue; - } - match (a.data(), b.data()) { - (UnivData::Zero(_), UnivData::Zero(_)) => {}, - (UnivData::Param(i, n, _), UnivData::Param(j, m, _)) => { - assert_eq!(i, j); - assert_eq!(n, m); - }, - (UnivData::Succ(a, _), UnivData::Succ(b, _)) => pending.push((a, b)), - (UnivData::Max(a, b, _), UnivData::Max(c, d, _)) - | (UnivData::IMax(a, b, _), UnivData::IMax(c, d, _)) => { - pending.extend([(a, c), (b, d)]); - }, - _ => panic!("universe variants differ"), - } - } -} - -fn assert_same_expr(a: &KExpr, b: &KExpr) { - let mut pending = vec![(a, b)]; - let mut seen = FxHashSet::default(); - while let Some((a, b)) = pending.pop() { - if !seen.insert(( - std::ptr::from_ref(a.data()).addr(), - std::ptr::from_ref(b.data()).addr(), - )) { - continue; - } - assert_eq!(a.lbr(), b.lbr()); - assert_eq!(a.count_0(), b.count_0()); - assert_eq!(a.info().has_fvars, b.info().has_fvars); - assert_eq!(a.mdata(), b.mdata()); - assert_eq!(a.univ_decor(), b.univ_decor()); - match (a.data(), b.data()) { - (ExprData::Var(i, n, _), ExprData::Var(j, m, _)) => { - assert_eq!(i, j); - assert_eq!(n, m); - }, - (ExprData::FVar(i, n, _), ExprData::FVar(j, m, _)) => { - assert_eq!(i, j); - assert_eq!(n, m); - }, - (ExprData::Sort(u, _), ExprData::Sort(v, _)) => assert_same_univ(u, v), - (ExprData::Const(i, us, _), ExprData::Const(j, vs, _)) => { - assert_eq!(i, j); - assert_eq!(us.len(), vs.len()); - for (u, v) in us.iter().zip(vs) { - assert_same_univ(u, v); - } - }, - (ExprData::App(f, a, _), ExprData::App(g, b, _)) => { - pending.extend([(f, g), (a, b)]); - }, - (ExprData::Lam(n, bi, _, t, b, _), ExprData::Lam(m, bj, _, u, c, _)) - | ( - ExprData::All(n, bi, _, _, t, b, _), - ExprData::All(m, bj, _, _, u, c, _), - ) => { - assert_eq!(n, m); - assert_eq!(bi, bj); - pending.extend([(t, u), (b, c)]); - }, - (ExprData::Let(n, t, v, b, nd, _), ExprData::Let(m, u, w, c, md, _)) => { - assert_eq!(n, m); - assert_eq!(nd, md); - pending.extend([(t, u), (v, w), (b, c)]); - }, - (ExprData::Prj(i, f, v, _), ExprData::Prj(j, g, w, _)) => { - assert_eq!(i, j); - assert_eq!(f, g); - pending.push((v, w)); - }, - (ExprData::Nat(n, a, _), ExprData::Nat(m, b, _)) => { - assert_eq!(n, m); - assert_eq!(a, b); - }, - (ExprData::Str(s, a, _), ExprData::Str(t, b, _)) => { - assert_eq!(s, t); - assert_eq!(a, b); - }, - _ => panic!("expression variants differ"), - } - } -} - -fn expr_dag(mut leaf: KExpr, depth: usize) -> KExpr { - for _ in 0..depth { - leaf = KExpr::all( - M::meta_field(name("x")), - M::meta_field(BinderInfo::Default), - leaf.clone(), - leaf, - ); - } - leaf -} - -fn univ_dag(mut leaf: KUniv, depth: usize) -> KUniv { - for _ in 0..depth { - // Use raw constructors: interning must preserve even unsimplified Max. - leaf = KUniv::new(UnivData::Max(leaf.clone(), leaf, fresh_uid())); - } - leaf -} - -fn fixtures(label: &str) -> Vec> { - let n = M::meta_field(name(label)); - let md = M::meta_field(metadata(label)); - let id = KId::new(Address::hash(b"constant"), n.clone()); - let u = KUniv::param(0, n.clone()); - let v = KUniv::param(1, n.clone()); - let levels = vec![ - KUniv::zero(), - KUniv::succ(u.clone()), - KUniv::new(UnivData::Max(u.clone(), v.clone(), fresh_uid())), - KUniv::new(UnivData::IMax(v, u.clone(), fresh_uid())), - u, - ]; - let var = KExpr::var_mdata(0, n.clone(), md.clone()); - let fvar = KExpr::fvar_mdata(FVarId(7), n.clone(), md.clone()); - let cnst = KExpr::cnst_mdata(id.clone(), levels.clone().into(), md.clone()); - let shared = KExpr::app_mdata(cnst.clone(), fvar.clone(), md.clone()); - let mut roots = vec![ - var.clone(), - fvar, - cnst, - shared.clone(), - KExpr::prj_mdata(id, 2, shared.clone(), md.clone()), - KExpr::nat_mdata(Nat::from(17u64), Address::hash(b"17"), md.clone()), - KExpr::str_mdata("hello".to_owned(), Address::hash(b"hello"), md.clone()), - expr_dag(shared.clone(), 6), - ]; - for level in levels { - roots.push(KExpr::sort_mdata(level, md.clone())); - } - for bi in [ - BinderInfo::Default, - BinderInfo::Implicit, - BinderInfo::StrictImplicit, - BinderInfo::InstImplicit, - ] { - roots.push(KExpr::lam_mdata( - n.clone(), - M::meta_field(bi.clone()), - shared.clone(), - var.clone(), - md.clone(), - )); - roots.push(KExpr::all_mdata( - n.clone(), - M::meta_field(bi), - shared.clone(), - var.clone(), - md.clone(), - )); - } - for non_dep in [false, true] { - roots.push(KExpr::let_mdata( - n.clone(), - shared.clone(), - var.clone(), - shared.clone(), - non_dep, - md.clone(), - )); - } - roots -} - -fn differential() { - for seeded in [false, true] { - let mut old = InternTable::::new(); - let mut new = InternTable::::new(); - if seeded { - for seed in fixtures("first") { - old.intern_expr_reference(seed.clone()); - new.intern_expr(seed); - } - } - for root in fixtures("second") { - let expected = old.intern_expr_reference(root.clone()); - let actual = new.intern_expr(root.clone()); - assert_same_expr(&actual, &expected); - assert!(new.intern_expr(root).ptr_eq(&actual)); - take_op_counts(); - assert!(new.intern_expr(actual.clone()).ptr_eq(&actual)); - assert_eq!(take_op_counts().intern_nodes, 1); - } - assert_eq!(new.exprs.len(), old.exprs.len()); - assert_eq!(new.univs.len(), old.univs.len()); - assert_eq!(new.canon_exprs.len(), old.canon_exprs.len()); - assert_eq!(new.canon_univs.len(), old.canon_univs.len()); - } -} - -#[test] -fn interning_matches_reference_in_both_modes_and_seeded_tables() { - differential::(); - differential::(); -} - -fn smart_constructor_differential() { - let mut old = KEnv::::new(); - let mut new = KEnv::::new(); - // Reusing old inputs after clearing must not reuse stale canonical keys. - let inputs = fixtures::("input"); - for seeded in [false, true, false] { - old.clear_releasing_memory(); - new.clear_releasing_memory(); - if seeded { - for seed in fixtures::("first") { - old.intern.intern_expr_reference(seed.clone()); - new.intern.intern_expr(seed); - } - } - for pair in inputs.windows(2) { - let (a, b) = (&pair[0], &pair[1]); - // Seed parent occurrences too: their metadata must win on a hit, - // including when the new constructor supplies different binder info. - if seeded { - for root in [ - KExpr::app_mdata( - a.clone(), - b.clone(), - M::meta_field(metadata("parent")), - ), - KExpr::all_mdata( - M::meta_field(name("parent")), - M::meta_field(BinderInfo::Implicit), - a.clone(), - b.clone(), - M::meta_field(metadata("parent")), - ), - ] { - old.intern.intern_expr_reference(root.clone()); - new.intern.intern_expr(root); - } - } - for _ in 0..2 { - let expected = - old.intern.intern_expr_reference(KExpr::app(a.clone(), b.clone())); - let actual = new.intern.intern_app(a, b); - assert_same_expr(&actual, &expected); - assert!(new.intern.intern_expr(actual.clone()).ptr_eq(&actual)); - let n = M::meta_field(name("second")); - let bi = M::meta_field(BinderInfo::InstImplicit); - let expected = old.intern.intern_expr_reference(KExpr::all( - n.clone(), - bi.clone(), - a.clone(), - b.clone(), - )); - let actual = new.intern.intern_all(n, bi, a, b); - assert_same_expr(&actual, &expected); - } - } - assert_eq!(new.intern.exprs.len(), old.intern.exprs.len()); - assert_eq!(new.intern.univs.len(), old.intern.univs.len()); - } -} - -#[test] -fn smart_constructors_match_reference_with_metadata_and_resets() { - smart_constructor_differential::(); - smart_constructor_differential::(); -} - -#[test] -fn smart_constructor_hit_does_not_invoke_node_factory() { - let mut intern = InternTable::::new(); - let a = intern.intern_expr(KExpr::var(0, ())); - let b = intern.intern_expr(KExpr::var(1, ())); - let key = ExprKey::App(*a.addr(), *b.addr()); - let app = intern.intern_expr_with(key.clone(), || KExpr::app(a, b)); - let hit = intern.intern_expr_with(key, || panic!("allocated on hit")); - assert!(app.ptr_eq(&hit)); - assert!(intern.canon_exprs.contains(app.addr())); -} - -#[test] -fn smart_constructor_canonicalizes_shared_noncanonical_children_once() { - let mut intern = InternTable::::new(); - intern.intern_expr(KExpr::sort(KUniv::zero())); - let input = expr_dag(KExpr::sort(KUniv::zero()), 60); - take_op_counts(); - let app = intern.intern_app(&input, &input); - assert_eq!(take_op_counts().intern_nodes, 124); - let ExprData::App(a, b, _) = app.data() else { panic!("app") }; - assert!(a.ptr_eq(b)); - assert!(!intern.canon_exprs.contains(input.addr())); -} - -#[test] -fn smart_constructor_preserves_same_uid_occurrence_metadata() { - let a = KExpr::::var(0, name("a")); - let mut info = a.info().clone(); - info.mdata = metadata("b"); - let b = KExpr::new(ExprData::Var(0, name("b"), info)); - let expected = - InternTable::new().intern_expr_reference(KExpr::app(a.clone(), b.clone())); - let actual = InternTable::new().intern_app(&a, &b); - assert_same_expr(&actual, &expected); - let ExprData::App(_, rhs, _) = actual.data() else { panic!("app") }; - assert!(rhs.ptr_eq(&b)); -} - -#[test] -fn constant_level_buffer_preserves_unchanged_prefix_and_decorations() { - for first_change in 0..=4 { - let mut old = InternTable::::new(); - let mut new = InternTable::::new(); - let mut levels = Vec::new(); - for i in 0..4 { - let seed = KUniv::param(i, name("first")); - old.intern_univ_reference(seed.clone()); - new.intern_univ(seed.clone()); - levels.push(if i < first_change { - seed - } else { - KUniv::param(i, name("second")) - }); - } - let root = KExpr::cnst_full( - KId::new(Address::hash(b"C"), name("C")), - levels.into(), - metadata("constant"), - Some(UnivDecor::Const(vec![Univ::zero(); 4].into())), - ); - let actual = new.intern_expr(root.clone()); - let expected = old.intern_expr_reference(root.clone()); - assert_same_expr(&actual, &expected); - assert_eq!(actual.ptr_eq(&root), first_change == 4); - } -} - -#[test] -#[ignore = "manual release benchmark; no wall-clock assertions"] -fn benchmark_smart_constructors() { - use std::{hint::black_box, time::Instant}; - for hits in [true, false] { - let count: usize = if hits { 200_000 } else { 30_000 }; - for smart in [false, true, true, false] { - let mut table = InternTable::::new(); - let head = table.intern_expr(KExpr::var(0, ())); - let inputs: Vec<_> = (0..if hits { 1 } else { count }) - .map(|i| { - table.intern_expr(KExpr::var(u64::try_from(i).unwrap() + 1, ())) - }) - .collect(); - if hits { - table.intern_app(&head, &inputs[0]); - } - let start = Instant::now(); - for i in 0..count { - let arg = inputs[if hits { 0 } else { i }].clone(); - black_box(if smart { - table.intern_app(&head, &arg) - } else { - table.intern_expr(KExpr::app(head.clone(), arg)) - }); - } - eprintln!( - "smart={smart} hits={hits} count={count} elapsed={:?}", - start.elapsed() - ); - } - } -} - -#[test] -fn shared_expression_dag_visits_edges_not_expanded_tree() { - let depth = 60; - let mut intern = InternTable::::new(); - intern.intern_expr(KExpr::sort(KUniv::zero())); - let root = expr_dag(KExpr::sort(KUniv::zero()), depth); - let mut previous: Option> = None; - for _ in 0..2 { - take_op_counts(); - let result = intern.intern_expr(root.clone()); - assert_eq!(take_op_counts().intern_nodes, (2 * depth + 2) as u64); - if let Some(previous) = previous.replace(result.clone()) { - assert!(previous.ptr_eq(&result)); - } - let mut cursor = &result; - for _ in 0..depth { - let ExprData::All(_, _, _, _, ty, body, _) = cursor.data() else { - panic!("all") - }; - assert!(ty.ptr_eq(body)); - cursor = ty; - } - assert_eq!(intern.exprs.len(), depth + 1); - assert!(!intern.canon_exprs.contains(root.addr())); - } -} - -#[test] -fn shared_universe_dag_is_memoized_across_constant_arguments() { - let depth = 60; - let mut intern = InternTable::::new(); - intern.intern_univ(KUniv::zero()); - let level = univ_dag(KUniv::zero(), depth); - take_op_counts(); - let canonical = intern.intern_univ(level.clone()); - assert_eq!(take_op_counts().intern_nodes, (2 * depth + 1) as u64); - let root = KExpr::cnst( - KId::new(Address::hash(b"C"), ()), - vec![level.clone(); 64].into(), - ); - take_op_counts(); - let result = intern.intern_expr(root); - assert_eq!(take_op_counts().intern_nodes, (2 * depth + 65) as u64); - let ExprData::Const(_, levels, _) = result.data() else { panic!("const") }; - assert!(levels.iter().all(|u| u.ptr_eq(&canonical))); - let mut cursor = &canonical; - for _ in 0..depth { - let UnivData::Max(a, b, _) = cursor.data() else { panic!("max") }; - assert!(a.ptr_eq(b)); - cursor = a; - } - assert_eq!(intern.univs.len(), depth + 1); - assert!(!intern.canon_univs.contains(level.addr())); -} - -#[test] -fn unchanged_inputs_use_canonical_fast_path_without_memo_entries() { - let mut intern = InternTable::::new(); - let root = expr_dag(KExpr::sort(KUniv::zero()), 40); - let mut memo = InternMemo::default(); - let result = intern.intern_expr_cached(&root, &mut memo); - assert!(result.ptr_eq(&root)); - assert_eq!(memo.exprs.capacity(), 0); - assert_eq!(memo.univs.capacity(), 0); -} - -#[test] -fn input_pointer_keys_preserve_same_uid_spelling_twins() { - for is_const in [false, true] { - let level = KUniv::zero(); - let a = if is_const { - KExpr::::cnst_mdata( - KId::new(Address::hash(b"C"), name("A")), - vec![level.clone()].into(), - metadata("A"), - ) - } else { - KExpr::sort_mdata(level.clone(), metadata("A")) - }; - let mut info = a.info().clone(); - info.mdata = metadata("B"); - let spelling = Univ::imax(Univ::succ(Univ::zero()), Univ::zero()); - let b = if is_const { - info.univ_decor = Some(UnivDecor::Const(vec![spelling].into())); - KExpr::new(ExprData::Const( - KId::new(Address::hash(b"C"), name("B")), - vec![level].into(), - info, - )) - } else { - info.univ_decor = Some(UnivDecor::Sort(spelling)); - KExpr::new(ExprData::Sort(level, info)) - }; - assert_eq!(a.addr(), b.addr()); - assert!(!a.ptr_eq(&b)); - let root = KExpr::app(a.clone(), KExpr::app(b, a)); - let mut old = InternTable::new(); - let mut new = InternTable::new(); - // Force both spelling occurrences to be rebuilt, so their shared input - // uid never becomes canonical and a uid-keyed memo would conflate them. - old.intern_univ_reference(KUniv::zero()); - new.intern_univ(KUniv::zero()); - let expected = old.intern_expr_reference(root.clone()); - let mut smart = InternTable::new(); - smart.intern_univ(KUniv::zero()); - let ExprData::App(f, a, _) = root.data() else { panic!("app") }; - assert_same_expr(&smart.intern_app(f, a), &expected); - let result = new.intern_expr(root); - assert_same_expr(&result, &expected); - let ExprData::App(a, rest, _) = result.data() else { panic!("app") }; - let ExprData::App(b, again, _) = rest.data() else { panic!("app") }; - assert!(!a.ptr_eq(b)); - assert!(a.ptr_eq(again)); - assert_eq!(a.mdata(), &metadata("A")); - assert_eq!(b.mdata(), &metadata("B")); - assert_ne!(a.univ_decor(), b.univ_decor()); - } -} - -#[test] -fn already_canonical_uid_fast_path_preserves_occurrence_metadata() { - let a = KExpr::::var(0, name("A")); - let mut info = a.info().clone(); - info.mdata = metadata("B"); - let b = KExpr::new(ExprData::Var(0, name("B"), info)); - let root = KExpr::app(a, b.clone()); - let expected = InternTable::new().intern_expr_reference(root.clone()); - let result = InternTable::new().intern_expr(root); - assert_same_expr(&result, &expected); - let ExprData::App(_, rhs, _) = result.data() else { panic!("app") }; - assert!(rhs.ptr_eq(&b)); -} - -#[test] -fn memo_does_not_survive_calls_or_environment_clears() { - let mut env = KEnv::::new(); - let leaf = KExpr::var(0, name("input")); - let root = expr_dag(leaf, 6); - for label in ["first", "second", "third"] { - env.clear(); - let seed = KExpr::var_mdata(0, name(label), metadata(label)); - env.intern.intern_expr(seed.clone()); - let result = env.intern.intern_expr(root.clone()); - let mut old = InternTable::new(); - old.intern_expr_reference(seed); - assert_same_expr(&result, &old.intern_expr_reference(root.clone())); - assert!(env.intern.intern_expr(root.clone()).ptr_eq(&result)); - } -} - -#[test] -#[ignore = "manual release benchmark; no wall-clock assertions"] -fn benchmark_shared_dag_interning() { - use std::time::Instant; - for depth in [12, 16, 20] { - let root = expr_dag(KExpr::sort(KUniv::zero()), depth); - let mut old = InternTable::::new(); - let mut new = InternTable::::new(); - old.intern_expr_reference(KExpr::sort(KUniv::zero())); - new.intern_expr(KExpr::sort(KUniv::zero())); - take_op_counts(); - let start = Instant::now(); - let expected = old.intern_expr_reference(root.clone()); - let old_time = start.elapsed(); - let old_visits = take_op_counts().intern_nodes; - let start = Instant::now(); - let result = new.intern_expr(root.clone()); - let new_time = start.elapsed(); - let new_visits = take_op_counts().intern_nodes; - assert_same_expr(&result, &expected); - eprintln!( - "depth={depth}: tree={old_time:?} ({old_visits} visits), \ - dag={new_time:?} ({new_visits} visits)" - ); - } -} - -#[test] -#[ignore = "manual release benchmark; no wall-clock assertions"] -fn benchmark_small_expression_interning() { - use std::hint::black_box; - use std::time::Instant; - for workload in ["canonical", "duplicate leaf", "duplicate app", "fresh app"] - { - for reference in [true, false] { - let intern: fn(&mut InternTable, KExpr) -> KExpr = - if reference { - InternTable::intern_expr_reference - } else { - InternTable::intern_expr - }; - let mut table = InternTable::new(); - let f = intern(&mut table, KExpr::var(1, ())); - let a = intern(&mut table, KExpr::var(0, ())); - let start = Instant::now(); - for i in 0..100_000 { - let input = match workload { - "canonical" => a.clone(), - "duplicate leaf" => KExpr::var(0, ()), - "duplicate app" => KExpr::app(f.clone(), a.clone()), - "fresh app" => { - let arg = intern(&mut table, KExpr::var(i + 2, ())); - KExpr::app(f.clone(), arg) - }, - _ => unreachable!(), - }; - black_box(intern(&mut table, input)); - } - eprintln!("{workload}, reference={reference}: {:?}", start.elapsed()); - } - } -} diff --git a/crates/kernel/src/ixon_checker.rs b/crates/kernel/src/ixon_checker.rs deleted file mode 100644 index 602a431dd..000000000 --- a/crates/kernel/src/ixon_checker.rs +++ /dev/null @@ -1,234 +0,0 @@ -//! Checking boundary for decoded Ixon environments. -//! -//! Callers decode .ixe bytes with Ixon before constructing this checker. -//! -//! The worker environment is private: every declaration enters through the -//! kernel's address-verified ingress, which checks local definition cycles -//! before admitting a block. External dependencies are Ixon content -//! addresses, so checking a declaration need not rewalk their expression trees. -//! As with `TypeChecker::check_ixon_const`, referenced declarations are assumptions; -//! check every work item to certify a complete environment. - -#[cfg(not(target_arch = "riscv64"))] -pub type IxonChecker = - crate::artifact_checker::ArtifactChecker; -#[cfg(not(target_arch = "riscv64"))] -pub use crate::artifact_checker::CheckStats; - -// The guest remains on its deferred legacy binding/dispatch interface. -#[cfg(target_arch = "riscv64")] -mod guest { - use ix_common::address::Address; - use ixon::env::Env; - - use crate::env::KEnv; - use crate::error::TcError; - use crate::id::KId; - use crate::mode::Anon; - use crate::profile::ProfileSink; - use crate::tc::TypeChecker; - - /// Diagnostics for the final member of the most recent block check. - #[derive(Default)] - pub struct CheckStats { - pub fuel_used: u64, - pub def_eq_peak: u32, - pub hot_misses: String, - } - - /// A worker whose kernel declarations come exclusively from Ixon ingress. - pub struct IxonChecker<'a> { - source: &'a Env, - kernel: KEnv, - last_check: CheckStats, - debug_label: Option, - } - - impl<'a> IxonChecker<'a> { - pub fn new(source: &'a Env) -> Self { - Self { - source, - kernel: KEnv::new(), - last_check: CheckStats::default(), - debug_label: None, - } - } - - /// Check one standalone declaration or the whole block containing `addr`. - pub fn check_const(&mut self, addr: &Address) -> Result<(), TcError> { - let mut checker = - TypeChecker::new_with_lazy_anon(&mut self.kernel, self.source); - checker.ixon_ingress = true; - if let Some(label) = &self.debug_label { - checker.set_debug_label(label.clone()); - } - let result = checker.check_ixon_const(&KId::new(addr.clone(), ())); - self.last_check = CheckStats { - fuel_used: checker.fuel_used(), - def_eq_peak: checker.def_eq_peak, - hot_misses: checker.hot_miss_summary(), - }; - checker.finish_constant_accounting(); - result - } - - pub fn last_check(&self) -> &CheckStats { - &self.last_check - } - - pub fn set_debug_label(&mut self, label: String) { - self.debug_label = Some(label); - } - - pub fn perf(&self) -> &crate::perf::PerfCounters { - &self.kernel.perf - } - - pub fn clear_releasing_memory(&mut self) { - self.kernel.clear_releasing_memory(); - } - - pub fn clear_with_capacity_limit(&mut self, max_capacity: usize) { - self.kernel.clear_with_capacity_limit(max_capacity); - } - - pub fn clear_reduction_caches(&mut self) { - self.kernel.clear_reduction_caches(); - } - - pub fn set_profile_sink(&mut self, sink: ProfileSink) { - self.kernel.profile_sink = Some(sink); - } - - pub fn take_profile_sink(&mut self) -> Option { - self.kernel.profile_sink.take() - } - } -} -#[cfg(target_arch = "riscv64")] -pub use guest::{CheckStats, IxonChecker}; - -#[cfg(test)] -mod tests { - use super::*; - use ix_common::address::Address; - use ix_common::env::DefinitionSafety; - use ixon::constant::{ - Constant, ConstantInfo, DefKind, Definition, MutConst, defn_proj_constant, - }; - use ixon::env::Env; - use ixon::expr::Expr; - use ixon::univ::Univ; - use std::sync::Arc; - - fn definition(value: Expr) -> Definition { - Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: Arc::new(Expr::Sort(1)), - value: Arc::new(value), - } - } - - fn store(env: &Env, constant: Constant) -> Address { - let addr = constant.commit().0; - env.store_const(addr.clone(), constant); - addr - } - - fn sorts(constant: &mut Constant) { - constant.univs = vec![Univ::zero(), Univ::succ(Univ::zero())]; - } - - fn loaded(env: &Env) -> Env { - let mut bytes = Vec::new(); - env.put(&mut bytes).unwrap(); - Env::get_anon(&mut bytes.as_slice()).unwrap() - } - - #[test] - fn ixon_check_keeps_external_dependencies_lazy() { - let env = Env::new(); - let mut base = Constant::new(ConstantInfo::Defn(definition(Expr::Sort(0)))); - sorts(&mut base); - let mut addr = store(&env, base); - for _ in 0..1024 { - let mut c = - Constant::new(ConstantInfo::Defn(definition(Expr::Ref(0, vec![])))); - sorts(&mut c); - c.refs.push(addr); - addr = store(&env, c); - } - let source = loaded(&env); - let mut checker = IxonChecker::new(&source); - checker.check_const(&addr).unwrap(); - // Only the root and its direct reference's type are needed. Regressing - // to a closure walk would materialize all 1025 declarations here. - assert_eq!(checker.kernel.consts.len(), 2); - } - - #[test] - fn ixon_admission_rejects_local_cycles_atomically() { - for cyclic in [false, true] { - let env = Env::new(); - let mut block = Constant::new(ConstantInfo::Muts(vec![ - MutConst::Defn(definition(Expr::Rec(1, vec![]))), - MutConst::Defn(definition(if cyclic { - Expr::Rec(0, vec![]) - } else { - Expr::Sort(0) - })), - ])); - sorts(&mut block); - let block_addr = store(&env, block); - let first = store(&env, defn_proj_constant(0, block_addr.clone())); - store(&env, defn_proj_constant(1, block_addr)); - let source = loaded(&env); - let mut checker = IxonChecker::new(&source); - let result = checker.check_const(&first); - if cyclic { - assert!( - result - .unwrap_err() - .to_string() - .contains("cyclic definition dependency") - ); - assert!(checker.kernel.consts.is_empty()); - assert!(checker.kernel.blocks.is_empty()); - } else { - result.unwrap(); - } - } - } - - #[test] - fn ixon_admission_verifies_in_memory_address_bindings() { - let source = Env::new(); - let mut c = Constant::new(ConstantInfo::Defn(definition(Expr::Sort(0)))); - sorts(&mut c); - let addr = Address::hash(b"wrong map key"); - source.store_const(addr.clone(), c); - assert!(IxonChecker::new(&source).check_const(&addr).is_err()); - } - - #[test] - fn ixon_admission_verifies_every_projection_before_publishing_a_block() { - let source = Env::new(); - let mut block = Constant::new(ConstantInfo::Muts(vec![ - MutConst::Defn(definition(Expr::Sort(0))), - MutConst::Defn(definition(Expr::Sort(0))), - ])); - sorts(&mut block); - let block_addr = store(&source, block); - let first = store(&source, defn_proj_constant(0, block_addr.clone())); - let sibling = defn_proj_constant(1, block_addr.clone()).commit().0; - // The requested projection and block are valid. Its sibling's bytes lie - // about the index, and must not be hidden by ingress of the whole block. - source.store_const(sibling, defn_proj_constant(0, block_addr)); - let source = loaded(&source); - let mut checker = IxonChecker::new(&source); - assert!(checker.check_const(&first).is_err()); - assert!(checker.kernel.consts.is_empty()); - } -} diff --git a/crates/kernel/src/lctx.rs b/crates/kernel/src/lctx.rs deleted file mode 100644 index 91740bf8b..000000000 --- a/crates/kernel/src/lctx.rs +++ /dev/null @@ -1,297 +0,0 @@ -//! Local context for free-variable based binder opening. -//! -//! Mirrors `refs/lean4/src/Lean/LocalContext.lean` and the C++ kernel's -//! `local_ctx`. During type checking, when a binder (`Lam`/`All`/`Let`) is -//! recursed into, we open it by replacing the bound `Var(0)` with a fresh -//! [`FVar`](super::expr::ExprData::FVar) and pushing a [`LocalDecl`] into the -//! [`LocalContext`]. -//! -//! Each fvar's [`FVarId`] participates in structural interning, so distinct -//! locals remain distinct in expression-keyed caches. Binder contracts are -//! retained here for reconstruction; conversion compares input contracts -//! separately from the resource validator. - -use rustc_hash::FxHashMap; - -use ix_common::env::{BinderInfo, Name}; -use ixon::contract::{BinderContract, LetContract, ValueContract}; - -use super::env::InternTable; -use super::expr::{FVarId, KExpr}; -use super::mode::KernelMode; -use super::subst::abstract_fvars; - -/// A single local-context entry. Either a regular binder (`CDecl`, from a -/// lambda or forall) or a let-binding (`LDecl`, with an associated value). -#[derive(Clone, Debug)] -pub enum LocalDecl { - /// Regular binder declaration: type and the original scalar contracts. - /// Lambda inference supplies the default result contract. - CDecl { - name: M::MField, - bi: M::MField, - ty: KExpr, - contract: BinderContract, - result: ValueContract, - }, - /// Let-binding declaration: carries both type and value. WHNF zeta-reduces - /// `FVar(id)` to `val` when the lookup hits an `LDecl`. Mirrors - /// `Lean.LocalDecl.ldecl`. - LDecl { - name: M::MField, - ty: KExpr, - val: KExpr, - contract: LetContract, - }, -} - -impl LocalDecl { - /// Fresh ordinary-Lean local; source binders provide explicit payloads. - pub fn cdecl( - name: M::MField, - bi: M::MField, - ty: KExpr, - ) -> Self { - Self::CDecl { - name, - bi, - ty, - contract: BinderContract::default(), - result: ValueContract::shared(), - } - } - - pub fn ldecl(name: M::MField, ty: KExpr, val: KExpr) -> Self { - Self::LDecl { name, ty, val, contract: LetContract::plain(false) } - } - - pub fn ty(&self) -> &KExpr { - match self { - LocalDecl::CDecl { ty, .. } | LocalDecl::LDecl { ty, .. } => ty, - } - } - - pub fn name(&self) -> &M::MField { - match self { - LocalDecl::CDecl { name, .. } | LocalDecl::LDecl { name, .. } => name, - } - } - - /// `Some(val)` for let-bindings, `None` otherwise. - pub fn val(&self) -> Option<&KExpr> { - match self { - LocalDecl::CDecl { .. } => None, - LocalDecl::LDecl { val, .. } => Some(val), - } - } - - pub fn is_let(&self) -> bool { - matches!(self, LocalDecl::LDecl { .. }) - } -} - -/// Insertion-ordered local context indexed by [`FVarId`]. -/// -/// Push/pop is O(1) via `Vec` operations on `decls`. Lookup by `FVarId` is -/// O(1) via the parallel `index` map. The two structures are kept in sync -/// by `push` and `truncate`. -#[derive(Clone, Debug)] -pub struct LocalContext { - /// Insertion-ordered fvars and their declarations. - decls: Vec<(FVarId, LocalDecl)>, - /// Position lookup: `index[fv_id] == i` iff `decls[i].0 == fv_id`. - index: FxHashMap, -} - -impl Default for LocalContext { - fn default() -> Self { - Self::new() - } -} - -impl LocalContext { - pub fn new() -> Self { - LocalContext { decls: Vec::new(), index: FxHashMap::default() } - } - - pub fn len(&self) -> usize { - self.decls.len() - } - - pub fn is_empty(&self) -> bool { - self.decls.is_empty() - } - - /// Look up a declaration by its [`FVarId`]. Returns `None` if the fvar is - /// not bound in this context. - pub fn find(&self, id: FVarId) -> Option<&LocalDecl> { - self.index.get(&id).map(|&i| &self.decls[i].1) - } - - /// Push a declaration. The caller is responsible for ensuring `id` is - /// fresh (typically from [`NameGenerator::fresh`]). - pub fn push(&mut self, id: FVarId, decl: LocalDecl) { - let pos = self.decls.len(); - self.decls.push((id, decl)); - self.index.insert(id, pos); - } - - /// Truncate the context to the given length, dropping any declarations - /// pushed since. The dropped fvars become unresolvable via [`Self::find`]. - pub fn truncate(&mut self, len: usize) { - while self.decls.len() > len { - let (id, _) = self.decls.pop().expect("len > 0 by loop guard"); - self.index.remove(&id); - } - } - - /// Iterate decls in insertion order. - pub fn iter(&self) -> impl Iterator)> { - self.decls.iter().map(|(id, d)| (*id, d)) - } - - /// Abstract `body` over `fvars` and wrap it in a chain of `Lam` (or `Let` - /// for `LDecl` entries) binders, innermost-first. - /// - /// The result has all of the listed fvars replaced by de Bruijn indices - /// (`fvars[0]` becomes the outermost binder, `fvars[n-1]` the innermost). - /// This is the inverse of the binder-opening pattern used during type - /// checking: open with [`NameGenerator::fresh`], recurse, close back - /// here so the result no longer mentions any of the opened fvars. - /// - /// Mirrors `Lean.LocalContext.mkLambda`. - pub fn mk_lambda( - &self, - intern: &mut InternTable, - fvars: &[FVarId], - body: &KExpr, - ) -> KExpr { - let abstracted = abstract_fvars(intern, body, fvars); - self.wrap_binders(intern, fvars, abstracted, /* as_lambda */ true) - } - - /// Abstract `body` over `fvars` and wrap it in a chain of `All` (or `Let` - /// for `LDecl` entries) binders, innermost-first. - /// - /// Same shape as [`Self::mk_lambda`] but emits `All` for `CDecl` entries. - /// Mirrors `Lean.LocalContext.mkForall`. - pub fn mk_pi( - &self, - intern: &mut InternTable, - fvars: &[FVarId], - body: &KExpr, - ) -> KExpr { - let abstracted = abstract_fvars(intern, body, fvars); - self.wrap_binders(intern, fvars, abstracted, /* as_lambda */ false) - } - - fn wrap_binders( - &self, - intern: &mut InternTable, - fvars: &[FVarId], - body: KExpr, - as_lambda: bool, - ) -> KExpr { - // Wrap from innermost to outermost: rightmost fvar is the innermost - // binder, so iterate fvars in reverse. - let mut acc = body; - for (i, fv) in fvars.iter().enumerate().rev() { - let decl = self - .find(*fv) - .expect("LocalContext::wrap_binders: fvar not in context"); - acc = match decl { - LocalDecl::CDecl { name, bi, ty, contract, result } => { - let ty = abstract_fvars(intern, ty, &fvars[..i]); - if as_lambda { - intern.intern_expr(KExpr::lam_contract( - name.clone(), - bi.clone(), - *contract, - ty, - acc, - )) - } else { - intern.intern_expr(KExpr::all_contract( - name.clone(), - bi.clone(), - *contract, - *result, - ty, - acc, - )) - } - }, - LocalDecl::LDecl { name, ty, val, contract } => { - let ty = abstract_fvars(intern, ty, &fvars[..i]); - let val = abstract_fvars(intern, val, &fvars[..i]); - // Let-bindings always close as `Let`, regardless of `as_lambda`. - // Fresh ordinary locals use nonDep=false; an opened source let - // retains its complete original payload. - intern.intern_expr(KExpr::let_contract( - name.clone(), - *contract, - ty, - val, - acc, - )) - }, - }; - } - acc - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::level::KUniv; - use crate::mode::Anon; - - type AE = KExpr; - type AU = KUniv; - - /// Anon-mode `M::MField` is `()`; tests construct it with this - /// trivially-named alias to make the call sites read like the meta - /// equivalents (`ANON_NAME` ↔ `Name::anon()`). - const ANON_NAME: () = (); - const ANON_BI: () = (); - - #[test] - fn lctx_push_find_truncate() { - let mut lctx: LocalContext = LocalContext::new(); - - let id1 = FVarId(0); - let id2 = FVarId(1); - let ty1 = AE::sort(AU::zero()); - let ty2 = AE::sort(AU::succ(AU::zero())); - - lctx.push(id1, LocalDecl::cdecl(ANON_NAME, ANON_BI, ty1.clone())); - lctx.push(id2, LocalDecl::cdecl(ANON_NAME, ANON_BI, ty2.clone())); - - assert_eq!(lctx.len(), 2); - assert_eq!(lctx.find(id1).map(|d| d.ty()), Some(&ty1)); - assert_eq!(lctx.find(id2).map(|d| d.ty()), Some(&ty2)); - - lctx.truncate(1); - assert_eq!(lctx.len(), 1); - assert!(lctx.find(id2).is_none()); - assert_eq!(lctx.find(id1).map(|d| d.ty()), Some(&ty1)); - - lctx.truncate(0); - assert!(lctx.is_empty()); - assert!(lctx.find(id1).is_none()); - } - - #[test] - fn fvar_distinct_ids_distinct_hashes() { - let id1 = FVarId(0); - let id2 = FVarId(1); - let fv1: AE = AE::fvar(id1, ANON_NAME); - let fv2: AE = AE::fvar(id2, ANON_NAME); - assert_ne!(fv1.addr(), fv2.addr()); - assert!(fv1.has_fvars()); - assert!(fv2.has_fvars()); - assert_eq!(fv1.lbr(), 0); - assert_eq!(fv2.lbr(), 0); - } -} diff --git a/crates/kernel/src/level.rs b/crates/kernel/src/level.rs deleted file mode 100644 index eb65e40eb..000000000 --- a/crates/kernel/src/level.rs +++ /dev/null @@ -1,1353 +0,0 @@ -//! Universe levels with optional metadata and Géran's canonical-form comparison. -//! -//! `KUniv` is an Arc-wrapped universe level. Each variant carries a -//! monotonic intern-table UID (`Addr = u64`) for hash-consed identity. -//! `Param` carries a positional `u64` parameter index plus `M::MField` — -//! the parameter name in Meta mode, erased to `()` in Anon mode. -//! -//! # Relationship to Lean4Lean -//! -//! `normalize_level` is a line-by-line port of Lean4Lean's `Level.Normalize` -//! (see `refs/lean4lean/Lean4Lean/Level.lean`), based on Yoan Géran's paper -//! "A Canonical Form for Universe Levels in Impredicative Type Theory" -//! (). The Rust `NormLevel` is -//! a `BTreeMap, Node>` indexed by sorted param-index paths — the -//! Rust analogue of Lean4Lean's `Std.TreeMap (List Name) Node`, with `u64` -//! param indices replacing `Name` since our anon-mode params are positional. -//! -//! Point of divergence: `norm_level_le` is intentionally stronger than -//! Lean4Lean's `NormLevel.le`. Lean4Lean's variant looks for a *single* -//! `p2 ⊆ p1` entry in `l2` that dominates both the constant and the variable -//! contributions of `n_p1`; ours splits that into independent per-ingredient -//! searches (`covers_const` and `covers_var`). See the detailed doc on -//! `norm_level_le` for the concrete witness that motivated the change. -//! -//! This is a soundness-preserving completeness strengthening, not a -//! disagreement with the canonical-form theory: Lean4Lean's -//! `NormLevel.subsumption_eval` is `sorry` in -//! `refs/lean4lean/Lean4Lean/Verify/Level.lean:545`, and there is no -//! `geq'_wf` / `NormLevel.le_wf` theorem anywhere in the Verify tree, so the -//! "complete for level algebra" claim in Lean4Lean's `divergences.md` is -//! aspirational for `geq'` specifically. `univ_eq` (via `norm_level_eq`) -//! matches Lean4Lean's `isEquiv'` bit-for-bit, since that direction *is* -//! proven sound (`isEquiv'_wf`, `Verify/Level.lean:578`) and the witness -//! that exposed `NormLevel.le`'s gap is not an equality case. - -use std::collections::BTreeMap; -use std::fmt; -use std::sync::Arc; - -use ix_common::env::Name; - -use super::env::Addr; -use super::mode::{KernelMode, MetaDisplay}; - -/// Universe level. Thin Arc wrapper — cheap to clone, O(1) identity -/// via `Arc::ptr_eq`. -#[derive(Clone, Debug)] -pub struct KUniv(Arc>); - -/// Universe level data. Each variant carries its intern-table UID (`Addr = u64`), -/// and `Param(u64, M::MField, Addr)` stores the positional parameter index. -#[derive(Clone, Debug)] -pub enum UnivData { - Zero(Addr), - Succ(KUniv, Addr), - Max(KUniv, KUniv, Addr), - IMax(KUniv, KUniv, Addr), - Param(u64, M::MField, Addr), -} - -impl KUniv { - /// Wrap raw data into a `KUniv`. - pub fn new(data: UnivData) -> Self { - KUniv(Arc::new(data)) - } - - pub fn data(&self) -> &UnivData { - &self.0 - } - - pub fn addr(&self) -> &Addr { - match self.data() { - UnivData::Zero(h) - | UnivData::Succ(_, h) - | UnivData::Max(_, _, h) - | UnivData::IMax(_, _, h) - | UnivData::Param(_, _, h) => h, - } - } - - pub fn ptr_eq(&self, other: &KUniv) -> bool { - Arc::ptr_eq(&self.0, &other.0) - } - - /// 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(_)) - } - - /// Semantic Prop test: `u ≡ 0` under every parameter assignment. The - /// syntactic `is_zero` misses spellings like `imax 1 0`, which the - /// kernel must classify as `Prop` (leanprover/lean4#14613, #14615). - /// Zero normalizes to empty entries only and `norm_level_eq` ignores - /// empty entries, so all-empty is exactly `univ_eq(u, zero)`. - pub fn is_semantic_zero(&self) -> bool { - self.is_zero() - || normalize_level(self) - .values() - .all(|n| n.constant == 0 && n.var.is_empty()) - } - - /// True if this level is an explicit numeral: `Succ^n(Zero)` for some n ≥ 0. - pub fn is_explicit(&self) -> bool { - match self.data() { - UnivData::Zero(_) => true, - UnivData::Succ(inner, _) => inner.is_explicit(), - _ => false, - } - } - - /// True if this level is `Succ^n(base)` with n > 0. Such a level is never - /// zero under any parameter assignment. - pub fn is_never_zero(&self) -> bool { - match self.data() { - UnivData::Succ(..) => true, - UnivData::Max(a, b, _) => a.is_never_zero() || b.is_never_zero(), - UnivData::IMax(_, b, _) => b.is_never_zero(), - _ => false, - } - } - - /// Peel the outermost constant offset: returns `(base, n)` where - /// `self = Succ^n(base)` and `base` is not `Succ`. - pub fn offset(&self) -> (&KUniv, u64) { - let mut u = self; - let mut n = 0u64; - loop { - match u.data() { - UnivData::Succ(inner, _) => { - u = inner; - n += 1; - }, - _ => return (u, n), - } - } - } -} - -impl KUniv { - pub fn zero() -> Self { - KUniv::new(UnivData::Zero(super::expr::fresh_uid())) - } - - pub fn succ(inner: KUniv) -> Self { - KUniv::new(UnivData::Succ(inner, super::expr::fresh_uid())) - } - - /// Construct `max(a, b)` with Lean-style simplifications: - /// - /// - `max(k₁, k₂) = max(k₁, k₂)` when both are explicit numerals - /// - `max(a, a) = a` - /// - `max(0, a) = a`, `max(a, 0) = a` - /// - `max(a, max(a, b)) = max(a, b)` (absorption) - /// - `max(max(a, b), b) = max(a, b)` (absorption) - /// - `max(succ^n(base), succ^m(base)) = succ^max(n,m)(base)` (same-base offset) - /// - /// Matches Lean's `mk_max` in `kernel/level.cpp:81-103`. - pub fn max(a: KUniv, b: KUniv) -> Self { - // Both explicit numerals (Succ^n(Zero)): take the larger. - if a.is_explicit() && b.is_explicit() { - let (_, na) = a.offset(); - let (_, nb) = b.offset(); - return if na >= nb { a } else { b }; - } - // Structural equality. - if a == b { - return a; - } - // Zero absorption. - if a.is_zero() { - return b; - } - if b.is_zero() { - return a; - } - // max(a, max(a, b')) = max(a, b'), max(a, max(b', a)) = max(b', a) - if let UnivData::Max(bl, br, _) = b.data() - && (*bl == a || *br == a) - { - return b; - } - // max(max(a', b), b) = max(a', b), max(max(b, a'), b) = max(b, a') - if let UnivData::Max(al, ar, _) = a.data() - && (*al == b || *ar == b) - { - return a; - } - // Same base, different offsets: succ^n(x) vs succ^m(x) → take the larger. - let (base_a, off_a) = a.offset(); - let (base_b, off_b) = b.offset(); - if base_a == base_b { - return if off_a >= off_b { a } else { b }; - } - // No simplification — construct the raw Max node. - Self::max_raw(a, b) - } - - /// Raw `Max` constructor without simplification. Used by `max()` after - /// all simplification opportunities are exhausted. - fn max_raw(a: KUniv, b: KUniv) -> Self { - KUniv::new(UnivData::Max(a, b, super::expr::fresh_uid())) - } - - /// Construct `imax(a, b)` with Lean-style simplifications: - /// - /// - `imax(a, b) = max(a, b)` when `b` is never zero - /// - `imax(a, 0) = 0` - /// - `imax(0, b) = b`, `imax(1, b) = b` - /// - `imax(a, a) = a` - /// - /// Matches Lean's `mk_imax` in `kernel/level.cpp:112-120`. - pub fn imax(a: KUniv, b: KUniv) -> Self { - if b.is_never_zero() { - return Self::max(a, b); - } - if b.is_zero() { - return b; // imax(a, 0) = 0 - } - if a.is_zero() { - return b; // imax(0, b) = b - } - // imax(1, b) = b (Lean: is_one check) - if let UnivData::Succ(inner, _) = a.data() - && inner.is_zero() - { - return b; - } - if a == b { - return a; // imax(a, a) = a - } - // No simplification — construct raw IMax node. - KUniv::new(UnivData::IMax(a, b, super::expr::fresh_uid())) - } - - pub fn param(idx: u64, name: M::MField) -> Self { - KUniv::new(UnivData::Param(idx, name, super::expr::fresh_uid())) - } -} - -// Structural equality (uid fast path, recursive fallback). -impl PartialEq for KUniv { - fn eq(&self, other: &Self) -> bool { - self.structural_eq(other) - } -} - -impl Eq for KUniv {} - -/// Meta mode: shows names when available, positional index as fallback. -/// Anon mode: shows positional parameter indices. -impl fmt::Display for KUniv { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt_univ(self, f) - } -} - -fn fmt_univ( - u: &KUniv, - f: &mut fmt::Formatter<'_>, -) -> fmt::Result { - match u.data() { - UnivData::Zero(_) => write!(f, "0"), - UnivData::Succ(_, _) => { - let (base, n) = u.offset(); - if base.is_zero() { - write!(f, "{n}") - } else { - fmt_univ(base, f)?; - write!(f, "+{n}") - } - }, - UnivData::Max(a, b, _) => { - write!(f, "max(")?; - fmt_univ(a, f)?; - write!(f, ", ")?; - fmt_univ(b, f)?; - write!(f, ")") - }, - UnivData::IMax(a, b, _) => { - write!(f, "imax(")?; - fmt_univ(a, f)?; - write!(f, ", ")?; - fmt_univ(b, f)?; - write!(f, ")") - }, - UnivData::Param(idx, name, _) => { - if name.has_meta() { - name.meta_fmt(f) - } else { - write!(f, "u{idx}") - } - }, - } -} - -// Géran's canonical-form normalization and comparison -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -struct VarNode { - idx: u64, - offset: u64, -} - -#[derive(Debug, Clone, Default)] -struct Node { - constant: u64, - var: Vec, -} - -impl Node { - fn add_var(&mut self, idx: u64, k: u64) { - match self.var.binary_search_by_key(&idx, |v| v.idx) { - Ok(pos) => self.var[pos].offset = self.var[pos].offset.max(k), - Err(pos) => self.var.insert(pos, VarNode { idx, offset: k }), - } - } -} - -/// Canonical form: a map from imax-paths (sorted param indices representing -/// the conditioning chain) to nodes tracking constant offsets and variable -/// contributions. -type NormLevel = BTreeMap, Node>; - -fn norm_add_var(s: &mut NormLevel, idx: u64, k: u64, path: &[u64]) { - s.entry(path.to_vec()).or_default().add_var(idx, k); -} - -/// Insert `(idx, k)` into the var list at `path`, taking the max of offsets -/// when `idx` is already present. Mirrors Lean4Lean's -/// `NormLevel.addNode v k path'` (`refs/lean4lean/Lean4Lean/Level.lean:92`); -/// `k` must be the current succ-accumulator from `normalize_aux`. -/// -/// An earlier port of this function dropped `k` and always inserted -/// `(idx, 0)`, which silently mis-normalized `Succ^n(imax(u, Param v))` -/// shapes for `n > 0`. Keep the `k` parameter. -fn norm_add_node(s: &mut NormLevel, idx: u64, k: u64, path: &[u64]) { - s.entry(path.to_vec()).or_default().add_var(idx, k); -} - -fn norm_add_const(s: &mut NormLevel, k: u64, path: &[u64]) { - if k == 0 || (k == 1 && !path.is_empty()) { - return; - } - let node = s.entry(path.to_vec()).or_default(); - node.constant = node.constant.max(k); -} - -/// Insert into a sorted list, returning `None` if already present. -fn ordered_insert(a: u64, list: &[u64]) -> Option> { - match list.binary_search(&a) { - Ok(_) => None, - Err(pos) => { - let mut result = list.to_vec(); - result.insert(pos, a); - Some(result) - }, - } -} - -/// Recursively flatten a level into canonical form, accumulating into `acc`. -/// `path` tracks the imax-conditioning chain, `k` is the accumulated succ offset. -fn normalize_aux( - l: &KUniv, - path: &[u64], - k: u64, - acc: &mut NormLevel, -) { - match l.data() { - UnivData::Zero(_) => { - norm_add_const(acc, k, path); - }, - UnivData::Succ(inner, _) => { - normalize_aux(inner, path, k + 1, acc); - }, - UnivData::Max(a, b, _) => { - normalize_aux(a, path, k, acc); - normalize_aux(b, path, k, acc); - }, - UnivData::IMax(_, b, _) if b.is_zero() => { - norm_add_const(acc, k, path); - }, - UnivData::IMax(u, b, _) if matches!(b.data(), UnivData::Succ(..)) => { - if let UnivData::Succ(v, _) = b.data() { - normalize_aux(u, path, k, acc); - normalize_aux(v, path, k + 1, acc); - } - }, - UnivData::IMax(u, b, _) if matches!(b.data(), UnivData::Max(..)) => { - if let UnivData::Max(v, w, _) = b.data() { - normalize_imax_max(u, v, w, path, k, acc); - } - }, - UnivData::IMax(u, b, _) if matches!(b.data(), UnivData::IMax(..)) => { - if let UnivData::IMax(v, w, _) = b.data() { - normalize_imax_imax(u, v, w, path, k, acc); - } - }, - UnivData::IMax(u, b, _) if matches!(b.data(), UnivData::Param(..)) => { - if let UnivData::Param(idx, _, _) = b.data() { - let idx = *idx; - if let Some(new_path) = ordered_insert(idx, path) { - // When param(idx) = 0, imax(u, 0) = 0, contributing k from outer succs. - norm_add_const(acc, k, path); - norm_add_node(acc, idx, k, &new_path); - normalize_aux(u, &new_path, k, acc); - } else { - // Param(idx) is already in path (so we're in an `imax(u, v)` where - // v = Param(idx) and idx is fixed > 0 by the enclosing chain). - // The outer k Succ's still contribute when idx > 0, which it is - // along this path. Matches Lean4Lean's `acc.addVar v k path`. - if k != 0 { - norm_add_var(acc, idx, k, path); - } - normalize_aux(u, path, k, acc); - } - } - }, - UnivData::Param(idx, _, _) => { - let idx = *idx; - if let Some(new_path) = ordered_insert(idx, path) { - norm_add_const(acc, k, path); - norm_add_node(acc, idx, k, &new_path); - } else if k != 0 { - norm_add_var(acc, idx, k, path); - } - }, - // All UnivData variants are covered above. If this is reached, - // it indicates a bug (e.g., a new variant was added without updating this match). - #[allow(unreachable_patterns)] - _ => unreachable!("normalize_aux: all UnivData variants should be covered"), - } -} - -/// Handle `imax(u, max(v, w))` = `max(imax(u, v), imax(u, w))`. -fn normalize_imax_max( - u: &KUniv, - v: &KUniv, - w: &KUniv, - path: &[u64], - k: u64, - acc: &mut NormLevel, -) { - normalize_imax_dispatch(u, v, path, k, acc); - normalize_imax_dispatch(u, w, path, k, acc); -} - -/// Handle `imax(u, imax(v, w))` = `max(imax(u, w), imax(v, w))`. -fn normalize_imax_imax( - u: &KUniv, - v: &KUniv, - w: &KUniv, - path: &[u64], - k: u64, - acc: &mut NormLevel, -) { - normalize_imax_dispatch(u, w, path, k, acc); - normalize_imax_dispatch(v, w, path, k, acc); -} - -/// Dispatch `imax(a, b)` normalization based on `b`'s shape. -fn normalize_imax_dispatch( - a: &KUniv, - b: &KUniv, - path: &[u64], - k: u64, - acc: &mut NormLevel, -) { - if b.is_zero() { - norm_add_const(acc, k, path); - } else if let UnivData::Succ(v, _) = b.data() { - normalize_aux(a, path, k, acc); - normalize_aux(v, path, k + 1, acc); - } else if let UnivData::Max(v, w, _) = b.data() { - normalize_imax_max(a, v, w, path, k, acc); - } else if let UnivData::IMax(v, w, _) = b.data() { - normalize_imax_imax(a, v, w, path, k, acc); - } else if let UnivData::Param(idx, _, _) = b.data() { - let idx = *idx; - if let Some(new_path) = ordered_insert(idx, path) { - // When param(idx) = 0, imax(a, 0) = 0, contributing k from outer succs. - norm_add_const(acc, k, path); - norm_add_node(acc, idx, k, &new_path); - normalize_aux(a, &new_path, k, acc); - } else { - // idx is already in path; outer k Succ's still contribute. - // Matches Lean4Lean's `acc.addVar v k path`. - if k != 0 { - norm_add_var(acc, idx, k, path); - } - normalize_aux(a, path, k, acc); - } - } else { - // All UnivData variants for `b` are covered above. - unreachable!( - "normalize_imax_dispatch: all UnivData variants for b should be covered" - ); - } -} - -// Subsumption (Phase 2) -fn subsume_vars(xs: &[VarNode], ys: &[VarNode]) -> Vec { - let mut result = Vec::new(); - let mut xi = 0; - let mut yi = 0; - while xi < xs.len() { - if yi >= ys.len() { - result.extend_from_slice(&xs[xi..]); - break; - } - match xs[xi].idx.cmp(&ys[yi].idx) { - std::cmp::Ordering::Less => { - result.push(xs[xi].clone()); - xi += 1; - }, - std::cmp::Ordering::Equal => { - if xs[xi].offset > ys[yi].offset { - result.push(xs[xi].clone()); - } - xi += 1; - yi += 1; - }, - std::cmp::Ordering::Greater => { - yi += 1; - }, - } - } - result -} - -fn is_subset(xs: &[u64], ys: &[u64]) -> bool { - let mut yi = 0; - for &x in xs { - while yi < ys.len() && ys[yi] < x { - yi += 1; - } - if yi >= ys.len() || ys[yi] != x { - return false; - } - yi += 1; - } - true -} - -fn subsumption(acc: &mut NormLevel) { - let snapshot: Vec<_> = - acc.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); - - for (p1, n1) in acc.iter_mut() { - for (p2, n2) in &snapshot { - if !is_subset(p2, p1) { - continue; - } - let same = p1.len() == p2.len(); - - if n1.constant != 0 { - let max_var_offset = n1.var.iter().map(|v| v.offset).max().unwrap_or(0); - let keep_const = (same || n1.constant > n2.constant) - && (n2.var.is_empty() || n1.constant > max_var_offset + 1); - if !keep_const { - n1.constant = 0; - } - } - - if !same && !n2.var.is_empty() { - n1.var = subsume_vars(&n1.var, &n2.var); - } - } - } -} - -// Comparison - -/// Check whether some entry `(p2, n2)` in `l2` with `p2 ⊆ p1` provides a -/// contribution that dominates `n1.const` along every assignment satisfying -/// `p1`'s activation. A `p2` entry contributes `n_p2.const` unconditionally -/// (in that branch), and each `v ∈ n_p2.var` contributes at least `v.offset + 1` -/// because `v.idx ∈ p2 ⊆ p1` guarantees `u_v ≥ 1`. -fn covers_const(l2: &NormLevel, p1: &[u64], c: u64) -> bool { - l2.iter().any(|(p2, n2)| { - is_subset(p2, p1) - && (c <= n2.constant || n2.var.iter().any(|v| c <= v.offset + 1)) - }) -} - -/// Check whether some entry `(p2, n2)` in `l2` with `p2 ⊆ p1` contains a -/// variable node that dominates `(w, off)`: i.e., some `v ∈ n_p2.var` with -/// `v.idx == w && v.offset >= off`. Because `v.idx` is always in `p2`, the -/// matching p2 automatically has `w ∈ p2 ⊆ p1`, keeping the branch analysis -/// consistent. -fn covers_var(l2: &NormLevel, p1: &[u64], w: u64, off: u64) -> bool { - l2.iter().any(|(p2, n2)| { - is_subset(p2, p1) && n2.var.iter().any(|v| v.idx == w && v.offset >= off) - }) -} - -/// Semantic `l1 ≤ l2` on canonical forms. For each `(p1, n1)` in `l1`, the -/// contribution `max(n1.const, u_w + v.off for v ∈ n1.var)` in the branch -/// where `p1`'s params are all positive must be dominated by the max of -/// contributions from `{(p2, n_p2) : p2 ⊆ p1}` in the same branch. -/// -/// # Divergence from Lean4Lean -/// -/// Lean4Lean's `NormLevel.le` (`refs/lean4lean/Lean4Lean/Level.lean:164`) -/// looks for a *single* `p2` covering both `n1.const` and `n1.var` -/// simultaneously — sound, but incomplete. Concrete witness (see -/// `prop_univ_max_is_geq_both_components_imax_witness`): -/// -/// ```text -/// a = Succ^3(0) -/// b = imax(imax(a, Param 0), Param 1) -/// m = max(a, b) -/// ``` -/// -/// After normalization + subsumption: -/// -/// ```text -/// normalize(m): [] → const=3, [1] → var=[(1,0)], [0,1] → var=[(0,0)] -/// normalize(b): [] → const=0, [1] → var=[(1,0)], [0,1] → {const=3, var=[(0,0)]} -/// ``` -/// -/// Checking `b ≤ m` at `p1 = [0,1]` needs both `const=3` and `var=[(0,0)]`. -/// `m[[]]` covers the const (no var); `m[[0,1]]` covers the var (const was -/// zeroed out by subsumption against `m[[]]`). No single `p2 ⊆ [0,1]` in -/// `m` has both, so Lean4Lean's `le` reports `m ≱ b` even though `m ≥ b` -/// holds for every parameter assignment. -/// -/// The version here splits the check into `covers_const` and `covers_var`, -/// each searching `l2` independently. This is sound: -/// -/// - For `n1.const = C`, if some `p2 ⊆ p1` has `n_p2.const ≥ C`, then along -/// any `ρ` with `p1` active, `p2` is active too, so `l2`'s total already -/// includes `n_p2.const ≥ C`. Same argument for the fallback clause -/// `v.offset + 1 ≥ C` with `v ∈ n_p2.var`, because every `v` inserted -/// during `normalize_aux` has `v.idx ∈ p2` (so `u_v ≥ 1` in an active -/// branch). -/// - For each `(w, off) ∈ n1.var`, if some `p2 ⊆ p1` has `(w, off') ∈ -/// n_p2.var` with `off' ≥ off`, then `l2`'s contribution along active -/// `p1` is at least `u_w + off' ≥ u_w + off`. -/// -/// This matches what Lean4Lean's paper-level theory expects but its -/// implementation doesn't cover (cf. the `sorry` on -/// `NormLevel.subsumption_eval` in `Verify/Level.lean:545`, and the absence -/// of any `geq'_wf`). -fn norm_level_le(l1: &NormLevel, l2: &NormLevel) -> bool { - for (p1, n1) in l1 { - if n1.constant == 0 && n1.var.is_empty() { - continue; - } - if n1.constant != 0 && !covers_const(l2, p1, n1.constant) { - return false; - } - for v in &n1.var { - if !covers_var(l2, p1, v.idx, v.offset) { - return false; - } - } - } - true -} - -/// Entry-wise equality of two normal forms, IGNORING empty entries -/// (constant 0, no vars — pure subsumption bookkeeping). Subsumption -/// EMPTIES a dominated entry rather than removing it, so a spelling like -/// `max (u+1) (imax (u+1) v)` retains an empty `[u,v]` entry that its -/// semantic equal `max (u+1) v` never creates; counting those entries -/// made `univ_eq` strictly finer than semantic equality (3 of 3,253,373 -/// whole-Mathlib entries). `norm_level_le` already skips them (see the -/// `continue` guard above); mirroring that here makes `univ_eq` the -/// exact semantic quotient (canonicity §10.6 R5, option (b)). -/// -/// `BTreeMap` iteration is key-ordered, so comparing the filtered -/// streams positionally is a sound map comparison. -fn norm_level_eq(l1: &NormLevel, l2: &NormLevel) -> bool { - let non_empty = - |&(_, n): &(&Vec, &Node)| n.constant != 0 || !n.var.is_empty(); - let mut it1 = l1.iter().filter(non_empty); - let mut it2 = l2.iter().filter(non_empty); - loop { - match (it1.next(), it2.next()) { - (None, None) => return true, - (Some((k1, n1)), Some((k2, n2))) => { - if k1 != k2 || n1.constant != n2.constant || n1.var != n2.var { - return false; - } - }, - _ => return false, - } - } -} - -/// Normalize a universe level to Géran's canonical form. -fn normalize_level(l: &KUniv) -> NormLevel { - let mut acc = NormLevel::new(); - acc.insert(Vec::new(), Node::default()); - normalize_aux(l, &[], 0, &mut acc); - subsumption(&mut acc); - acc -} - -/// Semantic universe equality: `u ≡ v` for all parameter assignments. -pub fn univ_eq(u: &KUniv, v: &KUniv) -> bool { - u.hash_eq(v) || norm_level_eq(&normalize_level(u), &normalize_level(v)) -} - -/// Check `u ≥ v` for all parameter assignments. -pub fn univ_geq(u: &KUniv, v: &KUniv) -> bool { - u.hash_eq(v) - || v.is_zero() - || norm_level_le(&normalize_level(v), &normalize_level(u)) -} - -impl UnivData { - /// Move the contents of every uniquely owned child that has children of - /// its own to `stack`, leaving a leaf in its place. - fn detach_unique_children(&mut self, stack: &mut Vec>) { - let mut detach = |child: &mut KUniv| { - if Arc::strong_count(&child.0) == 1 - && let Some(inner) = Arc::get_mut(&mut child.0) - && matches!( - inner, - UnivData::Succ(..) | UnivData::Max(..) | UnivData::IMax(..) - ) - { - stack.push(std::mem::replace(inner, UnivData::Zero(0))); - } - }; - match self { - UnivData::Succ(a, _) => detach(a), - UnivData::Max(a, b, _) | UnivData::IMax(a, b, _) => { - detach(a); - detach(b); - }, - UnivData::Zero(_) | UnivData::Param(..) => {}, - } - } -} - -/// Dropping a kernel universe frees its uniquely owned descendants from an -/// explicit stack, so deep `Succ`/`Max`/`IMax` chains cannot overflow the -/// call stack on drop. -impl Drop for UnivData { - fn drop(&mut self) { - let mut stack: Vec> = Vec::new(); - self.detach_unique_children(&mut stack); - while let Some(mut u) = stack.pop() { - u.detach_unique_children(&mut stack); - } - } -} - -#[cfg(test)] -mod tests { - use super::super::mode::{Anon, Meta}; - use super::*; - use ix_common::env::Name; - - type MU = KUniv; - type AU = KUniv; - - fn mk_name(s: &str) -> Name { - let mut name = Name::anon(); - for part in s.split('.') { - name = Name::str(name, part.to_string()); - } - name - } - - // ---- Constructors & hashing ---- - - #[test] - fn zero_hash_deterministic() { - // 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] - fn zero_and_succ_differ() { - let z = MU::zero(); - let s = MU::succ(z.clone()); - assert_ne!(z.addr(), s.addr()); - } - - #[test] - fn deep_kuniv_drop_does_not_stack_overflow() { - let mut u = AU::zero(); - for _ in 0..100_000 { - u = AU::succ(u); - } - drop(u); - } - - #[test] - fn succ_hash_depends_on_child() { - let s1 = MU::succ(MU::zero()); - let s2 = MU::succ(MU::succ(MU::zero())); - assert_ne!(s1.addr(), s2.addr()); - } - - #[test] - fn max_hash_depends_on_order() { - let p0 = AU::param(0, ()); - let p1 = AU::param(1, ()); - let m1 = AU::max(p0.clone(), p1.clone()); - let m2 = AU::max(p1, p0); - assert_ne!(m1.addr(), m2.addr()); - } - - #[test] - fn max_vs_imax_differ() { - let p0 = AU::param(0, ()); - let p1 = AU::param(1, ()); - let m = AU::max(p0.clone(), p1.clone()); - let im = AU::imax(p0, p1); - assert_ne!(m.addr(), im.addr()); - } - - #[test] - fn param_index_differs() { - let p0 = AU::param(0, ()); - let p1 = AU::param(1, ()); - assert_ne!(p0.addr(), p1.addr()); - } - - // ---- Meta mode: names are display-only for hashes ---- - - #[test] - 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, 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, b); - } - - // ---- Anon mode: names erased ---- - - #[test] - fn anon_param_same_index_same_hash() { - let a = AU::param(0, ()); - let b = AU::param(0, ()); - 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!( - 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!( - super::super::env::univ_key(&anon), - super::super::env::univ_key(&meta) - ); - } - - // ---- PartialEq ---- - - #[test] - fn eq_by_hash() { - let a = MU::succ(MU::zero()); - let b = MU::succ(MU::zero()); - assert_eq!(a, b); - assert_ne!(a, MU::zero()); - } - - // ---- is_zero / is_never_zero / offset ---- - - #[test] - fn is_zero_checks() { - assert!(AU::zero().is_zero()); - assert!(!AU::succ(AU::zero()).is_zero()); - assert!(!AU::param(0, ()).is_zero()); - } - - #[test] - fn is_never_zero_checks() { - let z = AU::zero(); - let s1 = AU::succ(z.clone()); - let p = AU::param(0, ()); - assert!(!z.is_never_zero()); - assert!(s1.is_never_zero()); - assert!(!p.is_never_zero()); - // max(succ(0), p) is never zero - assert!(AU::max(s1.clone(), p.clone()).is_never_zero()); - // imax(p, succ(0)) is never zero - assert!(AU::imax(p, s1).is_never_zero()); - } - - #[test] - fn offset_peeling() { - let z = MU::zero(); - let s1 = MU::succ(z.clone()); - let s3 = MU::succ(MU::succ(MU::succ(MU::param(0, mk_name("u"))))); - assert_eq!(z.offset().1, 0); - assert_eq!(s1.offset().1, 1); - assert!(s1.offset().0.is_zero()); - assert_eq!(s3.offset().1, 3); - assert!(matches!(s3.offset().0.data(), UnivData::Param(0, _, _))); - } - - // ---- Display ---- - - #[test] - fn display_zero() { - assert_eq!(format!("{}", MU::zero()), "0"); - assert_eq!(format!("{}", AU::zero()), "0"); - } - - #[test] - fn display_succ_chain() { - let s2 = MU::succ(MU::succ(MU::zero())); - assert_eq!(format!("{s2}"), "2"); - } - - #[test] - fn display_succ_offset() { - let p = MU::param(0, mk_name("u")); - let sp = MU::succ(MU::succ(p)); - assert_eq!(format!("{sp}"), "u+2"); - } - - #[test] - fn display_anon_param() { - assert_eq!(format!("{}", AU::param(0, ())), "u0"); - assert_eq!(format!("{}", AU::param(3, ())), "u3"); - } - - #[test] - fn display_meta_named_param() { - assert_eq!(format!("{}", MU::param(0, mk_name("v"))), "v"); - assert_eq!(format!("{}", MU::param(1, mk_name("w"))), "w"); - } - - #[test] - fn display_meta_anonymous_param() { - assert_eq!(format!("{}", MU::param(0, Name::anon())), "u0"); - } - - #[test] - fn display_max() { - let m = AU::max(AU::param(0, ()), AU::param(1, ())); - assert_eq!(format!("{m}"), "max(u0, u1)"); - } - - #[test] - fn display_imax() { - // imax(u0, 1) simplifies to max(u0, 1) since 1 is never zero. - let im = AU::imax(AU::param(0, ()), AU::succ(AU::zero())); - assert_eq!(format!("{im}"), "max(u0, 1)"); - // imax with a potentially-zero rhs stays as imax. - let im2 = AU::imax(AU::param(0, ()), AU::param(1, ())); - assert_eq!(format!("{im2}"), "imax(u0, u1)"); - } - - #[test] - fn display_meta_max_with_names() { - let m = MU::max(MU::param(0, mk_name("u")), MU::param(1, mk_name("v"))); - assert_eq!(format!("{m}"), "max(u, v)"); - } - - // ---- Géran comparison ---- - - #[test] - fn univ_eq_basic() { - let z = AU::zero(); - let s1 = AU::succ(z.clone()); - let p = AU::param(0, ()); - assert!(univ_eq(&z, &z)); - assert!(univ_eq(&s1, &s1)); - assert!(!univ_eq(&z, &s1)); - assert!(!univ_eq(&s1, &p)); - } - - #[test] - fn univ_eq_max_commutative() { - let p0 = AU::param(0, ()); - let p1 = AU::param(1, ()); - let m1 = AU::max(p0.clone(), p1.clone()); - let m2 = AU::max(p1, p0); - assert!(univ_eq(&m1, &m2)); - } - - #[test] - fn univ_eq_max_idempotent() { - let p = AU::param(0, ()); - let m = AU::max(p.clone(), p.clone()); - assert!(univ_eq(&m, &p)); - } - - #[test] - fn univ_eq_max_zero() { - let z = AU::zero(); - let p = AU::param(0, ()); - let m = AU::max(p.clone(), z); - assert!(univ_eq(&m, &p)); - } - - #[test] - fn univ_eq_imax_zero() { - let z = AU::zero(); - let p = AU::param(0, ()); - let im = AU::imax(p, z.clone()); - assert!(univ_eq(&im, &z)); - } - - #[test] - fn univ_eq_imax_succ() { - let s1 = AU::succ(AU::zero()); - let p = AU::param(0, ()); - // imax(p, succ(0)) = max(p, succ(0)) - let im = AU::imax(p.clone(), s1.clone()); - let m = AU::max(p, s1); - assert!(univ_eq(&im, &m)); - } - - #[test] - fn univ_eq_imax_distribute() { - let p0 = AU::param(0, ()); - let p1 = AU::param(1, ()); - let p2 = AU::param(2, ()); - // imax(p0, max(p1, p2)) = max(imax(p0, p1), imax(p0, p2)) - let m = AU::max(p1.clone(), p2.clone()); - let lhs = AU::imax(p0.clone(), m); - let im1 = AU::imax(p0.clone(), p1); - let im2 = AU::imax(p0, p2); - let rhs = AU::max(im1, im2); - assert!(univ_eq(&lhs, &rhs)); - } - - /// Evaluate a level under a concrete assignment of parameter indices - /// to naturals (`imax(a,b) = 0` when `b = 0`). Used to certify test - /// vectors semantically, independent of the comparator under test. - fn eval_level(l: &AU, assign: &[u64]) -> u64 { - match l.data() { - UnivData::Zero(_) => 0, - UnivData::Succ(i, _) => eval_level(i, assign) + 1, - UnivData::Max(a, b, _) => { - eval_level(a, assign).max(eval_level(b, assign)) - }, - UnivData::IMax(a, b, _) => { - let vb = eval_level(b, assign); - if vb == 0 { 0 } else { eval_level(a, assign).max(vb) } - }, - UnivData::Param(i, _, _) => assign[usize::try_from(*i).unwrap()], - } - } - - /// Canonicity §10.6 R5 (option (b)): subsumption EMPTIES a dominated - /// entry rather than removing it, so `max (u+1) (imax (u+1) v)` keeps - /// an empty entry its semantic equal `max (u+1) v` never creates — - /// the whole-Mathlib empty-entry class witness (3 of 3,253,373 - /// entries). `univ_eq` must ignore such entries. - #[test] - fn univ_eq_ignores_empty_subsumption_entries() { - let u = AU::param(0, ()); - let v = AU::param(1, ()); - let su = AU::succ(u.clone()); - let lhs = AU::max(su.clone(), AU::imax(su.clone(), v.clone())); - let rhs = AU::max(su.clone(), v.clone()); - // Certify the pair is genuinely semantically equal. - for a in 0..4u64 { - for b in 0..4u64 { - assert_eq!(eval_level(&lhs, &[a, b]), eval_level(&rhs, &[a, b])); - } - } - assert!(univ_eq(&lhs, &rhs)); - assert!(univ_eq(&rhs, &lhs)); - // Geq agrees in both directions (norm_level_le already skipped - // empty entries — this is the antisymmetry pair univ_eq now matches). - assert!(univ_geq(&lhs, &rhs)); - assert!(univ_geq(&rhs, &lhs)); - // Control: the comparator did not get weaker — a semantically - // distinct near-miss stays distinct (at u=v=0: lhs=1, other=0). - let smaller = AU::max(u.clone(), v.clone()); - assert!(!univ_eq(&lhs, &smaller)); - assert!(!univ_eq(&smaller, &lhs)); - } - - #[test] - fn univ_geq_basic() { - let z = AU::zero(); - let s1 = AU::succ(z.clone()); - let s2 = AU::succ(s1.clone()); - let p = AU::param(0, ()); - assert!(univ_geq(&z, &z)); - assert!(univ_geq(&s1, &z)); - assert!(univ_geq(&p, &z)); - assert!(univ_geq(&s2, &s1)); - assert!(!univ_geq(&s1, &s2)); - } - - #[test] - fn univ_geq_param() { - let p = AU::param(0, ()); - let sp = AU::succ(p.clone()); - assert!(univ_geq(&sp, &p)); - assert!(!univ_geq(&p, &sp)); - } - - // ---- Meta mode Géran (names don't affect semantic equality) ---- - - #[test] - fn meta_univ_eq_ignores_names() { - // Same structure, different names — semantically equal - let a = MU::param(0, mk_name("u")); - let b = MU::param(0, mk_name("v")); - // Identity is metadata-erased, and Géran comparison sees the same index. - assert_eq!(a, b); - assert!(univ_eq(&a, &b)); - } - - // ========================================================================= - // Property-style tests for universe-level algebra invariants. - // - // Use a deterministic seeded generator (xorshift) to produce randomized - // `KUniv` values of bounded depth and check algebraic laws: - // reflexivity, symmetry of equality, transitivity of geq, and interaction - // between geq and eq. - // ========================================================================= - - struct UPrng(u64); - impl UPrng { - fn new(seed: u64) -> Self { - UPrng(seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ 0xDEAD_BEEF_CAFE_BABE) - } - fn next_u64(&mut self) -> u64 { - let mut x = self.0; - x ^= x << 13; - x ^= x >> 7; - x ^= x << 17; - self.0 = x; - x - } - fn next_u32(&mut self, bound: u32) -> u32 { - // Truncating to u32 is intentional for the test RNG. - #[allow(clippy::cast_possible_truncation)] - let lo = self.next_u64() as u32; - lo % bound.max(1) - } - } - - /// Generate a bounded-depth `KUniv`. Parameter indices are drawn - /// from `0..=max_param` so multiple universes in the same test can share - /// parameters — important for geq transitivity tests. - fn gen_univ(rng: &mut UPrng, depth: u32, max_param: u64) -> AU { - if depth == 0 { - return match rng.next_u32(3) { - 0 => AU::zero(), - 1 => AU::param(rng.next_u64() % (max_param + 1), ()), - _ => AU::succ(AU::zero()), - }; - } - match rng.next_u32(5) { - 0 => AU::zero(), - 1 => AU::param(rng.next_u64() % (max_param + 1), ()), - 2 => AU::succ(gen_univ(rng, depth - 1, max_param)), - 3 => AU::max( - gen_univ(rng, depth - 1, max_param), - gen_univ(rng, depth - 1, max_param), - ), - _ => AU::imax( - gen_univ(rng, depth - 1, max_param), - gen_univ(rng, depth - 1, max_param), - ), - } - } - - #[test] - fn prop_univ_eq_reflexive() { - let mut rng = UPrng::new(0x1234); - for _ in 0..200 { - let u = gen_univ(&mut rng, 4, 3); - assert!(univ_eq(&u, &u), "reflexivity failed for {u:?}"); - } - } - - #[test] - fn prop_univ_eq_symmetric() { - let mut rng = UPrng::new(0xABCD); - for _ in 0..200 { - let a = gen_univ(&mut rng, 3, 2); - let b = gen_univ(&mut rng, 3, 2); - assert_eq!( - univ_eq(&a, &b), - univ_eq(&b, &a), - "symmetry failed for {a:?} vs {b:?}" - ); - } - } - - #[test] - fn prop_univ_geq_reflexive() { - let mut rng = UPrng::new(0x5678); - for _ in 0..200 { - let u = gen_univ(&mut rng, 4, 3); - assert!(univ_geq(&u, &u), "geq reflexivity failed for {u:?}"); - } - } - - #[test] - fn prop_univ_eq_implies_geq_both_ways() { - let mut rng = UPrng::new(0xF00D); - for _ in 0..200 { - let a = gen_univ(&mut rng, 3, 2); - let b = gen_univ(&mut rng, 3, 2); - if univ_eq(&a, &b) { - assert!( - univ_geq(&a, &b), - "eq implies geq failed (a>=b) for {a:?} == {b:?}" - ); - assert!( - univ_geq(&b, &a), - "eq implies geq failed (b>=a) for {a:?} == {b:?}" - ); - } - } - } - - #[test] - fn prop_univ_succ_is_geq_base() { - let mut rng = UPrng::new(0xBA_D0); - for _ in 0..200 { - let u = gen_univ(&mut rng, 3, 2); - let su = AU::succ(u.clone()); - assert!(univ_geq(&su, &u), "succ u must be >= u for {u:?}"); - // And the reverse is (usually) false; guard for Zero-valued u (only - // case where succ u vs u... no, actually succ u > u always in - // Géran's semantics, so strict one-way geq should always hold). - assert!(!univ_geq(&u, &su), "u must NOT be >= succ u for {u:?}"); - } - } - - /// Generate a universe that uses only Zero / Succ / Max / Param — no IMax. - /// Property-tested `univ_geq` reliably holds `max(a, b) >= {a, b}` on - /// this subset; see `prop_univ_max_is_geq_both_components_imax_known_limit` - /// for the IMax case that surfaced a gap in Géran's comparison during - /// the initial sweep. - fn gen_univ_no_imax(rng: &mut UPrng, depth: u32, max_param: u64) -> AU { - if depth == 0 { - return match rng.next_u32(3) { - 0 => AU::zero(), - 1 => AU::param(rng.next_u64() % (max_param + 1), ()), - _ => AU::succ(AU::zero()), - }; - } - match rng.next_u32(4) { - 0 => AU::zero(), - 1 => AU::param(rng.next_u64() % (max_param + 1), ()), - 2 => AU::succ(gen_univ_no_imax(rng, depth - 1, max_param)), - _ => AU::max( - gen_univ_no_imax(rng, depth - 1, max_param), - gen_univ_no_imax(rng, depth - 1, max_param), - ), - } - } - - #[test] - fn prop_univ_max_is_geq_both_components() { - let mut rng = UPrng::new(0xBEEF); - for _ in 0..200 { - let a = gen_univ_no_imax(&mut rng, 3, 2); - let b = gen_univ_no_imax(&mut rng, 3, 2); - let m = AU::max(a.clone(), b.clone()); - assert!(univ_geq(&m, &a), "max(a,b) >= a failed for a={a:?} b={b:?}"); - assert!(univ_geq(&m, &b), "max(a,b) >= b failed for a={a:?} b={b:?}"); - } - } - - /// Full property: `max(a, b) ≥ {a, b}` also holds when imax is allowed - /// anywhere in the operands. Previously this failed — see the witness - /// regression test below. - #[test] - fn prop_univ_max_is_geq_both_components_with_imax() { - let mut rng = UPrng::new(0xCAFE); - for _ in 0..400 { - let a = gen_univ(&mut rng, 3, 2); - let b = gen_univ(&mut rng, 3, 2); - let m = AU::max(a.clone(), b.clone()); - assert!(univ_geq(&m, &a), "max(a,b) >= a failed for a={a:?} b={b:?}"); - assert!(univ_geq(&m, &b), "max(a,b) >= b failed for a={a:?} b={b:?}"); - } - } - - /// Regression test for a property failure surfaced by property testing - /// with a full `gen_univ` that included IMax nodes. - /// - /// Witness: `univ_geq(max(a, b), b)` with `b = imax(imax(Succ^3(0), - /// Param(0)), Param(1))` and `a = Succ^3(0)`. Semantically the property - /// holds for every parameter assignment. - /// - /// The original Lean4Lean `NormLevel.le` was incomplete: it searched for - /// a single `p2 ⊆ p1` in `l2` covering both the constant and variable - /// ingredients of `n_p1`. Here `m`'s canonical form splits its `const=3` - /// at `[]` from its `var=[(0,0)]` at `[0,1]`, while `b`'s `[0,1]` carries - /// both. Our `norm_level_le` now checks each ingredient of `n_p1` - /// independently so different `p2`s may cover different parts. - #[test] - fn prop_univ_max_is_geq_both_components_imax_witness() { - let a = AU::succ(AU::succ(AU::succ(AU::zero()))); - // b = imax(imax(Succ^3(0), Param(0)), Param(1)) - let b = AU::imax(AU::imax(a.clone(), AU::param(0, ())), AU::param(1, ())); - let m = AU::max(a.clone(), b.clone()); - assert!( - univ_geq(&m, &b), - "max(a,b) >= b with imax-heavy b — Géran gap regression" - ); - } -} diff --git a/crates/kernel/src/lib.rs b/crates/kernel/src/lib.rs deleted file mode 100644 index fb747000a..000000000 --- a/crates/kernel/src/lib.rs +++ /dev/null @@ -1,184 +0,0 @@ -// Diagnostic env-var helpers. Some zkVM standard libraries return `Ok` -// from `std::env::var` for any key (no real env -// in the guest), so every `IX_*` flag fires "on" by default and runs -// expensive diagnostic code paths whose output the guest can't even surface. -// These wrappers short-circuit to "not present" on any zkVM target and -// delegate to the real call elsewhere. -// -// Use these in place of `std::env::var` / `std::env::var_os` everywhere in -// the kernel. The Result / Option chains downstream (`.is_ok()`, `.ok()`, -// `.is_some()`, `.is_err()`, `match`, ...) all work identically. -#[inline] -pub(crate) fn env_var(name: &str) -> Result { - #[cfg(target_os = "zkvm")] - { - let _ = name; - Err(std::env::VarError::NotPresent) - } - #[cfg(not(target_os = "zkvm"))] - { - std::env::var(name) - } -} - -#[inline] -pub(crate) fn env_var_os(name: &str) -> Option { - #[cfg(target_os = "zkvm")] - { - let _ = name; - None - } - #[cfg(not(target_os = "zkvm"))] - { - std::env::var_os(name) - } -} - -// ============================================================================= -// LazyLock replacements for diagnostic env-var statics. -// -// On non-zkVM targets we want the existing thread-safe lazy-init behaviour -// (`std::sync::LazyLock`). On zkVM targets the initialiser would always -// produce a "not set" value (because `env_var` is hard-wired to `Err`), so -// `LazyLock`'s per-access atomic state load is pure overhead. These types -// collapse to a plain `bool` / `Option` field on zkVM and a real -// `LazyLock` everywhere else. Same `*FLAG` / `FLAG.as_ref()` call sites -// either way. -// ============================================================================= - -/// Boolean env-var flag (e.g. `IX_HOT_MISSES`). Compiles to `false` on zkVM. -pub(crate) struct EnvFlag { - #[cfg(not(target_os = "zkvm"))] - inner: std::sync::LazyLock bool>, - #[cfg(target_os = "zkvm")] - inner: bool, -} - -impl EnvFlag { - pub(crate) const fn new(_init: fn() -> bool) -> Self { - Self { - #[cfg(not(target_os = "zkvm"))] - inner: std::sync::LazyLock::new(_init), - #[cfg(target_os = "zkvm")] - inner: false, - } - } -} - -impl std::ops::Deref for EnvFlag { - type Target = bool; - #[inline] - fn deref(&self) -> &bool { - &self.inner - } -} - -/// `Option` env-var flag (e.g. `IX_DELTA_TRACE`). Compiles to -/// `None` on zkVM (so `IX_X.as_ref()` always returns `None` cheaply). -pub(crate) struct EnvString { - #[cfg(not(target_os = "zkvm"))] - inner: std::sync::LazyLock, fn() -> Option>, - #[cfg(target_os = "zkvm")] - inner: Option, -} - -impl EnvString { - pub(crate) const fn new(_init: fn() -> Option) -> Self { - Self { - #[cfg(not(target_os = "zkvm"))] - inner: std::sync::LazyLock::new(_init), - #[cfg(target_os = "zkvm")] - inner: None, - } - } -} - -impl std::ops::Deref for EnvString { - type Target = Option; - #[inline] - fn deref(&self) -> &Option { - &self.inner - } -} - -/// `Option` env-var flag (e.g. `IX_MAX_REC_FUEL`). Compiles to `None` -/// on zkVM. -pub(crate) struct EnvOptU64 { - #[cfg(not(target_os = "zkvm"))] - inner: std::sync::LazyLock, fn() -> Option>, - #[cfg(target_os = "zkvm")] - inner: Option, -} - -impl EnvOptU64 { - pub(crate) const fn new(_init: fn() -> Option) -> Self { - Self { - #[cfg(not(target_os = "zkvm"))] - inner: std::sync::LazyLock::new(_init), - #[cfg(target_os = "zkvm")] - inner: None, - } - } -} - -impl std::ops::Deref for EnvOptU64 { - type Target = Option; - #[inline] - fn deref(&self) -> &Option { - &self.inner - } -} - -pub mod anon_env; -pub mod anon_work; -#[cfg(not(target_arch = "riscv64"))] -pub mod artifact_checker; -pub mod check; -pub mod claim; -pub mod congruence; -pub mod constant; -pub mod def_eq; -pub mod env; -pub mod equiv; -pub mod error; -pub mod expr; -pub mod extra_reductions; -pub mod id; -pub mod inductive; -pub mod infer; -pub mod ingress; -mod input; -pub mod ixon_checker; -pub mod lctx; -pub mod level; -pub mod mode; -pub mod perf; -pub mod primitive; -mod primitive_context; -#[cfg(not(target_arch = "riscv64"))] -pub mod role_admission; -// Sharding cost model + partitioner (out-of-circuit). `profile` records -// per-block heartbeats + the delta-unfold graph (the cost graph); `shard` -// partitions that graph into balanced, low-cross-ingress shards. Ported here -// from jcb/kernel-sharding (was the monolithic `crate::ix::{profile,shard}`): -// `profile` must live in the kernel crate because `KEnv` holds a `ProfileSink`. -// -// `profile` is dependency-light (hash maps only) so it compiles for the zkVM -// guest, where the `ProfileSink` field is simply always `None`. `shard` is the -// host-only partitioner — it uses rayon + `std::time` (gated to the same -// non-riscv targets that supply those deps), and the guest never partitions. -pub mod profile; -pub mod resource; -#[cfg(not(target_arch = "riscv64"))] -pub mod shard; -pub mod subst; -pub mod tc; -pub mod whnf; - -#[cfg(test)] -mod contract_tests; - -#[cfg(test)] -pub mod testing; -#[cfg(test)] -mod tutorial; diff --git a/crates/kernel/src/mode.rs b/crates/kernel/src/mode.rs deleted file mode 100644 index abd711e73..000000000 --- a/crates/kernel/src/mode.rs +++ /dev/null @@ -1,459 +0,0 @@ -//! Kernel mode metadata parameterization. -//! -//! All zero kernel types are parameterized by `M: KernelMode`, which controls -//! presence of metadata with `ZMode`: -//! -//! - **type Meta = ZMode**: metadata fields stored as `T`. -//! - **type Anon = ZMode**: metadata fields erased to `()`. -//! -//! `MetaHash` provides serialization into `blake3::Hasher` for callers that -//! explicitly need metadata ordering or diagnostics. Semantic expression and -//! universe hashes deliberately do not include metadata in either mode. - -use std::fmt::{self, Debug}; -use std::hash::Hash; - -use ix_common::env::{BinderInfo, DataValue, Name, NameData}; - -/// Serialize a metadata value into a `blake3::Hasher`. -/// The `()` impl is a no-op, so erased metadata contributes nothing. -pub trait MetaHash { - fn meta_hash(&self, hasher: &mut blake3::Hasher); -} - -impl MetaHash for () { - fn meta_hash(&self, _hasher: &mut blake3::Hasher) {} -} - -impl MetaHash for Name { - fn meta_hash(&self, hasher: &mut blake3::Hasher) { - hasher.update(self.get_hash().as_bytes()); - } -} - -impl MetaHash for BinderInfo { - fn meta_hash(&self, hasher: &mut blake3::Hasher) { - hasher.update(&[match self { - BinderInfo::Default => 0, - BinderInfo::Implicit => 1, - BinderInfo::StrictImplicit => 2, - BinderInfo::InstImplicit => 3, - }]); - } -} - -impl MetaHash for DataValue { - fn meta_hash(&self, hasher: &mut blake3::Hasher) { - ix_common::env::hash_data_value(self, hasher); - } -} - -impl MetaHash for Vec { - fn meta_hash(&self, hasher: &mut blake3::Hasher) { - for item in self { - item.meta_hash(hasher); - } - } -} - -impl MetaHash for (A, B) { - fn meta_hash(&self, hasher: &mut blake3::Hasher) { - self.0.meta_hash(hasher); - self.1.meta_hash(hasher); - } -} - -impl MetaHash for bool { - fn meta_hash(&self, hasher: &mut blake3::Hasher) { - hasher.update(&[u8::from(*self)]); - } -} - -impl MetaHash for Option { - fn meta_hash(&self, hasher: &mut blake3::Hasher) { - match self { - None => { - hasher.update(&[0]); - }, - Some(t) => { - hasher.update(&[1]); - t.meta_hash(hasher); - }, - } - } -} - -/// Check a metadata field for duplicate level parameter names. -/// `Vec` performs the real check; `()` (erased metadata) is a no-op. -pub trait CheckDupLevelParams { - fn has_duplicate_level_params(&self) -> bool; -} - -impl CheckDupLevelParams for Vec { - fn has_duplicate_level_params(&self) -> bool { - for (i, p) in self.iter().enumerate() { - if self[i + 1..].contains(p) { - return true; - } - } - false - } -} - -impl CheckDupLevelParams for () { - fn has_duplicate_level_params(&self) -> bool { - false - } -} - -/// Display metadata conditionally across kernel modes. -/// -/// In Meta mode, concrete types display their content. In Anon mode, `()` signals -/// no content via `has_meta() == false`, and callers provide a positional or hash -/// fallback. This enables a single generic `Display` impl per zero kernel type -/// instead of separate Meta/Anon impls. -pub trait MetaDisplay { - /// Whether this field carries displayable metadata. - /// `false` for `()` (Anon mode) and anonymous `Name`s. - fn has_meta(&self) -> bool; - - /// Format the metadata value. Callers should check `has_meta()` first - /// and provide a fallback (e.g., positional index) when `false`. - fn meta_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result; -} - -impl MetaDisplay for Name { - fn has_meta(&self) -> bool { - !matches!(self.as_data(), NameData::Anonymous(_)) - } - fn meta_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") - } -} - -impl MetaDisplay for BinderInfo { - fn has_meta(&self) -> bool { - true - } - fn meta_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - BinderInfo::Default => Ok(()), - BinderInfo::Implicit => write!(f, "{{}}"), - BinderInfo::StrictImplicit => write!(f, "⦃⦄"), - BinderInfo::InstImplicit => write!(f, "[]"), - } - } -} - -impl MetaDisplay for DataValue { - fn has_meta(&self) -> bool { - true - } - fn meta_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self:?}") - } -} - -impl MetaDisplay for Vec { - fn has_meta(&self) -> bool { - !self.is_empty() - } - fn meta_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for (i, item) in self.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - item.meta_fmt(f)?; - } - Ok(()) - } -} - -impl MetaDisplay for (A, B) { - fn has_meta(&self) -> bool { - self.0.has_meta() || self.1.has_meta() - } - fn meta_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.meta_fmt(f)?; - write!(f, ": ")?; - self.1.meta_fmt(f) - } -} - -impl MetaDisplay for bool { - fn has_meta(&self) -> bool { - true - } - fn meta_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") - } -} - -impl MetaDisplay for () { - fn has_meta(&self) -> bool { - false - } - fn meta_fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - Ok(()) - } -} - -impl MetaDisplay for Option { - fn has_meta(&self) -> bool { - self.as_ref().is_some_and(MetaDisplay::has_meta) - } - fn meta_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Some(t) => t.meta_fmt(f), - None => Ok(()), - } - } -} - -/// Controls metadata behavior for all zero kernel types. -pub trait KernelMode: 'static + Clone + Debug + Send + Sync { - /// `true` iff this mode carries metadata. Enables compile-time - /// pruning: code paths gated by `if M::HAS_META { … }` are - /// monomorphized into the dead-branch-eliminated form for the Anon - /// implementation. Use this to guard metadata lookups so they don't - /// execute in Anon mode. - const HAS_META: bool; - - /// A metadata field: stores `T` in Meta mode, erased to `()` in Anon mode. - type MField: - MetaHash + MetaDisplay + PartialEq + Clone + Debug + Hash + Send + Sync; - - /// Wrap a value into a metadata field. In Anon mode, the value is discarded. - fn meta_field< - T: MetaHash + MetaDisplay + PartialEq + Clone + Debug + Hash + Send + Sync, - >( - val: T, - ) -> Self::MField; - - /// Build a metadata field from a closure. Meta runs the closure to - /// produce the wrapped value; Anon discards the closure unevaluated - /// and returns `()`. The fallible counterpart is `meta_field_try`. - /// - /// Use this at metadata-extraction sites in mode-generic code: the - /// closure body — including arena walks, name resolution, and any - /// other work that only matters in Meta mode — is skipped entirely - /// when `M = Anon`, with no `Name::anon`-style placeholder - /// construction at the call site. - fn meta_field_with(f: F) -> Self::MField - where - T: MetaHash + MetaDisplay + PartialEq + Clone + Debug + Hash + Send + Sync, - F: FnOnce() -> T; - - /// Fallible variant of `meta_field_with`. Meta runs the closure (and - /// may return its `Err`); Anon discards the closure and returns - /// `Ok(())`. Use for sites where missing metadata is a Meta-mode - /// error but a no-op in Anon mode. - fn meta_field_try(f: F) -> Result, E> - where - T: MetaHash + MetaDisplay + PartialEq + Clone + Debug + Hash + Send + Sync, - F: FnOnce() -> Result; - - /// Extract a name from a metadata field when running in Meta mode. - fn meta_name(field: &Self::MField) -> Option; - - /// Borrow the value inside a metadata field: `Some(&T)` in Meta mode, - /// `None` in Anon mode. The generic counterpart of `meta_name`. - fn meta_get< - T: MetaHash + MetaDisplay + PartialEq + Clone + Debug + Hash + Send + Sync, - >( - field: &Self::MField, - ) -> Option<&T>; -} - -/// Const-generic kernel mode. `META` controls metadata fields. -#[derive(Clone, Debug)] -pub struct ZMode; - -/// Full metadata. For debugging, roundtrip validation, and pretty printing. -pub type Meta = ZMode; -/// No metadata. For anonymous structural mode. -pub type Anon = ZMode; - -impl KernelMode for ZMode { - const HAS_META: bool = true; - - type MField< - T: MetaHash + MetaDisplay + PartialEq + Clone + Debug + Hash + Send + Sync, - > = T; - - fn meta_field< - T: MetaHash + MetaDisplay + PartialEq + Clone + Debug + Hash + Send + Sync, - >( - val: T, - ) -> T { - val - } - - fn meta_field_with(f: F) -> T - where - T: MetaHash + MetaDisplay + PartialEq + Clone + Debug + Hash + Send + Sync, - F: FnOnce() -> T, - { - f() - } - - fn meta_field_try(f: F) -> Result - where - T: MetaHash + MetaDisplay + PartialEq + Clone + Debug + Hash + Send + Sync, - F: FnOnce() -> Result, - { - f() - } - - fn meta_name(field: &Name) -> Option { - Some(field.clone()) - } - - fn meta_get< - T: MetaHash + MetaDisplay + PartialEq + Clone + Debug + Hash + Send + Sync, - >( - field: &T, - ) -> Option<&T> { - Some(field) - } -} - -impl KernelMode for ZMode { - const HAS_META: bool = false; - - type MField< - T: MetaHash + MetaDisplay + PartialEq + Clone + Debug + Hash + Send + Sync, - > = (); - - fn meta_field< - T: MetaHash + MetaDisplay + PartialEq + Clone + Debug + Hash + Send + Sync, - >( - _val: T, - ) { - } - - fn meta_field_with(_f: F) - where - T: MetaHash + MetaDisplay + PartialEq + Clone + Debug + Hash + Send + Sync, - F: FnOnce() -> T, - { - // Anon: closure never invoked — the metadata extraction work it - // would have performed (arena walk, name resolution, etc.) is - // entirely skipped. - } - - fn meta_field_try(_f: F) -> Result<(), E> - where - T: MetaHash + MetaDisplay + PartialEq + Clone + Debug + Hash + Send + Sync, - F: FnOnce() -> Result, - { - Ok(()) - } - - fn meta_name(_field: &()) -> Option { - None - } - - fn meta_get< - T: MetaHash + MetaDisplay + PartialEq + Clone + Debug + Hash + Send + Sync, - >( - _field: &(), - ) -> Option<&T> { - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn mk_name(s: &str) -> Name { - Name::str(Name::anon(), s.to_string()) - } - - #[test] - fn meta_field_preserves_value() { - let name = mk_name("x"); - let field = Meta::meta_field(name.clone()); - assert_eq!(field, name); - } - - #[test] - fn anon_field_erases_value() { - let name = mk_name("x"); - Anon::meta_field(name); - assert_eq!((), ()); - } - - #[test] - fn meta_hash_name_writes_bytes() { - let name = mk_name("x"); - let mut h = blake3::Hasher::new(); - name.meta_hash(&mut h); - // Should have written 32 bytes (blake3 hash of name) - let result = h.finalize(); - // Just check it's not the empty hash - assert_ne!( - *result.as_bytes(), - *blake3::Hasher::new().finalize().as_bytes() - ); - } - - #[test] - fn meta_hash_unit_is_noop() { - let mut h1 = blake3::Hasher::new(); - let h2 = blake3::Hasher::new(); - ().meta_hash(&mut h1); - // h1 and h2 should produce identical results - assert_eq!(h1.finalize(), h2.finalize()); - } - - #[test] - fn meta_hash_binder_info_distinct() { - let variants = [ - BinderInfo::Default, - BinderInfo::Implicit, - BinderInfo::StrictImplicit, - BinderInfo::InstImplicit, - ]; - let hashes: Vec = variants - .iter() - .map(|bi| { - let mut h = blake3::Hasher::new(); - bi.meta_hash(&mut h); - h.finalize() - }) - .collect(); - // All 4 should be distinct - for i in 0..hashes.len() { - for j in (i + 1)..hashes.len() { - assert_ne!( - hashes[i], hashes[j], - "BinderInfo variants {i} and {j} hash the same" - ); - } - } - } - - #[test] - fn meta_hash_vec_sequential() { - let names = vec![mk_name("a"), mk_name("b")]; - let mut h1 = blake3::Hasher::new(); - names.meta_hash(&mut h1); - - let mut h2 = blake3::Hasher::new(); - mk_name("a").meta_hash(&mut h2); - mk_name("b").meta_hash(&mut h2); - - assert_eq!(h1.finalize(), h2.finalize()); - } - - #[test] - fn meta_hash_bool() { - let mut h_true = blake3::Hasher::new(); - let mut h_false = blake3::Hasher::new(); - true.meta_hash(&mut h_true); - false.meta_hash(&mut h_false); - assert_ne!(h_true.finalize(), h_false.finalize()); - } -} diff --git a/crates/kernel/src/perf.rs b/crates/kernel/src/perf.rs deleted file mode 100644 index 42868d892..000000000 --- a/crates/kernel/src/perf.rs +++ /dev/null @@ -1,451 +0,0 @@ -//! Performance counters for cache hit-rate and fuel-consumption analysis. -//! -//! The environment counters below use `IX_PERF_COUNTERS=1`; the separate -//! [`same_head`] diagnostic uses `IX_SAME_HEAD_PROFILE=1`. -//! When the variable is unset (production default), every recording call is a -//! single inlined branch on a `LazyLock` and skips the atomic increment -//! entirely. When set, the counters track: -//! -//! - `whnf_cache` and `whnf_no_delta_cache` hit/miss counts (audit §10). -//! - `infer_cache` and `infer_only_cache` hit/miss counts. -//! - `def_eq_cache` hit/miss counts. -//! - `def_eq_failure` set hit and insert counts. -//! - Per-constant peak `MAX_REC_FUEL` consumption (running max across all -//! constants checked, plus a total for averaging). -//! -//! Counters live on [`KEnv`](super::env::KEnv) and are dumped on `Drop` when -//! enabled, so a single `IX_PERF_COUNTERS=1` invocation of any harness that -//! tears down the kernel env (e.g. `rs_kernel_check_consts`) prints a summary -//! at the end. -//! -//! ## Why atomic counters even though we run per-constant in parallel? -//! -//! `KEnv` is shared across many `TypeChecker` threads, so the simplest -//! observability story is shared atomic counters. The `Ordering::Relaxed` -//! increment cost is negligible compared to the work being measured (cache -//! probes themselves involve DashMap shard locks which dwarf an atomic add). -//! When `IX_PERF_COUNTERS` is unset the lazy bool short-circuits even the -//! atomic op. - -use std::fmt; -use std::sync::atomic::{AtomicU64, Ordering}; - -pub(crate) mod hot_misses; -pub mod same_head; - -static PERF_ENABLED: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var_os("IX_PERF_COUNTERS").is_some()); - -/// Returns `true` iff `IX_PERF_COUNTERS` is set in the environment at the -/// time this is first read. The result is cached for the lifetime of the -/// process. -#[inline] -pub fn enabled() -> bool { - *PERF_ENABLED -} - -/// Atomic counters for cache hit-rate analysis. Gated by [`enabled`]. -#[derive(Default, Debug)] -pub struct PerfCounters { - // -- WHNF caches -- - pub whnf_cache_hits: AtomicU64, - pub whnf_cache_misses: AtomicU64, - pub whnf_no_delta_cache_hits: AtomicU64, - pub whnf_no_delta_cache_misses: AtomicU64, - pub whnf_core_cache_hits: AtomicU64, - pub whnf_core_cache_misses: AtomicU64, - - // -- Infer caches -- - pub infer_cache_hits: AtomicU64, - pub infer_cache_misses: AtomicU64, - pub infer_only_cache_hits: AtomicU64, - pub infer_only_cache_misses: AtomicU64, - /// Selectively materialized dependent application-prefix types. - pub dependent_prefix_inserts: AtomicU64, - - // -- Def-eq caches -- - pub def_eq_cache_hits: AtomicU64, - pub def_eq_cache_misses: AtomicU64, - pub def_eq_failure_hits: AtomicU64, - pub def_eq_failure_inserts: AtomicU64, - - // -- Unfold cache (constant body instantiation) -- - pub unfold_cache_hits: AtomicU64, - pub unfold_cache_misses: AtomicU64, - - // -- isProp cache (propositional-type detection for proof irrelevance) -- - pub is_prop_cache_hits: AtomicU64, - pub is_prop_cache_misses: AtomicU64, - - // -- Conservative declaration summaries -- - pub decl_summary_hits: AtomicU64, - pub decl_summary_misses: AtomicU64, - pub non_proof_skips: AtomicU64, - - // -- Recursive fuel -- - /// Running max of fuel actually consumed by any single constant check. - pub peak_rec_fuel_used: AtomicU64, - /// Cumulative fuel consumed across every constant check. - pub total_rec_fuel_used: AtomicU64, - /// Number of constants whose fuel was tracked (for averaging). - pub constants_checked: AtomicU64, -} - -/// Helper for the "record a cache hit" pattern: increments a counter only if -/// the global toggle is on. Marked `#[inline(always)]` so the unset-path -/// collapses to a single branch + return. -#[inline(always)] -fn bump(counter: &AtomicU64) { - if enabled() { - counter.fetch_add(1, Ordering::Relaxed); - } -} - -impl PerfCounters { - // ----------------------------------------------------------------------- - // WHNF caches - // ----------------------------------------------------------------------- - - pub fn record_whnf_hit(&self) { - bump(&self.whnf_cache_hits); - } - - pub fn record_whnf_miss(&self) { - bump(&self.whnf_cache_misses); - } - - pub fn record_whnf_no_delta_hit(&self) { - bump(&self.whnf_no_delta_cache_hits); - } - - pub fn record_whnf_no_delta_miss(&self) { - bump(&self.whnf_no_delta_cache_misses); - } - - pub fn record_whnf_core_hit(&self) { - bump(&self.whnf_core_cache_hits); - } - - pub fn record_whnf_core_miss(&self) { - bump(&self.whnf_core_cache_misses); - } - - // ----------------------------------------------------------------------- - // Infer caches - // ----------------------------------------------------------------------- - - pub fn record_infer_hit(&self) { - bump(&self.infer_cache_hits); - } - - pub fn record_infer_miss(&self) { - bump(&self.infer_cache_misses); - } - - pub fn record_infer_only_hit(&self) { - bump(&self.infer_only_cache_hits); - } - - pub fn record_infer_only_miss(&self) { - bump(&self.infer_only_cache_misses); - } - - pub fn record_dependent_prefix_insert(&self) { - bump(&self.dependent_prefix_inserts); - } - - // ----------------------------------------------------------------------- - // Def-eq caches - // ----------------------------------------------------------------------- - - pub fn record_def_eq_hit(&self) { - bump(&self.def_eq_cache_hits); - } - - pub fn record_def_eq_miss(&self) { - bump(&self.def_eq_cache_misses); - } - - pub fn record_def_eq_failure_hit(&self) { - bump(&self.def_eq_failure_hits); - } - - pub fn record_def_eq_failure_insert(&self) { - bump(&self.def_eq_failure_inserts); - } - - // ----------------------------------------------------------------------- - // Unfold cache - // ----------------------------------------------------------------------- - - pub fn record_unfold_hit(&self) { - bump(&self.unfold_cache_hits); - } - - pub fn record_unfold_miss(&self) { - bump(&self.unfold_cache_misses); - } - - // ----------------------------------------------------------------------- - // isProp cache - // ----------------------------------------------------------------------- - - pub fn record_is_prop_hit(&self) { - bump(&self.is_prop_cache_hits); - } - - pub fn record_is_prop_miss(&self) { - bump(&self.is_prop_cache_misses); - } - - pub fn record_decl_summary_hit(&self) { - bump(&self.decl_summary_hits); - } - pub fn record_decl_summary_miss(&self) { - bump(&self.decl_summary_misses); - } - pub fn record_non_proof_skip(&self) { - bump(&self.non_proof_skips); - } - - // ----------------------------------------------------------------------- - // Recursive fuel - // ----------------------------------------------------------------------- - - /// Record the fuel actually consumed by a single constant check. Updates - /// both the running max and the cumulative total. No-op when disabled. - pub fn record_constant_fuel_used(&self, used: u64) { - if !enabled() { - return; - } - self.total_rec_fuel_used.fetch_add(used, Ordering::Relaxed); - self.constants_checked.fetch_add(1, Ordering::Relaxed); - - // CAS loop on peak. Worst-case contention is O(threads); we expect very - // few peak updates over the life of a check, so this is cheap. - let mut current = self.peak_rec_fuel_used.load(Ordering::Relaxed); - while used > current { - match self.peak_rec_fuel_used.compare_exchange_weak( - current, - used, - Ordering::Relaxed, - Ordering::Relaxed, - ) { - Ok(_) => break, - Err(actual) => current = actual, - } - } - } - - // ----------------------------------------------------------------------- - // Reporting - // ----------------------------------------------------------------------- - - /// Render a one-shot human-readable summary. Cheap to call (a single - /// load of each counter) and safe to call concurrently with recording. - /// - /// When [`enabled`] is false the summary is empty so callers can dump - /// unconditionally. - pub fn summary(&self) -> String { - if !enabled() { - return String::new(); - } - let mut s = String::with_capacity(1024); - let _ = self.write_summary(&mut s); - s - } - - fn write_summary(&self, out: &mut impl fmt::Write) -> fmt::Result { - writeln!(out, "[ix-perf] cache hit rates:")?; - write_rate( - out, - " whnf_cache ", - &self.whnf_cache_hits, - &self.whnf_cache_misses, - )?; - write_rate( - out, - " whnf_no_delta ", - &self.whnf_no_delta_cache_hits, - &self.whnf_no_delta_cache_misses, - )?; - write_rate( - out, - " whnf_core ", - &self.whnf_core_cache_hits, - &self.whnf_core_cache_misses, - )?; - write_rate( - out, - " infer_cache ", - &self.infer_cache_hits, - &self.infer_cache_misses, - )?; - write_rate( - out, - " infer_only_cache ", - &self.infer_only_cache_hits, - &self.infer_only_cache_misses, - )?; - writeln!( - out, - " dependent_prefix_inserts={}", - self.dependent_prefix_inserts.load(Ordering::Relaxed) - )?; - write_rate( - out, - " def_eq_cache ", - &self.def_eq_cache_hits, - &self.def_eq_cache_misses, - )?; - write_rate( - out, - " unfold_cache ", - &self.unfold_cache_hits, - &self.unfold_cache_misses, - )?; - write_rate( - out, - " is_prop_cache ", - &self.is_prop_cache_hits, - &self.is_prop_cache_misses, - )?; - - let fail_hits = self.def_eq_failure_hits.load(Ordering::Relaxed); - write_rate( - out, - " decl_summary ", - &self.decl_summary_hits, - &self.decl_summary_misses, - )?; - writeln!( - out, - " non_proof_skips {}", - self.non_proof_skips.load(Ordering::Relaxed) - )?; - let fail_inserts = self.def_eq_failure_inserts.load(Ordering::Relaxed); - writeln!( - out, - " def_eq_failure {fail_hits} hits, {fail_inserts} inserts" - )?; - - let peak = self.peak_rec_fuel_used.load(Ordering::Relaxed); - let total = self.total_rec_fuel_used.load(Ordering::Relaxed); - let n = self.constants_checked.load(Ordering::Relaxed); - let avg = total.checked_div(n).unwrap_or(0); - writeln!(out, "[ix-perf] rec_fuel:")?; - writeln!( - out, - " peak/avg per constant: {peak} / {avg} ({n} constants checked, {total} total)" - ) - } -} - -// -- Reduction histograms (IX_REDUCE_HISTO) -------------------------------- -// -// Per-constant counts of delta unfolds and iota (recursor) reductions, -// process-global so a harness can run a single block and ask "which -// constants did the kernel grind on?". Gated separately from the cache -// counters because the DashMap entry bump is heavier than an atomic add. - -static REDUCE_HISTO_ENABLED: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var_os("IX_REDUCE_HISTO").is_some()); - -/// Returns `true` iff `IX_REDUCE_HISTO` is set in the environment. -#[inline] -pub fn reduce_histo_enabled() -> bool { - *REDUCE_HISTO_ENABLED -} - -/// Delta-unfold counts keyed by the unfolded constant's address. -pub static DELTA_HISTO: std::sync::LazyLock< - dashmap::DashMap, -> = std::sync::LazyLock::new(dashmap::DashMap::new); - -/// Iota-reduction counts keyed by the recursor's address. -pub static IOTA_HISTO: std::sync::LazyLock< - dashmap::DashMap, -> = std::sync::LazyLock::new(dashmap::DashMap::new); - -/// Count of `Nat.succ` peels performed by `try_reduce_nat_succ_iter`. -pub static NAT_SUCC_PEELS: AtomicU64 = AtomicU64::new(0); - -#[inline] -pub fn record_delta_histo(addr: &ix_common::address::Address) { - if reduce_histo_enabled() { - *DELTA_HISTO.entry(addr.clone()).or_insert(0) += 1; - } -} - -#[inline] -pub fn record_iota_histo(addr: &ix_common::address::Address) { - if reduce_histo_enabled() { - *IOTA_HISTO.entry(addr.clone()).or_insert(0) += 1; - } -} - -#[inline] -pub fn record_nat_succ_peel() { - if reduce_histo_enabled() { - NAT_SUCC_PEELS.fetch_add(1, Ordering::Relaxed); - } -} - -fn write_rate( - out: &mut impl fmt::Write, - label: &str, - hits: &AtomicU64, - misses: &AtomicU64, -) -> fmt::Result { - let h = hits.load(Ordering::Relaxed); - let m = misses.load(Ordering::Relaxed); - let total = h + m; - if total == 0 { - return writeln!(out, "{label} (no probes)"); - } - // 1-decimal rate is plenty for human reading. - #[allow(clippy::cast_precision_loss)] - let rate = (h as f64) / (total as f64) * 100.0; - writeln!(out, "{label} {h:>10} hits / {total:>10} total ({rate:>5.1}%)") -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn disabled_summary_is_empty() { - // Default test environment doesn't set IX_PERF_COUNTERS, so summary() - // should return an empty string regardless of recorded counts. - let p = PerfCounters::default(); - p.record_whnf_hit(); - p.record_whnf_miss(); - if !enabled() { - assert_eq!(p.summary(), ""); - } - } - - #[test] - fn rate_formatting_handles_zero_probes() { - let mut s = String::new(); - let h = AtomicU64::new(0); - let m = AtomicU64::new(0); - write_rate(&mut s, "test", &h, &m).unwrap(); - assert!(s.contains("no probes")); - } - - #[test] - fn peak_fuel_is_running_max() { - let p = PerfCounters::default(); - // Even when disabled, calls are no-ops so the test only checks shape. - if enabled() { - p.record_constant_fuel_used(100); - p.record_constant_fuel_used(50); - p.record_constant_fuel_used(200); - p.record_constant_fuel_used(150); - assert_eq!(p.peak_rec_fuel_used.load(Ordering::Relaxed), 200); - assert_eq!(p.total_rec_fuel_used.load(Ordering::Relaxed), 500); - assert_eq!(p.constants_checked.load(Ordering::Relaxed), 4); - } - } -} diff --git a/crates/kernel/src/perf/hot_misses.rs b/crates/kernel/src/perf/hot_misses.rs deleted file mode 100644 index 247809e33..000000000 --- a/crates/kernel/src/perf/hot_misses.rs +++ /dev/null @@ -1,282 +0,0 @@ -//! Bounded, diagnostic-only heavy hitters. No expression graphs are retained. -//! -//! Space-Saving counters replace the least frequent tracked key when full. -//! A replacement inherits that counter as its error bound. For each retained -//! key, the actual event count lies in [count - error, count]; it is exact -//! before any eviction. Unlike keeping just the first N keys, late hotspots -//! can enter the report. An indexed min-heap bounds storage AND update work -//! (O(log N)); a lazy heap would accumulate stale entries without bound. - -use std::fmt::{self, Write}; - -use rustc_hash::FxHashMap; - -use crate::env::{Addr, CtxAddr}; - -const MAX_ENTRIES: usize = 4096; -const MAX_LABEL_BYTES: usize = 512; - -/// Exact diagnostic identity, independent of the truncated display label. -/// `context` is present only for IX_HOT_MISS_CTX. FVars remain distinguished -/// by their expression UID, just as in the previous string-keyed report. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(crate) struct MissKey { - pub(crate) phase: &'static str, - pub(crate) a: Addr, - pub(crate) b: Option, - pub(crate) context: Option<(CtxAddr, u64)>, -} - -struct Entry { - key: MissKey, - label: String, - count: u64, - error: u64, -} - -#[derive(Default)] -pub(crate) struct HotMisses { - positions: FxHashMap, - heap: Vec, - events: u64, - replacements: u64, -} - -impl HotMisses { - pub(crate) fn record( - &mut self, - key: MissKey, - label: impl FnOnce(&mut Label) -> fmt::Result, - ) { - self.record_with_capacity(key, label, MAX_ENTRIES); - } - - fn record_with_capacity( - &mut self, - key: MissKey, - label: impl FnOnce(&mut Label) -> fmt::Result, - capacity: usize, - ) { - self.events = self.events.saturating_add(1); - if let Some(&i) = self.positions.get(&key) { - self.heap[i].count = self.heap[i].count.saturating_add(1); - self.sift_down(i); - return; - } - // Format ONLY on admission, not on every hit. Label storage is bounded - // even for enormous metadata names. Keys hold only UIDs/context hashes. - let mut text = Label(String::with_capacity(MAX_LABEL_BYTES)); - let _ = label(&mut text); - if self.heap.len() < capacity { - let i = self.heap.len(); - self.heap.push(Entry { key, label: text.0, count: 1, error: 0 }); - self.positions.insert(key, i); - let mut i = i; - while i > 0 { - let parent = (i - 1) / 2; - if self.heap[parent].count <= self.heap[i].count { - break; - } - self.swap(i, parent); - i = parent; - } - } else { - let min = &self.heap[0]; - let error = min.count; - self.positions.remove(&min.key); - self.heap[0] = - Entry { key, label: text.0, count: error.saturating_add(1), error }; - self.positions.insert(key, 0); - self.replacements = self.replacements.saturating_add(1); - self.sift_down(0); - } - } - - fn swap(&mut self, a: usize, b: usize) { - self.heap.swap(a, b); - *self.positions.get_mut(&self.heap[a].key).unwrap() = a; - *self.positions.get_mut(&self.heap[b].key).unwrap() = b; - } - - fn sift_down(&mut self, mut i: usize) { - loop { - let left = 2 * i + 1; - if left >= self.heap.len() { - return; - } - let right = left + 1; - let child = if right < self.heap.len() - && self.heap[right].count < self.heap[left].count - { - right - } else { - left - }; - if self.heap[i].count <= self.heap[child].count { - return; - } - self.swap(i, child); - i = child; - } - } - - pub(crate) fn clear(&mut self) { - self.positions.clear(); - self.heap.clear(); - self.events = 0; - self.replacements = 0; - } - - pub(crate) fn summary(&self) -> String { - if self.heap.is_empty() { - return String::new(); - } - let mut entries: Vec<_> = self.heap.iter().collect(); - entries.sort_unstable_by(|a, b| { - b.count.cmp(&a.count).then_with(|| a.label.cmp(&b.label)) - }); - let mut out = format!( - "[hot misses] events={} tracked={}/{} replacements={}; top {}; counts are [lower, upper] bounds, not exact after replacement:\n", - self.events, - entries.len(), - MAX_ENTRIES, - self.replacements, - entries.len().min(25) - ); - if self.events == u64::MAX { - out.push_str(" counters saturated: upper bounds may be truncated\n"); - } - for entry in entries.into_iter().take(25) { - let _ = writeln!( - out, - " [{:>8}, {:>8}] {}", - entry.count - entry.error, - entry.count, - entry.label - ); - } - out - } -} - -/// A UTF-8-safe bounded formatter. Returning Err stops Display writers before -/// they accumulate an arbitrarily large diagnostic string. The ellipsis is -/// included in the bound. Labels are never used as identities. -pub(crate) struct Label(String); - -impl Write for Label { - fn write_str(&mut self, s: &str) -> fmt::Result { - let remaining = MAX_LABEL_BYTES - self.0.len(); - if s.len() <= remaining { - self.0.push_str(s); - return Ok(()); - } - let prefix = self.0.len().min(MAX_LABEL_BYTES - 3); - self.0.truncate(self.0.floor_char_boundary(prefix)); - let end = s.floor_char_boundary(MAX_LABEL_BYTES - 3 - self.0.len()); - self.0.push_str(&s[..end]); - self.0.push_str("..."); - Err(fmt::Error) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn key(a: u64) -> MissKey { - MissKey { phase: "infer", a, b: None, context: None } - } - - fn record(misses: &mut HotMisses, id: u64, capacity: usize) { - misses.record_with_capacity(key(id), |s| write!(s, "uid{id}"), capacity); - } - - fn invariant(misses: &HotMisses, exact: &FxHashMap) { - assert_eq!(misses.heap.len(), misses.positions.len()); - assert_eq!(misses.events, exact.values().sum()); - assert_eq!(misses.events, misses.heap.iter().map(|e| e.count).sum()); - for (i, e) in misses.heap.iter().enumerate() { - assert_eq!(misses.positions[&e.key], i); - assert!(e.count - e.error <= exact[&e.key.a]); - assert!(exact[&e.key.a] <= e.count); - if i > 0 { - assert!(misses.heap[(i - 1) / 2].count <= e.count); - } - } - } - - #[test] - fn exact_until_full_and_labels_are_only_formatted_on_misses() { - let mut misses = HotMisses::default(); - record(&mut misses, 1, 4); - misses.record(key(1), |_| panic!("do not format an existing key")); - record(&mut misses, 2, 4); - assert_eq!(misses.replacements, 0); - assert_eq!(misses.heap[misses.positions[&key(1)]].count, 2); - assert!(misses.heap.iter().all(|e| e.error == 0)); - } - - #[test] - fn bounded_heap_and_error_intervals_match_exact_counts() { - for capacity in [1, 2, 7, 32] { - let mut misses = HotMisses::default(); - let mut exact = FxHashMap::default(); - let mut random = 1u64; - for i in 0..10_000 { - random = random.wrapping_mul(6364136223846793005).wrapping_add(1); - let id = if i % 3 == 0 { i % 5 } else { random >> 48 }; - record(&mut misses, id, capacity); - *exact.entry(id).or_default() += 1; - assert!(misses.heap.len() <= capacity); - invariant(&misses, &exact); - } - } - } - - #[test] - fn late_hotspots_displace_cold_entries_and_reset_clears_counts() { - let mut misses = HotMisses::default(); - for i in 0..100_000 { - record(&mut misses, i, MAX_ENTRIES); - } - for _ in 0..10_000 { - record(&mut misses, 100_001, MAX_ENTRIES); - } - assert_eq!(misses.heap.len(), MAX_ENTRIES); - assert_eq!(misses.positions.len(), MAX_ENTRIES); - let hot = &misses.heap[misses.positions[&key(100_001)]]; - assert_eq!(hot.count - hot.error, 10_000); - assert!(misses.summary().contains("uid100001")); - assert_eq!(misses.summary().lines().count(), 26); - misses.clear(); - assert!(misses.summary().is_empty()); - assert!(misses.positions.is_empty()); - assert_eq!(misses.events, 0); - assert_eq!(misses.replacements, 0); - record(&mut misses, 1, MAX_ENTRIES); - assert_eq!(misses.heap[0].count, 1); - assert_eq!(misses.heap[0].error, 0); - } - - #[test] - fn labels_are_bounded_utf8_and_never_merge_distinct_keys() { - let mut misses = HotMisses::default(); - for id in 0..2 { - misses.record(key(id), |s| write!(s, "{}", "λ".repeat(100_000))); - } - assert_eq!(misses.heap.len(), 2); - for e in &misses.heap { - assert!(e.label.len() <= MAX_LABEL_BYTES); - assert!(e.label.ends_with("...")); - } - let mut label = Label("λ".repeat(256)); - assert!(label.write_str("λ").is_err()); - assert!(label.0.len() <= MAX_LABEL_BYTES); - let context = Some((blake3::hash(b"ctx"), 3)); - misses.record(MissKey { context, ..key(0) }, |s| write!(s, "context")); - misses.record(MissKey { b: Some(0), ..key(0) }, |s| write!(s, "pair")); - misses.record(MissKey { phase: "whnf", ..key(0) }, |s| write!(s, "phase")); - assert_eq!(misses.heap.len(), 5); - } -} diff --git a/crates/kernel/src/perf/same_head.rs b/crates/kernel/src/perf/same_head.rs deleted file mode 100644 index da9aa71bf..000000000 --- a/crates/kernel/src/perf/same_head.rs +++ /dev/null @@ -1,383 +0,0 @@ -//! Diagnostic-only accounting of actual same-head conversion attempts. -//! -//! `IX_SAME_HEAD_PROFILE=1` records completed attempts by outcome and head. -//! Inclusive fuel overlaps for nested attempts; exclusive fuel subtracts -//! nested attempts, and root fuel counts each charged tick at most once. -//! Failed-attempt fuel is not necessarily all avoidable: even an abandoned -//! comparison can populate useful completed-result caches. Skipped probes -//! are counted separately and never reported as attempted comparisons. -//! No expression graphs are retained and no checking policy is changed. - -use ix_common::address::Address; - -static ENABLED: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var_os("IX_SAME_HEAD_PROFILE").is_some()); - -#[inline] -pub(crate) fn enabled() -> bool { - *ENABLED -} - -#[derive(Clone, Copy, Debug)] -pub(crate) enum Outcome { - Success, - Miss, - FuelAbort, - DepthAbort, - Error, -} - -#[cfg(not(target_os = "zkvm"))] -const OUTCOMES: [&str; 5] = - ["success", "miss", "fuel_abort", "depth_abort", "error"]; - -#[derive(Clone, Copy)] -pub(crate) enum Skip { - Window, - FailureCache, - Backoff, -} - -#[cfg(not(target_os = "zkvm"))] -mod native { - use super::*; - use rustc_hash::FxHashMap; - use std::{cell::RefCell, fmt::Write}; - - const MAX_HEADS: usize = 1024; - - #[derive(Default, Clone, Copy)] - struct Counts { - calls: u64, - inclusive: u64, - exclusive: u64, - roots: u64, - root_fuel: u64, - max: u64, - ge4096: u64, - ge65536: u64, - ge1000000: u64, - } - - impl Counts { - fn record(&mut self, fuel: u64, child_fuel: u64, root: bool) { - self.calls += 1; - self.inclusive = self.inclusive.saturating_add(fuel); - self.exclusive = - self.exclusive.saturating_add(fuel.saturating_sub(child_fuel)); - if root { - self.roots += 1; - self.root_fuel = self.root_fuel.saturating_add(fuel); - } - self.max = self.max.max(fuel); - self.ge4096 += u64::from(fuel >= 4096); - self.ge65536 += u64::from(fuel >= 65536); - self.ge1000000 += u64::from(fuel >= 1_000_000); - } - - fn line(&self, out: &mut String, label: &str, outcome: &str) { - if self.calls == 0 { - return; - } - let _ = writeln!( - out, - " {label} outcome={outcome} calls={} inclusive_fuel={} exclusive_fuel={} roots={} root_fuel={} max_fuel={} ge4096={} ge65536={} ge1000000={}", - self.calls, - self.inclusive, - self.exclusive, - self.roots, - self.root_fuel, - self.max, - self.ge4096, - self.ge65536, - self.ge1000000 - ); - } - } - - struct Frame { - head: Address, - regular: bool, - children: u64, - } - - #[derive(Default)] - pub(super) struct State { - stack: Vec, - totals: [[Counts; OUTCOMES.len()]; 2], - heads: FxHashMap<(Address, bool), [Counts; OUTCOMES.len()]>, - overflow: [Counts; OUTCOMES.len()], - skips: [[u64; 3]; 2], - max_depth: usize, - accounting_errors: u64, - root_traces: usize, - } - - fn class(regular: bool) -> &'static str { - if regular { "regular" } else { "non_regular" } - } - - impl State { - pub(super) fn begin(&mut self, head: &Address, regular: bool) -> usize { - let ticket = self.stack.len(); - self.stack.push(Frame { head: head.clone(), regular, children: 0 }); - self.max_depth = self.max_depth.max(self.stack.len()); - ticket - } - - pub(super) fn finish( - &mut self, - ticket: usize, - fuel: u64, - outcome: Outcome, - ) { - let frame = self.stack.pop().expect("paired diagnostic begin/finish"); - assert_eq!(ticket, self.stack.len(), "same-head diagnostic stack order"); - let root = self.stack.is_empty(); - self.accounting_errors += u64::from(frame.children > fuel); - if let Some(parent) = self.stack.last_mut() { - parent.children = parent.children.saturating_add(fuel); - } - let outcome = outcome as usize; - self.totals[usize::from(frame.regular)][outcome].record( - fuel, - frame.children, - root, - ); - let key = (frame.head, frame.regular); - if self.heads.len() < MAX_HEADS || self.heads.contains_key(&key) { - self.heads.entry(key).or_default()[outcome].record( - fuel, - frame.children, - root, - ); - } else { - // Keep global accounting exact even when per-head attribution fills. - self.overflow[outcome].record(fuel, frame.children, root); - } - } - - pub(super) fn skip(&mut self, regular: bool, reason: Skip) { - self.skips[usize::from(regular)][reason as usize] += 1; - } - - pub(super) fn take_root_trace( - &mut self, - ticket: Option, - fuel: u64, - ) -> Option { - if ticket != Some(0) || fuel < 65_536 || self.root_traces >= 32 { - return None; - } - self.root_traces += 1; - Some(self.root_traces) - } - - pub(super) fn summary(&self) -> String { - let mut out = format!( - "[same-head-profile] thread-local; inclusive fuel overlaps; root/exclusive fuel do not; active={} max_depth={} tracked_heads={} accounting_errors={}\n", - self.stack.len(), - self.max_depth, - self.heads.len(), - self.accounting_errors - ); - for regular in [true, false] { - let cls = class(regular); - let _ = writeln!( - out, - " {cls} skipped_window={} skipped_failure_cache={} skipped_backoff={}", - self.skips[usize::from(regular)][0], - self.skips[usize::from(regular)][1], - self.skips[usize::from(regular)][2] - ); - for (outcome, c) in - OUTCOMES.iter().zip(self.totals[usize::from(regular)]) - { - c.line(&mut out, cls, outcome); - } - } - let mut heads: Vec<_> = self.heads.iter().collect(); - heads.sort_unstable_by(|(ak, a), (bk, b)| { - let cost = |cs: &[Counts; OUTCOMES.len()]| -> u128 { - cs.iter().map(|c| u128::from(c.exclusive)).sum() - }; - cost(b).cmp(&cost(a)).then_with(|| ak.cmp(bk)) - }); - for ((head, regular), counts) in heads.iter().take(20).copied() { - let label = format!("head=#{} {}", head.hex(), class(*regular)); - for (outcome, c) in OUTCOMES.iter().zip(counts) { - c.line(&mut out, &label, outcome); - } - } - // A mostly nested head can have little exclusive cost but be the root - // that repeatedly admits a large subtree. Show that attribution too. - heads.sort_unstable_by(|(ak, a), (bk, b)| { - let cost = |cs: &[Counts; OUTCOMES.len()]| -> u128 { - cs.iter().map(|c| u128::from(c.root_fuel)).sum() - }; - cost(b).cmp(&cost(a)).then_with(|| ak.cmp(bk)) - }); - for ((head, regular), counts) in heads.into_iter().take(20) { - for (outcome, c) in OUTCOMES.iter().zip(counts) { - if c.roots > 0 { - let _ = writeln!( - out, - " root_head=#{} {} outcome={outcome} roots={} root_fuel={}", - head.hex(), - class(*regular), - c.roots, - c.root_fuel - ); - } - } - } - for (outcome, c) in OUTCOMES.iter().zip(self.overflow) { - c.line(&mut out, "untracked_heads", outcome); - } - out - } - } - - thread_local! { - pub(super) static STATE: RefCell = RefCell::new(State::default()); - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn nested_fuel_is_not_double_counted_and_skips_are_not_attempts() { - let mut s = State::default(); - let head = Address::hash(b"head"); - let root = s.begin(&head, true); - let child = s.begin(&head, false); - s.finish(child, 70, Outcome::Success); - s.skip(false, Skip::Window); - s.finish(root, 100, Outcome::Miss); - let child = s.totals[0][Outcome::Success as usize]; - let parent = s.totals[1][Outcome::Miss as usize]; - assert_eq!(child.exclusive + parent.exclusive, 100); - assert_eq!(child.root_fuel + parent.root_fuel, 100); - assert_eq!(child.inclusive + parent.inclusive, 170); - assert_eq!(s.skips[0][0], 1); - assert_eq!(child.calls + parent.calls, 2); - assert!(s.stack.is_empty()); - assert_eq!(s.accounting_errors, 0); - } - - #[test] - fn root_cost_is_reported_even_when_exclusive_cost_is_zero() { - let mut s = State::default(); - let head = Address::hash(b"root with nested cost"); - let root = s.begin(&head, true); - let child = s.begin(&Address::hash(b"child"), false); - s.finish(child, 100_000, Outcome::Miss); - s.finish(root, 100_000, Outcome::FuelAbort); - for i in 0u64..21 { - let t = s.begin(&Address::hash(&i.to_le_bytes()), true); - s.finish(t, 1, Outcome::Success); - } - let report = s.summary(); - assert!(!report.contains(&format!("\n head=#{} ", head.hex()))); - assert!(report.contains(&format!( - "root_head=#{} regular outcome=fuel_abort roots=1 root_fuel=100000", - head.hex() - ))); - } - - #[test] - fn root_trace_is_bounded_and_never_reports_nested_or_disabled_probes() { - let mut s = State::default(); - assert_eq!(s.take_root_trace(None, 100_000), None); - assert_eq!(s.take_root_trace(Some(1), 100_000), None); - assert_eq!(s.take_root_trace(Some(0), 65_535), None); - for i in 1..=32 { - assert_eq!(s.take_root_trace(Some(0), 65_536), Some(i)); - } - assert_eq!(s.take_root_trace(Some(0), 100_000), None); - } - - #[test] - fn aborts_have_cost_but_skips_do_not_and_head_storage_is_bounded() { - let mut s = State::default(); - for i in 0..MAX_HEADS + 1 { - let t = s.begin(&Address::hash(&i.to_le_bytes()), true); - s.finish(t, 4096, Outcome::FuelAbort); - } - let t = s.begin(&Address::hash(b"depth"), false); - s.finish(t, 19, Outcome::DepthAbort); - s.skip(true, Skip::FailureCache); - assert_eq!(s.heads.len(), MAX_HEADS); - assert_eq!(s.overflow[Outcome::FuelAbort as usize].calls, 1); - let totals = s.totals[1][Outcome::FuelAbort as usize]; - assert_eq!(totals.root_fuel, 4096 * (MAX_HEADS as u64 + 1)); - assert_eq!(totals.ge4096, MAX_HEADS as u64 + 1); - assert_eq!(totals.ge65536, 0); - assert!(s.summary().contains("outcome=depth_abort calls=1")); - } - } -} - -pub(crate) fn begin(head: &Address, regular: bool) -> Option { - if !enabled() { - return None; - } - #[cfg(not(target_os = "zkvm"))] - return Some(native::STATE.with(|s| s.borrow_mut().begin(head, regular))); - #[cfg(target_os = "zkvm")] - { - let _ = (head, regular); - None - } -} - -pub(crate) fn finish(ticket: Option, fuel: u64, outcome: Outcome) { - #[cfg(not(target_os = "zkvm"))] - if let Some(ticket) = ticket { - native::STATE.with(|s| s.borrow_mut().finish(ticket, fuel, outcome)); - } - #[cfg(target_os = "zkvm")] - let _ = (ticket, fuel, outcome); -} - -pub(crate) fn skip(regular: bool, reason: Skip) { - if enabled() { - #[cfg(not(target_os = "zkvm"))] - native::STATE.with(|s| s.borrow_mut().skip(regular, reason)); - } - #[cfg(target_os = "zkvm")] - let _ = (regular, reason); -} - -/// Admit at most 32 expensive root snapshots per thread-local check. Callers -/// only format the expressions after admission; no expression is retained. -pub(crate) fn take_root_trace( - ticket: Option, - fuel: u64, -) -> Option { - #[cfg(not(target_os = "zkvm"))] - return native::STATE.with(|s| s.borrow_mut().take_root_trace(ticket, fuel)); - #[cfg(target_os = "zkvm")] - { - let _ = (ticket, fuel); - None - } -} - -/// Clear only this thread's diagnostic state, between subject checks. -pub fn reset() { - #[cfg(not(target_os = "zkvm"))] - native::STATE.with(|s| *s.borrow_mut() = native::State::default()); -} - -/// Report only this thread; no effect on checking or the subject JSON. -pub fn summary() -> String { - if !enabled() { - return String::new(); - } - #[cfg(not(target_os = "zkvm"))] - return native::STATE.with(|s| s.borrow().summary()); - #[cfg(target_os = "zkvm")] - String::new() -} diff --git a/crates/kernel/src/primitive.rs b/crates/kernel/src/primitive.rs deleted file mode 100644 index b5f78b6ce..000000000 --- a/crates/kernel/src/primitive.rs +++ /dev/null @@ -1,834 +0,0 @@ -//! Well-known primitive constant KIds. -//! -//! Content-addresses are hardcoded blake3 hashes matching the kernel's -//! `build_primitives` in `src/ix/kernel/ingress.rs`. Regenerate with -//! `lake test -- rust-kernel-build-primitives`, which dumps the current -//! `(name, hex)` pairs for every `kernelPrimitives` entry — paste the -//! updated lines into `PrimAddrs::new`. -//! -//! `Primitives` stores `KId` values, resolved from the environment by -//! address so that names match in both Meta and Anon modes. `Lean.reduceBool` -//! and `Lean.reduceNat` are real constants, recognized by content address so -//! that the kernel can decline them (see [`native_evaluation_addrs`]). -//! `eager_reduce` is a synthetic kernel-only marker because Lean's -//! `eagerReduce` compiles to the same canonical content address as `id`; -//! address-only dispatch on the real constant would be unsound. - -use std::sync::LazyLock; - -use ix_common::address::Address; - -use super::env::KEnv; -use super::id::KId; -use super::mode::KernelMode; - -/// Well-known primitive KIds. -#[derive(Clone)] -pub struct Primitives { - // -- Nat -- - pub nat: KId, - pub nat_zero: KId, - pub nat_succ: KId, - pub nat_add: KId, - pub nat_pred: KId, - pub nat_sub: KId, - pub nat_mul: KId, - pub nat_pow: KId, - pub nat_gcd: KId, - pub nat_mod: KId, - pub nat_div: KId, - pub nat_bitwise: KId, - pub nat_beq: KId, - pub nat_ble: KId, - pub nat_land: KId, - pub nat_lor: KId, - pub nat_xor: KId, - pub nat_shift_left: KId, - pub nat_shift_right: KId, - - // -- Bool -- - pub bool_type: KId, - pub bool_true: KId, - pub bool_false: KId, - - // -- String / Char -- - pub string: KId, - pub string_mk: KId, - pub char_type: KId, - pub char_mk: KId, - pub char_of_nat: KId, - pub string_of_list: KId, - pub string_to_byte_array: KId, - pub byte_array_empty: KId, - - // -- List -- - pub list: KId, - pub list_nil: KId, - pub list_cons: KId, - - // -- Eq -- - pub eq: KId, - pub eq_refl: KId, - - // -- Quotient -- - pub quot_type: KId, - pub quot_ctor: KId, - pub quot_lift: KId, - pub quot_ind: KId, - - // -- Reduction markers -- - pub reduce_bool: KId, - pub reduce_nat: KId, - pub eager_reduce: KId, - - // -- Platform -- - pub system_platform_num_bits: KId, - pub system_platform_get_num_bits: KId, - pub subtype_val: KId, - - // -- Decidable / Nat comparison -- - pub nat_dec_le: KId, - pub nat_dec_eq: KId, - pub nat_dec_lt: KId, - pub decidable_rec: KId, - pub decidable_is_true: KId, - pub decidable_is_false: KId, - pub nat_le_of_ble_eq_true: KId, - pub nat_not_le_of_not_ble_eq_true: KId, - pub nat_eq_of_beq_eq_true: KId, - pub nat_ne_of_beq_eq_false: KId, - pub fin: KId, - pub bool_no_confusion: KId, - - // -- Int (type, ctors, ops) -- - // Int operations reduce by ordinary delta/iota plus native Nat reduction, - // matching Lean's kernel. We still record these primitive addresses for - // constructor recognition and Int decidable normalization. - pub int: KId, - pub int_of_nat: KId, - pub int_neg_succ: KId, - pub int_add: KId, - pub int_sub: KId, - pub int_mul: KId, - pub int_neg: KId, - pub int_emod: KId, - pub int_ediv: KId, - pub int_bmod: KId, - pub int_bdiv: KId, - pub int_nat_abs: KId, - pub int_pow: KId, - pub int_dec_eq: KId, - pub int_dec_le: KId, - pub int_dec_lt: KId, - - // -- Names previously matched via name-based `is_const_named` -- - // The whnf reductions in `whnf.rs` historically string-matched these - // by `id.name`, which is unsound under alpha-canonical content - // hashing: an expression that happens to be ingested with an - // alpha-twin's display name (e.g. `Lean.RBColor.rec` instead of - // `Bool.rec`) would miss the check despite identical addresses. - // Hardcoding each address per-name here lets the callsites compare - // by `id.addr ==` and stay alpha-stable. - pub punit: KId, - pub nat_rec: KId, - pub nat_cases_on: KId, - pub bit_vec: KId, - pub bit_vec_to_nat: KId, - pub bit_vec_of_nat: KId, - pub bit_vec_ult: KId, - pub decidable_decide: KId, - pub lt_lt: KId, - pub of_nat_of_nat: KId, - pub unit: KId, - pub punit_size_of_1: KId, - pub size_of_size_of: KId, - pub string_back: KId, - pub string_utf8_byte_size: KId, - - // -- Native string-value fast paths -- - // `String.append` / `String.decEq` on literal arguments reduce - // natively in whnf (same shape as the Nat ops in `try_reduce_nat`), - // so evaluator-grade normalization of string values doesn't fall - // into the structural ByteArray/UTF-8 model per character. - pub string_append: KId, - pub string_dec_eq: KId, -} - -/// `PrimAddrs` and `reserved_marker_name` live in `ix-common` so crates -/// below the kernel (notably witness builders, which must seed the -/// primitives the kernel fabricates during reduction) can reach them -/// without depending on `ix-kernel` — that edge would be a cycle, since -/// `ix-kernel` depends on `ixon`. Re-exported here because this is where -/// kernel code has always named them. -pub use ix_common::prim_addrs::{PrimAddrs, reserved_marker_name}; - -/// Lean's native-evaluation constants, by canonical content address: -/// `Lean.trustCompiler` and the interface built on it, `Lean.reduceBool`, -/// `Lean.reduceNat`, `Lean.ofReduceBool` and `Lean.ofReduceNat`. -/// -/// The checked theory is Lean with its three standard axioms; evaluation by -/// the compiler is outside it. The kernel therefore declines each of these -/// declarations and every declaration that refers to one of them, as the -/// certified checker does: it declines `trustCompiler` as a non-standard -/// axiom and blocks the four that depend on it. -/// -/// `reduceBool` and `reduceNat` reuse their [`PrimAddrs`] entries. The other -/// three live here rather than in `PrimAddrs`, whose parity table also -/// scopes IxVM witnesses. `lake test -- primitive-address-parity` compares -/// every entry with a live compile of the Lean declarations. -pub fn native_evaluation_addrs() -> &'static [(&'static str, Address); 5] { - static ADDRS: LazyLock<[(&'static str, Address); 5]> = LazyLock::new(|| { - let pinned = PrimAddrs::new(); - let h = |hex: &str| -> Address { - Address::from_hex(hex).expect("invalid native-evaluation address hex") - }; - [ - ( - "Lean.trustCompiler", - h("78d8bc334c7fd622793102acbf33ef9e3cae52615dcf5a9d5d63a56520640af2"), - ), - ("Lean.reduceBool", pinned.reduce_bool), - ("Lean.reduceNat", pinned.reduce_nat), - ( - "Lean.ofReduceBool", - h("560ece8034077f8861254f5e330e608f84b2779413c4bc27442b25a0683dba3b"), - ), - ( - "Lean.ofReduceNat", - h("1e8072e4aaed4cffe4f3199c07d92107b597a6271e7a0cd33723c675d748a05b"), - ), - ] - }); - &ADDRS -} - -/// Whether `addr` is one of [`native_evaluation_addrs`]. -pub fn is_native_evaluation_addr(addr: &Address) -> bool { - native_evaluation_addrs().iter().any(|(_, a)| a == addr) -} - -/// The address that stands for a primitive a bound artifact has not -/// admitted (see `Primitives::from_admitted`). It is not the hash of any -/// known bytes, so no authenticated record or reference can carry it. -pub const ABSENT_PRIMITIVE: &str = - "ff000000000000000000000000000000000000000000000000000000000000a0"; - -/// [`ABSENT_PRIMITIVE`] as an address. -pub fn absent_primitive() -> Address { - Address::from_hex(ABSENT_PRIMITIVE).expect("valid absent-primitive address") -} - -impl Primitives { - /// Resolve primitives from the environment using the canonical - /// content-hash address table (`PrimAddrs::new`). This is the correct - /// call for a kenv ingressed from compiled Ixon (the canonical - /// environment). - /// - /// Addresses that don't resolve fall back to a synthetic KId with the - /// address hex as the name. That is expected for the synthetic - /// `eager_reduce` marker and is a symptom of hash drift otherwise. - /// Regenerate stale hashes with - /// `lake test -- rust-kernel-build-primitives`. - pub fn from_env(env: &KEnv) -> Self { - Self::from_env_with(env, &PrimAddrs::new()) - } - - /// Resolve primitives from the environment using the LEON - /// content-hash address table (`PrimAddrs::new_orig`). This is the - /// correct call for `orig_kenv` (the direct-from-Lean environment - /// produced by `lean_ingress`), whose KIds live at LEON addresses. - /// - /// Without this variant, `from_env` would look up every primitive by - /// its canonical content address — which doesn't exist in `orig_kenv` - /// — and build a synthetic `@` KId for each. That cascades into - /// spurious `AppTypeMismatch` errors during original-constant - /// verification. Regenerate stale hashes with - /// `lake test -- rust-kernel-build-prim-origs`. - pub fn from_env_orig(env: &KEnv) -> Self { - Self::from_env_with(env, &PrimAddrs::new_orig()) - } - - /// The table for a bound artifact. A primitive whose role the artifact's - /// binding has admitted is its bound constant. Every other primitive (a role - /// not admitted, or a primitive with no role) is [`ABSENT_PRIMITIVE`], an - /// address no authenticated record has, so its acceleration never applies - /// and the term reduces by ordinary delta and iota. The synthetic - /// `eager_reduce` marker keeps its reserved address. - #[cfg(not(target_arch = "riscv64"))] - pub(crate) fn from_admitted(admitted: A, mut name_for_addr: F) -> Self - where - A: Fn(ixon::role_table::Role) -> Option
, - F: FnMut(&Address) -> Option, - { - use ixon::role_table::Role as R; - let absent = absent_primitive(); - let role = |r: R| admitted(r).unwrap_or_else(|| absent.clone()); - let a = PrimAddrs { - nat: role(R::Nat), - nat_zero: role(R::NatZero), - nat_succ: role(R::NatSucc), - nat_add: role(R::NatAdd), - nat_pred: role(R::NatPred), - nat_sub: role(R::NatSub), - nat_mul: role(R::NatMul), - nat_pow: role(R::NatPow), - nat_gcd: role(R::NatGcd), - nat_mod: role(R::NatMod), - nat_div: role(R::NatDiv), - nat_bitwise: absent.clone(), - nat_beq: role(R::NatBeq), - nat_ble: role(R::NatBle), - nat_land: role(R::NatLand), - nat_lor: role(R::NatLor), - nat_xor: role(R::NatXor), - nat_shift_left: role(R::NatShiftLeft), - nat_shift_right: role(R::NatShiftRight), - bool_type: role(R::Bool), - bool_true: role(R::BoolTrue), - bool_false: role(R::BoolFalse), - string: role(R::String), - string_mk: role(R::StringOfList), - char_type: role(R::Char), - char_mk: absent.clone(), - char_of_nat: role(R::CharOfNat), - string_of_list: role(R::StringOfList), - string_to_byte_array: role(R::StringToByteArray), - byte_array_empty: role(R::ByteArrayEmpty), - list: role(R::List), - list_nil: role(R::ListNil), - list_cons: role(R::ListCons), - eq: role(R::Eq), - eq_refl: role(R::EqRefl), - quot_type: role(R::Quot), - quot_ctor: role(R::QuotMk), - quot_lift: role(R::QuotLift), - quot_ind: role(R::QuotInd), - reduce_bool: absent.clone(), - reduce_nat: absent.clone(), - eager_reduce: PrimAddrs::new().eager_reduce, - system_platform_num_bits: absent.clone(), - system_platform_get_num_bits: absent.clone(), - subtype_val: absent.clone(), - nat_dec_le: role(R::NatDecLe), - nat_dec_eq: role(R::NatDecEq), - nat_dec_lt: role(R::NatDecLt), - decidable_rec: absent.clone(), - decidable_is_true: role(R::DecidableIsTrue), - decidable_is_false: role(R::DecidableIsFalse), - nat_le_of_ble_eq_true: absent.clone(), - nat_not_le_of_not_ble_eq_true: absent.clone(), - nat_eq_of_beq_eq_true: absent.clone(), - nat_ne_of_beq_eq_false: absent.clone(), - fin: absent.clone(), - bool_no_confusion: absent.clone(), - int: role(R::Int), - int_of_nat: role(R::IntOfNat), - int_neg_succ: role(R::IntNegSucc), - int_add: role(R::IntAdd), - int_sub: role(R::IntSub), - int_mul: role(R::IntMul), - int_neg: role(R::IntNeg), - int_emod: role(R::IntEmod), - int_ediv: role(R::IntEdiv), - int_bmod: role(R::IntBmod), - int_bdiv: role(R::IntBdiv), - int_nat_abs: role(R::IntNatAbs), - int_pow: role(R::IntPow), - int_dec_eq: role(R::IntDecEq), - int_dec_le: role(R::IntDecLe), - int_dec_lt: role(R::IntDecLt), - punit: role(R::PUnit), - pprod: absent.clone(), - pprod_mk: absent.clone(), - nat_rec: role(R::NatRec), - nat_cases_on: absent.clone(), - bit_vec: role(R::BitVec), - bit_vec_to_nat: role(R::BitVecToNat), - bit_vec_of_nat: role(R::BitVecOfNat), - bit_vec_ult: role(R::BitVecUlt), - decidable_decide: role(R::DecidableDecide), - lt_lt: absent.clone(), - of_nat_of_nat: absent.clone(), - unit: absent.clone(), - punit_size_of_1: absent.clone(), - size_of_size_of: absent.clone(), - string_back: role(R::StringBack), - string_utf8_byte_size: role(R::StringUtf8ByteSize), - string_append: role(R::StringAppend), - string_dec_eq: role(R::StringDecEq), - }; - Self::from_addrs_with(&a, |addr| { - name_for_addr(addr) - .map(|name| KId::new(addr.clone(), M::meta_field(name))) - }) - } - - /// Resolve canonical primitive KIds from an external address → name - /// lookup. Lazy IxOn workers call this before any primitive has - /// necessarily been faulted into their local KEnv, so Meta-mode KIds - /// still use the real serialized Lean names instead of synthetic - /// `@` fallbacks. - pub fn from_addr_names(mut name_for_addr: F) -> Self - where - F: FnMut(&Address) -> Option, - { - Self::from_addrs_with(&PrimAddrs::new(), |addr| { - name_for_addr(addr) - .map(|name| KId::new(addr.clone(), M::meta_field(name))) - }) - } - - /// Core primitive-resolution logic parameterized on the address - /// table. See `from_env` (canonical) and `from_env_orig` (LEON) for - /// the entry points. - fn from_env_with(env: &KEnv, a: &PrimAddrs) -> Self { - // Build addr → KId index from the env. - let mut by_addr = rustc_hash::FxHashMap::default(); - for (id, _) in env.iter() { - by_addr.entry(id.addr.clone()).or_insert_with(|| id.clone()); - } - - Self::from_addrs_with(a, |addr| by_addr.get(addr).cloned()) - } - - /// Shared primitive table construction once the caller has chosen the - /// address table and resolution source. - fn from_addrs_with(a: &PrimAddrs, mut resolve: F) -> Self - where - F: FnMut(&Address) -> Option>, - { - let mut r = |addr: &Address| -> KId { - resolve(addr).unwrap_or_else(|| { - let hex = addr.hex(); - let name = ix_common::env::Name::str( - ix_common::env::Name::anon(), - format!("@{}", &hex[..8]), - ); - KId::new(addr.clone(), M::meta_field(name)) - }) - }; - let marker = |addr: &Address, marker_name: &str| -> KId { - let name = ix_common::env::Name::str( - ix_common::env::Name::anon(), - format!("@{marker_name}"), - ); - KId::new(addr.clone(), M::meta_field(name)) - }; - - Primitives { - nat: r(&a.nat), - nat_zero: r(&a.nat_zero), - nat_succ: r(&a.nat_succ), - nat_add: r(&a.nat_add), - nat_pred: r(&a.nat_pred), - nat_sub: r(&a.nat_sub), - nat_mul: r(&a.nat_mul), - nat_pow: r(&a.nat_pow), - nat_gcd: r(&a.nat_gcd), - nat_mod: r(&a.nat_mod), - nat_div: r(&a.nat_div), - nat_bitwise: r(&a.nat_bitwise), - nat_beq: r(&a.nat_beq), - nat_ble: r(&a.nat_ble), - nat_land: r(&a.nat_land), - nat_lor: r(&a.nat_lor), - nat_xor: r(&a.nat_xor), - nat_shift_left: r(&a.nat_shift_left), - nat_shift_right: r(&a.nat_shift_right), - bool_type: r(&a.bool_type), - bool_true: r(&a.bool_true), - bool_false: r(&a.bool_false), - string: r(&a.string), - string_mk: r(&a.string_mk), - char_type: r(&a.char_type), - char_mk: r(&a.char_mk), - char_of_nat: r(&a.char_of_nat), - string_of_list: r(&a.string_of_list), - string_to_byte_array: r(&a.string_to_byte_array), - byte_array_empty: r(&a.byte_array_empty), - list: r(&a.list), - list_nil: r(&a.list_nil), - list_cons: r(&a.list_cons), - eq: r(&a.eq), - eq_refl: r(&a.eq_refl), - quot_type: r(&a.quot_type), - quot_ctor: r(&a.quot_ctor), - quot_lift: r(&a.quot_lift), - quot_ind: r(&a.quot_ind), - reduce_bool: r(&a.reduce_bool), - reduce_nat: r(&a.reduce_nat), - eager_reduce: marker(&a.eager_reduce, "eager_reduce"), - system_platform_num_bits: r(&a.system_platform_num_bits), - system_platform_get_num_bits: r(&a.system_platform_get_num_bits), - subtype_val: r(&a.subtype_val), - nat_dec_le: r(&a.nat_dec_le), - nat_dec_eq: r(&a.nat_dec_eq), - nat_dec_lt: r(&a.nat_dec_lt), - decidable_rec: r(&a.decidable_rec), - decidable_is_true: r(&a.decidable_is_true), - decidable_is_false: r(&a.decidable_is_false), - nat_le_of_ble_eq_true: r(&a.nat_le_of_ble_eq_true), - nat_not_le_of_not_ble_eq_true: r(&a.nat_not_le_of_not_ble_eq_true), - nat_eq_of_beq_eq_true: r(&a.nat_eq_of_beq_eq_true), - nat_ne_of_beq_eq_false: r(&a.nat_ne_of_beq_eq_false), - fin: r(&a.fin), - bool_no_confusion: r(&a.bool_no_confusion), - int: r(&a.int), - int_of_nat: r(&a.int_of_nat), - int_neg_succ: r(&a.int_neg_succ), - int_add: r(&a.int_add), - int_sub: r(&a.int_sub), - int_mul: r(&a.int_mul), - int_neg: r(&a.int_neg), - int_emod: r(&a.int_emod), - int_ediv: r(&a.int_ediv), - int_bmod: r(&a.int_bmod), - int_bdiv: r(&a.int_bdiv), - int_nat_abs: r(&a.int_nat_abs), - int_pow: r(&a.int_pow), - int_dec_eq: r(&a.int_dec_eq), - int_dec_le: r(&a.int_dec_le), - int_dec_lt: r(&a.int_dec_lt), - punit: r(&a.punit), - nat_rec: r(&a.nat_rec), - nat_cases_on: r(&a.nat_cases_on), - bit_vec: r(&a.bit_vec), - bit_vec_to_nat: r(&a.bit_vec_to_nat), - bit_vec_of_nat: r(&a.bit_vec_of_nat), - bit_vec_ult: r(&a.bit_vec_ult), - decidable_decide: r(&a.decidable_decide), - lt_lt: r(&a.lt_lt), - of_nat_of_nat: r(&a.of_nat_of_nat), - unit: r(&a.unit), - punit_size_of_1: r(&a.punit_size_of_1), - size_of_size_of: r(&a.size_of_size_of), - string_back: r(&a.string_back), - string_utf8_byte_size: r(&a.string_utf8_byte_size), - string_append: r(&a.string_append), - string_dec_eq: r(&a.string_dec_eq), - } - } -} - -#[cfg(test)] -mod tests { - use std::collections::HashMap; - - use super::*; - use crate::constant::KConst; - use crate::expr::KExpr; - use crate::id::KId; - use crate::level::KUniv; - use crate::mode::Anon; - use ix_common::env::Name; - - /// Collect every (field_name, addr) pair from `PrimAddrs` via reflection - /// over a macro invocation at the caller — done here by an inline array. - /// Keep in lockstep with `PrimAddrs`. - /// - /// Fields intentionally present as address-only dispatch markers (no Lean - /// constant) are marked below. - fn addrs_with_names(a: &PrimAddrs) -> Vec<(&'static str, &Address)> { - vec![ - ("nat", &a.nat), - ("nat_zero", &a.nat_zero), - ("nat_succ", &a.nat_succ), - ("nat_add", &a.nat_add), - ("nat_pred", &a.nat_pred), - ("nat_sub", &a.nat_sub), - ("nat_mul", &a.nat_mul), - ("nat_pow", &a.nat_pow), - ("nat_gcd", &a.nat_gcd), - ("nat_mod", &a.nat_mod), - ("nat_div", &a.nat_div), - ("nat_bitwise", &a.nat_bitwise), - ("nat_beq", &a.nat_beq), - ("nat_ble", &a.nat_ble), - ("nat_land", &a.nat_land), - ("nat_lor", &a.nat_lor), - ("nat_xor", &a.nat_xor), - ("nat_shift_left", &a.nat_shift_left), - ("nat_shift_right", &a.nat_shift_right), - ("bool_type", &a.bool_type), - ("bool_true", &a.bool_true), - ("bool_false", &a.bool_false), - ("string", &a.string), - ("string_mk", &a.string_mk), - ("char_type", &a.char_type), - ("char_mk", &a.char_mk), - ("char_of_nat", &a.char_of_nat), - ("string_of_list", &a.string_of_list), - ("string_to_byte_array", &a.string_to_byte_array), - ("byte_array_empty", &a.byte_array_empty), - ("list", &a.list), - ("list_nil", &a.list_nil), - ("list_cons", &a.list_cons), - ("eq", &a.eq), - ("eq_refl", &a.eq_refl), - ("quot_type", &a.quot_type), - ("quot_ctor", &a.quot_ctor), - ("quot_lift", &a.quot_lift), - ("quot_ind", &a.quot_ind), - ("reduce_bool", &a.reduce_bool), - ("reduce_nat", &a.reduce_nat), - ("eager_reduce", &a.eager_reduce), - ("system_platform_num_bits", &a.system_platform_num_bits), - ("system_platform_get_num_bits", &a.system_platform_get_num_bits), - ("subtype_val", &a.subtype_val), - ("nat_dec_le", &a.nat_dec_le), - ("nat_dec_eq", &a.nat_dec_eq), - ("nat_dec_lt", &a.nat_dec_lt), - ("decidable_rec", &a.decidable_rec), - ("decidable_is_true", &a.decidable_is_true), - ("decidable_is_false", &a.decidable_is_false), - ("nat_le_of_ble_eq_true", &a.nat_le_of_ble_eq_true), - ("nat_not_le_of_not_ble_eq_true", &a.nat_not_le_of_not_ble_eq_true), - ("nat_eq_of_beq_eq_true", &a.nat_eq_of_beq_eq_true), - ("nat_ne_of_beq_eq_false", &a.nat_ne_of_beq_eq_false), - ("fin", &a.fin), - ("bool_no_confusion", &a.bool_no_confusion), - ("int", &a.int), - ("int_of_nat", &a.int_of_nat), - ("int_neg_succ", &a.int_neg_succ), - ("int_add", &a.int_add), - ("int_sub", &a.int_sub), - ("int_mul", &a.int_mul), - ("int_neg", &a.int_neg), - ("int_emod", &a.int_emod), - ("int_ediv", &a.int_ediv), - ("int_bmod", &a.int_bmod), - ("int_bdiv", &a.int_bdiv), - ("int_pow", &a.int_pow), - ("int_nat_abs", &a.int_nat_abs), - ("int_dec_eq", &a.int_dec_eq), - ("int_dec_le", &a.int_dec_le), - ("int_dec_lt", &a.int_dec_lt), - ("punit", &a.punit), - ("pprod", &a.pprod), - ("pprod_mk", &a.pprod_mk), - ("nat_rec", &a.nat_rec), - ("nat_cases_on", &a.nat_cases_on), - ("bit_vec", &a.bit_vec), - ("bit_vec_to_nat", &a.bit_vec_to_nat), - ("bit_vec_of_nat", &a.bit_vec_of_nat), - ("bit_vec_ult", &a.bit_vec_ult), - ("decidable_decide", &a.decidable_decide), - ("lt_lt", &a.lt_lt), - ("of_nat_of_nat", &a.of_nat_of_nat), - ("unit", &a.unit), - ("punit_size_of_1", &a.punit_size_of_1), - ("size_of_size_of", &a.size_of_size_of), - ("string_back", &a.string_back), - ("string_utf8_byte_size", &a.string_utf8_byte_size), - ("string_append", &a.string_append), - ("string_dec_eq", &a.string_dec_eq), - ] - } - - /// Collapse the (field, addr) vec into address → fields-that-share-it. - fn find_duplicates(a: &PrimAddrs) -> Vec<(String, Vec<&'static str>)> { - let entries = addrs_with_names(a); - let mut by_addr: HashMap> = HashMap::new(); - for (name, addr) in entries { - by_addr.entry(addr.hex()).or_default().push(name); - } - let mut dups: Vec<(String, Vec<&'static str>)> = by_addr - .into_iter() - .filter(|(_, v)| v.len() > 1) - .map(|(k, mut v)| { - v.sort(); - (k, v) - }) - .collect(); - dups.sort_by(|a, b| a.0.cmp(&b.0)); - dups - } - - #[test] - fn prim_addrs_new_orig_has_no_duplicates() { - // LEON pre-compile table is regenerated from Lean reference and - // must never have field collisions. - let a = PrimAddrs::new_orig(); - let dups = find_duplicates(&a); - assert!( - dups.is_empty(), - "PrimAddrs::new_orig() has duplicate addresses:\n{dups:#?}" - ); - } - - /// `string_mk` and `string_of_list` intentionally share a canonical - /// content address: in Lean they're the same declaration. - /// `refs/lean4/src/Init/Prelude.lean` has - /// - /// ```lean - /// @[extern "lean_string_mk"] - /// def String.ofList (data : List Char) : String := - /// ⟨List.utf8Encode data, .intro data rfl⟩ - /// ``` - /// - /// `String.ofList` is the pure Lean definition; `lean_string_mk` is - /// its FFI extern name. The canonical (alpha-invariant, content-hashed) - /// form coalesces the two kernel-dispatch slots onto one address, which - /// is why `PrimAddrs::new()` stores the same hex for both — both - /// `prims.string_mk` and `prims.string_of_list` end up pointing at the - /// same `KId`. `PrimAddrs::new_orig()` holds them as distinct LEON - /// addresses because pre-compile the two names exist as separate - /// lookup keys. - /// - /// This test pins the intentional alias: if a future canonical-table - /// regeneration accidentally splits them we want a loud signal. - #[test] - fn prim_addrs_new_string_mk_and_of_list_are_intentionally_aliased() { - let a = PrimAddrs::new(); - assert_eq!( - a.string_mk.hex(), - a.string_of_list.hex(), - "string_mk and string_of_list must share a canonical address — \ - they are the same Lean declaration (String.ofList with extern \ - \"lean_string_mk\"). If this assertion fires after a hash-table \ - regeneration, check whether a Lean-side rename broke the alias \ - or whether the regeneration tool started emitting distinct hashes." - ); - } - - /// Canonical hash table regression guard: everything except the known - /// `string_mk` / `string_of_list` alias must be distinct. - #[test] - fn prim_addrs_new_no_unexpected_duplicates() { - let a = PrimAddrs::new(); - let dups = find_duplicates(&a); - // Filter out the intentional alias (string_mk + string_of_list) — - // see `prim_addrs_new_string_mk_and_of_list_are_intentionally_aliased`. - let unexpected: Vec<_> = dups - .into_iter() - .filter(|(_, fields)| { - !(fields.len() == 2 - && fields.contains(&"string_mk") - && fields.contains(&"string_of_list")) - }) - .collect(); - assert!( - unexpected.is_empty(), - "PrimAddrs::new() has unexpected duplicate addresses:\n{unexpected:#?}" - ); - } - - #[test] - fn primitives_from_env_empty_uses_synthetic_fallback() { - // With an empty env, every `r(&a.*)` lookup misses and produces a - // synthetic `@` KId. Confirm construction succeeds and - // yields recognizable synthetic names (in Meta mode). - let env = KEnv::::new(); - let p = Primitives::from_env(&env); - // The fallback name is `@`, a string part under an - // anonymous Name. Verify the `nat` field lives at the expected - // canonical address. - let canon = PrimAddrs::new(); - assert_eq!(p.nat.addr.hex(), canon.nat.hex()); - } - - #[test] - fn primitives_from_env_populated_resolves_against_env() { - // Insert a single constant at the canonical Nat address and confirm - // `Primitives::from_env` picks it up instead of falling back to - // synthesis. - let mut env = KEnv::::new(); - let canon = PrimAddrs::new(); - - let nat_id = KId::::new(canon.nat.clone(), ()); - let nat_axio = KConst::::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: KExpr::sort(KUniv::zero()), - }; - env.insert(nat_id.clone(), nat_axio); - - let p = Primitives::from_env(&env); - // Address still matches — the interesting property in Anon mode is - // that name metadata is erased anyway, so we only check the addr. - assert_eq!(p.nat.addr.hex(), canon.nat.hex()); - // The env entry should be the one the KEnv has (same address table). - assert!(env.get(&p.nat).is_some()); - } - - #[test] - fn primitives_from_env_orig_uses_orig_addrs() { - // from_env_orig uses PrimAddrs::new_orig (LEON addrs), not new(). - let env = KEnv::::new(); - let p = Primitives::from_env_orig(&env); - let orig = PrimAddrs::new_orig(); - let canon = PrimAddrs::new(); - assert_eq!(p.nat.addr.hex(), orig.nat.hex()); - // And the canonical addr is different from the LEON one — confirming - // the two tables aren't accidentally aliased. - assert_ne!(orig.nat.hex(), canon.nat.hex()); - } - - #[test] - fn primitives_from_env_orig_empty_fallback_name_is_synthetic() { - // Check that the synthetic fallback name has the `@<8hex>` shape for - // an address that doesn't exist in the env. Uses Meta mode so the - // name metadata is observable. - let env = KEnv::::new(); - let p = Primitives::from_env_orig(&env); - // Name of `p.nat` should be `@`. - let orig = PrimAddrs::new_orig(); - let expected = format!("@{}", &orig.nat.hex()[..8]); - let got_name = p.nat.name.clone(); - // Convert Name to string for comparison. - let got_str = format!("{got_name}"); - assert!( - got_str.contains(&expected), - "expected synthetic name containing {expected:?}, got {got_str:?}" - ); - // Silence unused-import lint. - let _: Name = Name::anon(); - } - - #[test] - fn new_and_default_match() { - // `Default` is implemented via `new`, so they must agree. - let a = PrimAddrs::new(); - let d = PrimAddrs::default(); - let entries_a = addrs_with_names(&a); - let entries_d = addrs_with_names(&d); - assert_eq!(entries_a.len(), entries_d.len()); - for ((name_a, addr_a), (name_d, addr_d)) in - entries_a.iter().zip(entries_d.iter()) - { - assert_eq!(name_a, name_d); - assert_eq!(addr_a.hex(), addr_d.hex()); - } - } - - #[test] - fn bound_extra_identities_require_admission_and_leave_legacy_helpers_absent() - { - use ixon::role_table::Role; - let key = Address::hash(b"admitted string append"); - let p = Primitives::::from_admitted( - |role| (role == Role::StringAppend).then(|| key.clone()), - |_| None, - ); - assert_eq!(p.string_append.addr, key); - for absent in [ - &p.string_dec_eq, - &p.string_back, - &p.string_utf8_byte_size, - &p.bit_vec_to_nat, - &p.int_add, - &p.nat_dec_eq, - &p.nat_le_of_ble_eq_true, - &p.nat_not_le_of_not_ble_eq_true, - &p.nat_eq_of_beq_eq_true, - &p.nat_ne_of_beq_eq_false, - &p.decidable_rec, - ] { - assert_eq!(absent.addr, absent_primitive()); - } - let none = Primitives::::from_admitted(|_| None, |_| None); - assert_eq!(none.string_append.addr, absent_primitive()); - } -} diff --git a/crates/kernel/src/primitive_context.rs b/crates/kernel/src/primitive_context.rs deleted file mode 100644 index bc3132165..000000000 --- a/crates/kernel/src/primitive_context.rs +++ /dev/null @@ -1,194 +0,0 @@ -//! Primitive identities and permission are separate. Bound candidate addresses -//! may build expected syntax; a bound artifact's primitive table holds only -//! the roles its binding has admitted (see `role_admission`). - -use ix_common::address::Address; -use ixon::role_table::Role; -use rustc_hash::{FxHashMap, FxHashSet}; -use std::sync::Arc; - -use crate::mode::KernelMode; -use crate::primitive::Primitives; - -#[derive(Clone)] -pub(crate) enum PrimitiveContext { - /// Unbound input: the pinned primitive addresses. - Legacy(Arc>), - /// A bound artifact, with the table built from its admitted roles and - /// what those roles allow. - #[cfg(not(target_arch = "riscv64"))] - Bound( - crate::role_admission::ArtifactSource, - Arc>, - Arc, - ), - #[cfg(not(target_arch = "riscv64"))] - UnpreparedBound, -} - -/// What a bound artifact's admitted roles allow beyond its table. -#[derive(Default)] -pub(crate) struct BoundRoles { - admitted: FxHashSet, - /// The standard axioms in the table: `propext`, `Classical.choice` and - /// `Quot.sound`. These, and only these, may be used. - axioms: FxHashSet
, - /// `sorryAx`, which may be declared but not used. - inert: FxHashSet
, -} - -impl PrimitiveContext { - /// The context of a bound artifact whose admitted roles are `admitted`: - /// those of the source's completed admission, or, while a group is being - /// validated, only the groups admitted before it. - #[cfg(not(target_arch = "riscv64"))] - pub(crate) fn bound( - source: crate::role_admission::ArtifactSource, - admitted: &FxHashMap, - name_for_addr: F, - ) -> Self - where - F: FnMut(&Address) -> Option, - { - let roles = admitted.keys().copied().collect(); - Self::bound_with_table(source, admitted, roles, name_for_addr) - } - - /// A bound context whose table holds `table` while only `admitted` roles - /// are admitted. The table may hold, besides the admitted roles, the - /// candidates of the group under validation, for declarations the kernel - /// recognises only at their table addresses (the quotient records). - #[cfg(not(target_arch = "riscv64"))] - pub(crate) fn bound_with_table( - source: crate::role_admission::ArtifactSource, - table: &FxHashMap, - admitted: FxHashSet, - name_for_addr: F, - ) -> Self - where - F: FnMut(&Address) -> Option, - { - let in_table = |roles: &[Role]| -> FxHashSet
{ - roles.iter().filter_map(|role| table.get(role).cloned()).collect() - }; - let roles = BoundRoles { - axioms: in_table(&[Role::Propext, Role::Choice, Role::QuotSound]), - inert: in_table(&[Role::SorryAx]), - admitted, - }; - let table = Primitives::from_admitted( - |role| table.get(&role).cloned(), - name_for_addr, - ); - Self::Bound(source, Arc::new(table), Arc::new(roles)) - } - - /// Whether checking follows the certified checker's safety tier: partial - /// definitions stay opaque, every safe declaration, not only a definition, - /// is fenced from partial and unsafe constants, and an opaque is checked as - /// safe. A bound artifact does; unbound input follows Lean's kernel. - pub(crate) fn follows_safety_tier(&self) -> bool { - match self { - #[cfg(not(target_arch = "riscv64"))] - Self::Bound(..) => true, - _ => false, - } - } - - /// Optional reducers follow semantic admission, never candidate addresses. - pub(crate) fn extra_reductions( - &self, - ) -> crate::extra_reductions::ExtraReductions { - use crate::extra_reductions::ExtraReductions; - match self { - #[cfg(not(target_arch = "riscv64"))] - Self::Bound(_, _, roles) => { - ExtraReductions::from_admitted(|role| roles.admitted.contains(&role)) - }, - _ => ExtraReductions::NONE, - } - } - - /// Whether the safe axiom at `address` may be used: any axiom in unbound - /// input; in a bound artifact only an admitted standard axiom. The - /// certified theory is Lean with its three standard axioms; unsafe axioms - /// are outside it (safe declarations cannot reference them). - pub(crate) fn axiom_usable(&self, address: &Address) -> bool { - match self { - Self::Legacy(_) => { - let _ = address; - true - }, - #[cfg(not(target_arch = "riscv64"))] - Self::Bound(_, _, roles) => roles.axioms.contains(address), - #[cfg(not(target_arch = "riscv64"))] - Self::UnpreparedBound => false, - } - } - - /// Whether the axiom at `address` may be declared: as usable, and also - /// `sorryAx`, which a bound artifact tolerates as a declaration only. - pub(crate) fn axiom_declarable(&self, address: &Address) -> bool { - match self { - #[cfg(not(target_arch = "riscv64"))] - Self::Bound(_, _, roles) => { - roles.inert.contains(address) || self.axiom_usable(address) - }, - _ => self.axiom_usable(address), - } - } - - /// The primitive table: the pinned one for unbound input, the admitted - /// one for a bound artifact, none before a bound input is prepared. - pub(crate) fn table(&self) -> Option<&Primitives> { - match self { - Self::Legacy(primitives) => Some(primitives), - #[cfg(not(target_arch = "riscv64"))] - Self::Bound(_, primitives, _) => Some(primitives), - #[cfg(not(target_arch = "riscv64"))] - Self::UnpreparedBound => None, - } - } - - /// Whether rules that need `role` may run. Unbound input keeps the - /// pinned addresses; a bound artifact needs the role admitted. - pub(crate) fn admits(&self, role: Role) -> bool { - match self { - Self::Legacy(_) => { - let _ = role; - true - }, - #[cfg(not(target_arch = "riscv64"))] - Self::Bound(_, _, roles) => roles.admitted.contains(&role), - #[cfg(not(target_arch = "riscv64"))] - Self::UnpreparedBound => false, - } - } - - pub(crate) fn candidate_address(&self, role: Role) -> Option<&Address> { - match self { - #[cfg(not(target_arch = "riscv64"))] - Self::Bound(source, _, _) => { - source.binding().and_then(|binding| binding.role(role)) - }, - _ => { - let _ = role; - None - }, - } - } - - pub(crate) fn entry_error(&self) -> Option<&'static str> { - match self { - #[cfg(not(target_arch = "riscv64"))] - Self::UnpreparedBound => { - Some("bound input requires an owned artifact checker") - }, - _ => None, - } - } - - pub(crate) fn unavailable(&self) -> String { - self.entry_error().unwrap_or("primitive table is unavailable").into() - } -} diff --git a/crates/kernel/src/profile.rs b/crates/kernel/src/profile.rs deleted file mode 100644 index 18a78fd53..000000000 --- a/crates/kernel/src/profile.rs +++ /dev/null @@ -1,645 +0,0 @@ -//! Out-of-circuit kernel profile (`.ixprof`) — the cost + delta-graph hints -//! that drive the sharding strategy (see `plans/sharding.md`). -//! -//! A profile is computed by running the Rust kernel **out of circuit** over an -//! environment and recording, per *block*: -//! -//! - `heartbeats`: total recursive fuel consumed checking the block's members -//! (the balance metric for partitioning), -//! - `serialized_size`: the block's serialized byte length (the ingress-cost -//! metric — net weight in the partition hypergraph), -//! - `const_count`: number of constants in the block, -//! - the set of *other blocks whose definition bodies the block delta-unfolds* -//! (the delta edges — the cost graph the partitioner cuts on). -//! -//! A "block" is the ingress unit: a `Muts` mutual block or a standalone -//! constant. Projection constants (`*Prj { block, .. }`) are attributed to -//! their `block` address; everything else is its own block. -//! -//! The delta graph is stored in compressed-sparse-row (CSR) form keyed by -//! stable block ids (assigned by sorting block addresses), and the on-disk -//! `.ixprof` format is an explicit little-endian binary so it does not depend -//! on the optional `serde`/`bincode` feature. - -// Block ids and counts are `u32` (envs are far below the limit); the binary -// decoder maps decode failures to a single message. Both are intentional here. -#![allow(clippy::cast_possible_truncation, clippy::map_err_ignore)] - -#[cfg(not(target_os = "zkvm"))] -use std::cell::Cell; - -use rustc_hash::{FxHashMap, FxHashSet}; - -use ix_common::address::Address; - -// ───────────────────────────────────────────────────────────────────────────── -// Per-constant operation counters (richer cost features than `heartbeats`). -// -// Heartbeats count kernel reduction *steps* but not the SIZE of the term each -// step touches, so they underrepresent work on def-eq-dense constants. -// These thread-local counters record the actual work volume — -// substitution-node visits, whnf/def-eq calls — which tracks guest cycles far -// more tightly, and `subst` feeds the planner's per-shard cost model. The -// profiler runs one constant per worker thread, so a thread-local accumulator -// captures one constant's totals with no arg threading through the -// (free-function) hot paths. Always recorded on native targets (every -// `ix profile` run needs them); compiled out entirely on the zkvm guest, which -// never records, so the in-circuit kernel pays nothing. -// ───────────────────────────────────────────────────────────────────────────── - -// thread_local! needs threads — only declared off the (single-threaded, -// no-std-ish) zkvm guest target, where every bump/take below compiles to a -// no-op. -#[cfg(not(target_os = "zkvm"))] -thread_local! { - static SUBST_NODES: Cell = const { Cell::new(0) }; - 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`). -#[inline(always)] -pub fn bump_subst_nodes() { - #[cfg(not(target_os = "zkvm"))] - SUBST_NODES.with(|c| c.set(c.get().wrapping_add(1))); -} - -/// Count one `whnf` entry. -#[inline(always)] -pub fn bump_whnf() { - #[cfg(not(target_os = "zkvm"))] - WHNF_CALLS.with(|c| c.set(c.get().wrapping_add(1))); -} - -/// Count one `is_def_eq` entry. -#[inline(always)] -pub fn bump_def_eq() { - #[cfg(not(target_os = "zkvm"))] - DEF_EQ_CALLS.with(|c| c.set(c.get().wrapping_add(1))); -} - -/// Add `work` units of big-Nat arithmetic limb-work (called per native Nat -/// binop in `compute_nat_bin`, weighted by operand limb sizes). Tracks the -/// cost of the Aiur `klimbs_*`/`u64_*` arithmetic circuits, which scale with -/// limb count and are invisible to the hb/subst/def_eq counters. -#[inline(always)] -pub fn bump_nat_arith(work: u64) { - #[cfg(not(target_os = "zkvm"))] - NAT_ARITH.with(|c| c.set(c.get().wrapping_add(work))); - #[cfg(target_os = "zkvm")] - 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 { - pub subst_nodes: u64, - 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). -pub fn take_op_counts() -> OpCounts { - #[cfg(not(target_os = "zkvm"))] - { - OpCounts { - subst_nodes: SUBST_NODES.with(|c| c.replace(0)), - 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")] - OpCounts::default() -} - -/// 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 = 2; - -/// Per-block recorded statistics. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct BlockEntry { - /// Content address of the block (a `Muts` block or a standalone constant). - pub addr: Address, - /// Total recursive fuel (heartbeats) consumed checking this block's members. - pub heartbeats: u64, - /// Serialized byte length of the block (ingress cost / net weight). - pub serialized_size: u32, - /// Number of constants in the block (1 for standalone constants). - pub const_count: u32, - /// Substitution-node visits (`instantiate_rev`) checking this block — the - /// 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. -/// -/// Blocks are indexed by stable id `0..num_blocks`. The delta graph is stored -/// in CSR form: `producers(c)` are the block ids whose definition bodies block -/// `c` delta-unfolds (the "consumer → producer" direction). Self-edges are -/// dropped; producer lists are sorted and deduplicated. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct BlockProfile { - blocks: Vec, - /// CSR row offsets into `delta_col`, length `blocks.len() + 1`. - delta_row: Vec, - /// CSR column indices: producer block ids, grouped by consumer. - delta_col: Vec, -} - -impl BlockProfile { - /// Number of blocks (vertices). - pub fn num_blocks(&self) -> usize { - self.blocks.len() - } - - /// Number of delta edges (consumer → producer pairs). - pub fn num_edges(&self) -> usize { - self.delta_col.len() - } - - /// The block entries, indexed by block id. - pub fn blocks(&self) -> &[BlockEntry] { - &self.blocks - } - - /// The entry for block id `i`. - pub fn block(&self, i: u32) -> &BlockEntry { - &self.blocks[i as usize] - } - - /// Producer block ids unfolded by consumer block `c` (sorted, deduped, no - /// self-edges). - pub fn producers(&self, c: u32) -> &[u32] { - let lo = self.delta_row[c as usize]; - let hi = self.delta_row[c as usize + 1]; - &self.delta_col[lo..hi] - } - - /// Build the reverse delta adjacency: for each producer block, the sorted set - /// of consumer blocks that unfold it. This is the natural form for the - /// partition hypergraph, where `net(p) = {p} ∪ consumers_of(p)`. - pub fn consumers_csr(&self) -> (Vec, Vec) { - let n = self.num_blocks(); - let mut counts = vec![0usize; n + 1]; - for &p in &self.delta_col { - counts[p as usize + 1] += 1; - } - for i in 0..n { - counts[i + 1] += counts[i]; - } - let row = counts.clone(); - let mut col = vec![0u32; self.delta_col.len()]; - let mut cursor = counts; - for c in 0..n as u32 { - for &p in self.producers(c) { - let slot = cursor[p as usize]; - col[slot] = c; - cursor[p as usize] += 1; - } - } - (row, col) - } - - /// Total heartbeats across all blocks. - pub fn total_heartbeats(&self) -> u128 { - self.blocks.iter().map(|b| u128::from(b.heartbeats)).sum() - } - - /// Serialize to the `.ixprof` binary format. - pub fn to_bytes(&self) -> Vec { - let n = self.blocks.len(); - let mut out = Vec::with_capacity( - 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()); - out.extend_from_slice(&(n as u32).to_le_bytes()); - for b in &self.blocks { - out.extend_from_slice(b.addr.as_bytes()); - out.extend_from_slice(&b.heartbeats.to_le_bytes()); - 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. - for &off in &self.delta_row { - out.extend_from_slice(&(off as u64).to_le_bytes()); - } - for &p in &self.delta_col { - out.extend_from_slice(&p.to_le_bytes()); - } - out - } - - /// Deserialize from the `.ixprof` binary format. - pub fn from_bytes(bytes: &[u8]) -> Result { - let mut r = Reader::new(bytes); - let magic = r.take(8)?; - if magic != MAGIC { - return Err(ProfileError::BadMagic); - } - let version = r.u32()?; - if version != VERSION { - return Err(ProfileError::BadVersion(version)); - } - let n = r.u32()? as usize; - let mut blocks = Vec::with_capacity(n); - for _ in 0..n { - let addr = Address::from_slice(r.take(32)?) - .map_err(|_| ProfileError::Truncated)?; - let heartbeats = r.u64()?; - 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; - let mut delta_row = Vec::with_capacity(n + 1); - for _ in 0..n + 1 { - delta_row.push(r.u64()? as usize); - } - let mut delta_col = Vec::with_capacity(num_edges); - for _ in 0..num_edges { - delta_col.push(r.u32()?); - } - // Structural validation: monotone offsets bounded by edge count, in-range ids. - if delta_row.len() != n + 1 - || delta_row.first() != Some(&0) - || delta_row.last() != Some(&num_edges) - { - return Err(ProfileError::Corrupt); - } - for w in delta_row.windows(2) { - if w[0] > w[1] { - return Err(ProfileError::Corrupt); - } - } - for &p in &delta_col { - if p as usize >= n { - return Err(ProfileError::Corrupt); - } - } - Ok(BlockProfile { blocks, delta_row, delta_col }) - } -} - -/// Errors from decoding a `.ixprof` file. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ProfileError { - BadMagic, - BadVersion(u32), - Truncated, - Corrupt, -} - -impl std::fmt::Display for ProfileError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ProfileError::BadMagic => write!(f, "not an .ixprof file (bad magic)"), - ProfileError::BadVersion(v) => { - write!(f, "unsupported .ixprof version {v}") - }, - ProfileError::Truncated => write!(f, "truncated .ixprof file"), - ProfileError::Corrupt => write!(f, "corrupt .ixprof file"), - } - } -} - -impl std::error::Error for ProfileError {} - -/// Minimal little-endian byte reader with bounds checking. -struct Reader<'a> { - buf: &'a [u8], - pos: usize, -} - -impl<'a> Reader<'a> { - fn new(buf: &'a [u8]) -> Self { - Reader { buf, pos: 0 } - } - fn take(&mut self, n: usize) -> Result<&'a [u8], ProfileError> { - let end = self.pos.checked_add(n).ok_or(ProfileError::Truncated)?; - if end > self.buf.len() { - return Err(ProfileError::Truncated); - } - let s = &self.buf[self.pos..end]; - self.pos = end; - Ok(s) - } - fn u32(&mut self) -> Result { - Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap())) - } - fn u64(&mut self) -> Result { - Ok(u64::from_le_bytes(self.take(8)?.try_into().unwrap())) - } -} - -/// Accumulates block-level statistics and delta edges (keyed by address), then -/// freezes into a [`BlockProfile`] with stable, address-sorted block ids. -/// -/// Phase A (the kernel recorder) feeds this with one `block(..)` per checked -/// block and one `delta_edge(consumer, producer)` per recorded cross-block -/// unfold. The builder is the merge point for per-worker accumulators: calling -/// `block`/`delta_edge` is commutative and idempotent w.r.t. edge sets, so -/// merge order does not affect the result. -#[derive(Default)] -pub struct ProfileBuilder { - blocks: FxHashMap, -} - -#[derive(Default)] -struct Accum { - heartbeats: u64, - serialized_size: u32, - const_count: u32, - ops: OpCounts, - producers: FxHashSet
, -} - -impl ProfileBuilder { - pub fn new() -> Self { - Self::default() - } - - /// 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, - 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.ops.add(&ops); - } - - /// Record that `consumer` delta-unfolds the body of `producer`. Self-edges - /// are ignored. Ensures both endpoints exist as blocks (with zeroed stats if - /// not yet seen) so the graph is well-formed even if a producer is only ever - /// referenced, never directly checked. - pub fn delta_edge(&mut self, consumer: Address, producer: Address) { - if consumer == producer { - return; - } - self.blocks.entry(producer.clone()).or_default(); - self.blocks.entry(consumer).or_default().producers.insert(producer); - } - - /// Freeze into an immutable [`BlockProfile`]. Block ids are assigned by - /// sorting addresses, so the result is deterministic regardless of insertion - /// order. - pub fn finish(self) -> BlockProfile { - let mut addrs: Vec
= self.blocks.keys().cloned().collect(); - addrs.sort(); - let id_of: FxHashMap = - addrs.iter().enumerate().map(|(i, a)| (a.clone(), i as u32)).collect(); - - let mut blocks = Vec::with_capacity(addrs.len()); - let mut delta_row = Vec::with_capacity(addrs.len() + 1); - let mut delta_col = Vec::new(); - delta_row.push(0usize); - - for addr in &addrs { - let a = &self.blocks[addr]; - blocks.push(BlockEntry { - addr: addr.clone(), - heartbeats: a.heartbeats, - serialized_size: a.serialized_size, - const_count: a.const_count, - 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(); - prods.dedup(); - delta_col.extend_from_slice(&prods); - delta_row.push(delta_col.len()); - } - - BlockProfile { blocks, delta_row, delta_col } - } -} - -/// Per-worker raw accumulator filled by the out-of-circuit kernel recorder: for -/// each *constant* (by address) checked on this worker, its heartbeats and the -/// set of constant addresses whose definition bodies it delta-unfolded. The -/// env-aware layer later maps these constant addresses to their home blocks and -/// attaches serialized sizes to produce a [`BlockProfile`]. -#[derive(Default, Debug)] -pub struct ProfileSink { - /// When true, the kernel clears its reduction-memo caches between constants - /// so recording is sound (no unfolds skipped by cross-constant cache hits) - /// and heartbeats reflect the no-cross-constant-memo in-circuit cost. - pub isolate: bool, - /// Consumer constant address → record. - pub records: FxHashMap, -} - -/// One constant's recorded statistics (pre block-aggregation). -#[derive(Default, Debug, Clone)] -pub struct ConstRecord { - /// Recursive fuel (heartbeats) consumed checking this constant. - pub fuel: u64, - /// Constant addresses whose bodies were delta-unfolded during the check. - pub producers: FxHashSet
, - /// Richer cost features (substitution-node visits, whnf/def-eq calls), - /// recorded on every native profiling run; compiled out (all zero) on the - /// zkvm target. - pub ops: OpCounts, -} - -impl ProfileSink { - pub fn new(isolate: bool) -> Self { - ProfileSink { isolate, records: FxHashMap::default() } - } - - /// Accumulate one constant's record (additive in fuel + op counts, set-union - /// in producers) so repeated flushes for the same constant combine correctly. - pub fn record( - &mut self, - consumer: Address, - fuel: u64, - producers: impl IntoIterator, - ops: OpCounts, - ) { - let rec = self.records.entry(consumer).or_default(); - rec.fuel = rec.fuel.saturating_add(fuel); - rec.producers.extend(producers); - rec.ops.add(&ops); - } - - /// Merge another worker's sink into this one (order-independent). - pub fn merge(&mut self, other: ProfileSink) { - for (addr, rec) in other.records { - let e = self.records.entry(addr).or_default(); - e.fuel = e.fuel.saturating_add(rec.fuel); - e.producers.extend(rec.producers); - e.ops.add(&rec.ops); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn addr(byte: u8) -> Address { - 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 = col[lo..hi].to_vec(); - got.sort_unstable(); - assert_eq!(got, vec![0, 2]); - } - - #[test] - fn roundtrip_serialization() { - let p = sample(); - let bytes = p.to_bytes(); - let q = BlockProfile::from_bytes(&bytes).unwrap(); - assert_eq!(p, q); - } - - #[test] - fn rejects_bad_magic_and_truncation() { - assert_eq!( - BlockProfile::from_bytes(b"nope").unwrap_err(), - ProfileError::Truncated - ); - let mut bytes = sample().to_bytes(); - bytes[0] = b'X'; - assert_eq!( - BlockProfile::from_bytes(&bytes).unwrap_err(), - ProfileError::BadMagic - ); - } - - #[test] - fn merge_order_independent() { - // Build the same logical profile with edges added in a different order and - // via separate builders merged conceptually; result must be identical. - let mut b = ProfileBuilder::new(); - b.delta_edge(addr(3), addr(2)); - b.block(addr(3), 300, 30, 1, ops(150)); - b.delta_edge(addr(1), addr(3)); - b.block(addr(2), 200, 20, 3, ops(100)); - b.delta_edge(addr(1), addr(2)); - b.block(addr(1), 100, 10, 1, ops(50)); - b.delta_edge(addr(2), addr(2)); - assert_eq!(b.finish(), sample()); - } -} diff --git a/crates/kernel/src/resource.rs b/crates/kernel/src/resource.rs deleted file mode 100644 index 5e7303543..000000000 --- a/crates/kernel/src/resource.rs +++ /dev/null @@ -1,397 +0,0 @@ -//! Combined resource and erased-type validation for addressed Ixon. -//! Kernel typechecking on its own makes no resource promise. - -#[cfg(any(test, target_arch = "riscv64"))] -use crate::anon_work::build_anon_work; -#[cfg(test)] -use crate::env::KEnv; -#[cfg(test)] -use crate::id::KId; -#[cfg(test)] -use crate::mode::Anon; -use crate::primitive::PrimAddrs; -#[cfg(not(target_arch = "riscv64"))] -use crate::role_admission::ArtifactSource; -#[cfg(test)] -use crate::tc::TypeChecker; -use ixon::env::Env; -use ixon::resource::addressed::{AddressedProgram, Profile, check_resources}; - -/// Minimal built-in profile, restricted to interfaces present in the closure. -pub fn standard_profile(env: &Env) -> Result { - #[cfg(not(target_arch = "riscv64"))] - if env.role_binding.is_some() { - // A profile is untrusted input, not a semantic capability. Actual erased - // typing below still requires the same source's admitted literal roles. - let source = - ArtifactSource::snapshot(env).map_err(|error| error.to_string())?; - let binding = - source.binding().ok_or("resource profile: binding was lost")?; - use ixon::role_table::Role; - let mut shareable_types: Vec<_> = [Role::Nat, Role::Bool, Role::String] - .into_iter() - .filter_map(|role| binding.role(role).cloned()) - .collect(); - shareable_types.sort_unstable(); - return Ok(Profile { - nat_type: binding.role(Role::Nat).cloned(), - string_type: binding.role(Role::String).cloned(), - shareable_types, - ..Default::default() - }); - } - let p = PrimAddrs::new(); - let mut shareable_types: Vec<_> = - [p.nat.clone(), p.bool_type.clone(), p.string.clone()] - .into_iter() - .filter(|a| env.consts.contains_key(a)) - .collect(); - shareable_types.sort_unstable(); - Ok(Profile { - nat_type: env.consts.contains_key(&p.nat).then_some(p.nat), - string_type: env.consts.contains_key(&p.string).then_some(p.string), - shareable_types, - ..Default::default() - }) -} - -pub fn check_literal_profile(profile: &Profile) -> Result<(), String> { - let p = PrimAddrs::new(); - for (selected, actual) in - [(&profile.nat_type, &p.nat), (&profile.string_type, &p.string)] - { - if selected.as_ref().is_some_and(|selected| selected != actual) { - return Err("resource validator: literal type differs from the kernel primitive identity".into()); - } - } - Ok(()) -} - -/// Validate both resources and erased types of the same canonical addressed -/// closure. A caller advertising a resource claim must bind the returned -/// profile address and this validator's identity to its subject. -pub fn validate( - env: &Env, - profile: &Profile, -) -> Result { - #[cfg(not(target_arch = "riscv64"))] - { - // The borrowed maps must be quiescent during capture; all subsequent raw - // resource and typing reads use this same owned snapshot. - let source = - ArtifactSource::snapshot(env).map_err(|error| error.to_string())?; - validate_source(&source, profile) - } - #[cfg(target_arch = "riscv64")] - { - check_literal_profile(profile)?; - let resolved = check_resources(env, profile)?; - let work = build_anon_work(env)?; - let mut checker = crate::ixon_checker::IxonChecker::new(env); - for item in work { - checker.check_const(item.primary()).map_err(|error| { - format!( - "resource validator: erased typing failed at {}: {error:?}", - item.primary().hex() - ) - })?; - // Retain structural declarations but keep reduction caches local to one - // work item, as in the existing anonymous driver. - checker.clear_reduction_caches(); - } - Ok(resolved) - } -} - -#[cfg(not(target_arch = "riscv64"))] -fn validate_source( - source: &ArtifactSource, - profile: &Profile, -) -> Result { - if let Some(binding) = source.binding() { - use ixon::role_table::Role; - for (selected, role) in - [(&profile.nat_type, Role::Nat), (&profile.string_type, Role::String)] - { - if selected - .as_ref() - .is_some_and(|address| binding.role(role) != Some(address)) - { - return Err( - "resource validator: literal type differs from the actual binding" - .into(), - ); - } - } - } else { - check_literal_profile(profile)?; - } - let resolved = check_resources(source, profile)?; - let work = crate::anon_work::build_artifact_work(source)?; - let mut checker = - crate::ixon_checker::IxonChecker::from_source(Ok(source.clone())); - for item in work { - checker.check_work(&item).map_err(|error| { - format!( - "resource validator: erased typing failed at {}: {error:?}", - item.primary().hex() - ) - })?; - checker.clear_reduction_caches(); - } - Ok(resolved) -} - -pub fn check_claim( - env: &Env, - profile: &Profile, - claim: &ixon::proof::Claim, -) -> Result { - #[cfg(not(target_arch = "riscv64"))] - { - let source = - ArtifactSource::snapshot(env).map_err(|error| error.to_string())?; - check_source_claim(&source, profile, claim) - } - #[cfg(target_arch = "riscv64")] - { - let ixon::proof::Claim::Resource { root, profile: expected_profile } = - claim - else { - return Err( - "resource validator: claim uses a different validator".into(), - ); - }; - let leaves: Vec<_> = - env.consts.iter().map(|entry| entry.key().clone()).collect(); - if ixon::merkle::merkle_root_canonical(&leaves).as_ref() != Some(root) { - return Err("resource validator: claim subject differs from the complete constant set".into()); - } - if &profile.address()? != expected_profile { - return Err( - "resource validator: claim profile differs from the supplied policy" - .into(), - ); - } - validate(env, profile) - } -} - -#[cfg(not(target_arch = "riscv64"))] -fn check_source_claim( - source: &ArtifactSource, - profile: &Profile, - claim: &ixon::proof::Claim, -) -> Result { - let ixon::proof::Claim::Resource { root, profile: expected_profile } = claim - else { - return Err("resource validator: claim uses a different validator".into()); - }; - if ixon::merkle::merkle_root_canonical(&source.addresses()).as_ref() - != Some(root) - { - return Err("resource validator: claim subject differs from the complete constant set".into()); - } - if &profile.address()? != expected_profile { - return Err( - "resource validator: claim profile differs from the supplied policy" - .into(), - ); - } - validate_source(source, profile) -} - -pub fn make_claim( - env: &Env, - profile: &Profile, -) -> Result { - #[cfg(not(target_arch = "riscv64"))] - { - let source = - ArtifactSource::snapshot(env).map_err(|error| error.to_string())?; - let root = ixon::merkle::merkle_root_canonical(&source.addresses()) - .ok_or("resource validator: empty claim subject")?; - let claim = - ixon::proof::Claim::Resource { root, profile: profile.address()? }; - check_source_claim(&source, profile, &claim)?; - Ok(claim) - } - #[cfg(target_arch = "riscv64")] - { - let leaves: Vec<_> = - env.consts.iter().map(|entry| entry.key().clone()).collect(); - let root = ixon::merkle::merkle_root_canonical(&leaves) - .ok_or("resource validator: empty claim subject")?; - let claim = - ixon::proof::Claim::Resource { root, profile: profile.address()? }; - check_claim(env, profile, &claim)?; - Ok(claim) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use ix_common::address::Address; - use ix_common::env::DefinitionSafety; - use ixon::constant::{ - Constant, ConstantInfo, Constructor, DefKind, Definition, Inductive, - MutConst, ctor_proj_constant, indc_proj_constant, - }; - use ixon::contract::{BinderContract, ValueContract}; - use ixon::expr::{Expr, Uses}; - use ixon::univ::Univ; - - fn store(env: &Env, c: Constant) -> Address { - let a = c.commit().0; - env.store_const(a.clone(), c); - a - } - - #[test] - fn cross_language_consumer_handoff() { - let profile = Profile::from_bytes(include_bytes!( - "../../../Tests/Fixtures/ixon-v4/handoff/profile.bin" - )) - .unwrap(); - let claim = ixon::proof::Claim::from_bytes(include_bytes!( - "../../../Tests/Fixtures/ixon-v4/handoff/accepted.claim" - )) - .unwrap(); - let accepted = Env::get( - &mut include_bytes!( - "../../../Tests/Fixtures/ixon-v4/handoff/accepted.ixe" - ) - .as_slice(), - ) - .unwrap(); - let rejected = Env::get( - &mut include_bytes!( - "../../../Tests/Fixtures/ixon-v4/handoff/rejected-local-escape.ixe" - ) - .as_slice(), - ) - .unwrap(); - check_claim(&accepted, &profile, &claim).unwrap(); - assert!(check_claim(&rejected, &profile, &claim).is_err()); - assert!(validate(&rejected, &profile).is_err()); - // The counterexample is well typed after erasure; its rejection is - // specifically a resource obligation, not a wire or Lean typing failure. - let mut kernel = KEnv::::new(); - for item in build_anon_work(&rejected).unwrap() { - let id = KId { addr: item.primary().clone(), name: () }; - TypeChecker::new_with_lazy_anon(&mut kernel, &rejected) - .check_const(&id) - .unwrap(); - } - } - - fn base() -> (Env, Address) { - let env = Env::new(); - let block = store( - &env, - Constant::with_tables( - ConstantInfo::Muts(vec![MutConst::Indc(Inductive { - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - typ: Expr::sort(0), - ctors: vec![Constructor { - is_unsafe: false, - lvls: 0, - cidx: 0, - params: 0, - fields: 0, - typ: Expr::rec(0, vec![]), - }], - })]), - vec![], - vec![], - vec![Univ::zero()], - ), - ); - let typ = store(&env, indc_proj_constant(0, block.clone())); - store(&env, ctor_proj_constant(0, 0, block)); - (env, typ) - } - #[test] - fn combines_independent_resource_and_type_checks() { - let (env, typ) = base(); - let c = BinderContract { - uses: Uses::Linear, - value: ValueContract::local_shared(), - }; - store( - &env, - Constant::with_tables( - ConstantInfo::Defn(Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: Expr::all_contract( - c, - ValueContract::local_shared(), - Expr::reference(0, vec![]), - Expr::reference(0, vec![]), - ), - value: Expr::lam_contract( - c, - Expr::reference(0, vec![]), - Expr::var(0), - ), - }), - vec![], - vec![typ.clone()], - vec![], - ), - ); - validate(&env, &Profile::default()).unwrap(); - let claim = make_claim(&env, &Profile::default()).unwrap(); - assert!(check_claim(&env, &Profile::default(), &claim).is_ok()); - let mut changed_profile = Profile::default(); - changed_profile.limits.steps += 1; - assert!(check_claim(&env, &changed_profile, &claim).is_err()); - assert!( - check_claim( - &env, - &Profile::default(), - &ixon::proof::Claim::CheckEnv { - ctx: ixon::proof::ClaimContext { - spec: ixon::proof::spec_digest(), - binding: Address::hash(b"binding"), - axioms: None, - binding_assumed: false, - literal_class: false, - }, - root: Address::hash(b"subject"), - assumptions: None - } - ) - .is_err() - ); - assert!( - validate(&env, &Profile { nat_type: Some(typ), ..Default::default() }) - .is_err() - ); - let malformed = env.clone(); - store( - &malformed, - Constant::with_tables( - ConstantInfo::Defn(Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: Expr::sort(0), - value: Expr::var(0), - }), - vec![], - vec![], - vec![Univ::zero()], - ), - ); - // This ordinary component has no resource annotation. It still must typecheck. - assert!(check_resources(&malformed, &Profile::default()).is_ok()); - assert!(validate(&malformed, &Profile::default()).is_err()); - } -} diff --git a/crates/kernel/src/role_admission.rs b/crates/kernel/src/role_admission.rs deleted file mode 100644 index 78f310d61..000000000 --- a/crates/kernel/src/role_admission.rs +++ /dev/null @@ -1,1024 +0,0 @@ -//! Native role admission. Wire decoding, candidate preparation and semantic -//! admission are distinct boundaries: a prepared source authenticates its -//! binding and every bound record, and only the group validators in -//! `groups` admit roles, a whole specification group at a time. -//! -//! # The certified checker's frontend, as this kernel reads it -//! -//! The certified checker reads an artifact as a stream folded in order, with -//! a committed prelude in front. Its frontend has six checks this kernel must -//! match. This kernel reads an artifact as content-addressed units checked -//! independently, with the bound roles admitted first, so each check is -//! either enforced here or ruled out by that representation: -//! -//! - **The prelude's shape** (the prelude names the pinned prelude roles) and -//! **the prelude copy** (a stream may not hold, at a prelude key, anything -//! but the prelude's entry): every prelude role ([`PRELUDE`]) must be bound -//! and its group admitted, or no unit of the artifact is checked -//! ([`ArtifactSource::prelude_refusal`]). The prelude's records are not -//! compared with a committed copy: the group validators compare them with -//! the same specifications the committed prelude was checked against, and -//! a bound address holds exactly one record. -//! - **The rest-only hoist** and **the hoist guard** reorder a stream so that a -//! `Nat` operation's ground precedes it, outside the prelude and without -//! splitting a unit. Nothing here depends on order: groups are admitted in -//! dependency order before any unit is checked, whatever order the records -//! come in, and units are never split or merged. -//! - **The units check** declines a basis record of the fold's own, a unit out -//! of shape, a prelude role declared past the front, and two units declaring -//! a common base. This kernel has no fold-own records and no generated model -//! records; a unit is a serialized record, whose recursive targets come from -//! the unit itself; a role's records are its bound addresses, one record -//! each; and a projection is authenticated against the one owner its address -//! commits to, so no two units declare one declaration. -//! - **The inferer's fuel** bounds the checker's untyped inference over model -//! records. This kernel has no untyped inference: every inference and -//! reduction is typed and bounded by fuel, the WHNF step limit and the stack -//! check, so a non-terminating head β in a declared type declines. - -use std::sync::{Arc, OnceLock}; - -use ix_common::address::Address; -use ix_common::env::DefinitionSafety; -use ixon::constant::{Constant, ConstantInfo, DefKind, Definition, MutConst}; -use ixon::env::{Env, RoleBinding}; -use ixon::lazy::LazyConstant; -use ixon::role_table::{Evidence, Role, RoleTable}; -use rustc_hash::{FxHashMap, FxHashSet}; - -/// Current IxC specification version; the wire codec deliberately accepts more. -pub const SPEC_VERSION: u8 = 3; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Error { - Binding(String), - Unbound, - MissingRole(Role), - MissingRecord(Address), - Record(Address, String), - Member(Address, &'static str), - NotTheorem(Address), - MissingBlob(Address), - BlobHash(Address), -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Binding(reason) => write!(f, "role binding: {reason}"), - Self::Unbound => write!(f, "artifact has no role binding"), - Self::MissingRole(role) => write!(f, "role binding: missing {role:?}"), - Self::MissingRecord(a) => { - write!(f, "role source: missing record {}", a.hex()) - }, - Self::Record(a, reason) => { - write!(f, "role source: record {}: {reason}", a.hex()) - }, - Self::Member(a, reason) => { - write!(f, "role source: member {}: {reason}", a.hex()) - }, - Self::NotTheorem(a) => { - write!(f, "role source: proof {} is not a safe theorem", a.hex()) - }, - Self::MissingBlob(a) => { - write!(f, "role source: missing blob {}", a.hex()) - }, - Self::BlobHash(a) => { - write!(f, "role source: blob {} hash mismatch", a.hex()) - }, - } - } -} -impl std::error::Error for Error {} - -static ALL_ROLES: [Role; 103] = Role::ALL; - -// Exactly Role.block/deps and Spec.evidenceAllowed in IxC/Kernel/Roles.lean. -// These describe candidate well-formedness, not declaration specifications. -fn block(role: Role) -> &'static [Role] { - use Role::{ - And, AndIntro, AndRec, BitVec, BitVecOfNat, Bool, BoolFalse, BoolRec, - BoolTrue, ByteArray, ByteArrayEmpty, Decidable, DecidableIsFalse, - DecidableIsTrue, Empty, EmptyRec, Eq, EqRec, EqRefl, False, FalseRec, Iff, - IffIntro, IffRec, Int, IntNegSucc, IntOfNat, List, ListCons, ListNil, Nat, - NatRec, NatSucc, NatZero, Nonempty, NonemptyIntro, NonemptyRec, PUnit, - PUnitRec, PUnitUnit, Quot, QuotInd, QuotLift, QuotMk, QuotSound, True, - TrueIntro, - }; - match role { - Nat | NatZero | NatSucc | NatRec => &[Nat, NatZero, NatSucc, NatRec], - Bool | BoolFalse | BoolTrue | BoolRec => { - &[Bool, BoolFalse, BoolTrue, BoolRec] - }, - Eq | EqRefl | EqRec => &[Eq, EqRefl, EqRec], - PUnit | PUnitUnit | PUnitRec => &[PUnit, PUnitUnit, PUnitRec], - Empty | EmptyRec => &[Empty, EmptyRec], - False | FalseRec => &[False, FalseRec], - True | TrueIntro => &[True, TrueIntro], - And | AndIntro | AndRec => &[And, AndIntro, AndRec], - Iff | IffIntro | IffRec => &[Iff, IffIntro, IffRec], - Nonempty | NonemptyIntro | NonemptyRec => { - &[Nonempty, NonemptyIntro, NonemptyRec] - }, - List | ListNil | ListCons => &[List, ListNil, ListCons], - Quot | QuotMk | QuotLift | QuotInd | QuotSound => { - &[Quot, QuotMk, QuotLift, QuotInd, QuotSound] - }, - Decidable | DecidableIsTrue | DecidableIsFalse => { - &[Decidable, DecidableIsTrue, DecidableIsFalse] - }, - Int | IntOfNat | IntNegSucc => &[Int, IntOfNat, IntNegSucc], - BitVec | BitVecOfNat => &[BitVec, BitVecOfNat], - ByteArray | ByteArrayEmpty => &[ByteArray, ByteArrayEmpty], - _ => &ALL_ROLES[usize::from(role.index())..=usize::from(role.index())], - } -} - -fn dependencies(role: Role) -> &'static [Role] { - use Role::{ - BitVec, BitVecOfNat, BitVecToNat, BitVecUlt, Bool, ByteArray, Char, - CharOfNat, CharUtf8Size, Choice, Decidable, DecidableDecide, - DecidableIsFalse, Eq, IfThenElse, Iff, Int, IntAdd, IntBdiv, IntBmod, - IntDecEq, IntDecLe, IntDecLt, IntEdiv, IntEmod, IntLe, IntMul, IntNatAbs, - IntNeg, IntNegOfNat, IntNegSucc, IntOfNat, IntPow, IntSub, IntSubNatNat, - List, ListAppend, Nat, NatAdd, NatBeq, NatBle, NatDecEq, NatDecLe, - NatDecLt, NatDiv, NatGcd, NatLand, NatLe, NatLor, NatMod, NatMul, NatPow, - NatPred, NatShiftLeft, NatShiftRight, NatSub, NatXor, Nonempty, Not, - OfDecideEqFalse, OfDecideEqTrue, Propext, Quot, QuotInd, QuotLift, QuotMk, - QuotSound, SorryAx, String, StringAppend, StringBack, StringDecEq, - StringOfList, StringToByteArray, StringUtf8ByteSize, - }; - match role { - Quot | QuotMk | QuotLift | QuotInd | QuotSound => &[Eq], - StringOfList => &[List, Char, String], - CharOfNat => &[Nat, Char], - NatPred | NatAdd | NatLe | IntOfNat | IntNegSucc | BitVec | BitVecOfNat => { - &[Nat] - }, - NatSub => &[Nat, NatPred], - NatMul => &[Nat, NatAdd], - NatPow => &[Nat, NatAdd, NatMul], - NatBeq | NatBle => &[Nat, Bool], - NatDiv | NatMod => &[Nat, NatPred, NatSub, NatBle, Bool, Eq], - NatGcd => &[Nat, NatBle, NatMod, Bool, Eq], - NatLand | NatXor => { - &[Nat, NatAdd, NatMul, NatBle, NatDiv, NatMod, Bool, Eq] - }, - NatLor => &[Nat, NatAdd, NatSub, NatMul, NatBle, NatDiv, NatMod, Bool, Eq], - NatShiftLeft => &[Nat, NatSub, NatMul, NatBle, Bool, Eq], - NatShiftRight => &[Nat, NatSub, NatBle, NatDiv, Bool, Eq], - Propext => &[Iff, Eq], - Choice => &[Nonempty], - SorryAx => &[Bool], - DecidableIsFalse => &[Not], - OfDecideEqTrue => &[Decidable, DecidableDecide, Bool, Eq], - OfDecideEqFalse => &[Decidable, DecidableDecide, Not, Bool, Eq], - DecidableDecide => &[Decidable, Not, Bool, Eq], - NatDecEq => &[Nat, Bool, Eq, Decidable, DecidableDecide, NatBeq], - NatDecLe => &[Nat, Bool, Eq, Decidable, DecidableDecide, NatLe, NatBle], - NatDecLt => &[Nat, Eq, Decidable, NatLe, NatDecLe], - IntNegOfNat | IntNatAbs => &[Nat, Int, Eq], - IntSubNatNat => &[Nat, Int, Bool, Eq, NatPred, NatSub, NatBle], - IntNeg => &[Nat, Int, Eq, IntNegOfNat], - IntAdd => &[Nat, Int, Eq, NatAdd, IntSubNatNat], - IntSub => &[Int, Eq, IntAdd, IntNeg], - IntMul => &[Nat, Int, Eq, NatMul, IntNegOfNat], - IntEdiv => &[Nat, Int, Eq, NatDiv, IntNeg], - IntEmod => &[Nat, Int, Eq, NatMod, IntSubNatNat], - IntPow => &[Nat, Int, Bool, Eq, NatPow, NatMod, NatBeq, IntNeg], - BitVecToNat => &[Nat, Eq, BitVec, NatMod, NatPow], - BitVecUlt => &[Nat, Bool, Eq, BitVec, NatMod, NatPow, NatBle], - StringDecEq => &[String, Bool, Eq, Decidable, DecidableDecide], - StringToByteArray => &[String, List, Char, StringOfList, Eq, ByteArray], - ListAppend => &[List, Eq], - StringAppend => &[String, List, Char, StringOfList, Eq, ListAppend], - IfThenElse => &[Decidable, Not, Eq], - IntLe => &[Int], - IntDecEq => &[Nat, Int, Bool, Eq, Decidable, DecidableDecide, NatBeq], - IntDecLe => { - &[Nat, Int, Bool, Eq, Decidable, DecidableDecide, IntLe, NatBle] - }, - IntDecLt => &[Nat, Int, Eq, Decidable, IntLe, IntAdd, IntDecLe], - IntBmod => &[ - Nat, Int, Eq, IfThenElse, IntLe, IntAdd, IntSub, IntEdiv, IntEmod, - IntDecLt, - ], - IntBdiv => &[ - Nat, Int, Eq, IfThenElse, IntLe, IntAdd, IntEdiv, IntEmod, IntDecLt, - NatDecEq, - ], - CharUtf8Size => &[Nat, Char, CharOfNat, Bool, Eq, NatMul, NatBle], - StringUtf8ByteSize => { - &[Nat, String, List, Char, StringOfList, Eq, NatAdd, CharUtf8Size] - }, - StringBack => { - &[Nat, String, List, Char, CharOfNat, StringOfList, Eq, NatMul] - }, - _ => &[], - } -} - -// Exactly Spec.evidenceAllowed: per equation, `Some(true)` a definitional entry -// only, `Some(false)` a proof only, `None` either. Empty for a role without -// equations. -fn evidence_allowed(role: Role) -> &'static [Option] { - use Role::{ - BitVecToNat, BitVecUlt, CharUtf8Size, DecidableDecide, IfThenElse, IntAdd, - IntBdiv, IntBmod, IntDecEq, IntDecLe, IntDecLt, IntEdiv, IntEmod, IntMul, - IntNatAbs, IntNeg, IntNegOfNat, IntPow, IntSub, IntSubNatNat, ListAppend, - NatAdd, NatBeq, NatBle, NatDecEq, NatDecLe, NatDecLt, NatDiv, NatGcd, - NatLand, NatLor, NatMod, NatMul, NatPow, NatPred, NatShiftLeft, - NatShiftRight, NatSub, NatXor, StringAppend, StringBack, StringDecEq, - StringToByteArray, StringUtf8ByteSize, - }; - const D: Option = Some(true); - const P: Option = Some(false); - const E: Option = None; - match role { - NatPred | NatAdd | NatSub | NatMul | NatPow => &[D, D], - NatBeq => &[D, D, D, D], - NatBle => &[D, D, D], - NatDiv | NatMod => &[P, P, P], - NatGcd | NatLand | NatLor | NatXor | NatShiftLeft | NatShiftRight - | IntSubNatNat => &[P, P], - DecidableDecide => &[E, E, P], - NatDecEq | NatDecLe | NatDecLt | IntSub | BitVecToNat | BitVecUlt - | StringDecEq | StringToByteArray | StringAppend | IntDecLt | IntBmod - | IntBdiv => &[E], - IntNegOfNat | IntNeg | IntNatAbs | ListAppend | IfThenElse - | StringUtf8ByteSize => &[E, E], - IntAdd | IntMul | IntEmod | IntDecEq | IntDecLe => &[E, E, E, E], - IntEdiv => &[E, E, E, E, E], - IntPow => &[E, P, P], - StringBack => &[E, E, E], - CharUtf8Size => &[P, P, P, P, P], - _ => &[], - } -} - -/// Authentication and table well-formedness only. There is no public unchecked -/// constructor, and obtaining this value does not authorize any native rule. -#[derive(Debug)] -pub struct BindingCandidate { - raw: RoleBinding, - table: RoleTable, -} - -impl BindingCandidate { - fn prepare(raw: RoleBinding) -> Result { - raw.check_hash().map_err(Error::Binding)?; - let table = RoleTable::from_bytes(&raw.bytes).map_err(Error::Binding)?; - if table.version != SPEC_VERSION { - return Err(Error::Binding(format!( - "unsupported specification version {}", - table.version - ))); - } - let mut roles = FxHashSet::default(); - let mut addresses = FxHashSet::default(); - for (role, address) in &table.bound { - if !roles.insert(*role) || !addresses.insert(address) { - return Err(Error::Binding("repeated bound role or address".into())); - } - } - for (role, _) in &table.bound { - for required in block(*role).iter().chain(dependencies(*role)) { - if !roles.contains(required) { - return Err(Error::Binding(format!( - "{role:?} requires {required:?}" - ))); - } - } - } - let mut evidence_roles = FxHashSet::default(); - for (role, evidence) in &table.evidence { - let allowed = evidence_allowed(*role); - if !roles.contains(role) - || !evidence_roles.insert(*role) - || allowed.is_empty() - || evidence.len() != allowed.len() - || evidence.iter().zip(allowed).any(|(entry, kind)| { - kind.is_some_and(|defeq| matches!(entry, Evidence::DefEq) != defeq) - }) - { - return Err(Error::Binding(format!( - "evidence does not match {role:?}'s specification" - ))); - } - } - for role in roles { - if !evidence_allowed(role).is_empty() && !evidence_roles.contains(&role) { - return Err(Error::Binding(format!( - "operation {role:?} has no evidence" - ))); - } - } - Ok(Self { raw, table }) - } - - pub fn address(&self) -> &Address { - &self.raw.address - } - pub fn bytes(&self) -> &[u8] { - &self.raw.bytes - } - pub fn role(&self, role: Role) -> Option<&Address> { - self.table.bound.iter().find_map(|(r, a)| (*r == role).then_some(a)) - } - pub fn required(&self, role: Role) -> Result<&Address, Error> { - self.role(role).ok_or(Error::MissingRole(role)) - } - pub fn evidence(&self, role: Role) -> Option<&[Evidence]> { - self - .table - .evidence - .iter() - .find_map(|(r, es)| (*r == role).then_some(es.as_slice())) - } -} - -#[derive(Debug)] -struct Source { - env: Env, - blobs: FxHashMap>>, - candidate: Option, - lookups: OnceLock>, - owners: OnceLock>, Error>>, - /// Addresses whose carried record (and, for a projection, its owner and - /// position) has been authenticated. Authentication is a function of this - /// immutable source alone, so a success is shared by every worker. - references: dashmap::DashSet
, - /// Records whose bytes have been authenticated (hash and canonical - /// encoding); a function of this immutable source, shared by every worker. - authentic: dashmap::DashSet
, - /// Authenticated mutual-owner shapes, without retaining expression trees. - /// Projection validation needs only member kinds and constructor positions. - owner_layouts: dashmap::DashMap>, - #[cfg(test)] - owner_decodes: std::sync::atomic::AtomicUsize, - /// The roles this source's binding has admitted, computed once by the - /// group validators (see [`ArtifactSource::admission`]). - admission: OnceLock, -} - -#[derive(Debug)] -enum MemberLayout { - Inductive { constructors: Box<[u64]>, is_unsafe: bool }, - Recursor { is_unsafe: bool }, - Definition { safety: DefinitionSafety }, -} - -impl MemberLayout { - fn from_member(member: &MutConst) -> Self { - match member { - MutConst::Indc(i) => Self::Inductive { - constructors: i.ctors.iter().map(|c| c.cidx).collect(), - is_unsafe: i.is_unsafe, - }, - MutConst::Recr(r) => Self::Recursor { is_unsafe: r.is_unsafe }, - MutConst::Defn(d) => Self::Definition { safety: d.safety }, - } - } - - fn in_safety_tier(&self) -> bool { - match self { - Self::Inductive { is_unsafe, .. } | Self::Recursor { is_unsafe } => { - *is_unsafe - }, - Self::Definition { safety } => *safety != DefinitionSafety::Safe, - } - } -} - -struct Projection { - owner: Address, - index: usize, - constructor: Option, - kind: u8, -} - -impl Projection { - fn validate(&self, layout: &[MemberLayout]) -> Result<(), &'static str> { - match (self.kind, layout.get(self.index)) { - (0, Some(MemberLayout::Inductive { constructors, .. })) => { - if let Some(index) = self.constructor { - let Some(cidx) = constructors.get(index) else { - return Err("constructor is missing"); - }; - if usize::try_from(*cidx).ok() != Some(index) { - return Err("constructor index differs"); - } - } - Ok(()) - }, - (1, Some(MemberLayout::Recursor { .. })) - | (2, Some(MemberLayout::Definition { .. })) => Ok(()), - _ => Err("projection kind/index differs from owner"), - } - } -} - -/// The stack of the thread that validates a source's groups. -const ADMISSION_STACK_SIZE: usize = 256 * 1024 * 1024; - -/// The outcome of admitting a source's bound roles: each group is admitted -/// whole, only after its validator has checked it, or not at all. -#[derive(Debug, Default)] -pub struct Admission { - roles: FxHashMap, - refused: Vec<(Role, String)>, - checked: dashmap::DashSet
, -} - -impl Admission { - /// The bound address of `role`, if its group was admitted. - pub fn address(&self, role: Role) -> Option<&Address> { - self.roles.get(&role) - } - /// The number of admitted roles. - pub fn admitted_count(&self) -> usize { - self.roles.len() - } - /// Every admitted role with its bound address. - pub(crate) fn roles(&self) -> &FxHashMap { - &self.roles - } - /// Each bound group that was not admitted, by its first role, with the - /// reason. - pub fn refused(&self) -> &[(Role, String)] { - &self.refused - } -} - -/// The prelude's roles: the certified checker's prelude shape, the blocks of -/// its committed prelude (Eq, Nat, PUnit, Empty, False, the quotient package, -/// And and Bool). A bound artifact must bind and admit all of them. -pub const PRELUDE: [Role; 26] = [ - Role::Eq, - Role::EqRefl, - Role::EqRec, - Role::Nat, - Role::NatZero, - Role::NatSucc, - Role::NatRec, - Role::PUnit, - Role::PUnitUnit, - Role::PUnitRec, - Role::Empty, - Role::EmptyRec, - Role::False, - Role::FalseRec, - Role::Quot, - Role::QuotMk, - Role::QuotLift, - Role::QuotInd, - Role::QuotSound, - Role::And, - Role::AndIntro, - Role::AndRec, - Role::Bool, - Role::BoolFalse, - Role::BoolTrue, - Role::BoolRec, -]; - -/// One privately owned input, shared cheaply between workers. No Env reference -/// escapes this API: its DashMaps must not become a mutable admission input. -/// Record backing buffers retain the loader's immutable heap/mmap contract. -#[derive(Clone, Debug)] -pub struct ArtifactSource(Arc); - -impl ixon::resource::addressed::RawSource for ArtifactSource { - fn constants(&self) -> Vec<(Address, LazyConstant)> { - self - .0 - .env - .consts - .iter() - .map(|entry| (entry.key().clone(), entry.value().clone())) - .collect() - } - fn blobs(&self) -> Vec<(Address, Vec)> { - self - .0 - .blobs - .iter() - .map(|(address, bytes)| (address.clone(), bytes.as_ref().clone())) - .collect() - } -} - -impl ArtifactSource { - pub fn prepare(mut env: Env) -> Result { - let candidate = - env.role_binding.clone().map(BindingCandidate::prepare).transpose()?; - let blobs = std::mem::take(&mut env.blobs) - .into_iter() - .map(|(address, bytes)| (address, Arc::new(bytes))) - .collect(); - let source = Self(Arc::new(Source { - env, - blobs, - candidate, - lookups: OnceLock::new(), - owners: OnceLock::new(), - references: dashmap::DashSet::new(), - authentic: dashmap::DashSet::new(), - owner_layouts: dashmap::DashMap::new(), - #[cfg(test)] - owner_decodes: std::sync::atomic::AtomicUsize::new(0), - admission: OnceLock::new(), - })); - if let Some(binding) = source.binding() { - for (_, address) in &binding.table.bound { - source.member(address)?; - } - for (_, entries) in &binding.table.evidence { - for entry in entries { - if let Evidence::Proof(address) = entry { - source.proof_record(address)?; - } - } - } - } - Ok(source) - } - - /// The records whose checks role admission defers: every bound record and - /// every proof of the binding's evidence. A group validator compares a - /// role with its specification, but it assumes the declared types of the - /// constants that the role's records and certificates mention. Those - /// records' units, and the units they reach, must be checked like any - /// other before a verdict may rest on an admitted role. - pub fn binding_support(&self) -> Vec
{ - self - .binding() - .map(|binding| binding.table.records().cloned().collect()) - .unwrap_or_default() - } - - /// The records a verdict on `roots` rests on: the reference closure of - /// `roots` and of [`Self::binding_support`]. A partial check is sound only - /// over this set; the closure of `roots` alone omits the role records and - /// certificates whose validity admission assumed. - pub fn verdict_closure( - &self, - roots: &[Address], - ) -> Result, Error> { - let mut all = roots.to_vec(); - all.extend(self.binding_support()); - self.closure(&all) - } - - /// Capture a borrowed input once, before sharing the result. The caller must - /// prevent concurrent map mutation during Env::clone; afterwards maps are - /// independent, constant buffers shared, and blob vectors copied only once. - pub fn snapshot(env: &Env) -> Result { - Self::prepare(env.clone()) - } - pub fn binding(&self) -> Option<&BindingCandidate> { - self.0.candidate.as_ref() - } - pub fn same_source(&self, other: &Self) -> bool { - Arc::ptr_eq(&self.0, &other.0) - } - pub fn required_role(&self, role: Role) -> Result<&Address, Error> { - self.binding().ok_or(Error::Unbound)?.required(role) - } - - /// The roles admitted for this source. Computed once, by validating each - /// bound group in dependency order with only the groups admitted before - /// it available; every worker sharing this source reads the result. - pub fn admission(&self) -> &Admission { - self.0.admission.get_or_init(|| { - // Validation runs the kernel; give it the stack a check gets. - std::thread::scope(|scope| { - std::thread::Builder::new() - .name("role-admission".into()) - .stack_size(ADMISSION_STACK_SIZE) - .spawn_scoped(scope, || groups::admit(self)) - .expect("spawn the role admission thread") - .join() - .unwrap_or_else(|panic| std::panic::resume_unwind(panic)) - }) - }) - } - /// Whether `role`'s group has been admitted. - pub fn is_admitted(&self, role: Role) -> bool { - self.admission().address(role).is_some() - } - /// Whether the declaration at `address` is in the safety tier: partial or - /// unsafe, so that a check of it is the certified checker's `typed` - /// outcome (well-typed, with partial and unsafe constants opaque) rather - /// than acceptance into the logical theory. - pub fn in_safety_tier(&self, address: &Address) -> bool { - let Ok(record) = self.record(address) else { return false }; - match record.projection() { - Ok(Some(projection)) => { - return self - .projection_layout(&record, &projection) - .is_ok_and(|layout| layout[projection.index].in_safety_tier()); - }, - Ok(None) => {}, - Err(_) => return false, - } - match &record.constant().info { - ConstantInfo::Defn(d) => d.safety != DefinitionSafety::Safe, - ConstantInfo::Axio(a) => a.is_unsafe, - ConstantInfo::Recr(r) => r.is_unsafe, - _ => false, - } - } - - /// Why a bound artifact's units may not be checked, if they may not: a - /// prelude role ([`PRELUDE`]) that is unbound, or whose group was refused. - pub fn prelude_refusal(&self) -> Option { - let binding = self.binding()?; - let role = PRELUDE.into_iter().find(|role| !self.is_admitted(*role))?; - Some(if binding.role(role).is_none() { - format!("the binding does not bind the prelude role {}", role.lean_name()) - } else { - format!("the prelude role {} is not admitted", role.lean_name()) - }) - } - /// The capability to use `role`'s rules, issued only for an admitted role. - pub fn capability(&self, role: Role) -> Option { - self.is_admitted(role).then(|| AdmittedRole { source: self.clone(), role }) - } - - // Metadata is taken from the same captured input. These narrow views do not - // expose an Env/DashMap that could mutate admission's source or read caches. - pub(crate) fn contains_record(&self, address: &Address) -> bool { - self.0.env.consts.contains_key(address) - } - pub(crate) fn addresses(&self) -> Vec
{ - self.0.env.consts.iter().map(|entry| entry.key().clone()).collect() - } - pub(crate) fn record_tag( - &self, - address: &Address, - ) -> Result { - self - .raw_record(address)? - .peek_variant() - .map_err(|reason| Error::Record(address.clone(), reason)) - } - pub(crate) fn named( - &self, - name: &ix_common::env::Name, - ) -> Option { - self.0.env.lookup_name(name) - } - pub(crate) fn name(&self, address: &Address) -> Option { - self.0.env.get_name(address) - } - pub(crate) fn hint( - &self, - address: &Address, - ) -> Option { - self.0.env.anon_hints.get(address).map(|entry| *entry) - } - pub(crate) fn lookups(&self) -> Arc { - Arc::clone(self.0.lookups.get_or_init(|| { - Arc::new(crate::ingress::build_ixon_ingress_lookups(&self.0.env)) - })) - } - pub(crate) fn unit_roles(&self, address: &Address) -> Result<&[Role], Error> { - let owners = self - .0 - .owners - .get_or_init(|| { - let mut owners: FxHashMap> = FxHashMap::default(); - if let Some(binding) = self.binding() { - for (role, address) in &binding.table.bound { - let member = self.member(address)?; - let owner = - member.owner().unwrap_or(member.record()).address().clone(); - owners.entry(owner).or_default().push(*role); - } - } - Ok(owners) - }) - .as_ref() - .map_err(Clone::clone)?; - let record = self.record(address)?; - let owner = match &record.constant().info { - ConstantInfo::IPrj(p) => &p.block, - ConstantInfo::CPrj(p) => &p.block, - ConstantInfo::RPrj(p) => &p.block, - ConstantInfo::DPrj(p) => &p.block, - _ => address, - }; - Ok(owners.get(owner).map_or(&[][..], Vec::as_slice)) - } - pub fn block_of(&self, address: &Address) -> Result { - let record = self.record(address)?; - Ok(match &record.constant().info { - ConstantInfo::IPrj(p) => p.block.clone(), - ConstantInfo::CPrj(p) => p.block.clone(), - ConstantInfo::RPrj(p) => p.block.clone(), - ConstantInfo::DPrj(p) => p.block.clone(), - _ => address.clone(), - }) - } - pub fn closure( - &self, - roots: &[Address], - ) -> Result, Error> { - let mut pending = roots.to_vec(); - let mut seen = FxHashSet::default(); - while let Some(address) = pending.pop() { - if !seen.insert(address.clone()) { - continue; - } - if self.contains_record(&address) { - let record = self.record(&address)?; - Env::closure_edges(&address, record.constant(), &mut pending); - } - if self.0.blobs.contains_key(&address) { - self.blob(&address)?; - } - } - Ok(seen) - } - - /// Every later admission read, including transitive support, enters here. - /// Authenticate exact raw bytes and decode them without LazyConstant::get, - /// even if the compiler supplied an already materialized Constant cache. - fn raw_record(&self, address: &Address) -> Result { - let raw = self - .0 - .env - .consts - .get(address) - .map(|entry| entry.value().clone()) - .ok_or_else(|| Error::MissingRecord(address.clone()))?; - if !self.0.authentic.contains(address) - && Address::hash(raw.raw_bytes()) != *address - { - return Err(Error::Record(address.clone(), "hash mismatch".into())); - } - Ok(raw) - } - - /// The record at `address`, authenticated: its bytes hash to the address - /// and are the canonical encoding of what they decode to. A source is - /// immutable, so each record is authenticated once and then only decoded. - pub fn record(&self, address: &Address) -> Result { - let raw = self.raw_record(address)?; - let bytes = raw.raw_bytes(); - let fail = |reason| Error::Record(address.clone(), reason); - let mut input = bytes; - let value = Constant::get(&mut input).map_err(fail)?; - if !self.0.authentic.contains(address) { - let mut canonical = Vec::new(); - value.put(&mut canonical); - if !input.is_empty() || canonical != bytes { - return Err(fail("noncanonical bytes".into())); - } - self.0.authentic.insert(address.clone()); - } - if let ConstantInfo::Muts(members) = &value.info { - #[cfg(test)] - self.0.owner_decodes.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - self.0.owner_layouts.entry(address.clone()).or_insert_with(|| { - members.iter().map(MemberLayout::from_member).collect() - }); - } - Ok(Record { address: address.clone(), raw, value }) - } - - pub fn blob(&self, address: &Address) -> Result { - let bytes = self - .0 - .blobs - .get(address) - .ok_or_else(|| Error::MissingBlob(address.clone()))?; - if Address::hash(bytes) != *address { - return Err(Error::BlobHash(address.clone())); - } - Ok(Blob { address: address.clone(), bytes: Arc::clone(bytes) }) - } - - /// Resolve physical projection/owner provenance, not semantic role identity. - pub fn member(&self, address: &Address) -> Result { - let record = self.record(address)?; - let Some(projection) = record.projection()? else { - return Ok(Member { - record, - owner: None, - index: None, - constructor: None, - }); - }; - // Payload consumers still get a fresh decoding of the authenticated - // owner. Validation-only callers use the compact layout instead. - let owner = self.record(&projection.owner)?; - self.projection_layout(&record, &projection)?; - Ok(Member { - record, - owner: Some(owner), - index: Some(projection.index), - constructor: projection.constructor, - }) - } - - fn projection_layout( - &self, - record: &Record, - projection: &Projection, - ) -> Result, Error> { - let fail = |reason| Error::Member(record.address.clone(), reason); - let cached = - self.0.owner_layouts.get(&projection.owner).map(|l| Arc::clone(&l)); - let layout = if let Some(layout) = cached { - layout - } else { - // Only record(), after authenticating the raw bytes and their canonical - // decoding, may publish a layout. Never consult LazyConstant's AST cache. - self.record(&projection.owner)?; - self - .0 - .owner_layouts - .get(&projection.owner) - .map(|l| Arc::clone(&l)) - .ok_or_else(|| fail("owner is not a mutual block"))? - }; - projection.validate(&layout).map_err(fail)?; - Ok(layout) - } - - fn authenticate_member(&self, address: &Address) -> Result<(), Error> { - let record = self.record(address)?; - if let Some(projection) = record.projection()? { - self.projection_layout(&record, &projection)?; - } - Ok(()) - } - - /// Authenticate the carried record a reference names: its bytes hash to - /// the address and decode canonically, and a projection is the member of - /// its owner at its index (see [`Self::member`]). Key presence alone is not - /// enough: a worker may already hold a declaration for the address, built - /// from an owner payload, which must not stand in for a corrupted record. - pub(crate) fn authenticate_reference( - &self, - address: &Address, - ) -> Result<(), Error> { - if self.0.references.contains(address) { - return Ok(()); - } - self.authenticate_member(address)?; - self.0.references.insert(address.clone()); - Ok(()) - } - - /// Only raw theorem-kind/owner coverage is established here. Its value and - /// certificate statement still require isolated semantic admission. - pub fn proof_record(&self, address: &Address) -> Result { - let member = self.member(address)?; - if !member.definition().is_some_and(|d| { - d.kind == DefKind::Theorem && d.safety == DefinitionSafety::Safe - }) { - return Err(Error::NotTheorem(address.clone())); - } - Ok(member) - } -} - -/// Genuine canonical bytes and their decoded payload. Neither type nor value -/// has been checked. The raw buffer is retained alongside its claimed key. -#[derive(Debug)] -pub struct Record { - address: Address, - raw: LazyConstant, - value: Constant, -} -impl Record { - fn projection(&self) -> Result, Error> { - let fail = |reason| Error::Member(self.address.clone(), reason); - use ConstantInfo as C; - let (owner, index, constructor, kind) = match &self.value.info { - C::IPrj(p) => (&p.block, p.idx, None, 0), - C::CPrj(p) => (&p.block, p.idx, Some(p.cidx), 0), - C::RPrj(p) => (&p.block, p.idx, None, 1), - C::DPrj(p) => (&p.block, p.idx, None, 2), - C::Muts(_) => return Err(fail("a block is not a member")), - _ => return Ok(None), - }; - if !self.value.refs.is_empty() - || !self.value.sharing.is_empty() - || !self.value.univs.is_empty() - { - return Err(fail("projection carries nonempty tables")); - } - Ok(Some(Projection { - owner: owner.clone(), - index: usize::try_from(index) - .map_err(|_error| fail("member index overflows"))?, - constructor: constructor - .map(usize::try_from) - .transpose() - .map_err(|_error| fail("constructor index overflows"))?, - kind, - })) - } - - pub fn address(&self) -> &Address { - &self.address - } - pub fn bytes(&self) -> &[u8] { - self.raw.raw_bytes() - } - pub fn constant(&self) -> &Constant { - &self.value - } - pub(crate) fn into_constant(self) -> Constant { - self.value - } -} - -#[derive(Debug)] -pub struct Blob { - address: Address, - bytes: Arc>, -} -impl Blob { - pub fn address(&self) -> &Address { - &self.address - } - pub fn bytes(&self) -> &[u8] { - &self.bytes - } -} - -#[derive(Debug)] -pub struct Member { - record: Record, - owner: Option, - index: Option, - constructor: Option, -} -impl Member { - pub fn record(&self) -> &Record { - &self.record - } - pub fn owner(&self) -> Option<&Record> { - self.owner.as_ref() - } - pub fn index(&self) -> Option { - self.index - } - pub fn constructor_index(&self) -> Option { - self.constructor - } - pub fn definition(&self) -> Option<&Definition> { - if self.constructor.is_some() { - return None; - } - match (&self.owner, self.index, &self.record.value.info) { - (None, None, ConstantInfo::Defn(d)) => Some(d), - (Some(owner), Some(i), _) => match &owner.value.info { - ConstantInfo::Muts(ms) => match ms.get(i) { - Some(MutConst::Defn(d)) => Some(d), - _ => None, - }, - _ => None, - }, - _ => None, - } - } -} - -/// The capability to use one role's rules for one source. Only -/// [`ArtifactSource::capability`] issues it, and only for a role whose group -/// its validator admitted; there is no other constructor, no -/// Default/deserialize implementation and no public field, so a decoded or -/// authenticated candidate cannot create one. -/// ```compile_fail -/// use ix_kernel::role_admission::AdmittedRole; -/// let rule: AdmittedRole = Default::default(); -/// ``` -/// ```compile_fail -/// use ix_kernel::role_admission::{AdmittedRole, ArtifactSource}; -/// use ixon::{env::Env, role_table::Role}; -/// let source = ArtifactSource::prepare(Env::new()).unwrap(); -/// let rule = AdmittedRole { source, role: Role::Nat }; -/// ``` -pub struct AdmittedRole { - source: ArtifactSource, - role: Role, -} -impl AdmittedRole { - pub fn role(&self) -> Role { - self.role - } - pub fn belongs_to(&self, source: &ArtifactSource) -> bool { - self.source.same_source(source) - } -} - -mod groups; - -#[cfg(test)] -mod tests; diff --git a/crates/kernel/src/role_admission/equations.rs b/crates/kernel/src/role_admission/equations.rs deleted file mode 100644 index d18a5c9ea..000000000 --- a/crates/kernel/src/role_admission/equations.rs +++ /dev/null @@ -1,305 +0,0 @@ -//! Admit generated definition specifications through the ordinary checker. -//! The operation under validation is absent from the native primitive table; -//! only earlier admitted groups can participate in checking its equations. - -use super::*; -use crate::expr::ExprData; - -pub(super) struct SpecGroup { - pub roles: &'static [Role], - pub needs: &'static [Role], -} - -pub(super) struct Equation { - pub statement: E, - /// Present exactly for an unconditional equation, closed over its variables. - pub definitional_sides: Option<(E, E)>, -} - -pub(super) enum Specification { - Anchor { levels: u64, typ: E }, - Definition { levels: u64, typ: E, equations: Vec }, -} - -pub(super) fn reference( - source: &ArtifactSource, - role: Role, - levels: &[U], -) -> Result { - let address = source.required_role(role).map_err(|e| e.to_string())?; - Ok(cnst(address, levels)) -} - -/// Check the generated terms independently of dependency metadata. Every -/// external reference must belong to a completely admitted group; the group -/// being checked may refer to its own candidates without accelerating them. -fn specification_references<'a>( - source: &ArtifactSource, - admitted: &Admission, - group: &[Role], - terms: impl IntoIterator, -) -> Result, String> { - let mut allowed = FxHashSet::default(); - for role in group { - allowed - .insert(source.required_role(*role).map_err(|e| e.to_string())?.clone()); - } - for (role, address) in admitted.roles() { - if super::super::block(*role).iter().all(|member| { - source - .required_role(*member) - .is_ok_and(|bound| admitted.address(*member) == Some(bound)) - }) { - allowed.insert(address.clone()); - } - } - let mut roots = FxHashSet::default(); - let mut seen = FxHashSet::default(); - let mut pending: Vec<_> = terms.into_iter().collect(); - while let Some(term) = pending.pop() { - if !seen.insert(term.hash_key()) { - continue; - } - if let ExprData::Const(id, ..) | ExprData::Prj(id, ..) = term.data() { - if !allowed.contains(&id.addr) { - return Err(format!( - "generated specification references {} outside the admitted groups", - id.addr.hex() - )); - } - roots.insert(id.addr.clone()); - } - match term.data() { - ExprData::Const(..) | ExprData::Var(..) | ExprData::Sort(..) => {}, - ExprData::App(fun, arg, _) => pending.extend([fun, arg]), - ExprData::Lam(_, _, _, domain, body, _) - | ExprData::All(_, _, _, _, domain, body, _) => { - pending.extend([domain, body]) - }, - ExprData::Let(_, typ, value, body, _, _) => { - pending.extend([typ, value, body]) - }, - ExprData::Prj(_, _, value, _) => pending.push(value), - ExprData::FVar(..) | ExprData::Nat(..) | ExprData::Str(..) => { - return Err( - "generated specification contains an open variable or literal".into(), - ); - }, - } - } - Ok(roots.into_iter().collect()) -} - -pub(super) fn validate_group( - source: &ArtifactSource, - admitted: &Admission, - roles: &[Role], -) -> Result<(), String> { - let mut anchors = Vec::new(); - for role in roles { - match specs::specification(*role, source)? { - Some(Specification::Anchor { levels, typ }) => { - anchors.push((*role, levels, typ)); - }, - Some(Specification::Definition { levels, typ, equations }) - if roles.len() == 1 => - { - return validate_definition( - source, admitted, *role, levels, &typ, equations, - ); - }, - _ => { - return Err( - "the group has no supported generated specification".into(), - ); - }, - } - } - if anchors.is_empty() { - return Err("an empty specification group".into()); - } - let mut roots = specification_references( - source, - admitted, - roles, - anchors.iter().map(|(_, _, typ)| typ), - )?; - validate_anchors(source, admitted, &anchors)?; - // New support anchors are ordinary checked declarations as well as having - // the specified type. They cannot introduce an axiom through the role table. - let addresses = roles - .iter() - .map(|role| source.required_role(*role).map_err(|e| e.to_string())) - .collect::, _>>()?; - let mut env = ingress_records(source, &addresses)?; - roots.extend(addresses.into_iter().cloned()); - check_support(source, admitted, &mut env, &roots) -} - -fn validate_definition( - source: &ArtifactSource, - admitted: &Admission, - role: Role, - levels: u64, - typ: &E, - equations: Vec, -) -> Result<(), String> { - let address = source.required_role(role).map_err(|e| e.to_string())?; - source.member(address).map_err(|e| e.to_string())?; - let evidence = - source.binding().and_then(|binding| binding.evidence(role)).unwrap_or(&[]); - if evidence.len() != equations.len() { - return Err(differs(address, "equation evidence count")); - } - let proofs: Vec
= evidence - .iter() - .filter_map(|entry| match entry { - Evidence::Proof(proof) => Some(proof.clone()), - Evidence::DefEq => None, - }) - .collect(); - let mut records = vec![address]; - records.extend(proofs.iter()); - let mut env = ingress_records(source, &records)?; - let (ty, _) = expect_safe_definition(&env, address, levels)?; - for proof in &proofs { - expect_theorem_evidence(&env, proof, levels)?; - } - // A specified RHS can mention an anchor that the actual definition and - // proof never reference. Include all generated terms, not only actual roots. - let mut terms = vec![typ]; - for equation in &equations { - terms.push(&equation.statement); - if let Some((left, right)) = &equation.definitional_sides { - terms.extend([left, right]); - } - } - let mut roots = specification_references(source, admitted, &[role], terms)?; - roots.extend(records.into_iter().cloned()); - check_support(source, admitted, &mut env, &roots)?; - let context = - PrimitiveContext::bound(source.clone(), admitted.roles(), |_| None); - let lookups = IxonIngressLookups::default(); - let mut checker = - TypeChecker::for_artifact_anon(&mut env, source, &lookups, context); - let inferred = checker.infer(typ).map_err(|e| e.to_string())?; - checker.ensure_sort(&inferred).map_err(|e| e.to_string())?; - if !checker.is_def_eq(&ty, typ).map_err(|e| e.to_string())? { - return Err(differs(address, "definition type")); - } - for (index, (equation, evidence)) in - equations.into_iter().zip(evidence).enumerate() - { - let fail = |error| format!("{}: equation {index}: {error}", address.hex()); - checker.reset(); - let inferred = - checker.infer(&equation.statement).map_err(|e| fail(e.to_string()))?; - checker.ensure_sort(&inferred).map_err(|e| fail(e.to_string()))?; - match evidence { - Evidence::DefEq => { - let Some((left, right)) = equation.definitional_sides else { - return Err(fail( - "a conditional equation needs proof evidence".into(), - )); - }; - let left_type = - checker.infer(&left).map_err(|e| fail(e.to_string()))?; - let right_type = - checker.infer(&right).map_err(|e| fail(e.to_string()))?; - if !checker - .is_def_eq(&left_type, &right_type) - .map_err(|e| fail(e.to_string()))? - || !checker - .is_def_eq(&left, &right) - .map_err(|e| fail(e.to_string()))? - { - return Err(fail("the definitional equation does not hold".into())); - } - }, - Evidence::Proof(proof) => { - let (ty, val) = - load_checked_theorem(source, admitted, &mut checker, proof, levels) - .map_err(&fail)?; - if !checker - .is_def_eq(&ty, &equation.statement) - .map_err(|e| fail(e.to_string()))? - { - return Err(fail("the theorem has a different statement".into())); - } - if !check_proof_term(&mut checker, &val, &equation.statement) - .map_err(&fail)? - { - return Err(fail("the proof has a different type".into())); - } - }, - } - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use ix_common::env::Name; - use ixon::env::{Env, RoleBinding}; - use ixon::role_table::RoleTable; - - #[test] - fn generated_references_require_complete_admitted_external_groups() { - let mut input = - include_bytes!("../../tests/fixtures/role_admission/basis.ixe") - .as_slice(); - let mut env = Env::get(&mut input).unwrap(); - let nat_group = crate::role_admission::block(Role::Nat); - let eq_group = crate::role_admission::block(Role::Eq); - let bound: Vec<_> = nat_group - .iter() - .chain(eq_group) - .map(|role| { - let name = - role.lean_name().split('.').fold(Name::anon(), |parent, part| { - Name::str(parent, part.to_owned()) - }); - (*role, env.lookup_name(&name).unwrap().addr) - }) - .collect(); - let table = RoleTable { - version: crate::role_admission::SPEC_VERSION, - bound, - evidence: vec![], - }; - let bytes = table.to_bytes().unwrap(); - env.role_binding = - Some(RoleBinding { address: Address::hash(&bytes), bytes: bytes.into() }); - let source = ArtifactSource::prepare(env).unwrap(); - let term = reference(&source, Role::NatSucc, &[]).unwrap(); - let mut admitted = Admission::default(); - assert!( - specification_references(&source, &admitted, eq_group, [&term]).is_err() - ); - admitted.roles.insert( - Role::NatSucc, - source.required_role(Role::NatSucc).unwrap().clone(), - ); - assert!( - specification_references(&source, &admitted, eq_group, [&term]).is_err() - ); - for role in nat_group { - admitted - .roles - .insert(*role, source.required_role(*role).unwrap().clone()); - } - assert!( - specification_references(&source, &admitted, eq_group, [&term]).is_ok() - ); - assert!( - specification_references( - &source, - &Admission::default(), - nat_group, - [&term] - ) - .is_ok() - ); - } -} diff --git a/crates/kernel/src/role_admission/groups.rs b/crates/kernel/src/role_admission/groups.rs deleted file mode 100644 index 7ccaaf6dd..000000000 --- a/crates/kernel/src/role_admission/groups.rs +++ /dev/null @@ -1,2381 +0,0 @@ -//! Group validators. Each admits one specification group of a binding for a -//! source, or refuses it with a reason. The groups and their specifications -//! are the certified checker's: -//! -//! - a basis block (`Nat`, `Eq`, `PUnit`, `Empty`, `False`, `Quot`) or a -//! standard axiom's family (`Iff`, `Nonempty`) is admitted only when -//! every bound role authenticates as the carried record at the position -//! the specification fixes (an inductive's unit holding nothing else, its -//! constructors by index, the quotient records by kind); the -//! declarations, read into a fresh anonymous environment, are exactly the -//! specified ones built from the bound addresses; and the kernel checks -//! them there with only the groups admitted before this one. Every role a -//! standard axiom's type mentions is such a block: an admitted axiom is -//! true only of the declarations it quantifies over, so a type does not -//! pin them; -//! - an anchor (the literal support, `Bool`, `True`, `And`) is admitted only -//! when its record authenticates, is not unsafe, has exactly the specified -//! level count and type, and that type checks as a type with only the -//! groups admitted before it. An anchor is specified by its type alone; -//! its declaration is checked like any other once admitted; -//! - an axiom (`propext`, `Classical.choice`, the inert `sorryAx`) is -//! admitted only when its record is a safe axiom of exactly the specified -//! level count and type over the admitted groups, and checks. -//! -//! A group is admitted whole or not at all, after the groups it needs. -//! Nothing outside this module creates an admission. - -use ix_common::address::Address; -use ix_common::env::{DefinitionSafety, QuotKind}; -use ixon::constant::DefKind; -use ixon::constant::{ConstantInfo, MutConst}; -use ixon::role_table::{Evidence, Role}; -use rustc_hash::{FxHashMap, FxHashSet}; - -use super::{Admission, ArtifactSource}; -use crate::constant::KConst; -use crate::env::KEnv; -use crate::expr::KExpr; -use crate::id::KId; -use crate::ingress::{IxonIngressLookups, ingress_anon_addr_shallow_input}; -use crate::input::Input; -use crate::level::KUniv; -use crate::mode::Anon; -use crate::primitive_context::PrimitiveContext; -use crate::tc::TypeChecker; - -type E = KExpr; -type U = KUniv; - -#[path = "equations.rs"] -mod equations; -#[path = "specs.rs"] -mod specs; - -/// One specification group. -struct Group { - /// Its roles, the lead first. - roles: &'static [Role], - /// The lead roles of the groups its specification mentions. - needs: &'static [Role], - validate: fn(&ArtifactSource, &Admission) -> Result<(), String>, -} - -/// Validators with declaration-shape or recurrence-specific checks. The -/// generated group order also includes definitions checked by their equations. -const SPECIALIZED_GROUPS: &[Group] = &[ - Group { - roles: &[Role::Nat, Role::NatZero, Role::NatSucc, Role::NatRec], - needs: &[], - validate: validate_nat, - }, - Group { - roles: &[Role::Eq, Role::EqRefl, Role::EqRec], - needs: &[], - validate: validate_eq, - }, - Group { - roles: &[Role::PUnit, Role::PUnitUnit, Role::PUnitRec], - needs: &[], - validate: validate_punit, - }, - Group { - roles: &[Role::Empty, Role::EmptyRec], - needs: &[], - validate: |source, admitted| { - validate_empty_like( - source, - admitted, - [Role::Empty, Role::EmptyRec], - type1(), - ) - }, - }, - Group { - roles: &[Role::False, Role::FalseRec], - needs: &[], - validate: |source, admitted| { - validate_empty_like( - source, - admitted, - [Role::False, Role::FalseRec], - prop(), - ) - }, - }, - Group { - roles: &[ - Role::Quot, - Role::QuotMk, - Role::QuotLift, - Role::QuotInd, - Role::QuotSound, - ], - needs: &[Role::Eq], - validate: validate_quot, - }, - Group { roles: &[Role::Char], needs: &[], validate: validate_char }, - Group { roles: &[Role::String], needs: &[], validate: validate_string }, - Group { - roles: &[Role::List, Role::ListNil, Role::ListCons], - needs: &[], - validate: validate_list, - }, - Group { - roles: &[Role::CharOfNat], - needs: &[Role::Nat, Role::Char], - validate: validate_char_of_nat, - }, - Group { - roles: &[Role::StringOfList], - needs: &[Role::List, Role::Char, Role::String], - validate: validate_string_of_list, - }, - Group { - roles: &[Role::Bool, Role::BoolFalse, Role::BoolTrue, Role::BoolRec], - needs: &[], - validate: validate_bool, - }, - Group { - roles: &[Role::NatPred], - needs: &[Role::Nat], - validate: |source, admitted| { - validate_structural_op(source, admitted, Role::NatPred) - }, - }, - Group { - roles: &[Role::NatAdd], - needs: &[Role::Nat], - validate: |source, admitted| { - validate_structural_op(source, admitted, Role::NatAdd) - }, - }, - Group { - roles: &[Role::NatSub], - needs: &[Role::Nat, Role::NatPred], - validate: |source, admitted| { - validate_structural_op(source, admitted, Role::NatSub) - }, - }, - Group { - roles: &[Role::NatMul], - needs: &[Role::Nat, Role::NatAdd], - validate: |source, admitted| { - validate_structural_op(source, admitted, Role::NatMul) - }, - }, - Group { - roles: &[Role::NatPow], - needs: &[Role::Nat, Role::NatAdd, Role::NatMul], - validate: |source, admitted| { - validate_structural_op(source, admitted, Role::NatPow) - }, - }, - Group { - roles: &[Role::NatBeq], - needs: &[Role::Nat, Role::Bool], - validate: |source, admitted| { - validate_structural_op(source, admitted, Role::NatBeq) - }, - }, - Group { - roles: &[Role::NatBle], - needs: &[Role::Nat, Role::Bool], - validate: |source, admitted| { - validate_structural_op(source, admitted, Role::NatBle) - }, - }, - Group { - roles: &[Role::True, Role::TrueIntro], - needs: &[], - validate: validate_true, - }, - Group { - roles: &[Role::And, Role::AndIntro, Role::AndRec], - needs: &[], - validate: validate_and, - }, - Group { - roles: &[Role::Iff, Role::IffIntro, Role::IffRec], - needs: &[], - validate: validate_iff, - }, - Group { - roles: &[Role::Nonempty, Role::NonemptyIntro, Role::NonemptyRec], - needs: &[], - validate: validate_nonempty, - }, - Group { - roles: &[Role::Propext], - needs: &[Role::Iff, Role::Eq], - validate: validate_propext, - }, - Group { - roles: &[Role::Choice], - needs: &[Role::Nonempty], - validate: validate_choice, - }, - Group { - roles: &[Role::SorryAx], - needs: &[Role::Bool], - validate: validate_sorry, - }, - Group { - roles: &[Role::NatDiv], - needs: &[ - Role::Nat, - Role::NatPred, - Role::NatSub, - Role::NatBle, - Role::Bool, - Role::Eq, - ], - validate: |source, admitted| validate_wf_op(source, admitted, Role::NatDiv), - }, - Group { - roles: &[Role::NatMod], - needs: &[ - Role::Nat, - Role::NatPred, - Role::NatSub, - Role::NatBle, - Role::Bool, - Role::Eq, - ], - validate: |source, admitted| validate_wf_op(source, admitted, Role::NatMod), - }, - Group { - roles: &[Role::NatGcd], - needs: &[Role::Nat, Role::NatBle, Role::NatMod, Role::Bool, Role::Eq], - validate: |source, admitted| validate_wf_op(source, admitted, Role::NatGcd), - }, - Group { - roles: &[Role::NatLand], - needs: &[ - Role::Nat, - Role::NatAdd, - Role::NatMul, - Role::NatBle, - Role::NatDiv, - Role::NatMod, - Role::Bool, - Role::Eq, - ], - validate: |source, admitted| { - validate_wf_op(source, admitted, Role::NatLand) - }, - }, - Group { - roles: &[Role::NatLor], - needs: &[ - Role::Nat, - Role::NatAdd, - Role::NatSub, - Role::NatMul, - Role::NatBle, - Role::NatDiv, - Role::NatMod, - Role::Bool, - Role::Eq, - ], - validate: |source, admitted| validate_wf_op(source, admitted, Role::NatLor), - }, - Group { - roles: &[Role::NatXor], - needs: &[ - Role::Nat, - Role::NatAdd, - Role::NatMul, - Role::NatBle, - Role::NatDiv, - Role::NatMod, - Role::Bool, - Role::Eq, - ], - validate: |source, admitted| validate_wf_op(source, admitted, Role::NatXor), - }, - Group { - roles: &[Role::NatShiftLeft], - needs: &[ - Role::Nat, - Role::NatSub, - Role::NatMul, - Role::NatBle, - Role::Bool, - Role::Eq, - ], - validate: |source, admitted| { - validate_wf_op(source, admitted, Role::NatShiftLeft) - }, - }, - Group { - roles: &[Role::NatShiftRight], - needs: &[ - Role::Nat, - Role::NatSub, - Role::NatBle, - Role::NatDiv, - Role::Bool, - Role::Eq, - ], - validate: |source, admitted| { - validate_wf_op(source, admitted, Role::NatShiftRight) - }, - }, -]; - -/// Admit every bound group of `source` that its validator accepts. -pub(super) fn admit(source: &ArtifactSource) -> Admission { - let mut admission = Admission::default(); - let Some(binding) = source.binding() else { - return admission; - }; - for group in specs::GROUPS { - let lead = group.roles[0]; - if binding.role(lead).is_none() { - continue; - } - let missing: Vec = group - .needs - .iter() - .copied() - .filter(|role| admission.address(*role).is_none()) - .collect(); - let outcome = if missing.is_empty() { - match SPECIALIZED_GROUPS - .iter() - .find(|existing| existing.roles == group.roles) - { - Some(existing) if existing.needs != group.needs => Err( - "generated dependencies differ from the existing validator".into(), - ), - Some(existing) => (existing.validate)(source, &admission), - None => equations::validate_group(source, &admission, group.roles), - } - } else { - Err(format!("needs the unadmitted group(s) {missing:?}")) - }; - match outcome { - Ok(()) => { - for role in group.roles { - if let Some(address) = binding.role(*role) { - admission.roles.insert(*role, address.clone()); - } - } - }, - Err(reason) => admission.refused.push((lead, reason)), - } - } - admission -} - -fn bound( - source: &ArtifactSource, - roles: [Role; N], -) -> Result<[Address; N], String> { - let mut out = Vec::with_capacity(N); - for role in roles { - out.push( - source.required_role(role).map_err(|error| error.to_string())?.clone(), - ); - } - out.try_into().map_err(|_error| "role count".to_string()) -} - -/// Locate the carried records of an inductive basis block. `inductive` is -/// member `i` of an owner unit holding only it and any of `recursors` that -/// are members (a recursor may instead be its own standalone record), and -/// `constructors[c]` is constructor `c` of member `i`. -fn locate_inductive_unit( - source: &ArtifactSource, - inductive: &Address, - constructors: &[&Address], - recursors: &[&Address], -) -> Result<(), String> { - let fail = |what: &str| Err(format!("{what} ({})", inductive.hex())); - let member = source.member(inductive).map_err(|e| e.to_string())?; - let (Some(owner), Some(index), None) = - (member.owner(), member.index(), member.constructor_index()) - else { - return fail("the inductive is not a member of a carried unit"); - }; - let owner_address = owner.address().clone(); - let ConstantInfo::Muts(members) = &owner.constant().info else { - return fail("the inductive's owner is not a mutual unit"); - }; - let Some(MutConst::Indc(declared)) = members.get(index) else { - return fail("the inductive's member is not an inductive"); - }; - if declared.ctors.len() != constructors.len() { - return fail("the inductive's constructor count differs"); - } - for (cidx, constructor) in constructors.iter().enumerate() { - let member = source.member(constructor).map_err(|e| e.to_string())?; - if member.owner().map(super::Record::address) != Some(&owner_address) - || member.index() != Some(index) - || member.constructor_index() != Some(cidx) - { - return fail(&format!("constructor {cidx} is not at its position")); - } - } - let mut in_unit = Vec::new(); - for recursor in recursors { - let member = source.member(recursor).map_err(|e| e.to_string())?; - match (member.owner(), member.index()) { - (None, None) - if matches!(member.record().constant().info, ConstantInfo::Recr(_)) => { - }, - (Some(owner), Some(at)) - if owner.address() == &owner_address - && member.constructor_index().is_none() - && matches!(members.get(at), Some(MutConst::Recr(_))) - && !in_unit.contains(&at) => - { - in_unit.push(at); - }, - _ => return fail("a recursor is neither standalone nor in the unit"), - } - } - if members.len() != 1 + in_unit.len() { - return fail("the unit holds members outside the specification"); - } - Ok(()) -} - -/// Read the declarations at `addresses` (with their units) into a fresh -/// anonymous environment. -fn ingress_records( - source: &ArtifactSource, - addresses: &[&Address], -) -> Result, String> { - let mut kenv = KEnv::::new(); - for address in addresses { - if !ingress_anon_addr_shallow_input( - &mut kenv, - Input::Artifact(source), - address, - )? { - return Err(format!("no carried record at {}", address.hex())); - } - } - Ok(kenv) -} - -/// Check the whole unit of each of `members` with only the groups in -/// `admitted`. -fn check_units( - source: &ArtifactSource, - admitted: &Admission, - kenv: &mut KEnv, - members: &[&Address], -) -> Result<(), String> { - check_units_with(source, admitted, &FxHashMap::default(), kenv, members) -} - -/// [`check_units`], with the group's own `candidates` also in the -/// primitive table but not admitted: the kernel recognises a quotient -/// record only at its table address. -fn check_units_with( - source: &ArtifactSource, - admitted: &Admission, - candidates: &FxHashMap, - kenv: &mut KEnv, - members: &[&Address], -) -> Result<(), String> { - let mut table = admitted.roles().clone(); - table - .extend(candidates.iter().map(|(role, address)| (*role, address.clone()))); - let roles: FxHashSet = admitted.roles().keys().copied().collect(); - let context = - PrimitiveContext::bound_with_table(source.clone(), &table, roles, |_| None); - let lookups = IxonIngressLookups::default(); - let mut checker = - TypeChecker::for_artifact_anon(kenv, source, &lookups, context); - for member in members { - if candidates.is_empty() && admitted.checked.contains(*member) { - continue; - } - if source.in_safety_tier(member) { - return Err(format!("{} is partial or unsafe", member.hex())); - } - checker - .check_ixon_const(&KId::new((*member).clone(), ())) - .map_err(|error| format!("{} does not check: {error}", member.hex()))?; - if candidates.is_empty() { - admitted.checked.insert((*member).clone()); - } - } - Ok(()) -} - -fn support_targets( - source: &ArtifactSource, - roots: &[Address], -) -> Result, String> { - let mut support: Vec<_> = - source.closure(roots).map_err(|e| e.to_string())?.into_iter().collect(); - support.sort_unstable(); - let mut targets = Vec::new(); - for entry in support { - if source.contains_record(&entry) { - if source.record_tag(&entry).map_err(|e| e.to_string())? - != ixon::lazy::ConstVariantTag::Muts - { - targets.push(entry); - } - } else { - source.blob(&entry).map_err(|e| e.to_string())?; - } - } - Ok(targets) -} - -/// Check every carried declaration in the closure of `roots`, including -/// helpers used only by a specified type, equation side, or proof. Inference -/// of a reference alone uses its declared type and does not validate its body. -fn check_support( - source: &ArtifactSource, - admitted: &Admission, - env: &mut KEnv, - roots: &[Address], -) -> Result<(), String> { - let targets = support_targets(source, roots)?; - let refs: Vec<&Address> = targets.iter().collect(); - check_units(source, admitted, env, &refs) -} - -// ----------------------------------------------------------------------- -// Expected declarations -// ----------------------------------------------------------------------- - -fn cnst(address: &Address, levels: &[U]) -> E { - KExpr::cnst(KId::new(address.clone(), ()), levels.to_vec().into()) -} -fn var(index: u64) -> E { - KExpr::var(index, ()) -} -fn app(f: E, a: E) -> E { - KExpr::app(f, a) -} -fn apps(f: E, args: Vec) -> E { - args.into_iter().fold(f, app) -} -fn pi(ty: E, body: E) -> E { - KExpr::all((), (), ty, body) -} -fn lam(ty: E, body: E) -> E { - KExpr::lam((), (), ty, body) -} -fn sort(level: U) -> E { - KExpr::sort(level) -} -fn param(index: u64) -> U { - KUniv::param(index, ()) -} -fn type1() -> E { - sort(KUniv::succ(KUniv::zero())) -} -fn prop() -> E { - sort(KUniv::zero()) -} - -/// The specified shape of an inductive type. -struct InductiveSpec { - lvls: u64, - params: u64, - indices: u64, - ty: E, - constructors: Vec
, -} - -/// The specified shape of a constructor. -struct ConstructorSpec { - lvls: u64, - cidx: u64, - params: u64, - fields: u64, - ty: E, -} - -/// The specified shape of a recursor, with each rule's field count and -/// right-hand side. -struct RecursorSpec { - lvls: u64, - params: u64, - indices: u64, - motives: u64, - minors: u64, - k: bool, - ty: E, - rules: Vec<(u64, E)>, -} - -fn declared( - kenv: &KEnv, - address: &Address, -) -> Result, String> { - kenv - .get(&KId::new(address.clone(), ())) - .ok_or_else(|| format!("no declaration at {}", address.hex())) -} - -fn differs(address: &Address, what: &str) -> String { - format!("{} differs from the specification: {what}", address.hex()) -} - -fn expect_safe_definition( - kenv: &KEnv, - address: &Address, - levels: u64, -) -> Result<(E, E), String> { - let KConst::Defn { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls, - ty, - val, - .. - } = declared(kenv, address)? - else { - return Err(differs(address, "not a safe definition")); - }; - if lvls != levels { - return Err(differs(address, "universe parameter count")); - } - Ok((ty, val)) -} - -fn expect_theorem_evidence( - kenv: &KEnv, - proof: &Address, - levels: u64, -) -> Result<(), String> { - let theorem = declared(kenv, proof)?; - if !matches!( - theorem, - KConst::Defn { - kind: DefKind::Theorem, - safety: DefinitionSafety::Safe, - lvls, - .. - } if lvls == levels - ) { - return Err(differs( - proof, - "not a safe theorem at the specified universes", - )); - } - Ok(()) -} - -fn load_checked_theorem( - source: &ArtifactSource, - admitted: &Admission, - checker: &mut TypeChecker<'_, Anon>, - proof: &Address, - levels: u64, -) -> Result<(E, E), String> { - for member in support_targets(source, std::slice::from_ref(proof))? { - if !admitted.checked.contains(&member) { - if source.in_safety_tier(&member) { - return Err(format!("{} is partial or unsafe", member.hex())); - } - checker - .check_ixon_const(&KId::new(member.clone(), ())) - .map_err(|error| error.to_string())?; - admitted.checked.insert(member); - } - } - let id = KId::new(proof.clone(), ()); - let theorem = checker.get_const(&id).map_err(|error| error.to_string())?; - let KConst::Defn { - kind: DefKind::Theorem, - safety: DefinitionSafety::Safe, - lvls, - ty, - val, - .. - } = &theorem - else { - return Err("is not a safe theorem".into()); - }; - if *lvls != levels { - return Err("has a different universe parameter count".into()); - } - checker - .validate_const_well_scoped(&theorem) - .map_err(|error| error.to_string())?; - checker - .check_no_unsafe_refs(val, DefinitionSafety::Safe) - .map_err(|error| error.to_string())?; - Ok((ty.clone(), val.clone())) -} - -fn check_proof_term( - checker: &mut TypeChecker<'_, Anon>, - proof_term: &E, - expected_type: &E, -) -> Result { - let actual = checker.infer(proof_term).map_err(|error| error.to_string())?; - checker.is_def_eq(&actual, expected_type).map_err(|error| error.to_string()) -} - -fn expect_inductive( - kenv: &KEnv, - address: &Address, - spec: &InductiveSpec, -) -> Result<(), String> { - let KConst::Indc { lvls, params, indices, is_unsafe, ty, ctors, .. } = - declared(kenv, address)? - else { - return Err(differs(address, "not an inductive")); - }; - let found: Vec<&Address> = ctors.iter().map(|id| &id.addr).collect(); - let wanted: Vec<&Address> = spec.constructors.iter().collect(); - if lvls != spec.lvls - || params != spec.params - || indices != spec.indices - || is_unsafe - { - return Err(differs(address, "arity or safety")); - } - if ty != spec.ty { - return Err(differs(address, "type")); - } - if found != wanted { - return Err(differs(address, "constructors")); - } - Ok(()) -} - -fn expect_constructor( - kenv: &KEnv, - address: &Address, - inductive: &Address, - spec: &ConstructorSpec, -) -> Result<(), String> { - let KConst::Ctor { - lvls, is_unsafe, induct, cidx, params, fields, ty, .. - } = declared(kenv, address)? - else { - return Err(differs(address, "not a constructor")); - }; - if lvls != spec.lvls - || is_unsafe - || &induct.addr != inductive - || cidx != spec.cidx - || params != spec.params - || fields != spec.fields - { - return Err(differs(address, "arity, owner or index")); - } - if ty != spec.ty { - return Err(differs(address, "type")); - } - Ok(()) -} - -fn expect_recursor( - kenv: &KEnv, - address: &Address, - spec: &RecursorSpec, -) -> Result<(), String> { - let KConst::Recr { - k, - is_unsafe, - lvls, - params, - indices, - motives, - minors, - ty, - rules, - .. - } = declared(kenv, address)? - else { - return Err(differs(address, "not a recursor")); - }; - if lvls != spec.lvls - || params != spec.params - || indices != spec.indices - || motives != spec.motives - || minors != spec.minors - || k != spec.k - || is_unsafe - { - return Err(differs(address, "arity, K-flag or safety")); - } - if ty != spec.ty { - return Err(differs(address, "type")); - } - if rules.len() != spec.rules.len() - || rules - .iter() - .zip(&spec.rules) - .any(|(rule, (fields, rhs))| rule.fields != *fields || &rule.rhs != rhs) - { - return Err(differs(address, "computation rules")); - } - Ok(()) -} - -// ----------------------------------------------------------------------- -// The Nat basis -// ----------------------------------------------------------------------- - -/// `Nat : Type` with `Nat.zero : Nat`, `Nat.succ : Nat → Nat` and -/// `Nat.rec.{u} {motive : Nat → Sort u} (zero : motive Nat.zero) -/// (succ : ∀ n, motive n → motive (Nat.succ n)) (t : Nat) : motive t`, whose -/// rules return `zero` and `succ n (Nat.rec motive zero succ n)`. -fn validate_nat( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - let [nat, zero, succ, rec] = - bound(source, [Role::Nat, Role::NatZero, Role::NatSucc, Role::NatRec])?; - locate_inductive_unit(source, &nat, &[&zero, &succ], &[&rec])?; - let mut kenv = ingress_records(source, &[&nat, &rec])?; - - let nat_t = || cnst(&nat, &[]); - expect_inductive( - &kenv, - &nat, - &InductiveSpec { - lvls: 0, - params: 0, - indices: 0, - ty: type1(), - constructors: vec![zero.clone(), succ.clone()], - }, - )?; - expect_constructor( - &kenv, - &zero, - &nat, - &ConstructorSpec { lvls: 0, cidx: 0, params: 0, fields: 0, ty: nat_t() }, - )?; - expect_constructor( - &kenv, - &succ, - &nat, - &ConstructorSpec { - lvls: 0, - cidx: 1, - params: 0, - fields: 1, - ty: pi(nat_t(), nat_t()), - }, - )?; - - let u = param(0); - // In the `motive` binder context. - let zero_minor = || app(var(0), cnst(&zero, &[])); - // In the `motive`, `zero` binder context. - let succ_minor = || { - pi( - nat_t(), - pi(app(var(2), var(0)), app(var(3), app(cnst(&succ, &[]), var(1)))), - ) - }; - let motive = || pi(nat_t(), sort(u.clone())); - let ty = pi( - motive(), - pi(zero_minor(), pi(succ_minor(), pi(nat_t(), app(var(3), var(0))))), - ); - let zero_rule = lam(motive(), lam(zero_minor(), lam(succ_minor(), var(1)))); - let succ_rule = lam( - motive(), - lam( - zero_minor(), - lam( - succ_minor(), - lam( - nat_t(), - app( - app(var(1), var(0)), - apps( - cnst(&rec, std::slice::from_ref(&u)), - vec![var(3), var(2), var(1), var(0)], - ), - ), - ), - ), - ), - ); - expect_recursor( - &kenv, - &rec, - &RecursorSpec { - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 2, - k: false, - ty, - rules: vec![(0, zero_rule), (1, succ_rule)], - }, - )?; - check_units(source, admitted, &mut kenv, &[&nat, &rec]) -} - -// ----------------------------------------------------------------------- -// The Eq, PUnit, Empty and False bases -// ----------------------------------------------------------------------- - -/// `Eq.{u} {α : Sort u} (a : α) : α → Prop` with -/// `Eq.refl.{u} {α : Sort u} (a : α) : Eq a a` and -/// `Eq.rec.{u_1, u} {α : Sort u} {a : α} -/// {motive : (b : α) → Eq a b → Sort u_1} (refl : motive a (Eq.refl a)) -/// {b : α} (t : Eq a b) : motive b t`, a K-like recursor whose rule returns -/// `refl`. -fn validate_eq( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - let [eq, refl, rec] = bound(source, [Role::Eq, Role::EqRefl, Role::EqRec])?; - locate_inductive_unit(source, &eq, &[&refl], &[&rec])?; - let mut kenv = ingress_records(source, &[&eq, &rec])?; - - // `Eq` and `Eq.refl` at their own level `u`. - let u = param(0); - expect_inductive( - &kenv, - &eq, - &InductiveSpec { - lvls: 1, - params: 2, - indices: 1, - ty: pi(sort(u.clone()), pi(var(0), pi(var(1), prop()))), - constructors: vec![refl.clone()], - }, - )?; - expect_constructor( - &kenv, - &refl, - &eq, - &ConstructorSpec { - lvls: 1, - cidx: 0, - params: 2, - fields: 0, - ty: pi( - sort(u.clone()), - pi(var(0), apps(cnst(&eq, &[u]), vec![var(1), var(0), var(0)])), - ), - }, - )?; - - // `Eq.rec.{u_1, u}`: `u_1` is level 0, `u` level 1. - let (u1, u) = (param(0), param(1)); - let eq_u = || cnst(&eq, std::slice::from_ref(&u)); - // In the `α`, `a` binder context. - let motive = || { - pi(var(1), pi(apps(eq_u(), vec![var(2), var(1), var(0)]), sort(u1.clone()))) - }; - // In the `α`, `a`, `motive` binder context. - let minor = || { - app( - app(var(0), var(1)), - apps(cnst(&refl, std::slice::from_ref(&u)), vec![var(2), var(1)]), - ) - }; - let ty = pi( - sort(u.clone()), - pi( - var(0), - pi( - motive(), - pi( - minor(), - pi( - var(3), - pi( - apps(eq_u(), vec![var(4), var(3), var(0)]), - app(app(var(3), var(1)), var(0)), - ), - ), - ), - ), - ), - ); - let rule = - lam(sort(u.clone()), lam(var(0), lam(motive(), lam(minor(), var(0))))); - expect_recursor( - &kenv, - &rec, - &RecursorSpec { - lvls: 2, - params: 2, - indices: 1, - motives: 1, - minors: 1, - k: true, - ty, - rules: vec![(0, rule)], - }, - )?; - check_units(source, admitted, &mut kenv, &[&eq, &rec]) -} - -/// `PUnit.{u} : Sort u` with `PUnit.unit.{u} : PUnit.{u}` and -/// `PUnit.rec.{u_1, u} {motive : PUnit.{u} → Sort u_1} -/// (unit : motive PUnit.unit) (t : PUnit.{u}) : motive t`, whose rule -/// returns `unit`. -fn validate_punit( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - let [punit, unit, rec] = - bound(source, [Role::PUnit, Role::PUnitUnit, Role::PUnitRec])?; - locate_inductive_unit(source, &punit, &[&unit], &[&rec])?; - let mut kenv = ingress_records(source, &[&punit, &rec])?; - - let u = param(0); - expect_inductive( - &kenv, - &punit, - &InductiveSpec { - lvls: 1, - params: 0, - indices: 0, - ty: sort(u.clone()), - constructors: vec![unit.clone()], - }, - )?; - expect_constructor( - &kenv, - &unit, - &punit, - &ConstructorSpec { - lvls: 1, - cidx: 0, - params: 0, - fields: 0, - ty: cnst(&punit, &[u]), - }, - )?; - - // `PUnit.rec.{u_1, u}`: `u_1` is level 0, `u` level 1. - let (u1, u) = (param(0), param(1)); - let punit_t = || cnst(&punit, std::slice::from_ref(&u)); - let motive = || pi(punit_t(), sort(u1.clone())); - let minor = || app(var(0), cnst(&unit, std::slice::from_ref(&u))); - let ty = pi(motive(), pi(minor(), pi(punit_t(), app(var(2), var(0))))); - let rule = lam(motive(), lam(minor(), var(0))); - expect_recursor( - &kenv, - &rec, - &RecursorSpec { - lvls: 2, - params: 0, - indices: 0, - motives: 1, - minors: 1, - k: false, - ty, - rules: vec![(0, rule)], - }, - )?; - check_units(source, admitted, &mut kenv, &[&punit, &rec]) -} - -/// An inductive with no constructors and no parameters in sort `sort` -/// (`Empty : Type`, `False : Prop`), with its recursor -/// `rec.{u} (motive : T → Sort u) (t : T) : motive t`, which has no rules. -fn validate_empty_like( - source: &ArtifactSource, - admitted: &Admission, - roles: [Role; 2], - sort_of_type: E, -) -> Result<(), String> { - let [ty_address, rec] = bound(source, roles)?; - locate_inductive_unit(source, &ty_address, &[], &[&rec])?; - let mut kenv = ingress_records(source, &[&ty_address, &rec])?; - expect_inductive( - &kenv, - &ty_address, - &InductiveSpec { - lvls: 0, - params: 0, - indices: 0, - ty: sort_of_type, - constructors: vec![], - }, - )?; - let t = || cnst(&ty_address, &[]); - expect_recursor( - &kenv, - &rec, - &RecursorSpec { - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 0, - k: false, - ty: pi(pi(t(), sort(param(0))), pi(t(), app(var(1), var(0)))), - rules: vec![], - }, - )?; - check_units(source, admitted, &mut kenv, &[&ty_address, &rec]) -} - -// ----------------------------------------------------------------------- -// The Quot basis -// ----------------------------------------------------------------------- - -/// The four quotient records (`Quot`, `Quot.mk`, `Quot.lift`, `Quot.ind`) -/// of the kinds and types Lean installs, and the axiom `Quot.sound`. -fn validate_quot( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - let [quot, mk, lift, ind, sound] = bound( - source, - [Role::Quot, Role::QuotMk, Role::QuotLift, Role::QuotInd, Role::QuotSound], - )?; - for (address, kind) in [ - (", QuotKind::Type), - (&mk, QuotKind::Ctor), - (&lift, QuotKind::Lift), - (&ind, QuotKind::Ind), - ] { - let member = source.member(address).map_err(|e| e.to_string())?; - if member.owner().is_some() - || !matches!(&member.record().constant().info, - ConstantInfo::Quot(record) if record.kind == kind) - { - return Err(differs(address, &format!("not a {kind:?} quotient record"))); - } - } - let member = source.member(&sound).map_err(|e| e.to_string())?; - if member.owner().is_some() - || !matches!(member.record().constant().info, ConstantInfo::Axio(_)) - { - return Err(differs(&sound, "not an axiom")); - } - let mut kenv = ingress_records(source, &[", &mk, &lift, &ind, &sound])?; - - let (u, v) = (param(0), param(1)); - let quot_u = || cnst(", std::slice::from_ref(&u)); - let mk_u = || cnst(&mk, std::slice::from_ref(&u)); - // `α → α → Prop`, in the `α` binder context. - let relation = || pi(var(0), pi(var(1), prop())); - let quot_ty = pi(sort(u.clone()), pi(relation(), sort(u.clone()))); - let mk_ty = pi( - sort(u.clone()), - pi(relation(), pi(var(1), apps(quot_u(), vec![var(2), var(1)]))), - ); - // `Quot.lift.{u, v} {α} {r} {β : Sort v} (f : α → β) - // (h : ∀ a b, r a b → f a = f b) (q : Quot r) : β`. - let lift_f = pi(var(2), var(1)); - let lift_h = pi( - var(3), - pi( - var(4), - pi( - apps(var(4), vec![var(1), var(0)]), - apps( - cnst(&admitted_eq(admitted)?, std::slice::from_ref(&v)), - vec![var(4), app(var(3), var(2)), app(var(3), var(1))], - ), - ), - ), - ); - let lift_ty = pi( - sort(u.clone()), - pi( - relation(), - pi( - sort(v.clone()), - pi( - lift_f, - pi(lift_h, pi(apps(quot_u(), vec![var(4), var(3)]), var(3))), - ), - ), - ), - ); - // `Quot.ind.{u} {α} {r} {β : Quot r → Prop} - // (mk : ∀ a, β (Quot.mk r a)) (q : Quot r) : β q`. - let ind_motive = pi(apps(quot_u(), vec![var(1), var(0)]), prop()); - let ind_minor = - pi(var(2), app(var(1), apps(mk_u(), vec![var(3), var(2), var(0)]))); - let ind_ty = pi( - sort(u.clone()), - pi( - relation(), - pi( - ind_motive, - pi( - ind_minor, - pi(apps(quot_u(), vec![var(3), var(2)]), app(var(2), var(0))), - ), - ), - ), - ); - // `Quot.sound.{u} {α} {r} {a b : α} : r a b → Quot.mk r a = Quot.mk r b`. - let sound_ty = pi( - sort(u.clone()), - pi( - relation(), - pi( - var(1), - pi( - var(2), - pi( - apps(var(2), vec![var(1), var(0)]), - apps( - cnst(&admitted_eq(admitted)?, std::slice::from_ref(&u)), - vec![ - apps(quot_u(), vec![var(4), var(3)]), - apps(mk_u(), vec![var(4), var(3), var(2)]), - apps(mk_u(), vec![var(4), var(3), var(1)]), - ], - ), - ), - ), - ), - ), - ); - for (address, kind, lvls, ty) in [ - (", QuotKind::Type, 1, quot_ty), - (&mk, QuotKind::Ctor, 1, mk_ty), - (&lift, QuotKind::Lift, 2, lift_ty), - (&ind, QuotKind::Ind, 1, ind_ty), - ] { - match declared(&kenv, address)? { - KConst::Quot { kind: found, lvls: found_lvls, ty: found_ty, .. } - if found == kind && found_lvls == lvls => - { - if found_ty != ty { - return Err(differs(address, "type")); - } - }, - _ => return Err(differs(address, "kind or level count")), - } - } - match declared(&kenv, &sound)? { - KConst::Axio { is_unsafe: false, lvls: 1, ty, .. } if ty == sound_ty => {}, - _ => return Err(differs(&sound, "safety, level count or type")), - } - - let candidates: FxHashMap = [ - (Role::Quot, quot.clone()), - (Role::QuotMk, mk.clone()), - (Role::QuotLift, lift.clone()), - (Role::QuotInd, ind.clone()), - (Role::QuotSound, sound.clone()), - ] - .into_iter() - .collect(); - check_units_with( - source, - admitted, - &candidates, - &mut kenv, - &[", &mk, &lift, &ind, &sound], - ) -} - -fn admitted_eq(admitted: &Admission) -> Result { - admitted.address(Role::Eq).cloned().ok_or_else(|| "Eq is not admitted".into()) -} - -// ----------------------------------------------------------------------- -// The literal-support anchors -// ----------------------------------------------------------------------- - -/// Whether a declaration is unsafe or partial. -fn is_unsafe(constant: &KConst) -> bool { - match constant { - KConst::Defn { safety, .. } => *safety != DefinitionSafety::Safe, - KConst::Recr { is_unsafe, .. } - | KConst::Axio { is_unsafe, .. } - | KConst::Indc { is_unsafe, .. } - | KConst::Ctor { is_unsafe, .. } => *is_unsafe, - KConst::Quot { .. } => false, - } -} - -/// Admit anchors `(role, level count, type)`, which are specified by their -/// types alone: each bound record authenticates, is not unsafe, has the -/// level count and exactly the type, and that type is a type with only the -/// groups admitted before. -fn validate_anchors( - source: &ArtifactSource, - admitted: &Admission, - anchors: &[(Role, u64, E)], -) -> Result<(), String> { - let mut addresses = Vec::with_capacity(anchors.len()); - for (role, _, _) in anchors { - let address = - source.required_role(*role).map_err(|error| error.to_string())?.clone(); - source.member(&address).map_err(|error| error.to_string())?; - addresses.push(address); - } - let references: Vec<&Address> = addresses.iter().collect(); - let mut kenv = ingress_records(source, &references)?; - for ((_, lvls, ty), address) in anchors.iter().zip(&addresses) { - let constant = declared(&kenv, address)?; - if is_unsafe(&constant) { - return Err(differs(address, "unsafe")); - } - if constant.lvls() != *lvls { - return Err(differs(address, "level count")); - } - if constant.ty() != ty { - return Err(differs(address, "type")); - } - } - let context = - PrimitiveContext::bound(source.clone(), admitted.roles(), |_| None); - let lookups = IxonIngressLookups::default(); - let mut checker = - TypeChecker::for_artifact_anon(&mut kenv, source, &lookups, context); - for ((_, _, ty), address) in anchors.iter().zip(&addresses) { - checker.reset(); - let sort = checker.infer(ty).map_err(|error| { - format!("{}: the specified type does not check: {error}", address.hex()) - })?; - checker.ensure_sort(&sort).map_err(|error| { - format!("{}: the specified type is not a type: {error}", address.hex()) - })?; - } - for member in support_targets(source, &addresses)? { - if source.in_safety_tier(&member) { - return Err(format!("{} is partial or unsafe", member.hex())); - } - if source.record_tag(&member).map_err(|e| e.to_string())? - == ixon::lazy::ConstVariantTag::Axio - && admitted.address(Role::Propext) != Some(&member) - && admitted.address(Role::Choice) != Some(&member) - { - return Err(format!( - "axiom {} is not an admitted standard axiom", - member.hex() - )); - } - } - Ok(()) -} - -/// `Char : Type`. -fn validate_char( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - validate_anchors(source, admitted, &[(Role::Char, 0, type1())]) -} - -/// `String : Type`. -fn validate_string( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - validate_anchors(source, admitted, &[(Role::String, 0, type1())]) -} - -/// `List.{u} : Type u → Type u`, `List.nil.{u} (α : Type u) : List α` and -/// `List.cons.{u} (α : Type u) (head : α) (tail : List α) : List α`. -fn validate_list( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - let list = source - .required_role(Role::List) - .map_err(|error| error.to_string())? - .clone(); - let type_u = || sort(KUniv::succ(param(0))); - let list_u = || cnst(&list, &[param(0)]); - validate_anchors( - source, - admitted, - &[ - (Role::List, 1, pi(type_u(), type_u())), - (Role::ListNil, 1, pi(type_u(), app(list_u(), var(0)))), - ( - Role::ListCons, - 1, - pi( - type_u(), - pi(var(0), pi(app(list_u(), var(1)), app(list_u(), var(2)))), - ), - ), - ], - ) -} - -/// `Char.ofNat : Nat → Char`. -fn validate_char_of_nat( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - let nat = admitted.address(Role::Nat).ok_or("Nat is not admitted")?; - let char_t = admitted.address(Role::Char).ok_or("Char is not admitted")?; - validate_anchors( - source, - admitted, - &[(Role::CharOfNat, 0, pi(cnst(nat, &[]), cnst(char_t, &[])))], - ) -} - -/// `String.ofList : List.{0} Char → String`. -fn validate_string_of_list( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - let list = admitted.address(Role::List).ok_or("List is not admitted")?; - let char_t = admitted.address(Role::Char).ok_or("Char is not admitted")?; - let string = - admitted.address(Role::String).ok_or("String is not admitted")?; - validate_anchors( - source, - admitted, - &[( - Role::StringOfList, - 0, - pi( - app(cnst(list, &[KUniv::zero()]), cnst(char_t, &[])), - cnst(string, &[]), - ), - )], - ) -} - -// ----------------------------------------------------------------------- -// Bool and the structural Nat operations -// ----------------------------------------------------------------------- - -/// `Bool : Type`, `Bool.false Bool.true : Bool` and -/// `Bool.rec.{u} {motive : Bool → Sort u} (false : motive false) -/// (true : motive true) (t : Bool) : motive t`, anchors specified by type. -fn validate_bool( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - let [bool_t, false_c, true_c] = - bound(source, [Role::Bool, Role::BoolFalse, Role::BoolTrue])?; - let bool_c = || cnst(&bool_t, &[]); - let rec_ty = pi( - pi(bool_c(), sort(param(0))), - pi( - app(var(0), cnst(&false_c, &[])), - pi(app(var(1), cnst(&true_c, &[])), pi(bool_c(), app(var(3), var(0)))), - ), - ); - validate_anchors( - source, - admitted, - &[ - (Role::Bool, 0, type1()), - (Role::BoolFalse, 0, bool_c()), - (Role::BoolTrue, 0, bool_c()), - (Role::BoolRec, 1, rec_ty), - ], - ) -} - -fn admitted_role(admitted: &Admission, role: Role) -> Result { - admitted - .address(role) - .map(|address| cnst(address, &[])) - .ok_or_else(|| format!("{} is not admitted", role.lean_name())) -} - -/// The type and recurrence equations of a structural `Nat` operation, the -/// certified checker's: each equation over `x` (`#1`) and `y` (`#0`), with -/// the operation itself as `op`. -fn structural_spec( - role: Role, - admitted: &Admission, - op: &E, -) -> Result<(E, Vec<(E, E)>), String> { - let nat = || admitted_role(admitted, Role::Nat); - let zero = admitted_role(admitted, Role::NatZero)?; - let succ = admitted_role(admitted, Role::NatSucc)?; - let (x, y) = (var(1), var(0)); - let s = |e: E| app(succ.clone(), e); - let op2 = |a: E, b: E| apps(op.clone(), vec![a, b]); - let binary = - |cod: E| -> Result { Ok(pi(nat()?, pi(nat()?, cod))) }; - Ok(match role { - Role::NatPred => ( - pi(nat()?, nat()?), - vec![ - (app(op.clone(), zero.clone()), zero), - (app(op.clone(), s(x.clone())), x), - ], - ), - Role::NatAdd => ( - binary(nat()?)?, - vec![ - (op2(x.clone(), zero), x.clone()), - (op2(x.clone(), s(y.clone())), s(op2(x, y))), - ], - ), - Role::NatSub => { - let pred = admitted_role(admitted, Role::NatPred)?; - ( - binary(nat()?)?, - vec![ - (op2(x.clone(), zero), x.clone()), - (op2(x.clone(), s(y.clone())), app(pred, op2(x, y))), - ], - ) - }, - Role::NatMul => { - let add = admitted_role(admitted, Role::NatAdd)?; - ( - binary(nat()?)?, - vec![ - (op2(x.clone(), zero.clone()), zero), - (op2(x.clone(), s(y.clone())), apps(add, vec![op2(x.clone(), y), x])), - ], - ) - }, - Role::NatPow => { - let mul = admitted_role(admitted, Role::NatMul)?; - ( - binary(nat()?)?, - vec![ - (op2(x.clone(), zero.clone()), s(zero)), - (op2(x.clone(), s(y.clone())), apps(mul, vec![op2(x.clone(), y), x])), - ], - ) - }, - Role::NatBeq => { - let bool_t = admitted_role(admitted, Role::Bool)?; - let true_c = admitted_role(admitted, Role::BoolTrue)?; - let false_c = admitted_role(admitted, Role::BoolFalse)?; - ( - binary(bool_t)?, - vec![ - (op2(zero.clone(), zero.clone()), true_c), - (op2(zero.clone(), s(y.clone())), false_c.clone()), - (op2(s(x.clone()), zero), false_c), - (op2(s(x.clone()), s(y.clone())), op2(x, y)), - ], - ) - }, - Role::NatBle => { - let bool_t = admitted_role(admitted, Role::Bool)?; - let true_c = admitted_role(admitted, Role::BoolTrue)?; - let false_c = admitted_role(admitted, Role::BoolFalse)?; - ( - binary(bool_t)?, - vec![ - (op2(zero.clone(), y.clone()), true_c), - (op2(s(x.clone()), zero), false_c), - (op2(s(x.clone()), s(y.clone())), op2(x, y)), - ], - ) - }, - _ => return Err(format!("{role:?} is not a structural operation")), - }) -} - -/// A structural `Nat` operation: a safe, level-monomorphic definition of -/// the specified type that checks, whose role evidence is one definitional -/// entry per recurrence equation, and whose equations hold by definitional -/// equality with its own references replaced by its value, checked with -/// only the groups admitted before (so its own fast path cannot discharge -/// them). -fn validate_structural_op( - source: &ArtifactSource, - admitted: &Admission, - role: Role, -) -> Result<(), String> { - let address = - source.required_role(role).map_err(|error| error.to_string())?.clone(); - source.member(&address).map_err(|error| error.to_string())?; - let mut kenv = ingress_records(source, &[&address])?; - let (ty, val) = expect_safe_definition(&kenv, &address, 0)?; - let (spec_ty, equations) = structural_spec(role, admitted, &val)?; - if ty != spec_ty { - return Err(differs(&address, "type")); - } - let evidence = - source.binding().and_then(|binding| binding.evidence(role)).unwrap_or(&[]); - if evidence.len() != equations.len() - || evidence.iter().any(|entry| !matches!(entry, Evidence::DefEq)) - { - return Err(format!( - "{}: the evidence is not one definitional entry per equation", - address.hex() - )); - } - check_support(source, admitted, &mut kenv, std::slice::from_ref(&address))?; - - let nat = admitted_role(admitted, Role::Nat)?; - let context = - PrimitiveContext::bound(source.clone(), admitted.roles(), |_| None); - let lookups = IxonIngressLookups::default(); - let mut checker = - TypeChecker::for_artifact_anon(&mut kenv, source, &lookups, context); - for (index, (lhs, rhs)) in equations.into_iter().enumerate() { - let close = |e: E| lam(nat.clone(), lam(nat.clone(), e)); - let (lhs, rhs) = (close(lhs), close(rhs)); - let fail = |error: crate::error::TcError| { - format!("{}: equation {index} does not check: {error}", address.hex()) - }; - checker.reset(); - let lhs_ty = checker.infer(&lhs).map_err(&fail)?; - let rhs_ty = checker.infer(&rhs).map_err(&fail)?; - let holds = checker.is_def_eq(&lhs_ty, &rhs_ty).map_err(&fail)? - && checker.is_def_eq(&lhs, &rhs).map_err(&fail)?; - if !holds { - return Err(format!("{}: equation {index} does not hold", address.hex())); - } - } - Ok(()) -} - -// ----------------------------------------------------------------------- -// The well-founded Nat operations -// ----------------------------------------------------------------------- - -/// The admitted constants a certificate statement mentions. -struct Vocabulary { - nat: E, - zero: E, - succ: E, - boolean: E, - true_c: E, - false_c: E, - eq: E, - ble: E, - sub: Option, - add: Option, - mul: Option, - div: Option, - modulo: Option, -} - -impl Vocabulary { - fn of(admitted: &Admission) -> Result { - let optional = |role| admitted_role(admitted, role).ok(); - Ok(Self { - nat: admitted_role(admitted, Role::Nat)?, - zero: admitted_role(admitted, Role::NatZero)?, - succ: admitted_role(admitted, Role::NatSucc)?, - boolean: admitted_role(admitted, Role::Bool)?, - true_c: admitted_role(admitted, Role::BoolTrue)?, - false_c: admitted_role(admitted, Role::BoolFalse)?, - eq: cnst(&admitted_eq(admitted)?, &[KUniv::succ(KUniv::zero())]), - ble: admitted_role(admitted, Role::NatBle)?, - sub: optional(Role::NatSub), - add: optional(Role::NatAdd), - mul: optional(Role::NatMul), - div: optional(Role::NatDiv), - modulo: optional(Role::NatMod), - }) - } -} - -fn needed(constant: &Option, name: &str) -> Result { - constant.clone().ok_or_else(|| format!("{name} is not admitted")) -} - -/// The certificate statements of a well-founded `Nat` operation, the -/// certified checker's: per certificate its hypotheses and its equation, -/// over `x` and `y`, with the operation itself as `op`. Guards are spelled -/// with `Nat.ble` and `1` as `Nat.succ Nat.zero`. -fn wf_statements( - role: Role, - v: &Vocabulary, - op: &E, - x: &E, - y: &E, -) -> Result, E)>, String> { - let s = |e: E| app(v.succ.clone(), e); - let one = s(v.zero.clone()); - let two = s(one.clone()); - let bin = |f: &E, a: E, b: E| apps(f.clone(), vec![a, b]); - let ble = |a: E, b: E| bin(&v.ble, a, b); - let eq_bool = |a: E, b: E| apps(v.eq.clone(), vec![v.boolean.clone(), a, b]); - let eq_nat = |a: E, b: E| apps(v.eq.clone(), vec![v.nat.clone(), a, b]); - let holds = |guard: E| eq_bool(guard, v.true_c.clone()); - let fails = |guard: E| eq_bool(guard, v.false_c.clone()); - let lhs = bin(op, x.clone(), y.clone()); - let one_le = |e: &E| ble(one.clone(), e.clone()); - Ok(match role { - Role::NatDiv | Role::NatMod => { - let sub = needed(&v.sub, "Nat.sub")?; - let rest = bin(op, bin(&sub, x.clone(), y.clone()), y.clone()); - let (recursive, base) = if role == Role::NatDiv { - (s(rest), v.zero.clone()) - } else { - (rest, x.clone()) - }; - vec![ - ( - vec![holds(ble(y.clone(), x.clone())), holds(one_le(y))], - eq_nat(lhs.clone(), recursive), - ), - ( - vec![fails(ble(y.clone(), x.clone()))], - eq_nat(lhs.clone(), base.clone()), - ), - (vec![fails(one_le(y))], eq_nat(lhs, base)), - ] - }, - Role::NatGcd => { - let modulo = needed(&v.modulo, "Nat.mod")?; - vec![ - ( - vec![holds(one_le(x))], - eq_nat( - lhs.clone(), - bin(op, bin(&modulo, y.clone(), x.clone()), x.clone()), - ), - ), - (vec![fails(one_le(x))], eq_nat(lhs, y.clone())), - ] - }, - Role::NatShiftLeft => { - let (sub, mul) = (needed(&v.sub, "Nat.sub")?, needed(&v.mul, "Nat.mul")?); - vec![ - ( - vec![holds(one_le(y))], - eq_nat( - lhs.clone(), - bin( - op, - bin(&mul, two, x.clone()), - bin(&sub, y.clone(), one.clone()), - ), - ), - ), - (vec![fails(one_le(y))], eq_nat(lhs, x.clone())), - ] - }, - Role::NatShiftRight => { - let (sub, div) = (needed(&v.sub, "Nat.sub")?, needed(&v.div, "Nat.div")?); - vec![ - ( - vec![holds(one_le(y))], - eq_nat( - lhs.clone(), - bin( - &div, - bin(op, x.clone(), bin(&sub, y.clone(), one.clone())), - two, - ), - ), - ), - (vec![fails(one_le(y))], eq_nat(lhs, x.clone())), - ] - }, - Role::NatLand | Role::NatLor | Role::NatXor => { - let add = needed(&v.add, "Nat.add")?; - let mul = needed(&v.mul, "Nat.mul")?; - let div = needed(&v.div, "Nat.div")?; - let modulo = needed(&v.modulo, "Nat.mod")?; - let halves = bin( - op, - bin(&div, x.clone(), two.clone()), - bin(&div, y.clone(), two.clone()), - ); - let x_bit = bin(&modulo, x.clone(), two.clone()); - let y_bit = bin(&modulo, y.clone(), two.clone()); - let low = match role { - Role::NatLand => bin(&mul, x_bit, y_bit), - Role::NatLor => { - let sub = needed(&v.sub, "Nat.sub")?; - bin( - &sub, - bin(&add, x_bit.clone(), y_bit.clone()), - bin(&mul, x_bit, y_bit), - ) - }, - _ => bin(&modulo, bin(&add, x_bit, y_bit), two.clone()), - }; - let base = if role == Role::NatLand { v.zero.clone() } else { y.clone() }; - vec![ - ( - vec![holds(one_le(x))], - eq_nat(lhs.clone(), bin(&add, bin(&mul, two, halves), low)), - ), - (vec![fails(one_le(x))], eq_nat(lhs, base)), - ] - }, - _ => return Err(format!("{role:?} is not a well-founded operation")), - }) -} - -/// Replace every occurrence of the level-monomorphic constant `target` in -/// `e` by the closed term `value`, under binders too (sharing preserved). -fn replace_constant( - e: &E, - target: &Address, - value: &E, - memo: &mut FxHashMap, -) -> E { - if let Some(done) = memo.get(&e.hash_key()) { - return done.clone(); - } - let mut go = |e: &E| replace_constant(e, target, value, memo); - let out = match e.data() { - crate::expr::ExprData::Const(id, levels, _) - if &id.addr == target && levels.is_empty() => - { - value.clone() - }, - crate::expr::ExprData::App(f, a, _) => KExpr::app(go(f), go(a)), - // Anonymous mode: names and binder infos are `()`. - crate::expr::ExprData::Lam((), (), contract, ty, body, _) => { - KExpr::lam_contract((), (), *contract, go(ty), go(body)) - }, - crate::expr::ExprData::All((), (), contract, result, ty, body, _) => { - KExpr::all_contract((), (), *contract, *result, go(ty), go(body)) - }, - crate::expr::ExprData::Let((), ty, val, body, contract, _) => { - KExpr::let_contract((), *contract, go(ty), go(val), go(body)) - }, - crate::expr::ExprData::Prj(id, field, val, _) => { - KExpr::prj(id.clone(), *field, go(val)) - }, - _ => e.clone(), - }; - memo.insert(e.hash_key(), out.clone()); - out -} - -/// The records reachable from `roots` through carried references: a -/// projection reaches its unit, a unit everything its members mention. A -/// unit is the granularity at which records are carried and checked. -fn reference_closure( - source: &ArtifactSource, - roots: Vec
, -) -> FxHashSet
{ - let mut seen = FxHashSet::default(); - let mut todo = roots; - while let Some(address) = todo.pop() { - if !seen.insert(address.clone()) { - continue; - } - // Literal blobs are references but not records. - let Ok(record) = source.record(&address) else { continue }; - let constant = record.constant(); - if let Some(block) = projection_unit(&constant.info) { - todo.push(block.clone()); - } - todo.extend(constant.refs.iter().cloned()); - } - seen -} - -fn projection_unit(info: &ConstantInfo) -> Option<&Address> { - match info { - ConstantInfo::IPrj(p) => Some(&p.block), - ConstantInfo::CPrj(p) => Some(&p.block), - ConstantInfo::RPrj(p) => Some(&p.block), - ConstantInfo::DPrj(p) => Some(&p.block), - _ => None, - } -} - -/// Evidence may mention only the operation and the reference closure of its -/// definition and dependency groups. The theorem and its body are checked -/// separately by the operation's validator. -fn check_evidence_locality( - source: &ArtifactSource, - admitted: &Admission, - role: Role, - address: &Address, - evidence: &[Address], -) -> Result<(), String> { - let mut roots: Vec
= vec![address.clone()]; - for dependency in super::dependencies(role) { - for member in super::block(*dependency) { - roots.extend(admitted.address(*member).cloned()); - } - } - let closure = reference_closure(source, roots); - let local = |reference: &Address| { - closure.contains(reference) - || source.record(reference).ok().is_some_and(|r| { - projection_unit(&r.constant().info).is_some_and(|b| closure.contains(b)) - }) - }; - for proof in evidence { - let proof_record = - source.proof_record(proof).map_err(|error| error.to_string())?; - for reference in &proof_record.record().constant().refs { - if reference != address - && source.record(reference).is_ok() - && !local(reference) - { - return Err(format!( - "{}: evidence {} mentions {} outside the operation's closure", - address.hex(), - proof.hex(), - reference.hex() - )); - } - } - } - Ok(()) -} - -/// A well-founded `Nat` operation: a safe, level-monomorphic definition of -/// type `Nat → Nat → Nat` that checks, whose role evidence is one proof per -/// certificate statement. Each local proof, with the operation's references -/// replaced by its value, must prove its equation over the statement's -/// variables and hypotheses, with only the groups admitted before. -fn validate_wf_op( - source: &ArtifactSource, - admitted: &Admission, - role: Role, -) -> Result<(), String> { - let address = - source.required_role(role).map_err(|error| error.to_string())?.clone(); - source.member(&address).map_err(|error| error.to_string())?; - let evidence: Vec
= source - .binding() - .and_then(|binding| binding.evidence(role)) - .unwrap_or(&[]) - .iter() - .map(|entry| match entry { - Evidence::Proof(proof) => Ok(proof.clone()), - Evidence::DefEq => Err(format!( - "{}: definitional evidence for a well-founded operation", - address.hex() - )), - }) - .collect::>()?; - let mut references: Vec<&Address> = vec![&address]; - references.extend(evidence.iter()); - let mut kenv = ingress_records(source, &references)?; - let (ty, val) = expect_safe_definition(&kenv, &address, 0)?; - let vocabulary = Vocabulary::of(admitted)?; - let nat = vocabulary.nat.clone(); - if ty != pi(nat.clone(), pi(nat.clone(), nat.clone())) { - return Err(differs(&address, "type")); - } - let statement_count = - wf_statements(role, &vocabulary, &val, &var(1), &var(0))?.len(); - if evidence.len() != statement_count { - return Err(format!( - "{}: the evidence is not one proof per certificate statement", - address.hex() - )); - } - check_support(source, admitted, &mut kenv, std::slice::from_ref(&address))?; - - // Locality: the closure of the operation (its unit's references) and of - // the roles, with their blocks, that its specification depends on. - - check_evidence_locality(source, admitted, role, &address, &evidence)?; - - let context = - PrimitiveContext::bound(source.clone(), admitted.roles(), |_| None); - let lookups = IxonIngressLookups::default(); - let mut checker = - TypeChecker::for_artifact_anon(&mut kenv, source, &lookups, context); - for (index, proof) in evidence.iter().enumerate() { - let fail = |what: String| { - format!("{}: certificate {index} ({}) {what}", address.hex(), proof.hex()) - }; - let (_, value) = - load_checked_theorem(source, admitted, &mut checker, proof, 0) - .map_err(&fail)?; - let proof_term = - replace_constant(&value, &address, &val, &mut FxHashMap::default()); - - // λ (x y : Nat) (h₁ … hₙ), proof x y h₁ … hₙ, against - // Π (x y : Nat) (h₁ … hₙ), equation. - let (hypotheses, _) = - &wf_statements(role, &vocabulary, &val, &var(1), &var(0))?[index]; - let count = u64::try_from(hypotheses.len()).map_err(|e| e.to_string())?; - let at_depth = |depth: u64| { - wf_statements(role, &vocabulary, &val, &var(depth + 1), &var(depth)) - .map(|statements| statements[index].clone()) - }; - let mut arguments = vec![var(count + 1), var(count)]; - arguments.extend((0..count).rev().map(var)); - let mut lambda = apps(proof_term, arguments); - let (_, mut pi_type) = at_depth(count)?; - for i in (0..count).rev() { - let (hypotheses, _) = at_depth(i)?; - let hypothesis = - hypotheses[usize::try_from(i).map_err(|e| e.to_string())?].clone(); - lambda = lam(hypothesis.clone(), lambda); - pi_type = pi(hypothesis, pi_type); - } - for _ in 0..2 { - lambda = lam(nat.clone(), lambda); - pi_type = pi(nat.clone(), pi_type); - } - checker.reset(); - let proves = - check_proof_term(&mut checker, &lambda, &pi_type).map_err(&fail)?; - if !proves { - return Err(fail("does not prove its statement".into())); - } - } - Ok(()) -} - -// ----------------------------------------------------------------------- -// The standard axioms and their families -// ----------------------------------------------------------------------- - -/// `True : Prop` and `True.intro : True`. -fn validate_true( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - let true_t = source - .required_role(Role::True) - .map_err(|error| error.to_string())? - .clone(); - validate_anchors( - source, - admitted, - &[(Role::True, 0, prop()), (Role::TrueIntro, 0, cnst(&true_t, &[]))], - ) -} - -/// `And (a b : Prop) : Prop`, `And.intro {a b : Prop} (left : a) -/// (right : b) : And a b` and `And.rec.{u} {a b : Prop} -/// {motive : And a b → Sort u} (intro : (left : a) → (right : b) → -/// motive (And.intro left right)) (t : And a b) : motive t`. -fn validate_and( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - let [and, intro] = bound(source, [Role::And, Role::AndIntro])?; - let and2 = |a: E, b: E| apps(cnst(&and, &[]), vec![a, b]); - let intro_ty = - pi(prop(), pi(prop(), pi(var(1), pi(var(1), and2(var(3), var(2)))))); - let rec_ty = pi( - prop(), - pi( - prop(), - pi( - pi(and2(var(1), var(0)), sort(param(0))), - pi( - pi( - var(2), - pi( - var(2), - app( - var(2), - apps(cnst(&intro, &[]), vec![var(4), var(3), var(1), var(0)]), - ), - ), - ), - pi(and2(var(3), var(2)), app(var(2), var(0))), - ), - ), - ), - ); - validate_anchors( - source, - admitted, - &[ - (Role::And, 0, pi(prop(), pi(prop(), prop()))), - (Role::AndIntro, 0, intro_ty), - (Role::AndRec, 1, rec_ty), - ], - ) -} - -/// `Iff (a b : Prop) : Prop` with `Iff.intro {a b : Prop} (mp : a → b) -/// (mpr : b → a) : Iff a b` and `Iff.rec.{u} {a b : Prop} -/// {motive : Iff a b → Sort u} (intro : ∀ mp mpr, motive (Iff.intro mp mpr)) -/// (t : Iff a b) : motive t`, whose rule returns `intro mp mpr`. A block, -/// not an anchor: `propext` is true only of this inductive. -fn validate_iff( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - let [iff, intro, rec] = - bound(source, [Role::Iff, Role::IffIntro, Role::IffRec])?; - locate_inductive_unit(source, &iff, &[&intro], &[&rec])?; - let mut kenv = ingress_records(source, &[&iff, &rec])?; - - let iff2 = |a: E, b: E| apps(cnst(&iff, &[]), vec![a, b]); - expect_inductive( - &kenv, - &iff, - &InductiveSpec { - lvls: 0, - params: 2, - indices: 0, - ty: pi(prop(), pi(prop(), prop())), - constructors: vec![intro.clone()], - }, - )?; - expect_constructor( - &kenv, - &intro, - &iff, - &ConstructorSpec { - lvls: 0, - cidx: 0, - params: 2, - fields: 2, - ty: pi( - prop(), - pi( - prop(), - pi(pi(var(1), var(1)), pi(pi(var(1), var(3)), iff2(var(3), var(2)))), - ), - ), - }, - )?; - - // In the `a`, `b` binder context. - let motive = || pi(iff2(var(1), var(0)), sort(param(0))); - // In the `a`, `b`, `motive` binder context. - let minor = || { - pi( - pi(var(2), var(2)), - pi( - pi(var(2), var(4)), - app( - var(2), - apps(cnst(&intro, &[]), vec![var(4), var(3), var(1), var(0)]), - ), - ), - ) - }; - let ty = pi( - prop(), - pi( - prop(), - pi(motive(), pi(minor(), pi(iff2(var(3), var(2)), app(var(2), var(0))))), - ), - ); - let rule = lam( - prop(), - lam( - prop(), - lam( - motive(), - lam( - minor(), - lam( - pi(var(3), var(3)), - lam(pi(var(3), var(5)), app(app(var(2), var(1)), var(0))), - ), - ), - ), - ), - ); - expect_recursor( - &kenv, - &rec, - &RecursorSpec { - lvls: 1, - params: 2, - indices: 0, - motives: 1, - minors: 1, - k: false, - ty, - rules: vec![(2, rule)], - }, - )?; - check_units(source, admitted, &mut kenv, &[&iff, &rec]) -} - -/// `Nonempty.{u} (α : Sort u) : Prop` with `Nonempty.intro.{u} {α : Sort u} -/// (val : α) : Nonempty α` and `Nonempty.rec.{u} {α : Sort u} -/// {motive : Nonempty α → Prop} (intro : ∀ val, motive (Nonempty.intro val)) -/// (t : Nonempty α) : motive t`, whose rule returns `intro val`. A block, not -/// an anchor: `Classical.choice` is true only of this inductive. -fn validate_nonempty( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - let [nonempty, intro, rec] = - bound(source, [Role::Nonempty, Role::NonemptyIntro, Role::NonemptyRec])?; - locate_inductive_unit(source, &nonempty, &[&intro], &[&rec])?; - let mut kenv = ingress_records(source, &[&nonempty, &rec])?; - - let u = param(0); - let nonempty_u = |e: E| app(cnst(&nonempty, std::slice::from_ref(&u)), e); - expect_inductive( - &kenv, - &nonempty, - &InductiveSpec { - lvls: 1, - params: 1, - indices: 0, - ty: pi(sort(u.clone()), prop()), - constructors: vec![intro.clone()], - }, - )?; - expect_constructor( - &kenv, - &intro, - &nonempty, - &ConstructorSpec { - lvls: 1, - cidx: 0, - params: 1, - fields: 1, - ty: pi(sort(u.clone()), pi(var(0), nonempty_u(var(1)))), - }, - )?; - - // In the `α` binder context. - let motive = || pi(nonempty_u(var(0)), prop()); - // In the `α`, `motive` binder context. - let minor = || { - pi( - var(1), - app( - var(1), - apps(cnst(&intro, std::slice::from_ref(&u)), vec![var(2), var(0)]), - ), - ) - }; - let ty = pi( - sort(u.clone()), - pi(motive(), pi(minor(), pi(nonempty_u(var(2)), app(var(2), var(0))))), - ); - let rule = lam( - sort(u.clone()), - lam(motive(), lam(minor(), lam(var(2), app(var(1), var(0))))), - ); - expect_recursor( - &kenv, - &rec, - &RecursorSpec { - lvls: 1, - params: 1, - indices: 0, - motives: 1, - minors: 1, - k: false, - ty, - rules: vec![(1, rule)], - }, - )?; - check_units(source, admitted, &mut kenv, &[&nonempty, &rec]) -} - -/// An axiom of the specified level count and type, checked as a declaration -/// with only the groups admitted before (and itself as an axiom the policy -/// allows). -fn validate_axiom( - source: &ArtifactSource, - admitted: &Admission, - role: Role, - lvls: u64, - ty: &E, -) -> Result<(), String> { - let address = - source.required_role(role).map_err(|error| error.to_string())?.clone(); - let member = source.member(&address).map_err(|error| error.to_string())?; - if member.owner().is_some() - || !matches!(member.record().constant().info, ConstantInfo::Axio(_)) - { - return Err(differs(&address, "not an axiom")); - } - let mut kenv = ingress_records(source, &[&address])?; - match declared(&kenv, &address)? { - KConst::Axio { is_unsafe: false, lvls: found, ty: found_ty, .. } - if found == lvls => - { - if &found_ty != ty { - return Err(differs(&address, "type")); - } - }, - _ => return Err(differs(&address, "safety or level count")), - } - let candidates: FxHashMap = - std::iter::once((role, address.clone())).collect(); - check_units_with(source, admitted, &candidates, &mut kenv, &[&address]) -} - -/// `propext {a b : Prop} : Iff a b → a = b`. -fn validate_propext( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - let iff = admitted.address(Role::Iff).ok_or("Iff is not admitted")?; - let eq = admitted_eq(admitted)?; - let ty = pi( - prop(), - pi( - prop(), - pi( - apps(cnst(iff, &[]), vec![var(1), var(0)]), - apps( - cnst(&eq, &[KUniv::succ(KUniv::zero())]), - vec![prop(), var(2), var(1)], - ), - ), - ), - ); - validate_axiom(source, admitted, Role::Propext, 0, &ty) -} - -/// `Classical.choice.{u} {α : Sort u} : Nonempty α → α`. -fn validate_choice( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - let nonempty = - admitted.address(Role::Nonempty).ok_or("Nonempty is not admitted")?; - let u = param(0); - let ty = pi( - sort(u.clone()), - pi(app(cnst(nonempty, std::slice::from_ref(&u)), var(0)), var(1)), - ); - validate_axiom(source, admitted, Role::Choice, 1, &ty) -} - -/// `sorryAx.{u} (α : Sort u) (synthetic : Bool) : α`: an inert axiom, -/// tolerated as a declaration and refused at every use. Its type fixes which -/// declaration is tolerated. -fn validate_sorry( - source: &ArtifactSource, - admitted: &Admission, -) -> Result<(), String> { - let bool_t = admitted.address(Role::Bool).ok_or("Bool is not admitted")?; - let ty = pi(sort(param(0)), pi(cnst(bool_t, &[]), var(1))); - validate_axiom(source, admitted, Role::SorryAx, 1, &ty) -} - -#[cfg(test)] -mod tests { - use super::{Role, SPECIALIZED_GROUPS, specs}; - use rustc_hash::FxHashSet; - - #[test] - fn the_groups_are_the_specification_blocks_in_dependency_order() { - let mut seen: FxHashSet = FxHashSet::default(); - for group in specs::GROUPS { - let lead = group.roles[0]; - assert_eq!(group.roles, super::super::block(lead), "{lead:?}"); - let needs: FxHashSet = group.needs.iter().copied().collect(); - let specified: FxHashSet = group - .roles - .iter() - .flat_map(|role| super::super::dependencies(*role)) - .filter(|role| !group.roles.contains(role)) - .copied() - .collect(); - assert_eq!(needs, specified, "{lead:?}"); - assert!(needs.iter().all(|need| seen.contains(need)), "{lead:?}"); - for role in group.roles { - assert!(seen.insert(*role), "{role:?} is in two groups"); - } - } - assert_eq!(seen.len(), Role::ALL.len()); - for specialized in SPECIALIZED_GROUPS { - let generated = specs::GROUPS - .iter() - .find(|group| group.roles == specialized.roles) - .unwrap(); - assert_eq!(specialized.needs, generated.needs); - } - } -} diff --git a/crates/kernel/src/role_admission/specs.rs b/crates/kernel/src/role_admission/specs.rs deleted file mode 100644 index df0f4d82d..000000000 --- a/crates/kernel/src/role_admission/specs.rs +++ /dev/null @@ -1,145 +0,0 @@ -// Generated from IxC's Role.spec by Ix/RoleBinding/RustSpecGen.lean. -// Run scripts/RustRoleSpecs.lean to regenerate or check this file. -// These terms describe candidates; only checked admission enables reductions. - -use super::equations::{Equation, SpecGroup, Specification, reference}; -use super::*; - -// Specification version 3; digest 6d02153ea311f3efd1778c49b2ee5f31e79a0bae95e533a08d4914fc05572d33. - -#[rustfmt::skip] -pub(super) const GROUPS: &[SpecGroup] = &[ - SpecGroup { roles: &[Role::ALL[0], Role::ALL[1], Role::ALL[2], Role::ALL[3]], needs: &[] }, - SpecGroup { roles: &[Role::ALL[8], Role::ALL[9], Role::ALL[10]], needs: &[] }, - SpecGroup { roles: &[Role::ALL[11], Role::ALL[12], Role::ALL[13]], needs: &[] }, - SpecGroup { roles: &[Role::ALL[14], Role::ALL[15]], needs: &[] }, - SpecGroup { roles: &[Role::ALL[16], Role::ALL[17]], needs: &[] }, - SpecGroup { roles: &[Role::ALL[32], Role::ALL[33], Role::ALL[34], Role::ALL[35], Role::ALL[36]], needs: &[Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[39]], needs: &[] }, - SpecGroup { roles: &[Role::ALL[37]], needs: &[] }, - SpecGroup { roles: &[Role::ALL[29], Role::ALL[30], Role::ALL[31]], needs: &[] }, - SpecGroup { roles: &[Role::ALL[40]], needs: &[Role::ALL[0], Role::ALL[39]] }, - SpecGroup { roles: &[Role::ALL[38]], needs: &[Role::ALL[29], Role::ALL[39], Role::ALL[37]] }, - SpecGroup { roles: &[Role::ALL[4], Role::ALL[5], Role::ALL[6], Role::ALL[7]], needs: &[] }, - SpecGroup { roles: &[Role::ALL[41]], needs: &[Role::ALL[0]] }, - SpecGroup { roles: &[Role::ALL[42]], needs: &[Role::ALL[0]] }, - SpecGroup { roles: &[Role::ALL[43]], needs: &[Role::ALL[0], Role::ALL[41]] }, - SpecGroup { roles: &[Role::ALL[44]], needs: &[Role::ALL[0], Role::ALL[42]] }, - SpecGroup { roles: &[Role::ALL[45]], needs: &[Role::ALL[0], Role::ALL[42], Role::ALL[44]] }, - SpecGroup { roles: &[Role::ALL[46]], needs: &[Role::ALL[0], Role::ALL[4]] }, - SpecGroup { roles: &[Role::ALL[47]], needs: &[Role::ALL[0], Role::ALL[4]] }, - SpecGroup { roles: &[Role::ALL[18], Role::ALL[19]], needs: &[] }, - SpecGroup { roles: &[Role::ALL[20], Role::ALL[21], Role::ALL[22]], needs: &[] }, - SpecGroup { roles: &[Role::ALL[23], Role::ALL[24], Role::ALL[25]], needs: &[] }, - SpecGroup { roles: &[Role::ALL[26], Role::ALL[27], Role::ALL[28]], needs: &[] }, - SpecGroup { roles: &[Role::ALL[56]], needs: &[Role::ALL[23], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[57]], needs: &[Role::ALL[26]] }, - SpecGroup { roles: &[Role::ALL[58]], needs: &[Role::ALL[4]] }, - SpecGroup { roles: &[Role::ALL[62]], needs: &[] }, - SpecGroup { roles: &[Role::ALL[59], Role::ALL[60], Role::ALL[61]], needs: &[Role::ALL[62]] }, - SpecGroup { roles: &[Role::ALL[66]], needs: &[Role::ALL[0]] }, - SpecGroup { roles: &[Role::ALL[70], Role::ALL[71], Role::ALL[72]], needs: &[Role::ALL[0]] }, - SpecGroup { roles: &[Role::ALL[94]], needs: &[Role::ALL[70]] }, - SpecGroup { roles: &[Role::ALL[83], Role::ALL[84]], needs: &[Role::ALL[0]] }, - SpecGroup { roles: &[Role::ALL[88], Role::ALL[89]], needs: &[] }, - SpecGroup { roles: &[Role::ALL[48]], needs: &[Role::ALL[0], Role::ALL[41], Role::ALL[43], Role::ALL[47], Role::ALL[4], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[49]], needs: &[Role::ALL[0], Role::ALL[41], Role::ALL[43], Role::ALL[47], Role::ALL[4], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[50]], needs: &[Role::ALL[0], Role::ALL[47], Role::ALL[49], Role::ALL[4], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[51]], needs: &[Role::ALL[0], Role::ALL[42], Role::ALL[44], Role::ALL[47], Role::ALL[48], Role::ALL[49], Role::ALL[4], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[52]], needs: &[Role::ALL[0], Role::ALL[42], Role::ALL[43], Role::ALL[44], Role::ALL[47], Role::ALL[48], Role::ALL[49], Role::ALL[4], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[53]], needs: &[Role::ALL[0], Role::ALL[42], Role::ALL[44], Role::ALL[47], Role::ALL[48], Role::ALL[49], Role::ALL[4], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[54]], needs: &[Role::ALL[0], Role::ALL[43], Role::ALL[44], Role::ALL[47], Role::ALL[4], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[55]], needs: &[Role::ALL[0], Role::ALL[43], Role::ALL[47], Role::ALL[48], Role::ALL[4], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[65]], needs: &[Role::ALL[59], Role::ALL[62], Role::ALL[4], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[63]], needs: &[Role::ALL[59], Role::ALL[65], Role::ALL[4], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[64]], needs: &[Role::ALL[59], Role::ALL[65], Role::ALL[62], Role::ALL[4], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[67]], needs: &[Role::ALL[0], Role::ALL[4], Role::ALL[8], Role::ALL[59], Role::ALL[65], Role::ALL[46]] }, - SpecGroup { roles: &[Role::ALL[68]], needs: &[Role::ALL[0], Role::ALL[4], Role::ALL[8], Role::ALL[59], Role::ALL[65], Role::ALL[66], Role::ALL[47]] }, - SpecGroup { roles: &[Role::ALL[69]], needs: &[Role::ALL[0], Role::ALL[8], Role::ALL[59], Role::ALL[66], Role::ALL[68]] }, - SpecGroup { roles: &[Role::ALL[73]], needs: &[Role::ALL[0], Role::ALL[70], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[74]], needs: &[Role::ALL[0], Role::ALL[70], Role::ALL[4], Role::ALL[8], Role::ALL[41], Role::ALL[43], Role::ALL[47]] }, - SpecGroup { roles: &[Role::ALL[75]], needs: &[Role::ALL[0], Role::ALL[70], Role::ALL[8], Role::ALL[73]] }, - SpecGroup { roles: &[Role::ALL[76]], needs: &[Role::ALL[0], Role::ALL[70], Role::ALL[8], Role::ALL[42], Role::ALL[74]] }, - SpecGroup { roles: &[Role::ALL[77]], needs: &[Role::ALL[70], Role::ALL[8], Role::ALL[76], Role::ALL[75]] }, - SpecGroup { roles: &[Role::ALL[78]], needs: &[Role::ALL[0], Role::ALL[70], Role::ALL[8], Role::ALL[44], Role::ALL[73]] }, - SpecGroup { roles: &[Role::ALL[79]], needs: &[Role::ALL[0], Role::ALL[70], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[80]], needs: &[Role::ALL[0], Role::ALL[70], Role::ALL[8], Role::ALL[48], Role::ALL[75]] }, - SpecGroup { roles: &[Role::ALL[81]], needs: &[Role::ALL[0], Role::ALL[70], Role::ALL[8], Role::ALL[49], Role::ALL[74]] }, - SpecGroup { roles: &[Role::ALL[82]], needs: &[Role::ALL[0], Role::ALL[70], Role::ALL[4], Role::ALL[8], Role::ALL[45], Role::ALL[49], Role::ALL[46], Role::ALL[75]] }, - SpecGroup { roles: &[Role::ALL[85]], needs: &[Role::ALL[0], Role::ALL[8], Role::ALL[83], Role::ALL[49], Role::ALL[45]] }, - SpecGroup { roles: &[Role::ALL[86]], needs: &[Role::ALL[0], Role::ALL[4], Role::ALL[8], Role::ALL[83], Role::ALL[49], Role::ALL[45], Role::ALL[47]] }, - SpecGroup { roles: &[Role::ALL[87]], needs: &[Role::ALL[37], Role::ALL[4], Role::ALL[8], Role::ALL[59], Role::ALL[65]] }, - SpecGroup { roles: &[Role::ALL[90]], needs: &[Role::ALL[37], Role::ALL[29], Role::ALL[39], Role::ALL[38], Role::ALL[8], Role::ALL[88]] }, - SpecGroup { roles: &[Role::ALL[91]], needs: &[Role::ALL[29], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[92]], needs: &[Role::ALL[37], Role::ALL[29], Role::ALL[39], Role::ALL[38], Role::ALL[8], Role::ALL[91]] }, - SpecGroup { roles: &[Role::ALL[93]], needs: &[Role::ALL[59], Role::ALL[62], Role::ALL[8]] }, - SpecGroup { roles: &[Role::ALL[95]], needs: &[Role::ALL[0], Role::ALL[70], Role::ALL[4], Role::ALL[8], Role::ALL[59], Role::ALL[65], Role::ALL[46]] }, - SpecGroup { roles: &[Role::ALL[96]], needs: &[Role::ALL[0], Role::ALL[70], Role::ALL[4], Role::ALL[8], Role::ALL[59], Role::ALL[65], Role::ALL[94], Role::ALL[47]] }, - SpecGroup { roles: &[Role::ALL[97]], needs: &[Role::ALL[0], Role::ALL[70], Role::ALL[8], Role::ALL[59], Role::ALL[94], Role::ALL[76], Role::ALL[96]] }, - SpecGroup { roles: &[Role::ALL[98]], needs: &[Role::ALL[0], Role::ALL[70], Role::ALL[8], Role::ALL[93], Role::ALL[94], Role::ALL[76], Role::ALL[77], Role::ALL[80], Role::ALL[81], Role::ALL[97]] }, - SpecGroup { roles: &[Role::ALL[99]], needs: &[Role::ALL[0], Role::ALL[70], Role::ALL[8], Role::ALL[93], Role::ALL[94], Role::ALL[76], Role::ALL[80], Role::ALL[81], Role::ALL[97], Role::ALL[67]] }, - SpecGroup { roles: &[Role::ALL[100]], needs: &[Role::ALL[0], Role::ALL[39], Role::ALL[40], Role::ALL[4], Role::ALL[8], Role::ALL[44], Role::ALL[47]] }, - SpecGroup { roles: &[Role::ALL[101]], needs: &[Role::ALL[0], Role::ALL[37], Role::ALL[29], Role::ALL[39], Role::ALL[38], Role::ALL[8], Role::ALL[42], Role::ALL[100]] }, - SpecGroup { roles: &[Role::ALL[102]], needs: &[Role::ALL[0], Role::ALL[37], Role::ALL[29], Role::ALL[39], Role::ALL[40], Role::ALL[38], Role::ALL[8], Role::ALL[44]] }, -]; - -#[rustfmt::skip] -pub(super) fn specification(role: Role, source: &ArtifactSource) -> Result, String> { - Ok(Some(match role.index() { - 4 | 37 | 39 | 70 | 88 => Specification::Anchor { levels: 0, typ: sort(U::succ(U::zero())) }, - 5 | 6 => Specification::Anchor { levels: 0, typ: reference(source, Role::ALL[4], &[])? }, - 7 => Specification::Anchor { levels: 1, typ: pi(pi(reference(source, Role::ALL[4], &[])?, sort(param(0))), pi(app(var(0), reference(source, Role::ALL[5], &[])?), pi(app(var(1), reference(source, Role::ALL[6], &[])?), pi(reference(source, Role::ALL[4], &[])?, app(var(3), var(0)))))) }, - 18 => Specification::Anchor { levels: 0, typ: sort(U::zero()) }, - 19 => Specification::Anchor { levels: 0, typ: reference(source, Role::ALL[18], &[])? }, - 20 => Specification::Anchor { levels: 0, typ: pi(sort(U::zero()), pi(sort(U::zero()), sort(U::zero()))) }, - 21 => Specification::Anchor { levels: 0, typ: pi(sort(U::zero()), pi(sort(U::zero()), pi(var(1), pi(var(1), app(app(reference(source, Role::ALL[20], &[])?, var(3)), var(2)))))) }, - 22 => Specification::Anchor { levels: 1, typ: pi(sort(U::zero()), pi(sort(U::zero()), pi(pi(app(app(reference(source, Role::ALL[20], &[])?, var(1)), var(0)), sort(param(0))), pi(pi(var(2), pi(var(2), app(var(2), app(app(app(app(reference(source, Role::ALL[21], &[])?, var(4)), var(3)), var(1)), var(0))))), pi(app(app(reference(source, Role::ALL[20], &[])?, var(3)), var(2)), app(var(2), var(0))))))) }, - 29 => Specification::Anchor { levels: 1, typ: pi(sort(U::succ(param(0))), sort(U::succ(param(0)))) }, - 30 => Specification::Anchor { levels: 1, typ: pi(sort(U::succ(param(0))), app(reference(source, Role::ALL[29], &[param(0)])?, var(0))) }, - 31 => Specification::Anchor { levels: 1, typ: pi(sort(U::succ(param(0))), pi(var(0), pi(app(reference(source, Role::ALL[29], &[param(0)])?, var(1)), app(reference(source, Role::ALL[29], &[param(0)])?, var(2))))) }, - 38 => Specification::Anchor { levels: 0, typ: pi(app(reference(source, Role::ALL[29], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), reference(source, Role::ALL[37], &[])?) }, - 40 => Specification::Anchor { levels: 0, typ: pi(reference(source, Role::ALL[0], &[])?, reference(source, Role::ALL[39], &[])?) }, - 59 => Specification::Anchor { levels: 0, typ: pi(sort(U::zero()), sort(U::succ(U::zero()))) }, - 60 => Specification::Anchor { levels: 0, typ: pi(sort(U::zero()), pi(var(0), app(reference(source, Role::ALL[59], &[])?, var(1)))) }, - 61 => Specification::Anchor { levels: 0, typ: pi(sort(U::zero()), pi(app(reference(source, Role::ALL[62], &[])?, var(0)), app(reference(source, Role::ALL[59], &[])?, var(1)))) }, - 62 => Specification::Anchor { levels: 0, typ: pi(sort(U::zero()), sort(U::zero())) }, - 63 => Specification::Anchor { levels: 0, typ: pi(sort(U::zero()), pi(app(reference(source, Role::ALL[59], &[])?, var(0)), pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, var(1)), var(0))), reference(source, Role::ALL[6], &[])?), var(2)))) }, - 64 => Specification::Anchor { levels: 0, typ: pi(sort(U::zero()), pi(app(reference(source, Role::ALL[59], &[])?, var(0)), pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, var(1)), var(0))), reference(source, Role::ALL[5], &[])?), app(reference(source, Role::ALL[62], &[])?, var(2))))) }, - 65 => Specification::Definition { levels: 0, typ: pi(sort(U::zero()), pi(app(reference(source, Role::ALL[59], &[])?, var(0)), reference(source, Role::ALL[4], &[])?)), equations: vec![Equation { statement: pi(sort(U::zero()), pi(var(0), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, var(1)), app(app(reference(source, Role::ALL[60], &[])?, var(1)), var(0)))), reference(source, Role::ALL[6], &[])?))), definitional_sides: Some((lam(sort(U::zero()), lam(var(0), app(app(reference(source, Role::ALL[65], &[])?, var(1)), app(app(reference(source, Role::ALL[60], &[])?, var(1)), var(0))))), lam(sort(U::zero()), lam(var(0), reference(source, Role::ALL[6], &[])?)))) }, Equation { statement: pi(sort(U::zero()), pi(app(reference(source, Role::ALL[62], &[])?, var(0)), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, var(1)), app(app(reference(source, Role::ALL[61], &[])?, var(1)), var(0)))), reference(source, Role::ALL[5], &[])?))), definitional_sides: Some((lam(sort(U::zero()), lam(app(reference(source, Role::ALL[62], &[])?, var(0)), app(app(reference(source, Role::ALL[65], &[])?, var(1)), app(app(reference(source, Role::ALL[61], &[])?, var(1)), var(0))))), lam(sort(U::zero()), lam(app(reference(source, Role::ALL[62], &[])?, var(0)), reference(source, Role::ALL[5], &[])?)))) }, Equation { statement: pi(sort(U::zero()), pi(app(reference(source, Role::ALL[59], &[])?, var(0)), pi(app(reference(source, Role::ALL[59], &[])?, var(1)), pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, var(2)), var(1))), app(app(reference(source, Role::ALL[65], &[])?, var(2)), var(0))), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, app(reference(source, Role::ALL[59], &[])?, var(3))), var(2)), var(1)))))), definitional_sides: None }] }, - 66 => Specification::Anchor { levels: 0, typ: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, sort(U::zero()))) }, - 67 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[59], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[0], &[])?), var(1)), var(0))))), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[0], &[])?), var(1)), var(0))), app(app(reference(source, Role::ALL[67], &[])?, var(1)), var(0)))), app(app(reference(source, Role::ALL[46], &[])?, var(1)), var(0))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[65], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[0], &[])?), var(1)), var(0))), app(app(reference(source, Role::ALL[67], &[])?, var(1)), var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[46], &[])?, var(1)), var(0)))))) }] }, - 68 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[59], &[])?, app(app(reference(source, Role::ALL[66], &[])?, var(1)), var(0))))), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, app(app(reference(source, Role::ALL[66], &[])?, var(1)), var(0))), app(app(reference(source, Role::ALL[68], &[])?, var(1)), var(0)))), app(app(reference(source, Role::ALL[47], &[])?, var(1)), var(0))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[65], &[])?, app(app(reference(source, Role::ALL[66], &[])?, var(1)), var(0))), app(app(reference(source, Role::ALL[68], &[])?, var(1)), var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[47], &[])?, var(1)), var(0)))))) }] }, - 69 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[59], &[])?, app(app(reference(source, Role::ALL[66], &[])?, app(reference(source, Role::ALL[2], &[])?, var(1))), var(0))))), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, app(reference(source, Role::ALL[59], &[])?, app(app(reference(source, Role::ALL[66], &[])?, app(reference(source, Role::ALL[2], &[])?, var(1))), var(0)))), app(app(reference(source, Role::ALL[69], &[])?, var(1)), var(0))), app(app(reference(source, Role::ALL[68], &[])?, app(reference(source, Role::ALL[2], &[])?, var(1))), var(0))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[69], &[])?, var(1)), var(0)))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[68], &[])?, app(reference(source, Role::ALL[2], &[])?, var(1))), var(0)))))) }] }, - 71 | 72 => Specification::Anchor { levels: 0, typ: pi(reference(source, Role::ALL[0], &[])?, reference(source, Role::ALL[70], &[])?) }, - 73 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[0], &[])?, reference(source, Role::ALL[70], &[])?), equations: vec![Equation { statement: app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(reference(source, Role::ALL[73], &[])?, reference(source, Role::ALL[1], &[])?)), app(reference(source, Role::ALL[71], &[])?, reference(source, Role::ALL[1], &[])?)), definitional_sides: Some((app(reference(source, Role::ALL[73], &[])?, reference(source, Role::ALL[1], &[])?), app(reference(source, Role::ALL[71], &[])?, reference(source, Role::ALL[1], &[])?))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(reference(source, Role::ALL[73], &[])?, app(reference(source, Role::ALL[2], &[])?, var(0)))), app(reference(source, Role::ALL[72], &[])?, var(0)))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[73], &[])?, app(reference(source, Role::ALL[2], &[])?, var(0)))), lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[72], &[])?, var(0))))) }] }, - 74 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, reference(source, Role::ALL[70], &[])?)), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[47], &[])?, var(0)), var(1))), reference(source, Role::ALL[6], &[])?), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[74], &[])?, var(2)), var(1))), app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[43], &[])?, var(2)), var(1))))))), definitional_sides: None }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[47], &[])?, var(0)), var(1))), reference(source, Role::ALL[5], &[])?), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[74], &[])?, var(2)), var(1))), app(reference(source, Role::ALL[72], &[])?, app(reference(source, Role::ALL[41], &[])?, app(app(reference(source, Role::ALL[43], &[])?, var(1)), var(2)))))))), definitional_sides: None }] }, - 75 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[70], &[])?, reference(source, Role::ALL[70], &[])?), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(reference(source, Role::ALL[75], &[])?, app(reference(source, Role::ALL[71], &[])?, var(0)))), app(reference(source, Role::ALL[73], &[])?, var(0)))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[75], &[])?, app(reference(source, Role::ALL[71], &[])?, var(0)))), lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[73], &[])?, var(0))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(reference(source, Role::ALL[75], &[])?, app(reference(source, Role::ALL[72], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, var(0))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[75], &[])?, app(reference(source, Role::ALL[72], &[])?, var(0)))), lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, var(0)))))) }] }, - 76 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[70], &[])?, reference(source, Role::ALL[70], &[])?)), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[42], &[])?, var(1)), var(0)))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[42], &[])?, var(1)), var(0))))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(app(reference(source, Role::ALL[74], &[])?, var(1)), app(reference(source, Role::ALL[2], &[])?, var(0)))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[74], &[])?, var(1)), app(reference(source, Role::ALL[2], &[])?, var(0))))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[74], &[])?, var(0)), app(reference(source, Role::ALL[2], &[])?, var(1)))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[74], &[])?, var(0)), app(reference(source, Role::ALL[2], &[])?, var(1))))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(reference(source, Role::ALL[72], &[])?, app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[42], &[])?, var(1)), var(0))))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[72], &[])?, app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[42], &[])?, var(1)), var(0)))))))) }] }, - 77 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[70], &[])?, reference(source, Role::ALL[70], &[])?)), equations: vec![Equation { statement: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[70], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[77], &[])?, var(1)), var(0))), app(app(reference(source, Role::ALL[76], &[])?, var(1)), app(reference(source, Role::ALL[75], &[])?, var(0)))))), definitional_sides: Some((lam(reference(source, Role::ALL[70], &[])?, lam(reference(source, Role::ALL[70], &[])?, app(app(reference(source, Role::ALL[77], &[])?, var(1)), var(0)))), lam(reference(source, Role::ALL[70], &[])?, lam(reference(source, Role::ALL[70], &[])?, app(app(reference(source, Role::ALL[76], &[])?, var(1)), app(reference(source, Role::ALL[75], &[])?, var(0))))))) }] }, - 78 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[70], &[])?, reference(source, Role::ALL[70], &[])?)), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[78], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[44], &[])?, var(1)), var(0)))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[78], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[44], &[])?, var(1)), var(0))))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[78], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(reference(source, Role::ALL[73], &[])?, app(app(reference(source, Role::ALL[44], &[])?, var(1)), app(reference(source, Role::ALL[2], &[])?, var(0))))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[78], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[73], &[])?, app(app(reference(source, Role::ALL[44], &[])?, var(1)), app(reference(source, Role::ALL[2], &[])?, var(0)))))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[78], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(reference(source, Role::ALL[73], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, var(1))), var(0)))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[78], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[73], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, var(1))), var(0))))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[78], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, var(1))), app(reference(source, Role::ALL[2], &[])?, var(0))))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[78], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, var(1))), app(reference(source, Role::ALL[2], &[])?, var(0)))))))) }] }, - 79 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[70], &[])?, reference(source, Role::ALL[0], &[])?), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[0], &[])?), app(reference(source, Role::ALL[79], &[])?, app(reference(source, Role::ALL[71], &[])?, var(0)))), var(0))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[79], &[])?, app(reference(source, Role::ALL[71], &[])?, var(0)))), lam(reference(source, Role::ALL[0], &[])?, var(0)))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[0], &[])?), app(reference(source, Role::ALL[79], &[])?, app(reference(source, Role::ALL[72], &[])?, var(0)))), app(reference(source, Role::ALL[2], &[])?, var(0)))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[79], &[])?, app(reference(source, Role::ALL[72], &[])?, var(0)))), lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[2], &[])?, var(0))))) }] }, - 80 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[70], &[])?, reference(source, Role::ALL[70], &[])?)), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[80], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[48], &[])?, var(1)), var(0)))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[80], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[48], &[])?, var(1)), var(0))))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[80], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(reference(source, Role::ALL[75], &[])?, app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[48], &[])?, var(1)), app(reference(source, Role::ALL[2], &[])?, var(0)))))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[80], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[75], &[])?, app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[48], &[])?, var(1)), app(reference(source, Role::ALL[2], &[])?, var(0))))))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[80], &[])?, app(reference(source, Role::ALL[72], &[])?, var(0))), app(reference(source, Role::ALL[71], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[71], &[])?, reference(source, Role::ALL[1], &[])?))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[80], &[])?, app(reference(source, Role::ALL[72], &[])?, var(0))), app(reference(source, Role::ALL[71], &[])?, reference(source, Role::ALL[1], &[])?))), lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[71], &[])?, reference(source, Role::ALL[1], &[])?)))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[80], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, var(0))))), app(reference(source, Role::ALL[72], &[])?, app(app(reference(source, Role::ALL[48], &[])?, var(1)), app(reference(source, Role::ALL[2], &[])?, var(0))))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[80], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, var(0)))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[72], &[])?, app(app(reference(source, Role::ALL[48], &[])?, var(1)), app(reference(source, Role::ALL[2], &[])?, var(0)))))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[80], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[48], &[])?, var(1)), app(reference(source, Role::ALL[2], &[])?, var(0)))))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[80], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[48], &[])?, var(1)), app(reference(source, Role::ALL[2], &[])?, var(0))))))))) }] }, - 81 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[70], &[])?, reference(source, Role::ALL[70], &[])?)), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[81], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[49], &[])?, var(1)), var(0)))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[81], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[49], &[])?, var(1)), var(0))))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[81], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[49], &[])?, var(1)), app(reference(source, Role::ALL[2], &[])?, var(0))))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[81], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[49], &[])?, var(1)), app(reference(source, Role::ALL[2], &[])?, var(0)))))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[81], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[74], &[])?, var(0)), app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[49], &[])?, var(1)), var(0))))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[81], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[74], &[])?, var(0)), app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[49], &[])?, var(1)), var(0)))))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[81], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(app(reference(source, Role::ALL[74], &[])?, app(reference(source, Role::ALL[2], &[])?, var(0))), app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[49], &[])?, var(1)), app(reference(source, Role::ALL[2], &[])?, var(0)))))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[81], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[74], &[])?, app(reference(source, Role::ALL[2], &[])?, var(0))), app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[49], &[])?, var(1)), app(reference(source, Role::ALL[2], &[])?, var(0))))))))) }] }, - 82 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[0], &[])?, reference(source, Role::ALL[70], &[])?)), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[82], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), var(0))), app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[45], &[])?, var(1)), var(0)))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[82], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), var(0)))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[45], &[])?, var(1)), var(0))))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[46], &[])?, app(app(reference(source, Role::ALL[49], &[])?, var(0)), app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), reference(source, Role::ALL[1], &[])?)), reference(source, Role::ALL[6], &[])?), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[82], &[])?, app(reference(source, Role::ALL[72], &[])?, var(2))), var(1))), app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[45], &[])?, app(reference(source, Role::ALL[2], &[])?, var(2))), var(1))))))), definitional_sides: None }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[46], &[])?, app(app(reference(source, Role::ALL[49], &[])?, var(0)), app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), reference(source, Role::ALL[1], &[])?)), reference(source, Role::ALL[5], &[])?), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[82], &[])?, app(reference(source, Role::ALL[72], &[])?, var(2))), var(1))), app(reference(source, Role::ALL[75], &[])?, app(reference(source, Role::ALL[71], &[])?, app(app(reference(source, Role::ALL[45], &[])?, app(reference(source, Role::ALL[2], &[])?, var(2))), var(1)))))))), definitional_sides: None }] }, - 83 => Specification::Anchor { levels: 0, typ: pi(reference(source, Role::ALL[0], &[])?, sort(U::succ(U::zero()))) }, - 84 => Specification::Anchor { levels: 0, typ: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(reference(source, Role::ALL[83], &[])?, var(1)))) }, - 85 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[0], &[])?, pi(app(reference(source, Role::ALL[83], &[])?, var(0)), reference(source, Role::ALL[0], &[])?)), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[0], &[])?), app(app(reference(source, Role::ALL[85], &[])?, var(1)), app(app(reference(source, Role::ALL[84], &[])?, var(1)), var(0)))), app(app(reference(source, Role::ALL[49], &[])?, var(0)), app(app(reference(source, Role::ALL[45], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), var(1)))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[85], &[])?, var(1)), app(app(reference(source, Role::ALL[84], &[])?, var(1)), var(0))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[49], &[])?, var(0)), app(app(reference(source, Role::ALL[45], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), var(1))))))) }] }, - 86 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[0], &[])?, pi(app(reference(source, Role::ALL[83], &[])?, var(0)), pi(app(reference(source, Role::ALL[83], &[])?, var(1)), reference(source, Role::ALL[4], &[])?))), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(app(reference(source, Role::ALL[86], &[])?, var(2)), app(app(reference(source, Role::ALL[84], &[])?, var(2)), var(1))), app(app(reference(source, Role::ALL[84], &[])?, var(2)), var(0)))), app(app(reference(source, Role::ALL[47], &[])?, app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[49], &[])?, var(1)), app(app(reference(source, Role::ALL[45], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), var(2))))), app(app(reference(source, Role::ALL[49], &[])?, var(0)), app(app(reference(source, Role::ALL[45], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), var(2)))))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[86], &[])?, var(2)), app(app(reference(source, Role::ALL[84], &[])?, var(2)), var(1))), app(app(reference(source, Role::ALL[84], &[])?, var(2)), var(0)))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[47], &[])?, app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[49], &[])?, var(1)), app(app(reference(source, Role::ALL[45], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), var(2))))), app(app(reference(source, Role::ALL[49], &[])?, var(0)), app(app(reference(source, Role::ALL[45], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), var(2))))))))) }] }, - 87 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[37], &[])?, pi(reference(source, Role::ALL[37], &[])?, app(reference(source, Role::ALL[59], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[37], &[])?), var(1)), var(0))))), equations: vec![Equation { statement: pi(reference(source, Role::ALL[37], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[37], &[])?), var(0)), var(0))), app(app(reference(source, Role::ALL[87], &[])?, var(0)), var(0)))), reference(source, Role::ALL[6], &[])?)), definitional_sides: Some((lam(reference(source, Role::ALL[37], &[])?, app(app(reference(source, Role::ALL[65], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[37], &[])?), var(0)), var(0))), app(app(reference(source, Role::ALL[87], &[])?, var(0)), var(0)))), lam(reference(source, Role::ALL[37], &[])?, reference(source, Role::ALL[6], &[])?))) }] }, - 89 => Specification::Anchor { levels: 0, typ: reference(source, Role::ALL[88], &[])? }, - 90 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[37], &[])?, reference(source, Role::ALL[88], &[])?), equations: vec![Equation { statement: app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[88], &[])?), app(reference(source, Role::ALL[90], &[])?, app(reference(source, Role::ALL[38], &[])?, app(reference(source, Role::ALL[30], &[U::zero()])?, reference(source, Role::ALL[39], &[])?)))), reference(source, Role::ALL[89], &[])?), definitional_sides: Some((app(reference(source, Role::ALL[90], &[])?, app(reference(source, Role::ALL[38], &[])?, app(reference(source, Role::ALL[30], &[U::zero()])?, reference(source, Role::ALL[39], &[])?))), reference(source, Role::ALL[89], &[])?)) }] }, - 91 => Specification::Definition { levels: 1, typ: pi(sort(U::succ(param(0))), pi(app(reference(source, Role::ALL[29], &[param(0)])?, var(0)), pi(app(reference(source, Role::ALL[29], &[param(0)])?, var(1)), app(reference(source, Role::ALL[29], &[param(0)])?, var(2))))), equations: vec![Equation { statement: pi(sort(U::succ(param(0))), pi(app(reference(source, Role::ALL[29], &[param(0)])?, var(0)), app(app(app(reference(source, Role::ALL[8], &[U::succ(param(0))])?, app(reference(source, Role::ALL[29], &[param(0)])?, var(1))), app(app(app(reference(source, Role::ALL[91], &[param(0)])?, var(1)), app(reference(source, Role::ALL[30], &[param(0)])?, var(1))), var(0))), var(0)))), definitional_sides: Some((lam(sort(U::succ(param(0))), lam(app(reference(source, Role::ALL[29], &[param(0)])?, var(0)), app(app(app(reference(source, Role::ALL[91], &[param(0)])?, var(1)), app(reference(source, Role::ALL[30], &[param(0)])?, var(1))), var(0)))), lam(sort(U::succ(param(0))), lam(app(reference(source, Role::ALL[29], &[param(0)])?, var(0)), var(0))))) }, Equation { statement: pi(sort(U::succ(param(0))), pi(var(0), pi(app(reference(source, Role::ALL[29], &[param(0)])?, var(1)), pi(app(reference(source, Role::ALL[29], &[param(0)])?, var(2)), app(app(app(reference(source, Role::ALL[8], &[U::succ(param(0))])?, app(reference(source, Role::ALL[29], &[param(0)])?, var(3))), app(app(app(reference(source, Role::ALL[91], &[param(0)])?, var(3)), app(app(app(reference(source, Role::ALL[31], &[param(0)])?, var(3)), var(2)), var(1))), var(0))), app(app(app(reference(source, Role::ALL[31], &[param(0)])?, var(3)), var(2)), app(app(app(reference(source, Role::ALL[91], &[param(0)])?, var(3)), var(1)), var(0)))))))), definitional_sides: Some((lam(sort(U::succ(param(0))), lam(var(0), lam(app(reference(source, Role::ALL[29], &[param(0)])?, var(1)), lam(app(reference(source, Role::ALL[29], &[param(0)])?, var(2)), app(app(app(reference(source, Role::ALL[91], &[param(0)])?, var(3)), app(app(app(reference(source, Role::ALL[31], &[param(0)])?, var(3)), var(2)), var(1))), var(0)))))), lam(sort(U::succ(param(0))), lam(var(0), lam(app(reference(source, Role::ALL[29], &[param(0)])?, var(1)), lam(app(reference(source, Role::ALL[29], &[param(0)])?, var(2)), app(app(app(reference(source, Role::ALL[31], &[param(0)])?, var(3)), var(2)), app(app(app(reference(source, Role::ALL[91], &[param(0)])?, var(3)), var(1)), var(0))))))))) }] }, - 92 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[37], &[])?, pi(reference(source, Role::ALL[37], &[])?, reference(source, Role::ALL[37], &[])?)), equations: vec![Equation { statement: pi(app(reference(source, Role::ALL[29], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), pi(app(reference(source, Role::ALL[29], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[37], &[])?), app(app(reference(source, Role::ALL[92], &[])?, app(reference(source, Role::ALL[38], &[])?, var(1))), app(reference(source, Role::ALL[38], &[])?, var(0)))), app(reference(source, Role::ALL[38], &[])?, app(app(app(reference(source, Role::ALL[91], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), var(1)), var(0)))))), definitional_sides: Some((lam(app(reference(source, Role::ALL[29], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), lam(app(reference(source, Role::ALL[29], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), app(app(reference(source, Role::ALL[92], &[])?, app(reference(source, Role::ALL[38], &[])?, var(1))), app(reference(source, Role::ALL[38], &[])?, var(0))))), lam(app(reference(source, Role::ALL[29], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), lam(app(reference(source, Role::ALL[29], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), app(reference(source, Role::ALL[38], &[])?, app(app(app(reference(source, Role::ALL[91], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), var(1)), var(0))))))) }] }, - 93 => Specification::Definition { levels: 1, typ: pi(sort(param(0)), pi(sort(U::zero()), pi(app(reference(source, Role::ALL[59], &[])?, var(0)), pi(var(2), pi(var(3), var(4)))))), equations: vec![Equation { statement: pi(sort(param(0)), pi(sort(U::zero()), pi(var(0), pi(var(2), pi(var(3), app(app(app(reference(source, Role::ALL[8], &[param(0)])?, var(4)), app(app(app(app(app(reference(source, Role::ALL[93], &[param(0)])?, var(4)), var(3)), app(app(reference(source, Role::ALL[60], &[])?, var(3)), var(2))), var(1)), var(0))), var(1))))))), definitional_sides: Some((lam(sort(param(0)), lam(sort(U::zero()), lam(var(0), lam(var(2), lam(var(3), app(app(app(app(app(reference(source, Role::ALL[93], &[param(0)])?, var(4)), var(3)), app(app(reference(source, Role::ALL[60], &[])?, var(3)), var(2))), var(1)), var(0))))))), lam(sort(param(0)), lam(sort(U::zero()), lam(var(0), lam(var(2), lam(var(3), var(1)))))))) }, Equation { statement: pi(sort(param(0)), pi(sort(U::zero()), pi(app(reference(source, Role::ALL[62], &[])?, var(0)), pi(var(2), pi(var(3), app(app(app(reference(source, Role::ALL[8], &[param(0)])?, var(4)), app(app(app(app(app(reference(source, Role::ALL[93], &[param(0)])?, var(4)), var(3)), app(app(reference(source, Role::ALL[61], &[])?, var(3)), var(2))), var(1)), var(0))), var(0))))))), definitional_sides: Some((lam(sort(param(0)), lam(sort(U::zero()), lam(app(reference(source, Role::ALL[62], &[])?, var(0)), lam(var(2), lam(var(3), app(app(app(app(app(reference(source, Role::ALL[93], &[param(0)])?, var(4)), var(3)), app(app(reference(source, Role::ALL[61], &[])?, var(3)), var(2))), var(1)), var(0))))))), lam(sort(param(0)), lam(sort(U::zero()), lam(app(reference(source, Role::ALL[62], &[])?, var(0)), lam(var(2), lam(var(3), var(0)))))))) }] }, - 94 => Specification::Anchor { levels: 0, typ: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[70], &[])?, sort(U::zero()))) }, - 95 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[70], &[])?, app(reference(source, Role::ALL[59], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), var(1)), var(0))))), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[95], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0))))), app(app(reference(source, Role::ALL[46], &[])?, var(1)), var(0))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[65], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[95], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[46], &[])?, var(1)), var(0)))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(app(reference(source, Role::ALL[95], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0))))), reference(source, Role::ALL[5], &[])?))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[65], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(app(reference(source, Role::ALL[95], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, reference(source, Role::ALL[5], &[])?)))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[95], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0))))), reference(source, Role::ALL[5], &[])?))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[65], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[95], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, reference(source, Role::ALL[5], &[])?)))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(app(reference(source, Role::ALL[95], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0))))), app(app(reference(source, Role::ALL[46], &[])?, var(1)), var(0))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[65], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(app(reference(source, Role::ALL[95], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[46], &[])?, var(1)), var(0)))))) }] }, - 96 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[70], &[])?, app(reference(source, Role::ALL[59], &[])?, app(app(reference(source, Role::ALL[94], &[])?, var(1)), var(0))))), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, app(app(reference(source, Role::ALL[94], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[96], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0))))), app(app(reference(source, Role::ALL[47], &[])?, var(1)), var(0))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[65], &[])?, app(app(reference(source, Role::ALL[94], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[96], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[47], &[])?, var(1)), var(0)))))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, app(app(reference(source, Role::ALL[94], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(app(reference(source, Role::ALL[96], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0))))), reference(source, Role::ALL[5], &[])?))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[65], &[])?, app(app(reference(source, Role::ALL[94], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(app(reference(source, Role::ALL[96], &[])?, app(reference(source, Role::ALL[71], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, reference(source, Role::ALL[5], &[])?)))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, app(app(reference(source, Role::ALL[94], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[96], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0))))), reference(source, Role::ALL[6], &[])?))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[65], &[])?, app(app(reference(source, Role::ALL[94], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[96], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[71], &[])?, var(0)))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, reference(source, Role::ALL[6], &[])?)))) }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[65], &[])?, app(app(reference(source, Role::ALL[94], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(app(reference(source, Role::ALL[96], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0))))), app(app(reference(source, Role::ALL[47], &[])?, var(0)), var(1))))), definitional_sides: Some((lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[65], &[])?, app(app(reference(source, Role::ALL[94], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))), app(app(reference(source, Role::ALL[96], &[])?, app(reference(source, Role::ALL[72], &[])?, var(1))), app(reference(source, Role::ALL[72], &[])?, var(0)))))), lam(reference(source, Role::ALL[0], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[47], &[])?, var(0)), var(1)))))) }] }, - 97 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[70], &[])?, app(reference(source, Role::ALL[59], &[])?, app(app(reference(source, Role::ALL[94], &[])?, app(app(reference(source, Role::ALL[76], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), var(0))))), equations: vec![Equation { statement: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[70], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, app(reference(source, Role::ALL[59], &[])?, app(app(reference(source, Role::ALL[94], &[])?, app(app(reference(source, Role::ALL[76], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), var(0)))), app(app(reference(source, Role::ALL[97], &[])?, var(1)), var(0))), app(app(reference(source, Role::ALL[96], &[])?, app(app(reference(source, Role::ALL[76], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), var(0))))), definitional_sides: Some((lam(reference(source, Role::ALL[70], &[])?, lam(reference(source, Role::ALL[70], &[])?, app(app(reference(source, Role::ALL[97], &[])?, var(1)), var(0)))), lam(reference(source, Role::ALL[70], &[])?, lam(reference(source, Role::ALL[70], &[])?, app(app(reference(source, Role::ALL[96], &[])?, app(app(reference(source, Role::ALL[76], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), var(0)))))) }] }, - 98 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[0], &[])?, reference(source, Role::ALL[70], &[])?)), equations: vec![Equation { statement: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[98], &[])?, var(1)), var(0))), app(app(app(app(app(reference(source, Role::ALL[93], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[94], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(app(reference(source, Role::ALL[81], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), app(app(reference(source, Role::ALL[80], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[71], &[])?, var(0))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))))), app(app(reference(source, Role::ALL[97], &[])?, app(app(reference(source, Role::ALL[81], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[80], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[71], &[])?, var(0))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))))), app(app(reference(source, Role::ALL[81], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[77], &[])?, app(app(reference(source, Role::ALL[81], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, var(0))))))), definitional_sides: Some((lam(reference(source, Role::ALL[70], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[98], &[])?, var(1)), var(0)))), lam(reference(source, Role::ALL[70], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(app(app(app(reference(source, Role::ALL[93], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[94], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(app(reference(source, Role::ALL[81], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), app(app(reference(source, Role::ALL[80], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[71], &[])?, var(0))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))))), app(app(reference(source, Role::ALL[97], &[])?, app(app(reference(source, Role::ALL[81], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[80], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[71], &[])?, var(0))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))))), app(app(reference(source, Role::ALL[81], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[77], &[])?, app(app(reference(source, Role::ALL[81], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, var(0)))))))) }] }, - 99 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[0], &[])?, reference(source, Role::ALL[70], &[])?)), equations: vec![Equation { statement: pi(reference(source, Role::ALL[70], &[])?, pi(reference(source, Role::ALL[0], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[99], &[])?, var(1)), var(0))), app(app(app(app(app(reference(source, Role::ALL[93], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[0], &[])?), var(0)), reference(source, Role::ALL[1], &[])?)), app(app(reference(source, Role::ALL[67], &[])?, var(0)), reference(source, Role::ALL[1], &[])?)), app(reference(source, Role::ALL[71], &[])?, reference(source, Role::ALL[1], &[])?)), app(app(app(app(app(reference(source, Role::ALL[93], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[94], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(app(reference(source, Role::ALL[81], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), app(app(reference(source, Role::ALL[80], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[71], &[])?, var(0))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))))), app(app(reference(source, Role::ALL[97], &[])?, app(app(reference(source, Role::ALL[81], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[80], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[71], &[])?, var(0))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))))), app(app(reference(source, Role::ALL[80], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[76], &[])?, app(app(reference(source, Role::ALL[80], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))))))), definitional_sides: Some((lam(reference(source, Role::ALL[70], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(reference(source, Role::ALL[99], &[])?, var(1)), var(0)))), lam(reference(source, Role::ALL[70], &[])?, lam(reference(source, Role::ALL[0], &[])?, app(app(app(app(app(reference(source, Role::ALL[93], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[0], &[])?), var(0)), reference(source, Role::ALL[1], &[])?)), app(app(reference(source, Role::ALL[67], &[])?, var(0)), reference(source, Role::ALL[1], &[])?)), app(reference(source, Role::ALL[71], &[])?, reference(source, Role::ALL[1], &[])?)), app(app(app(app(app(reference(source, Role::ALL[93], &[U::succ(U::zero())])?, reference(source, Role::ALL[70], &[])?), app(app(reference(source, Role::ALL[94], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(app(reference(source, Role::ALL[81], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), app(app(reference(source, Role::ALL[80], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[71], &[])?, var(0))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))))), app(app(reference(source, Role::ALL[97], &[])?, app(app(reference(source, Role::ALL[81], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[80], &[])?, app(app(reference(source, Role::ALL[76], &[])?, app(reference(source, Role::ALL[71], &[])?, var(0))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))))), app(app(reference(source, Role::ALL[80], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(app(reference(source, Role::ALL[76], &[])?, app(app(reference(source, Role::ALL[80], &[])?, var(1)), app(reference(source, Role::ALL[71], &[])?, var(0)))), app(reference(source, Role::ALL[71], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))))))))) }] }, - 100 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[39], &[])?, reference(source, Role::ALL[0], &[])?), equations: vec![Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[47], &[])?, app(reference(source, Role::ALL[2], &[])?, var(0))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))))))))), reference(source, Role::ALL[6], &[])?), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[0], &[])?), app(reference(source, Role::ALL[100], &[])?, app(reference(source, Role::ALL[40], &[])?, var(1)))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))), definitional_sides: None }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[47], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))))))))), var(0))), reference(source, Role::ALL[6], &[])?), pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[47], &[])?, app(reference(source, Role::ALL[2], &[])?, var(1))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))))))))))))), reference(source, Role::ALL[6], &[])?), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[0], &[])?), app(reference(source, Role::ALL[100], &[])?, app(reference(source, Role::ALL[40], &[])?, var(2)))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))))), definitional_sides: None }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[47], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))))))))))))), var(0))), reference(source, Role::ALL[6], &[])?), pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[47], &[])?, app(reference(source, Role::ALL[2], &[])?, var(1))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))))))))))))))))))))), reference(source, Role::ALL[6], &[])?), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[0], &[])?), app(reference(source, Role::ALL[100], &[])?, app(reference(source, Role::ALL[40], &[])?, var(2)))), app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))))))), definitional_sides: None }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[47], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))))))))))))))))))), var(0))), reference(source, Role::ALL[6], &[])?), pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[47], &[])?, app(reference(source, Role::ALL[2], &[])?, var(1))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))))))))))))))))))), reference(source, Role::ALL[6], &[])?), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[0], &[])?), app(reference(source, Role::ALL[100], &[])?, app(reference(source, Role::ALL[40], &[])?, var(2)))), app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))))))), definitional_sides: None }, Equation { statement: pi(reference(source, Role::ALL[0], &[])?, pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[47], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))))))))))))))))), var(0))), reference(source, Role::ALL[6], &[])?), pi(app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[4], &[])?), app(app(reference(source, Role::ALL[47], &[])?, app(reference(source, Role::ALL[2], &[])?, var(1))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))))))))))))))))))))))), reference(source, Role::ALL[6], &[])?), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[0], &[])?), app(reference(source, Role::ALL[100], &[])?, app(reference(source, Role::ALL[40], &[])?, var(2)))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))))))), definitional_sides: None }] }, - 101 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[37], &[])?, reference(source, Role::ALL[0], &[])?), equations: vec![Equation { statement: app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[0], &[])?), app(reference(source, Role::ALL[101], &[])?, app(reference(source, Role::ALL[38], &[])?, app(reference(source, Role::ALL[30], &[U::zero()])?, reference(source, Role::ALL[39], &[])?)))), reference(source, Role::ALL[1], &[])?), definitional_sides: Some((app(reference(source, Role::ALL[101], &[])?, app(reference(source, Role::ALL[38], &[])?, app(reference(source, Role::ALL[30], &[U::zero()])?, reference(source, Role::ALL[39], &[])?))), reference(source, Role::ALL[1], &[])?)) }, Equation { statement: pi(reference(source, Role::ALL[39], &[])?, pi(app(reference(source, Role::ALL[29], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[0], &[])?), app(reference(source, Role::ALL[101], &[])?, app(reference(source, Role::ALL[38], &[])?, app(app(app(reference(source, Role::ALL[31], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), var(1)), var(0))))), app(app(reference(source, Role::ALL[42], &[])?, app(reference(source, Role::ALL[100], &[])?, var(1))), app(reference(source, Role::ALL[101], &[])?, app(reference(source, Role::ALL[38], &[])?, var(0))))))), definitional_sides: Some((lam(reference(source, Role::ALL[39], &[])?, lam(app(reference(source, Role::ALL[29], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), app(reference(source, Role::ALL[101], &[])?, app(reference(source, Role::ALL[38], &[])?, app(app(app(reference(source, Role::ALL[31], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), var(1)), var(0)))))), lam(reference(source, Role::ALL[39], &[])?, lam(app(reference(source, Role::ALL[29], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), app(app(reference(source, Role::ALL[42], &[])?, app(reference(source, Role::ALL[100], &[])?, var(1))), app(reference(source, Role::ALL[101], &[])?, app(reference(source, Role::ALL[38], &[])?, var(0)))))))) }] }, - 102 => Specification::Definition { levels: 0, typ: pi(reference(source, Role::ALL[37], &[])?, reference(source, Role::ALL[39], &[])?), equations: vec![Equation { statement: app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[39], &[])?), app(reference(source, Role::ALL[102], &[])?, app(reference(source, Role::ALL[38], &[])?, app(reference(source, Role::ALL[30], &[U::zero()])?, reference(source, Role::ALL[39], &[])?)))), app(reference(source, Role::ALL[40], &[])?, app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?)))))))))), definitional_sides: Some((app(reference(source, Role::ALL[102], &[])?, app(reference(source, Role::ALL[38], &[])?, app(reference(source, Role::ALL[30], &[U::zero()])?, reference(source, Role::ALL[39], &[])?))), app(reference(source, Role::ALL[40], &[])?, app(reference(source, Role::ALL[2], &[])?, app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(app(reference(source, Role::ALL[44], &[])?, app(reference(source, Role::ALL[2], &[])?, app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))), app(reference(source, Role::ALL[2], &[])?, reference(source, Role::ALL[1], &[])?))))))))))) }, Equation { statement: pi(reference(source, Role::ALL[39], &[])?, app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[39], &[])?), app(reference(source, Role::ALL[102], &[])?, app(reference(source, Role::ALL[38], &[])?, app(app(app(reference(source, Role::ALL[31], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), var(0)), app(reference(source, Role::ALL[30], &[U::zero()])?, reference(source, Role::ALL[39], &[])?))))), var(0))), definitional_sides: Some((lam(reference(source, Role::ALL[39], &[])?, app(reference(source, Role::ALL[102], &[])?, app(reference(source, Role::ALL[38], &[])?, app(app(app(reference(source, Role::ALL[31], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), var(0)), app(reference(source, Role::ALL[30], &[U::zero()])?, reference(source, Role::ALL[39], &[])?))))), lam(reference(source, Role::ALL[39], &[])?, var(0)))) }, Equation { statement: pi(reference(source, Role::ALL[39], &[])?, pi(reference(source, Role::ALL[39], &[])?, pi(app(reference(source, Role::ALL[29], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), app(app(app(reference(source, Role::ALL[8], &[U::succ(U::zero())])?, reference(source, Role::ALL[39], &[])?), app(reference(source, Role::ALL[102], &[])?, app(reference(source, Role::ALL[38], &[])?, app(app(app(reference(source, Role::ALL[31], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), var(2)), app(app(app(reference(source, Role::ALL[31], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), var(1)), var(0)))))), app(reference(source, Role::ALL[102], &[])?, app(reference(source, Role::ALL[38], &[])?, app(app(app(reference(source, Role::ALL[31], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), var(1)), var(0)))))))), definitional_sides: Some((lam(reference(source, Role::ALL[39], &[])?, lam(reference(source, Role::ALL[39], &[])?, lam(app(reference(source, Role::ALL[29], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), app(reference(source, Role::ALL[102], &[])?, app(reference(source, Role::ALL[38], &[])?, app(app(app(reference(source, Role::ALL[31], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), var(2)), app(app(app(reference(source, Role::ALL[31], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), var(1)), var(0)))))))), lam(reference(source, Role::ALL[39], &[])?, lam(reference(source, Role::ALL[39], &[])?, lam(app(reference(source, Role::ALL[29], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), app(reference(source, Role::ALL[102], &[])?, app(reference(source, Role::ALL[38], &[])?, app(app(app(reference(source, Role::ALL[31], &[U::zero()])?, reference(source, Role::ALL[39], &[])?), var(1)), var(0))))))))) }] }, - _ => return Ok(None), - })) -} diff --git a/crates/kernel/src/role_admission/tests.rs b/crates/kernel/src/role_admission/tests.rs deleted file mode 100644 index 002970666..000000000 --- a/crates/kernel/src/role_admission/tests.rs +++ /dev/null @@ -1,543 +0,0 @@ -use super::*; -use ixon::constant::{Axiom, defn_proj_constant}; -use ixon::expr::Expr; -use ixon::univ::Univ; - -fn record(marker: u64, theorem: bool) -> Constant { - let info = if theorem { - ConstantInfo::Defn(Definition { - kind: DefKind::Theorem, - safety: DefinitionSafety::Safe, - lvls: marker, - typ: Arc::new(Expr::Sort(0)), - value: Arc::new(Expr::Var(0)), - }) - } else { - ConstantInfo::Axio(Axiom { - is_unsafe: false, - lvls: marker, - typ: Arc::new(Expr::Sort(0)), - }) - }; - let mut c = Constant::new(info); - c.univs.push(Arc::new(Univ::Zero)); - c -} - -fn store(env: &Env, c: Constant) -> Address { - let (address, bytes) = c.commit(); - env - .consts - .insert(address.clone(), LazyConstant::from_serialized(c, bytes, true)); - address -} - -fn bind(env: &mut Env, table: &RoleTable) { - let bytes = table.to_bytes().unwrap(); - env.role_binding = - Some(RoleBinding { address: Address::hash(&bytes), bytes: bytes.into() }); -} - -// Authentic, canonical records deliberately do NOT satisfy the role types or -// theorem propositions. Candidate collection cannot stand in for admission. -fn full_candidate() -> (Env, RoleTable) { - let mut env = Env::new(); - let bound: Vec<_> = Role::ALL - .into_iter() - .map(|role| (role, store(&env, record(u64::from(role.index()), false)))) - .collect(); - let proof = store(&env, record(100, true)); - let evidence = Role::ALL - .into_iter() - .filter_map(|role| { - let allowed = evidence_allowed(role); - (!allowed.is_empty()).then(|| { - ( - role, - allowed - .iter() - .map(|kind| match kind { - Some(false) => Evidence::Proof(proof.clone()), - _ => Evidence::DefEq, - }) - .collect(), - ) - }) - }) - .collect(); - let table = RoleTable { version: SPEC_VERSION, bound, evidence }; - bind(&mut env, &table); - (env, table) -} - -#[test] -fn candidates_preserve_exact_binding_without_admitting_role_types() { - let (env, table) = full_candidate(); - let raw = env.role_binding.clone().unwrap(); - let source = ArtifactSource::prepare(env).unwrap(); - let candidate = source.binding().unwrap(); - assert_eq!(candidate.bytes(), raw.bytes.as_ref()); - assert_eq!(candidate.address(), &raw.address); - for (role, address) in table.bound { - assert_eq!(candidate.required(role).unwrap(), &address); - assert!(matches!( - source.record(&address).unwrap().constant().info, - ConstantInfo::Axio(_) - )); - } - let proof = match &candidate.evidence(Role::NatDiv).unwrap()[0] { - Evidence::Proof(a) => a, - Evidence::DefEq => panic!("wrong evidence kind"), - }; - assert!(matches!( - source.proof_record(proof).unwrap().definition().unwrap().value.as_ref(), - Expr::Var(0) - )); -} - -#[test] -fn table_shape_matches_current_lean_specification_for_all_roles() { - let mut actual = format!( - "# Role.block/deps and Spec.evidenceAllowed, version{SPEC_VERSION}\n" - ); - let indices = |roles: &[Role]| { - roles.iter().map(|r| r.index().to_string()).collect::>().join(",") - }; - for role in Role::ALL { - let kinds = evidence_allowed(role) - .iter() - .map(|kind| match kind { - Some(true) => "defeq", - Some(false) => "proof", - None => "either", - }) - .collect::>() - .join(","); - actual.push_str(&format!( - "{}\t{}\t{}\t{kinds}\n", - role.index(), - indices(block(role)), - indices(dependencies(role)) - )); - } - assert_eq!( - actual, - include_str!("../../../../Tests/Fixtures/role-binding-v2/spec-shape.tsv") - ); -} - -#[test] -fn bound_empty_and_unbound_are_distinct_and_neither_fills_absent_roles() { - let mut env = Env::new(); - let table = - RoleTable { version: SPEC_VERSION, bound: vec![], evidence: vec![] }; - let unbound = ArtifactSource::snapshot(&env).unwrap(); - assert!(unbound.binding().is_none()); - assert!(matches!(unbound.required_role(Role::Nat), Err(Error::Unbound))); - bind(&mut env, &table); - let bound = ArtifactSource::prepare(env).unwrap(); - assert!(bound.binding().unwrap().role(Role::Nat).is_none()); - assert!(matches!( - bound.required_role(Role::Nat), - Err(Error::MissingRole(Role::Nat)) - )); -} - -#[test] -fn canonical_wire_tables_still_need_current_well_formedness() { - let (env, table) = full_candidate(); - let mut variants = vec![]; - let mut bad = table.clone(); - bad.version += 1; - variants.push(bad); - let mut bad = table.clone(); - bad.bound[1].1 = bad.bound[0].1.clone(); - variants.push(bad); - let mut bad = table.clone(); - bad.bound.retain(|(r, _)| *r != Role::NatRec); - variants.push(bad); - let mut bad = table.clone(); - bad.bound.retain(|(r, _)| *r != Role::Char); - variants.push(bad); - let mut bad = table.clone(); - bad.evidence.retain(|(r, _)| *r != Role::NatAdd); - variants.push(bad); - let mut bad = table.clone(); - bad.evidence.iter_mut().find(|(r, _)| *r == Role::NatDiv).unwrap().1 = - vec![Evidence::DefEq; 3]; - variants.push(bad); - let mut bad = table.clone(); - bad.evidence.iter_mut().find(|(r, _)| *r == Role::NatPred).unwrap().1.pop(); - variants.push(bad); - let mut bad = table.clone(); - bad.evidence.insert(0, (Role::Nat, vec![Evidence::DefEq])); - variants.push(bad); - let mut bad = table.clone(); - bad.bound.retain(|(r, _)| *r != Role::NatShiftLeft); - variants.push(bad); - for table in variants { - let mut input = env.clone(); - bind(&mut input, &table); - assert!( - RoleTable::from_bytes(&input.role_binding.as_ref().unwrap().bytes) - .is_ok() - ); - assert!(matches!(ArtifactSource::prepare(input), Err(Error::Binding(_)))); - } - let mut bad = env.clone(); - bad.role_binding.as_mut().unwrap().address = - Address::hash(b"wrong binding key"); - assert!(matches!(ArtifactSource::prepare(bad), Err(Error::Binding(_)))); - let mut bad = env; - let binding = bad.role_binding.as_mut().unwrap(); - let mut bytes = binding.bytes.to_vec(); - bytes.push(0); - binding.address = Address::hash(&bytes); - binding.bytes = bytes.into(); - assert!(matches!(ArtifactSource::prepare(bad), Err(Error::Binding(_)))); -} - -#[test] -fn missing_role_proof_and_owner_bytes_refuse_before_admission() { - let (env, _) = full_candidate(); - let role = env.role_binding.as_ref().unwrap(); - let table = RoleTable::from_bytes(&role.bytes).unwrap(); - let target = table.bound[0].1.clone(); - let missing = env.clone(); - missing.consts.remove(&target); - assert!(matches!( - ArtifactSource::prepare(missing), - Err(Error::MissingRecord(_)) - )); - let proof = match &table - .evidence - .iter() - .find(|(r, _)| *r == Role::NatDiv) - .unwrap() - .1[0] - { - Evidence::Proof(a) => a.clone(), - Evidence::DefEq => panic!("missing proof fixture"), - }; - let missing = env.clone(); - missing.consts.remove(&proof); - assert!(matches!( - ArtifactSource::prepare(missing), - Err(Error::MissingRecord(_)) - )); - let bad = env.clone(); - bad.consts.insert(target, LazyConstant::from_constant(record(900, false))); - assert!(matches!(ArtifactSource::prepare(bad), Err(Error::Record(_, _)))); - let mut bad_table = table; - for (_, entries) in &mut bad_table.evidence { - for e in entries { - if matches!(e, Evidence::Proof(_)) { - *e = Evidence::Proof(bad_table.bound[0].1.clone()); - } - } - } - let mut bad = env; - bind(&mut bad, &bad_table); - assert!(matches!(ArtifactSource::prepare(bad), Err(Error::NotTheorem(_)))); -} - -#[test] -fn every_transitive_record_and_blob_read_authenticates_its_own_bytes() { - let env = Env::new(); - let bad = Address::hash(b"forged record address"); - env.consts.insert(bad.clone(), LazyConstant::from_constant(record(0, false))); - let mut parent = record(1, false); - parent.refs.push(bad.clone()); - let root = store(&env, parent); - let bad_blob = Address::hash(b"forged blob address"); - env.blobs.insert(bad_blob.clone(), b"other bytes".to_vec()); - let source = ArtifactSource::prepare(env).unwrap(); - let parent = source.record(&root).unwrap(); - assert_eq!(parent.constant().refs.as_slice(), std::slice::from_ref(&bad)); - assert!(matches!(source.record(&bad), Err(Error::Record(_, _)))); - assert!(matches!(source.blob(&bad_blob), Err(Error::BlobHash(_)))); - let missing = Address::hash(b"not present"); - assert!(matches!(source.record(&missing), Err(Error::MissingRecord(_)))); - assert!(matches!(source.blob(&missing), Err(Error::MissingBlob(_)))); -} - -#[test] -fn snapshot_owns_maps_and_shares_raw_buffers_between_workers() { - let env = Env::new(); - let address = store(&env, record(0, false)); - let bytes = b"literal bytes".to_vec(); - let blob = Address::hash(&bytes); - env.blobs.insert(blob.clone(), bytes.clone()); - let source = ArtifactSource::snapshot(&env).unwrap(); - let worker = source.clone(); - let captured = source.record(&address).unwrap(); - env.consts.remove(&address); - env.blobs.insert(blob.clone(), vec![0]); - assert!(source.same_source(&worker)); - assert_eq!( - captured.bytes().as_ptr(), - worker.record(&address).unwrap().bytes().as_ptr() - ); - let left = source.blob(&blob).unwrap(); - let right = worker.blob(&blob).unwrap(); - assert_eq!(left.bytes(), bytes); - assert!(Arc::ptr_eq(&left.bytes, &right.bytes)); - assert!(!source.same_source(&ArtifactSource::prepare(Env::new()).unwrap())); -} - -#[test] -fn projected_proofs_keep_exact_record_owner_and_member_provenance() { - let env = Env::new(); - let ConstantInfo::Defn(theorem) = record(0, true).info else { - unreachable!() - }; - let mut block = - Constant::new(ConstantInfo::Muts(vec![MutConst::Defn(theorem)])); - block.univs.push(Arc::new(Univ::Zero)); - let owner = store(&env, block); - let projection = store(&env, defn_proj_constant(0, owner.clone())); - let source = ArtifactSource::snapshot(&env).unwrap(); - let proof = source.proof_record(&projection).unwrap(); - assert_eq!(proof.record().address(), &projection); - assert_eq!(proof.owner().unwrap().address(), &owner); - assert_eq!(proof.index(), Some(0)); - assert_eq!(proof.constructor_index(), None); - assert_eq!(Address::hash(proof.owner().unwrap().bytes()), owner); - let bad_projection = store(&env, defn_proj_constant(1, owner.clone())); - assert!(matches!( - ArtifactSource::snapshot(&env).unwrap().member(&bad_projection), - Err(Error::Member(_, _)) - )); - env.consts.remove(&owner); - assert!(matches!( - ArtifactSource::prepare(env).unwrap().member(&projection), - Err(Error::MissingRecord(_)) - )); - assert!(source.proof_record(&projection).is_ok()); -} - -#[test] -fn work_enumeration_decodes_each_wide_owner_a_bounded_number_of_times() { - let env = Env::new(); - let member = MutConst::Defn(Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: Arc::new(Expr::Sort(1)), - value: Arc::new(Expr::Sort(0)), - }); - let owner = store( - &env, - Constant::with_tables( - ConstantInfo::Muts(vec![member; 512]), - vec![], - vec![], - vec![Univ::zero(), Univ::succ(Univ::zero())], - ), - ); - for i in 0..512 { - store(&env, defn_proj_constant(i, owner.clone())); - } - let source = ArtifactSource::prepare(env).unwrap(); - let work = crate::anon_work::build_artifact_work(&source).unwrap(); - assert_eq!(work.len(), 1); - assert_eq!(work[0].targets().len(), 512); - // If a projection sorts before its owner, its layout is decoded first; - // scheduling the owner itself then needs one more payload decoding. - assert!( - source.0.owner_decodes.load(std::sync::atomic::Ordering::Relaxed) <= 2 - ); - let before = - source.0.owner_decodes.load(std::sync::atomic::Ordering::Relaxed); - let worker = source.clone(); - for target in work[0].targets() { - worker.authenticate_reference(target).unwrap(); - assert!(!worker.in_safety_tier(target)); - } - assert_eq!( - source.0.owner_decodes.load(std::sync::atomic::Ordering::Relaxed), - before - ); -} - -#[test] -fn cached_projection_validation_preserves_every_owner_shape_refusal() { - use ixon::constant::{ - Constructor, Inductive, Recursor, ctor_proj_constant, indc_proj_constant, - recr_proj_constant, - }; - let env = Env::new(); - let ConstantInfo::Defn(definition) = record(0, true).info else { - unreachable!() - }; - let ctor = |cidx| Constructor { - is_unsafe: false, - lvls: 0, - cidx, - params: 0, - fields: 0, - typ: Arc::new(Expr::Sort(0)), - }; - let owner = store( - &env, - Constant::with_tables( - ConstantInfo::Muts(vec![ - MutConst::Defn(definition), - MutConst::Indc(Inductive { - is_unsafe: true, - lvls: 0, - params: 0, - indices: 0, - typ: Arc::new(Expr::Sort(0)), - ctors: vec![ctor(0), ctor(7)], - }), - MutConst::Recr(Recursor { - k: false, - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - motives: 0, - minors: 0, - typ: Arc::new(Expr::Sort(0)), - rules: vec![], - }), - ]), - vec![], - vec![], - vec![Univ::zero()], - ), - ); - let other = store(&env, record(0, false)); - let mut with_tables = defn_proj_constant(0, owner.clone()); - with_tables.refs.push(other.clone()); - let cases = [ - (defn_proj_constant(0, owner.clone()), true), - (indc_proj_constant(1, owner.clone()), true), - (ctor_proj_constant(1, 0, owner.clone()), true), - (recr_proj_constant(2, owner.clone()), true), - (ctor_proj_constant(1, 1, owner.clone()), false), - (ctor_proj_constant(1, 2, owner.clone()), false), - (defn_proj_constant(1, owner.clone()), false), - (indc_proj_constant(0, owner.clone()), false), - (recr_proj_constant(3, owner.clone()), false), - (defn_proj_constant(u64::MAX, owner.clone()), false), - (defn_proj_constant(0, other), false), - (with_tables, false), - ] - .map(|(c, valid)| (store(&env, c), valid)); - let source = ArtifactSource::prepare(env).unwrap(); - // Warm the layout from authenticated bytes before trying bad projections. - source.authenticate_reference(&cases[0].0).unwrap(); - for (address, valid) in cases { - let payload = source.member(&address).map(|_| ()); - let cached = source.authenticate_reference(&address); - assert_eq!(payload, cached); - assert_eq!(cached.is_ok(), valid); - } - assert!( - source.in_safety_tier(&ixon::constant::ctor_proj_address(1, 0, &owner)) - ); -} - -#[test] -fn owner_layouts_require_authentic_canonical_bytes() { - let make_source = |owner: Address, raw: LazyConstant| { - let env = Env::new(); - env.consts.insert(owner.clone(), raw); - let projection = store(&env, defn_proj_constant(0, owner)); - (ArtifactSource::prepare(env).unwrap(), projection) - }; - let ConstantInfo::Defn(d) = record(0, true).info else { unreachable!() }; - let mut block = Constant::new(ConstantInfo::Muts(vec![MutConst::Defn(d)])); - block.univs.push(Univ::zero()); - let (_, mut bytes) = block.commit(); - let (source, projection) = make_source( - Address::hash(b"forged owner key"), - LazyConstant::from_constant(block), - ); - assert!(matches!( - source.authenticate_reference(&projection), - Err(Error::Record(_, _)) - )); - assert!(source.0.owner_layouts.is_empty()); - bytes.push(0); - let (source, projection) = - make_source(Address::hash(&bytes), LazyConstant::from_bytes(bytes.into())); - assert!(matches!( - source.authenticate_reference(&projection), - Err(Error::Record(_, _)) - )); - assert!(source.0.owner_layouts.is_empty()); -} - -#[test] -fn rehashed_trailing_record_bytes_are_not_canonical_records() { - let env = Env::new(); - let (_, mut bytes) = record(0, false).commit(); - bytes.push(0); - let address = Address::hash(&bytes); - env.consts.insert(address.clone(), LazyConstant::from_bytes(bytes.into())); - assert!(matches!( - ArtifactSource::prepare(env).unwrap().record(&address), - Err(Error::Record(_, _)) - )); -} - -// from_serialized deliberately debug-asserts cache consistency. Exercise the -// release boundary without weakening that assertion or adding a forge API. -#[cfg(not(debug_assertions))] -#[test] -fn materialized_cache_cannot_replace_authenticated_payload_in_release() { - let env = Env::new(); - for (cached, actual) in - [(record(0, true), record(1, false)), (record(0, false), record(1, true))] - { - let (address, bytes) = actual.commit(); - env.consts.insert( - address.clone(), - LazyConstant::from_serialized(cached.clone(), bytes.clone(), true), - ); - assert_eq!( - env.consts.get(&address).unwrap().get().unwrap().as_ref(), - &cached - ); - let source = ArtifactSource::snapshot(&env).unwrap(); - let observed = source.record(&address).unwrap(); - assert_eq!(observed.bytes(), bytes); - assert_eq!(observed.constant(), &actual); - assert_eq!( - source.proof_record(&address).is_ok(), - matches!(actual.info, ConstantInfo::Defn(_)) - ); - } -} - -#[test] -fn verdict_closure_includes_role_binding_support_and_evidence() { - let (mut env, mut table) = full_candidate(); - let helper_addr = store(&env, record(200, false)); - - let mut role_const = record(201, false); - role_const.refs.push(helper_addr.clone()); - let role_addr = store(&env, role_const); - table.bound.iter_mut().find(|(r, _)| *r == Role::Char).unwrap().1 = - role_addr.clone(); - bind(&mut env, &table); - - let target_addr = store(&env, record(202, false)); - - let source = ArtifactSource::snapshot(&env).unwrap(); - let closure = - source.verdict_closure(std::slice::from_ref(&target_addr)).unwrap(); - assert!(closure.contains(&target_addr)); - assert!(closure.contains(&role_addr)); - assert!(closure.contains(&helper_addr)); - for (_, entries) in &table.evidence { - for entry in entries { - if let Evidence::Proof(proof_addr) = entry { - assert!(closure.contains(proof_addr)); - } - } - } -} diff --git a/crates/kernel/src/subst.rs b/crates/kernel/src/subst.rs deleted file mode 100644 index 5b54cbdb4..000000000 --- a/crates/kernel/src/subst.rs +++ /dev/null @@ -1,1865 +0,0 @@ -//! Substitution and lifting for zero kernel expressions. -//! -//! All functions intern results through `InternTable` for pointer -//! deduplication. In addition, the traversal itself is memoized by -//! content hash for the duration of a single call — expressions are -//! content-addressed DAGs and the same sub-expression may appear many -//! times (well-founded-recursion unfolds, recursor rules with repeated -//! motives, etc.); without per-call memoization we re-walk every shared -//! occurrence, turning a DAG walk into a tree walk and blowing O(N) -//! sharing into O(2^k) work. Mirrors `lean4lean`'s `replaceM` which -//! 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}; -use super::expr::{ExprData, FVarId, KExpr}; -use super::mode::KernelMode; - -#[cfg(test)] -mod menv_tests; - -#[cfg(test)] -mod scratch_tests; - -/// When set, log every 100K `subst` (top-level) entries. Substitution is -/// called once per `App` in `infer` (plus other sites in whnf / def_eq), -/// and each call recursively rebuilds the body; a check that spends -/// seconds per infer call likely has substs dominating. The counter -/// only fires for the top-level `subst` entry, so recursive sub-calls -/// don't inflate the number. -static IX_SUBST_COUNT_LOG: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_SUBST_COUNT_LOG").is_ok()); - -static SUBST_COUNT: std::sync::atomic::AtomicUsize = - std::sync::atomic::AtomicUsize::new(0); - -/// Perform single substitution: `body[arg/Var(depth)]`. -/// -/// Replaces `Var(depth)` with `arg` (lifted by `depth`), shifts free -/// variables above `depth` down by 1. Uses `lbr()` for fast-path -/// skipping. The internal traversal is memoized by content hash so -/// shared sub-expressions within `body` are walked once per depth. -/// -/// Memoization scratch is borrowed from `env.subst_scratch` to avoid -/// allocating a fresh `FxHashMap` per call. `take_for_call` clears entries -/// and adaptively releases persistently sparse, oversized allocations. It -/// leaves an empty placeholder so the borrow checker lets us -/// thread `&mut env` and `&mut scratch` separately into `subst_cached`, -/// then put it back on the way out. `subst_cached` does not call back -/// into `subst`, so there is no risk of recursive scratch use. -pub fn subst( - env: &mut InternTable, - body: &KExpr, - arg: &KExpr, - depth: u64, -) -> KExpr { - 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 { - log::info!("[subst] count={n}"); - } - } - // Fast path: no loose bound vars at or below `depth` means nothing to - // substitute; returning the original Arc is cheap and cache-free. - if body.lbr() <= depth { - return body.clone(); - } - let mut cache = env.subst_scratch.take_for_call(); - let result = subst_cached(env, body, arg, depth, &mut cache); - env.subst_scratch.restore_after_call(cache); - result -} - -/// Substitution variant for short-lived WHNF intermediates. -/// -/// This deliberately does not use the global [`InternTable`]. It is intended -/// for reductions that may produce a long chain of distinct, never-reused -/// expressions, such as Nat literal recursor peeling. Interning those nodes -/// keeps every predecessor alive for the entire environment check. -pub fn subst_no_intern( - body: &KExpr, - arg: &KExpr, - depth: u64, -) -> KExpr { - if body.lbr() <= depth { - return body.clone(); - } - - match body.data() { - ExprData::Var(i, name, _) => { - let i = *i; - if i == depth { - lift_no_intern(arg, depth, 0) - } else if i > depth { - KExpr::var(i - 1, name.clone()) - } else { - body.clone() - } - }, - - ExprData::App(f, x, _) => { - let f2 = subst_no_intern(f, arg, depth); - let x2 = subst_no_intern(x, arg, depth); - KExpr::app(f2, x2) - }, - - ExprData::Lam(name, bi, contract, ty, inner, _) => { - let ty2 = subst_no_intern(ty, arg, depth); - let inner2 = subst_no_intern(inner, arg, depth + 1); - KExpr::lam_contract(name.clone(), bi.clone(), *contract, ty2, inner2) - }, - - ExprData::All(name, bi, contract, result, ty, inner, _) => { - let ty2 = subst_no_intern(ty, arg, depth); - let inner2 = subst_no_intern(inner, arg, depth + 1); - KExpr::all_contract( - name.clone(), - bi.clone(), - *contract, - *result, - ty2, - inner2, - ) - }, - - ExprData::Let(name, ty, val, inner, contract, _) => { - let ty2 = subst_no_intern(ty, arg, depth); - let val2 = subst_no_intern(val, arg, depth); - let inner2 = subst_no_intern(inner, arg, depth + 1); - KExpr::let_contract(name.clone(), *contract, ty2, val2, inner2) - }, - - ExprData::Prj(id, field, val, _) => { - let val2 = subst_no_intern(val, arg, depth); - KExpr::prj(id.clone(), *field, val2) - }, - - ExprData::FVar(..) - | ExprData::Sort(..) - | ExprData::Const(..) - | ExprData::Nat(..) - | ExprData::Str(..) => body.clone(), - } -} - -/// Inner recursive worker with memoization keyed by `(sub-expr addr, -/// depth)`. Depth enters the key because traversing under a binder -/// increments `depth`, and the substitution's semantics change: under -/// one extra binder, `Var(depth+1)` now targets the original -/// substitution site. Two subtrees with the same address but visited at -/// different depths must not share a result. -fn subst_cached( - env: &mut InternTable, - body: &KExpr, - arg: &KExpr, - depth: u64, - cache: &mut FxHashMap<(Addr, u64), KExpr>, -) -> KExpr { - if body.lbr() <= depth { - return body.clone(); - } - - // Pointer-identity cache: expressions are content-addressed, so two - // sub-trees with the same `addr()` are structurally equal, meaning - // `subst` at the same `depth` must produce the same result. Skipping - // re-traversal here is the whole point of the cache — for Lean bodies - // with significant sub-term sharing it turns an O(tree-size) walk - // into O(dag-size). - let key = (body.hash_key(), depth); - if let Some(cached) = cache.get(&key) { - return cached.clone(); - } - - let result = match body.data() { - ExprData::Var(i, name, _) => { - let i = *i; - if i == depth { - lift(env, arg, depth, 0) - } else if i > depth { - KExpr::var(i - 1, name.clone()) - } else { - // Unreachable under the outer `lbr() <= depth` guard (Var below - // `depth` is bound, so its lbr is below depth and we'd have - // returned early), but keep the explicit branch for clarity. - let r = body.clone(); - cache.insert(key, r.clone()); - return r; - } - }, - - ExprData::App(f, x, _) => { - let f2 = subst_cached(env, f, arg, depth, cache); - let x2 = subst_cached(env, x, arg, depth, cache); - let r = env.intern_app(&f2, &x2); - cache.insert(key, r.clone()); - return r; - }, - - ExprData::Lam(name, bi, contract, ty, inner, _) => { - let ty2 = subst_cached(env, ty, arg, depth, cache); - let inner2 = subst_cached(env, inner, arg, depth + 1, cache); - KExpr::lam_contract(name.clone(), bi.clone(), *contract, ty2, inner2) - }, - - ExprData::All(name, bi, contract, result, ty, inner, _) => { - let ty2 = subst_cached(env, ty, arg, depth, cache); - let inner2 = subst_cached(env, inner, arg, depth + 1, cache); - let r = env.intern_all_contract( - name.clone(), - bi.clone(), - *contract, - *result, - &ty2, - &inner2, - ); - cache.insert(key, r.clone()); - return r; - }, - - ExprData::Let(name, ty, val, inner, contract, _) => { - let ty2 = subst_cached(env, ty, arg, depth, cache); - let val2 = subst_cached(env, val, arg, depth, cache); - let inner2 = subst_cached(env, inner, arg, depth + 1, cache); - KExpr::let_contract(name.clone(), *contract, ty2, val2, inner2) - }, - - ExprData::Prj(id, field, val, _) => { - let val2 = subst_cached(env, val, arg, depth, cache); - KExpr::prj(id.clone(), *field, val2) - }, - - ExprData::FVar(..) - | ExprData::Sort(..) - | ExprData::Const(..) - | ExprData::Nat(..) - | ExprData::Str(..) => { - // Closed atoms — the outer `lbr() <= depth` guard should have - // caught these, so this arm is defensive. FVars carry no loose - // bound variables (lbr=0) so they always pass through unchanged. - // Cache to stay consistent with other branches. - let r = body.clone(); - cache.insert(key, r.clone()); - return r; - }, - }; - - let interned = env.intern_expr(result); - cache.insert(key, interned.clone()); - interned -} - -/// Perform simultaneous substitution: replace `Var(depth)..Var(depth+n-1)` -/// with `substs[0]..substs[n-1]`, shifting free variables above by `-n`. -/// -/// Uses the same per-call pointer-identity memoization as `subst` so -/// shared sub-expressions are traversed once per depth level (see the -/// module-level docs). -pub fn simul_subst( - env: &mut InternTable, - body: &KExpr, - substs: &[KExpr], - depth: u64, -) -> KExpr { - if body.lbr() <= depth { - return body.clone(); - } - // See `subst` for the take/restore pattern. `simul_subst_cached` - // does not call into `subst`/`simul_subst`, so it is safe to share the - // single `subst_scratch` between them. - let mut cache = env.subst_scratch.take_for_call(); - let result = simul_subst_cached(env, body, substs, depth, &mut cache); - env.subst_scratch.restore_after_call(cache); - result -} - -fn simul_subst_cached( - env: &mut InternTable, - body: &KExpr, - substs: &[KExpr], - depth: u64, - cache: &mut FxHashMap<(Addr, u64), KExpr>, -) -> KExpr { - if body.lbr() <= depth { - return body.clone(); - } - - let key = (body.hash_key(), depth); - if let Some(cached) = cache.get(&key) { - return cached.clone(); - } - - let n = substs.len() as u64; - - let result = match body.data() { - ExprData::Var(i, _, _) => { - let i = *i; - if i >= depth && i < depth + n { - #[allow(clippy::cast_possible_truncation)] - // guarded: i < depth + substs.len() - let r = lift(env, &substs[(i - depth) as usize], depth, 0); - cache.insert(key, r.clone()); - return r; - } else if i >= depth + n { - KExpr::var(i - n, M::meta_field(ix_common::env::Name::anon())) - } else { - let r = body.clone(); - cache.insert(key, r.clone()); - return r; - } - }, - - ExprData::App(f, x, _) => { - let f2 = simul_subst_cached(env, f, substs, depth, cache); - let x2 = simul_subst_cached(env, x, substs, depth, cache); - let r = env.intern_app(&f2, &x2); - cache.insert(key, r.clone()); - return r; - }, - - ExprData::Lam(name, bi, contract, ty, inner, _) => { - let ty2 = simul_subst_cached(env, ty, substs, depth, cache); - let inner2 = simul_subst_cached(env, inner, substs, depth + 1, cache); - KExpr::lam_contract(name.clone(), bi.clone(), *contract, ty2, inner2) - }, - - ExprData::All(name, bi, contract, result, ty, inner, _) => { - let ty2 = simul_subst_cached(env, ty, substs, depth, cache); - let inner2 = simul_subst_cached(env, inner, substs, depth + 1, cache); - let r = env.intern_all_contract( - name.clone(), - bi.clone(), - *contract, - *result, - &ty2, - &inner2, - ); - cache.insert(key, r.clone()); - return r; - }, - - ExprData::Let(name, ty, val, inner, contract, _) => { - let ty2 = simul_subst_cached(env, ty, substs, depth, cache); - let val2 = simul_subst_cached(env, val, substs, depth, cache); - let inner2 = simul_subst_cached(env, inner, substs, depth + 1, cache); - KExpr::let_contract(name.clone(), *contract, ty2, val2, inner2) - }, - - ExprData::Prj(id, field, val, _) => { - let val2 = simul_subst_cached(env, val, substs, depth, cache); - KExpr::prj(id.clone(), *field, val2) - }, - - ExprData::FVar(..) - | ExprData::Sort(..) - | ExprData::Const(..) - | ExprData::Nat(..) - | ExprData::Str(..) => { - let r = body.clone(); - cache.insert(key, r.clone()); - return r; - }, - }; - - let interned = env.intern_expr(result); - cache.insert(key, interned.clone()); - interned -} - -/// Shift free de Bruijn indices ≥ `cutoff` up by `shift`. -/// -/// Used when substituting an argument into a deeper context. Like -/// `subst`, memoizes by content hash within a single call so shared -/// sub-expressions are walked once per cutoff level. -pub fn lift( - env: &mut InternTable, - e: &KExpr, - shift: u64, - cutoff: u64, -) -> KExpr { - if shift == 0 || e.lbr() <= cutoff { - return e.clone(); - } - // Borrow the dedicated `lift_scratch`. `lift` is invoked from inside - // `subst_cached`, which already holds `subst_scratch`; using a separate - // buffer keeps both available simultaneously. `lift_cached` does not - // call back into `lift`/`subst`/`simul_subst`, so the scratch is safe - // to share across calls without nested-borrow risk. - let mut cache = env.lift_scratch.take_for_call(); - let result = lift_cached(env, e, shift, cutoff, &mut cache); - env.lift_scratch.restore_after_call(cache); - result -} - -fn lift_no_intern( - e: &KExpr, - shift: u64, - cutoff: u64, -) -> KExpr { - if shift == 0 || e.lbr() <= cutoff { - return e.clone(); - } - - match e.data() { - ExprData::Var(i, name, _) => { - let i = *i; - if i >= cutoff { KExpr::var(i + shift, name.clone()) } else { e.clone() } - }, - - ExprData::App(f, x, _) => { - let f2 = lift_no_intern(f, shift, cutoff); - let x2 = lift_no_intern(x, shift, cutoff); - KExpr::app(f2, x2) - }, - - ExprData::Lam(name, bi, contract, ty, body, _) => { - let ty2 = lift_no_intern(ty, shift, cutoff); - let body2 = lift_no_intern(body, shift, cutoff + 1); - KExpr::lam_contract(name.clone(), bi.clone(), *contract, ty2, body2) - }, - - ExprData::All(name, bi, contract, result, ty, body, _) => { - let ty2 = lift_no_intern(ty, shift, cutoff); - let body2 = lift_no_intern(body, shift, cutoff + 1); - KExpr::all_contract( - name.clone(), - bi.clone(), - *contract, - *result, - ty2, - body2, - ) - }, - - ExprData::Let(name, ty, val, body, contract, _) => { - let ty2 = lift_no_intern(ty, shift, cutoff); - let val2 = lift_no_intern(val, shift, cutoff); - let body2 = lift_no_intern(body, shift, cutoff + 1); - KExpr::let_contract(name.clone(), *contract, ty2, val2, body2) - }, - - ExprData::Prj(id, field, val, _) => { - let val2 = lift_no_intern(val, shift, cutoff); - KExpr::prj(id.clone(), *field, val2) - }, - - ExprData::FVar(..) - | ExprData::Sort(..) - | ExprData::Const(..) - | ExprData::Nat(..) - | ExprData::Str(..) => e.clone(), - } -} - -fn lift_cached( - env: &mut InternTable, - e: &KExpr, - shift: u64, - cutoff: u64, - cache: &mut FxHashMap<(Addr, u64), KExpr>, -) -> KExpr { - if shift == 0 || e.lbr() <= cutoff { - return e.clone(); - } - - // `shift` is fixed across a single call, so only `(addr, cutoff)` is - // needed to identify a unique traversal result. - let key = (e.hash_key(), cutoff); - if let Some(cached) = cache.get(&key) { - return cached.clone(); - } - - let result = match e.data() { - ExprData::Var(i, name, _) => { - let i = *i; - if i >= cutoff { - KExpr::var(i + shift, name.clone()) - } else { - let r = e.clone(); - cache.insert(key, r.clone()); - return r; - } - }, - - ExprData::App(f, x, _) => { - let f2 = lift_cached(env, f, shift, cutoff, cache); - let x2 = lift_cached(env, x, shift, cutoff, cache); - let r = env.intern_app(&f2, &x2); - cache.insert(key, r.clone()); - return r; - }, - - ExprData::Lam(name, bi, contract, ty, body, _) => { - let ty2 = lift_cached(env, ty, shift, cutoff, cache); - let body2 = lift_cached(env, body, shift, cutoff + 1, cache); - KExpr::lam_contract(name.clone(), bi.clone(), *contract, ty2, body2) - }, - - ExprData::All(name, bi, contract, result, ty, body, _) => { - let ty2 = lift_cached(env, ty, shift, cutoff, cache); - let body2 = lift_cached(env, body, shift, cutoff + 1, cache); - let r = env.intern_all_contract( - name.clone(), - bi.clone(), - *contract, - *result, - &ty2, - &body2, - ); - cache.insert(key, r.clone()); - return r; - }, - - ExprData::Let(name, ty, val, body, contract, _) => { - let ty2 = lift_cached(env, ty, shift, cutoff, cache); - let val2 = lift_cached(env, val, shift, cutoff, cache); - let body2 = lift_cached(env, body, shift, cutoff + 1, cache); - KExpr::let_contract(name.clone(), *contract, ty2, val2, body2) - }, - - ExprData::Prj(id, field, val, _) => { - let val2 = lift_cached(env, val, shift, cutoff, cache); - KExpr::prj(id.clone(), *field, val2) - }, - - ExprData::FVar(..) - | ExprData::Sort(..) - | ExprData::Const(..) - | ExprData::Nat(..) - | ExprData::Str(..) => { - let r = e.clone(); - cache.insert(key, r.clone()); - return r; - }, - }; - - let interned = env.intern_expr(result); - cache.insert(key, interned.clone()); - 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: Option>>, - /// The ancestor `jump_len` ordinary tail steps away, or `None` when - /// those steps reach the empty environment. Spans have size 2^k - 1. - jump: Option>>, - jump_len: u64, -} - -/// Persistent skew-binary random-access environment: O(1) push and -/// O(log n) lookup, with the most recent binding still O(1). -/// -/// Ordinary tails retain the original cons-list meaning. A jump skips a -/// complete preorder block of size 2^k - 1; following jumps partitions the -/// list into increasing blocks, with only the first two allowed equal. -/// Prepending merges two equal first blocks with the new head, or adds a -/// singleton block. Both cases need one node and constant work, even when -/// branching from an old snapshot. Lookup skips whole blocks or takes an -/// ordinary tail to descend into one, without materializing any closures. -/// -/// `len` lives on the handle, not on each tail. Compared with the original -/// cons node this adds one pointer-sized field on 64-bit hosts, rather than -/// a separate allocation or a logarithmic jump table at every binder. -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 { - let len = self.len.checked_add(1).expect("MEnv length overflow"); - let (jump, jump_len) = if let Some(first) = &self.node - && let Some(second) = &first.jump - && first.jump_len == second.jump_len - { - // New head + two equal blocks. The span cannot overflow: both - // blocks belong to `self`, whose length was checked above. - (second.jump.clone(), 1 + 2 * first.jump_len) - } else { - (self.node.clone(), 1) - }; - MEnv { - node: Some(Arc::new(MEnvNode { - head: c, - tail: self.node.clone(), - jump, - jump_len, - })), - len, - } - } - - /// Return the same closure as `i` ordinary tail steps, in O(log n). - /// `i` must be `< self.len()`; no closure is forced or copied here. - pub(crate) fn get(&self, i: u64) -> &Arc> { - let mut node = self.node.as_ref().expect("MEnv::get out of range"); - if i == 0 { - return &node.head; - } - let mut i = i; - // Recent bindings are common. Once the remaining offset is tiny, - // ordinary tails avoid a jump-size branch at every visited node. - while i >= 8 { - if node.jump_len <= i { - i -= node.jump_len; - node = node.jump.as_ref().expect("MEnv::get out of range"); - } else { - i -= 1; - node = node.tail.as_ref().expect("MEnv::get out of range"); - } - } - while i > 0 { - i -= 1; - node = node.tail.as_ref().expect("MEnv::get out of range"); - } - &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); - let r = intern.intern_app(&f2, &x2); - cache.insert(key, r.clone()); - return r; - }, - - ExprData::Lam(name, bi, contract, 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_contract(name.clone(), bi.clone(), *contract, ty2, inner2) - }, - - ExprData::All(name, bi, contract, result, ty, inner, _) => { - let ty2 = clo_subst_cached(intern, ty, env, depth, cache); - let inner2 = clo_subst_cached(intern, inner, env, depth + 1, cache); - let r = intern.intern_all_contract( - name.clone(), - bi.clone(), - *contract, - *result, - &ty2, - &inner2, - ); - cache.insert(key, r.clone()); - return r; - }, - - ExprData::Let(name, ty, val, inner, contract, _) => { - 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_contract(name.clone(), *contract, ty2, val2, inner2) - }, - - 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. -/// -/// Mirrors `lean4lean`'s `Expr.cheapBetaReduce` -/// (refs/lean4lean/Lean4Lean/Instantiate.lean:8-27) and the C++ kernel's -/// `cheap_beta_reduce` (refs/lean4/src/kernel/instantiate.cpp:211). -/// -/// For a spine `App(λx_0 ... λx_{n-1}. body, a_0, ..., a_{m-1})` we peel -/// `i = min(n, m)` lambdas. After peeling: -/// - **Closed body**: if `body.lbr() == 0`, no var refers to the peeled -/// binders or anything outside; rebuild `body @ a_i .. a_{m-1}`. -/// - **Single bvar body**: if `body` is `Var(k)` with `k < i`, the body -/// just selects one of the peeled args. Pick `a_{i-k-1}` and apply the -/// remaining args. -/// - Otherwise: defer to full WHNF; return the input unchanged. -/// -/// Used by `inferLambda` / `inferLet` (and equivalents) to clean up -/// redexes that arise when an inferred type has the form -/// `App(λ_. T, x)` — common when motives or `id`-like applications -/// appear in the body's type. Returning a redex-free form here saves -/// downstream `is_def_eq` and `whnf` from instantiating-then-reducing. -pub fn cheap_beta_reduce( - env: &mut InternTable, - e: &KExpr, -) -> KExpr { - // Only Apps can be redexes. - if !matches!(e.data(), ExprData::App(..)) { - return e.clone(); - } - - // Collect the spine. Mirrors `tc::collect_app_spine` but inlined to - // avoid a circular `tc` ↔ `subst` dependency. - let mut count = 0usize; - { - let mut cur = e; - while let ExprData::App(f, _, _) = cur.data() { - count += 1; - cur = f; - } - } - if count == 0 { - return e.clone(); - } - let mut args: Vec> = Vec::with_capacity(count); - let mut head = e.clone(); - while let ExprData::App(f, a, _) = head.data() { - args.push(a.clone()); - head = f.clone(); - } - args.reverse(); - - // Quick exit: head must be a lambda for any peeling to fire. - if !matches!(head.data(), ExprData::Lam(..)) { - return e.clone(); - } - - // Peel up to `args.len()` lambdas, advancing `head` to the body. - let mut i: usize = 0; - while i < args.len() { - if let ExprData::Lam(_, _, _, _, inner, _) = head.data() { - let inner = inner.clone(); - head = inner; - i += 1; - } else { - break; - } - } - - // Case A: body has no free var references. Safe to drop the peeled - // binders; rebuild App with remaining args. - if head.lbr() == 0 { - let mut result = head; - for arg in &args[i..] { - result = env.intern_app(&result, arg); - } - return result; - } - - // Case B: body is a single Var(k) referring to one of the peeled - // binders (k < i). The peeled lambdas were applied in spine order, so - // `Var(0)` is the innermost (last peeled, took `args[i-1]`) and - // `Var(k)` is `args[i-k-1]`. - if let ExprData::Var(k, _, _) = head.data() { - let k = *k; - if k < i as u64 { - #[allow(clippy::cast_possible_truncation)] - let chosen_idx = i - (k as usize) - 1; - let mut result = args[chosen_idx].clone(); - for arg in &args[i..] { - result = env.intern_app(&result, arg); - } - return result; - } - } - - // Otherwise the redex needs a real substitution; let WHNF handle it. - e.clone() -} - -/// Instantiate the outermost `n = fvars.len()` loose bound variables in -/// `body` by the corresponding fvars, in reverse order (mirrors -/// `Lean.Expr.instantiateRev` and the C++ kernel's `instantiate_rev`). -/// -/// For an opened binder body where `Var(0)` is the innermost bound and -/// `Var(n-1)` the outermost, calling `instantiate_rev(body, [fv_0, .., -/// fv_{n-1}])` replaces `Var(0) → fv_{n-1}`, ..., `Var(n-1) → fv_0`. Free -/// variables `Var(k)` with `k >= n` shift **down by `n`** because the -/// surrounding `n` binders have been opened and consumed. -/// -/// The argument array `fvars` must contain `KExpr`s whose `ExprData` is -/// `FVar(..)`. The function does not enforce this — the lambda head check -/// is the caller's responsibility — but the substitution is only sound -/// when every replacement is fvar-shaped (closed, lbr=0). Other shapes -/// would need their own lifting under each binder, which is what -/// [`simul_subst`] does. -/// -/// Fast path: returns `body` unchanged when `body.lbr() == 0` (the body -/// has no loose bvars to instantiate). -pub fn instantiate_rev( - env: &mut InternTable, - body: &KExpr, - fvars: &[KExpr], -) -> KExpr { - if fvars.is_empty() || body.lbr() == 0 { - return body.clone(); - } - // Borrow the dedicated `subst_scratch` (same allocation reuse trick as - // `subst`/`simul_subst`). `instantiate_rev_cached` does not call back - // into subst/simul_subst/lift, so the scratch is safe to share across - // top-level calls without nested-borrow risk. - let mut cache = env.subst_scratch.take_for_call(); - let result = instantiate_rev_cached(env, body, fvars, 0, &mut cache); - env.subst_scratch.restore_after_call(cache); - result -} - -fn instantiate_rev_cached( - env: &mut InternTable, - body: &KExpr, - fvars: &[KExpr], - depth: u64, - cache: &mut FxHashMap<(Addr, u64), KExpr>, -) -> KExpr { - // Profiler: count every substitution-node visit — the work-volume feature - // that `heartbeats` (step count) misses. Records out of circuit; the bump - // compiles out on the zkvm target. - crate::profile::bump_subst_nodes(); - // No loose bvars at or below `depth` means nothing to instantiate at - // this subtree. - if body.lbr() <= depth { - return body.clone(); - } - - let key = (body.hash_key(), depth); - if let Some(cached) = cache.get(&key) { - return cached.clone(); - } - - let n = fvars.len() as u64; - - let result = match body.data() { - ExprData::Var(i, _, _) => { - let i = *i; - if i >= depth && i < depth + n { - // `Var(depth)` corresponds to the innermost peeled binder, which - // matches `fvars[n-1]` (last element). `Var(depth + n - 1)` is - // the outermost, matching `fvars[0]`. - #[allow(clippy::cast_possible_truncation)] - let idx = (n - 1 - (i - depth)) as usize; - let r = fvars[idx].clone(); - cache.insert(key, r.clone()); - return r; - } else if i >= depth + n { - // Free variable above the instantiated range: shift down by `n`. - KExpr::var(i - n, M::meta_field(ix_common::env::Name::anon())) - } else { - // i < depth: bound by an inner binder we walked under; unchanged. - let r = body.clone(); - cache.insert(key, r.clone()); - return r; - } - }, - - ExprData::App(f, x, _) => { - let f2 = instantiate_rev_cached(env, f, fvars, depth, cache); - let x2 = instantiate_rev_cached(env, x, fvars, depth, cache); - let r = env.intern_app(&f2, &x2); - cache.insert(key, r.clone()); - return r; - }, - - ExprData::Lam(name, bi, contract, ty, inner, _) => { - let ty2 = instantiate_rev_cached(env, ty, fvars, depth, cache); - let inner2 = instantiate_rev_cached(env, inner, fvars, depth + 1, cache); - KExpr::lam_contract(name.clone(), bi.clone(), *contract, ty2, inner2) - }, - - ExprData::All(name, bi, contract, result, ty, inner, _) => { - let ty2 = instantiate_rev_cached(env, ty, fvars, depth, cache); - let inner2 = instantiate_rev_cached(env, inner, fvars, depth + 1, cache); - let r = env.intern_all_contract( - name.clone(), - bi.clone(), - *contract, - *result, - &ty2, - &inner2, - ); - cache.insert(key, r.clone()); - return r; - }, - - ExprData::Let(name, ty, val, inner, contract, _) => { - let ty2 = instantiate_rev_cached(env, ty, fvars, depth, cache); - let val2 = instantiate_rev_cached(env, val, fvars, depth, cache); - let inner2 = instantiate_rev_cached(env, inner, fvars, depth + 1, cache); - KExpr::let_contract(name.clone(), *contract, ty2, val2, inner2) - }, - - ExprData::Prj(id, field, val, _) => { - let val2 = instantiate_rev_cached(env, val, fvars, depth, cache); - KExpr::prj(id.clone(), *field, val2) - }, - - ExprData::FVar(..) - | ExprData::Sort(..) - | ExprData::Const(..) - | ExprData::Nat(..) - | ExprData::Str(..) => { - let r = body.clone(); - cache.insert(key, r.clone()); - return r; - }, - }; - - let interned = env.intern_expr(result); - cache.insert(key, interned.clone()); - interned -} - -/// Inverse of [`instantiate_rev`]: replace each occurrence of the listed -/// fvars in `body` with the appropriate `Var(level)` and shift other -/// loose bvars upward by `n` so the result is closed under `n` new -/// binders. `fvars[0]` becomes `Var(n - 1 + depth)` (outermost), `fvars[n-1]` -/// becomes `Var(depth)` (innermost). -/// -/// Used by `LocalContext::mk_lambda` / `mk_pi` to close a body back into -/// a chain of de Bruijn binders after binder opening. -/// -/// Fast path: returns `body` unchanged when there are no target fvars, or -/// when `body` has neither fvars nor loose bvars. A no-fvar term with loose -/// bvars must still be traversed: wrapping new binders shifts those bvars. -pub fn abstract_fvars( - env: &mut InternTable, - body: &KExpr, - fvars: &[FVarId], -) -> KExpr { - if fvars.is_empty() || (!body.has_fvars() && body.lbr() == 0) { - return body.clone(); - } - // Build a position map for O(1) fvar → position lookup. For typical - // usage (n ≤ 16), a linear scan would also be fine, but the map keeps - // the cost predictable for inductive validation paths that abstract - // larger fvar sets. - let mut pos: FxHashMap = FxHashMap::default(); - pos.reserve(fvars.len()); - for (i, fv) in fvars.iter().enumerate() { - // Innermost (last) gets position 0; outermost (first) gets position - // `n - 1`, matching the `instantiate_rev` convention. - pos.insert(*fv, (fvars.len() - 1 - i) as u64); - } - - let mut cache = env.subst_scratch.take_for_call(); - let n = fvars.len() as u64; - let result = abstract_fvars_cached(env, body, &pos, n, 0, &mut cache); - env.subst_scratch.restore_after_call(cache); - result -} - -fn abstract_fvars_cached( - env: &mut InternTable, - body: &KExpr, - pos: &FxHashMap, - n: u64, - depth: u64, - cache: &mut FxHashMap<(Addr, u64), KExpr>, -) -> KExpr { - // If this subtree has neither fvars nor loose bvars >= depth, nothing - // changes. (Loose bvars below `depth` are bound by enclosing binders we - // walked under, so they are unaffected.) - if !body.has_fvars() && body.lbr() <= depth { - return body.clone(); - } - - let key = (body.hash_key(), depth); - if let Some(cached) = cache.get(&key) { - return cached.clone(); - } - - let result = match body.data() { - ExprData::FVar(id, _, _) => { - // Replace target fvars with Var(level). Other fvars are leaves and - // pass through unchanged (they belong to outer abstractions). - if let Some(&p) = pos.get(id) { - let new_var = - KExpr::var(depth + p, M::meta_field(ix_common::env::Name::anon())); - let interned = env.intern_expr(new_var); - cache.insert(key, interned.clone()); - return interned; - } - let r = body.clone(); - cache.insert(key, r.clone()); - return r; - }, - - ExprData::Var(i, name, _) => { - let i = *i; - // Loose bvars at or above `depth` shift up by `n` because we are - // wrapping the body in `n` new binders. - if i >= depth { - KExpr::var(i + n, name.clone()) - } else { - let r = body.clone(); - cache.insert(key, r.clone()); - return r; - } - }, - - ExprData::App(f, x, _) => { - let f2 = abstract_fvars_cached(env, f, pos, n, depth, cache); - let x2 = abstract_fvars_cached(env, x, pos, n, depth, cache); - let r = env.intern_app(&f2, &x2); - cache.insert(key, r.clone()); - return r; - }, - - ExprData::Lam(name, bi, contract, ty, inner, _) => { - let ty2 = abstract_fvars_cached(env, ty, pos, n, depth, cache); - let inner2 = abstract_fvars_cached(env, inner, pos, n, depth + 1, cache); - KExpr::lam_contract(name.clone(), bi.clone(), *contract, ty2, inner2) - }, - - ExprData::All(name, bi, contract, result, ty, inner, _) => { - let ty2 = abstract_fvars_cached(env, ty, pos, n, depth, cache); - let inner2 = abstract_fvars_cached(env, inner, pos, n, depth + 1, cache); - let r = env.intern_all_contract( - name.clone(), - bi.clone(), - *contract, - *result, - &ty2, - &inner2, - ); - cache.insert(key, r.clone()); - return r; - }, - - ExprData::Let(name, ty, val, inner, contract, _) => { - let ty2 = abstract_fvars_cached(env, ty, pos, n, depth, cache); - let val2 = abstract_fvars_cached(env, val, pos, n, depth, cache); - let inner2 = abstract_fvars_cached(env, inner, pos, n, depth + 1, cache); - KExpr::let_contract(name.clone(), *contract, ty2, val2, inner2) - }, - - ExprData::Prj(id, field, val, _) => { - let val2 = abstract_fvars_cached(env, val, pos, n, depth, cache); - KExpr::prj(id.clone(), *field, val2) - }, - - ExprData::Sort(..) - | ExprData::Const(..) - | ExprData::Nat(..) - | ExprData::Str(..) => { - let r = body.clone(); - cache.insert(key, r.clone()); - return r; - }, - }; - - let interned = env.intern_expr(result); - cache.insert(key, interned.clone()); - interned -} - -// Internal helper used only by the property tests: allow `ExprData` → -// `KExpr` reconstruction for re-interning in determinism check. -#[cfg(test)] -impl ExprData { - fn into_kexpr(self) -> KExpr { - match self { - ExprData::Var(i, name, _) => KExpr::var(i, name), - ExprData::Sort(u, _) => KExpr::sort(u), - ExprData::Const(id, us, _) => KExpr::cnst(id, us), - ExprData::App(f, a, _) => KExpr::app(f, a), - ExprData::Lam(n, bi, contract, ty, body, _) => { - KExpr::lam_contract(n, bi, contract, ty, body) - }, - ExprData::All(n, bi, contract, result, ty, body, _) => { - KExpr::all_contract(n, bi, contract, result, ty, body) - }, - ExprData::Let(n, ty, val, body, contract, _) => { - KExpr::let_contract(n, contract, ty, val, body) - }, - ExprData::Prj(id, idx, val, _) => KExpr::prj(id, idx, val), - ExprData::Nat(n, addr, _) => KExpr::nat(n, addr), - ExprData::Str(s, addr, _) => KExpr::str(s, addr), - ExprData::FVar(id, name, _) => KExpr::fvar(id, name), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::id::KId; - use crate::level::KUniv; - use crate::mode::Anon; - use bignat::Nat; - use ix_common::address::Address; - - type AE = KExpr; - - fn mk_addr(s: &str) -> Address { - Address::hash(s.as_bytes()) - } - - #[test] - fn subst_var_0() { - let mut env = InternTable::::new(); - let v0 = AE::var(0, ()); - let arg = AE::nat(Nat::from(3u64), mk_addr("3")); - let result = subst(&mut env, &v0, &arg, 0); - assert_eq!(result, arg); - } - - #[test] - fn subst_closed_skip() { - let mut env = InternTable::::new(); - let nat = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let arg = AE::nat(Nat::from(3u64), mk_addr("3")); - let result = subst(&mut env, &nat, &arg, 0); - assert!(result.ptr_eq(&nat)); - } - - #[test] - fn subst_free_var_shift() { - let mut env = InternTable::::new(); - let v1 = AE::var(1, ()); - let arg = AE::nat(Nat::from(3u64), mk_addr("3")); - let result = subst(&mut env, &v1, &arg, 0); - assert_eq!(result, AE::var(0, ())); - } - - #[test] - fn subst_app() { - let mut env = InternTable::::new(); - let c = AE::cnst(KId::new(mk_addr("f"), ()), Box::new([])); - let v0 = AE::var(0, ()); - let app = AE::app(c.clone(), v0); - let arg = AE::nat(Nat::from(3u64), mk_addr("3")); - let result = subst(&mut env, &app, &arg, 0); - let expected = AE::app(c, arg); - assert_eq!(result, expected); - } - - #[test] - fn subst_under_lambda() { - let mut env = InternTable::::new(); - let nat = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let v1 = AE::var(1, ()); - // λ(_:Nat). Var(1) — body references outer variable - let lam = AE::lam((), (), nat.clone(), v1); - let arg = AE::nat(Nat::from(3u64), mk_addr("3")); - let result = subst(&mut env, &lam, &arg, 0); - // Result: λ(_:Nat). 3 - let expected = AE::lam((), (), nat, arg); - assert_eq!(result, expected); - } - - #[test] - fn subst_bound_var_unchanged() { - let mut env = InternTable::::new(); - let nat = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let v0 = AE::var(0, ()); - // λ(_:Nat). Var(0) — body is lambda-bound, closed under binder - let lam = AE::lam((), (), nat, v0); - let arg = AE::nat(Nat::from(3u64), mk_addr("3")); - let result = subst(&mut env, &lam, &arg, 0); - assert!(result.ptr_eq(&lam)); - } - - #[test] - fn lift_var() { - let mut env = InternTable::::new(); - let v0 = AE::var(0, ()); - // lift(Var(0), shift=1, cutoff=0) → Var(1) - let result = lift(&mut env, &v0, 1, 0); - assert_eq!(result, AE::var(1, ())); - // lift(Var(0), shift=1, cutoff=1) → Var(0) (below cutoff) - let result2 = lift(&mut env, &v0, 1, 1); - assert!(result2.ptr_eq(&v0)); - } - - #[test] - fn lift_zero_shift() { - let mut env = InternTable::::new(); - let v0 = AE::var(0, ()); - let result = lift(&mut env, &v0, 0, 0); - assert!(result.ptr_eq(&v0)); - } - - // ---- instantiate_rev ---- - - #[test] - fn instantiate_rev_empty_passthrough() { - let mut env = InternTable::::new(); - let v0 = AE::var(0, ()); - let result = instantiate_rev(&mut env, &v0, &[]); - assert!(result.ptr_eq(&v0)); - } - - #[test] - fn instantiate_rev_closed_passthrough() { - let mut env = InternTable::::new(); - let nat = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let fv0 = AE::fvar(FVarId(0), ()); - let result = instantiate_rev(&mut env, &nat, &[fv0]); - assert!(result.ptr_eq(&nat)); - } - - #[test] - fn instantiate_rev_innermost() { - let mut env = InternTable::::new(); - let v0 = AE::var(0, ()); - let fv0 = AE::fvar(FVarId(0), ()); - // Single-binder body: instantiate Var(0) → fvars[0] - let result = instantiate_rev(&mut env, &v0, std::slice::from_ref(&fv0)); - assert_eq!(result, fv0); - } - - #[test] - fn instantiate_rev_outermost() { - let mut env = InternTable::::new(); - let v1 = AE::var(1, ()); - let fv0 = AE::fvar(FVarId(0), ()); - let fv1 = AE::fvar(FVarId(1), ()); - // Two-binder body, body is Var(1): outermost binder → fvars[0] - let result = instantiate_rev(&mut env, &v1, &[fv0.clone(), fv1]); - assert_eq!(result, fv0); - } - - #[test] - fn instantiate_rev_mix() { - let mut env = InternTable::::new(); - let v0 = AE::var(0, ()); - let v1 = AE::var(1, ()); - let app = AE::app(v0, v1); - let fv0 = AE::fvar(FVarId(0), ()); - let fv1 = AE::fvar(FVarId(1), ()); - // Two-binder body: Var(0) → fvars[1]=fv1, Var(1) → fvars[0]=fv0 - let result = instantiate_rev(&mut env, &app, &[fv0.clone(), fv1.clone()]); - let expected = AE::app(fv1, fv0); - assert_eq!(result, expected); - } - - #[test] - fn instantiate_rev_free_var_shifts_down() { - let mut env = InternTable::::new(); - let v3 = AE::var(3, ()); - let fv0 = AE::fvar(FVarId(0), ()); - let fv1 = AE::fvar(FVarId(1), ()); - // Two binders peeled → Var(3) shifts down to Var(1) - let result = instantiate_rev(&mut env, &v3, &[fv0, fv1]); - assert_eq!(result, AE::var(1, ())); - } - - #[test] - fn instantiate_rev_under_inner_binder() { - let mut env = InternTable::::new(); - let nat = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let v0 = AE::var(0, ()); // bound by inner λ - let v1 = AE::var(1, ()); // refers to outer (the peeled binder at depth 0) - let inner = AE::app(v0, v1); - let lam = AE::lam((), (), nat.clone(), inner); - let fv0 = AE::fvar(FVarId(0), ()); - let result = instantiate_rev(&mut env, &lam, std::slice::from_ref(&fv0)); - // Inside the lambda, Var(0) is still bound, Var(1) becomes fv0. - let expected = AE::lam((), (), nat, AE::app(AE::var(0, ()), fv0)); - assert_eq!(result, expected); - } - - // ---- abstract_fvars ---- - - #[test] - fn abstract_fvars_empty_passthrough() { - let mut env = InternTable::::new(); - let v0 = AE::var(0, ()); - let result = abstract_fvars(&mut env, &v0, &[]); - assert!(result.ptr_eq(&v0)); - } - - #[test] - fn abstract_fvars_no_fvars_shifts_loose_bvar() { - let mut env = InternTable::::new(); - let v0 = AE::var(0, ()); - let result = abstract_fvars(&mut env, &v0, &[FVarId(0)]); - assert_eq!(result, AE::var(1, ())); - } - - #[test] - fn abstract_fvars_single_replacement() { - let mut env = InternTable::::new(); - let fv0 = AE::fvar(FVarId(0), ()); - // One target fvar → becomes Var(0) - let result = abstract_fvars(&mut env, &fv0, &[FVarId(0)]); - assert_eq!(result, AE::var(0, ())); - } - - #[test] - fn abstract_fvars_position_mapping() { - let mut env = InternTable::::new(); - let fv0 = AE::fvar(FVarId(0), ()); - let fv1 = AE::fvar(FVarId(1), ()); - let app = AE::app(fv0, fv1); - // [fv0, fv1]: fv0 outermost (Var(1)), fv1 innermost (Var(0)) - let result = abstract_fvars(&mut env, &app, &[FVarId(0), FVarId(1)]); - let expected = AE::app(AE::var(1, ()), AE::var(0, ())); - assert_eq!(result, expected); - } - - #[test] - fn abstract_fvars_unrelated_pass_through() { - let mut env = InternTable::::new(); - let fv0 = AE::fvar(FVarId(0), ()); - let fv2 = AE::fvar(FVarId(2), ()); - // fv2 is not in the abstraction list → unchanged - let result = abstract_fvars(&mut env, &fv2, &[FVarId(0), FVarId(1)]); - assert!(result.ptr_eq(&fv2)); - let _ = fv0; // silence unused - } - - #[test] - fn abstract_fvars_lifts_loose_bvars() { - let mut env = InternTable::::new(); - let fv0 = AE::fvar(FVarId(0), ()); - let v0 = AE::var(0, ()); - let app = AE::app(fv0, v0); - // Wrap one new binder around `app`; fv0 → Var(0); existing Var(0) - // (loose) shifts up to Var(1). - let result = abstract_fvars(&mut env, &app, &[FVarId(0)]); - let expected = AE::app(AE::var(0, ()), AE::var(1, ())); - assert_eq!(result, expected); - } - - #[test] - fn instantiate_rev_then_abstract_roundtrip() { - let mut env = InternTable::::new(); - // Body: λ. App(#0, #1) — under one extra binder; Var(0) is the inner - // peeled binder, Var(1) is the outer one. - let nat = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let body = - AE::lam((), (), nat.clone(), AE::app(AE::var(0, ()), AE::var(1, ()))); - let fv_outer_id = FVarId(7); - let fv_inner_id = FVarId(8); - let fv_outer = AE::fvar(fv_outer_id, ()); - let fv_inner = AE::fvar(fv_inner_id, ()); - - // Open: peel the outer binder around body... actually body itself is a - // lambda (the outer binder), and its inner is what we want to peel. - // For simplicity, treat `body` directly as a body under one peeled - // outer binder, then peel its inner lambda manually. - let opened_outer = - instantiate_rev(&mut env, &body, std::slice::from_ref(&fv_outer)); - // opened_outer is now: λ(Nat). App(#0, fv_outer) - let inner_body = match opened_outer.data() { - ExprData::Lam(_, _, _, _, b, _) => b.clone(), - _ => unreachable!(), - }; - let opened_inner = - instantiate_rev(&mut env, &inner_body, std::slice::from_ref(&fv_inner)); - // opened_inner is now: App(fv_inner, fv_outer) - let expected_open = AE::app(fv_inner.clone(), fv_outer.clone()); - assert_eq!(opened_inner, expected_open); - - // Close: abstract back over [fv_outer, fv_inner] — outer first. - let closed = - abstract_fvars(&mut env, &opened_inner, &[fv_outer_id, fv_inner_id]); - // Expected: App(#0, #1) — fv_inner → Var(0), fv_outer → Var(1). - let expected_closed = AE::app(AE::var(0, ()), AE::var(1, ())); - assert_eq!(closed, expected_closed); - } - - #[test] - fn simul_subst_basic() { - let mut env = InternTable::::new(); - let v0 = AE::var(0, ()); - let v1 = AE::var(1, ()); - let app = AE::app(v1, v0); // App(Var(1), Var(0)) - - let a = AE::nat(Nat::from(1u64), mk_addr("a")); - let b = AE::nat(Nat::from(2u64), mk_addr("b")); - - // simul_subst([a, b], depth=0): - // Var(0) → substs[0] = a - // Var(1) → substs[1] = b - let result = simul_subst(&mut env, &app, &[a.clone(), b.clone()], 0); - let expected = AE::app(b, a); - assert_eq!(result, expected); - } - - #[test] - fn simul_subst_shift() { - let mut env = InternTable::::new(); - let v2 = AE::var(2, ()); - - let a = AE::nat(Nat::from(1u64), mk_addr("a")); - let b = AE::nat(Nat::from(2u64), mk_addr("b")); - - // Var(2) >= depth+2 → shifted to Var(0) - let result = simul_subst(&mut env, &v2, &[a, b], 0); - assert_eq!(result, AE::var(0, ())); - } - - #[test] - fn intern_dedup() { - let mut env = InternTable::::new(); - let _v0 = AE::var(0, ()); - let v2 = AE::var(2, ()); - let arg = AE::nat(Nat::from(3u64), mk_addr("3")); - - // Two substitutions producing the same result should be pointer-equal after interning - let r1 = subst(&mut env, &v2, &arg, 0); - let r2 = subst(&mut env, &v2, &arg, 0); - assert!(r1.ptr_eq(&r2), "interned results should be ptr-equal"); - } - - // --------------------------------------------------------------------- - // cheap_beta_reduce — see lean4lean Instantiate.lean:8-27. - // --------------------------------------------------------------------- - - #[test] - fn cheap_beta_non_app_returns_input() { - let mut env = InternTable::::new(); - let v0 = AE::var(0, ()); - let result = cheap_beta_reduce(&mut env, &v0); - assert!(result.ptr_eq(&v0)); - - let nat = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let result = cheap_beta_reduce(&mut env, &nat); - assert!(result.ptr_eq(&nat)); - } - - #[test] - fn cheap_beta_app_non_lam_head_returns_input() { - let mut env = InternTable::::new(); - let f = AE::cnst(KId::new(mk_addr("f"), ()), Box::new([])); - let arg = AE::nat(Nat::from(3u64), mk_addr("3")); - let app = env.intern_expr(AE::app(f, arg)); - let result = cheap_beta_reduce(&mut env, &app); - assert!(result.ptr_eq(&app)); - } - - #[test] - fn cheap_beta_closed_body_drops_lam() { - // (λ_:Nat. Nat) 3 → Nat - let mut env = InternTable::::new(); - let nat = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let lam = AE::lam((), (), nat.clone(), nat.clone()); - let arg = AE::nat(Nat::from(3u64), mk_addr("3")); - let app = AE::app(lam, arg); - let result = cheap_beta_reduce(&mut env, &app); - assert_eq!(result, nat); - } - - #[test] - fn cheap_beta_bvar_picks_arg() { - // (λx:Nat. x) 3 → 3 - let mut env = InternTable::::new(); - let nat = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let v0 = AE::var(0, ()); - let lam = AE::lam((), (), nat, v0); - let arg = AE::nat(Nat::from(3u64), mk_addr("3")); - let app = AE::app(lam, arg.clone()); - let result = cheap_beta_reduce(&mut env, &app); - assert_eq!(result, arg); - } - - #[test] - fn cheap_beta_nested_bvar_picks_outer_arg() { - // (λa b. a) x y → x (a is Var(1) under both binders) - let mut env = InternTable::::new(); - let nat = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let v1 = AE::var(1, ()); // refers to outermost lambda - // λa:Nat. λb:Nat. a - let inner_lam = AE::lam((), (), nat.clone(), v1); - let outer_lam = AE::lam((), (), nat, inner_lam); - let x = AE::nat(Nat::from(7u64), mk_addr("x")); - let y = AE::nat(Nat::from(8u64), mk_addr("y")); - let app = AE::app(AE::app(outer_lam, x.clone()), y); - let result = cheap_beta_reduce(&mut env, &app); - assert_eq!(result, x); - } - - #[test] - fn cheap_beta_overapplied_appends_remaining() { - // (λx:Nat. x) y z → y z (Var(0) body, two args; pick args[0]=y, apply z) - let mut env = InternTable::::new(); - let nat = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let v0 = AE::var(0, ()); - let lam = AE::lam((), (), nat, v0); - let y = AE::cnst(KId::new(mk_addr("y"), ()), Box::new([])); - let z = AE::cnst(KId::new(mk_addr("z"), ()), Box::new([])); - let app = AE::app(AE::app(lam, y.clone()), z.clone()); - let result = cheap_beta_reduce(&mut env, &app); - let expected = AE::app(y, z); - assert_eq!(result, expected); - } - - #[test] - fn cheap_beta_non_trivial_body_returns_input() { - // (λx:Nat. f x) 3 — body is App(f, Var(0)), neither closed nor a single bvar - let mut env = InternTable::::new(); - let nat = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let f = AE::cnst(KId::new(mk_addr("f"), ()), Box::new([])); - let v0 = AE::var(0, ()); - let body = AE::app(f, v0); - let lam = AE::lam((), (), nat, body); - let arg = AE::nat(Nat::from(3u64), mk_addr("3")); - let app = env.intern_expr(AE::app(lam, arg)); - let result = cheap_beta_reduce(&mut env, &app); - // Non-trivial: defer to WHNF, return original. - assert_eq!(result, app); - } - - #[test] - fn cheap_beta_underapplied_returns_input() { - // (λa b. a) x — only one arg supplied; body Var(1) but only 1 lam peeled - // (we peel min(2 lams, 1 arg) = 1, body is `λb. Var(1)` — still a Lam, - // the loop terminates with i=1 and head=lam, which doesn't match Var - // case nor closed-body case). - // - // Actually after peeling 1 lambda, head is still `λb:Nat. Var(1)`, - // which has lbr=2 > 0 (Var(1) at this depth), and isn't a Var(k). - // So we fall through to the no-reduce case. - let mut env = InternTable::::new(); - let nat = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let v1 = AE::var(1, ()); - let inner_lam = AE::lam((), (), nat.clone(), v1); - let outer_lam = AE::lam((), (), nat, inner_lam); - let x = AE::cnst(KId::new(mk_addr("x"), ()), Box::new([])); - let app = env.intern_expr(AE::app(outer_lam, x)); - let result = cheap_beta_reduce(&mut env, &app); - assert_eq!(result, app); - } - - #[test] - fn cheap_beta_idempotent() { - // Result of cheap_beta_reduce should itself reduce to itself. - let mut env = InternTable::::new(); - let nat = AE::cnst(KId::new(mk_addr("Nat"), ()), Box::new([])); - let v0 = AE::var(0, ()); - let lam = AE::lam((), (), nat, v0); - let arg = AE::nat(Nat::from(3u64), mk_addr("3")); - let app = AE::app(lam, arg); - let r1 = cheap_beta_reduce(&mut env, &app); - let r2 = cheap_beta_reduce(&mut env, &r1); - assert_eq!(r1, r2); - } - - // ========================================================================= - // Property-style tests - // - // These use deterministic seeded generators rather than `quickcheck` so - // they run in the default test harness without extra glue. The - // generators produce a variety of bounded-depth `KExpr` shapes to - // exercise subst/lift invariants across a broad sample of inputs. - // ========================================================================= - - /// Small deterministic xorshift64 PRNG used for property-style tests. - /// Avoids pulling `rand` into the kernel test module. - struct Prng(u64); - impl Prng { - fn new(seed: u64) -> Self { - Prng(seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ 0xDEAD_BEEF_CAFE_BABE) - } - fn next_u64(&mut self) -> u64 { - let mut x = self.0; - x ^= x << 13; - x ^= x >> 7; - x ^= x << 17; - self.0 = x; - x - } - fn next_u32(&mut self, bound: u32) -> u32 { - // Truncating to u32 is intentional for the test RNG. - #[allow(clippy::cast_possible_truncation)] - let lo = self.next_u64() as u32; - lo % bound.max(1) - } - } - - /// 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. - fn gen_expr( - env: &mut InternTable, - rng: &mut Prng, - depth: u32, - max_var: u64, - ) -> AE { - if depth == 0 { - // Leaves - return match rng.next_u32(4) { - 0 => env.intern_expr(AE::var(rng.next_u64() % (max_var + 1), ())), - 1 => env.intern_expr(AE::sort(KUniv::zero())), - 2 => { - env.intern_expr(AE::cnst(KId::new(mk_addr("c"), ()), Box::new([]))) - }, - _ => env.intern_expr(gen_nat(rng)), - }; - } - let choice = rng.next_u32(5); - match choice { - 0 => env.intern_expr(AE::var(rng.next_u64() % (max_var + 1), ())), - 1 => { - let f = gen_expr(env, rng, depth - 1, max_var); - let a = gen_expr(env, rng, depth - 1, max_var); - env.intern_expr(AE::app(f, a)) - }, - 2 => { - let ty = gen_expr(env, rng, depth - 1, max_var); - let body = gen_expr(env, rng, depth - 1, max_var + 1); - env.intern_expr(AE::lam((), (), ty, body)) - }, - 3 => { - let ty = gen_expr(env, rng, depth - 1, max_var); - let body = gen_expr(env, rng, depth - 1, max_var + 1); - env.intern_expr(AE::all((), (), ty, body)) - }, - _ => env.intern_expr(AE::sort(KUniv::zero())), - } - } - - /// The actual maximum loose de Bruijn index found by traversal, for - /// cross-check against `expr.lbr()`. - fn observed_lbr(e: &AE) -> u64 { - fn walk(e: &AE, binders: u64, max: &mut u64) { - match e.data() { - ExprData::Var(i, _, _) => { - if *i >= binders { - let loose = *i - binders + 1; - if loose > *max { - *max = loose; - } - } - }, - ExprData::App(f, a, _) => { - walk(f, binders, max); - walk(a, binders, max); - }, - ExprData::Lam(_, _, _, ty, body, _) - | ExprData::All(_, _, _, _, ty, body, _) => { - walk(ty, binders, max); - walk(body, binders + 1, max); - }, - ExprData::Let(_, ty, val, body, _, _) => { - walk(ty, binders, max); - walk(val, binders, max); - walk(body, binders + 1, max); - }, - ExprData::Prj(_, _, val, _) => walk(val, binders, max), - ExprData::FVar(..) - | ExprData::Sort(..) - | ExprData::Const(..) - | ExprData::Nat(..) - | ExprData::Str(..) => {}, - } - } - let mut m = 0; - walk(e, 0, &mut m); - m - } - - #[test] - fn prop_lbr_matches_observed_walk() { - let mut env = InternTable::::new(); - let mut rng = Prng::new(0x1234_5678); - for _ in 0..200 { - let e = gen_expr(&mut env, &mut rng, 4, 3); - let observed = observed_lbr(&e); - let reported = e.lbr(); - assert_eq!( - reported, observed, - "lbr mismatch: reported={reported}, observed={observed}, e={e:?}" - ); - } - } - - #[test] - fn prop_intern_determinism() { - let mut env = InternTable::::new(); - let mut rng = Prng::new(0x55aa_55aa); - for _ in 0..200 { - let e = gen_expr(&mut env, &mut rng, 4, 3); - // Re-interning the same shape should return the same Arc. - let e2 = env.intern_expr(e.data().clone().into_kexpr()); - assert!( - e.ptr_eq(&e2), - "re-interning should produce ptr-equal expressions" - ); - } - } - - #[test] - fn prop_lift_zero_shift_is_identity() { - let mut env = InternTable::::new(); - let mut rng = Prng::new(0xCAFE_F00D); - for _ in 0..200 { - let e = gen_expr(&mut env, &mut rng, 4, 3); - let r = lift(&mut env, &e, 0, 0); - assert!(r.ptr_eq(&e), "lift with shift=0 must be identity"); - } - } - - #[test] - fn prop_subst_preserves_closed_expressions() { - let mut env = InternTable::::new(); - let mut rng = Prng::new(0xDEAD_BEEF); - // Closed sub-expressions are not walked — verify `subst` returns the - // same Arc. - let arg = AE::nat(Nat::from(7u64), mk_addr("arg")); - for _ in 0..100 { - let e = gen_expr(&mut env, &mut rng, 3, 0); - // Only closed (lbr == 0) expressions qualify; skip others. - if e.lbr() == 0 { - let r = subst(&mut env, &e, &arg, 0); - assert!( - r.ptr_eq(&e), - "subst must return ptr-equal for closed expressions" - ); - } - } - } -} diff --git a/crates/kernel/src/subst/menv_tests.rs b/crates/kernel/src/subst/menv_tests.rs deleted file mode 100644 index 44763f193..000000000 --- a/crates/kernel/src/subst/menv_tests.rs +++ /dev/null @@ -1,389 +0,0 @@ -//! Persistent-environment regressions and an opt-in paired microbenchmark. - -// Match the production closure API, including its worker-private Arc use; -// replacing these with Rc would change what the reference benchmark measures. -#![allow(clippy::arc_with_non_send_sync)] - -use std::hint::black_box; -use std::mem::size_of; -use std::sync::Arc; -use std::time::{Duration, Instant}; - -use super::{Clo, MEnv, MEnvNode, clo_readback}; -use crate::env::{InternTable, KEnv}; -use crate::expr::KExpr; -use crate::level::KUniv; -use crate::mode::Anon; -use crate::tc::TypeChecker; - -type Closure = Arc>; - -/// The original representation, retained only as a benchmark/reference. -#[derive(Clone)] -struct LinearEnv { - node: Option>, - len: u64, -} - -struct LinearNode { - head: Closure, - tail: LinearEnv, -} - -trait TestEnv: Clone { - fn empty() -> Self; - fn push(&self, c: Closure) -> Self; - fn get(&self, i: u64) -> &Closure; -} - -impl TestEnv for LinearEnv { - fn empty() -> Self { - Self { node: None, len: 0 } - } - - fn push(&self, c: Closure) -> Self { - Self { - node: Some(Arc::new(LinearNode { head: c, tail: self.clone() })), - len: self.len + 1, - } - } - - fn get(&self, mut i: u64) -> &Closure { - let mut node = self.node.as_ref().expect("linear lookup out of range"); - while i > 0 { - node = node.tail.node.as_ref().expect("linear lookup out of range"); - i -= 1; - } - &node.head - } -} - -impl TestEnv for MEnv { - fn empty() -> Self { - Self::empty() - } - - fn push(&self, c: Closure) -> Self { - self.push(c) - } - - fn get(&self, i: u64) -> &Closure { - self.get(i) - } -} - -fn closure(tag: u64) -> Closure { - Arc::new(Clo::closed(KExpr::var(tag, ()))) -} - -#[test] -fn menv_lookup_preserves_every_snapshot() { - let entries: Vec<_> = (0..256).map(closure).collect(); - let mut versions = vec![MEnv::empty()]; - for entry in &entries { - let next = versions.last().unwrap().push(entry.clone()); - versions.push(next); - } - for (len, env) in versions.iter().enumerate() { - assert_eq!(env.len(), u64::try_from(len).unwrap()); - for (i, expected) in entries[..len].iter().rev().enumerate() { - assert!(Arc::ptr_eq(env.get(u64::try_from(i).unwrap()), expected)); - } - } - // Release newest first so even the linear reference has shallow drops. - while versions.pop().is_some() {} -} - -#[test] -fn menv_persistent_branches_match_vector_model() { - let mut versions = vec![(MEnv::empty(), Vec::::new())]; - let mut rng = 1u64; - for tag in 0..2_000 { - rng = rng.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1); - let parent = - usize::try_from(rng % u64::try_from(versions.len()).unwrap()).unwrap(); - let (env, entries) = &versions[parent]; - let value = closure(tag); - let next = env.push(value.clone()); - let mut expected = entries.clone(); - expected.push(value); - for (i, entry) in expected.iter().rev().enumerate() { - assert!(Arc::ptr_eq(next.get(u64::try_from(i).unwrap()), entry)); - } - versions.push((next, expected)); - } - while versions.pop().is_some() {} -} - -#[test] -fn menv_jump_spans_match_ordinary_ancestors() { - let value = closure(0); - let mut versions = build_versions::>(4_096, &value); - for (len, env) in versions.iter().enumerate().skip(1) { - let node = env.node.as_ref().unwrap(); - let span = usize::try_from(node.jump_len).unwrap(); - assert!((span + 1).is_power_of_two()); - assert!(span <= len); - match (&node.jump, &versions[len - span].node) { - (Some(actual), Some(expected)) => assert!(Arc::ptr_eq(actual, expected)), - (None, None) => {}, - _ => panic!("jump must reach the same ancestor as its span"), - } - let mut block = node; - let mut position = 0; - while let Some(next) = &block.jump { - assert!(block.jump_len <= next.jump_len); - if block.jump_len == next.jump_len { - assert_eq!(position, 0, "only the first two blocks may be equal"); - } - block = next; - position += 1; - } - } - while versions.pop().is_some() {} -} - -#[test] -fn menv_deep_lookups_match_linear_model_with_logarithmic_hops() { - let entries: Vec<_> = (0..16_385).map(closure).collect(); - let mut versions = vec![MEnv::empty()]; - for value in &entries { - versions.push(versions.last().unwrap().push(value.clone())); - } - for len in [63usize, 64, 65, 1_023, 1_024, 1_025, 16_383, 16_384, 16_385] { - let env = &versions[len]; - for (index, expected) in entries[..len].iter().rev().enumerate() { - let index = u64::try_from(index).unwrap(); - assert!(Arc::ptr_eq(env.get(index), expected)); - // Check the structural work bound without adding instrumentation - // to the production lookup hot path. - let mut node = env.node.as_ref().unwrap(); - let mut remaining = index; - let mut hops = 0; - while remaining >= 8 { - if node.jump_len <= remaining { - remaining -= node.jump_len; - node = node.jump.as_ref().unwrap(); - } else { - remaining -= 1; - node = node.tail.as_ref().unwrap(); - } - hops += 1; - } - while remaining > 0 { - remaining -= 1; - node = node.tail.as_ref().unwrap(); - hops += 1; - } - assert!(Arc::ptr_eq(&node.head, expected)); - assert!(hops <= 2 * (env.len().ilog2() + 1)); - } - } - while versions.pop().is_some() {} -} - -#[test] -fn menv_readback_preserves_captured_environments_and_lifting() { - let mut env = MEnv::empty().push(closure(3)); - let mut versions = vec![env.clone()]; - for _ in 0..127 { - // Each new entry denotes the previous entry in its captured snapshot, - // not Var(0) in the eventual caller's environment. - env = env.push(Arc::new(Clo::new(KExpr::var(0, ()), env.clone()))); - versions.push(env.clone()); - } - let ty = KExpr::sort(KUniv::zero()); - let body = KExpr::lam( - (), - (), - ty.clone(), - KExpr::app( - KExpr::var(1, ()), - KExpr::app(KExpr::var(env.len(), ()), KExpr::var(env.len() + 5, ())), - ), - ); - let c = Clo::new(body, env); - let expected = KExpr::lam( - (), - (), - ty, - KExpr::app( - KExpr::var(4, ()), - KExpr::app(KExpr::var(4, ()), KExpr::var(5, ())), - ), - ); - let mut intern = InternTable::new(); - let result = clo_readback(&mut intern, &c); - assert_eq!(result, expected); - assert!(result.ptr_eq(&clo_readback(&mut intern, &c))); - drop(c); - while versions.pop().is_some() {} -} - -#[test] -fn menv_deep_beta_machine_selects_original_arguments() { - for depth in [7u64, 8, 15, 63, 64, 65, 127, 128, 129] { - for selected in [0, depth / 2, depth - 1] { - let mut env = KEnv::::new(); - let ty = KExpr::sort(KUniv::zero()); - // Open arguments let us distinguish every position and also verify - // that ambient variables are not captured by the machine binders. - let args: Vec<_> = (0..depth).map(|i| KExpr::var(i + 10, ())).collect(); - let mut expr = KExpr::var(depth - 1 - selected, ()); - for _ in 0..depth { - expr = KExpr::lam((), (), ty.clone(), expr); - } - for arg in &args { - expr = KExpr::app(expr, arg.clone()); - } - let mut tc = TypeChecker::new(&mut env); - let result = tc.whnf(&expr).unwrap(); - assert_eq!(result, args[usize::try_from(selected).unwrap()]); - } - } -} - -#[test] -#[should_panic(expected = "MEnv::get out of range")] -fn menv_empty_lookup_panics() { - let _ = MEnv::::empty().get(0); -} - -#[test] -#[should_panic(expected = "MEnv::get out of range")] -fn menv_len_lookup_panics() { - let env = MEnv::empty().push(closure(0)); - let _ = env.get(env.len()); -} - -#[test] -#[should_panic(expected = "MEnv::get out of range")] -fn menv_large_out_of_range_lookup_panics() { - let env = MEnv::empty().push(closure(0)); - let _ = env.get(u64::MAX); -} - -fn build_versions(depth: u64, value: &Closure) -> Vec { - let mut versions = Vec::with_capacity(usize::try_from(depth + 1).unwrap()); - versions.push(E::empty()); - for _ in 0..depth { - let next = versions.last().unwrap().push(value.clone()); - versions.push(next); - } - versions -} - -fn measure_lookup(env: &E, indices: &[u64], count: u64) -> f64 { - let start = Instant::now(); - let mut indices = indices.iter().cycle(); - for _ in 0..count { - black_box(black_box(env).get(black_box(*indices.next().unwrap()))); - } - start.elapsed().as_secs_f64() * 1e9 / f64::from(u32::try_from(count).unwrap()) -} - -fn median(mut samples: Vec) -> f64 { - samples.sort_by(f64::total_cmp); - samples[samples.len() / 2] -} - -fn build_sample( - depth: u64, - value: &Closure, -) -> (Duration, Duration) { - let start = Instant::now(); - let mut versions = black_box(build_versions::(depth, value)); - let build = start.elapsed(); - let start = Instant::now(); - while versions.pop().is_some() {} - (build, start.elapsed()) -} - -/// No timing assertions: performance results depend on host load/allocator. -/// Construction includes retaining each version; destruction releases newest -/// first. Closure allocation is excluded, to isolate the environment itself. -#[test] -#[ignore = "release microbenchmark: --release menv_lookup_benchmark -- --ignored --nocapture"] -fn menv_lookup_benchmark() { - eprintln!( - "MEnv node payload={} B; linear={} B; handle={} B; one node allocation/push (excludes Arc header/allocator rounding)", - size_of::>(), - size_of::(), - size_of::>(), - ); - let value = closure(0); - for depth in [8u64, 64, 1_024, 16_384] { - let mut current = build_versions::>(depth, &value); - let mut linear = build_versions::(depth, &value); - let uniform: Vec<_> = (0..256u64) - .map(|i| i.wrapping_mul(6_364_136_223_846_793_005) % depth) - .collect(); - for (pattern, indices) in [ - ("front", vec![0]), - ("near", (0..8).collect()), - ("oldest", vec![depth - 1]), - ("uniform", uniform), - ] { - let count = if pattern == "front" || pattern == "near" { - 500_000 - } else { - (4_000_000 / depth).clamp(4_096, 500_000) - }; - let mut current_samples = Vec::new(); - let mut linear_samples = Vec::new(); - for round in 0..7 { - if round % 2 == 0 { - current_samples.push(measure_lookup( - current.last().unwrap(), - &indices, - count, - )); - linear_samples.push(measure_lookup( - linear.last().unwrap(), - &indices, - count, - )); - } else { - linear_samples.push(measure_lookup( - linear.last().unwrap(), - &indices, - count, - )); - current_samples.push(measure_lookup( - current.last().unwrap(), - &indices, - count, - )); - } - } - let current_ns = median(current_samples); - let linear_ns = median(linear_samples); - eprintln!( - "depth={depth:5} {pattern:7}: current={current_ns:10.2} ns/lookup linear={linear_ns:10.2} ns/lookup ratio={:.3}", - current_ns / linear_ns - ); - } - while current.pop().is_some() {} - while linear.pop().is_some() {} - } - for depth in [1_024u32, 16_384] { - let mut build = [Vec::new(), Vec::new()]; - let mut drop = [Vec::new(), Vec::new()]; - for round in 0..7 { - for index in [round % 2, 1 - round % 2] { - let (built, dropped) = if index == 0 { - build_sample::>(u64::from(depth), &value) - } else { - build_sample::(u64::from(depth), &value) - }; - build[index].push(built.as_secs_f64() * 1e9 / f64::from(depth)); - drop[index].push(dropped.as_secs_f64() * 1e9 / f64::from(depth)); - } - } - let [current_build, linear_build] = build.map(median); - let [current_drop, linear_drop] = drop.map(median); - eprintln!( - "depth={depth:5} build: current={current_build:.2} linear={linear_build:.2} ns/binding; drop: current={current_drop:.2} linear={linear_drop:.2} ns/binding" - ); - } -} diff --git a/crates/kernel/src/subst/scratch_tests.rs b/crates/kernel/src/subst/scratch_tests.rs deleted file mode 100644 index db0194bc0..000000000 --- a/crates/kernel/src/subst/scratch_tests.rs +++ /dev/null @@ -1,364 +0,0 @@ -//! Scratch allocation regressions: results remain call-local while capacity -//! adapts to a large binder-opening traversal followed by tiny substitutions. - -use super::*; -use crate::env::KEnv; -use crate::id::KId; -use crate::level::KUniv; -use crate::mode::{Anon, Meta}; -use crate::tc::TypeChecker; -use ix_common::address::Address; -use ix_common::env::{BinderInfo, DataValue, Name}; - -fn meta_name(s: &str) -> M::MField { - M::meta_field(Name::str(Name::anon(), s.to_owned())) -} - -// A balanced tree has many distinct open nodes without deep recursion in -// construction, substitution, or destruction. Every leaf must be visited. -fn wide_open_tree( - intern: &mut InternTable, - leaves: u64, -) -> KExpr { - assert!(leaves.is_power_of_two()); - let mut layer: Vec<_> = (0..leaves) - .map(|i| intern.intern_expr(KExpr::var(i, meta_name::("v")))) - .collect(); - while layer.len() > 1 { - layer = layer - .as_chunks::<2>() - .0 - .iter() - .map(|pair| intern.intern_app(&pair[0], &pair[1])) - .collect(); - } - layer.pop().unwrap() -} - -fn large_then_small() { - let mut intern = InternTable::::new(); - let body = wide_open_tree(&mut intern, 8_192); - let fv = intern.intern_expr(KExpr::fvar(FVarId(0), meta_name::("bulk"))); - let _ = instantiate_rev(&mut intern, &body, &[fv]); - let high_water = intern.subst_scratch.capacity(); - assert!(high_water > 4_096); - - let var = intern.intern_expr(KExpr::var(0, meta_name::("v"))); - for i in 1..=5 { - let arg = - intern.intern_expr(KExpr::fvar(FVarId(i), meta_name::("small"))); - let result = subst(&mut intern, &var, &arg, 0); - assert!(result.ptr_eq(&arg), "memo entries leaked between calls"); - } - assert!( - intern.subst_scratch.capacity() <= 4_096, - "tiny substitutions retained the bulk table: high_water={high_water}, current={}", - intern.subst_scratch.capacity() - ); -} - -#[test] -fn scratch_releases_bulk_capacity_after_tiny_substitutions() { - large_then_small::(); - large_then_small::(); -} - -fn lift_large_then_small() { - let mut intern = InternTable::::new(); - let body = wide_open_tree(&mut intern, 8_192); - let _ = lift(&mut intern, &body, 1, 0); - assert!(intern.lift_scratch.capacity() > 4_096); - let var = intern.intern_expr(KExpr::var(0, meta_name::("v"))); - for shift in 1..=5 { - let result = lift(&mut intern, &var, shift, 0); - assert!(matches!(result.data(), ExprData::Var(i, ..) if *i == shift)); - } - assert!(intern.lift_scratch.capacity() <= 4_096); -} - -#[test] -fn scratch_lift_adapts_independently() { - lift_large_then_small::(); - lift_large_then_small::(); -} - -// Small, shared expressions containing every binder/child position touched -// by substitution. Metadata is deliberately nonempty in Meta mode. -fn expressions(intern: &mut InternTable) -> Vec> { - let mut nodes: Vec<_> = (0..4) - .map(|i| intern.intern_expr(KExpr::var(i, meta_name::("var")))) - .collect(); - for i in 0..4 { - nodes - .push(intern.intern_expr(KExpr::fvar(FVarId(i), meta_name::("free")))); - } - let metadata = M::meta_field(vec![vec![( - Name::str(Name::anon(), "tag".to_owned()), - DataValue::OfString("scratch differential".to_owned()), - )]]); - let ty = - intern.intern_expr(KExpr::sort_mdata(KUniv::zero(), metadata.clone())); - nodes.push(ty.clone()); - for i in 0..40 { - let a = nodes[i % nodes.len()].clone(); - let b = nodes[(i * 7 + 3) % nodes.len()].clone(); - let node = match i % 5 { - 0 => KExpr::app_mdata(a.clone(), a, metadata.clone()), - 1 => KExpr::lam( - meta_name::("lambda"), - M::meta_field(BinderInfo::Implicit), - a, - b, - ), - 2 => KExpr::all( - meta_name::("forall"), - M::meta_field(BinderInfo::InstImplicit), - a, - b, - ), - 3 => KExpr::let_(meta_name::("let"), ty.clone(), a, b, i % 2 == 0), - _ => KExpr::prj( - KId::new(Address::hash(b"scratch struct"), meta_name::("S")), - 0, - a, - ), - }; - nodes.push(intern.intern_expr(node)); - } - nodes -} - -fn differential() { - let mut intern = InternTable::::new(); - let bulk = wide_open_tree(&mut intern, 8_192); - let _ = lift(&mut intern, &bulk, 1, 0); - let fv = intern.intern_expr(KExpr::fvar(FVarId(0), meta_name::("free"))); - let _ = instantiate_rev(&mut intern, &bulk, &[fv]); - let nodes = expressions(&mut intern); - - for round in 0..4u64 { - let fvars: Vec<_> = (0..3) - .map(|i| { - intern.intern_expr(KExpr::fvar( - FVarId((i + round) % 4), - meta_name::("free"), - )) - }) - .collect(); - // Open replacements force nested lift calls while subst owns its memo. - let replacements = - [nodes[usize::try_from(round).unwrap()].clone(), fvars[0].clone()]; - let ids: Vec<_> = (0..3).map(|i| FVarId((i + round) % 4)).collect(); - let pos: FxHashMap<_, _> = ids - .iter() - .rev() - .enumerate() - .map(|(i, id)| (*id, u64::try_from(i).unwrap())) - .collect(); - for body in &nodes { - for op in 0..5 { - // Same trusted traversal with an independent, empty memo: no old - // result, capacity, replacement list, binder depth, or shift can leak - // from the adaptive cache into this reference result. - let mut fresh = FxHashMap::default(); - let expected = match op { - 0 => { - subst_cached(&mut intern, body, &replacements[0], round, &mut fresh) - }, - 1 => simul_subst_cached( - &mut intern, - body, - &replacements, - round, - &mut fresh, - ), - 2 => lift_cached(&mut intern, body, round + 1, round, &mut fresh), - 3 => instantiate_rev_cached(&mut intern, body, &fvars, 0, &mut fresh), - _ => abstract_fvars_cached(&mut intern, body, &pos, 3, 0, &mut fresh), - }; - let actual = match op { - 0 => subst(&mut intern, body, &replacements[0], round), - 1 => simul_subst(&mut intern, body, &replacements, round), - 2 => lift(&mut intern, body, round + 1, round), - 3 => instantiate_rev(&mut intern, body, &fvars), - _ => abstract_fvars(&mut intern, body, &ids), - }; - // Pointer equality in the SAME live interner also checks metadata - // and annotations, unlike KExpr's structural PartialEq alone. - assert!(actual.ptr_eq(&expected), "op={op} round={round}"); - } - } - } - assert!(intern.subst_scratch.capacity() <= 4_096); - assert!(intern.lift_scratch.capacity() <= 4_096); -} - -#[test] -fn scratch_mixed_operations_match_fresh_memos() { - differential::(); - differential::(); -} - -fn checked_application(polluted: bool) -> u64 { - let mut env = KEnv::::new(); - if polluted { - let bulk = wide_open_tree(&mut env.intern, 8_192); - let _ = lift(&mut env.intern, &bulk, 1, 0); - let fv = - env.intern.intern_expr(KExpr::fvar(FVarId(100), meta_name::("seed"))); - let _ = instantiate_rev(&mut env.intern, &bulk, &[fv]); - } - // (fun (A : Sort 1) => A) (Sort 0) is a closed, well-typed application. - let sort0 = KExpr::sort(KUniv::zero()); - let sort1 = KExpr::sort(KUniv::succ(KUniv::zero())); - let identity = KExpr::lam( - meta_name::("A"), - M::meta_field(BinderInfo::Default), - sort1.clone(), - KExpr::var(0, meta_name::("A")), - ); - let app = KExpr::app(identity, sort0.clone()); - let mut tc = TypeChecker::new(&mut env); - let ty = tc.infer(&app).unwrap(); - assert_eq!(ty, sort1); - let reduced = tc.whnf(&app).unwrap(); - assert_eq!(reduced, sort0); - tc.fuel_used() -} - -#[test] -fn scratch_history_does_not_change_kernel_results_or_fuel() { - assert_eq!( - checked_application::(false), - checked_application::(true) - ); - assert_eq!( - checked_application::(false), - checked_application::(true) - ); -} - -// Pre-adaptive take/clear/restore policy, kept only for a paired benchmark. -// The traversal, interner, operand shapes, and call-local memo keys are the -// same. All benchmark arguments are closed, so nested lift is a no-op. -fn legacy_subst( - intern: &mut InternTable, - slot: &mut FxHashMap<(Addr, u64), KExpr>, - body: &KExpr, - arg: &KExpr, -) -> KExpr { - // Match the disabled production diagnostic branch, without timing logs. - assert!(!*IX_SUBST_COUNT_LOG); - if body.lbr() == 0 { - return body.clone(); - } - let mut cache = std::mem::take(slot); - cache.clear(); - let result = subst_cached(intern, body, arg, 0, &mut cache); - *slot = cache; - result -} - -#[derive(Clone, Copy, Debug)] -enum Workload { - Small, - LargeThenSmall, - Dense, - Alternating, -} - -fn scratch_sample( - workload: Workload, - adaptive: bool, -) -> (std::time::Duration, usize, usize) { - use std::hint::black_box; - use std::time::Instant; - - let mut intern = InternTable::::new(); - let leaves = match workload { - Workload::Small => 32, - Workload::LargeThenSmall => 131_072, - Workload::Dense | Workload::Alternating => 4_096, - }; - let large = wide_open_tree(&mut intern, leaves); - let small = intern.intern_expr(KExpr::var(0, ())); - let arg = intern.intern_expr(KExpr::fvar(FVarId(0), ())); - let mut legacy = FxHashMap::default(); - // Simulate the real preceding bulk binder-opening operation, outside the - // timed loop. Both variants retain all of its canonical nodes identically. - if adaptive { - black_box(instantiate_rev(&mut intern, &large, std::slice::from_ref(&arg))); - } else { - black_box(instantiate_rev_cached( - &mut intern, - &large, - std::slice::from_ref(&arg), - 0, - &mut legacy, - )); - } - let before = - if adaptive { intern.subst_scratch.capacity() } else { legacy.capacity() }; - let iterations = match workload { - Workload::Small => 200_000, - Workload::LargeThenSmall => 5_000, - Workload::Dense => 100, - Workload::Alternating => 200, - }; - let start = Instant::now(); - for i in 0..iterations { - let body = match workload { - Workload::Dense => &large, - Workload::Alternating if i % 2 == 0 => &large, - _ => &small, - }; - let result = if adaptive { - subst(&mut intern, black_box(body), black_box(&arg), 0) - } else { - legacy_subst(&mut intern, &mut legacy, black_box(body), black_box(&arg)) - }; - black_box(result); - } - let elapsed = start.elapsed(); - let after = - if adaptive { intern.subst_scratch.capacity() } else { legacy.capacity() }; - if adaptive && matches!(workload, Workload::LargeThenSmall) { - assert!(after <= 4_096); - } else { - assert_eq!(before, after, "unexpected capacity churn in {workload:?}"); - } - (elapsed, before, after) -} - -#[test] -#[ignore = "manual paired release microbenchmark; no wall-clock assertions"] -fn benchmark_adaptive_scratch() { - for workload in [ - Workload::Small, - Workload::LargeThenSmall, - Workload::Dense, - Workload::Alternating, - ] { - let mut samples = [Vec::new(), Vec::new()]; - let mut capacities = [(0, 0); 2]; - for round in 0..5 { - // Alternate order to reduce warmup/thermal bias. Each sample has a - // fresh interner and scratch; construction and teardown are excluded. - for variant in [round % 2, 1 - round % 2] { - let (elapsed, before, after) = scratch_sample(workload, variant == 1); - samples[variant].push(elapsed); - capacities[variant] = (before, after); - } - } - let [old, new] = samples.map(|mut times| { - times.sort_unstable(); - times[times.len() / 2].as_secs_f64() * 1_000.0 - }); - eprintln!( - "{workload:?}: legacy={old:.3}ms adaptive={new:.3}ms ratio={:.3}; capacities legacy={:?} adaptive={:?}", - new / old, - capacities[0], - capacities[1] - ); - } -} diff --git a/crates/kernel/src/tc.rs b/crates/kernel/src/tc.rs deleted file mode 100644 index 34d9ec3d4..000000000 --- a/crates/kernel/src/tc.rs +++ /dev/null @@ -1,2234 +0,0 @@ -//! TypeChecker struct and core helpers. -//! -//! The TypeChecker is a lightweight handle for type-checking against one -//! worker-owned `KEnv`. -//! -//! WHNF, type inference, def-eq, and constant checking are in separate modules -//! that add `impl TypeChecker` blocks. - -use rustc_hash::FxHashMap; -use rustc_hash::FxHashSet; - -use ix_common::address::Address; -use ix_common::env::DefinitionSafety; -use ixon::constant::DefKind; -use ixon::env::Env as IxonEnv; - -use super::constant::{KConst, RecRule}; -use super::env::{Addr, CtxAddr, KEnv}; -use super::equiv::EquivManager; -use super::error::{TcError, u64_to_usize}; -use super::expr::{ExprData, FVarId, KExpr}; -use super::extra_reductions::{ExtraReduction, ExtraReductions}; -use super::id::KId; -use super::ingress::{ - IxonIngressLookups, ingress_addr_with_input, ingress_anon_addr_shallow_input, -}; -use super::lctx::LocalDecl; -use super::level::{KUniv, UnivData}; -use super::mode::KernelMode; -use super::perf::hot_misses::{HotMisses, MissKey}; -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() -> CtxAddr { - use std::sync::LazyLock; - static ADDR: LazyLock = - LazyLock::new(|| blake3::hash(b"ix.kernel.ctx.empty")); - *ADDR -} - -/// Maximum iterations in auxiliary WHNF loops (local per-call). -pub const MAX_WHNF_FUEL: u32 = 10_000; - -/// Maximum recursion depth for `is_def_eq`. -pub const MAX_DEF_EQ_DEPTH: u32 = 2_000; - -/// Shared recursive fuel budget, consumed by recursive whnf/infer/isDefEq -/// entries and by productive structural-WHNF beta/zeta/iota transitions. -/// This is cumulative work per declaration, not a recursion-depth limit; -/// its units are not directly comparable to lean4lean's step-indexed depth -/// allowance or the Lean C++ kernel's heartbeats. -/// -/// Large FLT proofs can finish successfully after roughly 54M counted steps -/// with shallow def-eq recursion. Leave headroom for these finite checks; -/// retain the independent depth/WHNF guards and the `IX_MAX_REC_FUEL` -/// override for bounded experiments and bisecting suspected loops. This -/// budget does not replace external time or memory limits. -pub const MAX_REC_FUEL: u64 = 100_000_000; - -/// Native stack, in bytes, a check keeps in reserve. Inference, WHNF and -/// conversion recurse through each other with no bound a crafted input -/// cannot exceed (a reduction whose major premise needs its own reduction, -/// a million levels deep, takes a million native frames), and fuel bounds -/// work, not depth. So each entry to them checks the stack its thread has -/// left and declines with [`TcError::StackExhausted`] below the checker's -/// reserve. The bound is the thread's actual stack, whatever its size (the -/// checker's workers have 256 MB). -/// -/// The reserve must cover the stack one guarded step uses before the next -/// check: over the kernel's tests at most about 166 KiB in a debug build -/// and 33 KiB in release. It is an eighth of the stack left when the checker is -/// created, between [`STACK_RESERVE_MIN`] and this maximum, so a 2 MB test -/// thread keeps 256 KiB and gives the rest to the check. -pub const STACK_RESERVE_MAX: usize = 1024 * 1024; - -/// The least stack reserve; see [`STACK_RESERVE_MAX`]. -pub const STACK_RESERVE_MIN: usize = 192 * 1024; - -/// The reserve for a checker created with the current thread's stack: an -/// eighth of what is left, within [`STACK_RESERVE_MIN`] and -/// [`STACK_RESERVE_MAX`]. -fn stack_reserve() -> usize { - #[cfg(not(target_arch = "riscv64"))] - if let Some(left) = stacker::remaining_stack() { - return (left / 8).clamp(STACK_RESERVE_MIN, STACK_RESERVE_MAX); - } - STACK_RESERVE_MAX -} - -static IX_MAX_REC_FUEL: crate::EnvOptU64 = crate::EnvOptU64::new(|| { - crate::env_var("IX_MAX_REC_FUEL").ok().and_then(|s| s.parse().ok()) -}); - -static IX_HOT_MISSES: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_HOT_MISSES").is_ok()); - -/// Opt-in, bounded native call stacks at resource guards. Useful with the -/// one-subject helper: ordinary CLI runs do not install a `log` subscriber. -static IX_GUARD_STACKS: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_GUARD_STACKS").is_ok()); -static GUARD_STACK_COUNT: std::sync::atomic::AtomicUsize = - std::sync::atomic::AtomicUsize::new(0); - -static IX_HOT_MISS_CTX: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_HOT_MISS_CTX").is_ok()); - -pub fn max_rec_fuel() -> u64 { - (*IX_MAX_REC_FUEL).unwrap_or(MAX_REC_FUEL) -} - -/// Temporary struct for recursor info during iota reduction, avoiding borrow -/// conflicts with `&self.env`. -/// -/// The layout fields are added on the WHNF hot path. This is sound only for an -/// accepted environment: `check_recursor_member` first proves their complete -/// sum does not overflow and equals canonical recursor generation. Direct -/// callers that construct an unchecked `KEnv` do not inherit that precondition. -pub struct IotaInfo { - pub k: bool, - pub params: usize, - pub motives: usize, - pub minors: usize, - pub indices: usize, - pub major_idx: usize, - pub rules: Vec>, - pub lvls: u64, -} - -pub struct LazyIxonIngress<'a> { - input: super::input::Input<'a>, - lookups: &'a IxonIngressLookups, - faulted_addrs: FxHashSet
, -} - -/// Lazy on-demand ingress for the anon kernel. Faults a missing -/// constant into the worker's `KEnv` using only `Constant` data — -/// no metadata access. Intended for use with envs loaded via -/// `Env::get_anon` (which discards metadata sections), but the -/// implementation never consults those sections regardless. -pub struct LazyAnonIngress<'a> { - input: super::input::Input<'a>, - faulted_addrs: FxHashSet
, -} - -/// Thread-local type-checking handle. Cheap to create — only allocates empty -/// vectors and counters. Kernel state lives in the borrowed worker `KEnv`. -pub struct TypeChecker<'a, M: KernelMode> { - /// Worker-owned kernel environment (constants, caches, intern table). - pub env: &'a mut KEnv, - /// Set only by the Ixon checker, whose private KEnv is populated entirely - /// by address-verified anonymous ingress with local recursion admission. - pub(crate) ixon_ingress: bool, - /// Optional read-only Ixon source used to fault constants into `env` when - /// typechecking discovers a missing address. - lazy_ixon: Option>, - /// Optional metadata-free lazy ingress (anon mode). Mutually exclusive with - /// `lazy_ixon` — `new_with_lazy_anon` constructs Anon-mode checkers; the - /// `lazy_ingress_addr` dispatcher uses whichever is set. The anon path - /// never reads `Env::named`/`Env::names`. - lazy_anon: Option>, - /// Explicit legacy identities or a bound context. Candidate IDs alone - /// provide no permission to execute a native reduction/conversion rule. - pub(crate) prims: super::primitive_context::PrimitiveContext, - - // -- Thread-local context -- - /// Local variable types, indexed by de Bruijn level. - pub ctx: Vec>, - /// Let-bound values, parallel to `ctx`. `Some(val)` for let-bindings, `None` - /// for lambda/forall bindings. Used for let-variable zeta-reduction in whnf_core. - pub let_vals: Vec>>, - /// Number of active let-bindings in `ctx`. - 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: CtxAddr, - /// Stack of previous ctx_ids for O(1) pop. - ctx_id_stack: Vec, - - // -- Thread-local optimization -- - /// Union-find for transitive def-eq caching (lean4lean EquivManager). - /// Thread-local: path halving mutates on reads, not safe to share. - pub equiv_manager: EquivManager, - - // -- Thread-local control -- - /// When true, `infer` skips def-eq checks (arg-type and let-value validation). - pub infer_only: bool, - /// Counter incremented while inside def-eq's cheap projection reductions. - /// Used by `is_def_eq` to route cheap false negatives into a cheap-only - /// cache while projected values are reduced structurally instead of through - /// full WHNF. - pub cheap_recursion_depth: u32, - /// Avoid recursively starting speculative projection-first comparisons. - pub(crate) in_projection_probe: bool, - /// Leaf conversion inside an application worklist must not start another - /// worklist. The original conversion algorithm remains its fallback. - pub(crate) in_app_congruence: bool, - /// Disable nested binder-batch probes, including throughout a failed - /// probe's ordinary recursive fallback. - pub(crate) in_binder_batch: bool, - /// When true, the Bool.true fast-path in is_def_eq fires even on open terms. - pub eager_reduce: bool, - /// The reductions outside the certified specification set that this - /// checker takes: none, except in unit tests of those reducers (see - /// [`crate::extra_reductions`]). Configuration, so `reset` keeps it. The - /// `KEnv` caches do not record it, so a checker that changes it must own - /// its environment. - pub(crate) extra_reductions: ExtraReductions, - /// Current def-eq recursion depth. - pub def_eq_depth: u32, - /// Stack depth of active `IX_DEF_EQ_TRACE` outer frames. While > 0, - /// inner def-eq tier dumps fire too. Diagnostic-only. - pub def_eq_trace_depth: u32, - /// Peak def-eq depth (diagnostics). - pub def_eq_peak: u32, - /// Shared recursive fuel remaining for this constant check. - pub rec_fuel: u64, - /// Native stack this checker keeps in reserve (see [`STACK_RESERVE_MAX`]). - stack_reserve: usize, - /// Unspent outer fuel temporarily withheld by same-head probes. Only used - /// to preserve the non-Regular startup window inside a larger Regular - /// slice; it never increases the fuel available to a nested computation. - pub(crate) same_head_fuel_reserve: u64, - /// Per-declaration admission history, not cached conversion facts. - pub(crate) same_head_backoff: super::def_eq::SameHeadBackoff, - /// Bounded non-semantic history for selective dependent-prefix caching. - pub(crate) prefix_admission: super::infer::PrefixAdmission, - /// Optional diagnostic label for the current top-level constant. - pub debug_label: Option, - - // -- Sharding profiler (see `plans/sharding.md`) -- - /// Address of the constant currently being checked, used to attribute - /// recorded heartbeats and delta-unfold edges. `None` unless a `profile_sink` - /// is installed on `env`; set by `begin_const` after each per-constant reset. - pub(crate) cur_const: Option
, - /// Addresses of constants whose bodies were delta-unfolded during the current - /// constant's check. Drained per constant by `record_current_fuel_used`. - pub(crate) delta_targets: FxHashSet
, - /// Gated, bounded heavy-hitter sampler for fuel-exhaustion diagnostics. - /// Populated only when `IX_HOT_MISSES=1`; never retains expressions. - hot_misses: HotMisses, - - /// Memoization cache for [`Self::ctx_addr_for_lbr`]. - /// - /// `ctx_addr_for_lbr(lbr)` is a pure function of `(self.ctx_id, lbr)`: - /// the function walks `self.ctx` from a depth-derived start, runs a - /// fixpoint over loose-bound-variable closures, and finalizes a blake3 - /// hash of the suffix. With millions of cache probes per big mathlib - /// block (each `whnf_key` / `infer_key` / `def_eq_ctx_key` triggers - /// one), this dominates lookup overhead. Memoizing on `(ctx_id, lbr)` - /// is sound because two contexts sharing the same `ctx_id` are bytewise - /// 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<(CtxAddr, u64), CtxAddr>, - - // -- Free-variable infrastructure -- - /// Local context for fvar-based binder opening. Some validation paths still - /// use the legacy `ctx`/`let_vals` stack, so `depth()` accounts for both - /// during the transition. - pub lctx: super::lctx::LocalContext, -} - -impl<'a, M: KernelMode> TypeChecker<'a, M> { - pub fn new(env: &'a mut KEnv) -> Self { - let prims = super::primitive_context::PrimitiveContext::Legacy( - std::sync::Arc::new(env.prims().clone()), - ); - Self::with_primitive_context(env, prims) - } - - fn with_primitive_context( - env: &'a mut KEnv, - prims: super::primitive_context::PrimitiveContext, - ) -> Self { - let extra_reductions = prims.extra_reductions(); - TypeChecker { - env, - lazy_ixon: None, - lazy_anon: None, - ixon_ingress: false, - prims, - ctx: Vec::new(), - let_vals: Vec::new(), - num_let_bindings: 0, - ctx_id: empty_ctx_addr(), - ctx_id_stack: Vec::new(), - equiv_manager: EquivManager::new(), - infer_only: false, - cheap_recursion_depth: 0, - in_projection_probe: false, - in_app_congruence: false, - in_binder_batch: false, - eager_reduce: false, - extra_reductions, - def_eq_depth: 0, - def_eq_trace_depth: 0, - def_eq_peak: 0, - rec_fuel: max_rec_fuel(), - stack_reserve: stack_reserve(), - same_head_fuel_reserve: 0, - same_head_backoff: Default::default(), - prefix_admission: Default::default(), - debug_label: None, - cur_const: None, - delta_targets: FxHashSet::default(), - hot_misses: HotMisses::default(), - ctx_addr_cache: FxHashMap::default(), - lctx: super::lctx::LocalContext::new(), - } - } - - pub fn primitives(&self) -> Result<&Primitives, TcError> { - self.prims.table().ok_or_else(|| TcError::Other(self.prims.unavailable())) - } - - pub(super) fn optional_primitives(&self) -> Option<&Primitives> { - self.prims.table() - } - - /// Refuse unless rules that need `role` may run: always for unbound input, - /// and for a bound artifact only once its binding's group for `role` has - /// been admitted. Support that a check cannot do without (literal typing, - /// literal/constructor conversion) calls this; an optional acceleration - /// instead finds the role absent from the table and does not fire. - pub(crate) fn require_role( - &self, - role: ixon::role_table::Role, - ) -> Result<(), TcError> { - if self.prims.admits(role) { - Ok(()) - } else { - Err(TcError::Other(format!( - "{} support is not admitted for this bound artifact", - role.lean_name() - ))) - } - } - - /// Whether a definition of this kind and safety may be unfolded: a - /// definition or theorem, and under the safety tier not a partial one. - /// - /// The certified checker keeps unsafe definitions opaque too, but it also - /// declines unsafe inductives, which this kernel checks as Lean does. The - /// unsafe definitions over them (their generated recursion helpers and - /// equations, as in Mathlib's Aesop) are checked by unfolding; kept - /// opaque, their checks exhaust memory instead of failing. So unsafe - /// definitions unfold here, beside the unsafe-inductive divergence. - pub(crate) fn unfolds( - &self, - kind: DefKind, - safety: DefinitionSafety, - ) -> bool { - matches!(kind, DefKind::Definition | DefKind::Theorem) - && (safety != DefinitionSafety::Partial - || !self.prims.follows_safety_tier()) - } - - /// Under the safety tier, a safe inductive's or constructor's type may - /// not mention a partial or unsafe constant. - pub(crate) fn fence_safe_type( - &mut self, - ty: &KExpr, - is_unsafe: bool, - ) -> Result<(), TcError> { - if is_unsafe || !self.prims.follows_safety_tier() { - return Ok(()); - } - self.check_no_unsafe_refs(ty, DefinitionSafety::Safe) - } - - /// Whether `id` is one of the table's four quotient primitives. - pub(crate) fn is_quot_primitive( - &self, - id: &KId, - ) -> Result> { - let p = self.primitives()?; - Ok( - [&p.quot_type, &p.quot_ctor, &p.quot_lift, &p.quot_ind] - .iter() - .any(|primitive| primitive.addr == id.addr), - ) - } - - pub(super) fn check_input_context(&self) -> Result<(), TcError> { - match self.prims.entry_error() { - Some(error) => Err(TcError::Other(error.into())), - None => Ok(()), - } - } - - pub fn new_with_lazy_ixon( - env: &'a mut KEnv, - ixon_env: &'a IxonEnv, - lookups: &'a IxonIngressLookups, - ) -> Self { - #[cfg(not(target_arch = "riscv64"))] - if ixon_env.role_binding.is_some() { - return Self::with_primitive_context( - env, - super::primitive_context::PrimitiveContext::UnpreparedBound, - ); - } - if !env.has_prims() { - let prims = Primitives::from_addr_names(|addr| { - lookups.name_for_addr(addr).cloned() - }); - let _ = env.set_prims(prims); - } - let mut tc = Self::new(env); - // Every declaration this checker sees arrives through Ixon ingress, - // which rejects local definition cycles before publishing a block, and - // external references are content addressed; the per-check dependency - // traversal is therefore redundant here, as it is for `IxonChecker`. - tc.ixon_ingress = true; - tc.lazy_ixon = Some(LazyIxonIngress { - input: super::input::Input::Legacy(ixon_env), - lookups, - faulted_addrs: FxHashSet::default(), - }); - tc - } - - #[cfg(not(target_arch = "riscv64"))] - pub(crate) fn for_artifact_named( - env: &'a mut KEnv, - source: &'a super::role_admission::ArtifactSource, - lookups: &'a IxonIngressLookups, - prims: super::primitive_context::PrimitiveContext, - ) -> Self { - let mut tc = Self::with_primitive_context(env, prims); - tc.ixon_ingress = true; - tc.lazy_ixon = Some(LazyIxonIngress { - input: super::input::Input::Artifact(source), - lookups, - faulted_addrs: FxHashSet::default(), - }); - tc - } - - pub fn try_get_const( - &mut self, - id: &KId, - ) -> Result>, TcError> { - self.check_input_context()?; - if let Some(c) = self.env.get(id) { - return Ok(Some(c)); - } - let lazy_enabled = self.lazy_ixon.is_some() || self.lazy_anon.is_some(); - self.lazy_ingress_addr(&id.addr)?; - match self.env.get(id) { - Some(c) => Ok(Some(c)), - None if lazy_enabled => Err(TcError::UnknownConst(id.addr.clone())), - None => Ok(None), - } - } - - pub fn get_const(&mut self, id: &KId) -> Result, TcError> { - self - .try_get_const(id)? - .ok_or_else(|| TcError::UnknownConst(id.addr.clone())) - } - - pub fn has_const(&mut self, id: &KId) -> Result> { - Ok(self.try_get_const(id)?.is_some()) - } - - pub fn try_get_block( - &mut self, - id: &KId, - ) -> Result>>, TcError> { - self.check_input_context()?; - if let Some(members) = self.env.get_block(id) { - return Ok(Some(members)); - } - self.lazy_ingress_addr(&id.addr)?; - Ok(self.env.get_block(id)) - } - - fn lazy_ingress_addr(&mut self, addr: &Address) -> Result<(), TcError> { - // Anon-mode lazy path. The `lazy_anon` field is only ever set by - // `TypeChecker::::new_with_lazy_anon` (defined in a non-generic - // impl block below), so its presence is a compile-time witness that - // `M = Anon`. The transmute below relies on this invariant. - if let Some(lazy) = self.lazy_anon.as_mut() { - if !lazy.faulted_addrs.insert(addr.clone()) { - return Ok(()); - } - // SAFETY: `lazy_anon` is only set by the `M = Anon` constructor, - // so this cast is the identity when reached. `KEnv` and - // `KEnv` have the same layout iff `M == Anon`; both - // `KernelMode` impls (`ZMode`, `ZMode`) are layout- - // compatible per-field only when `M` matches. The invariant holds. - let env_anon: &mut KEnv = unsafe { - &mut *(self.env as *mut KEnv).cast::>() - }; - return ingress_anon_addr_shallow_input(env_anon, lazy.input, addr) - .map(|_| ()) - .map_err(|msg| { - TcError::Other(format!("lazy anon ingress {}: {msg}", addr.hex())) - }); - } - let Some(lazy) = self.lazy_ixon.as_mut() else { - return Ok(()); - }; - if !lazy.faulted_addrs.insert(addr.clone()) { - return Ok(()); - } - ingress_addr_with_input(self.env, lazy.input, lazy.lookups, addr) - .map(|_| ()) - .map_err(|msg| { - TcError::Other(format!("lazy ingress {}: {msg}", addr.hex())) - }) - } - - // ----------------------------------------------------------------------- - // Context management - // ----------------------------------------------------------------------- - - /// Current logical binding depth. - /// - /// During the FVar transition, some code pushes legacy de-Bruijn locals into - /// `ctx` while newer code opens binders into `lctx`. Most paths use one or - /// the other, but mixed validation code can observe both; the logical depth - /// is the sum of the two stacks. - pub fn depth(&self) -> u64 { - (self.ctx.len() + self.lctx.len()) as u64 - } - - /// WHNF cache key: (expr_hash, ctx_hash). - /// - /// Uses the same suffix-aware key shape as [`infer_key`]: closed expressions - /// (lbr == 0) collapse to the empty context hash, and open expressions use - /// `ctx_addr_for_lbr(e.lbr())` to capture only the context suffix reachable - /// from the term's loose bound variables. - /// - /// Soundness: WHNF only consults the local context in two places, and - /// each is bounded by `e.lbr()`: - /// (1) let-zeta: `Var(i)` reduction looks up `let_vals[level]` for `i < e.lbr` - /// — frames `≥ depth - e.lbr` are covered by the suffix and `ctx_addr_for_lbr` - /// transitively closes over their types and values; - /// (2) recursive `infer` from `try_struct_eta_iota` / `synth_ctor_when_k` / - /// `try_proof_irrel` — those callees use their argument's own lbr, which - /// is `≤ e.lbr`, so the WHNF suffix dominates. - /// - /// 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, CtxAddr) { - (e.hash_key(), self.ctx_addr_for_lbr(e.lbr())) - } - - /// Type-inference cache key: (expr_hash, ctx_hash). - /// Closed expressions (lbr == 0) are context-independent. For open - /// expressions, only the context suffix reachable from their loose bound - /// variables matters. The suffix length is closed over binder type/value - /// 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, CtxAddr) { - (e.hash_key(), self.ctx_addr_for_lbr(e.lbr())) - } - - /// Context key for a definitional-equality pair. - /// - /// Def-eq may inspect both sides through WHNF, inference, proof - /// irrelevance, eta, and structural recursion. All of those operations are - /// bounded by the loose-bound-variable range reachable from the compared - /// 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) -> CtxAddr { - self.ctx_addr_for_lbr(a.lbr().max(b.lbr())) - } - - pub(crate) fn ctx_addr_for_lbr(&mut self, lbr: u64) -> CtxAddr { - if lbr == 0 || self.ctx.is_empty() { - return empty_ctx_addr(); - } - - // Memoize on (ctx_id, lbr) — the result is a pure function of these - // two inputs (ctx_id content-addresses the suffix-relevant prefix of - // self.ctx). Hot path on big mathlib blocks; called once per - // whnf_key / infer_key / def_eq_ctx_key. - let cache_key = (self.ctx_id, lbr); - if let Some(cached) = self.ctx_addr_cache.get(&cache_key) { - return *cached; - } - - let n = self.ctx.len(); - let mut need = usize::try_from(lbr).unwrap_or(usize::MAX).min(n); - - loop { - let start = n - need; - let mut next_need = need; - for i in start..n { - let frame_offset = n - i; - let ty_need = usize::try_from(self.ctx[i].lbr()).unwrap_or(usize::MAX); - next_need = next_need.max(frame_offset.saturating_add(ty_need)); - if let Some(val) = &self.let_vals[i] { - let val_need = usize::try_from(val.lbr()).unwrap_or(usize::MAX); - next_need = next_need.max(frame_offset.saturating_add(val_need)); - } - } - next_need = next_need.min(n); - if next_need == need { - break; - } - need = next_need; - } - - let result = if need == n { - self.ctx_id - } else { - let mut h = blake3::Hasher::new(); - h.update(b"ctx.suffix"); - h.update(&(need as u64).to_le_bytes()); - for i in (n - need)..n { - match &self.let_vals[i] { - Some(val) => { - h.update(b"let"); - 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().to_le_bytes()); - }, - } - } - h.finalize() - }; - - self.ctx_addr_cache.insert(cache_key, result); - result - } - - /// 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().to_le_bytes()); - h.update(self.ctx_id.as_bytes()); - self.ctx_id_stack.push(self.ctx_id); - self.ctx_id = h.finalize(); - self.ctx.push(ty); - self.let_vals.push(None); - } - - /// 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().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(); - self.ctx.push(ty); - self.let_vals.push(Some(val)); - self.num_let_bindings += 1; - } - - pub fn fresh_fvar_id(&mut self) -> FVarId { - self.env.fresh_fvar_id() - } - - /// Pop the most recent local variable. - pub fn pop_local(&mut self) { - if let Some(Some(_)) = self.let_vals.pop() { - self.num_let_bindings -= 1; - } - self.ctx.pop(); - self.ctx_id = self.ctx_id_stack.pop().unwrap_or_else(empty_ctx_addr); - } - - /// Look up a let-bound variable's value, lifted to the current depth. - /// Returns None if the variable is lambda/forall-bound (not a let). - pub fn lookup_let_val(&mut self, idx: u64) -> Option> { - let n = self.ctx.len(); - let idx_us = usize::try_from(idx).ok()?; - if idx_us >= n { - return None; - } - let level = n - 1 - idx_us; - let val = self.let_vals[level].as_ref()?.clone(); - Some(lift(&mut self.env.intern, &val, idx + 1, 0)) - } - - /// Whether a de-Bruijn variable points at a let-bound local. - pub fn is_let_var(&self, idx: u64) -> bool { - let n = self.ctx.len(); - let Some(idx_us) = usize::try_from(idx).ok() else { - return false; - }; - if idx_us >= n { - return false; - } - let level = n - 1 - idx_us; - self.let_vals[level].is_some() - } - - /// Save current depth for later restore. - pub fn save_depth(&self) -> usize { - self.ctx.len() - } - - /// Restore context to a previously saved depth. - pub fn restore_depth(&mut self, saved: usize) { - while self.ctx.len() > saved { - self.pop_local(); - } - } - - // ----------------------------------------------------------------------- - // Free-variable binder opening helpers - // ----------------------------------------------------------------------- - - /// Open a binder by minting a fresh [`FVarId`], pushing a `CDecl` to - /// `lctx`, and instantiating `body` so its `Var(0)` becomes the new - /// fvar (with `Var(>=1)` shifting down). Returns the opened body and - /// the fresh fvar id (the caller may pass `_` to discard). - /// - /// Mirrors lean4lean's `withLocalDecl` in shape; differs in that the - /// caller is responsible for `lctx.truncate(saved_len)` when leaving - /// the binder scope. - pub fn open_binder( - &mut self, - name: M::MField, - bi: M::MField, - ty: KExpr, - body: &KExpr, - ) -> (KExpr, FVarId) { - self.open_binder_contract( - name, - bi, - ixon::contract::BinderContract::default(), - ixon::contract::ValueContract::shared(), - ty, - body, - ) - } - - pub fn open_binder_contract( - &mut self, - name: M::MField, - bi: M::MField, - contract: ixon::contract::BinderContract, - result: ixon::contract::ValueContract, - ty: KExpr, - body: &KExpr, - ) -> (KExpr, FVarId) { - let fv_id = self.fresh_fvar_id(); - let fv = self.intern(KExpr::fvar(fv_id, name.clone())); - self.lctx.push(fv_id, LocalDecl::CDecl { name, bi, ty, contract, result }); - let body_open = instantiate_rev(&mut self.env.intern, body, &[fv]); - (body_open, fv_id) - } - - /// Anonymous variant of [`Self::open_binder`] that uses - /// `Name::anon()` / `BinderInfo::Default`. Convenient for kernel-internal - /// walks (inductive validation, recursor synthesis) that don't carry - /// user-visible binder metadata. - pub fn open_binder_anon( - &mut self, - ty: KExpr, - body: &KExpr, - ) -> (KExpr, FVarId) { - let name = M::meta_field(ix_common::env::Name::anon()); - let bi = M::meta_field(ix_common::env::BinderInfo::Default); - self.open_binder(name, bi, ty, body) - } - - /// Like [`Self::open_binder`] but also returns the fvar `KExpr` itself - /// (for callers that need to record it in a Vec for later - /// abstract_fvars / structural identity comparisons). - pub fn open_binder_with_fv( - &mut self, - name: M::MField, - bi: M::MField, - ty: KExpr, - body: &KExpr, - ) -> (KExpr, KExpr, FVarId) { - let fv_id = self.fresh_fvar_id(); - let fv = self.intern(KExpr::fvar(fv_id, name.clone())); - self.lctx.push(fv_id, LocalDecl::cdecl(name, bi, ty)); - let body_open = - instantiate_rev(&mut self.env.intern, body, std::slice::from_ref(&fv)); - (body_open, fv, fv_id) - } - - /// Anonymous-name variant of [`Self::open_binder_with_fv`]. - pub fn open_binder_anon_with_fv( - &mut self, - ty: KExpr, - body: &KExpr, - ) -> (KExpr, KExpr, FVarId) { - let name = M::meta_field(ix_common::env::Name::anon()); - let bi = M::meta_field(ix_common::env::BinderInfo::Default); - self.open_binder_with_fv(name, bi, ty, body) - } - - /// Push an `LDecl` for a let-bound fvar and instantiate the body. Returns - /// the opened body and the fresh fvar id. Mirrors `withLetDecl`-shaped - /// flows (e.g. inductive validation that needs to model the let value - /// for downstream WHNF zeta-reduction). - pub fn open_let( - &mut self, - name: M::MField, - ty: KExpr, - val: KExpr, - body: &KExpr, - ) -> (KExpr, FVarId) { - self.open_let_contract( - name, - ixon::contract::LetContract::plain(false), - ty, - val, - body, - ) - } - - pub fn open_let_contract( - &mut self, - name: M::MField, - contract: ixon::contract::LetContract, - ty: KExpr, - val: KExpr, - body: &KExpr, - ) -> (KExpr, FVarId) { - let fv_id = self.fresh_fvar_id(); - let fv = self.intern(KExpr::fvar(fv_id, name.clone())); - self.lctx.push(fv_id, LocalDecl::LDecl { name, ty, val, contract }); - let body_open = instantiate_rev(&mut self.env.intern, body, &[fv]); - (body_open, fv_id) - } - - /// Push a fresh fvar declaration without any body to instantiate. - /// Useful for paths that introduce a binder for type-tracking purposes - /// only (e.g. inductive validation walks where the binder is consumed - /// later or in parallel). Returns the fvar id and the interned fvar - /// expression. - pub fn push_fvar_decl_anon(&mut self, ty: KExpr) -> (FVarId, KExpr) { - let name = M::meta_field(ix_common::env::Name::anon()); - let bi = M::meta_field(ix_common::env::BinderInfo::Default); - let fv_id = self.fresh_fvar_id(); - let fv = self.intern(KExpr::fvar(fv_id, name.clone())); - self.lctx.push(fv_id, LocalDecl::cdecl(name, bi, ty)); - (fv_id, fv) - } - - /// Look up a bound variable's type, lifted to the current depth. - pub fn lookup_var(&mut self, idx: u64) -> Result, TcError> { - let n = self.ctx.len(); - let idx_us = u64_to_usize::(idx)?; - if idx_us >= n { - return Err(TcError::VarOutOfRange { idx, ctx_len: n }); - } - let level = n - 1 - idx_us; - let ty = self.ctx[level].clone(); - Ok(lift(&mut self.env.intern, &ty, idx + 1, 0)) - } - - // ----------------------------------------------------------------------- - // Universe helpers - // ----------------------------------------------------------------------- - - /// WHNF, then ensure it's a Sort. Returns the universe level. - pub fn ensure_sort(&mut self, e: &KExpr) -> Result, TcError> { - // Fast path: already a Sort, skip WHNF + tick. - if let ExprData::Sort(u, _) = e.data() { - return Ok(u.clone()); - } - let w = self.whnf(e)?; - match w.data() { - ExprData::Sort(u, _) => Ok(u.clone()), - _ => Err(TcError::TypeExpected), - } - } - - /// WHNF, then ensure it's a forall (All). Returns (domain, codomain). - pub fn ensure_forall( - &mut self, - e: &KExpr, - ) -> Result<(KExpr, KExpr), TcError> { - // Fast path: already a forall, skip WHNF + tick. - if let ExprData::All(_, _, _, _, a, b, _) = e.data() { - return Ok((a.clone(), b.clone())); - } - let w = self.whnf(e)?; - match w.data() { - ExprData::All(_, _, _, _, a, b, _) => Ok((a.clone(), b.clone())), - _ => Err(TcError::FunExpected { e: e.clone(), whnf: w }), - } - } - - /// Substitute universe parameters: replace `Param(i)` with `us[i]`. - /// - /// Returns `Err(UnivParamOutOfRange)` if any interior `Param(i)` has - /// `i >= us.len()`. Callers are expected to have validated the universe - /// arity upstream (e.g. `infer` of a `Const` node — see - /// `src/ix/kernel/infer.rs:41`); the `Result` here is defense-in-depth - /// against code paths that reach substitution without that check. - pub fn instantiate_univ_params( - &mut self, - e: &KExpr, - us: &[KUniv], - ) -> Result, TcError> { - if us.is_empty() { - return Ok(e.clone()); - } - // Per-call pointer-identity memoization: universe substitution does - // not change the term's bound-variable structure, so two sub-terms - // with the same content hash produce the same result for the same - // `us`. Shared sub-terms in a body (common under hash-consing) get - // visited once per call. See `src/ix/kernel/subst.rs` for the - // analogous optimisation on de-Bruijn substitution and the general - // "walk the DAG as a DAG" rationale. - let mut cache: FxHashMap> = FxHashMap::default(); - self.inst_univ_inner(e, us, &mut cache) - } - - fn inst_univ_inner( - &mut self, - e: &KExpr, - us: &[KUniv], - cache: &mut FxHashMap>, - ) -> Result, TcError> { - // Key by content hash only — `us` is fixed across the whole call. - let key = e.hash_key(); - if let Some(cached) = cache.get(&key) { - return Ok(cached.clone()); - } - - let result = match e.data() { - ExprData::Var(..) - | ExprData::FVar(..) - | ExprData::Nat(..) - | ExprData::Str(..) => { - // These have no universe parameters, so substitution is a no-op. - // Cache the pass-through so the ptr-identity check above fires - // for subsequent visits to the same sub-term. - let r = e.clone(); - cache.insert(key, r.clone()); - return Ok(r); - }, - - ExprData::Sort(u, _) => { - let u2 = self.subst_univ(u, us)?; - KExpr::sort(u2) - }, - - ExprData::Const(id, cur_us, _) => { - let new_us: Box<[KUniv]> = cur_us - .iter() - .map(|u| self.subst_univ(u, us)) - .collect::, _>>()?; - KExpr::cnst(id.clone(), new_us) - }, - - ExprData::App(f, a, _) => { - let f2 = self.inst_univ_inner(f, us, cache)?; - let a2 = self.inst_univ_inner(a, us, cache)?; - let r = self.env.intern.intern_app(&f2, &a2); - cache.insert(key, r.clone()); - return Ok(r); - }, - - ExprData::Lam(name, bi, contract, ty, body, _) => { - let ty2 = self.inst_univ_inner(ty, us, cache)?; - let body2 = self.inst_univ_inner(body, us, cache)?; - KExpr::lam_contract(name.clone(), bi.clone(), *contract, ty2, body2) - }, - - ExprData::All(name, bi, contract, result, ty, body, _) => { - let ty2 = self.inst_univ_inner(ty, us, cache)?; - let body2 = self.inst_univ_inner(body, us, cache)?; - let r = self.env.intern.intern_all_contract( - name.clone(), - bi.clone(), - *contract, - *result, - &ty2, - &body2, - ); - cache.insert(key, r.clone()); - return Ok(r); - }, - - ExprData::Let(name, ty, val, body, contract, _) => { - let ty2 = self.inst_univ_inner(ty, us, cache)?; - let val2 = self.inst_univ_inner(val, us, cache)?; - let body2 = self.inst_univ_inner(body, us, cache)?; - KExpr::let_contract(name.clone(), *contract, ty2, val2, body2) - }, - - ExprData::Prj(id, field, val, _) => { - let val2 = self.inst_univ_inner(val, us, cache)?; - KExpr::prj(id.clone(), *field, val2) - }, - }; - let interned = self.env.intern.intern_expr(result); - cache.insert(key, interned.clone()); - Ok(interned) - } - - /// Substitute universe params in a universe level. - /// - /// Fails with `UnivParamOutOfRange { idx, bound }` if an interior - /// `Param(idx)` references beyond `us.len()`. In a well-typed kernel - /// run, every call site supplies `us` whose length matches the - /// arity of the enclosing constant (validated by `infer` at the Const - /// gate), so this error never fires on well-formed input. It exists - /// to turn any internal invariant slip into a loud failure instead of - /// a silent orphan `Param` propagating downstream. - pub fn subst_univ( - &mut self, - u: &KUniv, - us: &[KUniv], - ) -> Result, TcError> { - match u.data() { - UnivData::Zero(_) => Ok(u.clone()), - UnivData::Param(i, _, _) => { - match usize::try_from(*i).ok().and_then(|i| us.get(i)) { - Some(v) => Ok(v.clone()), - None => { - Err(TcError::UnivParamOutOfRange { idx: *i, bound: us.len() }) - }, - } - }, - UnivData::Succ(inner, _) => { - let inner2 = self.subst_univ(inner, us)?; - Ok(KUniv::succ(inner2)) - }, - UnivData::Max(a, b, _) => { - let a2 = self.subst_univ(a, us)?; - let b2 = self.subst_univ(b, us)?; - Ok(KUniv::max(a2, b2)) - }, - UnivData::IMax(a, b, _) => { - let a2 = self.subst_univ(a, us)?; - let b2 = self.subst_univ(b, us)?; - Ok(KUniv::imax(a2, b2)) - }, - } - } - - // ----------------------------------------------------------------------- - // Per-constant reset (thread-local state only) - // ----------------------------------------------------------------------- - - /// Reset thread-local state between constants. Global caches in `KEnv` are - /// NOT cleared — they grow monotonically and are shared across all TCs. - pub fn reset(&mut self) { - self.ctx.clear(); - self.let_vals.clear(); - self.num_let_bindings = 0; - self.ctx_id = empty_ctx_addr(); - self.ctx_id_stack.clear(); - self.equiv_manager.clear(); - self.infer_only = false; - self.cheap_recursion_depth = 0; - self.in_projection_probe = false; - self.in_app_congruence = false; - self.in_binder_batch = false; - self.eager_reduce = false; - self.def_eq_depth = 0; - self.def_eq_peak = 0; - // Record fuel consumed by the *previous* constant check (if any) before - // wiping it. `Drop` records the final check in a TypeChecker's lifetime. - self.record_current_fuel_used(); - // Per-constant cache isolation for sound/faithful profile recording: clear - // the cross-constant reduction memo so the next constant re-executes every - // delta-unfold and its heartbeats reflect in-circuit (un-memoized) cost. - if self.env.profile_sink.as_ref().is_some_and(|s| s.isolate) { - self.env.clear_reduction_caches(); - } - self.rec_fuel = max_rec_fuel(); - self.same_head_fuel_reserve = 0; - self.same_head_backoff = Default::default(); - self.prefix_admission = Default::default(); - self.hot_misses.clear(); - // Reset the local context (it must always be empty between constants). - // The fvar id counter lives on KEnv and is intentionally not reset here: - // caches also live on KEnv, so reused fvar ids would make open-term cache - // entries unsound across TypeChecker instances. - self.lctx = super::lctx::LocalContext::new(); - } - - pub fn set_debug_label(&mut self, label: impl Into) { - self.debug_label = Some(label.into()); - } - - pub fn debug_label_matches_env(&self) -> bool { - match crate::env_var("IX_KERNEL_DEBUG_CONST") { - Ok(filter) if filter.is_empty() => true, - Ok(filter) => { - self.debug_label.as_ref().is_some_and(|label| label.contains(&filter)) - }, - Err(_) => true, - } - } - - /// Decline when the thread has less native stack left than this checker's - /// reserve. Where the stack cannot be measured, no bound applies. - #[inline] - pub(crate) fn check_stack(&self) -> Result<(), TcError> { - #[cfg(not(target_arch = "riscv64"))] - if stacker::remaining_stack().is_some_and(|left| left < self.stack_reserve) - { - return Err(TcError::StackExhausted); - } - Ok(()) - } - - /// Consume one unit of shared recursive fuel. Returns Err if exhausted, - /// or if the native stack is (see [`Self::check_stack`]): every recursive - /// WHNF and conversion entry ticks. - #[inline] - pub fn tick(&mut self) -> Result<(), TcError> { - self.check_stack()?; - if self.rec_fuel == 0 { - self.dump_guard_stack("recursive-fuel"); - if crate::env_var("IX_REC_FUEL_DUMP").is_ok() - && self.debug_label_matches_env() - { - // The CLI and Lean test executable do not install a `log` backend. - // This diagnostic is explicitly opt-in, so send its compact record - // directly to stderr (the backtrace below stays behind `log`). - eprintln!( - "[rec fuel] exhausted const={} depth={} def_eq_depth={} infer_only={} eager_reduce={}", - self.debug_label.as_deref().unwrap_or(""), - self.depth(), - self.def_eq_depth, - self.infer_only, - self.eager_reduce - ); - self.dump_hot_misses(); - log::info!("{}", std::backtrace::Backtrace::force_capture()); - } - return Err(TcError::MaxRecFuel); - } - self.rec_fuel -= 1; - Ok(()) - } - - /// Diagnostics only: at most four stacks per process, 100 lines each. - /// Capture only at a guard, never on the checking hot path; do not retain - /// expressions, change limits, or turn an exhausted check into a verdict. - pub(crate) fn dump_guard_stack(&self, guard: &str) { - if !*IX_GUARD_STACKS || !self.debug_label_matches_env() { - return; - } - if GUARD_STACK_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed) >= 4 - { - return; - } - eprintln!( - "[guard stack] {guard} const={} local_depth={} def_eq_depth={} fuel_used={}", - self.debug_label.as_deref().unwrap_or(""), - self.depth(), - self.def_eq_depth, - self.fuel_used(), - ); - let trace = std::backtrace::Backtrace::force_capture().to_string(); - for line in trace.lines().take(100) { - eprintln!("[guard stack] {line}"); - } - } - - /// Fuel consumed so far during the current check. Used by diagnostics that want - /// to report fuel consumed at a given point. - pub fn fuel_used(&self) -> u64 { - max_rec_fuel().saturating_sub(self.rec_fuel) - } - - pub fn finish_constant_accounting(&mut self) { - self.record_current_fuel_used(); - self.rec_fuel = max_rec_fuel(); - } - - fn record_current_fuel_used(&mut self) { - let used = self.fuel_used(); - if used > 0 { - self.env.perf.record_constant_fuel_used(used); - } - // Sharding profiler: flush the just-finished constant's heartbeats and - // delta-unfold edges. `delta_targets` is always drained (even when - // `cur_const` is unset) so producers never leak into the next constant. - if let Some(sink) = self.env.profile_sink.as_mut() { - let producers = std::mem::take(&mut self.delta_targets); - // Always drain the op counters (even when `cur_const` is unset) so they - // never leak into the next constant, mirroring `delta_targets`. - let ops = crate::profile::take_op_counts(); - if let Some(addr) = self.cur_const.take() { - sink.record(addr, used, producers, ops); - } - } - } - - /// Sharding profiler: mark `id` as the constant now being checked, so its - /// heartbeats and delta-unfold edges are attributed correctly. No-op unless a - /// `profile_sink` is installed. Called right after each per-constant `reset`. - #[inline] - pub(crate) fn begin_const(&mut self, id: &KId) { - if self.env.profile_sink.is_some() { - self.cur_const = Some(id.addr.clone()); - } - } - - /// Sharding profiler: record that the current constant delta-unfolds the body - /// of `id`. No-op unless a `profile_sink` is installed. - #[inline] - pub(crate) fn record_delta_target(&mut self, id: &KId) { - crate::perf::record_delta_histo(&id.addr); - if self.env.profile_sink.is_some() { - self.delta_targets.insert(id.addr.clone()); - } - } - - // ----------------------------------------------------------------------- - // Infer-only mode - // ----------------------------------------------------------------------- - - /// Run a closure with `infer_only` mode enabled. Restores the previous - /// mode on exit. In this mode, `infer` skips def-eq checks for App arg - /// types and Let value types — it only synthesizes the type. - pub fn with_infer_only(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { - let prev = self.infer_only; - self.infer_only = true; - let result = f(self); - self.infer_only = prev; - result - } - - // ----------------------------------------------------------------------- - // Reductions outside the certified specification set - // ----------------------------------------------------------------------- - - /// Whether this checker takes the reductions of `group`. - #[inline] - pub(crate) fn takes_extra(&self, group: ExtraReduction) -> bool { - self.extra_reductions.allows(group) - } - - /// Take every group of [`ExtraReduction`]. Only unit tests of those - /// reducers do, on their own environment. - #[cfg(test)] - pub(crate) fn enable_extra_reductions(&mut self) { - self.extra_reductions = ExtraReductions::ALL; - } - - /// Run a closure in a free-variable local-context scope. Declarations - /// pushed by the closure are discarded on both `Ok` and `Err` returns; - /// mutations to the rest of the checker state are retained. - pub(crate) fn with_lctx_scope( - &mut self, - f: impl FnOnce(&mut Self) -> R, - ) -> R { - let saved = self.lctx.len(); - let result = f(self); - self.lctx.truncate(saved); - result - } - - // ----------------------------------------------------------------------- - // Interning helper - // ----------------------------------------------------------------------- - - /// Check if expression is of the form `eagerReduce _ _` (2 args applied to the eagerReduce const). - pub fn is_eager_reduce(&self, e: &KExpr) -> bool { - let Some(primitives) = self.optional_primitives() else { - return false; - }; - let ExprData::App(f, _, _) = e.data() else { return false }; - let ExprData::App(head, _, _) = f.data() else { return false }; - matches!(head.data(), ExprData::Const(id, _, _) - if id.addr == primitives.eager_reduce.addr) - } - - /// Intern an expression through the mutable intern environment. - pub fn intern(&mut self, e: KExpr) -> KExpr { - self.env.intern.intern_expr(e) - } - - /// Intern a universe through the mutable intern environment. - pub fn intern_univ(&mut self, u: KUniv) -> KUniv { - self.env.intern.intern_univ(u) - } - - pub fn record_hot_miss(&mut self, phase: &'static str, e: &KExpr) { - if !*IX_HOT_MISSES { - return; - } - let context = (*IX_HOT_MISS_CTX) - .then(|| (self.ctx_addr_for_lbr(e.lbr()), self.depth())); - let key = MissKey { phase, a: *e.addr(), b: None, context }; - self.hot_misses.record(key, |out| { - use std::fmt::Write; - write!(out, "{phase} ")?; - write_hot_expr_shape(out, e)?; - write_hot_context(out, context) - }); - } - - pub fn record_hot_def_eq_miss(&mut self, a: &KExpr, b: &KExpr) { - if !*IX_HOT_MISSES { - return; - } - let context = - (*IX_HOT_MISS_CTX).then(|| (self.def_eq_ctx_key(a, b), self.depth())); - let key = - MissKey { phase: "defeq", a: *a.addr(), b: Some(*b.addr()), context }; - self.hot_misses.record(key, |out| { - use std::fmt::Write; - write!(out, "defeq ")?; - write_hot_expr_shape(out, a)?; - write!(out, " =?= ")?; - write_hot_expr_shape(out, b)?; - write_hot_context(out, context) - }); - } - - fn dump_hot_misses(&self) { - eprint!("{}", self.hot_miss_summary()); - } - - /// Snapshot opt-in miss counters without rerunning work or retaining terms. - /// The subject helper can report once at completion instead of dumping at - /// every exhausted speculative slice via `IX_REC_FUEL_DUMP`. - pub fn hot_miss_summary(&self) -> String { - if !*IX_HOT_MISSES { - return String::new(); - } - self.hot_misses.summary() - } -} - -// ----------------------------------------------------------------------- -// Anon-only constructors -// ----------------------------------------------------------------------- -// -// `new_with_lazy_anon` lives here so the `M = Anon` constraint is enforced -// at compile time. The dispatcher in `lazy_ingress_addr` relies on this: -// `lazy_anon` being `Some` is a witness that `M = Anon`, which makes the -// `&mut KEnv` → `&mut KEnv` cast a no-op transmute. - -impl<'a> TypeChecker<'a, super::mode::Anon> { - /// Construct an anon-mode typechecker with lazy on-demand ingress. - /// `anon_env` is expected to come from `Env::get_anon` (or otherwise - /// have empty metadata maps). The dispatcher in `lazy_ingress_addr` - /// never consults metadata regardless. - /// - /// Primitives are resolved by address only — `Primitives::from_addr_names` - /// receives a closure that always returns `None`, so the - /// `M::MField` slot ends up as `()` (always, in Anon mode). - pub fn new_with_lazy_anon( - env: &'a mut KEnv, - anon_env: &'a IxonEnv, - ) -> Self { - #[cfg(not(target_arch = "riscv64"))] - if anon_env.role_binding.is_some() { - return Self::with_primitive_context( - env, - super::primitive_context::PrimitiveContext::UnpreparedBound, - ); - } - if !env.has_prims() { - let prims = Primitives::from_addr_names(|_addr| None); - let _ = env.set_prims(prims); - } - let mut tc = Self::new(env); - tc.lazy_anon = Some(LazyAnonIngress { - input: super::input::Input::Legacy(anon_env), - faulted_addrs: FxHashSet::default(), - }); - tc - } - - #[cfg(not(target_arch = "riscv64"))] - pub(crate) fn for_artifact_anon( - env: &'a mut KEnv, - source: &'a super::role_admission::ArtifactSource, - _lookups: &'a IxonIngressLookups, - prims: super::primitive_context::PrimitiveContext, - ) -> Self { - let mut tc = Self::with_primitive_context(env, prims); - tc.ixon_ingress = true; - tc.lazy_anon = Some(LazyAnonIngress { - input: super::input::Input::Artifact(source), - faulted_addrs: FxHashSet::default(), - }); - tc - } -} - -// ----------------------------------------------------------------------- -// Free-standing helpers -// ----------------------------------------------------------------------- - -/// Check whether an expression mentions a constant with the given address. -/// Iterative (stack-based) — immune to stack overflow on deeply nested input. -pub fn expr_mentions_addr(e: &KExpr, addr: &Address) -> bool { - expr_mentions_any_addr(e, std::slice::from_ref(addr)) -} - -/// Check syntactic occurrences in one traversal for the entire address set. -/// No unfolding, context-dependent memoization or dependency traversal. -pub fn expr_mentions_any_addr( - e: &KExpr, - addrs: &[Address], -) -> bool { - expr_mentions_any_addr_impl(e, addrs, || {}) -} - -// Small trees are cheaper to inspect directly than to allocate a visited set. -// This is a fixed prefix budget, not an input-dependent heuristic: after it -// is spent the remaining walk is DAG-aware, so worst-case work stays linear -// in unique nodes/edges plus this constant prefix and its pending edges. -const OCCURRENCE_TREE_BUDGET: usize = 32; - -fn expr_mentions_any_addr_impl( - e: &KExpr, - addrs: &[Address], - mut on_visit: impl FnMut(), -) -> bool { - if addrs.is_empty() { - return false; - } - // Exact allocation identity, scoped to one fixed target set. Borrowing - // the root keeps every descendant alive, so pointer reuse is impossible. - // A shared subtree has the same occurrences at every binder depth. - let mut seen = FxHashSet::default(); - let mut tree_budget = OCCURRENCE_TREE_BUDGET; - let mut stack: Vec<&KExpr> = vec![e]; - while let Some(e) = stack.pop() { - // Zero-cost callback in production; tests bound attempted node visits - // so a regression cannot hang on an exponentially expanded diamond. - on_visit(); - if tree_budget > 0 { - tree_budget -= 1; - } else if !seen.insert(std::ptr::from_ref(e.data()).addr()) { - continue; - } - match e.data() { - ExprData::Const(id, _, _) => { - if addrs.contains(&id.addr) { - return true; - } - }, - ExprData::App(f, a, _) => { - stack.push(f); - stack.push(a); - }, - ExprData::Lam(_, _, _, ty, body, _) - | ExprData::All(_, _, _, _, ty, body, _) => { - stack.push(ty); - stack.push(body); - }, - ExprData::Let(_, ty, val, body, _, _) => { - stack.push(ty); - stack.push(val); - stack.push(body); - }, - ExprData::Prj(id, _, val, _) => { - if addrs.contains(&id.addr) { - return true; - } - stack.push(val); - }, - ExprData::Var(..) - | ExprData::FVar(..) - | ExprData::Sort(..) - | ExprData::Nat(..) - | ExprData::Str(..) => {}, - } - } - false -} - -/// Borrow the head of an application spine without collecting arguments or -/// cloning any Arcs. Does not reduce the expression. -pub(crate) fn app_head(mut e: &KExpr) -> &KExpr { - while let ExprData::App(f, _, _) = e.data() { - e = f; - } - e -} - -/// Borrow an application head and its arguments in source application order. -/// The caller-held root owns every returned node; nothing is borrowed from a -/// mutable interner/cache. Short spines need neither allocation nor Arc clones. -pub(crate) fn borrow_app_spine( - mut e: &KExpr, -) -> (&KExpr, smallvec::SmallVec<[&KExpr; 8]>) { - let mut args = smallvec::SmallVec::new(); - while let ExprData::App(f, a, _) = e.data() { - args.push(a); - e = f; - } - args.reverse(); - (e, args) -} - -/// Collect the application spine: `App(App(f, a1), a2)` → `(f, [a1, a2])`. -/// -/// Counts args first so the result `Vec` is allocated exactly once with -/// the correct capacity, sparing the first-push grow allocation on the -/// hot path. Most applications in mathlib have 1–8 args, so the count -/// pass is cheap (a chain walk) and saves one allocation + memcpy -/// compared to repeatedly growing from the default capacity. -pub fn collect_app_spine( - e: &KExpr, -) -> (KExpr, Vec>) { - // First pass: count arity without cloning. - let mut count = 0usize; - { - let mut cur = e; - while let ExprData::App(f, _, _) = cur.data() { - count += 1; - cur = f; - } - } - if count == 0 { - return (e.clone(), Vec::new()); - } - let mut args = Vec::with_capacity(count); - let mut cur = e; - while let ExprData::App(f, a, _) = cur.data() { - args.push(a.clone()); - cur = f; - } - args.reverse(); - (cur.clone(), args) -} - -#[cfg(test)] -mod spine_tests; - -fn write_hot_expr_shape( - out: &mut impl std::fmt::Write, - e: &KExpr, -) -> std::fmt::Result { - let mut head = e; - let mut arity = 0usize; - while let ExprData::App(f, _, _) = head.data() { - head = f; - arity += 1; - } - match head.data() { - ExprData::Var(i, _, _) => write!(out, "#{i}"), - ExprData::FVar(id, _, _) => write!(out, "{id}"), - // Do not render an arbitrarily large universe DAG or Nat literal. - ExprData::Sort(u, _) => write!(out, "Sort(uid{})", u.addr()), - ExprData::Const(id, us, _) => { - write_hot_id(out, id)?; - write!(out, ".{{{}}}", us.len()) - }, - ExprData::App(..) => unreachable!("peeled all applications"), - ExprData::Lam(..) => write!(out, "lam"), - ExprData::All(..) => write!(out, "forall"), - ExprData::Let(..) => write!(out, "let"), - ExprData::Prj(id, field, _, _) => { - write!(out, "Prj(")?; - write_hot_id(out, id)?; - write!(out, ".{field})") - }, - ExprData::Nat(..) => write!(out, "Nat(uid{})", head.addr()), - ExprData::Str(v, _, _) => write!(out, "Str(len={})", v.len()), - }?; - write!(out, "/{arity} lbr={} @uid{}", e.lbr(), e.addr()) -} - -fn write_hot_id( - out: &mut impl std::fmt::Write, - id: &KId, -) -> std::fmt::Result { - // Name::Display builds its whole `pretty()` string before passing it to - // the writer. A bounded output writer alone cannot limit that temporary. - // Check at most 64 borrowed components/256 bytes before invoking Display; - // oversized names fall back to their fixed-size declaration address. - let compact = M::meta_get(&id.name).is_none_or(|name| { - use ix_common::env::NameData; - let mut name = name; - let mut bytes = 256usize; - for _ in 0..64 { - let (parent, cost) = match name.as_data() { - NameData::Anonymous(_) => return true, - NameData::Str(parent, s, _) => (parent, s.len().saturating_add(1)), - NameData::Num(parent, n, _) => { - if n.to_u64().is_none() { - return false; - } - (parent, 21) - }, - }; - let Some(remaining) = bytes.checked_sub(cost) else { return false }; - bytes = remaining; - name = parent; - } - false - }); - if compact { write!(out, "{id}") } else { write!(out, "#{}", id.addr.hex()) } -} - -fn write_hot_context( - out: &mut impl std::fmt::Write, - context: Option<(CtxAddr, u64)>, -) -> std::fmt::Result { - if let Some((ctx, depth)) = context { - write!(out, " ctx={} depth={depth}", &ctx.to_hex()[..12])?; - } - Ok(()) -} - -#[cfg(test)] -mod scan_tests; - -#[cfg(test)] -mod tests { - use super::super::testing::{ - apps, cnst, mk_addr, mk_id, mk_name, pi, sort0, sort1, uzero, var, - }; - use super::*; - use crate::mode::Meta; - use ix_common::address::Address; - - fn new_tc() -> TypeChecker<'static, Meta> { - let env = Box::leak(Box::new(KEnv::::new())); - TypeChecker::new(env) - } - - #[test] - fn hot_miss_labels_do_not_pretty_print_unbounded_names() { - use ix_common::env::Name; - for name in [ - Name::str(Name::anon(), "λ".repeat(100_000)), - (0..128).fold(Name::anon(), |n, _| Name::str(n, "x".to_owned())), - ] { - let id = KId::::new(Address::hash(b"large-name"), name); - let mut out = String::new(); - write_hot_id(&mut out, &id).unwrap(); - assert_eq!(out, format!("#{}", id.addr.hex())); - } - let id = mk_id("List.below"); - let mut out = String::new(); - write_hot_id(&mut out, &id).unwrap(); - assert_eq!(out, format!("{id}")); - } - - // ---- Context push/pop ---- - - #[test] - fn push_pop_local_roundtrip() { - let mut tc = new_tc(); - assert_eq!(tc.depth(), 0); - tc.push_local(sort0()); - assert_eq!(tc.depth(), 1); - tc.push_local(sort1()); - assert_eq!(tc.depth(), 2); - tc.pop_local(); - assert_eq!(tc.depth(), 1); - tc.pop_local(); - assert_eq!(tc.depth(), 0); - } - - #[test] - fn fvar_ids_are_env_scoped_across_type_checkers() { - let mut env = KEnv::::new(); - let first = { - let mut tc = TypeChecker::new(&mut env); - tc.fresh_fvar_id() - }; - let second = { - let mut tc = TypeChecker::new(&mut env); - tc.fresh_fvar_id() - }; - assert_ne!(first, second); - assert_eq!(first.0, 0); - assert_eq!(second.0, 1); - } - - #[test] - fn push_let_increments_let_count() { - let mut tc = new_tc(); - assert_eq!(tc.num_let_bindings, 0); - tc.push_let(sort0(), sort0()); - assert_eq!(tc.num_let_bindings, 1); - tc.push_let(sort1(), sort1()); - assert_eq!(tc.num_let_bindings, 2); - tc.pop_local(); - assert_eq!(tc.num_let_bindings, 1); - tc.pop_local(); - assert_eq!(tc.num_let_bindings, 0); - } - - #[test] - fn push_local_does_not_touch_let_count() { - let mut tc = new_tc(); - tc.push_local(sort0()); - assert_eq!(tc.num_let_bindings, 0); - tc.push_let(sort0(), sort0()); - assert_eq!(tc.num_let_bindings, 1); - tc.push_local(sort0()); - assert_eq!(tc.num_let_bindings, 1); - tc.pop_local(); // pops the lambda-bound frame - assert_eq!(tc.num_let_bindings, 1); - tc.pop_local(); // pops the let - assert_eq!(tc.num_let_bindings, 0); - tc.pop_local(); // pops the original lambda - assert_eq!(tc.num_let_bindings, 0); - } - - // ---- ctx_id determinism and stack ---- - - #[test] - fn empty_ctx_id_is_the_same_const() { - let tc1 = new_tc(); - let tc2 = new_tc(); - assert_eq!(tc1.ctx_id, tc2.ctx_id); - assert_eq!(tc1.ctx_id, empty_ctx_addr()); - } - - #[test] - fn ctx_id_changes_when_pushing_different_types() { - let mut tc = new_tc(); - let initial = tc.ctx_id; - tc.push_local(sort0()); - let after_sort0 = tc.ctx_id; - assert_ne!(initial, after_sort0); - tc.push_local(sort1()); - let after_sort1 = tc.ctx_id; - assert_ne!(after_sort0, after_sort1); - } - - #[test] - fn ctx_id_same_pushes_yield_same_hash() { - // 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!(id1, tc2.ctx_id); - } - - #[test] - fn ctx_id_restores_on_pop() { - let mut tc = new_tc(); - let initial = tc.ctx_id; - tc.push_local(sort0()); - let level1 = tc.ctx_id; - tc.push_local(sort1()); - assert_ne!(level1, tc.ctx_id); - tc.pop_local(); - assert_eq!(tc.ctx_id, level1); - tc.pop_local(); - assert_eq!(tc.ctx_id, initial); - } - - #[test] - fn pop_from_empty_resets_to_empty_ctx_addr() { - let mut tc = new_tc(); - // Popping an empty stack must not panic — the implementation uses - // `unwrap_or_else(empty_ctx_addr)` as defensive fallback. - tc.pop_local(); - assert_eq!(tc.ctx_id, empty_ctx_addr()); - } - - #[test] - fn let_contributes_to_ctx_id_differently_than_local() { - let mut t_local = new_tc(); - let mut t_let = new_tc(); - t_local.push_local(sort0()); - t_let.push_let(sort0(), sort0()); - // Different frame domains: lambda vs let must hash distinctly. - assert_ne!(t_local.ctx_id, t_let.ctx_id); - } - - // ---- whnf_key ---- - - #[test] - fn whnf_key_empty_ctx_for_closed_expr() { - let mut tc = new_tc(); - let e = sort0(); - let (h, ctx) = tc.whnf_key(&e); - assert_eq!(h, e.hash_key()); - assert_eq!(ctx, empty_ctx_addr()); - } - - #[test] - fn whnf_key_includes_ctx_id_for_open_expr_without_lets() { - let mut tc = new_tc(); - // Push a lambda-bound local — num_let_bindings stays 0. - tc.push_local(sort0()); - let e = var(0); - let (h, ctx) = tc.whnf_key(&e); - assert_eq!(h, e.hash_key()); - assert_eq!(ctx, tc.ctx_id); - assert_ne!(ctx, empty_ctx_addr()); - } - - #[test] - fn whnf_key_includes_ctx_id_under_let_with_open_expr() { - let mut tc = new_tc(); - tc.push_let(sort0(), sort0()); - let e = var(0); - let (h, ctx) = tc.whnf_key(&e); - assert_eq!(h, e.hash_key()); - assert_ne!(ctx, empty_ctx_addr()); - assert_eq!(ctx, tc.ctx_id); - } - - #[test] - fn whnf_key_closed_expr_ignores_ctx_even_under_let() { - let mut tc = new_tc(); - tc.push_let(sort0(), sort0()); - let e = sort0(); // lbr == 0 - let (_, ctx) = tc.whnf_key(&e); - // Closed expression: empty ctx regardless of let-binding state. - assert_eq!(ctx, empty_ctx_addr()); - } - - #[test] - fn whnf_key_uses_suffix_across_different_outer_ctx() { - // The suffix-aware key should let an open subterm hit cache across - // different OUTER contexts when only the inner suffix matters. - // - // Both checkers push the same innermost local frame after a different - // 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 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!(outer1, tc2.ctx_id); - - let (h2, ctx2) = tc2.whnf_key(&e); - assert_eq!(h1, h2); - assert_eq!( - ctx1, ctx2, - "suffix-aware key should match across different outers" - ); - assert_ne!(ctx1, empty_ctx_addr()); - } - - // ---- infer_key ---- - - #[test] - fn infer_key_closed_expr_ignores_ctx() { - let mut tc = new_tc(); - tc.push_local(sort0()); - let e = sort0(); - let (h, ctx) = tc.infer_key(&e); - assert_eq!(h, e.hash_key()); - assert_eq!(ctx, empty_ctx_addr()); - } - - #[test] - fn infer_key_open_expr_includes_ctx_even_without_lets() { - let mut tc = new_tc(); - tc.push_local(sort0()); - let e = var(0); - let (h, ctx) = tc.infer_key(&e); - assert_eq!(h, e.hash_key()); - assert_eq!(ctx, tc.ctx_id); - assert_ne!(ctx, empty_ctx_addr()); - } - - // ---- lookup_var ---- - - #[test] - fn lookup_var_out_of_range() { - let mut tc = new_tc(); - tc.push_local(sort0()); - // idx 5 in a depth-1 context is OOR - let r = tc.lookup_var(5); - match r { - Err(TcError::VarOutOfRange { idx, ctx_len }) => { - assert_eq!(idx, 5); - assert_eq!(ctx_len, 1); - }, - other => panic!("expected VarOutOfRange, got {other:?}"), - } - } - - #[test] - fn lookup_var_returns_lifted_type() { - let mut tc = new_tc(); - // Outer binder: type is (Var 0). Inner binder: type is (Sort 1). - // lookup_var(1) should be the outer type lifted by 2 (depth - level = 2). - // Use a type with loose bvars so lifting is observable. - tc.push_local(var(3)); - tc.push_local(sort1()); - let t = tc.lookup_var(1).unwrap(); - // Lifted from Var(3) with lift-by-(idx+1)=2 → Var(3+2)=Var(5). - // The implementation calls `lift(&intern, &ty, idx + 1, 0)` which - // shifts all free bvars by idx+1. - match t.data() { - ExprData::Var(i, _, _) => assert_eq!(*i, 5), - other => panic!("expected Var, got {other:?}"), - } - } - - #[test] - fn lookup_let_val_returns_none_for_lambda_binding() { - let mut tc = new_tc(); - tc.push_local(sort0()); - assert!(tc.lookup_let_val(0).is_none()); - } - - #[test] - fn lookup_let_val_returns_some_for_let_binding() { - let mut tc = new_tc(); - tc.push_let(sort0(), sort1()); - let v = tc.lookup_let_val(0).expect("expected Some for let-bound var"); - // Closed value (Sort 1) — lift by 1 is a no-op on closed expressions. - assert!(matches!(v.data(), ExprData::Sort(..))); - } - - #[test] - fn lookup_let_val_out_of_range() { - let mut tc = new_tc(); - tc.push_let(sort0(), sort1()); - assert!(tc.lookup_let_val(10).is_none()); - } - - // ---- save_depth / restore_depth ---- - - #[test] - fn save_and_restore_depth_basic() { - let mut tc = new_tc(); - tc.push_local(sort0()); - let s = tc.save_depth(); - tc.push_local(sort1()); - tc.push_local(sort1()); - assert_eq!(tc.depth(), 3); - tc.restore_depth(s); - assert_eq!(tc.depth(), 1); - } - - #[test] - fn restore_depth_drops_let_count() { - let mut tc = new_tc(); - let s = tc.save_depth(); - tc.push_let(sort0(), sort0()); - tc.push_local(sort0()); - tc.push_let(sort1(), sort1()); - assert_eq!(tc.num_let_bindings, 2); - tc.restore_depth(s); - assert_eq!(tc.depth(), 0); - assert_eq!(tc.num_let_bindings, 0); - } - - // ---- tick / fuel ---- - - #[test] - fn configured_fuel_initializes_and_resets_checks() { - assert_eq!(MAX_REC_FUEL, 100_000_000); - let expected = crate::env_var("IX_MAX_REC_FUEL") - .ok() - .and_then(|s| s.parse::().ok()) - .unwrap_or(MAX_REC_FUEL); - assert_eq!(max_rec_fuel(), expected); - - let mut tc = new_tc(); - assert_eq!(tc.rec_fuel, expected); - tc.rec_fuel = 0; - tc.reset(); - assert_eq!(tc.rec_fuel, expected); - tc.rec_fuel = 0; - tc.finish_constant_accounting(); - assert_eq!(tc.rec_fuel, expected); - } - - #[test] - fn tick_consumes_fuel() { - let mut tc = new_tc(); - tc.rec_fuel = 3; - assert!(tc.tick().is_ok()); - assert!(tc.tick().is_ok()); - assert!(tc.tick().is_ok()); - match tc.tick() { - Err(TcError::MaxRecFuel) => {}, - other => panic!("expected MaxRecFuel, got {other:?}"), - } - } - - #[test] - fn tick_exhaustion_at_zero() { - let mut tc = new_tc(); - tc.rec_fuel = 0; - match tc.tick() { - Err(TcError::MaxRecFuel) => {}, - other => panic!("expected MaxRecFuel at zero fuel, got {other:?}"), - } - } - - // ---- with_infer_only ---- - - #[test] - fn with_infer_only_scoping() { - let mut tc = new_tc(); - assert!(!tc.infer_only); - let r = tc.with_infer_only(|tc| { - assert!(tc.infer_only); - 42 - }); - assert_eq!(r, 42); - assert!(!tc.infer_only); - } - - #[test] - fn with_infer_only_nested_restores() { - let mut tc = new_tc(); - tc.infer_only = true; - tc.with_infer_only(|tc| { - assert!(tc.infer_only); - }); - assert!(tc.infer_only, "outer infer_only=true must be preserved"); - tc.infer_only = false; - tc.with_infer_only(|tc| { - assert!(tc.infer_only); - }); - assert!(!tc.infer_only, "outer infer_only=false must be preserved"); - } - - // ---- reset ---- - - #[test] - fn reset_clears_thread_local_state() { - let mut tc = new_tc(); - tc.push_local(sort0()); - tc.push_let(sort1(), sort1()); - tc.infer_only = true; - tc.eager_reduce = true; - tc.def_eq_depth = 5; - tc.def_eq_peak = 10; - tc.rec_fuel = 1; - - tc.reset(); - - assert_eq!(tc.depth(), 0); - assert_eq!(tc.num_let_bindings, 0); - assert_eq!(tc.ctx_id, empty_ctx_addr()); - assert!(!tc.infer_only); - assert!(!tc.eager_reduce); - assert_eq!(tc.def_eq_depth, 0); - assert_eq!(tc.def_eq_peak, 0); - assert_eq!(tc.rec_fuel, max_rec_fuel()); - } - - // ---- instantiate_univ_params / subst_univ ---- - - #[test] - fn instantiate_univ_params_empty_us_is_noop() { - let mut tc = new_tc(); - let e = sort0(); - let r = tc.instantiate_univ_params(&e, &[]).unwrap(); - // Empty us triggers the ptr-equal fast path. - assert!(e.ptr_eq(&r)); - } - - #[test] - fn instantiate_univ_params_sort_param() { - let mut tc = new_tc(); - // Sort (Param 0) with us = [Zero] → Sort Zero. - let e = KExpr::::sort(KUniv::param(0, mk_name("u"))); - let r = tc.instantiate_univ_params(&e, &[uzero()]).unwrap(); - match r.data() { - ExprData::Sort(u, _) => match u.data() { - UnivData::Zero(_) => {}, - other => panic!("expected Zero, got {other:?}"), - }, - _ => panic!("expected Sort"), - } - } - - #[test] - fn subst_univ_out_of_range_errors() { - let mut tc = new_tc(); - // Param(5) with only 2 universes supplied → UnivParamOutOfRange. - let u = KUniv::::param(5, mk_name("u")); - match tc.subst_univ(&u, &[uzero(), uzero()]) { - Err(TcError::UnivParamOutOfRange { idx, bound }) => { - assert_eq!(idx, 5); - assert_eq!(bound, 2); - }, - other => panic!("expected UnivParamOutOfRange, got {other:?}"), - } - } - - #[test] - fn subst_univ_through_succ_max_imax() { - let mut tc = new_tc(); - // max(succ(Param(0)), imax(Param(1), Zero)) with us=[Zero, succ(Zero)]. - let u = KUniv::::max( - KUniv::succ(KUniv::param(0, mk_name("u"))), - KUniv::imax(KUniv::param(1, mk_name("v")), KUniv::zero()), - ); - let us = [KUniv::zero(), KUniv::succ(KUniv::zero())]; - let r = tc.subst_univ(&u, &us).unwrap(); - // Structural traversal must succeed. Exact normalization output is - // owned by KUniv::max/imax simplification — we only verify no error. - // The result is still some KUniv value. - let _ = r; - } - - // ---- ensure_sort / ensure_forall fast paths ---- - - #[test] - fn ensure_sort_on_sort_succeeds() { - let mut tc = new_tc(); - let u = tc.ensure_sort(&sort0()).unwrap(); - assert!(matches!(u.data(), UnivData::Zero(_))); - } - - #[test] - fn ensure_forall_on_forall_succeeds() { - let mut tc = new_tc(); - let e = pi(sort0(), sort1()); - let (dom, cod) = tc.ensure_forall(&e).unwrap(); - assert!(matches!(dom.data(), ExprData::Sort(..))); - assert!(matches!(cod.data(), ExprData::Sort(..))); - } - - // ---- Free-standing helpers ---- - - #[test] - fn collect_app_spine_non_app_empty_args() { - let e = sort0(); - let (head, args) = collect_app_spine(&e); - assert_eq!(args.len(), 0); - assert!(head.ptr_eq(&e) || head.hash_eq(&e)); - } - - #[test] - fn collect_app_spine_single_app() { - let f = cnst("f", &[]); - let a = sort0(); - let e = KExpr::::app(f.clone(), a.clone()); - let (head, args) = collect_app_spine(&e); - assert_eq!(args.len(), 1); - assert!(head.hash_eq(&f)); - } - - #[test] - fn collect_app_spine_multi_app_preserves_order() { - let f = cnst("f", &[]); - let a = sort0(); - let b = sort1(); - let c = var(0); - let e = apps(f.clone(), &[a.clone(), b.clone(), c.clone()]); - let (head, args) = collect_app_spine(&e); - assert_eq!(args.len(), 3); - assert!(head.hash_eq(&f)); - assert!(args[0].hash_eq(&a)); - assert!(args[1].hash_eq(&b)); - assert!(args[2].hash_eq(&c)); - } - - #[test] - fn expr_mentions_addr_finds_const() { - let target_id = mk_id("target"); - let target = cnst("target", &[]); - // Deep embedding: λ x. app target (var 0) - let e = KExpr::::lam( - mk_name("x"), - ix_common::env::BinderInfo::Default, - sort0(), - KExpr::app(target, var(0)), - ); - assert!(expr_mentions_addr(&e, &target_id.addr)); - } - - #[test] - fn expr_mentions_addr_not_found() { - let other_addr = mk_addr("other"); - let e = pi(sort0(), sort1()); - assert!(!expr_mentions_addr::(&e, &other_addr)); - } - - #[test] - fn expr_mentions_any_addr_finds_one() { - let a = mk_id("a"); - let b = mk_id("b"); - let e = cnst("b", &[]); - let addrs: Vec
= vec![a.addr.clone(), b.addr.clone()]; - assert!(expr_mentions_any_addr::(&e, &addrs)); - } - - #[test] - fn expr_mentions_addr_through_let_all_branches() { - let target_id = mk_id("target"); - let e = KExpr::::let_( - mk_name("x"), - sort0(), - sort0(), - cnst("target", &[]), - false, - ); - assert!(expr_mentions_addr(&e, &target_id.addr)); - } - - #[test] - fn expr_mentions_addr_detects_proj_struct_id() { - let target_id = mk_id("MyStruct"); - let e = KExpr::::prj(target_id.clone(), 0, var(0)); - assert!(expr_mentions_addr(&e, &target_id.addr)); - } -} diff --git a/crates/kernel/src/tc/scan_tests.rs b/crates/kernel/src/tc/scan_tests.rs deleted file mode 100644 index e693ae8ec..000000000 --- a/crates/kernel/src/tc/scan_tests.rs +++ /dev/null @@ -1,239 +0,0 @@ -//! Differential and work-bound tests for syntactic expression inspection. - -use super::*; -use crate::expr::FVarId; -use crate::mode::{Anon, Meta}; -use bignat::Nat; -use ix_common::env::{BinderInfo, Name}; - -// Frozen tree walker: keep it independent of the DAG-aware implementation. -fn mentions_reference(e: &KExpr, addr: &Address) -> bool { - let mut stack = vec![e]; - while let Some(e) = stack.pop() { - match e.data() { - ExprData::Const(id, _, _) => { - if &id.addr == addr { - return true; - } - }, - ExprData::App(f, a, _) => stack.extend([f, a]), - ExprData::Lam(_, _, _, t, b, _) | ExprData::All(_, _, _, _, t, b, _) => { - stack.extend([t, b]); - }, - ExprData::Let(_, t, v, b, _, _) => stack.extend([t, v, b]), - ExprData::Prj(id, _, v, _) => { - if &id.addr == addr { - return true; - } - stack.push(v); - }, - ExprData::Var(..) - | ExprData::FVar(..) - | ExprData::Sort(..) - | ExprData::Nat(..) - | ExprData::Str(..) => {}, - } - } - false -} - -fn differential() { - let a = Address::hash(b"A"); - let b = Address::hash(b"B"); - let missing = Address::hash(b"absent"); - let name = || M::meta_field(Name::anon()); - let bi = || M::meta_field(BinderInfo::Default); - let mut nodes: Vec> = vec![ - KExpr::var(0, name()), - KExpr::fvar(FVarId(0), name()), - KExpr::sort(KUniv::zero()), - KExpr::cnst(KId::new(a.clone(), name()), Box::new([])), - KExpr::cnst(KId::new(b.clone(), name()), Box::new([])), - KExpr::nat(Nat::from(17u64), Address::hash(b"17")), - KExpr::str("hello".to_owned(), Address::hash(b"hello")), - ]; - for i in 0..24 { - let x = nodes[i].clone(); - let y = nodes[(i * 7 + 3) % nodes.len()].clone(); - let z = nodes[(i + 4) % nodes.len()].clone(); - nodes.push(match i % 5 { - 0 => KExpr::app(x, y), - 1 => KExpr::lam(name(), bi(), x, y), - 2 => KExpr::all(name(), bi(), x, y), - 3 => KExpr::let_(name(), x, y, z, i % 2 == 0), - _ => KExpr::prj(KId::new(a.clone(), name()), 2, x), - }); - } - for node in &nodes { - for targets in [ - vec![], - vec![missing.clone()], - vec![a.clone()], - vec![b.clone()], - vec![missing.clone(), b.clone()], - vec![a.clone(), a.clone(), b.clone()], - ] { - let expected = targets.iter().any(|a| mentions_reference(node, a)); - assert_eq!(expr_mentions_any_addr(node, &targets), expected); - for addr in &targets { - assert_eq!( - expr_mentions_addr(node, addr), - mentions_reference(node, addr) - ); - } - } - } -} - -#[test] -fn occurrence_queries_match_tree_reference_in_both_modes() { - differential::(); - differential::(); -} - -#[test] -fn occurrence_queries_visit_diamond_edges_linearly() { - let target = Address::hash(b"present"); - let mut root = - KExpr::::cnst(KId::new(target.clone(), ()), Box::new([])); - let depth = 60; - for _ in 0..depth { - root = KExpr::app(root.clone(), root); - } - let mut visits = 0; - assert!(!expr_mentions_any_addr_impl( - &root, - &[Address::hash(b"missing"), Address::hash(b"also missing")], - || { - visits += 1; - assert!( - visits <= 2 * OCCURRENCE_TREE_BUDGET + 2 * depth + 1, - "expanded shared DAG as a tree" - ); - }, - )); - assert!(visits > 2 * depth); - // Neither the visited set nor a negative result survives a query. - assert!(expr_mentions_addr(&root, &target)); - assert!(!expr_mentions_any_addr_impl(&root, &[], || panic!( - "empty query walked" - ))); -} - -#[test] -fn occurrence_query_checks_every_binder_and_projection_position() { - let id = KId::::new(Address::hash(b"target"), ()); - let hit = KExpr::cnst(id.clone(), Box::new([])); - let miss = KExpr::var(0, ()); - for e in [ - KExpr::lam((), (), hit.clone(), miss.clone()), - KExpr::lam((), (), miss.clone(), hit.clone()), - KExpr::all((), (), hit.clone(), miss.clone()), - KExpr::all((), (), miss.clone(), hit.clone()), - KExpr::let_((), hit.clone(), miss.clone(), miss.clone(), false), - KExpr::let_((), miss.clone(), hit.clone(), miss.clone(), false), - KExpr::let_((), miss.clone(), miss.clone(), hit.clone(), true), - KExpr::prj(id.clone(), 0, miss), - KExpr::prj(KId::new(Address::hash(b"other"), ()), 0, hit), - ] { - assert!(expr_mentions_addr(&e, &id.addr)); - } -} - -#[test] -fn borrowed_app_head_matches_owned_collector_by_pointer() { - for head in [ - KExpr::::cnst(KId::new(Address::hash(b"f"), ()), Box::new([])), - KExpr::var(0, ()), - KExpr::prj(KId::new(Address::hash(b"S"), ()), 0, KExpr::var(1, ())), - ] { - let mut e = head.clone(); - // Holding each prefix also keeps final cleanup from recursively dropping - // a deep unique spine on the test runner's small stack. - let mut prefixes = Vec::new(); - for n in 0..1024 { - if [0, 1, 2, 3, 8, 1023].contains(&n) { - let (owned_head, args) = collect_app_spine(&e); - assert!(app_head(&e).ptr_eq(&owned_head)); - assert!(app_head(&e).ptr_eq(&head)); - assert_eq!(args.len(), n); - } - prefixes.push(e.clone()); - e = KExpr::app(e, KExpr::var(0, ())); - } - drop(e); - while prefixes.pop().is_some() {} - } -} - -#[test] -fn eager_reduce_requires_exactly_two_arguments_and_correct_head() { - let mut env = KEnv::::new(); - let tc = TypeChecker::new(&mut env); - for id in [ - tc.primitives().unwrap().eager_reduce.clone(), - KId::new(Address::hash(b"other"), Name::anon()), - ] { - let mut e = KExpr::cnst(id.clone(), Box::new([])); - for arity in 0..5 { - assert_eq!( - tc.is_eager_reduce(&e), - arity == 2 && id == tc.primitives().unwrap().eager_reduce - ); - e = KExpr::app(e, KExpr::var(0, Name::anon())); - } - } - let non_const = KExpr::app( - KExpr::app(KExpr::var(0, Name::anon()), KExpr::var(1, Name::anon())), - KExpr::var(2, Name::anon()), - ); - assert!(!tc.is_eager_reduce(&non_const)); -} - -#[test] -#[ignore = "manual release benchmark; no wall-clock assertions"] -fn benchmark_occurrence_and_head_walks() { - use std::{hint::black_box, time::Instant}; - let absent = Address::hash(b"absent"); - for depth in [0, 4, 16, 20] { - let mut e = KExpr::::var(0, ()); - for _ in 0..depth { - e = KExpr::app(e.clone(), e); - } - let repeats = if depth <= 4 { 100_000 } else { 4 }; - for dag in [false, true, true, false] { - let start = Instant::now(); - for _ in 0..repeats { - black_box(if dag { - expr_mentions_addr(&e, &absent) - } else { - mentions_reference(&e, &absent) - }); - } - eprintln!( - "occurrence dag={dag} depth={depth} repeats={repeats} elapsed={:?}", - start.elapsed() - ); - } - } - for arity in [0, 2, 8, 64] { - let mut e = KExpr::::var(0, ()); - for _ in 0..arity { - e = KExpr::app(e, KExpr::var(1, ())); - } - for borrowed in [false, true, true, false] { - let start = Instant::now(); - for _ in 0..100_000 { - if borrowed { - black_box(app_head(&e)); - } else { - black_box(collect_app_spine(&e)); - } - } - eprintln!( - "head borrowed={borrowed} arity={arity} elapsed={:?}", - start.elapsed() - ); - } - } -} diff --git a/crates/kernel/src/tc/spine_tests.rs b/crates/kernel/src/tc/spine_tests.rs deleted file mode 100644 index 9a051199a..000000000 --- a/crates/kernel/src/tc/spine_tests.rs +++ /dev/null @@ -1,74 +0,0 @@ -use super::*; -use crate::mode::{Anon, Meta}; -use ix_common::env::{BinderInfo, Name}; - -fn spine_identity(arity: usize) { - let head = KExpr::::var(3, M::meta_field(Name::anon())); - let mut root = head.clone(); - let mut expected = Vec::new(); - for i in 0..arity { - // Named lambdas make preserving the original occurrences observable - // in Meta mode too. No interning/canonicalization is part of a spine walk. - let arg = KExpr::lam( - M::meta_field(Name::str(Name::anon(), format!("arg{i}"))), - M::meta_field(BinderInfo::Implicit), - KExpr::sort(KUniv::zero()), - KExpr::var(i as u64, M::meta_field(Name::anon())), - ); - expected.push(arg.clone()); - root = KExpr::app(root, arg); - } - let (borrowed_head, borrowed) = borrow_app_spine(&root); - assert!(borrowed_head.ptr_eq(&head)); - assert_eq!(borrowed.len(), arity); - assert_eq!(borrowed.spilled(), arity > 8); - for (a, b) in borrowed.iter().zip(&expected) { - assert!(a.ptr_eq(b)); - } - let (owned_head, owned) = collect_app_spine(&root); - assert!(owned_head.ptr_eq(borrowed_head)); - for (a, b) in owned.iter().zip(&borrowed) { - assert!(a.ptr_eq(b)); - } - - // Safe while caller-owned roots survive even if the interner is mutated - // or discarded; the borrow does not reach into that table's storage. - let mut env = KEnv::::new(); - env.intern.intern_expr(root.clone()); - drop(env); - assert!(borrowed[arity - 1].ptr_eq(&expected[arity - 1])); -} - -#[test] -fn borrowed_spines_preserve_order_identity_and_inline_boundary() { - for n in [1, 2, 7, 8, 9, 16, 33] { - spine_identity::(n); - spine_identity::(n); - } -} - -#[test] -fn borrowed_empty_spine_preserves_head_without_spilling() { - fn check() { - let e = KExpr::::sort(KUniv::zero()); - let (head, args) = borrow_app_spine(&e); - assert!(std::ptr::eq(head, &e)); - assert!(args.is_empty()); - assert!(!args.spilled()); - } - check::(); - check::(); -} - -#[test] -fn deep_borrowed_spines_are_iterative() { - std::thread::Builder::new() - .stack_size(256 * 1024 * 1024) - .spawn(|| { - spine_identity::(4096); - spine_identity::(4096); - }) - .unwrap() - .join() - .unwrap(); -} diff --git a/crates/kernel/src/testing.rs b/crates/kernel/src/testing.rs deleted file mode 100644 index 7a8e3fa91..000000000 --- a/crates/kernel/src/testing.rs +++ /dev/null @@ -1,265 +0,0 @@ -//! Shared test helpers for zero kernel tests. -//! -//! Provides convenience constructors for `KExpr`, `KUniv`, `KId`, -//! and `KConst` to reduce boilerplate in hand-built test environments. - -use ix_common::address::Address; -use ix_common::env::{BinderInfo, DefinitionSafety, Name, ReducibilityHints}; -use ixon::constant::DefKind; - -use super::constant::KConst; -use super::env::KEnv; -use super::expr::KExpr; -use super::id::KId; -use super::level::KUniv; -use super::mode::Meta; -use super::tc::TypeChecker; - -// ---- Type aliases ---- - -pub type ME = KExpr; -pub type MU = KUniv; -pub type MId = KId; - -// ---- Name / Address / Id ---- - -pub fn mk_name(s: &str) -> Name { - let mut name = Name::anon(); - for part in s.split('.') { - name = Name::str(name, part.to_string()); - } - name -} - -pub fn mk_addr(s: &str) -> Address { - Address::hash(s.as_bytes()) -} - -pub fn mk_id(s: &str) -> MId { - KId::new(mk_addr(s), mk_name(s)) -} - -// ---- Expressions ---- - -pub fn var(i: u64) -> ME { - ME::var(i, mk_name("_")) -} - -pub fn nvar(name: &str, i: u64) -> ME { - ME::var(i, mk_name(name)) -} - -pub fn sort0() -> ME { - ME::sort(MU::zero()) -} - -pub fn sort1() -> ME { - ME::sort(MU::succ(MU::zero())) -} - -pub fn sort(u: MU) -> ME { - ME::sort(u) -} - -pub fn pi(dom: ME, cod: ME) -> ME { - ME::all(mk_name("_"), BinderInfo::Default, dom, cod) -} - -pub fn npi(name: &str, dom: ME, cod: ME) -> ME { - ME::all(mk_name(name), BinderInfo::Default, dom, cod) -} - -pub fn ipi(name: &str, dom: ME, cod: ME) -> ME { - ME::all(mk_name(name), BinderInfo::Implicit, dom, cod) -} - -pub fn lam(dom: ME, body: ME) -> ME { - ME::lam(mk_name("_"), BinderInfo::Default, dom, body) -} - -pub fn nlam(name: &str, dom: ME, body: ME) -> ME { - ME::lam(mk_name(name), BinderInfo::Default, dom, body) -} - -pub fn app(f: ME, a: ME) -> ME { - ME::app(f, a) -} - -pub fn apps(f: ME, args: &[ME]) -> ME { - let mut e = f; - for a in args { - e = ME::app(e, a.clone()); - } - e -} - -pub fn cnst(name: &str, us: &[MU]) -> ME { - ME::cnst(mk_id(name), us.into()) -} - -pub fn let_(ty: ME, val: ME, body: ME) -> ME { - ME::let_(mk_name("_"), ty, val, body, false) -} - -// ---- Universes ---- - -pub fn uzero() -> MU { - MU::zero() -} - -pub fn usucc(u: MU) -> MU { - MU::succ(u) -} - -pub fn umax(a: MU, b: MU) -> MU { - MU::max(a, b) -} - -pub fn uimax(a: MU, b: MU) -> MU { - MU::imax(a, b) -} - -pub fn param(n: u64) -> MU { - MU::param(n, mk_name("u")) -} - -pub fn nparam(name: &str, n: u64) -> MU { - MU::param(n, mk_name(name)) -} - -// ---- Constant builders ---- - -pub fn mk_defn( - name: &str, - lvls: u64, - level_params: Vec, - ty: ME, - val: ME, - hints: ReducibilityHints, -) -> (MId, KConst) { - let id = mk_id(name); - let c = KConst::Defn { - name: mk_name(name), - level_params, - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints, - lvls, - ty, - val, - lean_all: vec![id.clone()], - block: id.clone(), - }; - (id, c) -} - -pub fn mk_thm( - name: &str, - lvls: u64, - level_params: Vec, - ty: ME, - val: ME, -) -> (MId, KConst) { - let id = mk_id(name); - let c = KConst::Defn { - name: mk_name(name), - level_params, - kind: DefKind::Theorem, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Opaque, - lvls, - ty, - val, - lean_all: vec![id.clone()], - block: id.clone(), - }; - (id, c) -} - -pub fn mk_axiom( - name: &str, - lvls: u64, - level_params: Vec, - ty: ME, -) -> (MId, KConst) { - let id = mk_id(name); - let c = KConst::Axio { - name: mk_name(name), - level_params, - is_unsafe: false, - lvls, - ty, - }; - (id, c) -} - -// ---- Common environment builders ---- - -/// Add Eq.{u} and Eq.refl.{u} as axioms to the environment. -/// Eq : {α : Sort u} → α → α → Prop -/// Eq.refl : {α : Sort u} → (a : α) → Eq a a -pub fn add_eq_axioms(env: &mut KEnv) { - let eq_ty = - ipi("α", sort(param(0)), npi("a", var(0), npi("b", var(1), sort0()))); - let (eq_id, eq_c) = mk_axiom("Eq", 1, vec![mk_name("u")], eq_ty); - env.insert(eq_id, eq_c); - - let eq_refl_ty = ipi( - "α", - sort(param(0)), - npi("a", var(0), apps(cnst("Eq", &[param(0)]), &[var(1), var(0), var(0)])), - ); - let (refl_id, refl_c) = - mk_axiom("Eq.refl", 1, vec![mk_name("u")], eq_refl_ty); - env.insert(refl_id, refl_c); -} - -/// Convenience: Eq.{u} α a b -pub fn eq_expr(u: MU, alpha: ME, a: ME, b: ME) -> ME { - apps(cnst("Eq", &[u]), &[alpha, a, b]) -} - -/// Convenience: Eq.refl.{u} α a -pub fn eq_refl_expr(u: MU, alpha: ME, a: ME) -> ME { - apps(cnst("Eq.refl", &[u]), &[alpha, a]) -} - -// ---- Test runner helpers ---- - -pub fn check_accepts(env: &mut KEnv, id: &MId) { - let mut tc = TypeChecker::new(env); - match tc.check_const(id) { - Ok(()) => {}, - Err(e) => panic!("expected {id} to be accepted, got error: {e:?}"), - } -} - -pub fn check_rejects(env: &mut KEnv, id: &MId) { - let mut tc = TypeChecker::new(env); - match tc.check_const(id) { - Err(_) => {}, - Ok(()) => panic!("expected {id} to be rejected, but it was accepted"), - } -} - -/// Check with custom primitives (needed for Nat literal tests etc.) -pub fn check_accepts_with_prims( - env: &mut KEnv, - id: &MId, - prims: super::primitive::Primitives, -) { - let mut tc = TypeChecker::new(env); - tc.prims = super::primitive_context::PrimitiveContext::Legacy( - std::sync::Arc::new(prims), - ); - match tc.check_const(id) { - Ok(()) => {}, - Err(e) => panic!("expected {id} to be accepted, got error: {e:?}"), - } -} - -/// Build Primitives resolved from a test environment. -/// The env should contain all the primitives the test needs. -pub fn test_prims(env: &KEnv) -> super::primitive::Primitives { - super::primitive::Primitives::from_env(env) -} diff --git a/crates/kernel/src/tutorial/basic.rs b/crates/kernel/src/tutorial/basic.rs deleted file mode 100644 index adbc562d8..000000000 --- a/crates/kernel/src/tutorial/basic.rs +++ /dev/null @@ -1,633 +0,0 @@ -//! Basic definitions, levels, lets, forall checks, and level params. - -#[cfg(test)] -mod tests { - - use crate::env::KEnv; - use crate::mode::Meta; - use crate::testing::*; - use ix_common::env::ReducibilityHints; - - // ========================================================================== - // Batch 1: Basic definitions (Tutorial.lean lines 16–60) - // ========================================================================== - - /// good_def basicDef : Type := Prop - #[test] - fn good_basic_def() { - let mut env = KEnv::::new(); - let (id, c) = mk_defn( - "basicDef", - 0, - vec![], - sort1(), - sort0(), - ReducibilityHints::Abbrev, - ); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// bad_def badDef : Prop := Type - /// Value `Type` has type `Type 1`, not `Prop`. - #[test] - fn bad_def_type_mismatch() { - let mut env = KEnv::::new(); - let (id, c) = - mk_defn("badDef", 0, vec![], sort0(), sort1(), ReducibilityHints::Abbrev); - env.insert(id.clone(), c); - check_rejects(&mut env, &id); - } - - /// good_def arrowType : Type := Prop → Prop - #[test] - fn good_arrow_type() { - let mut env = KEnv::::new(); - let (id, c) = mk_defn( - "arrowType", - 0, - vec![], - sort1(), - pi(sort0(), sort0()), // Prop → Prop - ReducibilityHints::Abbrev, - ); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// good_def dependentType : Prop := ∀ (p : Prop), p - #[test] - fn good_dependent_type() { - let mut env = KEnv::::new(); - let (id, c) = mk_defn( - "dependentType", - 0, - vec![], - sort0(), - npi("p", sort0(), var(0)), // ∀ (p : Prop), p - ReducibilityHints::Abbrev, - ); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// good_def constType : Type → Type → Type := fun x y => x - #[test] - fn good_const_type() { - let mut env = KEnv::::new(); - let (id, c) = mk_defn( - "constType", - 0, - vec![], - pi(sort1(), pi(sort1(), sort1())), // Type → Type → Type - nlam("x", sort1(), nlam("y", sort1(), var(1))), // fun x y => x - ReducibilityHints::Abbrev, - ); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// good_def betaReduction : constType Prop (Prop → Prop) := ∀ p : Prop, p - /// Requires `constType` in env. `constType Prop (Prop → Prop)` reduces to `Prop`. - #[test] - fn good_beta_reduction() { - let mut env = KEnv::::new(); - // constType : Type → Type → Type := fun x y => x - let (ct_id, ct_c) = mk_defn( - "constType", - 0, - vec![], - pi(sort1(), pi(sort1(), sort1())), - nlam("x", sort1(), nlam("y", sort1(), var(1))), - ReducibilityHints::Abbrev, - ); - env.insert(ct_id, ct_c); - - // betaReduction : constType Prop (Prop → Prop) := ∀ p : Prop, p - // constType Prop (Prop → Prop) β-reduces to Prop - let ty = app(app(cnst("constType", &[]), sort0()), pi(sort0(), sort0())); - let (id, c) = mk_defn( - "betaReduction", - 0, - vec![], - ty, - npi("p", sort0(), var(0)), - ReducibilityHints::Abbrev, - ); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// good_def betaReduction2 : ∀ (p : Prop), constType Prop (Prop → Prop) := fun p => p - #[test] - fn good_beta_reduction2() { - let mut env = KEnv::::new(); - let (ct_id, ct_c) = mk_defn( - "constType", - 0, - vec![], - pi(sort1(), pi(sort1(), sort1())), - nlam("x", sort1(), nlam("y", sort1(), var(1))), - ReducibilityHints::Abbrev, - ); - env.insert(ct_id, ct_c); - - // ∀ (p : Prop), constType Prop (Prop → Prop) - let ct_applied = - app(app(cnst("constType", &[]), sort0()), pi(sort0(), sort0())); - let ty = npi("p", sort0(), ct_applied); - let val = nlam("p", sort0(), var(0)); - let (id, c) = - mk_defn("betaReduction2", 0, vec![], ty, val, ReducibilityHints::Abbrev); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// good_def forallSortWhnf : Prop := ∀ (p : id Prop) (x : p), p - /// `id Prop` must WHNF to `Prop` (a Sort) for the forall to typecheck. - #[test] - fn good_forall_sort_whnf() { - let mut env = KEnv::::new(); - // id : Type → Type := fun x => x - let (id_id, id_c) = mk_defn( - "id", - 0, - vec![], - pi(sort1(), sort1()), - nlam("x", sort1(), var(0)), - ReducibilityHints::Abbrev, - ); - env.insert(id_id, id_c); - - // forallSortWhnf : Prop := ∀ (p : id Prop) (x : p), p - let id_prop = app(cnst("id", &[]), sort0()); // id Prop - let val = npi("p", id_prop, npi("x", var(0), var(1))); - let (id, c) = mk_defn( - "forallSortWhnf", - 0, - vec![], - sort0(), - val, - ReducibilityHints::Abbrev, - ); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// bad_def nonTypeType : constType := Prop - /// `constType` is `Type → Type → Type`, not a Sort — can't be a type annotation. - #[test] - fn bad_non_type_type() { - let mut env = KEnv::::new(); - let (ct_id, ct_c) = mk_defn( - "constType", - 0, - vec![], - pi(sort1(), pi(sort1(), sort1())), - nlam("x", sort1(), nlam("y", sort1(), var(1))), - ReducibilityHints::Abbrev, - ); - env.insert(ct_id, ct_c); - - // nonTypeType : constType := Prop - // constType is (Type → Type → Type), not a Sort - let (id, c) = mk_defn( - "nonTypeType", - 0, - vec![], - cnst("constType", &[]), // not a sort! - sort0(), - ReducibilityHints::Abbrev, - ); - env.insert(id.clone(), c); - check_rejects(&mut env, &id); - } - - // ========================================================================== - // Batch 2: Level computation (Tutorial.lean lines 62–118) - // ========================================================================== - - /// levelComp1 : Sort 1 := Sort (imax 1 0) - /// imax 1 0 = 0 (because second arg is 0), so Sort(imax 1 0) = Sort 0 = Prop - /// But type is Sort 1 = Type, so Prop : Type is correct. - #[test] - fn good_level_comp1() { - let mut env = KEnv::::new(); - let ty = sort(usucc(uzero())); // Sort 1 - let val = sort(uimax(usucc(uzero()), uzero())); // Sort (imax 1 0) - let (id, c) = - mk_defn("levelComp1", 0, vec![], ty, val, ReducibilityHints::Opaque); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// levelComp2 : Sort 2 := Sort (imax 0 1) - /// imax 0 1 = max 0 1 = 1 (since second arg is nonzero), so Sort(imax 0 1) = Sort 1 = Type. - /// Type : Sort 2 is correct. - #[test] - fn good_level_comp2() { - let mut env = KEnv::::new(); - let ty = sort(usucc(usucc(uzero()))); // Sort 2 - let val = sort(uimax(uzero(), usucc(uzero()))); // Sort (imax 0 1) - let (id, c) = - mk_defn("levelComp2", 0, vec![], ty, val, ReducibilityHints::Opaque); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// levelComp3 : Sort 3 := Sort (imax 2 1) - /// imax 2 1 = max 2 1 = 2, so Sort(imax 2 1) = Sort 2. Sort 2 : Sort 3. - #[test] - fn good_level_comp3() { - let mut env = KEnv::::new(); - let ty = sort(usucc(usucc(usucc(uzero())))); // Sort 3 - let val = sort(uimax(usucc(usucc(uzero())), usucc(uzero()))); // Sort (imax 2 1) - let (id, c) = - mk_defn("levelComp3", 0, vec![], ty, val, ReducibilityHints::Opaque); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// levelComp4.{u} : Type 0 := Sort (imax u 0) - /// imax u 0 = 0 for all u (second arg is zero), so Sort(imax u 0) = Prop. - /// Prop : Type 0 is correct. - #[test] - fn good_level_comp4() { - let mut env = KEnv::::new(); - let ty = sort(usucc(uzero())); // Type 0 = Sort 1 - let val = sort(uimax(param(0), uzero())); // Sort (imax u 0) - let (id, c) = mk_defn( - "levelComp4", - 1, - vec![mk_name("u")], - ty, - val, - ReducibilityHints::Abbrev, - ); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// levelComp5.{u} : Type u := Sort (imax u u) - /// imax u u = u (if u=0 then 0, else max u u = u). - /// Sort u : Type u = Sort (u+1). - #[test] - fn good_level_comp5() { - let mut env = KEnv::::new(); - let ty = sort(usucc(param(0))); // Type u = Sort (u+1) - let val = sort(uimax(param(0), param(0))); // Sort (imax u u) - let (id, c) = mk_defn( - "levelComp5", - 1, - vec![mk_name("u")], - ty, - val, - ReducibilityHints::Abbrev, - ); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// imax1 : (p : Prop) → Prop := fun p => Type → p - /// Inside the lambda, p : Prop, so (Type → p) : Sort(imax 2 1) but - /// actually the type of (Type → p) where p : Prop uses imax: - /// Type : Sort 2, p : Sort 0, so (Type → p) : Sort (imax 2 0) = Sort 0 = Prop. - /// Wait, p is a variable of type Prop. The forall (Type → p) has domain Sort 2 - /// and codomain p (which is in Sort 0 since p : Prop). So the forall is - /// in Sort(imax 2 0) = Sort 0 = Prop. So fun p => (Type → p) : Prop → Prop. - /// And (p : Prop) → Prop : Prop. - #[test] - fn good_imax1() { - let mut env = KEnv::::new(); - // (p : Prop) → Prop - let ty = npi("p", sort0(), sort0()); - // fun p => Type → p - // Inside lambda: p is var(0). Inside the pi body, p shifts to var(1). - let val = nlam("p", sort0(), pi(sort1(), var(1))); - let (id, c) = - mk_defn("imax1", 0, vec![], ty, val, ReducibilityHints::Abbrev); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// imax2 : (α : Type) → Type 1 := fun α => Type → α - /// Inside lambda: α is var(0) : Type = Sort 1. - /// (Type → α) has domain Type : Sort 2 and codomain α : Sort 1. - /// So (Type → α) : Sort(imax 2 1) = Sort(max 2 1) = Sort 2 = Type 1. - /// fun α => (Type → α) : (α : Type) → Type 1. - #[test] - fn good_imax2() { - let mut env = KEnv::::new(); - // (α : Type) → Type 1 - let ty = npi("α", sort1(), sort(usucc(usucc(uzero())))); - // fun α => Type → α - let val = nlam("α", sort1(), pi(sort1(), var(0))); - let (id, c) = - mk_defn("imax2", 0, vec![], ty, val, ReducibilityHints::Abbrev); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - // ========================================================================== - // Batch 2b: Variable inference & def-eq (Tutorial.lean lines 119–125) - // ========================================================================== - - /// inferVar : ∀ (f : Prop) (g : f), f := fun f g => g - #[test] - fn good_infer_var() { - let mut env = KEnv::::new(); - // ∀ (f : Prop) (g : f), f - let ty = npi("f", sort0(), npi("g", var(0), var(1))); - // fun f g => g - let val = nlam("f", sort0(), nlam("g", var(0), var(0))); - let (id, c) = - mk_defn("inferVar", 0, vec![], ty, val, ReducibilityHints::Abbrev); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// defEqLambda : ∀ (f : (Prop → Prop) → Prop) (g : (a : Prop → Prop) → f a), - /// f (fun p => p → p) := fun f g => g (fun p => p → p) - #[test] - fn good_def_eq_lambda() { - let mut env = KEnv::::new(); - // f : (Prop → Prop) → Prop - let f_ty = pi(pi(sort0(), sort0()), sort0()); - // g : (a : Prop → Prop) → f a - // Under f binder: f is var(0) - // g : ∀ (a : Prop → Prop), app(var(1), var(0)) - let g_ty = npi("a", pi(sort0(), sort0()), app(var(1), var(0))); - // result: f (fun p => p → p) - let pp = nlam("p", sort0(), pi(var(0), var(1))); // fun p => p → p - let result = app(var(1), pp.clone()); - let ty = npi("f", f_ty.clone(), npi("g", g_ty, result)); - // fun f g => g (fun p => p → p) - let val = nlam( - "f", - f_ty, - nlam( - "g", - npi("a", pi(sort0(), sort0()), app(var(1), var(0))), - app(var(0), pp), - ), - ); - let (id, c) = - mk_defn("defEqLambda", 0, vec![], ty, val, ReducibilityHints::Abbrev); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - // ========================================================================== - // Batch 2c: Let declarations (Tutorial.lean lines 159–196) - // ========================================================================== - - /// letType : Sort 1 := let x : Sort 1 := Sort 0; x - /// The let reduces: x = Sort 0, so the value is Sort 0 : Sort 1. - #[test] - fn good_let_type() { - let mut env = KEnv::::new(); - let ty = sort1(); - // let x : Sort 1 := Sort 0; x (= bvar 0) - let val = let_(sort1(), sort0(), var(0)); - let (id, c) = - mk_defn("letType", 0, vec![], ty, val, ReducibilityHints::Opaque); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// letTypeDep : aDepProp (Sort 0) := let x : Sort 1 := Sort 0; mkADepProp x - /// Requires aDepProp and mkADepProp axioms. - #[test] - fn good_let_type_dep() { - let mut env = KEnv::::new(); - // axiom aDepProp : Type → Prop - let (adp_id, adp_c) = mk_axiom("aDepProp", 0, vec![], pi(sort1(), sort0())); - env.insert(adp_id, adp_c); - // axiom mkADepProp : ∀ t, aDepProp t - let (mkadp_id, mkadp_c) = mk_axiom( - "mkADepProp", - 0, - vec![], - npi("t", sort1(), app(cnst("aDepProp", &[]), var(0))), - ); - env.insert(mkadp_id, mkadp_c); - - // letTypeDep : aDepProp (Sort 0) := let x : Sort 1 := Sort 0; mkADepProp x - let ty = app(cnst("aDepProp", &[]), sort0()); - let val = let_(sort1(), sort0(), app(cnst("mkADepProp", &[]), var(0))); - let (id, c) = - mk_defn("letTypeDep", 0, vec![], ty, val, ReducibilityHints::Opaque); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// letRed : (let x : Sort 1 := Sort 0; x) := aProp - /// The type has a let that reduces to Sort 0 = Prop. aProp : Prop. - #[test] - fn good_let_red() { - let mut env = KEnv::::new(); - let (ap_id, ap_c) = mk_axiom("aProp", 0, vec![], sort0()); - env.insert(ap_id, ap_c); - - // type: let x : Sort 1 := Sort 0; x — reduces to Sort 0 = Prop - let ty = let_(sort1(), sort0(), var(0)); - let val = cnst("aProp", &[]); - let (id, c) = - mk_defn("letRed", 0, vec![], ty, val, ReducibilityHints::Opaque); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - // ========================================================================== - // Batch 6: Duplicate level params (Tutorial.lean line 98–106) - // ========================================================================== - - /// tut06_bad01: definition with duplicate level params [u, u] - #[test] - fn bad_duplicate_level_params() { - let mut env = KEnv::::new(); - let (id, c) = mk_defn( - "tut06_bad01", - 2, // claims 2 level params - vec![mk_name("u"), mk_name("u")], // duplicate! - sort(usucc(uzero())), // Sort 1 - sort0(), // Sort 0 - ReducibilityHints::Opaque, - ); - env.insert(id.clone(), c); - check_rejects(&mut env, &id); - } - - // ========================================================================== - // Batch 7: forallSortBad and nonPropThm (Tutorial.lean lines 41–61) - // ========================================================================== - - /// forallSortBad: value has a forall whose domain is id Type Prop, which - /// reduces to Prop (a Sort) — but the outer structure uses it wrong. - /// The value is: ∀ (_ : id Type Prop), ∀ (_ : bvar0), ∀ (_ : bvar0), bvar1 - /// After reducing id Type Prop → Prop: - /// ∀ (_ : Prop), ∀ (_ : bvar0), ∀ (_ : bvar0), bvar1 - /// bvar0 in the 2nd forall refers to a Prop variable, which is not a Sort. - /// But with the unreduced `id Type Prop`, the domain `bvar0` might look different. - /// The test is: type = Sort 0, value has this arrow expression. - /// The kernel should check that each forall's domain is a Sort (after WHNF). - /// The innermost domain `bvar0` refers to a variable of type Prop, not a Sort. - #[test] - fn bad_forall_sort_bad() { - let mut env = KEnv::::new(); - // id : {α : Sort u} → α → α, simplified as Type → Type → Type... no. - // id.{2} : Sort 2 → Sort 2 := fun x => x - // id.{2} (Sort 1) (Sort 0) = Sort 0 = Prop - // Let's use: id_univ2 : Sort 2 → Sort 2 := fun x => x - let (id2_id, id2_c) = mk_defn( - "id2", - 0, - vec![], - pi(sort(usucc(usucc(uzero()))), sort(usucc(usucc(uzero())))), // Sort 2 → Sort 2 - nlam("x", sort(usucc(usucc(uzero()))), var(0)), - ReducibilityHints::Abbrev, - ); - env.insert(id2_id, id2_c); - - // forallSortBad : Prop := ∀ (_ : id2 (Sort 1) applied to Sort 0... ) - // Actually simpler: the domain is (id2 Prop) which reduces to Prop. - // Then the next domain is bvar(0) which is a Prop value, NOT a Sort. - // - // value = ∀ (_ : id2 Prop), ∀ (_ : bvar0), bvar1 - // After WHNF of `id2 Prop` → Prop. Then domain 2 is bvar0 : Prop (not a Sort). - // Wait, id2 : Sort 2 → Sort 2. Prop = Sort 0 : Sort 1, not Sort 2. - // So id2 Prop would fail (Prop : Sort 1, not Sort 2). - // - // Let's use a simpler approach: id at level 1. - // id1 : Sort 1 → Sort 1 := fun x => x - // id1 Prop = Prop (since Prop : Sort 1) - let (id1_id, id1_c) = mk_defn( - "id1", - 0, - vec![], - pi(sort(usucc(uzero())), sort(usucc(uzero()))), // Sort 1 → Sort 1 - nlam("x", sort(usucc(uzero())), var(0)), - ReducibilityHints::Abbrev, - ); - env.insert(id1_id, id1_c); - - // value = ∀ (_ : id1 Prop), ∀ (_ : bvar0), bvar1 - // id1 Prop reduces to Prop (a Sort). First forall OK. - // Second forall: domain = bvar0 (the variable of type Prop). Not a Sort! - let id1_prop = app(cnst("id1", &[]), sort0()); - // ∀ (_ : id1 Prop), ∀ (_ : bvar0), ∀ (_ : bvar0), bvar1 - // depth 1: _1 : Prop (from id1 Prop) - // depth 2: _2 : _1 (var(0) at depth 1 = _1, a Prop variable). _2 has type _1 : Prop. - // depth 3: domain = bvar0 = _2 (var(0) at depth 2). _2 has type _1 (Prop value). - // infer(_2) = _1. ensure_sort(_1) must fail: _1 is a Prop variable, not a Sort. - let value = npi( - "_", - id1_prop, // ∀ _1 : id1 Prop, ... - npi( - "_", - var(0), // ∀ _2 : _1, ... (_1 : Prop, so _2 has a Prop-typed type) - npi( - "_", - var(0), // ∀ _3 : _2, ... — _2's type is _1 (a Prop var, NOT Sort) - var(1), - ), - ), - ); // _2 - - let (id, c) = mk_defn( - "forallSortBad", - 0, - vec![], - sort0(), - value, - ReducibilityHints::Opaque, - ); - env.insert(id.clone(), c); - check_rejects(&mut env, &id); - } - - // ========================================================================== - // Batch 15: levelParams test (Tutorial.lean 93–96) - // ========================================================================== - - /// levelParams: levelParamF.{u} Prop (Prop → Prop) := ∀ p : Prop, p - /// where levelParamF.{u} : Sort u → Sort u → Sort u := fun α β => α - #[test] - fn good_level_params() { - let mut env = KEnv::::new(); - // levelParamF.{u} : Sort u → Sort u → Sort u := fun α β => α - let lpf_ty = pi(sort(param(0)), pi(sort(param(0)), sort(param(0)))); - // Inside the pi's: at depth 2, α=var(1), β=var(0). Return α = var(1). - let lpf_val = nlam("α", sort(param(0)), nlam("β", sort(param(0)), var(1))); - let (lpf_id, lpf_c) = mk_defn( - "levelParamF", - 1, - vec![mk_name("u")], - lpf_ty, - lpf_val, - ReducibilityHints::Abbrev, - ); - env.insert(lpf_id, lpf_c); - - // levelParams : levelParamF.{0} Prop (Prop → Prop) := ∀ p : Prop, p - // levelParamF.{0} Prop (Prop → Prop) reduces to Prop (first arg) - // Lean infers levelParamF.{1} since Prop : Type = Sort 1 - let ty = app( - app(cnst("levelParamF", &[usucc(uzero())]), sort0()), - pi(sort0(), sort0()), - ); - let val = npi("p", sort0(), var(0)); - let (id, c) = - mk_defn("levelParams", 0, vec![], ty, val, ReducibilityHints::Abbrev); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - // ========================================================================== - // Batch 18: nonPropThm (Tutorial.lean 55–61) - // ========================================================================== - - /// nonPropThm: theorem whose type is Sort 0, value is Prop → bvar0 - /// A theorem's type must itself be a Prop (Sort 0), but the VALUE's - /// inferred type must match. Here type = Sort 0 but value = Prop → bvar0 - /// which has type Sort 1 (a function type), not Sort 0. - #[test] - fn bad_non_prop_thm() { - let mut env = KEnv::::new(); - // type = Sort 0 = Prop - // value = Prop → bvar0 = ∀ (_ : Prop), bvar0 - // But inside the pi body bvar0 refers to the pi's variable (of type Prop). - // infer(value) = Sort(imax 1 0) = Sort 0 = Prop... wait. - // Actually: domain Prop : Sort 1, so l_a = 1. - // Codomain: bvar0 has type Prop. infer(bvar0) = Prop = Sort 0, l_b = 0. - // Pi type: Sort(imax 1 0) = Sort 0 = Prop. - // So the value HAS type Prop, same as the declared type. This should be accepted. - // - // Hmm, looking at the tutorial more carefully: the value is - // arrow (.sort 0) (.bvar 0) - // where .bvar 0 in the ARROW BODY refers to the arrow's own bound var. - // So this is ∀ (_ : Prop), _ where _ is the bound var itself. - // The bound var has type Prop. infer(bvar0) = Prop = Sort 0. - // For this to be valid as a pi body, we need the body's type to be a Sort. - // Sort 0 IS a Sort. So the pi is well-typed: Sort(imax 1 0) = Sort 0 = Prop. - // - // But the tutorial says this is BAD because "The type of a theorem has to be a proposition." - // The theorem's type IS Sort 0 = Prop. And the value also has type Prop. - // Maybe the BAD part is that a theorem's declared type must be a proposition - // (i.e., have type Prop), but Sort 0 itself has type Sort 1, not Prop. - // - // Actually: the declared type of the theorem is `.sort 0`. The TYPE OF `.sort 0` is - // `.sort 1`. For a theorem, we check `infer(ty)` and `ensure_sort` — that gives level 1. - // Then we should additionally check that this level IS 0 (Prop). - // The kernel currently doesn't enforce "theorem types must be Prop." - // - // This is a theorem-specific check that the zero kernel may not implement. - let ty = sort0(); // Sort 0 = Prop - let val = pi(sort0(), var(0)); // Prop → bvar0 - let (id, c) = mk_thm("nonPropThm", 0, vec![], ty, val); - env.insert(id.clone(), c); - // The lean kernel requires theorems' types to be Prop (level 0). - // Sort 0 has type Sort 1, so the theorem type is in Sort 1, not Prop. - check_rejects(&mut env, &id); - } -} diff --git a/crates/kernel/src/tutorial/defeq.rs b/crates/kernel/src/tutorial/defeq.rs deleted file mode 100644 index 46fb671b5..000000000 --- a/crates/kernel/src/tutorial/defeq.rs +++ /dev/null @@ -1,2317 +0,0 @@ -//! Proof irrelevance, eta, and equality tests. - -#[cfg(test)] -mod tests { - - use crate::constant::{KConst, RecRule}; - use crate::env::KEnv; - use crate::mode::Meta; - use crate::testing::*; - use ix_common::env::Name; - - // ========================================================================== - // Batch 4: Proof irrelevance and eta (Tutorial.lean lines 953–1013) - // ========================================================================== - - /// proofIrrelevance : ∀ (p : Prop) (h1 h2 : p), h1 = h2 := fun _ _ _ => rfl - #[test] - fn good_proof_irrelevance() { - let mut env = KEnv::::new(); - add_eq_axioms(&mut env); - - // ∀ (p : Prop) (h1 h2 : p), Eq.{0} p h1 h2 - // depth 3: p=var(2), h1=var(1), h2=var(0) - let ty = npi( - "p", - sort0(), - npi( - "h1", - var(0), - npi("h2", var(1), eq_expr(uzero(), var(2), var(1), var(0))), - ), - ); - - // fun p h1 h2 => Eq.refl.{0} p h1 - // Eq.refl h1 : Eq h1 h1, but declared type says Eq h1 h2. - // Proof irrelevance makes h1 = h2 since both : p (a Prop). - let val = nlam( - "p", - sort0(), - nlam( - "h1", - var(0), - nlam("h2", var(1), eq_refl_expr(uzero(), var(2), var(1))), - ), - ); - - let (id, c) = mk_defn( - "proofIrrelevance", - 0, - vec![], - ty, - val, - ix_common::env::ReducibilityHints::Abbrev, - ); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// funEta : ∀ (α β : Type) (f : α → β), (fun x => f x) = f := fun _ _ f => rfl - #[test] - fn good_fun_eta() { - let mut env = KEnv::::new(); - add_eq_axioms(&mut env); - - // ∀ (α : Type) (β : Type) (f : α → β), (fun x => f x) = f - // At f_ty position (depth 2): α=var(1), β=var(0) - // α → β at depth 2: pi(var(1), var(1)) — inside pi body β shifts from 0→1 - let f_ty = pi(var(1), var(1)); - // Inside body (depth 3): f=var(0), β=var(1), α=var(2) - // eta_lhs = fun (x : α) => f x. α at depth 3 = var(2). - // Inside lambda (depth 4): x=var(0), f=var(1), β=var(2), α=var(3) - let eta_lhs = nlam("x", var(2), app(var(1), var(0))); - // α → β at depth 3: pi(var(2), var(2)) — inside pi body β shifts from 1→2 - let eq_app = apps( - cnst("Eq", &[usucc(uzero())]), - &[pi(var(2), var(2)), eta_lhs, var(0)], - ); - let ty = npi("α", sort1(), npi("β", sort1(), npi("f", f_ty, eq_app))); - - // fun α β f => Eq.refl.{1} (α → β) f - // At depth 3 inside val: f=var(0), β=var(1), α=var(2) - let val = nlam( - "α", - sort1(), - nlam( - "β", - sort1(), - nlam( - "f", - pi(var(1), var(1)), - apps( - cnst("Eq.refl", &[usucc(uzero())]), - &[pi(var(2), var(2)), var(0)], - ), - ), - ), - ); - - let (id, c) = mk_thm("funEta", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// funEtaBad : ∀ (α β : Type) (g : α → α) (f : α → β), (fun x => f (g x)) = f - /// BAD: eta should NOT identify functions with different bodies. - #[test] - fn bad_fun_eta() { - let mut env = KEnv::::new(); - add_eq_axioms(&mut env); - - // ∀ (α : Type) (β : Type) (g : α → α) (f : α → β), (fun x => f (g x)) = f - // At g_ty position (depth 2): α=var(1), β=var(0) - // g : α → α = pi(var(1), var(2)) — inside pi: α shifts from 1→2 - // At f_ty position (depth 3): α=var(2), β=var(1), g=var(0) - // f : α → β = pi(var(2), var(2)) — inside pi: β shifts from 1→2 - // Inside body (depth 4): f=var(0), g=var(1), β=var(2), α=var(3) - // lhs = fun (x : α) => f (g x). α at depth 4 = var(3). - // Inside lambda (depth 5): x=var(0), f=var(1), g=var(2), β=var(3), α=var(4) - let lhs = nlam("x", var(3), app(var(1), app(var(2), var(0)))); - // α → β at depth 4: pi(var(3), var(3)) — inside pi β shifts from 2→3 - let eq_app = - apps(cnst("Eq", &[usucc(uzero())]), &[pi(var(3), var(3)), lhs, var(0)]); - let ty = npi( - "α", - sort1(), - npi( - "β", - sort1(), - npi( - "g", - pi(var(1), var(2)), // g : α → α (at depth 2) - npi( - "f", - pi(var(2), var(2)), // f : α → β (at depth 3) - eq_app, - ), - ), - ), - ); - - // fun α β g f => Eq.refl f (bogus: claims f∘g = f) - // At depth 4 inside val: f=var(0), g=var(1), β=var(2), α=var(3) - let val = nlam( - "α", - sort1(), - nlam( - "β", - sort1(), - nlam( - "g", - pi(var(1), var(2)), - nlam( - "f", - pi(var(2), var(2)), - apps( - cnst("Eq.refl", &[usucc(uzero())]), - &[pi(var(3), var(3)), var(0)], - ), - ), - ), - ), - ); - - let (id, c) = mk_thm("funEtaBad", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_rejects(&mut env, &id); - } - - /// funEtaDep : ∀ (α : Type) (β : α → Type) (f : ∀ a, β a), (fun a => f a) = f - #[test] - fn good_fun_eta_dep() { - let mut env = KEnv::::new(); - add_eq_axioms(&mut env); - - // At depth 3: f=var(0), β=var(1), α=var(2) - // f : ∀ (a : α), β a. At depth 2: α=var(1), β=var(0) - // f_ty = ∀ (a : α), β a = npi("a", var(1), app(var(1), var(0))) - // Inside f_ty pi: a=var(0), β=var(1), α=var(2). β a = app(var(1), var(0)) - let f_ty = npi("a", var(1), app(var(1), var(0))); - - // eta_lhs = fun a => f a. At depth 3: α=var(2), f=var(0) - // lambda domain: α at depth 3 = var(2) - // Inside lambda (depth 4): a=var(0), f=var(1), β=var(2), α=var(3) - let eta_lhs = nlam("a", var(2), app(var(1), var(0))); - - // ∀ a, β a at depth 3 (for Eq type arg): - // npi("a", var(2), app(var(2), var(0))) — inside pi: β shifts from 1→2 - let pi_ty = npi("a", var(2), app(var(2), var(0))); - - // Eq.{1} (∀ a, β a) (fun a => f a) f - let eq_app = eq_expr(usucc(uzero()), pi_ty.clone(), eta_lhs, var(0)); - - // β : α → Type. At depth 1: α = var(0). β_ty = npi("a", var(0), sort1()) - // But β is NOT the pi type, it's a variable of type α → Type - let beta_ty = pi(var(0), sort1()); // α → Type (non-dependent arrow) - - let ty = npi( - "α", - sort1(), - npi("β", beta_ty.clone(), npi("f", f_ty.clone(), eq_app)), - ); - - // fun α β f => Eq.refl.{1} (∀ a, β a) f - let val = nlam( - "α", - sort1(), - nlam( - "β", - beta_ty, - nlam("f", f_ty, eq_refl_expr(usucc(uzero()), pi_ty, var(0))), - ), - ); - - let (id, c) = mk_thm("funEtaDep", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - // ========================================================================== - // Batch 10: Structure eta (Tutorial.lean line 967–968) - // ========================================================================== - - /// structEta : ∀ (α β : Type u) (x : α × β), x = ⟨x.1, x.2⟩ ∧ ⟨x.1, x.2⟩ = x - /// Needs Prod, And, Eq. For now test a simpler version: - /// ∀ (p : Prop) (h : p), h = h - #[test] - fn good_trivial_eq() { - let mut env = KEnv::::new(); - add_eq_axioms(&mut env); - - // ∀ (p : Prop) (h : p), Eq.{0} p h h - let ty = npi( - "p", - sort0(), - npi("h", var(0), eq_expr(uzero(), var(1), var(0), var(0))), - ); - // fun p h => Eq.refl.{0} p h - let val = nlam( - "p", - sort0(), - nlam("h", var(0), eq_refl_expr(uzero(), var(1), var(0))), - ); - let (id, c) = mk_thm("trivialEq", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// bad: claim Eq.refl proves h1 = h2 for NON-Prop types (no proof irrelevance) - /// ∀ (α : Type) (a b : α), Eq a b - #[test] - fn bad_non_prop_eq() { - let mut env = KEnv::::new(); - add_eq_axioms(&mut env); - - // ∀ (α : Type) (a b : α), Eq.{1} α a b - // depth 3: α=var(2), a=var(1), b=var(0) - let ty = npi( - "α", - sort1(), - npi( - "a", - var(0), - npi("b", var(1), eq_expr(usucc(uzero()), var(2), var(1), var(0))), - ), - ); - // fun α a b => Eq.refl.{1} α a (claims Eq a a, but type says Eq a b — no proof irrel for Type) - let val = nlam( - "α", - sort1(), - nlam( - "a", - var(0), - nlam("b", var(1), eq_refl_expr(usucc(uzero()), var(2), var(1))), - ), - ); - let (id, c) = mk_thm("badNonPropEq", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_rejects(&mut env, &id); - } - - // ========================================================================== - // Batch 12: Unit eta (Tutorial.lean 958–965) - // ========================================================================== - - /// Build a PUnit-like unit type environment. - /// MyUnit : Type, MyUnit.star : MyUnit, MyUnit.rec - fn unit_env() -> KEnv { - let mut env = KEnv::::new(); - let n = "MyUnit"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.star")); - let rec_id = mk_id(&format!("{n}.rec")); - - // MyUnit : Type - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // MyUnit.star : MyUnit - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.star")), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 0, - ty: cnst(n, &[]), - }, - ); - - // MyUnit.rec : ∀ {motive : MyUnit → Sort u} (star : motive MyUnit.star) (t : MyUnit), motive t - let motive_ty = pi(cnst(n, &[]), sort(param(0))); - let minor_star = app(var(0), cnst(&format!("{n}.star"), &[])); - let rec_ty = ipi( - "motive", - motive_ty, - npi( - "star", - minor_star.clone(), - npi("t", cnst(n, &[]), app(var(2), var(0))), - ), - ); - - // Rule: star case → λ motive star_val, star_val - let rule_rhs = nlam( - "motive", - pi(cnst(n, &[]), sort(param(0))), - nlam("star", app(var(0), cnst(&format!("{n}.star"), &[])), var(0)), - ); - - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![mk_name("u")], - k: true, // k = true: single ctor, no fields → structure-like - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![RecRule { ctor: Name::anon(), fields: 0, rhs: rule_rhs }], - lean_all: vec![block_id.clone()], - }, - ); - - env.blocks.insert(block_id.clone(), vec![block_id, ctor_id, rec_id]); - add_eq_axioms(&mut env); - env - } - - /// unitEta: ∀ (x y : MyUnit), x = y - /// Any two values of a unit type are definitionally equal (structure eta). - #[test] - fn good_unit_eta() { - let mut env = unit_env(); - // ∀ (x y : MyUnit), Eq.{1} MyUnit x y - let ty = npi( - "x", - cnst("MyUnit", &[]), - npi( - "y", - cnst("MyUnit", &[]), - eq_expr(usucc(uzero()), cnst("MyUnit", &[]), var(1), var(0)), - ), - ); - // fun x y => Eq.refl.{1} MyUnit x - // Kernel uses structure eta: x = MyUnit.star = y - let val = nlam( - "x", - cnst("MyUnit", &[]), - nlam( - "y", - cnst("MyUnit", &[]), - eq_refl_expr(usucc(uzero()), cnst("MyUnit", &[]), var(1)), - ), - ); - let (id, c) = mk_thm("unitEta", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - // ========================================================================== - // Acc inductive + reduction (Tutorial.lean 1161–1181) - // ========================================================================== - - /// Build Acc inductive environment. - /// Acc : {α : Sort u} → (α → α → Prop) → α → Prop - /// Acc.intro : ∀ {α} {r} {x}, (∀ y, r y x → Acc r y) → Acc r x - /// Acc.rec with k = false (NOT a structure-like recursor) - fn acc_env() -> KEnv { - let mut env = KEnv::::new(); - add_eq_axioms(&mut env); - - // We also need Bool for the reduction test - let bool_id = mk_id("Bool"); - let false_id = mk_id("Bool.false"); - let true_id = mk_id("Bool.true"); - env.insert( - bool_id.clone(), - KConst::Indc { - name: mk_name("Bool"), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: bool_id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![false_id.clone(), true_id.clone()], - lean_all: vec![bool_id.clone()], - }, - ); - env.insert( - false_id.clone(), - KConst::Ctor { - name: mk_name("Bool.false"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: bool_id.clone(), - cidx: 0, - params: 0, - fields: 0, - ty: cnst("Bool", &[]), - }, - ); - env.insert( - true_id.clone(), - KConst::Ctor { - name: mk_name("Bool.true"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: bool_id.clone(), - cidx: 1, - params: 0, - fields: 0, - ty: cnst("Bool", &[]), - }, - ); - env.blocks.insert(bool_id.clone(), vec![bool_id, false_id, true_id]); - - let n = "Acc"; - let block_id = mk_id(n); - let intro_id = mk_id("Acc.intro"); - let rec_id = mk_id("Acc.rec"); - - // Acc.{u} : {α : Sort u} → (α → α → Prop) → α → Prop - // depth 0: u = param(0) - // {α : Sort u} implicit, (r : α → α → Prop), (x : α) → Prop - let acc_ty = ipi( - "α", - sort(param(0)), - npi("r", pi(var(0), pi(var(1), sort0())), npi("x", var(1), sort0())), - ); - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![mk_name("u")], - lvls: 1, - params: 2, - indices: 1, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: acc_ty, - ctors: vec![intro_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // Acc.intro.{u} : {α : Sort u} → {r : α → α → Prop} → {x : α} → - // (∀ y, r y x → Acc r y) → Acc r x - // depth 3 (inside α, r, x all implicit): α=var(2), r=var(1), x=var(0) - // field: ∀ (y : α), r y x → Acc r y - // depth 4 (inside y): y=var(0), x=var(1), r=var(2), α=var(3) - // r y x = app(app(var(2), var(0)), var(1)) - // Acc r y = app(app(app(cnst("Acc", [param(0)]), var(3)), var(2)), var(0)) - // depth 5 (inside r y x →): same + arrow binder - let r_y_x = app(app(var(2), var(0)), var(1)); - let acc_r_y = apps(cnst("Acc", &[param(0)]), &[var(3), var(2), var(0)]); - let intro_field = npi("y", var(2), pi(r_y_x, acc_r_y)); - // result: Acc r x at depth 4 (inside field binder) - let acc_r_x = apps(cnst("Acc", &[param(0)]), &[var(3), var(2), var(1)]); - let intro_ty = ipi( - "α", - sort(param(0)), - ipi( - "r", - pi(var(0), pi(var(1), sort0())), - ipi("x", var(1), pi(intro_field, acc_r_x)), - ), - ); - env.insert( - intro_id.clone(), - KConst::Ctor { - name: mk_name("Acc.intro"), - level_params: vec![mk_name("u")], - is_unsafe: false, - lvls: 1, - induct: block_id.clone(), - cidx: 0, - params: 2, - fields: 1, - ty: intro_ty, - }, - ); - - // Acc.rec.{u, v} — Acc is NOT k-like (it's a Prop with data field) - // Acc.rec.{u, v} : ∀ {α : Sort v} {r : α → α → Prop} - // {motive : ∀ (x : α), Acc r x → Sort u} - // (intro : ∀ (x : α) (h : ∀ y, r y x → Acc r y), - // (∀ y (hr : r y x), motive y (h y hr)) → motive x (Acc.intro h)) - // {x : α} (t : Acc r x), motive x t - // - // d2 (inside α, r): α=var(1), r=var(0) - // motive : ∀ (x : α), Acc r x → Sort u - // d3: x=var(0), r=var(1), α=var(2). Acc r x = Acc.{v} var(2) var(1) var(0) - // d4: acc=var(0), x=var(1), r=var(2), α=var(3). Sort u = sort(param(0)) - let acc_rx_d3 = apps(cnst("Acc", &[param(1)]), &[var(2), var(1), var(0)]); - let motive_ty = npi("x", var(1), pi(acc_rx_d3, sort(param(0)))); - - // intro minor at d3 (inside motive): - // ∀ (x : α) (h : ∀ y, r y x → Acc r y) - // (ih : ∀ y (hr : r y x), motive y (h y hr)), - // motive x (Acc.intro h) - // d3: motive=var(0), r=var(1), α=var(2) - // d4: x=var(0), motive=var(1), r=var(2), α=var(3) - // h_ty: ∀ (y : α), r y x → Acc r y - // d5: y=var(0), x=var(1), motive=var(2), r=var(3), α=var(4) - // r y x = app(app(var(3), var(0)), var(1)) - // d6: (inside r y x pi) Acc r y = Acc.{v} var(5) var(4) var(1)... wait - // d6: proof=var(0), y=var(1), x=var(2), motive=var(3), r=var(4), α=var(5) - // Acc r y = apps(Acc.{v}, [var(5), var(4), var(1)]) - let h_ty_d4 = npi( - "y", - var(3), - pi( - app(app(var(3), var(0)), var(1)), - apps(cnst("Acc", &[param(1)]), &[var(5), var(4), var(1)]), - ), - ); - // d5: h=var(0), x=var(1), motive=var(2), r=var(3), α=var(4) - // ih_ty: ∀ (y : α) (hr : r y x), motive y (h y hr) - // d6: y=var(0), h=var(1), x=var(2), motive=var(3), r=var(4), α=var(5) - // r y x = app(app(var(4), var(0)), var(2)) - // d7: hr=var(0), y=var(1), h=var(2), x=var(3), motive=var(4), r=var(5), α=var(6) - // motive y (h y hr) = app(app(var(4), var(1)), app(app(var(2), var(1)), var(0))) - let ih_ty_d5 = npi( - "y", - var(4), - npi( - "hr", - app(app(var(4), var(0)), var(2)), - app(app(var(4), var(1)), app(app(var(2), var(1)), var(0))), - ), - ); - // d6: ih=var(0), h=var(1), x=var(2), motive=var(3), r=var(4), α=var(5) - // result: motive x (Acc.intro h) = app(app(var(3), var(2)), Acc.intro.{v} α r x h) - // Acc.intro applied: apps(Acc.intro.{v}, [var(5), var(4), var(2), var(1)]) - let acc_intro_app = - apps(cnst("Acc.intro", &[param(1)]), &[var(5), var(4), var(2), var(1)]); - let minor_result = app(app(var(3), var(2)), acc_intro_app); - let intro_minor = - npi("x", var(2), npi("h", h_ty_d4, npi("ih", ih_ty_d5, minor_result))); - - // d4 (inside intro): intro=var(0), motive=var(1), r=var(2), α=var(3) - // {x : α}: x domain = var(3) = α - // d5 (inside x): x=var(0), intro=var(1), motive=var(2), r=var(3), α=var(4) - // t : Acc r x = Acc.{v} var(4) var(3) var(0) - let acc_rx_d5 = apps(cnst("Acc", &[param(1)]), &[var(4), var(3), var(0)]); - // d6 (inside t): t=var(0), x=var(1), intro=var(2), motive=var(3), r=var(4), α=var(5) - // motive x t = app(app(var(3), var(1)), var(0)) - let rec_ty = ipi( - "α", - sort(param(1)), - ipi( - "r", - pi(var(0), pi(var(1), sort0())), - ipi( - "motive", - motive_ty, - npi( - "intro", - intro_minor.clone(), - ipi( - "x", - var(3), - npi("t", acc_rx_d5, app(app(var(3), var(1)), var(0))), - ), - ), - ), - ), - ); - - // Rule for Acc.intro (1 field: the h argument) - // rhs: λ {α} {r} motive intro_case x h, - // intro_case x h (fun y hr => Acc.rec.{u,v} α r motive intro_case (h y hr)) - // d4 (after α, r, motive, intro_case): intro_case=var(0), motive=var(1), r=var(2), α=var(3) - // d5 (after x): x=var(0), intro_case=var(1), motive=var(2), r=var(3), α=var(4) - // d6 (after h): h=var(0), x=var(1), intro_case=var(2), motive=var(3), r=var(4), α=var(5) - // ih = fun y hr => Acc.rec motive intro_case (h y hr) - // d7: y=var(0), h=var(1), x=var(2), intro_case=var(3), motive=var(4), r=var(5), α=var(6) - // r y x at d7 = app(app(var(5), var(0)), var(2)) - // d8: hr=var(0), y=var(1), h=var(2), x=var(3), intro=var(4), motive=var(5), r=var(6), α=var(7) - // h y hr = app(app(var(2), var(1)), var(0)) - // Acc.rec.{u,v} α r motive intro (h y hr) = apps(Acc.rec, [var(7), var(6), var(5), var(4), app(app(var(2), var(1)), var(0))]) - // But Acc.rec also needs x and t args... hmm no, the rule rhs only takes params+minors+fields. - // Actually for Acc.rec, the args are: {α} {r} {motive} (intro_case) {x} (t : Acc r x) - // The rule rhs peels: {α}, {r}, motive, intro_case, then the ctor's fields. - // Acc.intro has 1 field (h). So rule rhs has 4 + 1 = 5 lambdas: - // λ {α} {r} motive intro_case h, ... - // Wait, actually the rule rhs takes: params(2) + motives(1) + minors(1) + fields(1) = 5 lambdas - // And the x argument is substituted from the Acc.intro's index. - - // Actually, looking at how the kernel's iota reduction works: the rule rhs - // takes motives + minors + fields lambdas. The params and the major's - // args are handled by the iota reduction itself. - // So for Acc.rec: - // motives = 1 (motive), minors = 1 (intro_case) - // Acc.intro fields = 1 (h) - // Rule rhs: λ motive intro_case h, intro_case ... h ... - // - // The lean4lean rule rhs for Acc.rec.intro is: - // λ motive intro_case h, intro_case (Acc.intro-major-x) h (λ y hr, Acc.rec motive intro_case y (h y hr)) - // But x comes from the major argument's decomposition, substituted for the index. - // - // This is getting very complex. Let me use a different approach: - // Since the test just checks `Acc.rec (fun _ _ _ => p) (Acc.intro h) = p`, - // I can provide the rule rhs as: - // λ motive intro_case h, intro_case var(?) h (λ y hr, Acc.rec motive intro_case y (h y hr)) - // - // For the specific test, intro_case = (fun _ _ _ => p), so the result is p - // regardless of the ih computation. I just need the rule to apply intro_case. - // - // Per check_recursor, the rule's fields = 1 (the h from Acc.intro). - // The rhs should have motives+minors+fields = 1+1+1 = 3 lambdas. - // After iota: Acc.rec params are substituted, indices substituted from major, - // then rhs is applied to motive, intro_case, and the field (h). - - // rhs: λ motive intro_case h, intro_case x h ih - // But x comes from the major arg decomposition — it's injected by the iota rule. - // Actually, looking at iota reduction code: the rule rhs takes only - // motives+minors lambdas, then the ctor fields are applied separately. - // Let me check the existing Bool.rec and N.rec rules for reference. - - // Looking at Bool.rec rule: fields=0, rhs = λ motive hf ht, hf (or ht) - // That's motives(1) + minors(2) = 3 lambdas, 0 fields applied externally. - // - // N.rec succ rule: fields=1, rhs = λ motive h_zero h_succ n, h_succ n (rec...) - // That's motives(1) + minors(2) + fields(1) = 4 lambdas. - // - // So Acc.rec intro rule: motives(1) + minors(1) + fields(1) = 3 lambdas. - // rhs: λ motive intro_case h, intro_case x h (λ y hr, Acc.rec motive intro_case (h y hr)) - // - // But where does x come from? In the iota rule for indexed types, x is - // substituted from the major arg. After decomposing Acc.intro applied args, - // the index x is known. Then the rule rhs is instantiated. - // - // Hmm, actually for Acc, the params are α and r (params=2). - // After iota strips params from the major, the major's ctor is Acc.intro - // with fields = 1 (the h argument). But x is an INDEX, not a field. - // - // The iota reduction substitutes indices from the ctor args. - // For Acc.intro, the constructor is: - // Acc.intro : {α} → {r} → {x} → (∀ y, r y x → Acc r y) → Acc r x - // params = 2 (α, r), remaining args = {x} and h. - // But x is implicit and corresponds to the index. The field count is 1 (h). - // - // In the iota rule, after extracting params and the ctor args: - // ctor_args after params = [x, h] - // fields = 1 (h only) - // The rule rhs takes: λ motive intro_case h_field - // And x is substituted from the ctor args' index position. - // - // This is very subtle. For the test, the motive is (fun _ _ => Bool) - // and intro_case is (fun _ _ _ => p). So the result is just p. - // - // Let me just construct a rule that works for this case. - // rhs: λ motive intro_case h, intro_case x h ih - // where x and ih are... actually I think the rhs for indexed recursors - // doesn't take x as a lambda parameter — x comes from the major decomposition. - // - // Let me look at what the kernel generates for Acc.rec and match that. - // For now, let me try providing a rule that just applies intro_case: - - // Actually, the simplest approach: provide an empty rules vec (no rules). - // The kernel's check_recursor will GENERATE the correct rule and compare. - // Since we provide no rules, the comparison will fail... unless we skip it. - // - // Hmm, that won't work. Let me just leave minors: 0 and rules: [] for now, - // and test only accRecNoEta (which doesn't need reduction). - // The accRecReduction test requires a working rule. - - // For now: keep the minimal recursor (works for accRecNoEta). - // TODO: add full Acc.rec rule for accRecReduction test. - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name("Acc.rec"), - level_params: vec![mk_name("u"), mk_name("v")], - k: false, - is_unsafe: false, - lvls: 2, - params: 2, - indices: 1, - motives: 1, - minors: 1, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - - env.blocks.insert(block_id.clone(), vec![block_id, intro_id, rec_id]); - env - } - - /// accRecNoEta: Acc.rec does NOT have structure eta - /// bad_thm: ∀ {α} (r : α → α → Prop) (a : α) (h : Acc r a) (p : Bool), - /// Acc.rec (fun _ _ _ => p) h = p - /// This should be REJECTED because Acc.rec is not k-like (k=false), - /// so it can't reduce on a non-constructor argument `h`. - #[test] - fn bad_acc_rec_no_eta() { - let mut env = acc_env(); - - // ∀ {α : Type} (r : α → α → Prop) (a : α) (h : Acc r a) (p : Bool), ... - // depth 5: p=var(0), h=var(1), a=var(2), r=var(3), α=var(4) - let acc_r_a = - apps(cnst("Acc", &[usucc(uzero())]), &[var(4), var(3), var(2)]); - - // Acc.rec.{1,1} (fun _ _ _ => p) h : should NOT reduce - let motive = nlam( - "x", - var(4), - nlam( - "_", - apps(cnst("Acc", &[usucc(uzero())]), &[var(5), var(4), var(0)]), - cnst("Bool", &[]), - ), - ); - let rec_app = apps( - cnst("Acc.rec", &[usucc(uzero()), usucc(uzero())]), - &[ - var(4), // α - var(3), // r - motive, // motive - var(2), // x = a - var(1), // t = h - ], - ); - - let ty = ipi( - "α", - sort1(), - npi( - "r", - pi(var(0), pi(var(1), sort0())), - npi( - "a", - var(1), - npi( - "h", - acc_r_a.clone(), - npi( - "p", - cnst("Bool", &[]), - eq_expr(usucc(uzero()), cnst("Bool", &[]), rec_app, var(0)), - ), - ), - ), - ), - ); - - // Value: fun α r a h p => Eq.refl p (BOGUS — claims reduction happened) - let val = ME::lam( - mk_name("α"), - ix_common::env::BinderInfo::Implicit, - sort1(), - nlam( - "r", - pi(var(0), pi(var(1), sort0())), - nlam( - "a", - var(1), - nlam( - "h", - apps(cnst("Acc", &[usucc(uzero())]), &[var(2), var(1), var(0)]), - nlam( - "p", - cnst("Bool", &[]), - eq_refl_expr(usucc(uzero()), cnst("Bool", &[]), var(0)), - ), - ), - ), - ), - ); - - let (id, c) = mk_thm("accRecNoEta", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_rejects(&mut env, &id); - } - - // ========================================================================== - // Rule K tests (Tutorial.lean 906–928) - // Requires Eq as a full inductive + Bool - // ========================================================================== - - /// Build environment with Bool + Eq as full inductives (not just axioms). - /// Eq.{u} : {α : Sort u} → α → α → Prop (indexed, 2 params, 1 index) - /// Eq.refl.{u} : {α : Sort u} → (a : α) → Eq a a - /// Eq.rec.{u,v} with k = true (enables Rule K) - fn eq_inductive_env() -> KEnv { - let mut env = KEnv::::new(); - - // -- Bool -- - let bool_id = mk_id("Bool"); - let false_id = mk_id("Bool.false"); - let true_id = mk_id("Bool.true"); - let bool_rec_id = mk_id("Bool.rec"); - - env.insert( - bool_id.clone(), - KConst::Indc { - name: mk_name("Bool"), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: bool_id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![false_id.clone(), true_id.clone()], - lean_all: vec![bool_id.clone()], - }, - ); - env.insert( - false_id.clone(), - KConst::Ctor { - name: mk_name("Bool.false"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: bool_id.clone(), - cidx: 0, - params: 0, - fields: 0, - ty: cnst("Bool", &[]), - }, - ); - env.insert( - true_id.clone(), - KConst::Ctor { - name: mk_name("Bool.true"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: bool_id.clone(), - cidx: 1, - params: 0, - fields: 0, - ty: cnst("Bool", &[]), - }, - ); - // Bool.rec (minimal, no rules needed for these tests) - let bm = pi(cnst("Bool", &[]), sort(param(0))); - let bm_f = app(var(0), cnst("Bool.false", &[])); - let bm_t = app(var(1), cnst("Bool.true", &[])); - let bool_rec_ty = ipi( - "motive", - bm, - npi( - "hf", - bm_f, - npi("ht", bm_t, npi("t", cnst("Bool", &[]), app(var(3), var(0)))), - ), - ); - env.insert( - bool_rec_id.clone(), - KConst::Recr { - name: mk_name("Bool.rec"), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 2, - block: bool_id.clone(), - member_idx: 0, - ty: bool_rec_ty, - rules: vec![], - lean_all: vec![bool_id.clone()], - }, - ); - env - .blocks - .insert(bool_id, vec![mk_id("Bool"), false_id, true_id, bool_rec_id]); - - // -- Eq.{u} : {α : Sort u} → α → α → Prop -- - // 2 params (α, a), 1 index (b) - let eq_id = mk_id("Eq"); - let refl_id = mk_id("Eq.refl"); - let eq_rec_id = mk_id("Eq.rec"); - - // Eq.{u} : {α : Sort u} → α → α → Prop - let eq_ty = - ipi("α", sort(param(0)), npi("a", var(0), npi("b", var(1), sort0()))); - env.insert( - eq_id.clone(), - KConst::Indc { - name: mk_name("Eq"), - level_params: vec![mk_name("u")], - lvls: 1, - params: 2, - indices: 1, - is_unsafe: false, - block: eq_id.clone(), - member_idx: 0, - ty: eq_ty, - ctors: vec![refl_id.clone()], - lean_all: vec![eq_id.clone()], - }, - ); - - // Eq.refl.{u} : {α : Sort u} → (a : α) → @Eq α a a - // depth 2 (inside α, a): α=var(1), a=var(0) - let eq_refl_ty = ipi( - "α", - sort(param(0)), - npi( - "a", - var(0), - apps(cnst("Eq", &[param(0)]), &[var(1), var(0), var(0)]), - ), - ); - env.insert( - refl_id.clone(), - KConst::Ctor { - name: mk_name("Eq.refl"), - level_params: vec![mk_name("u")], - is_unsafe: false, - lvls: 1, - induct: eq_id.clone(), - cidx: 0, - params: 2, - fields: 0, - ty: eq_refl_ty, - }, - ); - - // Eq.rec.{u, u_1} : ∀ {α : Sort u_1} {a : α} - // {motive : (a' : α) → @Eq α a a' → Sort u} - // (refl : motive a (@Eq.refl α a)) - // {a' : α} (t : @Eq α a a'), motive a' t - // - // k = true (enables Rule K) - // - // Params: α (implicit), a (named) → 2 params - // Indices: a' → 1 index - // Motives: motive → 1 - // Minors: refl → 1 - - // Eq.rec.{u, u_1} type: - // ∀ {α : Sort u_1} {a : α} {motive : (a' : α) → Eq α a a' → Sort u} - // (refl : motive a (Eq.refl α a)) {a' : α} (t : Eq α a a'), motive a' t - // - // At depth 2 (inside α, a): α=var(1), a=var(0) - // motive_ty = (a' : α) → Eq α a a' → Sort u - // At depth 2: α = var(1). Domain a' : α = var(1). - // At depth 3 (inside a'): a'=var(0), a=var(1), α=var(2) - // Eq α a a' = Eq.{u_1} var(2) var(1) var(0) - // At depth 4 (inside eq pi): sort(param(0)) - let eq_a_aprime_d3 = - apps(cnst("Eq", &[param(1)]), &[var(2), var(1), var(0)]); - let motive_ty = npi("a'", var(1), pi(eq_a_aprime_d3, sort(param(0)))); - - // minor refl: motive a (Eq.refl α a) - // At depth 3 (inside motive binder): motive=var(0), a=var(1), α=var(2) - let eq_refl_a_d3 = apps(cnst("Eq.refl", &[param(1)]), &[var(2), var(1)]); - let minor_refl = app(app(var(0), var(1)), eq_refl_a_d3); - - // major args: {a' : α} (t : Eq α a a') - // At depth 4 (inside refl binder): refl=var(0), motive=var(1), a=var(2), α=var(3) - // a' domain: α = var(3) - // At depth 5 (inside a'): a'=var(0), refl=var(1), motive=var(2), a=var(3), α=var(4) - // Eq α a a' = Eq.{u_1} var(4) var(3) var(0) - let eq_a_aprime_d5 = - apps(cnst("Eq", &[param(1)]), &[var(4), var(3), var(0)]); - // At depth 6 (inside t): t=var(0), a'=var(1), refl=var(2), motive=var(3), a=var(4), α=var(5) - // result: motive a' t = app(app(var(3), var(1)), var(0)) - let result = app(app(var(3), var(1)), var(0)); - - let eq_rec_ty = ipi( - "α", - sort(param(1)), - ipi( - "a", - var(0), - ipi( - "motive", - motive_ty, - npi( - "refl", - minor_refl, - ipi("a'", var(3), npi("t", eq_a_aprime_d5, result)), - ), - ), - ), - ); - - // Rule: Eq.refl case - // rhs: λ {α} {a} (motive) (refl_val), refl_val - // At depth 2 (inside α, a): α=var(1), a=var(0) - let motive_ty_r = npi( - "a'", - var(1), - pi( - apps(cnst("Eq", &[param(1)]), &[var(2), var(1), var(0)]), - sort(param(0)), - ), - ); - // At depth 3 (inside motive): motive=var(0), a=var(1), α=var(2) - let eq_refl_r = apps(cnst("Eq.refl", &[param(1)]), &[var(2), var(1)]); - let minor_r = app(app(var(0), var(1)), eq_refl_r); - let rule_rhs = ME::lam( - mk_name("α"), - ix_common::env::BinderInfo::Implicit, - sort(param(1)), - ME::lam( - mk_name("a"), - ix_common::env::BinderInfo::Implicit, - var(0), - nlam("motive", motive_ty_r, nlam("refl", minor_r, var(0))), - ), - ); - - env.insert( - eq_rec_id.clone(), - KConst::Recr { - name: mk_name("Eq.rec"), - level_params: vec![mk_name("u"), mk_name("u_1")], - k: true, // Rule K enabled! - is_unsafe: false, - lvls: 2, - params: 2, - indices: 1, - motives: 1, - minors: 1, - block: eq_id.clone(), - member_idx: 0, - ty: eq_rec_ty, - rules: vec![RecRule { ctor: Name::anon(), fields: 0, rhs: rule_rhs }], - lean_all: vec![eq_id.clone()], - }, - ); - - env.blocks.insert(eq_id, vec![mk_id("Eq"), refl_id, eq_rec_id]); - env - } - - /// ruleK: ∀ (h : true = true) (a : Bool), Eq.rec (motive := fun _ _ => Bool) a h = a - /// Rule K fires because Eq.rec has k=true and the major `h : true = true` - /// can be replaced by Eq.refl true (same constructor indices). - #[test] - fn good_rule_k() { - let mut env = eq_inductive_env(); - - // true = true = @Eq Bool true true - let tt_eq = apps( - cnst("Eq", &[usucc(uzero())]), - &[cnst("Bool", &[]), cnst("Bool.true", &[]), cnst("Bool.true", &[])], - ); - - // Eq.rec.{1,1} (α := Bool) (a := true) (motive := fun _ _ => Bool) a h - // depth 2: h=var(1), a=var(0) - // Actually: ∀ (h : true = true) (a : Bool), ... - // depth 2: a=var(0), h=var(1) - let motive = nlam( - "_", - cnst("Bool", &[]), - nlam( - "_", - apps( - cnst("Eq", &[usucc(uzero())]), - &[cnst("Bool", &[]), cnst("Bool.true", &[]), var(0)], - ), - cnst("Bool", &[]), - ), - ); - let rec_app = apps( - cnst("Eq.rec", &[usucc(uzero()), usucc(uzero())]), - &[ - cnst("Bool", &[]), // α - cnst("Bool.true", &[]), // a - motive, // motive: fun _ _ => Bool - var(0), // refl case value = a (var(0) at depth 2) - cnst("Bool.true", &[]), // a' = true (index) - var(1), // t = h - ], - ); - - // type: ∀ (h : true = true) (a : Bool), Eq.{1} Bool (rec...) a - let ty = npi( - "h", - tt_eq.clone(), - npi( - "a", - cnst("Bool", &[]), - eq_expr(usucc(uzero()), cnst("Bool", &[]), rec_app, var(0)), - ), - ); - - // value: fun h a => Eq.refl.{1} Bool a - let val = nlam( - "h", - tt_eq, - nlam( - "a", - cnst("Bool", &[]), - eq_refl_expr(usucc(uzero()), cnst("Bool", &[]), var(0)), - ), - ); - - let (id, c) = mk_thm("ruleK", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// ruleKbad: ∀ (h : true = false) (a : Bool), Eq.rec (motive := fun _ _ => Bool) a h = a - /// Rule K should NOT fire because the constructor indices don't match (true ≠ false). - #[test] - fn bad_rule_k() { - let mut env = eq_inductive_env(); - - // true = false = @Eq Bool true false - let tf_eq = apps( - cnst("Eq", &[usucc(uzero())]), - &[cnst("Bool", &[]), cnst("Bool.true", &[]), cnst("Bool.false", &[])], - ); - - let motive = nlam( - "_", - cnst("Bool", &[]), - nlam( - "_", - apps( - cnst("Eq", &[usucc(uzero())]), - &[cnst("Bool", &[]), cnst("Bool.true", &[]), var(0)], - ), - cnst("Bool", &[]), - ), - ); - let rec_app = apps( - cnst("Eq.rec", &[usucc(uzero()), usucc(uzero())]), - &[ - cnst("Bool", &[]), - cnst("Bool.true", &[]), - motive, - var(0), // a - cnst("Bool.false", &[]), // a' = false (doesn't match a = true) - var(1), // h - ], - ); - - let ty = npi( - "h", - tf_eq.clone(), - npi( - "a", - cnst("Bool", &[]), - eq_expr(usucc(uzero()), cnst("Bool", &[]), rec_app, var(0)), - ), - ); - - let val = nlam( - "h", - tf_eq, - nlam( - "a", - cnst("Bool", &[]), - eq_refl_expr(usucc(uzero()), cnst("Bool", &[]), var(0)), - ), - ); - - let (id, c) = mk_thm("ruleKbad", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_rejects(&mut env, &id); - } - - /// A conclusive Rule-K rejection must not be followed by normalization of - /// the same major premise. This is the small reducer shape behind the - /// TauCeti `castLpₗᵢ` regression: the constructor candidate has already - /// proved that the equality indices differ, while the propositional proof - /// itself can be arbitrarily expensive to expose. - #[test] - fn rule_k_reject_skips_expensive_major_whnf() { - let mut env = eq_inductive_env(); - - let tf_eq = apps( - cnst("Eq", &[usucc(uzero())]), - &[cnst("Bool", &[]), cnst("Bool.true", &[]), cnst("Bool.false", &[])], - ); - let (impossible_id, impossible) = - mk_axiom("impossibleEq", 0, vec![], tf_eq.clone()); - env.insert(impossible_id, impossible); - - // A valid proof term whose WHNF takes more shared ticks than this test - // grants. Inference sees the declared equality type immediately; only - // the redundant post-rejection major WHNF enters this beta chain. - let mut slow_proof = cnst("impossibleEq", &[]); - for _ in 0..1024 { - slow_proof = app(nlam("h", tf_eq.clone(), var(0)), slow_proof); - } - let (slow_id, slow) = mk_defn( - "slowImpossibleEq", - 0, - vec![], - tf_eq, - slow_proof, - ix_common::env::ReducibilityHints::Abbrev, - ); - env.insert(slow_id, slow); - - let motive = nlam( - "_", - cnst("Bool", &[]), - nlam( - "_", - apps( - cnst("Eq", &[usucc(uzero())]), - &[cnst("Bool", &[]), cnst("Bool.true", &[]), var(0)], - ), - cnst("Bool", &[]), - ), - ); - let rec_app = apps( - cnst("Eq.rec", &[usucc(uzero()), usucc(uzero())]), - &[ - cnst("Bool", &[]), - cnst("Bool.true", &[]), - motive, - cnst("Bool.true", &[]), - cnst("Bool.false", &[]), - cnst("slowImpossibleEq", &[]), - ], - ); - - let mut tc = crate::tc::TypeChecker::new(&mut env); - tc.rec_fuel = 512; - let reduced = tc.whnf(&rec_app).expect( - "a definitive K rejection should not normalize the expensive major", - ); - assert_eq!(reduced.hash_key(), rec_app.hash_key()); - } - - // ========================================================================== - // Projection tests (Tutorial.lean 760–900) - // Requires And as structure - // ========================================================================== - - /// Build And : Prop → Prop → Prop with And.intro constructor. - fn and_env() -> KEnv { - let mut env = KEnv::::new(); - add_eq_axioms(&mut env); - - let n = "And"; - let block_id = mk_id(n); - let intro_id = mk_id("And.intro"); - let rec_id = mk_id("And.rec"); - - // And : Prop → Prop → Prop (2 params) - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 2, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: npi("a", sort0(), npi("b", sort0(), sort0())), - ctors: vec![intro_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // And.intro : ∀ {a b : Prop}, a → b → And a b - // depth 4: b_val=var(0), a_val=var(1), b=var(2), a=var(3) - let intro_ty = ipi( - "a", - sort0(), - ipi( - "b", - sort0(), - npi( - "left", - var(1), - npi("right", var(1), app(app(cnst(n, &[]), var(3)), var(2))), - ), - ), - ); - env.insert( - intro_id.clone(), - KConst::Ctor { - name: mk_name("And.intro"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 2, - fields: 2, - ty: intro_ty, - }, - ); - - // And.rec with k=true (structure, eliminates into any Sort) - let and_ab = app(app(cnst(n, &[]), var(1)), var(0)); - let motive_ty = pi(and_ab.clone(), sort(param(0))); - // minor: ∀ (left : a) (right : b), motive (And.intro left right) - // depth 5: right=var(0), left=var(1), motive=var(2), b=var(3), a=var(4) - let mk_app = - apps(cnst("And.intro", &[]), &[var(4), var(3), var(1), var(0)]); - let minor_intro = - npi("left", var(3), npi("right", var(3), app(var(2), mk_app))); - let rec_ty = npi( - "a", - sort0(), - npi( - "b", - sort0(), - ipi( - "motive", - motive_ty, - npi("intro", minor_intro, npi("t", and_ab, app(var(2), var(0)))), - ), - ), - ); - - // Rule: And.intro case - // rhs: λ a b motive intro_val left right, intro_val left right - let and_ab_r = app(app(cnst(n, &[]), var(1)), var(0)); - let motive_ty_r = pi(and_ab_r, sort(param(0))); - let mk_app_r = - apps(cnst("And.intro", &[]), &[var(4), var(3), var(1), var(0)]); - let minor_r = - npi("left", var(3), npi("right", var(3), app(var(2), mk_app_r))); - let rule_rhs = nlam( - "a", - sort0(), - nlam( - "b", - sort0(), - nlam( - "motive", - motive_ty_r, - nlam( - "intro_case", - minor_r, - nlam( - "left", - var(3), - nlam("right", var(3), app(app(var(2), var(1)), var(0))), - ), - ), - ), - ), - ); - - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name("And.rec"), - level_params: vec![mk_name("u")], - k: true, - is_unsafe: false, - lvls: 1, - params: 2, - indices: 0, - motives: 1, - minors: 1, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![RecRule { ctor: Name::anon(), fields: 2, rhs: rule_rhs }], - lean_all: vec![block_id.clone()], - }, - ); - - env.blocks.insert(block_id, vec![mk_id("And"), intro_id, rec_id]); - env - } - - /// projOutOfRange: .proj And 2 z — And only has fields 0,1 (left, right) - #[test] - fn bad_proj_out_of_range() { - let mut env = and_env(); - - // type: ∀ (x y : Prop) (z : And x y), x - // depth 3: z=var(0), y=var(1), x=var(2) - let and_xy = app(app(cnst("And", &[]), var(1)), var(0)); - let ty = - npi("x", sort0(), npi("y", sort0(), npi("z", and_xy.clone(), var(2)))); - - // value: fun x y z => .proj And 2 z (index 2 is out of range!) - let proj = ME::prj(mk_id("And"), 2, var(0)); - let val = nlam("x", sort0(), nlam("y", sort0(), nlam("z", and_xy, proj))); - - let (id, c) = mk_defn( - "projOutOfRange", - 0, - vec![], - ty, - val, - ix_common::env::ReducibilityHints::Opaque, - ); - env.insert(id.clone(), c); - check_rejects(&mut env, &id); - } - - /// projNotStruct: .proj N 0 x — N is not a structure (2 ctors) - #[test] - fn bad_proj_not_struct() { - let mut env = KEnv::::new(); - - // Need N (Nat-like) with 2 ctors — not a structure - let n = "N"; - let block_id = mk_id(n); - let zero_id = mk_id("N.zero"); - let succ_id = mk_id("N.succ"); - let rec_id = mk_id("N.rec"); - - let nat = || cnst(n, &[]); - - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![zero_id.clone(), succ_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - env.insert( - zero_id.clone(), - KConst::Ctor { - name: mk_name("N.zero"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 0, - ty: nat(), - }, - ); - env.insert( - succ_id.clone(), - KConst::Ctor { - name: mk_name("N.succ"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 1, - params: 0, - fields: 1, - ty: pi(nat(), nat()), - }, - ); - // Minimal recursor - let rec_ty = ipi( - "motive", - pi(nat(), sort(param(0))), - npi("t", nat(), app(var(1), var(0))), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name("N.rec"), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 0, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - env.blocks.insert(block_id, vec![mk_id(n), zero_id, succ_id, rec_id]); - - // type: N → N, value: fun x => .proj N 0 x - let ty = pi(nat(), nat()); - let val = nlam("x", nat(), ME::prj(mk_id("N"), 0, var(0))); - let (id, c) = mk_defn( - "projNotStruct", - 0, - vec![], - ty, - val, - ix_common::env::ReducibilityHints::Opaque, - ); - env.insert(id.clone(), c); - check_rejects(&mut env, &id); - } - - // ========================================================================== - // Structure eta with And (Tutorial.lean 968) - // ========================================================================== - - /// And.left/And.right as projection functions — tests that the kernel - /// can type-check definitions that project from And. - #[test] - fn good_and_left() { - let mut env = and_env(); - - // And.left : ∀ {a b : Prop}, And a b → a - // depth 3: h=var(0), b=var(1), a=var(2) - let and_ab = app(app(cnst("And", &[]), var(1)), var(0)); - let ty = ipi("a", sort0(), ipi("b", sort0(), pi(and_ab.clone(), var(2)))); - - // fun {a} {b} (h : And a b) => .proj And 0 h - let val = ME::lam( - mk_name("a"), - ix_common::env::BinderInfo::Implicit, - sort0(), - ME::lam( - mk_name("b"), - ix_common::env::BinderInfo::Implicit, - sort0(), - nlam("h", and_ab, ME::prj(mk_id("And"), 0, var(0))), - ), - ); - - let (id, c) = mk_defn( - "And.left", - 0, - vec![], - ty, - val, - ix_common::env::ReducibilityHints::Abbrev, - ); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - #[test] - fn good_and_right() { - let mut env = and_env(); - - let and_ab = app(app(cnst("And", &[]), var(1)), var(0)); - let ty = ipi("a", sort0(), ipi("b", sort0(), pi(and_ab.clone(), var(1)))); // returns b, not a - - let val = ME::lam( - mk_name("a"), - ix_common::env::BinderInfo::Implicit, - sort0(), - ME::lam( - mk_name("b"), - ix_common::env::BinderInfo::Implicit, - sort0(), - nlam("h", and_ab, ME::prj(mk_id("And"), 1, var(0))), - ), - ); - - let (id, c) = mk_defn( - "And.right", - 0, - vec![], - ty, - val, - ix_common::env::ReducibilityHints::Abbrev, - ); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - // ========================================================================== - // ruleKAcc (Tutorial.lean 926) — already covered by bad_acc_rec_no_eta - // but with explicit Sort u parameter - // ========================================================================== - - /// typeWithTypeFieldPoly: inductive Type (u+1) with a Type u field - #[test] - fn good_type_with_type_field_poly() { - let mut env = KEnv::::new(); - let n = "TypeWithTypeFieldPoly"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - // TypeWithTypeFieldPoly.{u} : Sort (u+2) = Type (u+1) - let sort_u2 = sort(usucc(usucc(param(0)))); - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![mk_name("u")], - lvls: 1, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: sort_u2, - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // mk : Sort (u+1) → TypeWithTypeFieldPoly (field = Type u = Sort (u+1)) - let sort_u1 = sort(usucc(param(0))); - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![mk_name("u")], - is_unsafe: false, - lvls: 1, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 1, - ty: npi("α", sort_u1.clone(), cnst(n, &[param(0)])), - }, - ); - - let rec_ty = ipi( - "motive", - pi(cnst(n, &[param(0)]), sort(param(1))), - npi( - "mk", - npi( - "α", - sort_u1, - app(var(1), app(cnst(&format!("{n}.mk"), &[param(0)]), var(0))), - ), - npi("t", cnst(n, &[param(0)]), app(var(2), var(0))), - ), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![mk_name("u"), mk_name("v")], - k: false, - is_unsafe: false, - lvls: 2, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_accepts(&mut env, &block_id); - } - - // ========================================================================== - // PropStructure projection tests (Tutorial.lean 791–848) - // - // PropStructure.{u,v} : Prop with 6 fields: - // 0: aProof : PUnit.{u} — proof - // 1: someData : PUnit.{v} — DATA - // 2: aSecondProof : PUnit.{u} — proof - // 3: someMoreData : PUnit.{v} — DATA - // 4: aProofAboutData : someMoreData = someMoreData — proof (depends on data) - // 5: aFinalProof : PUnit.{u} — proof (after dependent data) - // - // For Prop structures, projection restrictions apply: - // - Data projections: FORBIDDEN - // - Proof projections before dependent data: ALLOWED - // - Any projection after dependent data field: FORBIDDEN - // ========================================================================== - - /// Build PUnit.{u} + Eq + PropStructure.{u,v} env. - fn prop_structure_env() -> KEnv { - let mut env = KEnv::::new(); - add_eq_axioms(&mut env); - - // -- PUnit.{u} : Sort u, PUnit.unit.{u} : PUnit.{u} -- - let pu_id = mk_id("PUnit"); - let pu_unit_id = mk_id("PUnit.unit"); - let pu_rec_id = mk_id("PUnit.rec"); - - env.insert( - pu_id.clone(), - KConst::Indc { - name: mk_name("PUnit"), - level_params: vec![mk_name("u")], - lvls: 1, - params: 0, - indices: 0, - is_unsafe: false, - block: pu_id.clone(), - member_idx: 0, - ty: sort(param(0)), // Sort u - ctors: vec![pu_unit_id.clone()], - lean_all: vec![pu_id.clone()], - }, - ); - env.insert( - pu_unit_id.clone(), - KConst::Ctor { - name: mk_name("PUnit.unit"), - level_params: vec![mk_name("u")], - is_unsafe: false, - lvls: 1, - induct: pu_id.clone(), - cidx: 0, - params: 0, - fields: 0, - ty: cnst("PUnit", &[param(0)]), - }, - ); - // PUnit.rec minimal - let pu_motive = pi(cnst("PUnit", &[param(0)]), sort(param(1))); - let pu_minor = app(var(0), cnst("PUnit.unit", &[param(0)])); - let pu_rec_ty = ipi( - "motive", - pu_motive, - npi( - "unit", - pu_minor, - npi("t", cnst("PUnit", &[param(0)]), app(var(2), var(0))), - ), - ); - env.insert( - pu_rec_id.clone(), - KConst::Recr { - name: mk_name("PUnit.rec"), - level_params: vec![mk_name("u"), mk_name("v")], - k: true, - is_unsafe: false, - lvls: 2, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: pu_id.clone(), - member_idx: 0, - ty: pu_rec_ty, - rules: vec![], - lean_all: vec![pu_id.clone()], - }, - ); - env.blocks.insert(pu_id, vec![mk_id("PUnit"), pu_unit_id, pu_rec_id]); - - // -- PropStructure.{u,v} : Prop -- - // Constructor mk with 6 fields: - // (aProof : PUnit.{u}) (someData : PUnit.{v}) (aSecondProof : PUnit.{u}) - // (someMoreData : PUnit.{v}) (aProofAboutData : someMoreData = someMoreData) - // (aFinalProof : PUnit.{u}) - let ps_id = mk_id("PropStructure"); - let ps_mk_id = mk_id("PropStructure.mk"); - let ps_rec_id = mk_id("PropStructure.rec"); - - env.insert( - ps_id.clone(), - KConst::Indc { - name: mk_name("PropStructure"), - level_params: vec![mk_name("u"), mk_name("v")], - lvls: 2, - params: 0, - indices: 0, - is_unsafe: false, - block: ps_id.clone(), - member_idx: 0, - ty: sort0(), // Prop - ctors: vec![ps_mk_id.clone()], - lean_all: vec![ps_id.clone()], - }, - ); - - // mk.{u,v} constructor type (6 fields → PropStructure.{u,v}) - // Field types at increasing depth: - // d0: (aProof : PUnit.{u}) - // d1: (someData : PUnit.{v}) — aProof=var(0) - // d2: (aSecondProof : PUnit.{u}) — someData=var(0), aProof=var(1) - // d3: (someMoreData : PUnit.{v}) - // d4: (aProofAboutData : Eq.{v} PUnit.{v} someMoreData someMoreData) - // someMoreData=var(0) at d4, so Eq.{v} PUnit.{v} var(0) var(0) - // but param(1)=v, so: apps(Eq, [param(1)], [PUnit.{v}, var(0), var(0)]) - // Wait, Eq.{u_1} takes {α : Sort u_1}, so Eq at level v: - // cnst("Eq", &[param(1)]) applied to PUnit.{v}, var(0), var(0) - // d5: (aFinalProof : PUnit.{u}) - // d6: result = PropStructure.{u,v} - - let pu_u = cnst("PUnit", &[param(0)]); - let pu_v = cnst("PUnit", &[param(1)]); - // At depth 4 (after 4 fields): someMoreData = var(0) - let eq_field = - apps(cnst("Eq", &[param(1)]), &[pu_v.clone(), var(0), var(0)]); - let ps_result = cnst("PropStructure", &[param(0), param(1)]); - - let mk_ty = npi( - "aProof", - pu_u.clone(), // d0→d1: aProof=var(0) - npi( - "someData", - pu_v.clone(), // d1→d2 - npi( - "aSecondProof", - pu_u.clone(), // d2→d3 - npi( - "someMoreData", - pu_v.clone(), // d3→d4: someMoreData=var(0) - npi( - "aProofAboutData", - eq_field, // d4→d5 - npi( - "aFinalProof", - pu_u.clone(), // d5→d6 - ps_result, - ), - ), - ), - ), - ), - ); - - env.insert( - ps_mk_id.clone(), - KConst::Ctor { - name: mk_name("PropStructure.mk"), - level_params: vec![mk_name("u"), mk_name("v")], - is_unsafe: false, - lvls: 2, - induct: ps_id.clone(), - cidx: 0, - params: 0, - fields: 6, - ty: mk_ty, - }, - ); - - // Minimal recursor (Prop elimination only since it's a Prop structure) - let ps_motive = pi(cnst("PropStructure", &[param(0), param(1)]), sort0()); - let ps_rec_ty = ipi( - "motive", - ps_motive, - npi( - "t", - cnst("PropStructure", &[param(0), param(1)]), - app(var(1), var(0)), - ), - ); - env.insert( - ps_rec_id.clone(), - KConst::Recr { - name: mk_name("PropStructure.rec"), - level_params: vec![mk_name("u"), mk_name("v")], - k: false, - is_unsafe: false, - lvls: 2, - params: 0, - indices: 0, - motives: 1, - minors: 0, - block: ps_id.clone(), - member_idx: 0, - ty: ps_rec_ty, - rules: vec![], - lean_all: vec![ps_id.clone()], - }, - ); - env.blocks.insert(ps_id, vec![mk_id("PropStructure"), ps_mk_id, ps_rec_id]); - - env - } - - /// Helper: build test `name : PropStructure.{0,1} → resType := fun x => .proj PropStructure idx x` - fn mk_prop_structure_proj_test( - env: &mut KEnv, - name: &str, - res_ty: ME, - idx: u64, - ) -> MId { - let ps01 = cnst("PropStructure", &[uzero(), usucc(uzero())]); - let ty = pi(ps01.clone(), res_ty); - let val = nlam("x", ps01, ME::prj(mk_id("PropStructure"), idx, var(0))); - let (id, c) = mk_defn( - name, - 0, - vec![], - ty, - val, - ix_common::env::ReducibilityHints::Opaque, - ); - env.insert(id.clone(), c); - id - } - - /// projProp1 (good): idx=0, aProof : PUnit.{0} — proof before all data - #[test] - fn good_proj_prop1() { - let mut env = prop_structure_env(); - let id = mk_prop_structure_proj_test( - &mut env, - "projProp1", - cnst("PUnit", &[uzero()]), - 0, - ); - check_accepts(&mut env, &id); - } - - /// projProp2 (bad): idx=1, someData : PUnit.{1} — data projection forbidden - #[test] - fn bad_proj_prop2() { - let mut env = prop_structure_env(); - let id = mk_prop_structure_proj_test( - &mut env, - "projProp2", - cnst("PUnit", &[usucc(uzero())]), - 1, - ); - check_rejects(&mut env, &id); - } - - /// projProp3 (good): idx=2, aSecondProof : PUnit.{0} — proof before dependent data - #[test] - fn good_proj_prop3() { - let mut env = prop_structure_env(); - let id = mk_prop_structure_proj_test( - &mut env, - "projProp3", - cnst("PUnit", &[uzero()]), - 2, - ); - check_accepts(&mut env, &id); - } - - /// projProp4 (bad): idx=3, someMoreData : PUnit.{1} — data projection forbidden - #[test] - fn bad_proj_prop4() { - let mut env = prop_structure_env(); - let id = mk_prop_structure_proj_test( - &mut env, - "projProp4", - cnst("PUnit", &[usucc(uzero())]), - 3, - ); - check_rejects(&mut env, &id); - } - - /// projProp5 (bad): idx=4, aProofAboutData — proof that depends on data field - #[test] - fn bad_proj_prop5() { - let mut env = prop_structure_env(); - // Result type: Eq.{1} PUnit.{1} (.proj PropStructure 3 x) (.proj PropStructure 3 x) - // Inside the lambda (depth 1): x = var(0) - let proj3 = ME::prj(mk_id("PropStructure"), 3, var(0)); - let res_ty_inner = apps( - cnst("Eq", &[usucc(uzero())]), - &[cnst("PUnit", &[usucc(uzero())]), proj3.clone(), proj3], - ); - // But this res_ty is inside the pi binder (at depth 1 where x=var(0)) - // The helper mk_prop_structure_proj_test wraps it in pi(PS, res_ty) - // so res_ty should reference var(0) for x. But var(0) inside pi body - // IS x. The .proj expressions use var(0) = x. Good. - let id = - mk_prop_structure_proj_test(&mut env, "projProp5", res_ty_inner, 4); - check_rejects(&mut env, &id); - } - - /// projProp6 (bad): idx=5, aFinalProof : PUnit.{0} — after dependent data - #[test] - fn bad_proj_prop6() { - let mut env = prop_structure_env(); - let id = mk_prop_structure_proj_test( - &mut env, - "projProp6", - cnst("PUnit", &[uzero()]), - 5, - ); - check_rejects(&mut env, &id); - } - - // ========================================================================== - // etaRuleK corner case (Tutorial.lean 987–999) - // - // Partially applied Eq.rec with rule K should NOT trigger eta expansion. - // @Eq.rec Bool true (fun _ _ => Bool) (a (Eq.refl true)) _ ≠ a - // even though Eq.rec could reduce via Rule K if fully applied. - // ========================================================================== - - /// etaRuleK: ∀ (a : true = true → Bool), - /// @Eq (true = true → Bool) (Eq.rec (fun _ _ => Bool) (a (Eq.refl true)) _) a - /// BAD: partially applied recursor should not eta-expand to match `a`. - #[test] - fn bad_eta_rule_k() { - let mut env = eq_inductive_env(); - - let u1 = usucc(uzero()); - let bool_ty = cnst("Bool", &[]); - - // true = true - let tt_eq = apps( - cnst("Eq", std::slice::from_ref(&u1)), - &[bool_ty.clone(), cnst("Bool.true", &[]), cnst("Bool.true", &[])], - ); - - // (true = true → Bool) — the type of `a` - let a_ty = pi(tt_eq.clone(), bool_ty.clone()); - - // motive for Eq.rec: fun _ _ => Bool - let motive = nlam( - "_", - bool_ty.clone(), - nlam( - "_", - apps( - cnst("Eq", std::slice::from_ref(&u1)), - &[bool_ty.clone(), cnst("Bool.true", &[]), var(0)], - ), - bool_ty.clone(), - ), - ); - - // a (Eq.refl true) : Bool — where a : true = true → Bool - // depth 1: a = var(0) - let refl_true = apps( - cnst("Eq.refl", std::slice::from_ref(&u1)), - &[bool_ty.clone(), cnst("Bool.true", &[])], - ); - let a_applied = app(var(0), refl_true.clone()); - - // Eq.rec.{1,1} Bool true motive (a (Eq.refl true)) : {a' : Bool} → (true = a') → Bool - // This is a PARTIAL application — missing the a' and t arguments. - // It is a function (true = true → Bool) via Rule K expansion at a'=true. - let rec_partial = apps( - cnst("Eq.rec", &[u1.clone(), u1.clone()]), - &[ - bool_ty.clone(), // α = Bool - cnst("Bool.true", &[]), // a = true - motive, // motive: fun _ _ => Bool - a_applied, // refl minor = a (Eq.refl true) : Bool - ], - ); - // rec_partial has 4 args but Eq.rec needs 6. So rec_partial : {a' : Bool} → (true = a') → Bool - - // The key claim (bogus): rec_partial = a - // Both have type (true = true → Bool), but they're not def-eq because - // partial recursor application should not trigger eta expansion. - let lhs = rec_partial; - let ty = - npi("a", a_ty.clone(), eq_expr(u1.clone(), a_ty.clone(), lhs, var(0))); - let val = nlam("a", a_ty, eq_refl_expr(u1, pi(tt_eq, bool_ty), var(0))); - - let (id, c) = mk_defn( - "etaRuleK", - 0, - vec![], - ty, - val, - ix_common::env::ReducibilityHints::Opaque, - ); - env.insert(id.clone(), c); - check_rejects(&mut env, &id); - } - - // ========================================================================== - // etaCtor corner case (Tutorial.lean 1001–1013) - // - // Partially applied constructor should NOT trigger eta expansion. - // T.mk (x True.intro).val ≠ x even though T.mk applied to both - // fields would reconstruct the structure. - // ========================================================================== - - /// Build a simple structure T with val : Bool, proof : True - fn t_struct_env() -> KEnv { - let mut env = eq_inductive_env(); - - // True : Prop, single ctor True.intro - let true_ty_id = mk_id("True"); - let true_intro_id = mk_id("True.intro"); - let true_rec_id = mk_id("True.rec"); - - env.insert( - true_ty_id.clone(), - KConst::Indc { - name: mk_name("True"), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: true_ty_id.clone(), - member_idx: 0, - ty: sort0(), - ctors: vec![true_intro_id.clone()], - lean_all: vec![true_ty_id.clone()], - }, - ); - env.insert( - true_intro_id.clone(), - KConst::Ctor { - name: mk_name("True.intro"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: true_ty_id.clone(), - cidx: 0, - params: 0, - fields: 0, - ty: cnst("True", &[]), - }, - ); - let true_motive = pi(cnst("True", &[]), sort(param(0))); - let true_minor = app(var(0), cnst("True.intro", &[])); - let true_rec_ty = ipi( - "motive", - true_motive, - npi( - "intro", - true_minor, - npi("t", cnst("True", &[]), app(var(2), var(0))), - ), - ); - env.insert( - true_rec_id.clone(), - KConst::Recr { - name: mk_name("True.rec"), - level_params: vec![mk_name("u")], - k: true, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: true_ty_id.clone(), - member_idx: 0, - ty: true_rec_ty, - rules: vec![], - lean_all: vec![true_ty_id.clone()], - }, - ); - env - .blocks - .insert(true_ty_id, vec![mk_id("True"), true_intro_id, true_rec_id]); - - // T : Type, structure with val : Bool, proof : True - let t_id = mk_id("T"); - let t_mk_id = mk_id("T.mk"); - let t_rec_id = mk_id("T.rec"); - - env.insert( - t_id.clone(), - KConst::Indc { - name: mk_name("T"), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: t_id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![t_mk_id.clone()], - lean_all: vec![t_id.clone()], - }, - ); - // T.mk : Bool → True → T - env.insert( - t_mk_id.clone(), - KConst::Ctor { - name: mk_name("T.mk"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: t_id.clone(), - cidx: 0, - params: 0, - fields: 2, - ty: npi( - "val", - cnst("Bool", &[]), - npi("proof", cnst("True", &[]), cnst("T", &[])), - ), - }, - ); - // T.rec minimal - let t_motive = pi(cnst("T", &[]), sort(param(0))); - let t_minor = npi( - "val", - cnst("Bool", &[]), - npi( - "proof", - cnst("True", &[]), - app(var(2), apps(cnst("T.mk", &[]), &[var(1), var(0)])), - ), - ); - let t_rec_ty = ipi( - "motive", - t_motive, - npi("mk", t_minor, npi("t", cnst("T", &[]), app(var(2), var(0)))), - ); - env.insert( - t_rec_id.clone(), - KConst::Recr { - name: mk_name("T.rec"), - level_params: vec![mk_name("u")], - k: true, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: t_id.clone(), - member_idx: 0, - ty: t_rec_ty, - rules: vec![], - lean_all: vec![t_id.clone()], - }, - ); - env.blocks.insert(t_id, vec![mk_id("T"), t_mk_id, t_rec_id]); - - env - } - - /// etaCtor: ∀ (x : True → T), (T.mk (x True.intro).val) = x - /// BAD: partially applied constructor should not eta-expand. - /// T.mk applied to .val projection gives a partial application (True → T), - /// but this should NOT be identified with x via eta. - #[test] - fn bad_eta_ctor() { - let mut env = t_struct_env(); - - let u1 = usucc(uzero()); - - // x : True → T - let x_ty = pi(cnst("True", &[]), cnst("T", &[])); - - // depth 1: x = var(0) - // (x True.intro) : T - let x_intro = app(var(0), cnst("True.intro", &[])); - // (x True.intro).val = .proj T 0 (x True.intro) : Bool - let x_val = ME::prj(mk_id("T"), 0, x_intro); - // T.mk (x True.intro).val : True → T (partial application — missing proof field) - let partial_mk = app(cnst("T.mk", &[]), x_val); - - // Eq (True → T) (T.mk (x True.intro).val) x - let ty = npi( - "x", - x_ty.clone(), - eq_expr(u1.clone(), x_ty.clone(), partial_mk, var(0)), - ); - let val = nlam("x", x_ty.clone(), eq_refl_expr(u1, x_ty, var(0))); - - let (id, c) = mk_defn( - "etaCtor", - 0, - vec![], - ty, - val, - ix_common::env::ReducibilityHints::Opaque, - ); - env.insert(id.clone(), c); - check_rejects(&mut env, &id); - } -} diff --git a/crates/kernel/src/tutorial/inductive.rs b/crates/kernel/src/tutorial/inductive.rs deleted file mode 100644 index a204fdd17..000000000 --- a/crates/kernel/src/tutorial/inductive.rs +++ /dev/null @@ -1,1865 +0,0 @@ -//! Good and bad inductive type tests. - -#[cfg(test)] -mod tests { - - use crate::constant::{KConst, RecRule}; - use crate::env::KEnv; - use crate::mode::Meta; - use crate::testing::*; - use ix_common::env::{Name, ReducibilityHints}; - - // ========================================================================== - // Batch 3: Bad inductives (Tutorial.lean lines 247–610) - // ========================================================================== - - /// Helper: build an inductive with no ctors, no recursor, just checking the type - fn mk_simple_indc( - env: &mut KEnv, - name: &str, - lvls: u64, - level_params: &[Name], - ty: &ME, - ) -> MId { - let block_id = mk_id(name); - let rec_name = &format!("{name}.rec"); - let rec_id = mk_id(rec_name); - // Inductive - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(name), - level_params: level_params.to_owned(), - lvls, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: ty.clone(), - ctors: vec![], - lean_all: vec![block_id.clone()], - }, - ); - // Dummy recursor (check_inductive needs one in the block) - let mut rec_lvl_params = vec![mk_name("u_rec")]; - rec_lvl_params.extend(level_params.to_owned()); - let rec_ty = npi( - "motive", - pi(cnst(name, &[]), sort(param(0))), - npi("t", cnst(name, &[]), app(var(1), var(0))), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(rec_name), - level_params: rec_lvl_params, - k: false, - is_unsafe: false, - lvls: lvls + 1, - params: 0, - indices: 0, - motives: 1, - minors: 0, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - env.blocks.insert(block_id.clone(), vec![block_id.clone(), rec_id]); - block_id - } - - /// inductBadNonSort: inductive with type = constType (not a Sort) - #[test] - fn bad_induct_non_sort_type() { - let mut env = KEnv::::new(); - let (ct_id, ct_c) = mk_defn( - "constType", - 0, - vec![], - pi(sort1(), pi(sort1(), sort1())), - nlam("x", sort1(), nlam("y", sort1(), var(1))), - ReducibilityHints::Abbrev, - ); - env.insert(ct_id, ct_c); - - let id = mk_simple_indc( - &mut env, - "inductBadNonSort", - 0, - &[], - &cnst("constType", &[]), // not a Sort! - ); - check_rejects(&mut env, &id); - } - - /// inductBadNonSort2: inductive with type = aType (axiom, not a Sort) - #[test] - fn bad_induct_non_sort_type2() { - let mut env = KEnv::::new(); - let (at_id, at_c) = mk_axiom("aType", 0, vec![], sort1()); - env.insert(at_id, at_c); - - let id = mk_simple_indc( - &mut env, - "inductBadNonSort2", - 0, - &[], - &cnst("aType", &[]), // aType : Type, but aType itself is not a Sort - ); - check_rejects(&mut env, &id); - } - - /// inductTooFewParams: claims numParams=2 but type only has 1 arrow - #[test] - fn bad_induct_too_few_params() { - let mut env = KEnv::::new(); - let block_id = mk_id("inductTooFewParams"); - let rec_id = mk_id("inductTooFewParams.rec"); - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name("inductTooFewParams"), - level_params: vec![], - lvls: 0, - params: 2, // claims 2 params - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: pi(sort0(), sort0()), // only 1 arrow — Prop → Prop - ctors: vec![], - lean_all: vec![block_id.clone()], - }, - ); - // Minimal recursor - let rec_ty = npi( - "motive", - pi(pi(sort0(), sort0()), sort(param(0))), - npi("t", pi(sort0(), sort0()), app(var(1), var(0))), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name("inductTooFewParams.rec"), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 2, - indices: 0, - motives: 1, - minors: 0, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - env.blocks.insert(block_id.clone(), vec![block_id.clone(), rec_id]); - check_rejects(&mut env, &block_id); - } - - /// indNeg: classic negative recursive occurrence: (I → I) → I - #[test] - fn bad_induct_negative_occurrence() { - let mut env = KEnv::::new(); - let n = "indNeg"; - let block_id = mk_id(n); - let ctor_id = mk_id("indNeg.mk"); - let rec_id = mk_id("indNeg.rec"); - - // indNeg : Type - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // indNeg.mk : (indNeg → indNeg) → indNeg - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name("indNeg.mk"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 1, - ty: pi(pi(cnst(n, &[]), cnst(n, &[])), cnst(n, &[])), - }, - ); - - // Dummy recursor - let motive_ty = pi(cnst(n, &[]), sort(param(0))); - let minor = npi( - "f", - pi(cnst(n, &[]), cnst(n, &[])), - app(var(1), app(var(0), var(0))), - ); - let rec_ty = npi( - "motive", - motive_ty, - npi("mk", minor, npi("t", cnst(n, &[]), app(var(2), var(0)))), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name("indNeg.rec"), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_rejects(&mut env, &block_id); - } - - /// typeWithTooHighTypeField: inductive Type 1 with a field of Type 1 (too high) - #[test] - fn bad_induct_too_high_field() { - let mut env = KEnv::::new(); - let n = "typeWithTooHighTypeField"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - // typeWithTooHighTypeField : Sort 1 = Type - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: sort1(), // Type = Sort 1 - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // .mk : Sort 1 → typeWithTooHighTypeField - // Field of type Sort 1 = Type, but inductive is in Sort 1 = Type. - // Fields must be < Sort level of inductive, so Type (Sort 1) is too high for Type inductive. - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 1, - ty: pi(sort1(), cnst(n, &[])), // Sort 1 → I - }, - ); - - // Dummy recursor - let motive_ty = pi(cnst(n, &[]), sort(param(0))); - let minor = npi( - "α", - sort1(), - app(var(1), app(cnst(&format!("{n}.mk"), &[]), var(0))), - ); - let rec_ty = npi( - "motive", - motive_ty, - npi("mk", minor, npi("t", cnst(n, &[]), app(var(2), var(0)))), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_rejects(&mut env, &block_id); - } - - // ========================================================================== - // Batch 3b: More bad inductives (Tutorial.lean lines 280–550) - // ========================================================================== - - /// inductWrongCtorParams: constructor's result has wrong parameter application - #[test] - fn bad_induct_wrong_ctor_params() { - let mut env = KEnv::::new(); - // axiom aProp : Prop - let (ap_id, ap_c) = mk_axiom("aProp", 0, vec![], sort0()); - env.insert(ap_id, ap_c); - - let n = "inductWrongCtorParams"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - // I : Prop → Type (1 param) - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 1, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: pi(sort0(), sort1()), - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // mk : ∀ (x : Type), I aProp — passes aProp instead of x as param - // At depth 1 (inside x binder): x = var(0) - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 1, - fields: 0, - ty: npi("x", sort1(), app(cnst(n, &[]), cnst("aProp", &[]))), - }, - ); - - // Dummy recursor - let rec_ty = ipi( - "motive", - pi(sort0(), pi(app(cnst(n, &[]), var(0)), sort(param(0)))), - npi( - "t", - sort0(), - npi("x", app(cnst(n, &[]), var(0)), app(app(var(2), var(1)), var(0))), - ), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 1, - indices: 0, - motives: 1, - minors: 0, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_rejects(&mut env, &block_id); - } - - /// reflOccLeft: recursive occurrence on LEFT of arrow behind further arrows - /// Constructor: (Nat → (I → Nat)) → I — I appears in negative position - #[test] - fn bad_induct_refl_occ_left() { - let mut env = KEnv::::new(); - // Need Nat as an axiom - let (nat_id, nat_c) = mk_axiom("Nat", 0, vec![], sort1()); - env.insert(nat_id, nat_c); - - let n = "reflOccLeft"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // mk : (Nat → (I → Nat)) → I - // The field type is Nat → (I → Nat), I occurs in negative position (left of inner arrow) - let field_ty = pi(cnst("Nat", &[]), pi(cnst(n, &[]), cnst("Nat", &[]))); - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 1, - ty: pi(field_ty, cnst(n, &[])), - }, - ); - - // Dummy recursor - let rec_ty = npi( - "motive", - pi(cnst(n, &[]), sort(param(0))), - npi( - "mk", - pi( - pi(cnst("Nat", &[]), pi(cnst(n, &[]), cnst("Nat", &[]))), - app(var(1), cnst(n, &[])), - ), - npi("t", cnst(n, &[]), app(var(2), var(0))), - ), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_rejects(&mut env, &block_id); - } - - /// reflOccInIndex: recursive occurrence in INDEX position behind arrow - /// I : Type → Type, ctor mk : (α : Type) → (Nat → I (I α)) → I α - #[test] - fn bad_induct_refl_occ_in_index() { - let mut env = KEnv::::new(); - let (nat_id, nat_c) = mk_axiom("Nat", 0, vec![], sort1()); - env.insert(nat_id, nat_c); - - let n = "reflOccInIndex"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - // I : Type → Type (0 params, 1 index) - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 1, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: npi("α", sort1(), sort1()), - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // mk : (α : Type) → (Nat → I (I α)) → I α - // At depth 1 (inside α): α = var(0) - // field: Nat → I (I α) — I applied to (I α), recursive in index - let i_alpha = app(cnst(n, &[]), var(0)); // I α - let i_i_alpha = app(cnst(n, &[]), i_alpha); // I (I α) - let _field_ty = pi(cnst("Nat", &[]), i_i_alpha); // Nat → I (I α), shifts inside pi - // But inside the field pi: Nat binder is var(0), α = var(1) - // So we need: pi(Nat, I(I(var(1)))) — var(1) = α shifted - let i_alpha_s = app(cnst(n, &[]), var(1)); - let i_i_alpha_s = app(cnst(n, &[]), i_alpha_s); - let field_ty_correct = pi(cnst("Nat", &[]), i_i_alpha_s); - let result = app(cnst(n, &[]), var(1)); // I α, with α shifted by field binder - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 1, - ty: npi("α", sort1(), pi(field_ty_correct, result)), - }, - ); - - // Dummy recursor - let rec_ty = npi( - "motive", - pi(sort1(), pi(app(cnst(n, &[]), var(0)), sort(param(0)))), - npi( - "t", - sort1(), - npi("x", app(cnst(n, &[]), var(0)), app(app(var(2), var(1)), var(0))), - ), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 1, - motives: 1, - minors: 1, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_rejects(&mut env, &block_id); - } - - // ========================================================================== - // Batch 8: More bad inductives (Tutorial.lean lines 347–557) - // ========================================================================== - - /// inductWrongCtorResParams: constructor result has parameters swapped - /// I : Prop → Prop → Type, mk : (x : Prop) → (y : Prop) → I y x (swapped!) - #[test] - fn bad_induct_wrong_ctor_res_params() { - let mut env = KEnv::::new(); - let n = "inductWrongCtorResParams"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - // I : Prop → Prop → Type (2 params) - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 2, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: npi("x", sort0(), npi("y", sort0(), sort1())), - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // mk : (x : Prop) → (y : Prop) → I y x (params swapped in result!) - // depth 2: x=var(1), y=var(0) - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 2, - fields: 0, - ty: npi( - "x", - sort0(), - npi("y", sort0(), app(app(cnst(n, &[]), var(0)), var(1))), - ), // I y x — swapped - }, - ); - - let rec_ty = npi( - "x", - sort0(), - npi( - "y", - sort0(), - ipi( - "motive", - pi(app(app(cnst(n, &[]), var(1)), var(0)), sort(param(0))), - npi("t", app(app(cnst(n, &[]), var(2)), var(1)), app(var(1), var(0))), - ), - ), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 2, - indices: 0, - motives: 1, - minors: 0, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_rejects(&mut env, &block_id); - } - - /// reduceCtorType: constructor type is `id Type I` instead of manifest `I` - /// The kernel should NOT reduce the constructor's overall type. - #[test] - fn bad_reduce_ctor_type() { - let mut env = KEnv::::new(); - // id1 : Sort 1 → Sort 1 := fun x => x - let (id1_id, id1_c) = mk_defn( - "id1", - 0, - vec![], - pi(sort(usucc(uzero())), sort(usucc(uzero()))), - nlam("x", sort(usucc(uzero())), var(0)), - ReducibilityHints::Abbrev, - ); - env.insert(id1_id, id1_c); - - let n = "reduceCtorType"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // mk : id1 I (should be just I, not wrapped in id1) - // id1 I reduces to I, but the kernel shouldn't reduce the ctor type - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 0, - ty: app(cnst("id1", &[]), cnst(n, &[])), // id1 I instead of I - }, - ); - - let rec_ty = npi( - "motive", - pi(cnst(n, &[]), sort(param(0))), - npi( - "mk", - app(var(0), cnst(&format!("{n}.mk"), &[])), - npi("t", cnst(n, &[]), app(var(2), var(0))), - ), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_rejects(&mut env, &block_id); - } - - /// indNegReducible: negative occurrence hidden behind reducible def - /// constType aType I → I where constType x y = x, so this reduces to aType → I - /// But the kernel should catch the negative occurrence before reducing. - #[test] - fn bad_induct_neg_reducible() { - let mut env = KEnv::::new(); - // constType : Type → Type → Type := fun x y => x - let (ct_id, ct_c) = mk_defn( - "constType", - 0, - vec![], - pi(sort1(), pi(sort1(), sort1())), - nlam("x", sort1(), nlam("y", sort1(), var(1))), - ReducibilityHints::Abbrev, - ); - env.insert(ct_id, ct_c); - // aType : Type - let (at_id, at_c) = mk_axiom("aType", 0, vec![], sort1()); - env.insert(at_id, at_c); - - let n = "indNegReducible"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // mk : (constType aType I → I) → I - // constType aType I = aType (first arg), so field type is (aType → I) - // But before reduction: constType aType I has I in head-normal form's first arg - // The kernel checks HNF and sees I in the function domain = negative occurrence - let ct_app = - app(app(cnst("constType", &[]), cnst("aType", &[])), cnst(n, &[])); - let field_ty = pi(ct_app, cnst(n, &[])); // (constType aType I) → I - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 1, - ty: pi(field_ty, cnst(n, &[])), - }, - ); - - let rec_ty = npi( - "motive", - pi(cnst(n, &[]), sort(param(0))), - npi( - "mk", - pi( - pi( - pi( - app( - app(cnst("constType", &[]), cnst("aType", &[])), - cnst(n, &[]), - ), - cnst(n, &[]), - ), - cnst(n, &[]), - ), - app(var(1), cnst(n, &[])), - ), - npi("t", cnst(n, &[]), app(var(2), var(0))), - ), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_rejects(&mut env, &block_id); - } - - /// indReducedAway: `mk : constType aType I → I` where `constType x y = x`. - /// The field mentions `I` only before reduction: its whnf is `aType`, so - /// Lean's `check_positivity` (whnf first, then the occurrence test) takes - /// it as a nonrecursive field and accepts the block. The kernel used to test the occurrence - /// before whnf and reject the whnf'd head `aType` as "not a valid inductive - /// app". `bad_induct_neg_reducible` is the negative neighbour: there the - /// whnf is a Pi whose domain still mentions `I`. - #[test] - fn good_induct_occurrence_reduced_away() { - let mut env = KEnv::::new(); - // constType : Type → Type → Type := fun x y => x - let (ct_id, ct_c) = mk_defn( - "constType", - 0, - vec![], - pi(sort1(), pi(sort1(), sort1())), - nlam("x", sort1(), nlam("y", sort1(), var(1))), - ReducibilityHints::Abbrev, - ); - env.insert(ct_id, ct_c); - // aType : Type - let (at_id, at_c) = mk_axiom("aType", 0, vec![], sort1()); - env.insert(at_id, at_c); - - let n = "indReducedAway"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // mk : constType aType I → I - let ct_app = - app(app(cnst("constType", &[]), cnst("aType", &[])), cnst(n, &[])); - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 1, - ty: pi(ct_app.clone(), cnst(n, &[])), - }, - ); - - // rec : {motive : I → Sort u} → ((x : constType aType I) → motive (mk x)) - // → (t : I) → motive t (the field is not recursive: no IH) - let rec_ty = ipi( - "motive", - pi(cnst(n, &[]), sort(param(0))), - npi( - "mk", - npi( - "x", - ct_app, - app(var(1), app(cnst(&format!("{n}.mk"), &[]), var(0))), - ), - npi("t", cnst(n, &[]), app(var(2), var(0))), - ), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_accepts(&mut env, &block_id); - } - - // ========================================================================== - // Batch 9: Good inductives with universe constraints (Tutorial.lean 558–610) - // ========================================================================== - - /// predWithTypeField : Prop — inductive Prop with a Type field (allowed for Props) - #[test] - fn good_pred_with_type_field() { - let mut env = KEnv::::new(); - let n = "PredWithTypeField"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - // PredWithTypeField : Prop - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: sort0(), // Prop - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // mk : Type → PredWithTypeField (field is Type, allowed for Prop inductives) - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 1, - ty: npi("α", sort1(), cnst(n, &[])), - }, - ); - - // Recursor (can only eliminate into Prop for this kind of inductive) - let rec_ty = ipi( - "motive", - pi(cnst(n, &[]), sort0()), - npi( - "mk", - npi( - "α", - sort1(), - app(var(1), app(cnst(&format!("{n}.mk"), &[]), var(0))), - ), - npi("t", cnst(n, &[]), app(var(2), var(0))), - ), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![], // no extra level param — eliminates only into Prop - k: false, - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_accepts(&mut env, &block_id); - } - - /// typeWithTypeField : Type 1 — inductive Type 1 with a Type field (allowed) - #[test] - fn good_type_with_type_field() { - let mut env = KEnv::::new(); - let n = "TypeWithTypeField"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - // TypeWithTypeField : Sort 2 = Type 1 - let sort2 = sort(usucc(usucc(uzero()))); - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: sort2, // Type 1 - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // mk : Type → TypeWithTypeField (field is Type = Sort 1, OK for Type 1 inductive) - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 1, - ty: npi("α", sort1(), cnst(n, &[])), - }, - ); - - let rec_ty = ipi( - "motive", - pi(cnst(n, &[]), sort(param(0))), - npi( - "mk", - npi( - "α", - sort1(), - app(var(1), app(cnst(&format!("{n}.mk"), &[]), var(0))), - ), - npi("t", cnst(n, &[]), app(var(2), var(0))), - ), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_accepts(&mut env, &block_id); - } - - // ========================================================================== - // Batch 11: inductInIndex, inductWrongCtorResLevel (Tutorial.lean 377–436) - // ========================================================================== - - /// inductWrongCtorResLevel: constructor result applies inductive with - /// swapped level params [u2, u1] instead of [u1, u2] - #[test] - fn bad_induct_wrong_ctor_res_level() { - let mut env = KEnv::::new(); - let n = "inductWrongCtorResLevel"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - // I.{u1, u2} : Prop → Prop → Type (2 params, 2 level params) - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![mk_name("u1"), mk_name("u2")], - lvls: 2, - params: 2, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: npi("x", sort0(), npi("y", sort0(), sort1())), - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // mk.{u1, u2} : (x : Prop) → (y : Prop) → I.{u2, u1} x y - // Note: level params are SWAPPED in the result: [u2, u1] instead of [u1, u2] - // depth 2: x=var(1), y=var(0) - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![mk_name("u1"), mk_name("u2")], - is_unsafe: false, - lvls: 2, - induct: block_id.clone(), - cidx: 0, - params: 2, - fields: 0, - ty: npi( - "x", - sort0(), - npi( - "y", - sort0(), - // I.{u2, u1} x y — level params swapped! - app(app(cnst(n, &[param(1), param(0)]), var(1)), var(0)), - ), - ), - }, - ); - - // Dummy recursor - let rec_ty = npi( - "x", - sort0(), - npi( - "y", - sort0(), - ipi( - "motive", - pi( - app(app(cnst(n, &[param(0), param(1)]), var(1)), var(0)), - sort(param(2)), - ), - npi( - "t", - app(app(cnst(n, &[param(0), param(1)]), var(2)), var(1)), - app(var(1), var(0)), - ), - ), - ), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![mk_name("u_rec"), mk_name("u1"), mk_name("u2")], - k: false, - is_unsafe: false, - lvls: 3, - params: 2, - indices: 0, - motives: 1, - minors: 0, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_rejects(&mut env, &block_id); - } - - /// inductInIndex: constructor result has inductive applied to itself in index position - /// I : Prop → Prop, mk : I (I aProp) — recursive occurrence in index - #[test] - fn bad_induct_in_index() { - let mut env = KEnv::::new(); - let (ap_id, ap_c) = mk_axiom("aProp", 0, vec![], sort0()); - env.insert(ap_id, ap_c); - - let n = "inductInIndex"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - // I : Prop → Prop (0 params, 1 index) - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 1, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: pi(sort0(), sort0()), - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // mk : I (I aProp) — I applied with I(aProp) as index - let i_aprop = app(cnst(n, &[]), cnst("aProp", &[])); - let i_i_aprop = app(cnst(n, &[]), i_aprop); - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 0, - ty: i_i_aprop, - }, - ); - - let rec_ty = ipi( - "motive", - npi("x", sort0(), pi(app(cnst(n, &[]), var(0)), sort0())), - npi( - "x", - sort0(), - npi("t", app(cnst(n, &[]), var(0)), app(app(var(2), var(1)), var(0))), - ), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![], - k: false, - is_unsafe: false, - lvls: 0, - params: 0, - indices: 1, - motives: 1, - minors: 0, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_rejects(&mut env, &block_id); - } - - // ========================================================================== - // Batch 14: Inductive with dup level params (Tutorial.lean 282–296) - // ========================================================================== - - /// inductLevelParam: inductive with duplicate level params [u, u] - #[test] - fn bad_induct_dup_level_params() { - let mut env = KEnv::::new(); - let id = mk_simple_indc( - &mut env, - "inductLevelParam", - 2, // 2 level params - &[mk_name("u"), mk_name("u")], // duplicate! - &sort1(), - ); - check_rejects(&mut env, &id); - } - - // ========================================================================== - // Batch 17: BoolProp — Prop inductive with 2 ctors, large elim restriction - // (Tutorial.lean 658–663) - // ========================================================================== - - /// BoolProp : Prop with 2 constructors — recursor can only eliminate into Prop - #[test] - fn good_bool_prop_rec() { - let mut env = KEnv::::new(); - - let n = "BoolProp"; - let block_id = mk_id(n); - let a_id = mk_id("BoolProp.a"); - let b_id = mk_id("BoolProp.b"); - let rec_id = mk_id("BoolProp.rec"); - - // BoolProp : Prop - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: sort0(), // Prop - ctors: vec![a_id.clone(), b_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - env.insert( - a_id.clone(), - KConst::Ctor { - name: mk_name("BoolProp.a"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 0, - ty: cnst(n, &[]), - }, - ); - - env.insert( - b_id.clone(), - KConst::Ctor { - name: mk_name("BoolProp.b"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 1, - params: 0, - fields: 0, - ty: cnst(n, &[]), - }, - ); - - // BoolProp.rec : ∀ {motive : BoolProp → Prop} - // (a : motive BoolProp.a) (b : motive BoolProp.b) (x : BoolProp), motive x - // Note: eliminates into Prop only (no level param), because 2 ctors for a Prop inductive - let motive_ty = pi(cnst(n, &[]), sort0()); // BoolProp → Prop - let minor_a = app(var(0), cnst("BoolProp.a", &[])); - let minor_b = app(var(1), cnst("BoolProp.b", &[])); - let rec_ty = ipi( - "motive", - motive_ty.clone(), - npi( - "a", - minor_a.clone(), - npi("b", minor_b.clone(), npi("x", cnst(n, &[]), app(var(3), var(0)))), - ), - ); - - let rule_a_rhs = nlam( - "motive", - motive_ty.clone(), - nlam("ha", minor_a.clone(), nlam("hb", minor_b.clone(), var(1))), - ); - let rule_b_rhs = nlam( - "motive", - motive_ty, - nlam("ha", minor_a, nlam("hb", minor_b, var(0))), - ); - - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name("BoolProp.rec"), - level_params: vec![], - k: false, - is_unsafe: false, - lvls: 0, // no level param — Prop only - params: 0, - indices: 0, - motives: 1, - minors: 2, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![ - RecRule { ctor: Name::anon(), fields: 0, rhs: rule_a_rhs }, - RecRule { ctor: Name::anon(), fields: 0, rhs: rule_b_rhs }, - ], - lean_all: vec![block_id.clone()], - }, - ); - - env.blocks.insert( - block_id.clone(), - vec![block_id.clone(), a_id, b_id, rec_id.clone()], - ); - - // Check the inductive - check_accepts(&mut env, &block_id); - // Check the recursor - check_accepts(&mut env, &rec_id); - } - - // ========================================================================== - // Batch 19: reduceCtorParam — good inductive where ctor param type needs reduction - // (Tutorial.lean 468–485) - // ========================================================================== - - /// reduceCtorParam: inductive I : Type → Type with ctor - /// mk : (α : id Type) → (constType (I α) (I α)) → I α - /// The kernel should reduce `id Type` → Type and `constType (I α) (I α)` → I α - /// in ctor parameter positions. - #[test] - fn good_reduce_ctor_param() { - let mut env = KEnv::::new(); - - // id1 : Sort 1 → Sort 1 := fun x => x - let (id1_id, id1_c) = mk_defn( - "id1", - 0, - vec![], - pi(sort(usucc(uzero())), sort(usucc(uzero()))), - nlam("x", sort(usucc(uzero())), var(0)), - ReducibilityHints::Abbrev, - ); - env.insert(id1_id, id1_c); - - // constType : Type → Type → Type := fun x y => x - let (ct_id, ct_c) = mk_defn( - "constType", - 0, - vec![], - pi(sort1(), pi(sort1(), sort1())), - nlam("x", sort1(), nlam("y", sort1(), var(1))), - ReducibilityHints::Abbrev, - ); - env.insert(ct_id, ct_c); - - let n = "reduceCtorParam"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - // reduceCtorParam : Type → Type (1 param) - // is_rec = true because field `constType (I α) (I α)` reduces to `I α` (recursive) - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 1, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: pi(sort1(), sort1()), - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // mk : (α : id1 Type) → (constType (I α) (I α)) → I α - // id1 Type reduces to Type, constType (I α) (I α) reduces to I α - // depth 1 (inside α binder): α=var(0) - // The param type is `id1 Type` = `app(cnst("id1"), sort1())` - // The field type is `constType (I α) (I α)` at depth 1: - // app(app(cnst("constType"), app(cnst(n), var(0))), app(cnst(n), var(0))) - // Inside the field pi (depth 2): α=var(1), field binder=var(0) - // Result: I α at depth 2 = app(cnst(n), var(1)) - let id1_type = app(cnst("id1", &[]), sort1()); - let i_alpha = app(cnst(n, &[]), var(0)); - let field_ty = app(app(cnst("constType", &[]), i_alpha.clone()), i_alpha); - let result = app(cnst(n, &[]), var(1)); // I α shifted by field binder - - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 1, - fields: 1, - ty: npi("α", id1_type, pi(field_ty, result)), - }, - ); - - // Recursor - let motive_ty = - npi("α", sort1(), pi(app(cnst(n, &[]), var(0)), sort(param(0)))); - let minor = npi( - "α", - sort1(), - npi( - "x", - app( - app(cnst("constType", &[]), app(cnst(n, &[]), var(0))), - app(cnst(n, &[]), var(0)), - ), - app(app(var(2), var(1)), app(cnst(&format!("{n}.mk"), &[]), var(0))), - ), - ); - let rec_ty = ipi( - "motive", - motive_ty, - npi( - "mk", - minor, - npi( - "α", - sort1(), - npi("t", app(cnst(n, &[]), var(0)), app(app(var(3), var(1)), var(0))), - ), - ), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 1, - indices: 0, - motives: 1, - minors: 1, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_accepts(&mut env, &block_id); - } - - // ========================================================================== - // reduceCtorParamRefl: reflexive inductive with reducible ctor param types - // (Tutorial.lean 1095–1107) - // ========================================================================== - - /// reduceCtorParamRefl: I : Type → Type, 1 param - /// mk : (α : id Type) → (α → constType (I α) (I α)) → I α - /// Field type α → constType (I α) (I α) reduces to α → I α (reflexive occurrence). - /// Kernel should reduce ctor param types and accept this reflexive inductive. - #[test] - fn good_reduce_ctor_param_refl() { - let mut env = KEnv::::new(); - - // id1 : Sort 1 → Sort 1 := fun x => x - let (id1_id, id1_c) = mk_defn( - "id1", - 0, - vec![], - pi(sort(usucc(uzero())), sort(usucc(uzero()))), - nlam("x", sort(usucc(uzero())), var(0)), - ReducibilityHints::Abbrev, - ); - env.insert(id1_id, id1_c); - - // constType : Type → Type → Type := fun x y => x - let (ct_id, ct_c) = mk_defn( - "constType", - 0, - vec![], - pi(sort1(), pi(sort1(), sort1())), - nlam("x", sort1(), nlam("y", sort1(), var(1))), - ReducibilityHints::Abbrev, - ); - env.insert(ct_id, ct_c); - - let n = "reduceCtorParamRefl"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - // I : Type → Type (1 param), reflexive - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 1, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: pi(sort1(), sort1()), - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // mk : (α : id1 Type) → (α → constType (I α) (I α)) → I α - // Param type: id1 Type (reduces to Type) - // Field type: α → constType (I α) (I α) where α=var(0) at depth 1 - // Inside field pi (depth 2): x=var(0), α=var(1) - // constType (I α) (I α) = constType (I var(1)) (I var(1)) reduces to I var(1) - let id1_type = app(cnst("id1", &[]), sort1()); - let i_alpha = app(cnst(n, &[]), var(1)); // I α at depth 2 - let ct_i_i = app(app(cnst("constType", &[]), i_alpha.clone()), i_alpha); - let field_ty = pi(var(0), ct_i_i); // α → constType (I α) (I α) at depth 1 - // result: I α at depth 2 (inside field binder) - let result = app(cnst(n, &[]), var(1)); - - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 1, - fields: 1, - ty: npi("α", id1_type, pi(field_ty, result)), - }, - ); - - // Minimal recursor - let rec_ty = ipi( - "motive", - npi("α", sort1(), pi(app(cnst(n, &[]), var(0)), sort(param(0)))), - npi( - "α", - sort1(), - npi("t", app(cnst(n, &[]), var(0)), app(app(var(2), var(1)), var(0))), - ), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 1, - indices: 0, - motives: 1, - minors: 0, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_accepts(&mut env, &block_id); - } - - /// reduceCtorParamRefl2: variant where constType (I α) α reduces to I α (not I α, I α) - /// mk : (α : id Type) → (α → constType (I α) α) → I α - /// Field: α → constType (I α) α reduces to α → I α (reflexive) - #[test] - fn good_reduce_ctor_param_refl2() { - let mut env = KEnv::::new(); - - let (id1_id, id1_c) = mk_defn( - "id1", - 0, - vec![], - pi(sort(usucc(uzero())), sort(usucc(uzero()))), - nlam("x", sort(usucc(uzero())), var(0)), - ReducibilityHints::Abbrev, - ); - env.insert(id1_id, id1_c); - let (ct_id, ct_c) = mk_defn( - "constType", - 0, - vec![], - pi(sort1(), pi(sort1(), sort1())), - nlam("x", sort1(), nlam("y", sort1(), var(1))), - ReducibilityHints::Abbrev, - ); - env.insert(ct_id, ct_c); - - let n = "reduceCtorParamRefl2"; - let block_id = mk_id(n); - let ctor_id = mk_id(&format!("{n}.mk")); - let rec_id = mk_id(&format!("{n}.rec")); - - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 1, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: pi(sort1(), sort1()), - ctors: vec![ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // mk : (α : id1 Type) → (α → constType (I α) α) → I α - // d1: α=var(0). id1 Type as domain. - // d2 (inside field pi): x=var(0), α=var(1) - // constType (I α) α = constType (I var(1)) var(1) → reduces to I var(1) - let id1_type = app(cnst("id1", &[]), sort1()); - let i_alpha_d2 = app(cnst(n, &[]), var(1)); // I α at depth 2 - let ct_i_a = app(app(cnst("constType", &[]), i_alpha_d2), var(1)); // constType (I α) α - let field_ty = pi(var(0), ct_i_a); // α → constType (I α) α at d1 - let result = app(cnst(n, &[]), var(1)); // I α at d2 - - env.insert( - ctor_id.clone(), - KConst::Ctor { - name: mk_name(&format!("{n}.mk")), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 1, - fields: 1, - ty: npi("α", id1_type, pi(field_ty, result)), - }, - ); - - let rec_ty = ipi( - "motive", - npi("α", sort1(), pi(app(cnst(n, &[]), var(0)), sort(param(0)))), - npi( - "α", - sort1(), - npi("t", app(cnst(n, &[]), var(0)), app(app(var(2), var(1)), var(0))), - ), - ); - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name(&format!("{n}.rec")), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 1, - indices: 0, - motives: 1, - minors: 0, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![], - lean_all: vec![block_id.clone()], - }, - ); - - env - .blocks - .insert(block_id.clone(), vec![block_id.clone(), ctor_id, rec_id]); - check_accepts(&mut env, &block_id); - } -} diff --git a/crates/kernel/src/tutorial/mod.rs b/crates/kernel/src/tutorial/mod.rs deleted file mode 100644 index 8a58b7236..000000000 --- a/crates/kernel/src/tutorial/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Tests translated from lean-kernel-arena tutorial/Tutorial.lean. -//! -//! Each test builds a small `KEnv` and checks that the zero kernel -//! correctly accepts or rejects specific constants. -//! -//! Organized by category: -//! - `basic`: definitions, levels, lets, forall checks -//! - `inductive`: good and bad inductive types -//! - `reduction`: recursor reduction, Peano arithmetic, Bool/Nat rec -//! - `defeq`: proof irrelevance, eta, equality - -mod basic; -mod defeq; -mod inductive; -mod reduction; diff --git a/crates/kernel/src/tutorial/reduction.rs b/crates/kernel/src/tutorial/reduction.rs deleted file mode 100644 index 3edf67885..000000000 --- a/crates/kernel/src/tutorial/reduction.rs +++ /dev/null @@ -1,1727 +0,0 @@ -//! Recursor reduction tests: Peano arithmetic, Bool.rec, Nat.rec. - -#[cfg(test)] -mod tests { - - use crate::constant::KConst; - use crate::constant::RecRule; - use crate::env::KEnv; - use crate::mode::Meta; - use crate::testing::*; - use ix_common::env::{Name, ReducibilityHints}; - - // ========================================================================== - // Batch 5: Peano arithmetic (Tutorial.lean lines 127–153) - // ========================================================================== - - /// Build a Church-numeral Peano env: - /// PN := ∀ α, (α → α) → α → α - /// PN.zero : PN := fun α s z => z - /// PN.succ : PN → PN := fun n α s z => s (n α s z) - fn peano_env() -> KEnv { - let mut env = KEnv::::new(); - // PN := ∀ α, (α → α) → α → α - // = ∀ (α : Type), (α → α) → α → α - // depth 0: α=var(0). (α → α) = pi(var(0), var(1)). α → α at depth 1. - // Full: npi("α", sort1(), pi(pi(var(0), var(1)), pi(var(1), var(2)))) - let pn_ty = sort1(); // PN : Type - let _pn_val = npi( - "α", - sort1(), - pi( - pi(var(0), var(1)), // (α → α) at depth 1: α shifted to var(1) - pi( - var(1), // α at depth 2: α = var(2)... wait - var(2), - ), - ), - ); // α at depth 3 - // Actually: ∀ (α : Type), (α → α) → α → α - // = npi("α", Sort 1, npi("s", pi(var(0), var(1)), npi("z", var(1), var(2)))) - // depth 0 (outside): nothing - // depth 1 (inside α): α = var(0) - // s_ty = α → α = pi(var(0), var(1)) — inside pi: α shifts to var(1) - // depth 2 (inside s): s = var(0), α = var(1) - // z_ty = α = var(1) - // depth 3 (inside z): z = var(0), s = var(1), α = var(2) - // result = α = var(2) - let pn_val2 = - npi("α", sort1(), npi("s", pi(var(0), var(1)), npi("z", var(1), var(2)))); - let (pn_id, pn_c) = - mk_defn("PN", 0, vec![], pn_ty, pn_val2, ReducibilityHints::Abbrev); - env.insert(pn_id, pn_c); - - // PN.zero : PN := fun α s z => z - let (z_id, z_c) = mk_defn( - "PN.zero", - 0, - vec![], - cnst("PN", &[]), - nlam( - "α", - sort1(), - nlam("s", pi(var(0), var(1)), nlam("z", var(1), var(0))), - ), - ReducibilityHints::Abbrev, - ); - env.insert(z_id, z_c); - - // PN.succ : PN → PN := fun n α s z => s (n α s z) - // depth 4: z=var(0), s=var(1), α=var(2), n=var(3) - // n α s z = app(app(app(var(3), var(2)), var(1)), var(0)) - // s (n α s z) = app(var(1), app(app(app(var(3), var(2)), var(1)), var(0))) - let succ_body = app(var(1), apps(var(3), &[var(2), var(1), var(0)])); - let (s_id, s_c) = mk_defn( - "PN.succ", - 0, - vec![], - pi(cnst("PN", &[]), cnst("PN", &[])), - nlam( - "n", - cnst("PN", &[]), - nlam( - "α", - sort1(), - nlam("s", pi(var(0), var(1)), nlam("z", var(1), succ_body)), - ), - ), - ReducibilityHints::Abbrev, - ); - env.insert(s_id, s_c); - - // PN.add : PN → PN → PN := fun n m α s z => n α s (m α s z) - // depth 5: z=0, s=1, α=2, m=3, n=4 - let add_body = - apps(var(4), &[var(2), var(1), apps(var(3), &[var(2), var(1), var(0)])]); - let (a_id, a_c) = mk_defn( - "PN.add", - 0, - vec![], - pi(cnst("PN", &[]), pi(cnst("PN", &[]), cnst("PN", &[]))), - nlam( - "n", - cnst("PN", &[]), - nlam( - "m", - cnst("PN", &[]), - nlam( - "α", - sort1(), - nlam("s", pi(var(0), var(1)), nlam("z", var(1), add_body)), - ), - ), - ), - ReducibilityHints::Abbrev, - ); - env.insert(a_id, a_c); - - // PN.mul : PN → PN → PN := fun n m α s z => n α (m α s) z - // depth 5: z=0, s=1, α=2, m=3, n=4 - // m α s = app(app(var(3), var(2)), var(1)) - let mul_body = - apps(var(4), &[var(2), app(app(var(3), var(2)), var(1)), var(0)]); - let (m_id, m_c) = mk_defn( - "PN.mul", - 0, - vec![], - pi(cnst("PN", &[]), pi(cnst("PN", &[]), cnst("PN", &[]))), - nlam( - "n", - cnst("PN", &[]), - nlam( - "m", - cnst("PN", &[]), - nlam( - "α", - sort1(), - nlam("s", pi(var(0), var(1)), nlam("z", var(1), mul_body)), - ), - ), - ), - ReducibilityHints::Abbrev, - ); - env.insert(m_id, m_c); - - // Convenience: PN.lit0 .. PN.lit4 - let lit0 = cnst("PN.zero", &[]); - let lit1 = app(cnst("PN.succ", &[]), lit0.clone()); - let lit2 = app(cnst("PN.succ", &[]), lit1.clone()); - let lit4 = - app(cnst("PN.succ", &[]), app(cnst("PN.succ", &[]), lit2.clone())); - for (name, val) in [ - ("PN.lit0", lit0), - ("PN.lit1", lit1), - ("PN.lit2", lit2.clone()), - ("PN.lit4", lit4), - ] { - let (id, c) = mk_defn( - name, - 0, - vec![], - cnst("PN", &[]), - val, - ReducibilityHints::Abbrev, - ); - env.insert(id, c); - } - - add_eq_axioms(&mut env); - env - } - - /// peano1 : ∀ (t : PN → Prop) (v : (n : PN) → t n), t PN.lit2 := fun t v => v PN.lit2 - #[test] - fn good_peano1() { - let env = peano_env(); - let ty = npi( - "t", - pi(cnst("PN", &[]), sort0()), - npi( - "v", - npi("n", cnst("PN", &[]), app(var(1), var(0))), - app(var(1), cnst("PN.lit2", &[])), - ), - ); - let val = nlam( - "t", - pi(cnst("PN", &[]), sort0()), - nlam( - "v", - npi("n", cnst("PN", &[]), app(var(1), var(0))), - app(var(0), cnst("PN.lit2", &[])), - ), - ); - let mut env2 = env; - let (id, c) = mk_thm("peano1", 0, vec![], ty, val); - env2.insert(id.clone(), c); - check_accepts(&mut env2, &id); - } - - /// peano2 : ∀ (t : PN → Prop) (v : (n : PN) → t n), t PN.lit2 := fun t v => v (PN.add PN.lit1 PN.lit1) - /// Tests that 1 + 1 reduces to 2 via Church numeral reduction. - #[test] - fn good_peano2() { - let env = peano_env(); - let ty = npi( - "t", - pi(cnst("PN", &[]), sort0()), - npi( - "v", - npi("n", cnst("PN", &[]), app(var(1), var(0))), - app(var(1), cnst("PN.lit2", &[])), - ), - ); - // Value uses add lit1 lit1 instead of lit2 - let one_plus_one = - app(app(cnst("PN.add", &[]), cnst("PN.lit1", &[])), cnst("PN.lit1", &[])); - let val = nlam( - "t", - pi(cnst("PN", &[]), sort0()), - nlam( - "v", - npi("n", cnst("PN", &[]), app(var(1), var(0))), - app(var(0), one_plus_one), - ), - ); - let mut env2 = env; - let (id, c) = mk_thm("peano2", 0, vec![], ty, val); - env2.insert(id.clone(), c); - check_accepts(&mut env2, &id); - } - - /// peano3 : ∀ (t : PN → Prop) (v : (n : PN) → t n), t PN.lit4 := fun t v => v (PN.mul PN.lit2 PN.lit2) - /// Tests that 2 * 2 reduces to 4 via Church numeral reduction. - #[test] - fn good_peano3() { - let env = peano_env(); - let ty = npi( - "t", - pi(cnst("PN", &[]), sort0()), - npi( - "v", - npi("n", cnst("PN", &[]), app(var(1), var(0))), - app(var(1), cnst("PN.lit4", &[])), - ), - ); - let two_times_two = - app(app(cnst("PN.mul", &[]), cnst("PN.lit2", &[])), cnst("PN.lit2", &[])); - let val = nlam( - "t", - pi(cnst("PN", &[]), sort0()), - nlam( - "v", - npi("n", cnst("PN", &[]), app(var(1), var(0))), - app(var(0), two_times_two), - ), - ); - let mut env2 = env; - let (id, c) = mk_thm("peano3", 0, vec![], ty, val); - env2.insert(id.clone(), c); - check_accepts(&mut env2, &id); - } - - // ========================================================================== - // Batch 13: Bool inductive + recursor reduction (Tutorial.lean 206, 693) - // ========================================================================== - - /// Build Bool environment with working recursor rules. - fn bool_env() -> KEnv { - let mut env = KEnv::::new(); - let n = "Bool"; - let block_id = mk_id(n); - let false_id = mk_id("Bool.false"); - let true_id = mk_id("Bool.true"); - let rec_id = mk_id("Bool.rec"); - - // Bool : Type - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![false_id.clone(), true_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // Bool.false : Bool - env.insert( - false_id.clone(), - KConst::Ctor { - name: mk_name("Bool.false"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 0, - ty: cnst(n, &[]), - }, - ); - - // Bool.true : Bool - env.insert( - true_id.clone(), - KConst::Ctor { - name: mk_name("Bool.true"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 1, - params: 0, - fields: 0, - ty: cnst(n, &[]), - }, - ); - - // Bool.rec : ∀ {motive : Bool → Sort u} (false : motive Bool.false) (true : motive Bool.true) (t : Bool), motive t - let motive_ty = pi(cnst(n, &[]), sort(param(0))); - let minor_false = app(var(0), cnst("Bool.false", &[])); - let minor_true = app(var(1), cnst("Bool.true", &[])); - let rec_ty = ipi( - "motive", - motive_ty.clone(), - npi( - "false", - minor_false.clone(), - npi( - "true", - minor_true.clone(), - npi("t", cnst(n, &[]), app(var(3), var(0))), - ), - ), - ); - - // Rule 0 (false): λ motive hf ht, hf - let rule_false_rhs = nlam( - "motive", - motive_ty.clone(), - nlam("hf", minor_false.clone(), nlam("ht", minor_true.clone(), var(1))), - ); - // Rule 1 (true): λ motive hf ht, ht - let rule_true_rhs = nlam( - "motive", - motive_ty, - nlam("hf", minor_false, nlam("ht", minor_true, var(0))), - ); - - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name("Bool.rec"), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 2, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![ - RecRule { ctor: Name::anon(), fields: 0, rhs: rule_false_rhs }, - RecRule { ctor: Name::anon(), fields: 0, rhs: rule_true_rhs }, - ], - lean_all: vec![block_id.clone()], - }, - ); - - env - .blocks - .insert(block_id.clone(), vec![block_id, false_id, true_id, rec_id]); - add_eq_axioms(&mut env); - env - } - - /// boolRecEqns: Bool.rec false_val true_val false = false_val - /// ∧ Bool.rec false_val true_val true = true_val - #[test] - fn good_bool_rec_reduction() { - let mut env = bool_env(); - - // Test: Bool.rec (motive := fun _ => Bool) Bool.false Bool.true Bool.false = Bool.false - // i.e., the recursor on false returns the false-case value - // - // ∀ {motive : Bool → Sort 1} (hf : motive Bool.false) (ht : motive Bool.true), - // Eq.{1} (motive Bool.false) (Bool.rec hf ht Bool.false) hf - // - // Simplified: test with concrete motive = fun _ => Bool - let motive = nlam("_", cnst("Bool", &[]), cnst("Bool", &[])); // fun _ => Bool - let rec_app = apps( - cnst("Bool.rec", &[usucc(uzero())]), - &[ - motive.clone(), - cnst("Bool.false", &[]), // false case returns Bool.false - cnst("Bool.true", &[]), // true case returns Bool.true - cnst("Bool.false", &[]), // major: false - ], - ); - // After reduction: Bool.rec ... false = false-case = Bool.false - let ty = eq_expr( - usucc(uzero()), - cnst("Bool", &[]), - rec_app, - cnst("Bool.false", &[]), - ); - let val = - eq_refl_expr(usucc(uzero()), cnst("Bool", &[]), cnst("Bool.false", &[])); - let (id, c) = mk_thm("boolRecFalse", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// Bool.rec on true returns the true-case value - #[test] - fn good_bool_rec_reduction_true() { - let mut env = bool_env(); - - let motive = nlam("_", cnst("Bool", &[]), cnst("Bool", &[])); - let rec_app = apps( - cnst("Bool.rec", &[usucc(uzero())]), - &[ - motive, - cnst("Bool.false", &[]), - cnst("Bool.true", &[]), - cnst("Bool.true", &[]), // major: true - ], - ); - let ty = eq_expr( - usucc(uzero()), - cnst("Bool", &[]), - rec_app, - cnst("Bool.true", &[]), - ); - let val = - eq_refl_expr(usucc(uzero()), cnst("Bool", &[]), cnst("Bool.true", &[])); - let (id, c) = mk_thm("boolRecTrue", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - // ========================================================================== - // Batch 16: Nat inductive + recursor reduction (Tutorial.lean 231, 710–718) - // ========================================================================== - - /// Build N (Nat-like) environment with working recursor rules. - fn nat_env() -> KEnv { - let mut env = KEnv::::new(); - let n = "N"; - let block_id = mk_id(n); - let zero_id = mk_id("N.zero"); - let succ_id = mk_id("N.succ"); - let rec_id = mk_id("N.rec"); - - let nat = || cnst(n, &[]); - - // N : Type - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![zero_id.clone(), succ_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // N.zero : N - env.insert( - zero_id.clone(), - KConst::Ctor { - name: mk_name("N.zero"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 0, - ty: nat(), - }, - ); - - // N.succ : N → N - env.insert( - succ_id.clone(), - KConst::Ctor { - name: mk_name("N.succ"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 1, - params: 0, - fields: 1, - ty: pi(nat(), nat()), - }, - ); - - // N.rec : ∀ {motive : N → Sort u} (zero : motive N.zero) - // (succ : ∀ (a : N), motive a → motive a.succ) (t : N), motive t - let motive_ty = pi(nat(), sort(param(0))); - let minor_zero = app(var(0), cnst("N.zero", &[])); - // succ minor: ∀ (a : N) (ih : motive a), motive (N.succ a) - // depth of succ minor (inside motive binder): motive = var(1) - // Inside the succ forall: a=var(0), motive=var(2) - // Inside the ih forall: ih=var(0), a=var(1), motive=var(3) - let minor_succ = npi( - "a", - nat(), - npi( - "ih", - app(var(2), var(0)), - app(var(3), app(cnst("N.succ", &[]), var(1))), - ), - ); - let rec_ty = ipi( - "motive", - motive_ty.clone(), - npi( - "zero", - minor_zero.clone(), - npi("succ", minor_succ.clone(), npi("t", nat(), app(var(3), var(0)))), - ), - ); - - // Rule 0 (zero, 0 fields): λ motive h_zero h_succ, h_zero - let rule_zero_rhs = nlam( - "motive", - motive_ty.clone(), - nlam( - "h_zero", - minor_zero.clone(), - nlam("h_succ", minor_succ.clone(), var(1)), - ), - ); - - // Rule 1 (succ, 1 field): λ motive h_zero h_succ n, h_succ n (N.rec motive h_zero h_succ n) - // depth 4: n=var(0), h_succ=var(1), h_zero=var(2), motive=var(3) - let nat_rec = cnst("N.rec", &[param(0)]); - let ih = apps(nat_rec, &[var(3), var(2), var(1), var(0)]); - let rule_succ_rhs = nlam( - "motive", - motive_ty, - nlam( - "h_zero", - minor_zero, - nlam( - "h_succ", - minor_succ, - nlam("n", nat(), app(app(var(1), var(0)), ih)), - ), - ), - ); - - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name("N.rec"), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 2, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![ - RecRule { ctor: Name::anon(), fields: 0, rhs: rule_zero_rhs }, - RecRule { ctor: Name::anon(), fields: 1, rhs: rule_succ_rhs }, - ], - lean_all: vec![block_id.clone()], - }, - ); - - env - .blocks - .insert(block_id.clone(), vec![block_id, zero_id, succ_id, rec_id]); - add_eq_axioms(&mut env); - env - } - - /// N.add defined via N.rec: - /// N.add := N.rec (fun m => m) (fun n ih m => (ih m).succ) - /// Tests: N.add N.zero m = m ∧ N.add (N.succ n) m = N.succ (N.add n m) - #[test] - fn good_n_rec_reduction() { - let mut env = nat_env(); - - let nat = || cnst("N", &[]); - - // N.add : N → N → N := - // N.rec.{1} (motive := fun _ => N → N) - // (fun m => m) -- zero case - // (fun n ih m => N.succ (ih m)) -- succ case - let motive = nlam("_", nat(), pi(nat(), nat())); // fun _ => N → N - - // zero case: fun m => m - let zero_case = nlam("m", nat(), var(0)); - - // succ case: fun n ih m => N.succ (ih m) - // depth 3: m=var(0), ih=var(1) : N → N, n=var(2) : N - let succ_case = nlam( - "n", - nat(), - nlam( - "ih", - pi(nat(), nat()), - nlam("m", nat(), app(cnst("N.succ", &[]), app(var(1), var(0)))), - ), - ); - - let add_val = - apps(cnst("N.rec", &[usucc(uzero())]), &[motive, zero_case, succ_case]); - let (add_id, add_c) = mk_defn( - "N.add", - 0, - vec![], - pi(nat(), pi(nat(), nat())), - add_val, - ReducibilityHints::Abbrev, - ); - env.insert(add_id, add_c); - - // Test 1: ∀ m, N.add N.zero m = m - // N.add N.zero = (N.rec ...) N.zero → reduces zero case → fun m => m - // So N.add N.zero m = m - let ty1 = npi( - "m", - nat(), - eq_expr( - usucc(uzero()), - nat(), - app(app(cnst("N.add", &[]), cnst("N.zero", &[])), var(0)), - var(0), - ), - ); - let val1 = nlam("m", nat(), eq_refl_expr(usucc(uzero()), nat(), var(0))); - let (id1, c1) = mk_thm("nAddZero", 0, vec![], ty1, val1); - env.insert(id1.clone(), c1); - check_accepts(&mut env, &id1); - } - - /// N.add N.succ reduction: N.add (N.succ n) m = N.succ (N.add n m) - #[test] - fn good_n_rec_reduction_succ() { - let mut env = nat_env(); - let nat = || cnst("N", &[]); - - let motive = nlam("_", nat(), pi(nat(), nat())); - let zero_case = nlam("m", nat(), var(0)); - let succ_case = nlam( - "n", - nat(), - nlam( - "ih", - pi(nat(), nat()), - nlam("m", nat(), app(cnst("N.succ", &[]), app(var(1), var(0)))), - ), - ); - - let add_val = - apps(cnst("N.rec", &[usucc(uzero())]), &[motive, zero_case, succ_case]); - let (add_id, add_c) = mk_defn( - "N.add", - 0, - vec![], - pi(nat(), pi(nat(), nat())), - add_val, - ReducibilityHints::Abbrev, - ); - env.insert(add_id, add_c); - - // Test 2: ∀ n m, N.add (N.succ n) m = N.succ (N.add n m) - // depth 2: n=var(1), m=var(0) - let lhs = - app(app(cnst("N.add", &[]), app(cnst("N.succ", &[]), var(1))), var(0)); - let rhs = - app(cnst("N.succ", &[]), app(app(cnst("N.add", &[]), var(1)), var(0))); - let ty2 = npi( - "n", - nat(), - npi("m", nat(), eq_expr(usucc(uzero()), nat(), lhs, rhs)), - ); - let val2 = nlam( - "n", - nat(), - nlam( - "m", - nat(), - eq_refl_expr( - usucc(uzero()), - nat(), - app( - cnst("N.succ", &[]), - app(app(cnst("N.add", &[]), var(1)), var(0)), - ), - ), - ), - ); - let (id2, c2) = mk_thm("nAddSucc", 0, vec![], ty2, val2); - env.insert(id2.clone(), c2); - check_accepts(&mut env, &id2); - } - - // ========================================================================== - // RTree: reflexive inductive (Tutorial.lean 1145–1159) - // ========================================================================== - - /// Build an environment with Bool + RTree (reflexive inductive). - /// RTree : Type, RTree.leaf : RTree, RTree.node : (Bool → RTree) → RTree - fn rtree_env() -> KEnv { - let mut env = bool_env(); - - let n = "RTree"; - let block_id = mk_id(n); - let leaf_id = mk_id("RTree.leaf"); - let node_id = mk_id("RTree.node"); - let rec_id = mk_id("RTree.rec"); - - let rt = || cnst(n, &[]); - - // RTree : Type - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![leaf_id.clone(), node_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // RTree.leaf : RTree - env.insert( - leaf_id.clone(), - KConst::Ctor { - name: mk_name("RTree.leaf"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 0, - params: 0, - fields: 0, - ty: rt(), - }, - ); - - // RTree.node : (Bool → RTree) → RTree - env.insert( - node_id.clone(), - KConst::Ctor { - name: mk_name("RTree.node"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: block_id.clone(), - cidx: 1, - params: 0, - fields: 1, - ty: npi("children", pi(cnst("Bool", &[]), rt()), rt()), - }, - ); - - // RTree.rec : ∀ {motive : RTree → Sort u} - // (leaf : motive RTree.leaf) - // (node : ∀ (children : Bool → RTree), (∀ b, motive (children b)) → motive (RTree.node children)) - // (t : RTree), motive t - let motive_ty = pi(rt(), sort(param(0))); - // depth 1 (inside motive): motive = var(0) - let minor_leaf = app(var(0), cnst("RTree.leaf", &[])); - // minor_node at depth 2 (inside motive, leaf): motive = var(1) - // ∀ (children : Bool → RTree), (∀ b, motive (children b)) → motive (RTree.node children) - // depth 3 (inside children): children = var(0), motive = var(2) - // ih: ∀ (b : Bool), motive (children b) — depth 4: b=var(0), children=var(1), motive=var(3) - let ih_ty = npi("b", cnst("Bool", &[]), app(var(3), app(var(1), var(0)))); - // depth 4 (inside ih): ih=var(0), children=var(1), motive=var(3) - let node_result = app(var(3), app(cnst("RTree.node", &[]), var(1))); - let minor_node = - npi("children", pi(cnst("Bool", &[]), rt()), pi(ih_ty, node_result)); - let rec_ty = ipi( - "motive", - motive_ty.clone(), - npi( - "leaf", - minor_leaf.clone(), - npi("node", minor_node.clone(), npi("t", rt(), app(var(3), var(0)))), - ), - ); - - // Rule 0 (leaf, 0 fields): λ motive h_leaf h_node, h_leaf - let rule_leaf_rhs = nlam( - "motive", - motive_ty.clone(), - nlam( - "h_leaf", - minor_leaf.clone(), - nlam("h_node", minor_node.clone(), var(1)), - ), - ); - - // Rule 1 (node, 1 field): λ motive h_leaf h_node children, - // h_node children (fun b => RTree.rec motive h_leaf h_node (children b)) - // depth 4: children=var(0), h_node=var(1), h_leaf=var(2), motive=var(3) - let rec_call_ih = nlam( - "b", - cnst("Bool", &[]), - // depth 5: b=var(0), children=var(1), h_node=var(2), h_leaf=var(3), motive=var(4) - apps( - cnst("RTree.rec", &[param(0)]), - &[var(4), var(3), var(2), app(var(1), var(0))], - ), - ); - let rule_node_rhs = nlam( - "motive", - motive_ty, - nlam( - "h_leaf", - minor_leaf, - nlam( - "h_node", - minor_node, - nlam( - "children", - pi(cnst("Bool", &[]), rt()), - app(app(var(1), var(0)), rec_call_ih), - ), - ), - ), - ); - - env.insert( - rec_id.clone(), - KConst::Recr { - name: mk_name("RTree.rec"), - level_params: vec![mk_name("u")], - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 2, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![ - RecRule { ctor: Name::anon(), fields: 0, rhs: rule_leaf_rhs }, - RecRule { ctor: Name::anon(), fields: 1, rhs: rule_node_rhs }, - ], - lean_all: vec![block_id.clone()], - }, - ); - - env - .blocks - .insert(block_id.clone(), vec![block_id, leaf_id, node_id, rec_id]); - env - } - - /// RTree.left : RTree → RTree := - /// RTree.rec .leaf (fun children _ih => children true) t - /// rtreeRecReduction : ∀ (t1 t2 : RTree), (RTree.node (Bool.rec t2 t1)).left = t1 - #[test] - fn good_rtree_rec_reduction() { - let mut env = rtree_env(); - - let rt = || cnst("RTree", &[]); - - // RTree.left : RTree → RTree := - // fun t => RTree.rec (motive := fun _ => RTree) .leaf - // (fun children _ih => children true) t - let motive = nlam("_", rt(), rt()); - let leaf_case = cnst("RTree.leaf", &[]); - // node case: fun children ih => children Bool.true - // depth 2: ih=var(0), children=var(1) - let ih_ty = npi("b", cnst("Bool", &[]), rt()); // simplified: ∀ b, RTree - let node_case = nlam( - "children", - pi(cnst("Bool", &[]), rt()), - nlam("_ih", ih_ty, app(var(1), cnst("Bool.true", &[]))), - ); - - let left_val = nlam( - "t", - rt(), - apps( - cnst("RTree.rec", &[usucc(uzero())]), - &[motive, leaf_case, node_case, var(0)], - ), - ); - let (left_id, left_c) = mk_defn( - "RTree.left", - 0, - vec![], - pi(rt(), rt()), - left_val, - ReducibilityHints::Abbrev, - ); - env.insert(left_id, left_c); - - // Test: ∀ (t1 t2 : RTree), (RTree.node (Bool.rec t2 t1)).left = t1 - // Bool.rec.{1} (fun _ => RTree) t2 t1 : Bool → RTree - // Then RTree.node applied to this, then .left - // depth 2: t1=var(1), t2=var(0)... wait, t1 first then t2: - // ∀ (t1 : RTree) (t2 : RTree), ... - // depth 2: t2=var(0), t1=var(1) - let bool_rec_app = apps( - cnst("Bool.rec", &[usucc(uzero())]), - &[ - nlam("_", cnst("Bool", &[]), rt()), // motive: fun _ => RTree - var(0), // false case = t2 - var(1), // true case = t1 - ], - ); - // RTree.node (Bool.rec ...) : RTree - let node_app = app(cnst("RTree.node", &[]), bool_rec_app); - // RTree.left (RTree.node ...) should reduce to t1 - let lhs = app(cnst("RTree.left", &[]), node_app); - let ty = npi( - "t1", - rt(), - npi("t2", rt(), eq_expr(usucc(uzero()), rt(), lhs, var(1))), - ); - let val = nlam( - "t1", - rt(), - nlam("t2", rt(), eq_refl_expr(usucc(uzero()), rt(), var(1))), - ); - - let (id, c) = mk_thm("rtreeRecReduction", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - // ========================================================================== - // Nat literal tests (Tutorial.lean 930–951) - // ========================================================================== - - /// aNatLit : Nat := .lit (.natVal 0) - /// Type checking a Nat literal — needs Primitives wired up. - #[test] - fn good_nat_lit() { - let mut env = nat_env(); - let nat = || cnst("N", &[]); - - // We need to use the actual Nat type for nat literals. - // The zero kernel's infer_nat_type uses prims.nat to construct the type. - // We use N as our Nat, so we need prims.nat = mk_id("N"). - // aNatLit : N := NatVal(0) - use bignat::Nat; - use ix_common::address::Address; - let nat_0 = ME::nat(Nat::from(0u64), Address::hash(b"natval_0")); - let (id, c) = - mk_defn("aNatLit", 0, vec![], nat(), nat_0, ReducibilityHints::Opaque); - env.insert(id.clone(), c); - let mut prims = test_prims(&env); - prims.nat = mk_id("N"); - prims.nat_zero = mk_id("N.zero"); - prims.nat_succ = mk_id("N.succ"); - check_accepts_with_prims(&mut env, &id, prims); - } - - /// natLitEq : Eq N 3 (N.succ (N.succ (N.succ N.zero))) := Eq.refl 3 - /// Nat literal 3 must reduce to succ(succ(succ(zero))). - #[test] - fn good_nat_lit_eq() { - let mut env = nat_env(); - let nat = || cnst("N", &[]); - - use bignat::Nat; - use ix_common::address::Address; - - let nat_3 = ME::nat(Nat::from(3u64), Address::hash(b"natval_3")); - let succ_succ_succ_zero = app( - cnst("N.succ", &[]), - app(cnst("N.succ", &[]), app(cnst("N.succ", &[]), cnst("N.zero", &[]))), - ); - - // Eq.{1} N 3 (succ (succ (succ zero))) - let ty = eq_expr(usucc(uzero()), nat(), nat_3.clone(), succ_succ_succ_zero); - // Eq.refl.{1} N 3 - let val = eq_refl_expr(usucc(uzero()), nat(), nat_3); - - let (id, c) = mk_thm("natLitEq", 0, vec![], ty, val); - env.insert(id.clone(), c); - let mut prims = test_prims(&env); - prims.nat = mk_id("N"); - prims.nat_zero = mk_id("N.zero"); - prims.nat_succ = mk_id("N.succ"); - check_accepts_with_prims(&mut env, &id, prims); - } - - // ========================================================================== - // Prod + projection reduction (Tutorial.lean 701–705, 902–903) - // ========================================================================== - - /// Build Prod.{u,v} : Type u → Type v → Type (max u v) environment. - fn prod_env() -> KEnv { - let mut env = KEnv::::new(); - add_eq_axioms(&mut env); - - // Also need Bool for projection tests - let bool_id = mk_id("Bool"); - let false_id = mk_id("Bool.false"); - let true_id = mk_id("Bool.true"); - env.insert( - bool_id.clone(), - KConst::Indc { - name: mk_name("Bool"), - level_params: vec![], - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: bool_id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![false_id.clone(), true_id.clone()], - lean_all: vec![bool_id.clone()], - }, - ); - env.insert( - false_id.clone(), - KConst::Ctor { - name: mk_name("Bool.false"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: bool_id.clone(), - cidx: 0, - params: 0, - fields: 0, - ty: cnst("Bool", &[]), - }, - ); - env.insert( - true_id.clone(), - KConst::Ctor { - name: mk_name("Bool.true"), - level_params: vec![], - is_unsafe: false, - lvls: 0, - induct: bool_id.clone(), - cidx: 1, - params: 0, - fields: 0, - ty: cnst("Bool", &[]), - }, - ); - env.blocks.insert(bool_id, vec![mk_id("Bool"), false_id, true_id]); - - let n = "Prod"; - let block_id = mk_id(n); - let mk_ctor_id = mk_id("Prod.mk"); - let rec_ctor_id = mk_id("Prod.rec"); - - // Prod.{u,v} : Type u → Type v → Type (max u v) - // param(0) = u, param(1) = v - let prod_ty = npi( - "α", - sort(usucc(param(0))), - npi("β", sort(usucc(param(1))), sort(usucc(umax(param(0), param(1))))), - ); - env.insert( - block_id.clone(), - KConst::Indc { - name: mk_name(n), - level_params: vec![mk_name("u"), mk_name("v")], - lvls: 2, - params: 2, - indices: 0, - is_unsafe: false, - block: block_id.clone(), - member_idx: 0, - ty: prod_ty, - ctors: vec![mk_ctor_id.clone()], - lean_all: vec![block_id.clone()], - }, - ); - - // Prod.mk.{u,v} : {α : Type u} → {β : Type v} → α → β → Prod α β - // depth 2 (inside α, β implicit): α=var(1), β=var(0) - // depth 4 (inside fst, snd): fst=var(1), snd=var(0), β=var(2), α=var(3) - let mk_ty = ipi( - "α", - sort(usucc(param(0))), - ipi( - "β", - sort(usucc(param(1))), - npi( - "fst", - var(1), - npi( - "snd", - var(1), - app(app(cnst(n, &[param(0), param(1)]), var(3)), var(2)), - ), - ), - ), - ); - env.insert( - mk_ctor_id.clone(), - KConst::Ctor { - name: mk_name("Prod.mk"), - level_params: vec![mk_name("u"), mk_name("v")], - is_unsafe: false, - lvls: 2, - induct: block_id.clone(), - cidx: 0, - params: 2, - fields: 2, - ty: mk_ty, - }, - ); - - // Prod.rec.{u,v,w} with k=true (structure) - // ∀ {α : Type u} {β : Type v} {motive : Prod α β → Sort w} - // (mk : ∀ (fst : α) (snd : β), motive (Prod.mk fst snd)) - // (t : Prod α β), motive t - // - // d2 (inside α, β): α=var(1), β=var(0) - let prod_ab_d2 = app(app(cnst(n, &[param(0), param(1)]), var(1)), var(0)); - let motive_ty = pi(prod_ab_d2, sort(param(2))); - // d3 (inside motive): motive=var(0), β=var(1), α=var(2) - // minor mk: ∀ (fst : α) (snd : β), motive (Prod.mk fst snd) - // d5 (inside fst, snd): snd=var(0), fst=var(1), motive=var(2), β=var(3), α=var(4) - let mk_app = apps( - cnst("Prod.mk", &[param(0), param(1)]), - &[var(4), var(3), var(1), var(0)], - ); - let minor_mk = npi("fst", var(2), npi("snd", var(2), app(var(2), mk_app))); - // d4 (inside mk): mk=var(0), motive=var(1), β=var(2), α=var(3) - let prod_ab_d4 = app(app(cnst(n, &[param(0), param(1)]), var(3)), var(2)); - // d5 (inside t): t=var(0), mk=var(1), motive=var(2), β=var(3), α=var(4) - let rec_ty = ipi( - "α", - sort(usucc(param(0))), - ipi( - "β", - sort(usucc(param(1))), - ipi( - "motive", - motive_ty, - npi( - "mk", - minor_mk.clone(), - npi("t", prod_ab_d4, app(var(2), var(0))), - ), - ), - ), - ); - - // Rule: Prod.mk case (2 fields) - // rhs: λ {α} {β} (motive) (mk_case) (fst) (snd), mk_case fst snd - // depth 6: snd=var(0), fst=var(1), mk_case=var(2), motive=var(3), β=var(4), α=var(5) - let prod_ab_r = app(app(cnst(n, &[param(0), param(1)]), var(1)), var(0)); - let motive_ty_r = pi(prod_ab_r, sort(param(2))); - let mk_app_r = apps( - cnst("Prod.mk", &[param(0), param(1)]), - &[var(4), var(3), var(1), var(0)], - ); - let minor_mk_r = - npi("fst", var(2), npi("snd", var(2), app(var(2), mk_app_r))); - // rhs: λ {α} {β} motive mk_case fst snd, mk_case fst snd - // d4 (after α,β,motive,mk_case): mk_case=0, motive=1, β=2, α=3 - // fst domain: α = var(3) - // d5 (after fst): fst=0, mk_case=1, motive=2, β=3, α=4 - // snd domain: β = var(3) - // d6 (body): snd=0, fst=1, mk_case=2, motive=3, β=4, α=5 - // mk_case fst snd = app(app(var(2), var(1)), var(0)) - let rule_rhs = ME::lam( - mk_name("α"), - ix_common::env::BinderInfo::Implicit, - sort(usucc(param(0))), - ME::lam( - mk_name("β"), - ix_common::env::BinderInfo::Implicit, - sort(usucc(param(1))), - nlam( - "motive", - motive_ty_r, - nlam( - "mk_case", - minor_mk_r, - nlam( - "fst", - var(3), - nlam("snd", var(3), app(app(var(2), var(1)), var(0))), - ), - ), - ), - ), - ); - - env.insert( - rec_ctor_id.clone(), - KConst::Recr { - name: mk_name("Prod.rec"), - level_params: vec![mk_name("u"), mk_name("v"), mk_name("w")], - k: true, - is_unsafe: false, - lvls: 3, - params: 2, - indices: 0, - motives: 1, - minors: 1, - block: block_id.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![RecRule { ctor: Name::anon(), fields: 2, rhs: rule_rhs }], - lean_all: vec![block_id.clone()], - }, - ); - - env.blocks.insert(block_id, vec![mk_id("Prod"), mk_ctor_id, rec_ctor_id]); - env - } - - /// projRed : (Prod.mk true false).2 = false - /// Projection .proj Prod 1 (Prod.mk true false) reduces to false. - #[test] - fn good_proj_red() { - let mut env = prod_env(); - - // Prod.mk.{0,0} Bool Bool true false : Prod Bool Bool - let pair = apps( - cnst("Prod.mk", &[uzero(), uzero()]), - &[ - cnst("Bool", &[]), - cnst("Bool", &[]), - cnst("Bool.true", &[]), - cnst("Bool.false", &[]), - ], - ); - // .proj Prod 1 pair = false - let proj = ME::prj(mk_id("Prod"), 1, pair); - // Eq.{1} Bool (.proj Prod 1 (mk true false)) false - let ty = - eq_expr(usucc(uzero()), cnst("Bool", &[]), proj, cnst("Bool.false", &[])); - let val = - eq_refl_expr(usucc(uzero()), cnst("Bool", &[]), cnst("Bool.false", &[])); - - let (id, c) = mk_thm("projRed", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// structEta : ∀ (x : Prod Bool Bool), x = Prod.mk (.proj Prod 0 x) (.proj Prod 1 x) - /// Structure eta: a value of a structure type equals the constructor applied to its projections. - #[test] - fn good_struct_eta() { - let mut env = prod_env(); - - let prod_bb = app( - app(cnst("Prod", &[uzero(), uzero()]), cnst("Bool", &[])), - cnst("Bool", &[]), - ); - - // depth 1: x=var(0) : Prod Bool Bool - let proj0 = ME::prj(mk_id("Prod"), 0, var(0)); - let proj1 = ME::prj(mk_id("Prod"), 1, var(0)); - let reconstructed = apps( - cnst("Prod.mk", &[uzero(), uzero()]), - &[cnst("Bool", &[]), cnst("Bool", &[]), proj0, proj1], - ); - - // ∀ (x : Prod Bool Bool), Eq.{1} (Prod Bool Bool) x (Prod.mk (x.1) (x.2)) - let ty = npi( - "x", - prod_bb.clone(), - eq_expr(usucc(uzero()), prod_bb.clone(), var(0), reconstructed), - ); - - // fun x => Eq.refl.{1} (Prod Bool Bool) x - let val = - nlam("x", prod_bb.clone(), eq_refl_expr(usucc(uzero()), prod_bb, var(0))); - - let (id, c) = mk_thm("structEta", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - /// prodRecEqns: Prod.rec f (Prod.mk true false) = f true false = true - #[test] - fn good_prod_rec_reduction() { - let mut env = prod_env(); - let u1 = usucc(uzero()); - - let prod_bb = app( - app(cnst("Prod", &[uzero(), uzero()]), cnst("Bool", &[])), - cnst("Bool", &[]), - ); - let motive = nlam("_", prod_bb, cnst("Bool", &[])); - let f_case = - nlam("a", cnst("Bool", &[]), nlam("b", cnst("Bool", &[]), var(1))); - let pair = apps( - cnst("Prod.mk", &[uzero(), uzero()]), - &[ - cnst("Bool", &[]), - cnst("Bool", &[]), - cnst("Bool.true", &[]), - cnst("Bool.false", &[]), - ], - ); - let rec_app = apps( - cnst("Prod.rec", &[uzero(), uzero(), u1.clone()]), - &[cnst("Bool", &[]), cnst("Bool", &[]), motive, f_case, pair], - ); - let ty = - eq_expr(u1.clone(), cnst("Bool", &[]), rec_app, cnst("Bool.true", &[])); - let val = eq_refl_expr(u1, cnst("Bool", &[]), cnst("Bool.true", &[])); - - let (id, c) = mk_thm("prodRecEqns", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_accepts(&mut env, &id); - } - - // ========================================================================== - // Quotient tests (Tutorial.lean 1185–1224) - // ========================================================================== - - /// Add Eq as a full inductive (not just axioms) — needed for Quot.lift validation. - fn add_eq_inductive(env: &mut KEnv) { - let eq_id = mk_id("Eq"); - let refl_id = mk_id("Eq.refl"); - let eq_rec_id = mk_id("Eq.rec"); - - let eq_ty = - ipi("α", sort(param(0)), npi("a", var(0), npi("b", var(1), sort0()))); - env.insert( - eq_id.clone(), - KConst::Indc { - name: mk_name("Eq"), - level_params: vec![mk_name("u")], - lvls: 1, - params: 2, - indices: 1, - is_unsafe: false, - block: eq_id.clone(), - member_idx: 0, - ty: eq_ty, - ctors: vec![refl_id.clone()], - lean_all: vec![eq_id.clone()], - }, - ); - - let eq_refl_ty = ipi( - "α", - sort(param(0)), - npi( - "a", - var(0), - apps(cnst("Eq", &[param(0)]), &[var(1), var(0), var(0)]), - ), - ); - env.insert( - refl_id.clone(), - KConst::Ctor { - name: mk_name("Eq.refl"), - level_params: vec![mk_name("u")], - is_unsafe: false, - lvls: 1, - induct: eq_id.clone(), - cidx: 0, - params: 2, - fields: 0, - ty: eq_refl_ty, - }, - ); - - // Minimal Eq.rec (k=true) - let eq_a_aprime = apps(cnst("Eq", &[param(1)]), &[var(2), var(1), var(0)]); - let motive_ty = npi("a'", var(1), pi(eq_a_aprime, sort(param(0)))); - let eq_refl_a = apps(cnst("Eq.refl", &[param(1)]), &[var(2), var(1)]); - let minor_refl = app(app(var(0), var(1)), eq_refl_a); - let eq_a_aprime_d5 = - apps(cnst("Eq", &[param(1)]), &[var(4), var(3), var(0)]); - let result = app(app(var(3), var(1)), var(0)); - let eq_rec_ty = ipi( - "α", - sort(param(1)), - ipi( - "a", - var(0), - ipi( - "motive", - motive_ty, - npi( - "refl", - minor_refl, - ipi("a'", var(3), npi("t", eq_a_aprime_d5, result)), - ), - ), - ), - ); - env.insert( - eq_rec_id.clone(), - KConst::Recr { - name: mk_name("Eq.rec"), - level_params: vec![mk_name("u"), mk_name("u_1")], - k: true, - is_unsafe: false, - lvls: 2, - params: 2, - indices: 1, - motives: 1, - minors: 1, - block: eq_id.clone(), - member_idx: 0, - ty: eq_rec_ty, - rules: vec![], - lean_all: vec![eq_id.clone()], - }, - ); - env.blocks.insert(eq_id, vec![mk_id("Eq"), refl_id, eq_rec_id]); - } - - /// Build Quot environment: Quot, Quot.mk, Quot.lift, Quot.ind as KConst::Quot. - /// Also includes Eq as full inductive (needed for Quot.lift validation). - fn quot_env() -> KEnv { - let mut env = KEnv::::new(); - add_eq_inductive(&mut env); - - use ix_common::env::QuotKind; - - // Quot.{u} : {α : Sort u} → (α → α → Prop) → Sort u - // depth 1 (inside α): α = var(0) - let quot_ty = ipi( - "α", - sort(param(0)), - pi(pi(var(0), pi(var(1), sort0())), sort(param(0))), - ); - env.insert( - mk_id("Quot"), - KConst::Quot { - name: mk_name("Quot"), - level_params: vec![mk_name("u")], - kind: QuotKind::Type, - lvls: 1, - ty: quot_ty, - }, - ); - - // Quot.mk.{u} : {α : Sort u} → (r : α → α → Prop) → α → Quot r - // depth 2 (inside α, r): α=var(1), r=var(0) - // depth 3 (inside a): a=var(0), r=var(1), α=var(2) - // Quot α r = app(app(Quot.{u}, var(2)), var(1)) - let quot_mk_ty = ipi( - "α", - sort(param(0)), - npi( - "r", - pi(var(0), pi(var(1), sort0())), - npi("a", var(1), app(app(cnst("Quot", &[param(0)]), var(2)), var(1))), - ), - ); - env.insert( - mk_id("Quot.mk"), - KConst::Quot { - name: mk_name("Quot.mk"), - level_params: vec![mk_name("u")], - kind: QuotKind::Ctor, - lvls: 1, - ty: quot_mk_ty, - }, - ); - - // Quot.lift.{u,v} : - // {α : Sort u} → {r : α → α → Prop} → {β : Sort v} → - // (f : α → β) → (h : ∀ a b, r a b → f a = f b) → Quot r → β - // - // d0: α - // d1: r. α=var(0) - // d2: β. r=var(0), α=var(1) - // d3: f. β=var(0), r=var(1), α=var(2). f : α → β = pi(var(2), var(1)) - // Inside f's pi: var(0)=arg, var(1)=β, var(2)=r, var(3)=α. body=var(1)=β ✓ - // d4: h. f=var(0), β=var(1), r=var(2), α=var(3) - // h : ∀ (a b : α), r a b → Eq.{v} β (f a) (f b) - // d5: a. a=var(0), f=var(1), β=var(2), r=var(3), α=var(4) - // d6: b. b=var(0), a=var(1), f=var(2), β=var(3), r=var(4), α=var(5) - // r a b = app(app(var(4), var(1)), var(0)) - // d7: (inside r a b →) - // f a = app(var(3), var(2)), f b = app(var(3), var(1)) - // Eq.{v} β (f a) (f b) = eq_expr(param(1), var(4), app(var(3), var(2)), app(var(3), var(1))) - // h_ty = npi("a", var(3), npi("b", var(4), - // pi(app(app(var(4), var(1)), var(0)), - // eq_expr(param(1), var(4), app(var(3), var(2)), app(var(3), var(1)))))) - // d5: (inside h). h=var(0), f=var(1), β=var(2), r=var(3), α=var(4) - // Quot r → β: pi(Quot α r, β) - // Quot α r = app(app(Quot.{u}, var(4)), var(3)) - // d6: (inside pi) β = var(3) - let f_ty = pi(var(2), var(1)); // α → β at d3 - let h_ty = npi( - "a", - var(3), - npi( - "b", - var(4), - pi( - app(app(var(4), var(1)), var(0)), - eq_expr(param(1), var(4), app(var(3), var(2)), app(var(3), var(1))), - ), - ), - ); - let _quot_r_3 = (); // unused, remove old - let quot_lift_ty = ipi( - "α", - sort(param(0)), - ipi( - "r", - pi(var(0), pi(var(1), sort0())), - ipi( - "β", - sort(param(1)), - npi( - "f", - f_ty, - npi( - "h", - h_ty, - pi(app(app(cnst("Quot", &[param(0)]), var(4)), var(3)), var(3)), - ), - ), - ), - ), - ); - env.insert( - mk_id("Quot.lift"), - KConst::Quot { - name: mk_name("Quot.lift"), - level_params: vec![mk_name("u"), mk_name("v")], - kind: QuotKind::Lift, - lvls: 2, - ty: quot_lift_ty, - }, - ); - - // Quot.ind.{u} : - // {α : Sort u} → {r : α → α → Prop} → {β : Quot r → Prop} → - // (mk : ∀ a, β (Quot.mk r a)) → (q : Quot r) → β q - // - // d0: α - // d1: r. α=var(0) - // d2: β. r=var(0), α=var(1). β : Quot α r → Prop - // Quot α r at d2 = app(app(Quot.{u}, var(1)), var(0)) - let quot_r_d2 = app(app(cnst("Quot", &[param(0)]), var(1)), var(0)); - let beta_ty = pi(quot_r_d2, sort0()); - // d3: mk. β=var(0), r=var(1), α=var(2) - // mk : ∀ (a : α), β (Quot.mk r a) - // d4: a. a=var(0), β=var(1), r=var(2), α=var(3) - // Quot.mk.{u} α r a = apps(Quot.mk, [var(3), var(2), var(0)]) - let quot_mk_r_a = - apps(cnst("Quot.mk", &[param(0)]), &[var(3), var(2), var(0)]); - let mk_minor = npi("a", var(2), app(var(1), quot_mk_r_a)); - // d4: q. mk=var(0), β=var(1), r=var(2), α=var(3) - // Quot α r at d4 = app(app(Quot.{u}, var(3)), var(2)) - let quot_r_d4 = app(app(cnst("Quot", &[param(0)]), var(3)), var(2)); - // d5: (inside q). q=var(0), mk=var(1), β=var(2), r=var(3), α=var(4) - let result = app(var(2), var(0)); // β q - let quot_ind_ty = ipi( - "α", - sort(param(0)), - ipi( - "r", - pi(var(0), pi(var(1), sort0())), - ipi("β", beta_ty, npi("mk", mk_minor, npi("q", quot_r_d4, result))), - ), - ); - env.insert( - mk_id("Quot.ind"), - KConst::Quot { - name: mk_name("Quot.ind"), - level_params: vec![mk_name("u")], - kind: QuotKind::Ind, - lvls: 1, - ty: quot_ind_ty, - }, - ); - - env - } - - fn quot_prims(env: &KEnv) -> crate::primitive::Primitives { - let mut prims = test_prims(env); - prims.quot_type = mk_id("Quot"); - prims.quot_ctor = mk_id("Quot.mk"); - prims.quot_lift = mk_id("Quot.lift"); - prims.quot_ind = mk_id("Quot.ind"); - prims.eq = mk_id("Eq"); - prims.eq_refl = mk_id("Eq.refl"); - prims - } - - /// quotMkType: type assertion for Quot.mk - #[test] - fn good_quot_mk_type() { - let mut env = quot_env(); - let prims = quot_prims(&env); - check_accepts_with_prims(&mut env, &mk_id("Quot.mk"), prims); - } - - /// quotLiftType: type assertion for Quot.lift - #[test] - fn good_quot_lift_type() { - let mut env = quot_env(); - let prims = quot_prims(&env); - check_accepts_with_prims(&mut env, &mk_id("Quot.lift"), prims); - } - - /// quotIndType: type assertion for Quot.ind - #[test] - fn good_quot_ind_type() { - let mut env = quot_env(); - let prims = quot_prims(&env); - check_accepts_with_prims(&mut env, &mk_id("Quot.ind"), prims); - } - - /// quotLiftReduction: Quot.lift f h (Quot.mk r a) = f a - #[test] - fn good_quot_lift_reduction() { - let mut env = quot_env(); - let prims = quot_prims(&env); - - // We need a concrete type for testing. Use Bool (as axiom). - let (bool_id, bool_c) = mk_axiom("Bool", 0, vec![], sort1()); - env.insert(bool_id, bool_c); - let (true_id, true_c) = mk_axiom("Bool.true", 0, vec![], cnst("Bool", &[])); - env.insert(true_id, true_c); - - // r : Bool → Bool → Prop (axiom) - let (r_id, r_c) = mk_axiom( - "r", - 0, - vec![], - pi(cnst("Bool", &[]), pi(cnst("Bool", &[]), sort0())), - ); - env.insert(r_id, r_c); - - // f : Bool → Bool (axiom) - let (f_id, f_c) = - mk_axiom("f", 0, vec![], pi(cnst("Bool", &[]), cnst("Bool", &[]))); - env.insert(f_id, f_c); - - // h : ∀ (a b : Bool), r a b → Eq.{1} Bool (f a) (f b) - // d0: a. a=var(0) - // d1: b. b=var(0), a=var(1) - // r a b (pi domain at d2): r=cnst, a=var(1), b=var(0) ✓ - // d2: (inside pi for r a b →). proof=var(0), b=var(1), a=var(2) - // Eq.{1} Bool (f a) (f b): f a = app(f, var(2)), f b = app(f, var(1)) - let r_ab = app(app(cnst("r", &[]), var(1)), var(0)); - let h_ty = npi( - "a", - cnst("Bool", &[]), - npi( - "b", - cnst("Bool", &[]), - pi( - r_ab, - eq_expr( - usucc(uzero()), - cnst("Bool", &[]), - app(cnst("f", &[]), var(2)), // f a — a is var(2) at depth 3 - app(cnst("f", &[]), var(1)), - ), // f b — b is var(1) at depth 3 - ), - ), - ); - let (h_id, h_c) = mk_axiom("h", 0, vec![], h_ty); - env.insert(h_id, h_c); - - // Quot.lift f h (Quot.mk r Bool.true) = f Bool.true - let quot_mk_app = apps( - cnst("Quot.mk", &[usucc(uzero())]), - &[cnst("Bool", &[]), cnst("r", &[]), cnst("Bool.true", &[])], - ); - let lift_app = apps( - cnst("Quot.lift", &[usucc(uzero()), usucc(uzero())]), - &[ - cnst("Bool", &[]), // α - cnst("r", &[]), // r - cnst("Bool", &[]), // β - cnst("f", &[]), // f - cnst("h", &[]), // h - quot_mk_app, // Quot.mk r Bool.true - ], - ); - let f_true = app(cnst("f", &[]), cnst("Bool.true", &[])); - - // Eq.{1} Bool (Quot.lift f h (Quot.mk r true)) (f true) - let ty = - eq_expr(usucc(uzero()), cnst("Bool", &[]), lift_app, f_true.clone()); - let val = eq_refl_expr(usucc(uzero()), cnst("Bool", &[]), f_true); - - let (id, c) = mk_thm("quotLiftReduction", 0, vec![], ty, val); - env.insert(id.clone(), c); - check_accepts_with_prims(&mut env, &id, prims); - } -} diff --git a/crates/kernel/src/whnf.rs b/crates/kernel/src/whnf.rs deleted file mode 100644 index a35e998af..000000000 --- a/crates/kernel/src/whnf.rs +++ /dev/null @@ -1,7439 +0,0 @@ -//! Weak head normal form reduction. -//! -//! Multi-phase: whnf_core (beta, iota, zeta) → proj → nat → quot → delta. - -use std::sync::{Arc, LazyLock}; - -use rustc_hash::FxHashSet; - -use crate::def_eq::IX_STEP_TRACE; - -use ix_common::address::Address; -use ixon::constant::DefKind; -use ixon::role_table::Role; - -/// When set, emit a `[iota stuck]` line whenever `try_iota` can't resolve -/// its major premise to a constructor. Set `IX_IOTA_STUCK=1` to activate -/// and optionally pass a substring filter (e.g. `IX_IOTA_STUCK=Poly.rec`) -/// to suppress recursor-unrelated noise. -static IX_IOTA_STUCK: crate::EnvString = - crate::EnvString::new(|| crate::env_var("IX_IOTA_STUCK").ok()); - -/// When set, log total `nat_to_constructor` calls every 100k. Lets us see -/// whether a given check is doing runaway Nat iota expansion (signalling -/// a `Nat.rec motive base step N` whose step unconditionally forces `ih` -/// \u2014 the pattern the old 2^20 threshold guarded against). -static IX_NAT_EXPAND_LOG: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_NAT_EXPAND_LOG").is_ok()); - -/// Global counter for `nat_to_constructor` calls. Read lazily via -/// `IX_NAT_EXPAND_LOG`. `fetch_add(_, Relaxed)` is a near-free no-op when -/// logging is off (the compiler lifts the load+branch out of hot paths). -static NAT_EXPAND_COUNT: std::sync::atomic::AtomicUsize = - std::sync::atomic::AtomicUsize::new(0); - -static IX_NAT_IOTA_TRACE: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_NAT_IOTA_TRACE").is_ok()); - -/// `IX_KSYNTH_LOG=1`: one `[ksynth-attempt]`/`[ksynth-reject]` line per -/// `synth_ctor_when_k` candidate that reaches / fails the def-eq verify -/// (the silent K-synthesis fallback). Cross-kernel comparand of the Lean -/// kernel's `kSynthAttempts`/`kSynthRejects` IX_TC_STATS counters. -static IX_KSYNTH_LOG: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_KSYNTH_LOG").is_ok()); - -static NAT_IOTA_TRACE_COUNT: std::sync::atomic::AtomicUsize = - std::sync::atomic::AtomicUsize::new(0); - -static IX_NAT_LINEAR_REC_TRACE: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_NAT_LINEAR_REC_TRACE").is_ok()); - -static NAT_LINEAR_REC_TRACE_COUNT: std::sync::atomic::AtomicUsize = - std::sync::atomic::AtomicUsize::new(0); - -/// When set, log every 1M whnf entries. A check using tens of millions -/// of whnf calls on a single constant is deep in pathological territory. -static IX_WHNF_COUNT_LOG: crate::EnvFlag = - crate::EnvFlag::new(|| crate::env_var("IX_WHNF_COUNT_LOG").is_ok()); - -static WHNF_COUNT: std::sync::atomic::AtomicUsize = - std::sync::atomic::AtomicUsize::new(0); - -static IX_DELTA_TRACE: crate::EnvString = - crate::EnvString::new(|| crate::env_var("IX_DELTA_TRACE").ok()); - -static IX_PROJ_TRACE: crate::EnvString = - crate::EnvString::new(|| crate::env_var("IX_PROJ_TRACE").ok()); - -static IX_NAT_TRACE: crate::EnvString = - crate::EnvString::new(|| crate::env_var("IX_NAT_TRACE").ok()); - -const PRIM_REDUCER_OPEN_ARG_REC_FUEL: u64 = 4096; - -use super::constant::KConst; -use super::error::{TcError, u64_to_usize}; -use super::expr::{ExprData, KExpr}; -use super::extra_reductions::ExtraReduction; -use super::id::KId; -use super::level::KUniv; -use super::mode::KernelMode; -use super::subst::{ - Clo, MEnv, clo_readback, clo_subst, subst, subst_no_intern, -}; -use super::tc::{ - IotaInfo, MAX_WHNF_FUEL, TypeChecker, app_head, collect_app_spine, -}; - -use bignat::Nat; - -/// Reduction policy for structural WHNF. -/// -/// `cheap_proj` and `cheap_rec` mirror Lean4Lean's `cheapProj` and `cheapRec` -/// flags (`refs/lean4lean/Lean4Lean/TypeChecker.lean:337–341`): when set, -/// projection-of-`Prj`'s value uses `whnf_core` instead of full `whnf`, and -/// the recursor's major premise reduces with the same structural variant. -/// -/// The only non-full policy currently used is `DEF_EQ_CORE`, matching -/// Lean/Lean4Lean's `whnfCore (cheapProj := true)` scaffold in def-eq. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -struct WhnfFlags { - cheap_rec: bool, - cheap_proj: bool, -} - -impl WhnfFlags { - const FULL: Self = Self { cheap_rec: false, cheap_proj: false }; - const DEF_EQ_CORE: Self = Self { cheap_rec: false, cheap_proj: true }; - - #[inline] - fn is_full(self) -> bool { - !self.cheap_rec && !self.cheap_proj - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum NatSuccMode { - Collapse, - Stuck, -} - -struct NatRecLiteralParts { - spine: Vec>, - major: Nat, - base_idx: usize, - step_idx: usize, - major_idx: usize, -} - -/// Outcome of probing Rule-K constructor synthesis. -/// -/// `DefinitiveReject` is deliberately stronger than `Inconclusive`: it is -/// returned only when a full-strength DefEq proves that the inferred major -/// type cannot be the nullary constructor's type. Subject reduction then -/// rules out exposing that constructor by WHNF, so retrying on the proof term -/// itself is both futile and potentially extremely expensive. Cheap-mode -/// false negatives remain inconclusive and retain the conservative fallback. -enum KSynthOutcome { - Synthesized(KExpr), - DefinitiveReject, - Inconclusive, -} - -/// 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 { - 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 { - return PrimFamily::BitVec; - } - if *addr == p.string_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> { - fn dump_whnf_fuel( - &self, - phase: &str, - original: &KExpr, - current: &KExpr, - ) { - if crate::env_var("IX_WHNF_FUEL_DUMP").is_err() - || !self.debug_label_matches_env() - { - return; - } - let (orig_head, orig_args) = collect_app_spine(original); - let (cur_head, cur_args) = collect_app_spine(current); - // This diagnostic is explicitly opt-in and guard failures are rare. Use - // stderr for the one-line provenance record so `ix check-rs` remains - // useful even when its embedding executable has not installed a `log` - // subscriber (the CLI currently does not). Keep the potentially huge - // full expressions on `log::info!` below. - eprintln!( - "[whnf fuel] {phase} const={} depth={} used={} original={} original_head={} original_args={} current={} current_head={} current_args={}", - self.debug_label.as_deref().unwrap_or(""), - self.depth(), - self.fuel_used(), - original.hash_key(), - orig_head, - orig_args.len(), - current.hash_key(), - cur_head, - cur_args.len() - ); - log::info!( - "[whnf fuel] {phase} const={} depth={} original_head={} original_args={} current_head={} current_args={}", - self.debug_label.as_deref().unwrap_or(""), - self.depth(), - orig_head, - orig_args.len(), - cur_head, - cur_args.len() - ); - log::info!(" original: {original}"); - log::info!(" current: {current}"); - } - - fn dump_delta_trace(&self, id: &KId, arity: usize, e: &KExpr) { - let Some(filter) = IX_DELTA_TRACE.as_ref() else { - return; - }; - if !self.debug_label_matches_env() { - return; - } - let id_s = id.to_string(); - if !filter.is_empty() && !id_s.contains(filter) { - return; - } - log::info!( - "[delta] const={} depth={} head={} args={arity} expr={}", - self.debug_label.as_deref().unwrap_or(""), - self.depth(), - id, - e - ); - } - - fn dump_proj_trace( - &self, - id: &KId, - field: u64, - wval: &KExpr, - ctor_params: Option, - result: Option<&KExpr>, - ) { - let Some(filter) = IX_PROJ_TRACE.as_ref() else { - return; - }; - if !self.debug_label_matches_env() { - return; - } - let id_s = id.to_string(); - if !filter.is_empty() && !id_s.contains(filter) { - return; - } - let (head, args) = collect_app_spine(wval); - match result { - 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(), - id, - field, - head, - args.len(), - ctor_params, - result - ), - None => log::info!( - "[proj] const={} depth={} proj={} field={} struct_head={} struct_args={} ctor_params={:?} result=", - self.debug_label.as_deref().unwrap_or(""), - self.depth(), - id, - field, - head, - args.len(), - ctor_params - ), - } - } - - fn dump_nat_trace(&self, phase: &str, e: &KExpr) { - let Some(filter) = IX_NAT_TRACE.as_ref() else { - return; - }; - if !self.debug_label_matches_env() { - return; - } - let (head, args) = collect_app_spine(e); - let head_s = head.to_string(); - if !filter.is_empty() && !head_s.contains(filter) { - return; - } - log::info!( - "[nat] const={} depth={} phase={} head={} args={} expr={}", - self.debug_label.as_deref().unwrap_or(""), - self.depth(), - phase, - head, - args.len(), - e - ); - } - - /// Full WHNF: loop of whnf_no_delta → delta (one step). - pub fn whnf(&mut self, e: &KExpr) -> Result, TcError> { - self.check_input_context()?; - self.whnf_with_nat_succ_mode(e, NatSuccMode::Collapse) - } - - fn whnf_with_nat_succ_mode( - &mut self, - e: &KExpr, - nat_succ_mode: NatSuccMode, - ) -> Result, TcError> { - 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 { - log::info!("[whnf] count={n}"); - } - } - crate::profile::bump_whnf(); - // Quick exit for non-reducing forms. - match e.data() { - ExprData::Sort(..) - | ExprData::All(..) - | ExprData::Lam(..) - | ExprData::Nat(..) - | ExprData::Str(..) => return Ok(e.clone()), - ExprData::Var(i, _, _) if !self.is_let_var(*i) => return Ok(e.clone()), - _ => {}, - } - // 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()); - } - - // Context-aware cache: closed exprs use ptr only; open exprs include - // ctx_id because some reductions consult local binder types. - let key = self.whnf_key(e); - let use_cache = nat_succ_mode == NatSuccMode::Collapse; - let transient_nat_work = self.is_transient_nat_literal_work(e)?; - if use_cache && !transient_nat_work { - if let Some(cached) = self.env.whnf_cache.get(&key) { - self.env.perf.record_whnf_hit(); - return Ok(cached.clone()); - } - // Both probes missed. - self.env.perf.record_whnf_miss(); - self.record_hot_miss("whnf", e); - } - - // Tick AFTER fast paths and cache: only consume shared fuel for actual work. - // Quick exits (Sort/All/Lam/Nat/Str) and cache hits are free. - self.tick()?; - - let mut cur = e.clone(); - let mut fuel = MAX_WHNF_FUEL; - // Cycle detection: long delta-unfolding chains in mathlib hit hundreds of - // distinct intermediates, so a Vec linear scan is O(N²). Use a hash set - // for O(1) lookup. Equality on `Addr` is a 32-byte blake3 compare, so we - // pay one hash + one cmp per iteration. - let mut seen: FxHashSet = FxHashSet::default(); - - loop { - if fuel == 0 { - self.dump_whnf_fuel("whnf", e, &cur); - return Err(TcError::MaxRecDepth); - } - fuel -= 1; - - cur = self.whnf_no_delta_impl(&cur, WhnfFlags::FULL, nat_succ_mode)?; - let cur_key = cur.hash_key(); - if !seen.insert(cur_key) { - break; - } - - // Primitive reduction, dispatched by memoized head family — the - // 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. - let family = self.head_prim_family(&cur); - - // BitVec definitions reduce through Nat comparisons. Keep this before - // delta so small definitional facts such as `BitVec.ult x 0#w` collapse - // without unfolding the full Fin-backed representation of BitVec. - if family == PrimFamily::BitVec - && let Some(reduced) = self.try_reduce_bitvec(&cur)? - { - cur = reduced; - continue; - } - - // 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 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 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 family == PrimFamily::Str - && let Some(reduced) = self.try_reduce_string(&cur)? - { - cur = reduced; - continue; - } - - // Native char values: `Char.ofNat ` with a valid codepoint is - // a WHNF value. Do not delta-unfold it into its - // `dite Nat.isValidChar …` body — that grinds through Decidable - // instance terms and `of_decide_eq_true` proofs per character. - // Consumers that need the `Char.mk` constructor form (iota majors, - // projections, def-eq lazy delta) expand it explicitly. - if self.char_lit_value(&cur).is_some() { - 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. The three go - // together as `ExtraReduction::NatOffsets`, which is off by default. - 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; - } - - break; - } - - if use_cache && !transient_nat_work { - self.env.whnf_cache.insert(key, cur.clone()); - } - Ok(cur) - } - - /// Structural WHNF: beta, iota, zeta. NO delta. FULL flags. - /// - /// This is the standard structural normalizer used outside the def-eq - /// lazy-delta path. With `WhnfFlags::FULL`, recursive sub-reductions and - /// `try_iota` use full delta on majors and projected values, matching - /// pre-`WhnfFlags` behavior of `whnf_core`. - pub(super) fn whnf_core( - &mut self, - e: &KExpr, - ) -> Result, TcError> { - self.whnf_core_with_flags(e, WhnfFlags::FULL) - } - - /// Structural WHNF for def-eq's cheap projection scaffold: - /// `whnfCore (cheapProj := true)` in Lean/Lean4Lean. Projection values are - /// reduced structurally instead of through full WHNF, but recursor majors - /// still use full WHNF because def-eq does not enable `cheapRec` here. - /// - /// Increments `cheap_recursion_depth` for the duration of the call so - /// `is_def_eq` can detect it is running inside a cheap reduction and - /// keep cheap-mode false negatives out of the full def-eq cache. - pub(super) fn whnf_core_for_def_eq( - &mut self, - e: &KExpr, - ) -> Result, TcError> { - self.cheap_recursion_depth += 1; - let result = self.whnf_core_with_flags(e, WhnfFlags::DEF_EQ_CORE); - self.cheap_recursion_depth -= 1; - result - } - - /// Internal flags-threaded core: callers go through [`whnf_core`] or - /// [`whnf_core_for_def_eq`]. Recursive sub-reductions and `try_iota` - /// propagate the same flags so a def-eq structural pass does not - /// accidentally unfold projected values. - /// - /// FULL-mode results are cached in [`KEnv::whnf_core_cache`], mirroring - /// lean4lean's `whnfCoreCache` (TypeChecker.lean:19) and lean4 C++'s - /// `m_whnf_core`. Cheap-mode results are NOT cached — projection values - /// reduce structurally instead of through full WHNF, so cheap output is - /// not safe to share with full callers. - fn whnf_core_with_flags( - &mut self, - e: &KExpr, - flags: WhnfFlags, - ) -> Result, TcError> { - // Fast pre-cache: leaves that whnf_core never reduces. Returning - // `e.clone()` directly skips both the `whnf_key` build (a `ctx_addr` - // probe + hash compose) and the `whnf_core_cache` probe/insert, and - // — more importantly — keeps the cache from filling with trivial - // `e → e` entries that dominate cache size on big mathlib blocks. - // - // `Const` is in the leaf set here (unlike `whnf`/`whnf_no_delta`) - // because `whnf_core` does NOT delta-unfold. `Var` is a leaf only - // when there are no active let-bindings; otherwise it might - // zeta-reduce against a let-bound value via `lookup_let_val`. - match e.data() { - ExprData::Sort(..) - | ExprData::All(..) - | ExprData::Lam(..) - | ExprData::Nat(..) - | ExprData::Str(..) - | ExprData::Const(..) => return Ok(e.clone()), - ExprData::Var(i, _, _) if !self.is_let_var(*i) => return Ok(e.clone()), - _ => {}, - } - - let key = self.whnf_key(e); - let transient_nat_work = self.is_transient_nat_literal_work(e)?; - if flags.is_full() { - if !transient_nat_work - && let Some(cached) = self.env.whnf_core_cache.get(&key) - { - self.env.perf.record_whnf_core_hit(); - return Ok(cached.clone()); - } - self.env.perf.record_whnf_core_miss(); - self.record_hot_miss("whnf-core", e); - let result = self.whnf_core_with_flags_uncached(e, flags)?; - if !transient_nat_work { - self.env.whnf_core_cache.insert(key, result.clone()); - } - Ok(result) - } else { - // Cheap mode: consult/populate its own cache. Inside the def-eq lazy - // delta loop the same operand reduces through whnf_core repeatedly - // (once per loop iteration, also re-entered through whnf_no_delta_impl - // → whnf_core_with_flags), so caching here cuts O(N²) iteration cost - // back to O(N). Soundness mirrors `whnf_no_delta_cheap_cache`: - // cheap-mode results are never shared with full callers. - if !transient_nat_work - && let Some(cached) = self.env.whnf_core_cheap_cache.get(&key) - { - self.env.perf.record_whnf_core_hit(); - return Ok(cached.clone()); - } - self.env.perf.record_whnf_core_miss(); - self.record_hot_miss("whnf-core-cheap", e); - let result = self.whnf_core_with_flags_uncached(e, flags)?; - if !transient_nat_work { - self.env.whnf_core_cheap_cache.insert(key, result.clone()); - } - Ok(result) - } - } - - /// Inner loop for [`whnf_core_with_flags`]. Does not consult or update - /// `whnf_core_cache`; the caller wraps it for FULL mode. - fn whnf_core_with_flags_uncached( - &mut self, - e: &KExpr, - flags: WhnfFlags, - ) -> Result, TcError> { - let mut cur = e.clone(); - - loop { - // Each trip either returns or performs a structural beta/zeta/iota - // reduction before continuing. Charge that productive work to the - // per-constant budget instead of imposing an unrelated 10k per-call - // ceiling. The closure machine below follows the same policy. - self.tick()?; - - match cur.data() { - // Legacy let-bound variable zeta-reduction: substitute the - // let-bound value. Still active for inductive validation paths - // and tests that push values via `push_let` rather than opening - // let binders into LDecl fvars. - ExprData::Var(i, _, _) => { - if let Some(val) = self.lookup_let_val(*i) { - cur = val; - continue; - } - return Ok(cur); - }, - // Let-bound fvar zeta-reduction: substitute the let-bound value. - // Mirrors lean4lean's `whnfFVar` branch - // (refs/lean4lean/Lean4Lean/TypeChecker.lean:233). - ExprData::FVar(id, _, _) => { - if let Some(super::lctx::LocalDecl::LDecl { val, .. }) = - self.lctx.find(*id) - { - cur = val.clone(); - continue; - } - return Ok(cur); - }, - ExprData::Sort(..) - | ExprData::All(..) - | ExprData::Lam(..) - | ExprData::Nat(..) - | ExprData::Str(..) - | ExprData::Const(..) => return Ok(cur), - - // Projection reduction. Matches Lean4Lean's `reduceProj` - // (`refs/lean4lean/Lean4Lean/TypeChecker.lean:284–292`): - // let mut c ← (if cheapProj then whnfCore struct cheapRec cheapProj - // else whnf struct) - // - // FULL flags use full `whnf` on the struct value so delta unfolding - // can expose a constructor. CHEAP flags stay structural — the - // projection stays stuck if the struct value doesn't already reduce - // structurally to a ctor application. The caller is responsible for - // handling stuck projections (def-eq compares them structurally). - // - ExprData::Prj(id, field, val, _) => { - let field = *field; - let id = id.clone(); - let val = val.clone(); - let wval = if flags.cheap_proj { - self.whnf_core_with_flags(&val, flags)? - } else { - self.whnf(&val)? - }; - if let Some(result) = self.try_proj_reduce(&id, field, &wval)? { - cur = result; - continue; - } - return Ok(cur); // stuck projection - }, - - // Zeta: let elimination - ExprData::Let(_, _, val, body, _, _) => { - let val = val.clone(); - let body = body.clone(); - cur = subst(&mut self.env.intern, &body, &val, 0); - continue; - }, - - ExprData::App(..) => {}, - } - - // App: collect spine, whnf_core head, try beta/iota - let (f0, args) = super::tc::borrow_app_spine(&cur); - let f = self.whnf_core_with_flags(f0, flags)?; - - // 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 reduced = self.machine_whnf(f, &args, flags)?; - drop(args); - cur = reduced; - continue; - } - - // If head reduced, rebuild and try iota - if !f.ptr_eq(f0) { - let mut rebuilt = f; - for arg in &args { - rebuilt = self.intern(KExpr::app(rebuilt, (*arg).clone())); - } - drop(args); - if let Some(reduced) = self.try_iota_with_flags(&rebuilt, flags)? { - cur = reduced; - continue; - } - return Ok(rebuilt); - } - - // Try iota on original - drop(args); - if let Some(reduced) = self.try_iota_with_flags(&cur, flags)? { - cur = reduced; - continue; - } - - return Ok(cur); - } - } - - /// 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: every beta, zeta, and closure-iota transition charges the shared - /// per-constant recursion budget. A single closed computation may perform - /// more than `MAX_WHNF_FUEL` productive transitions (the PrimeGaps packed - /// certificate folds are a concrete example), so the closure machine must - /// not reuse the outer WHNF loop's 10k *local* iteration guard. - /// 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], - 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, contract, ty, body, _) => { - if let Some(c) = spine.pop() { - // Beta: O(1) environment push. - self.tick()?; - 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_contract( - name.clone(), - bi.clone(), - *contract, - ty2, - body2, - ))); - } - }, - - ExprData::Let(_, _, val, body, _, _) => { - // Zeta: O(1) environment push. - self.tick()?; - 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)? - { - self.tick()?; - 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, contract, result, 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_contract( - name.clone(), - bi.clone(), - *contract, - *result, - 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( - &mut self, - e: &KExpr, - ) -> Result, TcError> { - self.check_input_context()?; - self.whnf_no_delta_impl(e, WhnfFlags::FULL, NatSuccMode::Collapse) - } - - /// Def-eq no-delta WHNF. This is broader than Lean's pure `whnfCore` - /// because Ix relies on the no-delta layer for primitive/native reductions, - /// but it preserves Lean's cheap projection policy for projected values. - pub(super) fn whnf_no_delta_for_def_eq( - &mut self, - e: &KExpr, - ) -> Result, TcError> { - self.cheap_recursion_depth += 1; - let result = - self.whnf_no_delta_impl(e, WhnfFlags::DEF_EQ_CORE, NatSuccMode::Collapse); - self.cheap_recursion_depth -= 1; - result - } - - fn whnf_no_delta_impl( - &mut self, - e: &KExpr, - flags: WhnfFlags, - nat_succ_mode: NatSuccMode, - ) -> Result, TcError> { - match e.data() { - ExprData::Sort(..) - | ExprData::All(..) - | ExprData::Lam(..) - | ExprData::Nat(..) - | ExprData::Str(..) => return Ok(e.clone()), - ExprData::Var(i, _, _) if !self.is_let_var(*i) => return Ok(e.clone()), - _ => {}, - } - - let key = self.whnf_key(e); - let use_cache = nat_succ_mode == NatSuccMode::Collapse; - let transient_nat_work = self.is_transient_nat_literal_work(e)?; - if flags.is_full() { - if use_cache - && !transient_nat_work - && let Some(cached) = self.env.whnf_no_delta_cache.get(&key) - { - self.env.perf.record_whnf_no_delta_hit(); - return Ok(cached.clone()); - } - // Both probes missed. - if use_cache { - self.env.perf.record_whnf_no_delta_miss(); - self.record_hot_miss("whnf-no-delta", e); - } - } else { - // Cheap-mode (DEF_EQ_CORE): consult its own cache. Cheap output is NOT - // shared with full callers, but cheap → cheap reuse is sound and is the - // dominant pattern inside the lazy-delta loop, where the same operand - // is re-reduced after every delta_unfold_one of the *other* operand. - if use_cache - && !transient_nat_work - && let Some(cached) = self.env.whnf_no_delta_cheap_cache.get(&key) - { - self.env.perf.record_whnf_no_delta_hit(); - return Ok(cached.clone()); - } - if use_cache { - self.env.perf.record_whnf_no_delta_miss(); - self.record_hot_miss("whnf-no-delta-cheap", e); - } - } - - let mut cur = e.clone(); - let mut fuel = MAX_WHNF_FUEL; - - loop { - if fuel == 0 { - self.dump_whnf_fuel("whnf_no_delta", e, &cur); - return Err(TcError::MaxRecDepth); - } - fuel -= 1; - - cur = self.whnf_core_with_flags(&cur, flags)?; - - // Projection reduction is now handled inside `whnf_core_with_flags` - // (`whnfCore`/`reduceProj` at TypeChecker.lean:284-292, 337-341). - // `whnf_core` either returns a stuck `Prj` - // (struct value didn't reduce to a ctor) or a fully-reduced field. - // - // We only need to handle the App-of-Prj case here, since `whnf_core` - // doesn't iterate after a Prj reduces (its loop returns once the - // outermost Prj is resolved). When the outer expression is - // `App(Prj(S, i, val), args...)`, `whnf_core` reduces the App spine - // and may leave the Prj head stuck; `try_proj_app_reduce` gives it - // one more attempt with the same projection policy. - if let Some((proj_result, args)) = - self.try_proj_app_reduce(&cur, flags)? - { - let mut result = proj_result; - for arg in &args { - result = self.intern(KExpr::app(result, arg.clone())); - } - cur = result; - 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 family == PrimFamily::BitVec - && let Some(reduced) = self.try_reduce_bitvec(&cur)? - { - cur = reduced; - continue; - } - - // Nat primitive reduction - if family == PrimFamily::Nat - && let Some(reduced) = - self.try_reduce_nat_with_succ_mode(&cur, nat_succ_mode)? - { - cur = reduced; - continue; - } - - // String primitives must run before projection-definition - // rewriting. In the compiled environment, wrappers such as - // `String.toByteArray` are projection definitions; - // once rewritten to `Prj`, the cheap primitive recognizers no longer - // see the original head. - if family == PrimFamily::Str - && let Some(reduced) = self.try_reduce_string(&cur)? - { - cur = reduced; - continue; - } - - if flags.is_full() - && let Some(reduced) = self.try_reduce_projection_definition(&cur)? - { - cur = reduced; - continue; - } - - // Quotient reduction - if let Some(reduced) = self.try_quot_reduce(&cur)? { - cur = reduced; - continue; - } - - break; - } - - if use_cache && !transient_nat_work { - if flags.is_full() { - self.env.whnf_no_delta_cache.insert(key, cur.clone()); - } else { - self.env.whnf_no_delta_cheap_cache.insert(key, cur.clone()); - } - } - Ok(cur) - } - - /// Delta unfold: unfold one defined constant. - pub fn delta_unfold_one( - &mut self, - e: &KExpr, - ) -> Result>, TcError> { - if let Some(unfolded) = self.try_delta_unfold(e)? { - return Ok(Some(unfolded)); - } - // Bare constant - if let ExprData::Const(id, us, _) = e.data() - && let Some(KConst::Defn { kind, safety, val, .. }) = - self.try_get_const(id)? - && self.unfolds(kind, safety) - { - self.dump_delta_trace(id, 0, e); - self.record_delta_target(id); - let val = val.clone(); - let us: Vec<_> = us.to_vec(); - return Ok(Some(self.unfold_const_value(e, &val, &us)?)); - } - Ok(None) - } - - /// Try delta-unfold on application head. - fn try_delta_unfold( - &mut self, - e: &KExpr, - ) -> Result>, TcError> { - let (head, args) = collect_app_spine(e); - - let (id, us) = match head.data() { - ExprData::Const(id, us, _) => (id, us), - _ => return Ok(None), - }; - - let val = match self.try_get_const(id)? { - Some(KConst::Defn { kind, safety, val, .. }) - if self.unfolds(kind, safety) => - { - self.dump_delta_trace(id, args.len(), e); - self.record_delta_target(id); - val.clone() - }, - _ => return Ok(None), - }; - - let us: Vec<_> = us.to_vec(); - let val = self.unfold_const_value(&head, &val, &us)?; - - let mut result = val; - for arg in &args { - result = self.intern(KExpr::app(result, arg.clone())); - } - - Ok(Some(result)) - } - - /// Cache wrapper around `instantiate_univ_params` for delta unfolding. - /// - /// `head_expr` is the `Const(id, us)` head whose body we are unfolding; - /// its content hash already encodes `(id, us)`, so we use it directly - /// as the cache key. The cached value is the universe-instantiated body - /// returned by `instantiate_univ_params(val, us)`. - /// - /// Soundness: `instantiate_univ_params` is a pure function of `(val, us)` - /// — it only walks the term and substitutes universe params, touching - /// neither `tc.ctx` nor any thread-local mutable state. Two distinct - /// `(id, us)` pairs always produce distinct head hashes (KExpr interning - /// is by content), so cache hits are content-correct. - /// - /// Mirrors the lean4 C++ kernel `m_unfold` cache in `type_checker.cpp`. - fn unfold_const_value( - &mut self, - head_expr: &KExpr, - val: &KExpr, - us: &[KUniv], - ) -> Result, TcError> { - let key = head_expr.hash_key(); - if let Some(cached) = self.env.unfold_cache.get(&key) { - self.env.perf.record_unfold_hit(); - return Ok(cached.clone()); - } - self.env.perf.record_unfold_miss(); - let result = self.instantiate_univ_params(val, us)?; - self.env.unfold_cache.insert(key, result.clone()); - Ok(result) - } - - // ----------------------------------------------------------------------- - // Iota reduction - // ----------------------------------------------------------------------- - - /// Try iota: recursor applied to constructor. - /// - /// Flags-threaded: when `flags.cheap_rec` is set, the major premise (and - /// the freshly-built string-literal constructor) reduce with cheap WHNF, - /// mirroring Lean4Lean's `cheapRec` behaviour at TypeChecker.lean:337–341. - /// Internal-only — callers go through `whnf_core_with_flags`. - fn try_iota_with_flags( - &mut self, - e: &KExpr, - flags: WhnfFlags, - ) -> Result>, TcError> { - let (head, spine) = super::tc::borrow_app_spine(e); - - let (rec_id, rec_us) = match head.data() { - ExprData::Const(id, us, _) => (id.clone(), us.clone()), - _ => return Ok(None), - }; - - let recr = match self.try_get_const(&rec_id)? { - Some(KConst::Recr { - k, - params, - motives, - minors, - indices, - rules, - lvls, - .. - }) => { - let major_idx = u64_to_usize::(params + motives + minors + indices)?; - if spine.len() <= major_idx { - 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` is already owned here (moved out of the KConst clone - // `try_get_const` returned) — do not clone it again. - rules, - lvls, - } - }, - _ => return Ok(None), - }; - - // Only an actual recursor needs ownership for reduction/reconstruction. - // The common non-recursor rejection above only inspected borrowed nodes. - let spine: Vec<_> = spine.into_iter().cloned().collect(); - - // K-like recursor: try to synthesize a nullary constructor before WHNF. - // This handles cases like `Eq.rec motive minor major` where major isn't - // a constructor but its type matches the inductive — we build `Eq.refl params...`. - let major = &spine[recr.major_idx]; - let major = if recr.k { - match self.synth_ctor_when_k(major, &rec_id, &recr, &rec_us)? { - KSynthOutcome::Synthesized(ctor) => ctor, - KSynthOutcome::DefinitiveReject => return Ok(None), - KSynthOutcome::Inconclusive => major.clone(), - } - } else { - major.clone() - }; - let major = match self.cleanup_nat_offset_major(&major)? { - Some(cleaned) => cleaned, - None => major, - }; - - // WHNF the major premise. Cheap mode skips delta on the major itself, - // matching Lean4Lean's `cheapRec` (TypeChecker.lean:337–341); the rest of - // the iota machinery still gets a structural normal form to inspect. - let mut major_whnf = if flags.cheap_rec { - self.whnf_core_with_flags(&major, flags)? - } else { - self.whnf(&major)? - }; - - // Nat literal → constructor form (one level: n → Nat.succ(lit(n-1))). - // - // Mirrors lean4 (`refs/lean4/src/kernel/inductive.h:91-93`) and - // lean4lean (`refs/lean4lean/Lean4Lean/Inductive/Reduce.lean:70`): - // unconditional peel. Truly runaway recursors (step case forces the - // IH on every iteration) are bounded by `MAX_WHNF_FUEL` / outer - // `MaxRecDepth`, same as upstream. An earlier ix-specific - // throttle-by-counter scheme was found to mis-classify omega-style - // proofs that legitimately crunch many independent large-Nat - // recursors in one check; if a real runaway shows up we will fall - // back to fuel-based detection and not the counter. - let mut major_was_nat_lit = false; - if let ExprData::Nat(val, _, _) = major_whnf.data() { - if *IX_NAT_IOTA_TRACE { - let n = NAT_IOTA_TRACE_COUNT - .fetch_add(1, std::sync::atomic::Ordering::Relaxed); - if n < 32 { - log::info!( - "[nat_iota_trace] rec={} major_bits={} spine={} major_idx={}", - rec_id, - val.0.bits(), - spine.len(), - recr.major_idx - ); - } - } - major_was_nat_lit = true; - major_whnf = self.nat_to_constructor(&val.clone())?; - } - if let Some(cleaned) = self.cleanup_nat_offset_major(&major_whnf)? { - major_whnf = cleaned; - } - // String literal → constructor form (matching lean4lean - // Reduce.lean:71 / C++ inductive.h:95). The expansion takes one - // delta step past `String.ofList` (see `str_lit_to_ctor_app`) so the - // native collapse rule in `try_reduce_string` can't fold it straight - // back into the literal; constructor fields stay lazy. - if let ExprData::Str(val, _, _) = major_whnf.data() { - let val = val.clone(); - major_whnf = self.str_lit_to_ctor_app(&val)?; - } - // Native char values are kept stuck by whnf (they never unfold to - // their `dite`-validity body on their own). A recursor that - // genuinely scrutinizes a char pays for the structural expansion - // here, once — exactly what pre-native reduction did on every whnf. - // Cheap mode leaves the major stuck, as it did before the fast path - // (`Char.ofNat` is a Defn head that cheap whnf never delta-unfolds). - if !flags.cheap_rec - && self.char_lit_value(&major_whnf).is_some() - && let Some(unfolded) = self.delta_unfold_one(&major_whnf)? - { - major_whnf = self.whnf(&unfolded)?; - } - - // Check if major is a constructor application - let (ctor_head, ctor_args) = collect_app_spine(&major_whnf); - let is_ctor = match ctor_head.data() { - ExprData::Const(id, _, _) => { - matches!(self.try_get_const(id)?, Some(KConst::Ctor { .. })) - }, - _ => false, - }; - - // Diagnostic: when the major doesn't reduce to a ctor, iota is stuck. - // Surface which recursor + major shape we got \u2014 the major's head - // tells us which downstream reduction (delta, iota, nat, int) failed - // to complete. - 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) { - log::info!("[iota stuck] rec={rec_name}"); - log::info!("[iota stuck] major: {major}"); - log::info!("[iota stuck] major whnf: {major_whnf}"); - } - } - - if is_ctor { - crate::perf::record_iota_histo(&rec_id.addr); - let ctor_id = match ctor_head.data() { - ExprData::Const(id, _, _) => id, - _ => unreachable!(), - }; - let (cidx, ctor_fields) = match self.get_const(ctor_id)? { - KConst::Ctor { cidx, fields, .. } => { - (u64_to_usize::(cidx)?, u64_to_usize::(fields)?) - }, - _ => return Ok(None), - }; - - if cidx >= recr.rules.len() { - return Ok(None); - } - let rule = &recr.rules[cidx]; - // H6: Check level params arity (lean4lean Reduce.lean:76) - if rec_us.len() as u64 != recr.lvls { - return Ok(None); - } - // H5: Check nfields ≤ major_args (lean4lean Reduce.lean:75) - if ctor_fields > ctor_args.len() { - return Ok(None); - } - let rec_us_vec: Vec<_> = rec_us.to_vec(); - let rhs = self.instantiate_univ_params(&rule.rhs, &rec_us_vec)?; - - let pmm_end = recr.params + recr.motives + recr.minors; - let field_start = ctor_args.len() - ctor_fields; - let mut result = rhs; - for arg in spine.iter().take(pmm_end.min(spine.len())) { - result = self.apply_iota_arg(result, arg, major_was_nat_lit); - } - for arg in ctor_args.iter().skip(field_start) { - result = self.apply_iota_arg(result, arg, major_was_nat_lit); - } - for arg in spine.iter().skip(recr.major_idx + 1) { - result = self.apply_iota_arg(result, arg, major_was_nat_lit); - } - return Ok(Some(result)); - } - - // Struct eta iota fallback - if let Some(result) = - self.try_struct_eta_iota(&rec_id, &recr, &rec_us, &spine)? - { - return Ok(Some(result)); - } - - Ok(None) - } - - pub fn is_struct_like(&mut self, id: &KId) -> Result> { - match self.try_get_const(id)? { - Some(KConst::Indc { indices, ctors, .. }) - if indices == 0 && ctors.len() == 1 => {}, - _ => return Ok(false), - } - Ok(!self.computed_is_rec(id)?) - } - - fn apply_iota_arg( - &mut self, - result: KExpr, - arg: &KExpr, - transient: bool, - ) -> KExpr { - if transient { - if let ExprData::Lam(_, _, _, _, body, _) = result.data() { - let body = body.clone(); - return subst_no_intern(&body, arg, 0); - } - KExpr::app(result, arg.clone()) - } else { - self.intern(KExpr::app(result, arg.clone())) - } - } - - /// 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.optional_primitives().is_none() { - return Ok(false); - } - let (head, nargs) = Self::spine_head_and_len(e); - let ExprData::Const(id, _, _) = head.data() else { - return Ok(false); - }; - if id.addr == self.primitives()?.nat_rec.addr - || id.addr == self.primitives()?.nat_cases_on.addr - { - return self.is_nat_literal_recursor_app(e); - } - if id.addr == self.primitives()?.nat_succ.addr - && nargs == 1 - && let ExprData::App(_, arg, _) = e.data() - { - return self.is_nat_literal_recursor_app(arg); - } - Ok(false) - } - - fn is_nat_literal_recursor_app( - &mut self, - e: &KExpr, - ) -> Result> { - if self.optional_primitives().is_none() { - return Ok(false); - } - // 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); - }; - if id.addr != self.primitives()?.nat_rec.addr - && id.addr != self.primitives()?.nat_cases_on.addr - { - return Ok(false); - } - - let Some(KConst::Recr { params, motives, minors, indices, .. }) = - self.try_get_const(id)? - else { - return Ok(false); - }; - let (_, spine) = collect_app_spine(e); - let major_idx = u64_to_usize::(params + motives + minors + indices)?; - Ok( - spine - .get(major_idx) - .is_some_and(|major| matches!(major.data(), ExprData::Nat(..))), - ) - } - - /// Lean's `cleanupNatOffsetMajor` for recursor reduction. - /// - /// If the major premise is definitionally an offset `base + k` with `k > 0`, - /// expose exactly one constructor layer as `Nat.succ (base + (k-1))`. - /// This prevents `Nat.rec ... (x + huge)` from delta-unfolding `Nat.add` - /// and allocating one intermediate literal per predecessor. Closed Nat - /// arithmetic is left alone so the primitive Nat reducer can compute it - /// directly to a compact literal. - fn cleanup_nat_offset_major( - &mut self, - e: &KExpr, - ) -> Result>, TcError> { - if !self.takes_extra(ExtraReduction::NatOffsets) { - return Ok(None); - } - if self.eval_nat_offset_literal(e, 0).is_some() { - return Ok(None); - } - let Some((base, offset)) = self.nat_offset(e, 0)? else { - return Ok(None); - }; - if offset.0 == num_bigint::BigUint::ZERO { - return Ok(None); - } - - let pred_offset = Nat(&offset.0 - 1u64); - let pred = if pred_offset.0 == num_bigint::BigUint::ZERO { - base - } else { - let pred_lit = self.nat_expr_from_value(pred_offset); - self.mk_nat_add(base, pred_lit)? - }; - Ok(Some(self.mk_nat_succ(pred)?)) - } - - fn nat_offset( - &mut self, - e: &KExpr, - depth: u16, - ) -> Result, Nat)>, TcError> { - if self.optional_primitives().is_none() { - return Ok(None); - } - const MAX_NAT_OFFSET_DEPTH: u16 = 256; - if depth >= MAX_NAT_OFFSET_DEPTH { - return Ok(None); - } - - let (head, args) = collect_app_spine(e); - let ExprData::Const(id, _, _) = head.data() else { - return Ok(None); - }; - - if id.addr == self.primitives()?.nat_succ.addr && args.len() == 1 { - let (base, offset) = self.nat_offset_or_zero(&args[0], depth + 1)?; - return Ok(Some((base, Nat(offset.0 + 1u64)))); - } - - // Reading `Nat.add base k` as an offset is part of the offset forms; - // without them, only `Nat.succ` layers count. - if self.takes_extra(ExtraReduction::NatOffsets) - && args.len() == 2 - && id.addr == self.primitives()?.nat_add.addr - { - let Some(rhs) = self.eval_nat_offset_literal(&args[1], depth + 1) else { - return Ok(None); - }; - let (base, offset) = self.nat_offset_or_zero(&args[0], depth + 1)?; - return Ok(Some((base, Nat(offset.0 + rhs.0)))); - } - - Ok(None) - } - - fn nat_offset_or_zero( - &mut self, - e: &KExpr, - depth: u16, - ) -> Result<(KExpr, Nat), TcError> { - Ok( - self - .nat_offset(e, depth)? - .unwrap_or_else(|| (e.clone(), Nat(num_bigint::BigUint::ZERO))), - ) - } - - /// Syntactic, no-delta evaluator for Nat offset constants. - /// - /// This is intentionally weaker than WHNF: it only recognizes already - /// exposed Nat literals/constructors and primitive Nat arithmetic whose - /// arguments are themselves syntactically evaluable. It is used to avoid - /// rewriting closed arithmetic offsets before `try_reduce_nat` can compute - /// them, and to evaluate the literal offset side of `Nat.add`. - fn eval_nat_offset_literal( - &mut self, - e: &KExpr, - depth: u16, - ) -> Option { - const MAX_NAT_OFFSET_EVAL_DEPTH: u16 = 256; - if depth >= MAX_NAT_OFFSET_EVAL_DEPTH { - return None; - } - - if let Some(n) = extract_nat_value(e, self.optional_primitives()?) { - return Some(n); - } - - let (head, args) = collect_app_spine(e); - let ExprData::Const(id, _, _) = head.data() else { - return None; - }; - - if id.addr == self.optional_primitives()?.nat_pred.addr && args.len() == 1 { - let n = self.eval_nat_offset_literal(&args[0], depth + 1)?; - let result = if n.0 == num_bigint::BigUint::ZERO { - Nat(num_bigint::BigUint::ZERO) - } else { - Nat(n.0 - 1u64) - }; - return Some(result); - } - - if self.is_nat_bin_arith_addr(&id.addr) && args.len() == 2 { - let a = self.eval_nat_offset_literal(&args[0], depth + 1)?; - let b = self.eval_nat_offset_literal(&args[1], depth + 1)?; - return compute_nat_bin(&id.addr, self.optional_primitives()?, &a, &b); - } - - 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> { - if !self.takes_extra(ExtraReduction::NatOffsets) - || self.optional_primitives().is_none() - { - return Ok(None); - } - // 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.primitives()?.nat_add.addr - && id.addr != self.primitives()?.nat_div.addr - && id.addr != self.primitives()?.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.primitives()?.nat_add.addr; - let is_divmod = id.addr == self.primitives()?.nat_div.addr - || id.addr == self.primitives()?.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.primitives()?) 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.primitives()?).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 self.optional_primitives().is_none() { - return Ok(None); - } - if let Some(v) = extract_nat_value(e, self.primitives()?) { - 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.primitives()?) { - 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`], or, without the offset forms, as the - /// `r` `Nat.succ` layers the decomposition read off the term. - pub(super) fn nat_offset_rebuild( - &mut self, - base: Option>, - r: Nat, - ) -> Result, TcError> { - Ok({ - match base { - None => self.nat_expr_from_value(r), - Some(b) if r.0 == num_bigint::BigUint::ZERO => b, - Some(b) => { - // Without the offset forms a base's offset counts its own `succ` - // layers, so it is small and the layers are rebuilt as they were. - if !self.takes_extra(ExtraReduction::NatOffsets) - && let Some(layers) = r.to_u64() - { - return self.nat_succ_n(b, layers); - } - let lit = self.nat_expr_from_value(r); - self.mk_nat_add(b, lit)? - }, - } - }) - } - - fn mk_nat_succ(&mut self, pred: KExpr) -> Result, TcError> { - Ok({ - let succ = KExpr::cnst(self.primitives()?.nat_succ.clone(), Box::new([])); - KExpr::app(succ, pred) - }) - } - - fn mk_nat_add( - &mut self, - a: KExpr, - b: KExpr, - ) -> Result, TcError> { - Ok({ - let add = KExpr::cnst(self.primitives()?.nat_add.clone(), Box::new([])); - let result = KExpr::app(add, a); - KExpr::app(result, b) - }) - } - - fn try_struct_eta_iota( - &mut self, - rec_id: &KId, - recr: &IotaInfo, - rec_us: &[KUniv], - spine: &[KExpr], - ) -> Result>, TcError> { - if recr.rules.len() != 1 { - return Ok(None); - } - if rec_us.len() as u64 != recr.lvls { - return Ok(None); - } - let rule = &recr.rules[0]; - - let rec_ty = match self.try_get_const(rec_id)? { - Some(c) => c.ty().clone(), - None => return Ok(None), - }; - let rec_ty = match self.instantiate_univ_params(&rec_ty, rec_us) { - Ok(ty) => ty, - Err(_) => return Ok(None), - }; - let skip = (recr.params + recr.motives + recr.minors + recr.indices) as u64; - let ind_id = match self.get_major_inductive_id(&rec_ty, skip) { - Ok(id) => id, - Err(_) => return Ok(None), - }; - if !self.is_struct_like(&ind_id)? { - return Ok(None); - } - - // H3: Prop guard — don't eta-expand Prop-typed structures (lean4lean toCtorWhenStruct:51) - let major = &spine[recr.major_idx]; - let major_ty = match self.with_infer_only(|tc| tc.infer(major)) { - Ok(ty) => ty, - Err(_) => return Ok(None), - }; - let major_sort = match self.with_infer_only(|tc| tc.infer(&major_ty)) { - Ok(ty) => ty, - Err(_) => return Ok(None), - }; - let major_sort_w = match self.whnf(&major_sort) { - Ok(w) => w, - Err(_) => return Ok(None), - }; - if matches!(major_sort_w.data(), ExprData::Sort(u, _) if u.is_semantic_zero()) - { - return Ok(None); - } - let rec_us_vec: Vec<_> = rec_us.to_vec(); - let rhs = self.instantiate_univ_params(&rule.rhs, &rec_us_vec)?; - let pmm_end = recr.params + recr.motives + recr.minors; - let mut result = rhs; - for arg in spine.iter().take(pmm_end.min(spine.len())) { - result = self.intern(KExpr::app(result, arg.clone())); - } - for i in 0..rule.fields { - let proj = self.intern(KExpr::prj(ind_id.clone(), i, major.clone())); - result = self.intern(KExpr::app(result, proj)); - } - for arg in spine.iter().skip(recr.major_idx + 1) { - result = self.intern(KExpr::app(result, arg.clone())); - } - Ok(Some(result)) - } - - // ----------------------------------------------------------------------- - // K-rule: synthesize nullary constructor - // ----------------------------------------------------------------------- - - /// For K-like recursors, try to synthesize a nullary constructor from the - /// major premise's type. A full-strength failed candidate comparison is a - /// definitive rejection; optional probe misses and cheap-mode false results - /// are inconclusive and preserve the ordinary major-WHNF fallback. - /// - /// Algorithm (following lean4lean/nanoda): - /// 1. Infer major's type, WHNF it - /// 2. Check head constant matches the recursor's target inductive - /// 3. Build nullary ctor: `Ctor.{levels} params...` - /// 4. Infer ctor's type, check def-eq with major's type - fn synth_ctor_when_k( - &mut self, - major: &KExpr, - rec_id: &KId, - recr: &IotaInfo, - rec_us: &[KUniv], - ) -> Result, TcError> { - if rec_us.len() as u64 != recr.lvls { - return Ok(KSynthOutcome::Inconclusive); - } - // Infer major's type (infer-only: we just need the type, not validation) - let major_ty = match self.with_infer_only(|tc| tc.infer(major)) { - Ok(ty) => ty, - Err(_) => return Ok(KSynthOutcome::Inconclusive), - }; - let major_ty_w = match self.whnf(&major_ty) { - Ok(w) => w, - Err(_) => return Ok(KSynthOutcome::Inconclusive), - }; - - // Extract head constant of the type - let (ty_head, ty_args) = collect_app_spine(&major_ty_w); - let ty_head_id = match ty_head.data() { - ExprData::Const(id, _, _) => id.clone(), - _ => return Ok(KSynthOutcome::Inconclusive), - }; - - // Get the recursor's target inductive from its type - let rec_ty = match self.try_get_const(rec_id)? { - Some(c) => c.ty().clone(), - None => return Ok(KSynthOutcome::Inconclusive), - }; - let rec_ty = match self.instantiate_univ_params(&rec_ty, rec_us) { - Ok(ty) => ty, - Err(_) => return Ok(KSynthOutcome::Inconclusive), - }; - let skip = (recr.params + recr.motives + recr.minors + recr.indices) as u64; - let ind_id = match self.get_major_inductive_id(&rec_ty, skip) { - Ok(id) => id, - Err(_) => return Ok(KSynthOutcome::Inconclusive), - }; - - // Head of major's type must match the recursor's target inductive - if ty_head_id.addr != ind_id.addr { - return Ok(KSynthOutcome::Inconclusive); - } - - // Get the first constructor - let ctor_id = match self.try_get_const(&ind_id)? { - Some(KConst::Indc { ctors, .. }) if !ctors.is_empty() => ctors[0].clone(), - _ => return Ok(KSynthOutcome::Inconclusive), - }; - - // Build nullary ctor application: Ctor.{levels} params... - let ctor_us = match ty_head.data() { - ExprData::Const(_, us, _) => us.clone(), - _ => return Ok(KSynthOutcome::Inconclusive), - }; - let mut ctor_app = self.intern(KExpr::cnst(ctor_id, ctor_us)); - for arg in ty_args.iter().take(recr.params) { - ctor_app = self.intern(KExpr::app(ctor_app, arg.clone())); - } - - // Verify: infer ctor's type and check def-eq with major's type - let ctor_ty = match self.with_infer_only(|tc| tc.infer(&ctor_app)) { - Ok(ty) => ty, - Err(_) => return Ok(KSynthOutcome::Inconclusive), - }; - if *IX_KSYNTH_LOG { - 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()); - } - // Cheap projection reduction can report a false negative that a later - // full comparison resolves. Only a normal/full DefEq rejection can be - // used to skip WHNF of the major premise. - return Ok(if self.cheap_recursion_depth == 0 { - KSynthOutcome::DefinitiveReject - } else { - KSynthOutcome::Inconclusive - }); - } - - Ok(KSynthOutcome::Synthesized(ctor_app)) - } - - // ----------------------------------------------------------------------- - // Projection reduction - // ----------------------------------------------------------------------- - - pub(super) fn try_proj_reduce( - &mut self, - id: &KId, - field: u64, - wval: &KExpr, - ) -> Result>, TcError> { - // String literal → constructor form before trying projection. The - // expansion takes one delta step past `String.ofList` (see - // `str_lit_to_ctor_app`) so the native collapse rule can't fold it - // back into the literal; in the compiled env the projected fields - // stay lazy (`.toByteArray` = the unevaluated UTF-8 encoding). - let wval_expanded; - let wval = if let ExprData::Str(s, _, _) = wval.data() { - wval_expanded = self.str_lit_to_ctor_app(&s.clone())?; - &wval_expanded - } else if self.char_lit_value(wval).is_some() { - // Native char values stay stuck in whnf; a projection (`.val`) - // needs the `Char.mk` form, so expand structurally, once. - match self.delta_unfold_one(wval)? { - Some(unfolded) => { - wval_expanded = self.whnf(&unfolded)?; - &wval_expanded - }, - None => wval, - } - } else { - wval - }; - - let (head, args) = collect_app_spine(wval); - - let ctor_id = match head.data() { - ExprData::Const(id, _, _) => id, - _ => { - self.dump_proj_trace(id, field, wval, None, None); - return Ok(None); - }, - }; - - let ctor_params = match self.try_get_const(ctor_id)? { - Some(KConst::Ctor { params, .. }) => match usize::try_from(params) { - Ok(params) => params, - Err(_) => return Ok(None), - }, - _ => { - self.dump_proj_trace(id, field, wval, None, None); - return Ok(None); - }, - }; - - let field_start = ctor_params; - let Ok(field_idx) = usize::try_from(field) else { - return Ok(None); - }; - let idx = field_start + field_idx; - let result = args.get(idx).cloned(); - self.dump_proj_trace(id, field, wval, Some(ctor_params), result.as_ref()); - Ok(result) - } - - /// Try to reduce a projection-headed application: App(Prj(S, i, v), args...). - /// Returns Some((reduced_proj, remaining_args)) if the projection reduced. - fn try_proj_app_reduce( - &mut self, - e: &KExpr, - flags: WhnfFlags, - ) -> Result, Vec>)>, TcError> { - let (head, args) = collect_app_spine(e); - if args.is_empty() { - return Ok(None); - } - - if let ExprData::Prj(id, field, val, _) = head.data() { - let field = *field; - let id = id.clone(); - let val = val.clone(); - let wval = if flags.cheap_proj { - self.whnf_core_with_flags(&val, flags)? - } else { - self.whnf(&val)? - }; - if let Some(result) = self.try_proj_reduce(&id, field, &wval)? { - return Ok(Some((result, args))); - } - } - Ok(None) - } - - fn try_reduce_projection_definition( - &mut self, - e: &KExpr, - ) -> Result>, TcError> { - let (head, args) = collect_app_spine(e); - let ExprData::Const(id, _, _) = head.data() else { - return Ok(None); - }; - let val = - match self.try_get_const(id)? { - Some(KConst::Defn { - kind: DefKind::Definition, safety, val, .. - }) if self.unfolds(DefKind::Definition, safety) => val, - _ => return Ok(None), - }; - let (arity, struct_id, field, struct_arg_idx) = - match self.projection_definition_info(&val) { - Some(info) => info, - None => return Ok(None), - }; - if args.len() < arity { - return Ok(None); - } - let mut result = - self.intern(KExpr::prj(struct_id, field, args[struct_arg_idx].clone())); - for arg in args.iter().skip(arity) { - result = self.intern(KExpr::app(result, arg.clone())); - } - Ok(Some(result)) - } - - fn projection_definition_info( - &self, - val: &KExpr, - ) -> Option<(usize, KId, u64, usize)> { - let mut arity = 0usize; - let mut cur = val.clone(); - loop { - match cur.data() { - ExprData::Lam(_, _, _, _, body, _) => { - arity += 1; - cur = body.clone(); - }, - ExprData::Prj(struct_id, field, projected, _) => { - let ExprData::Var(idx, _, _) = projected.data() else { - return None; - }; - let idx = usize::try_from(*idx).ok()?; - if idx >= arity { - return None; - } - let struct_arg_idx = arity - 1 - idx; - return Some((arity, struct_id.clone(), *field, struct_arg_idx)); - }, - _ => return None, - } - } - } - - // ----------------------------------------------------------------------- - // Helpers - // ----------------------------------------------------------------------- - - /// Get the major premise's inductive KId from a recursor type. - /// - /// Strategy: peel `skip` foralls per Lean's stored `params + motives + - /// minors + indices` count, then expect the next forall's domain to - /// have an inductive `Const` head. For well-formed Lean recursors this - /// lands exactly on the major premise. - /// - /// Resilience: if the strict `skip` position's domain head is not an - /// inductive `Const`, peel up to `MAX_EXTRA_FORALLS` additional foralls - /// scanning for the first one whose domain head IS an inductive - /// `KConst::Indc`. This handles recursor shapes where Lean's stored - /// counts don't align with the kernel's view of the forall structure - /// after WHNF (e.g., nested-inductive recursors that carry extra - /// instance/motive binders not captured by `num_params/num_motives/...`). - /// - /// We specifically require the head to be an **inductive** constant, not - /// any Const: minor premises of recursors like `Nat.rec`'s `succ` case - /// have a forall `(n : Nat)` where `Nat` is a Const inductive, but - /// those are consumed by the initial `skip` pass. The scan only ever - /// fires when `skip` under-counts; in that case the first Const - /// inductive encountered is structurally the major. - pub fn get_major_inductive_id( - &mut self, - rec_ty: &KExpr, - skip: u64, - ) -> Result, TcError> { - const MAX_EXTRA_FORALLS: u64 = 8; - let saved = self.save_depth(); - let result = (|| -> Result, TcError> { - let mut ty = rec_ty.clone(); - for _ in 0..skip { - let w = self.whnf(&ty)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - // Keep the peeled binder in scope. The body is open, and must not - // resolve one of its Vars through an unrelated caller frame. - self.push_local(dom.clone()); - ty = body.clone(); - }, - _ => { - return Err(TcError::Other( - "get_major_inductive_id: not enough foralls".into(), - )); - }, - } - } - - // Scan forward looking for a forall whose domain has a `KConst::Indc` - // head. Accept the first match. Bounded so we can't loop forever. - for _ in 0..=MAX_EXTRA_FORALLS { - let w = self.whnf(&ty)?; - match w.data() { - ExprData::All(_, _, _, _, dom, body, _) => { - let head = app_head(dom); - if let ExprData::Const(id, _, _) = head.data() { - // Only accept if the head resolves to an inductive. - if matches!(self.try_get_const(id)?, Some(KConst::Indc { .. })) { - return Ok(id.clone()); - } - } - self.push_local(dom.clone()); - ty = body.clone(); - }, - _ => { - return Err(TcError::Other( - "get_major_inductive_id: expected forall at major".into(), - )); - }, - } - } - - Err(TcError::Other( - "get_major_inductive_id: no inductive-headed forall within scan bound" - .into(), - )) - })(); - self.restore_depth(saved); - result - } - - /// Convert a Nat literal to constructor form: 0 → Nat.zero, n+1 → Nat.succ(n-1). - fn nat_to_constructor(&mut self, val: &Nat) -> Result, TcError> { - self.require_role(Role::Nat)?; - Ok({ - use num_bigint::BigUint; - // Global diagnostic: count expansions and log every 100k. A legitimate - // `Nat.rec motive base step hugeFuel` where `step` only forces `ih` - // on `Poly.add` paths will fire a handful of times. A pathological - // linearly-recursing body would fire millions. Gated behind - // `IX_NAT_EXPAND_LOG=1` so normal runs stay quiet. - if *IX_NAT_EXPAND_LOG { - let n = - NAT_EXPAND_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - if n.is_multiple_of(10_000) { - log::info!( - "[nat_to_constructor] count={n} val_bits={}", - val.0.bits() - ); - } - } - if val.0 == BigUint::ZERO { - KExpr::cnst(self.primitives()?.nat_zero.clone(), Box::new([])) - } else { - let pred_val = Nat(&val.0 - BigUint::from(1u64)); - let pred_addr = Address::hash(&pred_val.to_le_bytes()); - let pred_expr = KExpr::nat(pred_val, pred_addr); - let succ = - KExpr::cnst(self.primitives()?.nat_succ.clone(), Box::new([])); - KExpr::app(succ, pred_expr) - } - }) - } - - fn nat_literal(&mut self, n: u64) -> KExpr { - let val = Nat::from(n); - let addr = Address::hash(&val.to_le_bytes()); - KExpr::nat(val, addr) - } - - /// 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 Some(primitives) = self.optional_primitives() else { - return PrimFamily::Other; - }; - 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(primitives, &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, - ) -> Result>, TcError> { - self.try_reduce_nat_with_succ_mode(e, NatSuccMode::Collapse) - } - - fn try_reduce_nat_with_succ_mode( - &mut self, - e: &KExpr, - nat_succ_mode: NatSuccMode, - ) -> Result>, TcError> { - if self.optional_primitives().is_none() { - return Ok(None); - } - let (head, args) = collect_app_spine(e); - let addr = match head.data() { - ExprData::Const(id, _, _) => id.addr.clone(), - _ => return Ok(None), - }; - // Nat.succ n → n + 1 - if addr == self.primitives()?.nat_succ.addr && args.len() == 1 { - if nat_succ_mode == NatSuccMode::Stuck { - return Ok(None); - } - return self.try_reduce_nat_succ_iter(&args[0]); - } - - if args.len() < 2 { - return Ok(None); - } - - let is_bin_arith = self.is_nat_bin_arith_addr(&addr); - let is_bin_pred = self.is_nat_bin_pred_addr(&addr); - - if !is_bin_arith && !is_bin_pred { - return Ok(None); - } - self.dump_nat_trace("candidate", e); - - if is_bin_pred { - return self.try_reduce_nat_predicate(&addr, &args); - } - - let Some(wa) = self.whnf_prim_arg(&args[0])? else { - return Ok(None); - }; - let Some(wb) = self.whnf_prim_arg(&args[1])? else { - return Ok(None); - }; - self.dump_nat_trace("arg0-whnf", &wa); - self.dump_nat_trace("arg1-whnf", &wb); - let a_val = match extract_nat_lit(&wa, self.primitives()?) { - Some(v) => v.clone(), - None => { - self.dump_nat_trace("arg0-not-nat", &wa); - return Ok(None); - }, - }; - let b_val = match extract_nat_lit(&wb, self.primitives()?) { - Some(v) => v.clone(), - None => { - self.dump_nat_trace("arg1-not-nat", &wb); - return Ok(None); - }, - }; - - let result_expr = if is_bin_arith { - let result = - match compute_nat_bin(&addr, self.primitives()?, &a_val, &b_val) { - Some(r) => r, - None => { - self.dump_nat_trace("not-computed", e); - return Ok(None); // can't compute, leave unreduced - }, - }; - let blob_addr = Address::hash(&result.to_le_bytes()); - KExpr::nat(result, blob_addr) - } else { - let b = if addr == self.primitives()?.nat_beq.addr { - a_val == b_val - } else { - a_val <= b_val - }; - let bool_id = if b { - self.primitives()?.bool_true.clone() - } else { - self.primitives()?.bool_false.clone() - }; - self.intern(KExpr::cnst(bool_id, Box::new([]))) - }; - - let mut result = result_expr; - for arg in args.iter().skip(2) { - result = self.intern(KExpr::app(result, arg.clone())); - } - Ok(Some(result)) - } - - fn try_reduce_nat_succ_iter( - &mut self, - arg: &KExpr, - ) -> Result>, TcError> { - if self.optional_primitives().is_none() { - return Ok(None); - } - // Stuck-chain memo. The inner WHNF below runs in `NatSuccMode::Stuck`, - // which bypasses the WHNF caches, so without this memo a stuck - // `succ^k(x)` chain is re-peeled in full from every depth it is - // encountered at — O(k²) fuel for `x + literal` on a symbolic `x`. - // Every suffix of a stuck chain is itself stuck, so on the stuck exit - // all visited keys are recorded. - let entry_key = self.whnf_key(arg); - if self.env.nat_succ_stuck.contains(&entry_key) { - return Ok(None); - } - 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(); - - loop { - if let Some(result) = - self.try_reduce_nat_succ_linear_rec(&cur, &offset)? - { - return Ok(Some(result)); - } - - let w = self.whnf_with_nat_succ_mode(&cur, NatSuccMode::Stuck)?; - if let Some(n) = extract_nat_lit(&w, self.primitives()?) { - let result = Nat(&n.0 + &offset); - let blob_addr = Address::hash(&result.to_le_bytes()); - return Ok(Some(KExpr::nat(result, blob_addr))); - } - - let (head, args) = collect_app_spine(&w); - if let ExprData::Const(id, _, _) = head.data() - && id.addr == self.primitives()?.nat_succ.addr - && args.len() == 1 - { - crate::perf::record_nat_succ_peel(); - offset += 1u64; - cur = args[0].clone(); - let cur_key = self.whnf_key(&cur); - if self.env.nat_succ_stuck.contains(&cur_key) { - // Known-stuck suffix: the whole chain above it is stuck too. - self.env.nat_succ_stuck.extend(visited); - return Ok(None); - } - visited.push(cur_key); - // The whnf'd form `succ(cur)` can also surface later as a - // succ-iter argument; record it alongside the raw chain. - let w_key = self.whnf_key(&w); - visited.push(w_key); - continue; - } - - self.env.nat_succ_stuck.extend(visited); - return Ok(None); - } - } - - fn try_reduce_nat_succ_linear_rec( - &mut self, - arg: &KExpr, - offset: &num_bigint::BigUint, - ) -> Result>, TcError> { - if !self.takes_extra(ExtraReduction::NatRecLinear) - || self.optional_primitives().is_none() - { - return Ok(None); - } - let Some(parts) = self.nat_rec_literal_parts(arg)? else { - return Ok(None); - }; - let Some(base) = parts.spine.get(parts.base_idx) else { - return Ok(None); - }; - let Some(step) = parts.spine.get(parts.step_idx) else { - return Ok(None); - }; - if *IX_NAT_LINEAR_REC_TRACE { - let n = NAT_LINEAR_REC_TRACE_COUNT - .fetch_add(1, std::sync::atomic::Ordering::Relaxed); - if n < 8 { - let step_whnf = self.whnf(step)?; - log::info!( - "[nat_linear_rec] major_bits={} base_idx={} step_idx={} spine={} step_whnf={}", - parts.major.0.bits(), - parts.base_idx, - parts.step_idx, - parts.spine.len(), - step_whnf - ); - } - } - if !self.is_nat_succ_ih_step(step)? { - return Ok(None); - } - - let base = base.clone(); - let base_whnf = self.whnf(&base)?; - let Some(base_val) = extract_nat_value(&base_whnf, self.primitives()?) - else { - // 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; - total += parts.major.0; - total += offset; - let result = Nat(total); - let blob_addr = Address::hash(&result.to_le_bytes()); - Ok(Some(KExpr::nat(result, blob_addr))) - } - - fn nat_rec_literal_parts( - &mut self, - e: &KExpr, - ) -> Result>, TcError> { - if self.optional_primitives().is_none() { - return Ok(None); - } - let (head, spine) = collect_app_spine(e); - let ExprData::Const(id, _, _) = head.data() else { - return Ok(None); - }; - if id.addr != self.primitives()?.nat_rec.addr { - return Ok(None); - } - - let Some(KConst::Recr { params, motives, minors, indices, .. }) = - self.try_get_const(id)? - else { - return Ok(None); - }; - let params = u64_to_usize::(params)?; - let motives = u64_to_usize::(motives)?; - let minors = u64_to_usize::(minors)?; - let indices = u64_to_usize::(indices)?; - if minors < 2 { - return Ok(None); - } - - let base_idx = params + motives; - let step_idx = base_idx + 1; - let major_idx = params + motives + minors + indices; - let Some(major) = spine.get(major_idx) else { - return Ok(None); - }; - let ExprData::Nat(major, _, _) = major.data() else { - return Ok(None); - }; - let major = major.clone(); - - Ok(Some(NatRecLiteralParts { spine, major, base_idx, step_idx, major_idx })) - } - - fn is_nat_succ_ih_step( - &mut self, - step: &KExpr, - ) -> Result> { - if self.optional_primitives().is_none() { - return Ok(false); - } - let step = self.whnf(step)?; - let ExprData::Lam(_, _, _, _, body, _) = step.data() else { - return Ok(false); - }; - let ExprData::Lam(_, _, _, _, body, _) = body.data() else { - return Ok(false); - }; - - let (head, args) = collect_app_spine(body); - let ExprData::Const(id, _, _) = head.data() else { - return Ok(false); - }; - if id.addr != self.primitives()?.nat_succ.addr || args.len() != 1 { - return Ok(false); - } - Ok(matches!(args[0].data(), ExprData::Var(0, _, _))) - } - - fn nat_expr_from_value(&mut self, n: Nat) -> KExpr { - let blob_addr = Address::hash(&n.to_le_bytes()); - KExpr::nat(n, blob_addr) - } - - fn nat_succ_n( - &mut self, - mut e: KExpr, - n: u64, - ) -> Result, TcError> { - Ok({ - for _ in 0..n { - let succ = self.intern(KExpr::cnst( - self.primitives()?.nat_succ.clone(), - Box::new([]), - )); - e = self.intern(KExpr::app(succ, e)); - } - e - }) - } - - fn is_nat_bin_arith_addr(&self, addr: &Address) -> bool { - let Some(primitives) = self.optional_primitives() else { - return false; - }; - let p = primitives; - *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 - } - - fn is_nat_bin_pred_addr(&self, addr: &Address) -> bool { - let Some(primitives) = self.optional_primitives() else { - return false; - }; - *addr == primitives.nat_beq.addr || *addr == primitives.nat_ble.addr - } - - /// WHNF an argument of a native primitive reduction (Nat ops, string - /// ops). Closed arguments reduce with full shared fuel; open arguments - /// get a small local budget so a stuck symbolic argument can't drain - /// the check's fuel before the caller falls back to the structural - /// path. `Ok(None)` = budget exhausted, leave the primitive unreduced. - fn whnf_prim_arg( - &mut self, - arg: &KExpr, - ) -> Result>, TcError> { - if !arg.has_fvars() || self.eager_reduce { - return Ok(Some(self.whnf(arg)?)); - } - - let saved_fuel = self.rec_fuel; - let local_fuel = saved_fuel.min(PRIM_REDUCER_OPEN_ARG_REC_FUEL); - self.rec_fuel = local_fuel; - let result = self.whnf(arg); - let consumed = local_fuel.saturating_sub(self.rec_fuel); - self.rec_fuel = saved_fuel.saturating_sub(consumed); - - match result { - Ok(w) => Ok(Some(w)), - Err(TcError::MaxRecDepth | TcError::MaxRecFuel) => Ok(None), - Err(err) => Err(err), - } - } - - /// Recursors / casesOn whose Nat-typed major can leave the term stuck. - /// `BitVec.toNat` projects through to a Nat that may itself be stuck on - /// a recursor, so it goes here too. Used by shallow native probes that must - /// not treat these as concrete Nat values. - /// - /// Replaces a name-based `is_const_named(id, &["Nat.rec", "Nat.casesOn", - /// "BitVec.toNat"])` whose alpha-twin display names (e.g. `Lean.RBColor.rec` - /// for `Bool.rec`) silently bypass the check under canonical hashing. - fn is_nat_stuck_recursor_addr(&self, addr: &Address) -> bool { - let Some(primitives) = self.optional_primitives() else { - return false; - }; - *addr == primitives.nat_rec.addr - || *addr == primitives.nat_cases_on.addr - || *addr == primitives.bit_vec_to_nat.addr - } - - fn try_reduce_nat_predicate( - &mut self, - addr: &Address, - args: &[KExpr], - ) -> Result>, TcError> { - if self.optional_primitives().is_none() { - return Ok(None); - } - let Some(wa) = self.whnf_prim_arg(&args[0])? else { - return Ok(None); - }; - let Some(a_val) = extract_nat_lit(&wa, self.primitives()?) else { - return Ok(None); - }; - let Some(wb) = self.whnf_prim_arg(&args[1])? else { - return Ok(None); - }; - let Some(b_val) = extract_nat_lit(&wb, self.primitives()?) else { - return Ok(None); - }; - let decision = if *addr == self.primitives()?.nat_beq.addr { - a_val == b_val - } else { - a_val <= b_val - }; - Ok(Some(self.nat_predicate_bool_result(decision, args)?)) - } - - fn nat_predicate_bool_result( - &mut self, - decision: bool, - args: &[KExpr], - ) -> Result, TcError> { - Ok({ - let bool_id = if decision { - self.primitives()?.bool_true.clone() - } else { - self.primitives()?.bool_false.clone() - }; - let mut result = self.intern(KExpr::cnst(bool_id, Box::new([]))); - for arg in args.iter().skip(2) { - result = self.intern(KExpr::app(result, arg.clone())); - } - result - }) - } - - /// A shallow Nat evaluator for bounded native helpers. - /// - /// This is intentionally not used by `Nat.beq`/`Nat.ble` primitive - /// reduction; those follow Lean and only compare WHNF'd literal-extension - /// arguments. BitVec helpers use this narrower evaluator to avoid forcing - /// large recursive Nat models when only a bounded width is useful. - fn try_eval_nat_value_for_pred( - &mut self, - e: &KExpr, - ) -> Result, TcError> { - self.try_eval_nat_value_for_pred_core(e, 0) - } - - fn try_eval_nat_value_for_pred_core( - &mut self, - e: &KExpr, - depth: u8, - ) -> Result, TcError> { - if self.optional_primitives().is_none() { - return Ok(None); - } - const MAX_PRED_NAT_EVAL_DEPTH: u8 = 64; - if depth >= MAX_PRED_NAT_EVAL_DEPTH { - return Ok(None); - } - if let Some(n) = extract_nat_lit(e, self.primitives()?) { - return Ok(Some(n.clone())); - } - - if self.is_stuck_nat_predicate_probe(e) { - return Ok(None); - } - - let (head, args) = collect_app_spine(e); - match head.data() { - ExprData::Const(id, _, _) => { - if id.addr == self.primitives()?.nat_succ.addr && args.len() == 1 { - let Some(pred) = - self.try_eval_nat_value_for_pred_core(&args[0], depth + 1)? - else { - return Ok(None); - }; - return Ok(Some(Nat(pred.0 + 1u64))); - } - if id.addr == self.primitives()?.nat_pred.addr && args.len() == 1 { - let Some(n) = - self.try_eval_nat_value_for_pred_core(&args[0], depth + 1)? - else { - return Ok(None); - }; - let result = if n.0 == num_bigint::BigUint::ZERO { - Nat(num_bigint::BigUint::ZERO) - } else { - Nat(n.0 - 1u64) - }; - return Ok(Some(result)); - } - if self.is_nat_bin_arith_addr(&id.addr) && args.len() == 2 { - let Some(a) = - self.try_eval_nat_value_for_pred_core(&args[0], depth + 1)? - else { - return Ok(None); - }; - let Some(b) = - self.try_eval_nat_value_for_pred_core(&args[1], depth + 1)? - else { - return Ok(None); - }; - return Ok(compute_nat_bin(&id.addr, self.primitives()?, &a, &b)); - } - }, - ExprData::Var(..) - | ExprData::FVar(..) - | ExprData::Sort(..) - | ExprData::Lam(..) - | ExprData::All(..) - | ExprData::Str(..) - | ExprData::Nat(..) => return Ok(None), - ExprData::App(..) | ExprData::Let(..) | ExprData::Prj(..) => {}, - } - - let w = self.whnf(e)?; - self.dump_nat_trace("pred-arg-whnf", &w); - if let Some(n) = extract_nat_value(&w, self.primitives()?) { - return Ok(Some(n)); - } - if &w == e { - return Ok(None); - } - self.try_eval_nat_value_for_pred_core(&w, depth + 1) - } - - fn is_stuck_nat_predicate_probe(&self, e: &KExpr) -> bool { - let Some(primitives) = self.optional_primitives() else { - return false; - }; - let head = app_head(e); - match head.data() { - ExprData::Const(id, _, _) => { - self.is_nat_bin_pred_addr(&id.addr) - || self.is_nat_stuck_recursor_addr(&id.addr) - }, - ExprData::Prj(id, _, val, _) => { - if id.addr == primitives.fin.addr { - return true; - } - let val_head = app_head(val); - matches!( - val_head.data(), - ExprData::Const(val_id, _, _) - if self.is_nat_stuck_recursor_addr(&val_id.addr) - ) - }, - _ => false, - } - } - - /// Native Nat.decLe/decEq/decLt reduction. - /// - /// Intercepts `Nat.decLe n m`, `Nat.decEq n m`, `Nat.decLt n m` when both - /// arguments are Nat literals. Computes the boolean result natively and - /// constructs the appropriate `Decidable.isTrue prop proof` or - /// `Decidable.isFalse prop proof`. - /// - /// Constructors in the kernel are fully explicit: - /// `Decidable.isTrue : (p : Prop) → p → Decidable p` - /// `Decidable.isFalse : (p : Prop) → (p → False) → Decidable p` - /// so the proposition `p` must be supplied as the first argument. - /// - /// Proof terms: - /// - decLe true: `Decidable.isTrue prop (Nat.le_of_ble_eq_true n m (Eq.refl.{1} Bool Bool.true))` - /// - decEq true: `Decidable.isTrue prop (Nat.eq_of_beq_eq_true n m (Eq.refl.{1} Bool Bool.true))` - /// - decEq false: `Decidable.isFalse prop (Nat.ne_of_beq_eq_false n m (Eq.refl.{1} Bool Bool.false))` - /// - decLe false: falls through to delta (proof requires `False` primitive not yet available) - /// - decLt n m: delegates to decLe (n+1) m - pub(super) fn try_reduce_decidable( - &mut self, - e: &KExpr, - ) -> Result>, TcError> { - if !self.takes_extra(ExtraReduction::Decidable) - || self.optional_primitives().is_none() - { - return Ok(None); - } - let (head, args) = collect_app_spine(e); - let addr = match head.data() { - ExprData::Const(id, _, _) => id.addr.clone(), - _ => return Ok(None), - }; - - let p = self.primitives()?; - let is_dec_le = addr == p.nat_dec_le.addr; - let is_dec_eq = addr == p.nat_dec_eq.addr; - let is_dec_lt = addr == p.nat_dec_lt.addr; - let is_int_dec_le = addr == p.int_dec_le.addr; - let is_int_dec_eq = addr == p.int_dec_eq.addr; - let is_int_dec_lt = addr == p.int_dec_lt.addr; - if is_int_dec_le || is_int_dec_eq || is_int_dec_lt { - return self.try_normalize_int_decidable(&addr, &args); - } - if !is_dec_le && !is_dec_eq && !is_dec_lt { - return Ok(None); - } - if args.len() < 2 { - return Ok(None); - } - - let wa = self.whnf(&args[0])?; - let wb = self.whnf(&args[1])?; - let a_val = match extract_nat_value(&wa, self.primitives()?) { - Some(v) => v, - None => return Ok(None), - }; - let b_val = match extract_nat_value(&wb, self.primitives()?) { - Some(v) => v, - None => return Ok(None), - }; - - // S5: Eq.refl is universe-polymorphic: @Eq.refl.{u}. - // For Bool : Type = Sort 1, we need u = 1 = Succ(Zero). - let u1 = KUniv::succ(KUniv::zero()); - - // decLt n m → decLe (n+1) m - if is_dec_lt { - let succ_a = Nat(&a_val.0 + 1u64); - let succ_a_addr = Address::hash(&succ_a.to_le_bytes()); - let succ_a_expr = self.intern(KExpr::nat(succ_a, succ_a_addr)); - // Build: Nat.decLe (n+1) m - let dec_le_const = self.intern(KExpr::cnst( - self.primitives()?.nat_dec_le.clone(), - Box::new([]), - )); - let mut result = self.intern(KExpr::app(dec_le_const, succ_a_expr)); - result = self.intern(KExpr::app(result, args[1].clone())); - for arg in args.iter().skip(2) { - result = self.intern(KExpr::app(result, arg.clone())); - } - // Recursively reduce the decLe - return Ok(Some(result)); - } - - // Extract the proposition from the type of `e`. - // `e : Decidable prop` → we need `prop` as the first constructor argument. - // Use infer_only to avoid def-eq checks (safe within WHNF). - let prop = match self.with_infer_only(|tc| tc.infer(e)) { - Ok(e_ty) => { - let e_ty_whnf = self.whnf(&e_ty)?; - let (_, type_args) = collect_app_spine(&e_ty_whnf); - match type_args.into_iter().next() { - Some(p) => p, - None => return Ok(None), // not `Decidable prop` — bail - } - }, - Err(_) => return Ok(None), // inference failed — bail to delta - }; - - let (b_result, proof_true_fn, proof_false_fn) = if is_dec_le { - ( - a_val <= b_val, - &self.primitives()?.nat_le_of_ble_eq_true, - &self.primitives()?.nat_not_le_of_not_ble_eq_true, - ) - } else { - // is_dec_eq - ( - a_val == b_val, - &self.primitives()?.nat_eq_of_beq_eq_true, - &self.primitives()?.nat_ne_of_beq_eq_false, - ) - }; - let proof_true_fn = proof_true_fn.clone(); - let proof_false_fn = proof_false_fn.clone(); - - let result_expr = if b_result { - // Decidable.isTrue prop (proof_fn n m (Eq.refl.{1} Bool Bool.true)) - let eq_refl = self.intern(KExpr::cnst( - self.primitives()?.eq_refl.clone(), - Box::new([u1.clone()]), - )); - let bool_ty = self.intern(KExpr::cnst( - self.primitives()?.bool_type.clone(), - Box::new([]), - )); - let bool_true = self.intern(KExpr::cnst( - self.primitives()?.bool_true.clone(), - Box::new([]), - )); - let refl_proof = self.intern(KExpr::app(eq_refl, bool_ty)); - let refl_proof = self.intern(KExpr::app(refl_proof, bool_true)); - - // Build: proof_fn n m refl_proof - let proof_const = - self.intern(KExpr::cnst(proof_true_fn.clone(), Box::new([]))); - let proof = self.intern(KExpr::app(proof_const, args[0].clone())); - let proof = self.intern(KExpr::app(proof, args[1].clone())); - let proof = self.intern(KExpr::app(proof, refl_proof)); - - // Build: Decidable.isTrue prop proof - let is_true = self.intern(KExpr::cnst( - self.primitives()?.decidable_is_true.clone(), - Box::new([]), - )); - let r = self.intern(KExpr::app(is_true, prop)); - self.intern(KExpr::app(r, proof)) - } else if is_dec_eq { - // Decidable.isFalse prop (Nat.ne_of_beq_eq_false n m (Eq.refl.{1} Bool Bool.false)) - let eq_refl = self.intern(KExpr::cnst( - self.primitives()?.eq_refl.clone(), - Box::new([u1.clone()]), - )); - let bool_ty = self.intern(KExpr::cnst( - self.primitives()?.bool_type.clone(), - Box::new([]), - )); - let bool_false = self.intern(KExpr::cnst( - self.primitives()?.bool_false.clone(), - Box::new([]), - )); - let refl_proof = self.intern(KExpr::app(eq_refl, bool_ty)); - let refl_proof = self.intern(KExpr::app(refl_proof, bool_false)); - - let proof_const = - self.intern(KExpr::cnst(proof_false_fn.clone(), Box::new([]))); - let proof = self.intern(KExpr::app(proof_const, args[0].clone())); - let proof = self.intern(KExpr::app(proof, args[1].clone())); - let proof = self.intern(KExpr::app(proof, refl_proof)); - - // Build: Decidable.isFalse prop proof - let is_false = self.intern(KExpr::cnst( - self.primitives()?.decidable_is_false.clone(), - Box::new([]), - )); - let r = self.intern(KExpr::app(is_false, prop)); - self.intern(KExpr::app(r, proof)) - } else { - // decLe false: the proof requires `Bool.noConfusion.{0} False Bool.false Bool.true` - // which needs a `False` primitive not yet registered. Fall through to - // delta reduction which correctly unfolds Nat.decLe to its definition body. - return Ok(None); - }; - - let mut result = result_expr; - for arg in args.iter().skip(2) { - result = self.intern(KExpr::app(result, arg.clone())); - } - Ok(Some(result)) - } - - fn try_normalize_int_decidable( - &mut self, - addr: &Address, - args: &[KExpr], - ) -> Result>, TcError> { - if self.optional_primitives().is_none() { - return Ok(None); - } - if args.len() < 2 { - return Ok(None); - } - - let wa = self.whnf(&args[0])?; - let wb = self.whnf(&args[1])?; - let Some(a_val) = extract_int_lit(&wa, self.primitives()?) else { - return Ok(None); - }; - let Some(b_val) = extract_int_lit(&wb, self.primitives()?) else { - return Ok(None); - }; - - let a = intern_int_lit(self, a_val)?; - let b = intern_int_lit(self, b_val)?; - if a.hash_key() == args[0].hash_key() && b.hash_key() == args[1].hash_key() - { - return Ok(None); - } - - let head_id = if *addr == self.primitives()?.int_dec_eq.addr { - self.primitives()?.int_dec_eq.clone() - } else if *addr == self.primitives()?.int_dec_le.addr { - self.primitives()?.int_dec_le.clone() - } else { - self.primitives()?.int_dec_lt.clone() - }; - let head = self.intern(KExpr::cnst(head_id, Box::new([]))); - let mut result = self.intern(KExpr::app(head, a)); - result = self.intern(KExpr::app(result, b)); - for arg in args.iter().skip(2) { - result = self.intern(KExpr::app(result, arg.clone())); - } - Ok(Some(result)) - } - - /// Quotient reduction (Quot.lift, Quot.ind). - fn try_quot_reduce( - &mut self, - e: &KExpr, - ) -> Result>, TcError> { - if self.optional_primitives().is_none() { - return Ok(None); - } - let (head, args) = collect_app_spine(e); - let addr = match head.data() { - ExprData::Const(id, _, _) => id.addr.clone(), - _ => return Ok(None), - }; - - // Quot.lift: 6 args, f at 3, major at 5 - // Quot.ind: 5 args, f at 3, major at 4 - let (f_idx, major_idx) = if addr == self.primitives()?.quot_lift.addr { - if args.len() < 6 { - return Ok(None); - } - (3usize, 5usize) - } else if addr == self.primitives()?.quot_ind.addr { - if args.len() < 5 { - return Ok(None); - } - (3usize, 4usize) - } else { - return Ok(None); - }; - - let major_whnf = self.whnf(&args[major_idx])?; - let (mk_head, mk_args) = collect_app_spine(&major_whnf); - let mk_addr = match mk_head.data() { - ExprData::Const(id, _, _) => &id.addr, - _ => return Ok(None), - }; - if *mk_addr != self.primitives()?.quot_ctor.addr { - return Ok(None); - } - - // Quot.mk has exactly 3 args: (α, r, a). Value is the last. - if mk_args.len() != 3 { - return Ok(None); - } - let quot_val = mk_args[2].clone(); - - let mut result = self.intern(KExpr::app(args[f_idx].clone(), quot_val)); - for arg in args.iter().skip(major_idx + 1) { - result = self.intern(KExpr::app(result, arg.clone())); - } - Ok(Some(result)) - } - - // ----------------------------------------------------------------------- - // BitVec reduction - // ----------------------------------------------------------------------- - - /// Reduce the small BitVec fragment that is definitionally Nat-backed: - /// - `BitVec.toNat (BitVec.ofNat w n)` reduces to `n % 2^w` - /// - `BitVec.ult w x y` reduces by evaluating `x.toNat < y.toNat` - /// - /// Both heads are functions, so every argument they depend on is read. - /// Class methods (`OfNat.ofNat (BitVec w) n inst`, `@LT.lt (BitVec w) inst - /// x y` under `decide`) are not recognized: their value is the instance's, - /// which a prover chooses, so they reduce by unfolding the instance. - fn try_reduce_bitvec( - &mut self, - e: &KExpr, - ) -> Result>, TcError> { - if !self.takes_extra(ExtraReduction::BitVec) - || self.optional_primitives().is_none() - { - return Ok(None); - } - let (head, args) = collect_app_spine(e); - let ExprData::Const(id, _, _) = head.data() else { - return Ok(None); - }; - - if id.addr == self.primitives()?.bit_vec_to_nat.addr && args.len() >= 2 { - if !self.prims.admits(Role::BitVecToNat) { - return Ok(None); - } - if self.prims.follows_safety_tier() { - let Some((width, _)) = self.bitvec_of_nat_args(&args[1]) else { - return Ok(None); - }; - if !self.is_def_eq(&args[0], &width)? { - return Ok(None); - } - } - if let Some(result) = self.try_reduce_bitvec_to_nat(&args[1])? { - return Ok(Some(self.finish_app_result(result, &args, 2))); - } - return Ok(None); - } - - if id.addr == self.primitives()?.bit_vec_ult.addr && args.len() >= 3 { - if !self.prims.admits(Role::BitVecUlt) - || !self.prims.admits(Role::BitVecToNat) - { - return Ok(None); - } - if self.prims.follows_safety_tier() { - // The specification covers matching ofNat operands. A bound BitVec - // anchor need not make ofNat surjective, or its width injective. - for operand in &args[1..3] { - let Some((width, _)) = self.bitvec_of_nat_args(operand) else { - return Ok(None); - }; - if !self.is_def_eq(&args[0], &width)? { - return Ok(None); - } - } - } - if let Some(result) = - self.try_reduce_bitvec_ult(&args[0], &args[1], &args[2])? - { - return Ok(Some(self.finish_app_result(result, &args, 3))); - } - return Ok(None); - } - - Ok(None) - } - - fn try_reduce_bitvec_ult( - &mut self, - width: &KExpr, - lhs: &KExpr, - rhs: &KExpr, - ) -> Result>, TcError> { - if self.optional_primitives().is_none() { - return Ok(None); - } - let lhs_nat = self.bitvec_to_nat_expr(width, lhs)?; - let rhs_nat = self.bitvec_to_nat_expr(width, rhs)?; - let rhs_nat_whnf = self.whnf(&rhs_nat)?; - if let Some(rhs_val) = extract_nat_value(&rhs_nat_whnf, self.primitives()?) - { - if rhs_val.0 == num_bigint::BigUint::ZERO { - let result = self.intern(KExpr::cnst( - self.primitives()?.bool_false.clone(), - Box::new([]), - )); - return Ok(Some(result)); - } - - let lhs_nat_whnf = self.whnf(&lhs_nat)?; - if let Some(lhs_val) = - extract_nat_value(&lhs_nat_whnf, self.primitives()?) - { - let result_id = if lhs_val.0 < rhs_val.0 { - self.primitives()?.bool_true.clone() - } else { - self.primitives()?.bool_false.clone() - }; - let result = self.intern(KExpr::cnst(result_id, Box::new([]))); - return Ok(Some(result)); - } - } - - // `BitVec.ult x y` is definitionally `decide (x.toNat < y.toNat)`. - // Kernel Nat LT reduces through `Nat.ble (Nat.succ x.toNat) y.toNat`. - let lhs_succ = self.nat_succ_n(lhs_nat, 1)?; - let ble = self - .intern(KExpr::cnst(self.primitives()?.nat_ble.clone(), Box::new([]))); - let cmp_lhs = self.intern(KExpr::app(ble, lhs_succ)); - let cmp = self.intern(KExpr::app(cmp_lhs, rhs_nat)); - let result = self.whnf(&cmp)?; - if self.bool_lit_value(&result).is_some() { - Ok(Some(result)) - } else { - Ok(None) - } - } - - fn bitvec_to_nat_expr( - &mut self, - width: &KExpr, - value: &KExpr, - ) -> Result, TcError> { - if let Some(result) = self.try_reduce_bitvec_to_nat(value)? { - return Ok(result); - } - - let head = self.intern(KExpr::cnst( - self.primitives()?.bit_vec_to_nat.clone(), - Box::new([]), - )); - let with_width = self.intern(KExpr::app(head, width.clone())); - Ok(self.intern(KExpr::app(with_width, value.clone()))) - } - - fn try_reduce_bitvec_to_nat( - &mut self, - value: &KExpr, - ) -> Result>, TcError> { - if self.optional_primitives().is_none() { - return Ok(None); - } - let Some((width, n_expr)) = self.bitvec_of_nat_args(value) else { - return Ok(None); - }; - - let n_whnf = self.whnf(&n_expr)?; - let Some(n) = extract_nat_value(&n_whnf, self.primitives()?) else { - return Ok(None); - }; - - if n.0 == num_bigint::BigUint::ZERO { - return Ok(Some(self.nat_literal(0))); - } - - let width_val = self.try_eval_nat_value_for_pred(&width)?; - let Some(width) = width_val.and_then(|w| w.to_u64()) else { - return Ok(None); - }; - - const REDUCE_BITVEC_WIDTH_MAX: u64 = 1 << 24; - if width > REDUCE_BITVEC_WIDTH_MAX { - return Ok(None); - } - - // `width` was bounded above by `REDUCE_BITVEC_WIDTH_MAX = 1 << 24`, so - // it always fits in `usize` on every supported target. - let width_usize = usize::try_from(width).unwrap_or(usize::MAX); - let modulus = num_bigint::BigUint::from(1u64) << width_usize; - let result = Nat(n.0 % modulus); - Ok(Some(self.nat_expr_from_value(result))) - } - - /// `(w, n)` of `BitVec.ofNat w n`. The numeral `@OfNat.ofNat (BitVec w) n - /// inst` is not read here: its value is `inst`'s. - fn bitvec_of_nat_args(&self, e: &KExpr) -> Option<(KExpr, KExpr)> { - let (head, args) = collect_app_spine(e); - let ExprData::Const(id, _, _) = head.data() else { - return None; - }; - if id.addr == self.optional_primitives()?.bit_vec_of_nat.addr - && args.len() == 2 - { - Some((args[0].clone(), args[1].clone())) - } else { - None - } - } - - fn bool_lit_value(&self, e: &KExpr) -> Option { - let ExprData::Const(id, _, _) = e.data() else { - return None; - }; - if id.addr == self.optional_primitives()?.bool_true.addr { - Some(true) - } else if id.addr == self.optional_primitives()?.bool_false.addr { - Some(false) - } else { - None - } - } - - fn finish_app_result( - &mut self, - mut result: KExpr, - args: &[KExpr], - consumed: usize, - ) -> KExpr { - for arg in args.iter().skip(consumed) { - result = self.intern(KExpr::app(result, arg.clone())); - } - result - } - - // ----------------------------------------------------------------------- - // String primitive reduction - // ----------------------------------------------------------------------- - - /// Native reductions for string primitives on literal arguments. - /// - /// Read-only ops (`String.back`, `String.utf8ByteSize`, - /// `String.toByteArray` of `""`) mirror the accelerated Nat ops. The - /// value-building ops keep evaluator-grade normalization out of the - /// structural ByteArray/UTF-8 model, which grinds per character - /// through `Char.ofNat` validity `dite`s and `List.utf8Encode`: - /// - /// - `String.ofList`/`String.mk` applied to a literal char list - /// collapses to a `Str` literal — the exact inverse of - /// `str_lit_to_constructor`. - /// - `String.append` on two literals → the concatenated literal. - /// - `String.decEq` on two equal literals → `Decidable.isTrue` with an - /// `Eq.refl` witness (unequal literals fall back to the structural - /// path: core Lean has no `String` analog of - /// `Nat.ne_of_beq_eq_false` to witness `isFalse` with). - /// - /// Every rule is guarded to be defeq-indistinguishable from the - /// structural reduction it replaces: char codepoints must be valid - /// Unicode scalar values (`Char.ofNat` maps invalid ones to `'\0'`, so - /// invalid literals must keep reducing structurally), and stuck or - /// open arguments leave the term untouched. - pub(super) fn try_reduce_string( - &mut self, - e: &KExpr, - ) -> Result>, TcError> { - if !self.takes_extra(ExtraReduction::String) - || self.optional_primitives().is_none() - { - return Ok(None); - } - let (head, args) = collect_app_spine(e); - let ExprData::Const(id, _, _) = head.data() else { - return Ok(None); - }; - let addr = &id.addr; - - // String.ofList / String.mk: collapse a literal char-list argument. - // The two names share one canonical address (see `PrimAddrs`) but - // are distinct LEON addresses, so check both. - if *addr == self.primitives()?.string_of_list.addr - || *addr == self.primitives()?.string_mk.addr - { - if !self.prims.admits(Role::StringOfList) { - return Ok(None); - } - if args.len() != 1 { - return Ok(None); - } - let mut s = String::new(); - if !self.walk_literal_char_list(&args[0], &mut s)? { - return Ok(None); - } - let blob_addr = Address::hash(s.as_bytes()); - return Ok(Some(self.intern(KExpr::str(s, blob_addr)))); - } - - if *addr == self.primitives()?.string_append.addr { - if !self.prims.admits(Role::StringAppend) { - return Ok(None); - } - if args.len() != 2 { - return Ok(None); - } - let Some((sa, sb)) = self.two_string_lit_args(&args[0], &args[1])? else { - return Ok(None); - }; - let joined = format!("{sa}{sb}"); - let blob_addr = Address::hash(joined.as_bytes()); - return Ok(Some(self.intern(KExpr::str(joined, blob_addr)))); - } - - if *addr == self.primitives()?.string_dec_eq.addr { - if !self.prims.admits(Role::StringDecEq) { - return Ok(None); - } - if args.len() != 2 { - return Ok(None); - } - return self.try_reduce_string_dec_eq(e, &args); - } - - let is_back = *addr == self.primitives()?.string_back.addr; - let is_utf8_byte_size = - *addr == self.primitives()?.string_utf8_byte_size.addr; - let is_to_byte_array = - *addr == self.primitives()?.string_to_byte_array.addr; - if (!is_back && !is_utf8_byte_size && !is_to_byte_array) || args.len() != 1 - { - return Ok(None); - } - let role = if is_back { - Role::StringBack - } else if is_utf8_byte_size { - Role::StringUtf8ByteSize - } else { - Role::StringToByteArray - }; - if !self.prims.admits(role) { - return Ok(None); - } - - let s = match args[0].data() { - ExprData::Str(s, _, _) => s, - _ => return Ok(None), - }; - if is_utf8_byte_size { - let n = Nat::from(s.len() as u64); - let addr = Address::hash(&n.to_le_bytes()); - return Ok(Some(self.intern(KExpr::nat(n, addr)))); - } - if is_to_byte_array { - if s.is_empty() { - return Ok(Some(self.intern(KExpr::cnst( - self.primitives()?.byte_array_empty.clone(), - Box::new([]), - )))); - } - return Ok(None); - } - - let codepoint = s.chars().last().map_or(65u32, u32::from); - Ok(Some(self.char_of_nat_expr(u64::from(codepoint))?)) - } - - /// WHNF both arguments with the shared fuel-capped policy and require - /// both to be `Str` literals. - fn two_string_lit_args( - &mut self, - a: &KExpr, - b: &KExpr, - ) -> Result, TcError> { - let Some(wa) = self.whnf_prim_arg(a)? else { - return Ok(None); - }; - let ExprData::Str(sa, _, _) = wa.data() else { - return Ok(None); - }; - let sa = sa.clone(); - let Some(wb) = self.whnf_prim_arg(b)? else { - return Ok(None); - }; - let ExprData::Str(sb, _, _) = wb.data() else { - return Ok(None); - }; - Ok(Some((sa, sb.clone()))) - } - - /// `String.decEq` on two EQUAL literals → `Decidable.isTrue prop - /// (Eq.refl.{1} String lit)`. Mirrors `try_reduce_decidable`'s Nat - /// shape: the proposition is recovered from the inferred type - /// (`Decidable prop`), and the witness is proof-irrelevant, so any - /// well-typed inhabitant is defeq-indistinguishable from the one - /// structural reduction would build. - fn try_reduce_string_dec_eq( - &mut self, - e: &KExpr, - args: &[KExpr], - ) -> Result>, TcError> { - if self.optional_primitives().is_none() { - return Ok(None); - } - let Some((sa, sb)) = self.two_string_lit_args(&args[0], &args[1])? else { - return Ok(None); - }; - if sa != sb { - return Ok(None); - } - - // Recover `prop` from `e : Decidable prop` (see try_reduce_decidable). - let prop = match self.with_infer_only(|tc| tc.infer(e)) { - Ok(e_ty) => { - let e_ty_whnf = self.whnf(&e_ty)?; - let (_, type_args) = collect_app_spine(&e_ty_whnf); - match type_args.into_iter().next() { - Some(p) => p, - None => return Ok(None), - } - }, - Err(_) => return Ok(None), - }; - - // Eq.refl.{1} String — String : Type = Sort 1, so u = 1. - let u1 = KUniv::succ(KUniv::zero()); - let eq_refl = self - .intern(KExpr::cnst(self.primitives()?.eq_refl.clone(), Box::new([u1]))); - let string_ty = - self.intern(KExpr::cnst(self.primitives()?.string.clone(), Box::new([]))); - let lit_addr = Address::hash(sa.as_bytes()); - let lit = self.intern(KExpr::str(sa, lit_addr)); - let refl = self.intern(KExpr::app(eq_refl, string_ty)); - let refl = self.intern(KExpr::app(refl, lit)); - - let is_true = self.intern(KExpr::cnst( - self.primitives()?.decidable_is_true.clone(), - Box::new([]), - )); - let result = self.intern(KExpr::app(is_true, prop)); - Ok(Some(self.intern(KExpr::app(result, refl)))) - } - - /// Walk a `List Char` spine whose nodes and elements WHNF to literal - /// constructor form, appending decoded codepoints to `out`. Elements - /// must be native char values (`Char.ofNat `, valid scalar) — - /// the form `str_lit_to_constructor` builds and whnf keeps stuck. - /// Returns `Ok(false)` on any non-literal node (stuck tail, open - /// element, invalid codepoint): the caller falls back to the - /// structural path. - fn walk_literal_char_list( - &mut self, - list: &KExpr, - out: &mut String, - ) -> Result> { - if self.optional_primitives().is_none() { - return Ok(false); - } - let mut cur = list.clone(); - loop { - let Some(w) = self.whnf_prim_arg(&cur)? else { - return Ok(false); - }; - let (head, args) = collect_app_spine(&w); - let ExprData::Const(id, _, _) = head.data() else { - return Ok(false); - }; - // List.nil.{u} α - if id.addr == self.primitives()?.list_nil.addr { - return Ok(args.len() == 1); - } - // List.cons.{u} α head tail - if id.addr != self.primitives()?.list_cons.addr || args.len() != 3 { - return Ok(false); - } - let elem = match self.char_lit_value(&args[1]) { - Some(c) => Some(c), - None => match self.whnf_prim_arg(&args[1])? { - Some(we) => self.char_lit_value(&we), - None => None, - }, - }; - let Some(c) = elem else { - return Ok(false); - }; - out.push(c); - cur = args[2].clone(); - } - } - - /// Recognize the kernel's native char-value form: `Char.ofNat - /// ` where the literal is a valid Unicode scalar value. - /// (`char::from_u32` rejects exactly the complement of - /// `Nat.isValidChar`: surrogates and codepoints above `0x10FFFF`.) - /// Purely syntactic — no reduction, and no spine allocation: this - /// runs on every main-whnf-loop iteration. - pub(super) fn char_lit_value(&self, e: &KExpr) -> Option { - // Exactly `App(Const(Char.ofNat), Nat-lit)` — a nested App head - // (over-application) or any other shape is not a char value. - let ExprData::App(f, arg, _) = e.data() else { - return None; - }; - let ExprData::Const(id, _, _) = f.data() else { - return None; - }; - if id.addr != self.optional_primitives()?.char_of_nat.addr { - return None; - } - let ExprData::Nat(n, _, _) = arg.data() else { - return None; - }; - let cp = n.to_u64()?; - u32::try_from(cp).ok().and_then(char::from_u32) - } - - fn char_of_nat_expr(&mut self, n: u64) -> Result, TcError> { - Ok({ - let char_of_nat = self.intern(KExpr::cnst( - self.primitives()?.char_of_nat.clone(), - Box::new([]), - )); - let nat_val = Nat::from(n); - let nat_addr = Address::hash(&nat_val.to_le_bytes()); - let nat_lit = self.intern(KExpr::nat(nat_val, nat_addr)); - self.intern(KExpr::app(char_of_nat, nat_lit)) - }) - } -} - -// --------------------------------------------------------------------------- -// Free-standing helpers for nat reduction -// --------------------------------------------------------------------------- - -use super::primitive::Primitives; - -/// Zero constant shared across `extract_nat_lit` calls. -static NAT_ZERO_LITERAL: LazyLock = - LazyLock::new(|| Nat(num_bigint::BigUint::ZERO)); - -/// Extract a nat value from a literal or `Nat.zero` constructor. -/// -/// Matches both `Nat(n)` literals and the `Nat.zero` constructor constant, -/// mirroring C++ `is_nat_lit_ext` and lean4lean `rawNatLitExt?`. After iota -/// reduction, `Nat.zero` can appear as `Const(Nat.zero, [])` which must be -/// recognized for native Nat operations to fire. -fn extract_nat_lit<'a, M: KernelMode>( - e: &'a KExpr, - prims: &Primitives, -) -> Option<&'a Nat> { - match e.data() { - ExprData::Nat(val, _, _) => Some(val), - ExprData::Const(id, _, _) if id.addr == prims.nat_zero.addr => { - Some(&NAT_ZERO_LITERAL) - }, - _ => None, - } -} - -/// Extract a Nat value from either literal form or a constructor numeral. -/// -/// Iota reduction on `Nat` literals can expose the matched value as -/// `Nat.succ ` inside branch bodies. Some non-Nat -/// primitive helpers recover that value here before deciding whether a -/// surrounding native reduction can proceed. -fn extract_nat_value( - e: &KExpr, - prims: &Primitives, -) -> Option { - if let Some(n) = extract_nat_lit(e, prims) { - return Some(n.clone()); - } - - let (head, args) = collect_app_spine(e); - let ExprData::Const(id, _, _) = head.data() else { - return None; - }; - if id.addr != prims.nat_succ.addr || args.len() != 1 { - return None; - } - let pred = extract_nat_value(&args[0], prims)?; - Some(Nat(pred.0 + 1u64)) -} - -fn gcd_biguint( - a: &num_bigint::BigUint, - b: &num_bigint::BigUint, -) -> num_bigint::BigUint { - let mut x = a.clone(); - let mut y = b.clone(); - while y != num_bigint::BigUint::ZERO { - let t = y.clone(); - y = &x % &y; - x = t; - } - x -} - -/// Compute a binary nat operation. Returns `None` if the operation can't be -/// computed (e.g., exponent too large) — caller leaves the expression unreduced. -fn compute_nat_bin( - addr: &Address, - p: &Primitives, - a: &Nat, - 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; - // ~quadratic for mul/div/mod/gcd, linear for add/sub/bitwise/shift). Invisible - // to hb/subst/def_eq, so it's the missing feature for FFT-cost prediction. - { - let la = (a.0.bits().max(1)).div_ceil(64); - let lb = (b.0.bits().max(1)).div_ceil(64); - let work = if *addr == p.nat_mul.addr - || *addr == p.nat_div.addr - || *addr == p.nat_mod.addr - || *addr == p.nat_gcd.addr - { - la.saturating_mul(lb) - } else if *addr == p.nat_pow.addr { - let lo = la.max(lb); - lo.saturating_mul(lo) - } else { - la.max(lb) - }; - crate::profile::bump_nat_arith(work); - } - let r = if *addr == p.nat_add.addr { - &a.0 + &b.0 - } else if *addr == p.nat_sub.addr { - if a.0 >= b.0 { &a.0 - &b.0 } else { zero } - } else if *addr == p.nat_mul.addr { - &a.0 * &b.0 - } else if *addr == p.nat_div.addr { - if b.0 == zero { zero } else { &a.0 / &b.0 } - } else if *addr == p.nat_mod.addr { - if b.0 == zero { a.0.clone() } else { &a.0 % &b.0 } - } else if *addr == p.nat_pow.addr { - // Limit matches C++ kernel `ReducePowMaxExp` and lean4lean `reducePowMaxExp`. - 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 => { - // 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 { - gcd_biguint(&a.0, &b.0) - } else if *addr == p.nat_land.addr { - &a.0 & &b.0 - } else if *addr == p.nat_lor.addr { - &a.0 | &b.0 - } else if *addr == p.nat_xor.addr { - &a.0 ^ &b.0 - } else if *addr == p.nat_shift_left.addr { - 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 - } else { - return None; - }; - Some(Nat(r)) -} - -// --------------------------------------------------------------------------- -// Int literal helpers -// --------------------------------------------------------------------------- -// -// Lean's C++ kernel has no parallel `reduce_int` (only `reduce_nat` + -// `reduce_native`). Instead, it reduces Int operations symbolically through -// `Int.rec` pattern matching on `Int.ofNat` / `Int.negSucc`, cascading into -// native Nat ops. For expressions like `Int.bmod (-1) (2^32)`, that chain -// goes through `Decidable.rec (LT.lt Int ...) ...` which in turn requires -// reducing `Int.decLt = decNonneg (b - a)` through `Int.sub` / `Int.subNatNat` -// etc. — tractable for Lean's kernel but a known source of stuck reductions -// when any link of the chain is missing. Lean's stdlib mitigates with -// `Int.ble'` / `Int.blt'` "for kernel reduction" hand-crafted `noncomputable` -// defs, but they still cascade through delta+iota. -// -// Our kernel takes the direct route: if the head of an app-spine is a known -// Int primitive and all arguments whnf to literals (Int, Nat, or Bool), we -// compute the result natively and short-circuit the whole delta+iota chain. - -use num_bigint::BigInt; - -/// An Int literal in canonical kernel constructor form. -/// -/// Lean's canonical form is `Int.ofNat n` (non-negative) or -/// `Int.negSucc n` (`= -(n+1)`, ≤ -1). We flatten both into a single -/// `BigInt` for arithmetic and re-encode via `intern_int_lit` afterwards. -type IntVal = BigInt; - -/// Extract an Int value from an app-spine whose head is `Int.ofNat` or -/// `Int.negSucc` applied to a Nat literal. Returns `None` for any other -/// shape so the caller leaves the expression unreduced for delta+iota to -/// handle. -/// -/// Callers typically pass a whnf'd expression so partially-applied -/// constructors (e.g. `Int.ofNat` with a non-literal argument) will -/// naturally be rejected here. -fn extract_int_lit( - e: &KExpr, - prims: &Primitives, -) -> Option { - let (head, args) = collect_app_spine(e); - let (head_id, _) = match head.data() { - ExprData::Const(id, us, _) => (id, us), - _ => return None, - }; - if args.len() != 1 { - return None; - } - let nat_val = extract_nat_value(&args[0], prims)?; - let n: BigInt = nat_val.0.clone().into(); - if head_id.addr == prims.int_of_nat.addr { - Some(n) // Int.ofNat n = n - } else if head_id.addr == prims.int_neg_succ.addr { - Some(-(n + BigInt::from(1))) // Int.negSucc n = -(n+1) - } else { - None - } -} - -/// Build a canonical-form Int literal expression: `Int.ofNat n` for n ≥ 0, -/// `Int.negSucc (|n| - 1)` for n < 0. Used as the return form of native -/// Int reductions so subsequent delta+iota steps see the value in its -/// ctor-headed shape (letting `decNonneg` / `Int.rec` iota-reduce in the -/// caller). -fn intern_int_lit( - tc: &mut TypeChecker<'_, M>, - v: IntVal, -) -> Result, TcError> { - use num_bigint::Sign; - let (sign, magnitude) = v.into_parts(); - let nat_val = match sign { - Sign::Minus => { - // negSucc n encodes -(n+1); shift magnitude down by 1 to get n. - // Safe: Sign::Minus implies magnitude >= 1, so subtract can't - // underflow. - Nat(magnitude - 1u32) - }, - Sign::NoSign | Sign::Plus => Nat(magnitude), - }; - let nat_addr = Address::hash(&nat_val.to_le_bytes()); - let nat_expr = tc.intern(KExpr::nat(nat_val, nat_addr)); - let ctor_id = match sign { - Sign::Minus => tc.primitives()?.int_neg_succ.clone(), - _ => tc.primitives()?.int_of_nat.clone(), - }; - let ctor = tc.intern(KExpr::cnst(ctor_id, Box::new([]))); - // With Sign::NoSign (zero) we use int_of_nat → Int.ofNat 0 = 0. - // With non-negative => Int.ofNat n. With negative => Int.negSucc (n-1). - Ok(tc.intern(KExpr::app(ctor, nat_expr))) -} - -#[cfg(test)] -mod tests { - - use super::super::constant::KConst; - use super::super::env::KEnv; - use super::super::expr::{ExprData, KExpr}; - use super::super::id::KId; - use super::super::level::KUniv; - use super::super::mode::{Anon, Meta}; - use super::super::primitive::Primitives; - use super::super::tc::TypeChecker; - use super::*; - use ix_common::address::Address; - use ix_common::env::{DefinitionSafety, ReducibilityHints}; - use ixon::constant::DefKind; - - type AE = KExpr; - type AU = KUniv; - - fn mk_addr(s: &str) -> Address { - Address::hash(s.as_bytes()) - } - fn mk_id(s: &str) -> KId { - KId::new(mk_addr(s), ()) - } - fn sort0() -> AE { - AE::sort(AU::zero()) - } - fn sort1() -> AE { - AE::sort(AU::succ(AU::zero())) - } - - /// Build a minimal env with a single definition: `id := λ x. x : Sort 0 → Sort 0` - fn env_with_id() -> KEnv { - let mut env = KEnv::new(); - let id_ty = AE::all((), (), sort0(), sort0()); // Sort 0 → Sort 0 - let id_val = AE::lam((), (), sort0(), AE::var(0, ())); // λ x. x - env.insert( - mk_id("id"), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Abbrev, - lvls: 0, - ty: id_ty, - val: id_val, - lean_all: (), - block: mk_id("id"), - }, - ); - // Opaque constant - let opaq_ty = sort0(); - let opaq_val = sort0(); - env.insert( - mk_id("opaque"), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Opaque, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Opaque, - lvls: 0, - ty: opaq_ty, - val: opaq_val, - lean_all: (), - block: mk_id("opaque"), - }, - ); - let opaque_def_ty = sort0(); - let opaque_def_val = sort1(); - env.insert( - mk_id("opaque_def"), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Opaque, - lvls: 0, - ty: opaque_def_ty, - val: opaque_def_val, - lean_all: (), - block: mk_id("opaque_def"), - }, - ); - env - } - - #[test] - fn whnf_var_identity() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - let v = AE::var(0, ()); - assert_eq!(tc.whnf(&v).unwrap(), v); - } - - #[test] - fn whnf_sort_identity() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - assert_eq!(tc.whnf(&sort0()).unwrap(), sort0()); - } - - #[test] - fn whnf_lam_identity() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - let lam = AE::lam((), (), sort0(), AE::var(0, ())); - assert_eq!(tc.whnf(&lam).unwrap(), lam); - } - - #[test] - fn whnf_beta_simple() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - // (λ x. x) a → a - let lam = AE::lam((), (), sort0(), AE::var(0, ())); - let a = AE::sort(AU::succ(AU::zero())); - let app = AE::app(lam, a.clone()); - assert_eq!(tc.whnf(&app).unwrap(), a); - } - - #[test] - fn whnf_beta_multi() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - // (λ x y. x) a b → a - let body = AE::var(1, ()); // x (de Bruijn 1, the outer binder) - let inner_lam = AE::lam((), (), sort0(), body); - let outer_lam = AE::lam((), (), sort0(), inner_lam); - let a = sort0(); - let b = sort1(); - let app = AE::app(AE::app(outer_lam, a.clone()), b); - assert_eq!(tc.whnf(&app).unwrap(), a); - } - - #[test] - fn whnf_zeta() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - // let x := Sort 0 in x → Sort 0 - let let_e = AE::let_((), sort1(), sort0(), AE::var(0, ()), true); - assert_eq!(tc.whnf(&let_e).unwrap(), sort0()); - } - - #[test] - fn whnf_delta() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - // id(Sort 0) should delta-unfold id then beta-reduce - let id_const = AE::cnst(mk_id("id"), Box::new([])); - let app = AE::app(id_const, sort0()); - 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(); - let mut tc = TypeChecker::new(&mut env); - let opaque = AE::cnst(mk_id("opaque"), Box::new([])); - // Opaque should NOT be unfolded - let result = tc.whnf(&opaque).unwrap(); - assert!(matches!(result.data(), ExprData::Const(..))); - } - - #[test] - fn whnf_delta_opaque_hint_unfolds() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - let opaque_def = AE::cnst(mk_id("opaque_def"), Box::new([])); - let result = tc.whnf(&opaque_def).unwrap(); - assert_eq!(result, sort1()); - } - - #[test] - fn major_scan_does_not_capture_an_unrelated_caller_let() { - let target_id = mk_id("major-scan-target"); - let mut env = KEnv::new(); - env.insert( - target_id.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: target_id.clone(), - member_idx: 0, - ty: sort0(), - ctors: Vec::new(), - lean_all: (), - }, - ); - let mut tc = TypeChecker::new(&mut env); - let forged = AE::all((), (), AE::cnst(target_id, Box::new([])), sort0()); - // Peeling the outer forall exposes Var 0. Without retaining its binder, - // WHNF zeta-reduces that variable through this unrelated caller let and - // fabricates the inductive-headed forall above. - let rec_ty = AE::all((), (), sort0(), AE::var(0, ())); - tc.push_let(sort0(), forged.clone()); - let saved_depth = tc.save_depth(); - let saved_ctx_id = tc.ctx_id; - let saved_let_count = tc.num_let_bindings; - - assert!(tc.get_major_inductive_id(&rec_ty, 1).is_err()); - assert_eq!(tc.save_depth(), saved_depth); - assert_eq!(tc.ctx_id, saved_ctx_id); - assert_eq!(tc.num_let_bindings, saved_let_count); - assert!( - matches!(&tc.let_vals[0], Some(value) if value == &forged), - "the caller let must survive the scoped scan unchanged" - ); - } - - #[test] - fn whnf_string_back_empty_literal() { - use super::super::testing as kt; - - let mut env = KEnv::new(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let back = - kt::ME::cnst(tc.primitives().unwrap().string_back.clone(), Box::new([])); - let empty = kt::ME::str(String::new(), Address::hash(b"")); - let result = tc.whnf(&kt::ME::app(back, empty)).unwrap(); - let (head, args) = collect_app_spine(&result); - match head.data() { - ExprData::Const(id, _, _) => { - assert_eq!(id.addr, tc.primitives().unwrap().char_of_nat.addr) - }, - other => panic!("expected Char.ofNat head, got {:?}", other), - } - assert_eq!(args.len(), 1); - match args[0].data() { - ExprData::Nat(v, _, _) => { - assert_eq!(v.0, num_bigint::BigUint::from(65u64)); - }, - other => panic!("expected default Char Nat literal, got {:?}", other), - } - } - - #[test] - fn whnf_string_utf8_byte_size_literal() { - use super::super::testing as kt; - - let mut env = KEnv::new(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let size = kt::ME::cnst( - tc.primitives().unwrap().string_utf8_byte_size.clone(), - Box::new([]), - ); - let s = kt::ME::str("L∃∀N".to_string(), Address::hash("L∃∀N".as_bytes())); - let result = tc.whnf(&kt::ME::app(size, s)).unwrap(); - match result.data() { - ExprData::Nat(v, _, _) => { - assert_eq!(v.0, num_bigint::BigUint::from(8u64)); - }, - other => { - panic!("expected UTF-8 byte length Nat literal, got {:?}", other) - }, - } - } - - #[test] - fn def_eq_string_to_byte_array_empty() { - use super::super::testing as kt; - - let mut env = KEnv::new(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let to_byte_array = kt::ME::cnst( - tc.primitives().unwrap().string_to_byte_array.clone(), - Box::new([]), - ); - let empty_string = kt::ME::str(String::new(), Address::hash(b"")); - let lhs = kt::ME::app(to_byte_array, empty_string); - let rhs = kt::ME::cnst( - tc.primitives().unwrap().byte_array_empty.clone(), - Box::new([]), - ); - assert!(tc.is_def_eq(&lhs, &rhs).unwrap()); - } - - #[test] - fn whnf_nat_ble_zero_length_string_to_list_literal_is_true() { - use super::super::constant::RecRule; - - // Do not add these to `Primitives`: Lean reduces this through ordinary - // delta/iota/projection/string-literal expansion, not a native kernel op. - fn canonical_id(hex: &str) -> KId { - KId::new(Address::from_hex(hex).unwrap(), ()) - } - fn apps_ae(mut f: AE, args: &[AE]) -> AE { - for arg in args { - f = app(f, arg.clone()); - } - f - } - - let prims = Primitives::from_env(&KEnv::::new()); - let string_to_list_id = canonical_id( - "85bfb457e4487849a08e03ec1d7288888726463caf9768c06a12bf4451e2a7db", - ); - let list_length_id = canonical_id( - "a9f286f64c677e133822d2fd2167ca5c5ab29ae6a78fe1ac1f4eec8a452f56b2", - ); - let list_id = mk_id("Test.List"); - let list_nil_id = mk_id("Test.List.nil"); - let list_cons_id = mk_id("Test.List.cons"); - let list_rec_id = mk_id("Test.List.rec"); - let list_const = AE::cnst(list_id.clone(), Box::new([])); - - let mut env = KEnv::::new(); - env.insert( - list_id.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 1, - indices: 0, - is_unsafe: false, - block: list_id.clone(), - member_idx: 0, - ty: pi(sort0(), sort0()), - ctors: vec![list_nil_id.clone(), list_cons_id.clone()], - lean_all: (), - }, - ); - env.insert( - list_nil_id.clone(), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: list_id.clone(), - cidx: 0, - params: 1, - fields: 0, - ty: pi(sort0(), app(list_const.clone(), var(0))), - }, - ); - env.insert( - list_cons_id.clone(), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: list_id.clone(), - cidx: 1, - params: 1, - fields: 2, - ty: pi( - sort0(), - pi( - var(0), - pi( - app(list_const.clone(), var(1)), - app(list_const.clone(), var(2)), - ), - ), - ), - }, - ); - - let rec_const = AE::cnst(list_rec_id.clone(), Box::new([])); - let ih = - apps_ae(rec_const.clone(), &[var(5), var(4), var(3), var(2), var(0)]); - let cons_result = apps_ae(var(2), &[var(1), var(0), ih]); - env.insert( - list_rec_id.clone(), - KConst::Recr { - name: (), - level_params: (), - k: false, - is_unsafe: false, - lvls: 0, - params: 1, - indices: 0, - motives: 1, - minors: 2, - block: list_id.clone(), - member_idx: 0, - ty: sort0(), - rules: vec![ - RecRule { - ctor: (), - fields: 0, - rhs: lam(sort0(), lam(sort0(), lam(sort0(), lam(sort0(), var(1))))), - }, - RecRule { - ctor: (), - fields: 2, - rhs: lam( - sort0(), - lam( - sort0(), - lam( - sort0(), - lam(sort0(), lam(sort0(), lam(sort0(), cons_result))), - ), - ), - ), - }, - ], - lean_all: (), - }, - ); - - let char_ty = AE::cnst(prims.char_type.clone(), Box::new([])); - let char_of_nat = AE::cnst(prims.char_of_nat.clone(), Box::new([])); - let list_nil = AE::cnst(list_nil_id.clone(), Box::new([])); - let list_cons = AE::cnst(list_cons_id.clone(), Box::new([])); - let nil_char = app(list_nil, char_ty.clone()); - let char_a = app(char_of_nat, mk_nat(65)); - let one_char_list = - apps_ae(list_cons, &[char_ty.clone(), char_a, nil_char]); - env.insert( - string_to_list_id.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(0), - lvls: 0, - ty: sort0(), - val: lam(sort0(), one_char_list), - lean_all: (), - block: string_to_list_id.clone(), - }, - ); - - let nat_succ = AE::cnst(prims.nat_succ.clone(), Box::new([])); - let motive = lam(sort0(), nat()); - let cons_case = lam( - var(1), - lam(app(list_const.clone(), var(2)), lam(nat(), app(nat_succ, var(0)))), - ); - let length_body = - apps_ae(rec_const, &[var(1), motive, mk_nat(0), cons_case, var(0)]); - env.insert( - list_length_id.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(0), - lvls: 1, - ty: sort0(), - val: lam(sort0(), lam(app(list_const, var(0)), length_body)), - lean_all: (), - block: list_length_id.clone(), - }, - ); - - let mut tc = TypeChecker::new(&mut env); - let string_to_list = AE::cnst(string_to_list_id, Box::new([])); - let list_length = AE::cnst(list_length_id, Box::new([KUniv::zero()])); - let nat_ble = - AE::cnst(tc.primitives().unwrap().nat_ble.clone(), Box::new([])); - - let sample = " 0123abcABC:,;`\\/"; - let str_lit = AE::str(sample.to_string(), Address::hash(sample.as_bytes())); - let chars = app(string_to_list, str_lit); - let len = apps_ae(list_length, &[char_ty, chars]); - let expr = apps_ae(nat_ble, &[mk_nat(0), len]); - - let result = tc.whnf(&expr).unwrap(); - match result.data() { - ExprData::Const(id, _, _) => { - assert_eq!(id.addr, tc.primitives().unwrap().bool_true.addr); - }, - other => panic!("expected Bool.true, got {other:?}"), - } - } - - #[test] - fn whnf_cache_hit() { - let mut env = env_with_id(); - let mut tc = TypeChecker::new(&mut env); - let id_const = AE::cnst(mk_id("id"), Box::new([])); - let app = AE::app(id_const, sort0()); - let r1 = tc.whnf(&app).unwrap(); - let r2 = tc.whnf(&app).unwrap(); - // Both should return the same result - assert_eq!(r1, r2); - } - - fn nat() -> AE { - AE::cnst(mk_id("Nat"), Box::new([])) - } - fn param(n: u64) -> AU { - AU::param(n, ()) - } - fn pi(a: AE, b: AE) -> AE { - AE::all((), (), a, b) - } - fn app(f: AE, a: AE) -> AE { - AE::app(f, a) - } - fn lam(a: AE, b: AE) -> AE { - AE::lam((), (), a, b) - } - fn var(i: u64) -> AE { - AE::var(i, ()) - } - fn cnst(name: &str, us: &[AU]) -> AE { - AE::cnst(mk_id(name), us.to_vec().into_boxed_slice()) - } - fn mk_nat(n: u64) -> AE { - let v = Nat::from(n); - let addr = Address::hash(&v.to_le_bytes()); - AE::nat(v, addr) - } - - fn unit() -> AE { - cnst("Unit", &[]) - } - - fn unit_env() -> KEnv { - use super::super::constant::RecRule; - - let mut env = KEnv::new(); - let block = mk_id("Unit"); - let unit_id = mk_id("Unit"); - let unit_unit_id = mk_id("Unit.unit"); - - env.insert( - unit_id.clone(), - KConst::Indc { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - block: block.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![unit_unit_id.clone()], - lean_all: (), - }, - ); - env.insert( - unit_unit_id.clone(), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: unit_id.clone(), - cidx: 0, - params: 0, - fields: 0, - ty: unit(), - }, - ); - - let motive_ty = pi(unit(), sort1()); - let unit_unit = cnst("Unit.unit", &[]); - let minor_ty = app(var(0), unit_unit); - let rec_ty = pi( - motive_ty.clone(), - pi(minor_ty.clone(), pi(unit(), app(var(2), var(0)))), - ); - let rule_rhs = lam(motive_ty, lam(minor_ty, var(0))); - env.insert( - mk_id("Unit.rec"), - KConst::Recr { - name: (), - level_params: (), - k: false, - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: block.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![RecRule { ctor: (), fields: 0, rhs: rule_rhs }], - lean_all: (), - }, - ); - env.blocks.insert(block, vec![unit_id, unit_unit_id, mk_id("Unit.rec")]); - env - } - - #[test] - fn whnf_unit_like_rec_eta_on_open_major() { - let mut env = unit_env(); - let mut tc = TypeChecker::new(&mut env); - tc.push_local(unit()); - - let motive = lam(unit(), unit()); - let minor = cnst("Unit.unit", &[]); - let rec = cnst("Unit.rec", &[]); - let expr = app(app(app(rec, motive), minor.clone()), var(0)); - let result = tc.whnf(&expr).unwrap(); - - assert_eq!(result, minor); - } - - fn mk_meta_nat(n: u64) -> super::super::testing::ME { - let v = Nat::from(n); - let addr = Address::hash(&v.to_le_bytes()); - super::super::testing::ME::nat(v, addr) - } - - /// Build a Nat env with Nat, Nat.zero, Nat.succ, Nat.rec, and Nat.sub. - /// Nat.sub is defined as a primitive that the kernel's try_reduce_nat handles, - /// but also has a delta-unfoldable body using Nat.rec (to test reduction order). - fn nat_env() -> KEnv { - use super::super::constant::RecRule; - - let mut env = KEnv::new(); - let block = mk_id("Nat"); - - // Nat : Sort 1 - env.insert( - mk_id("Nat"), - KConst::Indc { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - block: block.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![mk_id("Nat.zero"), mk_id("Nat.succ")], - lean_all: (), - }, - ); - env.insert( - mk_id("Nat.zero"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Nat"), - cidx: 0, - params: 0, - fields: 0, - ty: nat(), - }, - ); - env.insert( - mk_id("Nat.succ"), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Nat"), - cidx: 1, - params: 0, - fields: 1, - ty: pi(nat(), nat()), - }, - ); - let prims = Primitives::from_env(&KEnv::new()); - if prims.nat_zero.addr != mk_id("Nat.zero").addr { - env.insert( - prims.nat_zero.clone(), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Nat"), - cidx: 0, - params: 0, - fields: 0, - ty: nat(), - }, - ); - } - if prims.nat_succ.addr != mk_id("Nat.succ").addr { - env.insert( - prims.nat_succ.clone(), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: mk_id("Nat"), - cidx: 1, - params: 0, - fields: 1, - ty: pi(nat(), nat()), - }, - ); - } - - // Nat.rec : ∀ {motive : Nat → Sort u} (zero : motive 0) (succ : ∀ n, motive n → motive (succ n)) (t : Nat), motive t - let motive_ty = pi(nat(), AE::sort(param(0))); - let minor_zero = app(var(0), cnst("Nat.zero", &[])); - let minor_succ = pi( - nat(), - pi(app(var(2), var(0)), app(var(3), app(cnst("Nat.succ", &[]), var(1)))), - ); - let rec_ty = pi( - motive_ty, - pi(minor_zero, pi(minor_succ, pi(nat(), app(var(3), var(0))))), - ); - let rule_zero_rhs = lam(sort0(), lam(sort0(), lam(sort0(), var(1)))); - let nat_rec_const = cnst("Nat.rec", &[param(0)]); - let ih = app(app(app(app(nat_rec_const, var(3)), var(2)), var(1)), var(0)); - let rule_succ_rhs = lam( - sort0(), - lam(sort0(), lam(sort0(), lam(nat(), app(app(var(1), var(0)), ih)))), - ); - env.insert( - mk_id("Nat.rec"), - KConst::Recr { - name: (), - level_params: (), - k: false, - is_unsafe: false, - lvls: 1, - params: 0, - indices: 0, - motives: 1, - minors: 2, - block: block.clone(), - member_idx: 0, - ty: rec_ty, - rules: vec![ - RecRule { ctor: (), fields: 0, rhs: rule_zero_rhs }, - RecRule { ctor: (), fields: 1, rhs: rule_succ_rhs }, - ], - lean_all: (), - }, - ); - - // Nat.sub : Nat → Nat → Nat - // Body: a simple definition that the kernel should reduce natively. - // In practice Nat.sub's body uses Nat.rec, but try_reduce_nat - // should intercept it before delta unfolding exposes the body. - let sub_ty = pi(nat(), pi(nat(), nat())); - // Body is irrelevant for the native reduction test — just use a placeholder. - // To test the delta-unfold-before-native-reduce bug, we make the body - // something that would diverge if delta-unfolded: Nat.rec applied to arg. - // Nat.sub a b = Nat.rec (motive := λ _, Nat) a (λ n ih, Nat.pred ih) b - // But for simplicity, just use λ a b. a (dummy body). - let sub_val = lam(nat(), lam(nat(), var(1))); - env.insert( - mk_id("Nat.sub"), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(0), - lvls: 0, - ty: sub_ty, - val: sub_val, - lean_all: (), - block: mk_id("Nat.sub"), - }, - ); - - env.blocks.insert( - block, - vec![ - mk_id("Nat"), - mk_id("Nat.zero"), - mk_id("Nat.succ"), - mk_id("Nat.rec"), - ], - ); - env - } - - fn insert_nat_add_model(env: &mut KEnv, add_id: KId) { - let empty = KEnv::new(); - let prims = Primitives::from_env(&empty); - let add_ty = pi(nat(), pi(nat(), nat())); - let succ = AE::cnst(prims.nat_succ.clone(), Box::new([])); - let add_val = lam(nat(), lam(nat(), app(succ.clone(), app(succ, var(1))))); - env.insert( - add_id.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(0), - lvls: 0, - ty: add_ty, - val: add_val, - lean_all: (), - block: add_id, - }, - ); - } - - #[test] - fn whnf_nat_sub_native() { - // Nat.sub 1000 500 should reduce to Nat(500) via try_reduce_nat, - // without delta-unfolding Nat.sub's body. - let mut env = nat_env(); - // Build primitives from an empty env to get hardcoded addresses as KIds - let empty = KEnv::new(); - let prims = Primitives::from_env(&empty); - // Insert Nat.sub at its REAL primitive address so try_reduce_nat recognizes it - let sub_id = prims.nat_sub.clone(); - let sub_ty = pi(nat(), pi(nat(), nat())); - let sub_val = lam(nat(), lam(nat(), var(1))); // dummy body: λ a b. a - env.insert( - sub_id.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(0), - lvls: 0, - ty: sub_ty, - val: sub_val, - lean_all: (), - block: sub_id.clone(), - }, - ); - let mut tc = TypeChecker::new(&mut env); - let sub_const = AE::cnst(sub_id, Box::new([])); - let expr = app(app(sub_const, mk_nat(1000)), mk_nat(500)); - let result = tc.whnf(&expr).unwrap(); - match result.data() { - ExprData::Nat(v, _, _) => assert_eq!( - v.0, - num_bigint::BigUint::from(500u64), - "Nat.sub 1000 500 should be 500" - ), - other => panic!("expected Nat(500), got {:?}", other), - } - } - - #[test] - fn whnf_nat_primitive_accepts_constructor_value_with_loose_bvar() { - // Iota on Nat literals can expose a value as `Nat.succ `. - // Sparse-case code also carries binders that disappear after WHNF of - // primitive arguments, so primitive reduction must not reject the whole - // application just because it syntactically contains a loose bvar. - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let add = AE::cnst(tc.primitives().unwrap().nat_add.clone(), Box::new([])); - let succ = - AE::cnst(tc.primitives().unwrap().nat_succ.clone(), Box::new([])); - let ctor_num = app(succ, mk_nat(4)); - let dead_open_arg = app(lam(nat(), ctor_num), var(0)); - let expr = app(app(add, dead_open_arg), mk_nat(2)); - let result = tc.whnf(&expr).unwrap(); - match result.data() { - ExprData::Nat(v, _, _) => { - assert_eq!(v.0, num_bigint::BigUint::from(7u64)); - }, - other => panic!("expected Nat(7), got {:?}", other), - } - } - - #[test] - fn whnf_nat_ble_large() { - // Nat.ble 2^32 2^32 should reduce to Bool.true via try_reduce_nat - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let ble = AE::cnst(tc.primitives().unwrap().nat_ble.clone(), Box::new([])); - let big = mk_nat(1u64 << 32); - let expr = app(app(ble, big.clone()), big); - let result = tc.whnf(&expr).unwrap(); - // Should be Bool.true constant - match result.data() { - ExprData::Const(id, _, _) => { - assert_eq!(id.addr, tc.primitives().unwrap().bool_true.addr) - }, - other => panic!("expected Bool.true, got {:?}", other), - } - } - - #[test] - 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); - insert_nat_add_model(&mut env, prims.nat_add.clone()); - - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let add = AE::cnst(tc.primitives().unwrap().nat_add.clone(), Box::new([])); - 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.primitives().unwrap().nat_add.clone(), Box::new([])); - let succ = - AE::cnst(tc.primitives().unwrap().nat_succ.clone(), Box::new([])); - 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] - fn whnf_nat_add_ofnat_zero_lhs_stays_stuck() { - use super::super::testing as kt; - - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - let nat_ty = - kt::ME::cnst(tc.primitives().unwrap().nat.clone(), Box::new([])); - let ofnat_zero = kt::apps( - kt::cnst("OfNat.ofNat", &[]), - &[nat_ty, mk_meta_nat(0), kt::cnst("instOfNatNat", &[])], - ); - let add = - kt::ME::cnst(tc.primitives().unwrap().nat_add.clone(), Box::new([])); - let expr = kt::apps(add, &[ofnat_zero, kt::var(0)]); - let result = tc.whnf(&expr).unwrap(); - assert_eq!(result, expr); - } - - #[test] - fn whnf_nat_mul_ofnat_one_rhs_stays_stuck() { - use super::super::testing as kt; - - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - let nat_ty = - kt::ME::cnst(tc.primitives().unwrap().nat.clone(), Box::new([])); - let ofnat_one = kt::apps( - kt::cnst("OfNat.ofNat", &[]), - &[nat_ty, mk_meta_nat(1), kt::cnst("instOfNatNat", &[])], - ); - let mul = - kt::ME::cnst(tc.primitives().unwrap().nat_mul.clone(), Box::new([])); - let expr = kt::apps(mul, &[kt::var(0), ofnat_one]); - let result = tc.whnf(&expr).unwrap(); - assert_eq!(result, expr); - } - - #[test] - fn try_reduce_nat_ofnat_nat_literal_arg_stays_stuck() { - use super::super::testing as kt; - - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - let nat_ty = - kt::ME::cnst(tc.primitives().unwrap().nat.clone(), Box::new([])); - let ofnat_one = kt::apps( - kt::cnst("OfNat.ofNat", &[]), - &[nat_ty, mk_meta_nat(1), kt::cnst("instOfNatNat", &[])], - ); - let add = - kt::ME::cnst(tc.primitives().unwrap().nat_add.clone(), Box::new([])); - let expr = kt::apps(add, &[ofnat_one, mk_meta_nat(2)]); - assert!(tc.try_reduce_nat(&expr).unwrap().is_none()); - } - - #[test] - fn whnf_nat_mul_symbolic_zero_rhs_stays_stuck() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let mul = AE::cnst(tc.primitives().unwrap().nat_mul.clone(), Box::new([])); - let expr = app(app(mul, var(0)), mk_nat(0)); - let result = tc.whnf(&expr).unwrap(); - assert_eq!(result, expr); - } - - #[test] - fn def_eq_nat_add_literal_lhs_not_succ_chain() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - tc.push_local(nat()); - - for n in 0..=4 { - let add = - AE::cnst(tc.primitives().unwrap().nat_add.clone(), Box::new([])); - let lhs = app(app(add, mk_nat(n)), var(0)); - let mut rhs = var(0); - for _ in 0..n { - let succ = - AE::cnst(tc.primitives().unwrap().nat_succ.clone(), Box::new([])); - rhs = app(succ, rhs); - } - - assert!( - !tc.is_def_eq(&lhs, &rhs).unwrap(), - "Nat.add {n} x must stay distinct from succ^{n} x" - ); - } - } - - #[test] - fn def_eq_nat_mul_non_iota_symbolic_cases_stay_distinct() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - tc.push_local(nat()); - - let mul = AE::cnst(tc.primitives().unwrap().nat_mul.clone(), Box::new([])); - let x = var(0); - - let lhs_zero = app(app(mul.clone(), mk_nat(0)), x.clone()); - assert!( - !tc.is_def_eq(&lhs_zero, &mk_nat(0)).unwrap(), - "Nat.mul 0 x must not reduce to 0 while x is stuck" - ); - - let lhs_one = app(app(mul.clone(), mk_nat(1)), x.clone()); - assert!( - !tc.is_def_eq(&lhs_one, &x).unwrap(), - "Nat.mul 1 x must not reduce to x while x is stuck" - ); - - let rhs_one = app(app(mul, x.clone()), mk_nat(1)); - assert!( - !tc.is_def_eq(&rhs_one, &x).unwrap(), - "Nat.mul x 1 must not reduce directly to x" - ); - } - - #[test] - fn whnf_nat_mod_literal_by_symbolic_lower_bound_stays_stuck() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let add = AE::cnst(tc.primitives().unwrap().nat_add.clone(), Box::new([])); - let modu = AE::cnst(tc.primitives().unwrap().nat_mod.clone(), Box::new([])); - let denom = app(app(add, var(0)), mk_nat(2)); - let expr = app(app(modu, mk_nat(1)), denom); - let result = tc.whnf(&expr).unwrap(); - assert_eq!(result, expr); - } - - #[test] - fn try_reduce_nat_sub_symbolic_literal_rhs_stays_stuck() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let add = AE::cnst(tc.primitives().unwrap().nat_add.clone(), Box::new([])); - let sub = AE::cnst(tc.primitives().unwrap().nat_sub.clone(), Box::new([])); - let lhs = app(app(add, var(0)), mk_nat(2)); - let expr = app(app(sub, lhs), mk_nat(1)); - assert!(tc.try_reduce_nat(&expr).unwrap().is_none()); - } - - #[test] - fn whnf_bitvec_ult_zero_rhs_is_false() { - use super::super::testing as kt; - - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let bv_of_nat = kt::ME::cnst( - tc.primitives().unwrap().bit_vec_of_nat.clone(), - Box::new([]), - ); - let bv_ult = - kt::ME::cnst(tc.primitives().unwrap().bit_vec_ult.clone(), Box::new([])); - let zero = kt::apps(bv_of_nat, &[kt::var(1), mk_meta_nat(0)]); - let ult = kt::apps(bv_ult, &[kt::var(1), kt::var(0), zero]); - let result = tc.whnf(&ult).unwrap(); - match result.data() { - ExprData::Const(id, _, _) => { - assert_eq!(id.addr, tc.primitives().unwrap().bool_false.addr) - }, - other => panic!("expected Bool.false, got {:?}", other), - } - } - - #[test] - fn whnf_bitvec_to_nat_ofnat_zero_is_zero() { - use super::super::testing as kt; - - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let bv_of_nat = kt::ME::cnst( - tc.primitives().unwrap().bit_vec_of_nat.clone(), - Box::new([]), - ); - let bv_to_nat = kt::ME::cnst( - tc.primitives().unwrap().bit_vec_to_nat.clone(), - Box::new([]), - ); - let zero = kt::apps(bv_of_nat, &[kt::var(0), mk_meta_nat(0)]); - let expr = kt::apps(bv_to_nat, &[kt::var(0), zero]); - let result = tc.whnf(&expr).unwrap(); - match result.data() { - ExprData::Nat(v, _, _) => { - assert_eq!(v.0, num_bigint::BigUint::ZERO); - }, - other => panic!("expected Nat(0), got {:?}", other), - } - } - - // The class methods below take their value from an instance a prover - // chooses. With the instance an opaque variable, none may reduce: a - // non-canonical `LT (BitVec w)` can make `x < 0#w` hold, an `OfNat (BitVec - // w) 5` can be `0#w`, and a `SizeOf Unit` can give any size. - - #[test] - fn whnf_decide_bitvec_lt_reads_the_instance() { - use super::super::testing as kt; - - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let width = kt::var(1); - let bv_const = - kt::ME::cnst(tc.primitives().unwrap().bit_vec.clone(), Box::new([])); - let bv_of_nat = kt::ME::cnst( - tc.primitives().unwrap().bit_vec_of_nat.clone(), - Box::new([]), - ); - let lt_lt = - kt::ME::cnst(tc.primitives().unwrap().lt_lt.clone(), Box::new([])); - let dec_decide = kt::ME::cnst( - tc.primitives().unwrap().decidable_decide.clone(), - Box::new([]), - ); - let bv_ty = kt::apps(bv_const, std::slice::from_ref(&width)); - let zero = kt::apps(bv_of_nat, &[width, mk_meta_nat(0)]); - let prop = kt::apps(lt_lt, &[bv_ty, kt::var(2), kt::var(0), zero]); - let decide = kt::apps(dec_decide, &[prop, kt::var(3)]); - let result = tc.whnf(&decide).unwrap(); - assert!( - tc.bool_lit_value(&result).is_none(), - "decide over an opaque LT instance must not reduce, got {result:?}" - ); - } - - #[test] - fn whnf_bitvec_to_nat_of_numeral_reads_the_instance() { - use super::super::testing as kt; - - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let width = mk_meta_nat(8); - let bv_const = - kt::ME::cnst(tc.primitives().unwrap().bit_vec.clone(), Box::new([])); - let of_nat = kt::ME::cnst( - tc.primitives().unwrap().of_nat_of_nat.clone(), - Box::new([]), - ); - let bv_to_nat = kt::ME::cnst( - tc.primitives().unwrap().bit_vec_to_nat.clone(), - Box::new([]), - ); - let bv_ty = kt::apps(bv_const, std::slice::from_ref(&width)); - let five = kt::apps(of_nat, &[bv_ty, mk_meta_nat(5), kt::var(0)]); - let expr = kt::apps(bv_to_nat, &[width, five]); - let result = tc.whnf(&expr).unwrap(); - assert!( - !matches!(result.data(), ExprData::Nat(..)), - "BitVec.toNat of a numeral with an opaque instance must not reduce, \ - got {result:?}" - ); - } - - #[test] - fn whnf_size_of_unit_reads_the_instance() { - use super::super::testing as kt; - - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - let unit = - kt::ME::cnst(tc.primitives().unwrap().unit.clone(), Box::new([])); - let size_of = kt::ME::cnst( - tc.primitives().unwrap().size_of_size_of.clone(), - Box::new([kt::usucc(kt::uzero())]), - ); - let expr = kt::apps(size_of, &[unit, kt::var(1), kt::var(0)]); - let result = tc.whnf(&expr).unwrap(); - assert!( - !matches!(result.data(), ExprData::Nat(..)), - "SizeOf.sizeOf Unit with an opaque instance must not reduce, got {result:?}" - ); - } - - #[test] - fn whnf_punit_size_of_1_has_no_native_rule() { - // `PUnit._sizeOf_1 x` reduces by unfolding its body (`PUnit.rec 1 x`, - // structure eta on `x`), not by a rule of its own: with no definition - // in the environment it stays stuck. - use super::super::testing as kt; - - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - let size_of_1 = kt::ME::cnst( - tc.primitives().unwrap().punit_size_of_1.clone(), - Box::new([kt::usucc(kt::uzero())]), - ); - let expr = kt::app(size_of_1, kt::var(0)); - let result = tc.whnf(&expr).unwrap(); - assert!( - !matches!(result.data(), ExprData::Nat(..)), - "PUnit._sizeOf_1 must not reduce natively, got {result:?}" - ); - } - - #[test] - fn whnf_fin_val_of_decidable_rec_on_a_variable_stays_a_projection() { - // `(Decidable.rec p motive f g d).val` with `d` a variable: Lean's - // kernel leaves the projection on the stuck recursor, so does this one. - use super::super::testing as kt; - - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - let fin = kt::ME::cnst(tc.primitives().unwrap().fin.clone(), Box::new([])); - let fin3 = kt::app(fin, mk_meta_nat(3)); - let rec = kt::ME::cnst( - tc.primitives().unwrap().decidable_rec.clone(), - Box::new([kt::usucc(kt::uzero())]), - ); - let motive = kt::lam(kt::sort0(), fin3); - let minor_f = kt::lam(kt::sort0(), kt::var(3)); - let minor_g = kt::lam(kt::sort0(), kt::var(4)); - let rec_app = - kt::apps(rec, &[kt::var(0), motive, minor_f, minor_g, kt::var(1)]); - let expr = kt::ME::prj(tc.primitives().unwrap().fin.clone(), 0, rec_app); - let result = tc.whnf(&expr).unwrap(); - assert!( - matches!(result.data(), ExprData::Prj(..)), - "Fin.val of a stuck Decidable.rec must stay a projection, got {result:?}" - ); - } - - #[test] - fn whnf_def_eq_nat_sub_large() { - // Simulate the real failure: a definition whose type-check requires - // proving `Nat.sub (2^16) x =?= y` via def-eq. If Nat.sub gets - // delta-unfolded to Nat.rec before try_reduce_nat intercepts it, - // the kernel diverges on iota reduction. - let mut env = nat_env(); - // Build primitives from an empty env to get hardcoded addresses as KIds - let empty = KEnv::new(); - let prims = Primitives::from_env(&empty); - let sub_id = prims.nat_sub.clone(); - let sub_ty = pi(nat(), pi(nat(), nat())); - // Body that uses Nat.rec — if delta-unfolded, this would produce - // Nat.rec motive zero_case succ_case (lit 65536) which diverges. - // But try_reduce_nat should intercept Nat.sub first. - let sub_val = lam(nat(), lam(nat(), var(1))); // dummy - env.insert( - sub_id.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(0), - lvls: 0, - ty: sub_ty, - val: sub_val, - lean_all: (), - block: sub_id.clone(), - }, - ); - let mut tc = TypeChecker::new(&mut env); - let sub_const = AE::cnst(sub_id, Box::new([])); - let big = mk_nat(65536); // 2^16 - let expr = app(app(sub_const, big), mk_nat(0)); - let result = tc.whnf(&expr).unwrap(); - match result.data() { - ExprData::Nat(v, _, _) => { - assert_eq!(v.0, num_bigint::BigUint::from(65536u64)) - }, - other => panic!("expected Nat(65536), got {:?}", other), - } - } - - #[test] - fn def_eq_large_nat_literals() { - // Two identical large Nat literals should be equal via the fast-path - // (direct value comparison, not O(n) succ peeling). - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let a = mk_nat(1 << 20); // ~1 million - let b = mk_nat(1 << 20); - assert!( - tc.is_def_eq(&a, &b).unwrap(), - "identical large Nat literals should be def-eq" - ); - } - - /// `Nat.rec (fun _ => Nat) 0 (fun _ ih => Nat.rec (fun _ => Nat) 0 - /// (fun _ _ => 0) ih) n`, reduced on a thread of `stack` bytes: each - /// level's major premise is the next level's reduction, so this takes `n` - /// native levels. - fn whnf_nested_recursors_on_stack( - n: u64, - stack: usize, - ) -> Result> { - std::thread::Builder::new() - .stack_size(stack) - .spawn(move || { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let recursor = |step: AE, major: AE| { - let rec = cnst("Nat.rec", &[AU::succ(AU::zero())]); - app(app(app(app(rec, lam(nat(), nat())), mk_nat(0)), step), major) - }; - let constant_step = lam(nat(), lam(nat(), mk_nat(0))); - let step = lam(nat(), lam(nat(), recursor(constant_step, var(0)))); - tc.whnf(&recursor(step, mk_nat(n))) - }) - .unwrap() - .join() - .unwrap() - } - - #[test] - fn whnf_shallow_nested_recursors_reduce() { - let result = whnf_nested_recursors_on_stack(100, 16 << 20).unwrap(); - assert!(matches!(result.data(), ExprData::Nat(..) | ExprData::Const(..))); - } - - #[test] - fn whnf_deep_nested_recursors_decline_instead_of_overflowing() { - // A million levels need far more than 16 MB of stack, or than the 2 MB a - // thread gets by default (where the reserve is at its smallest); without - // the stack check each aborts the test process with a stack overflow. - for stack in [16 << 20, 2 << 20] { - assert!(matches!( - whnf_nested_recursors_on_stack(1_000_000, stack), - Err(TcError::StackExhausted) - )); - } - } - - #[test] - fn whnf_nat_rec_small() { - // Nat.rec (motive) zero_case succ_case (Nat(3)) should reduce via iota - // to succ_case 2 (succ_case 1 (succ_case 0 zero_case)) - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let rec = cnst("Nat.rec", &[AU::succ(AU::zero())]); // Nat.rec.{1} - // motive := λ _, Nat - let motive = lam(nat(), nat()); - // zero_case := Nat(42) - let zero_case = mk_nat(42); - // succ_case := λ n ih, Nat.succ ih - let succ_case = lam(nat(), lam(nat(), app(cnst("Nat.succ", &[]), var(0)))); - let expr = app(app(app(app(rec, motive), zero_case), succ_case), mk_nat(3)); - let result = tc.whnf(&expr).unwrap(); - // Should be Nat.succ(Nat.succ(Nat.succ(Nat(42)))) - // After native succ reduction: Nat(45) - match result.data() { - ExprData::Nat(v, _, _) => { - assert_eq!(v.0, num_bigint::BigUint::from(45u64)) - }, - ExprData::App(..) => { - // Might be Nat.succ chain — that's also acceptable - log::info!("Nat.rec result is App chain (not folded to literal)"); - }, - other => panic!("unexpected Nat.rec result: {:?}", other), - } - } - - // ----------------------------------------------------------------------- - // USize.size reduction chain tests - // ----------------------------------------------------------------------- - - /// Build an env with a USize.size-shaped reduction chain over a 64-bit - /// width: - /// `bits64 := 64` (reducible def) - /// Nat.pow at the correct primitive address - /// USize.size := Nat.pow 2 bits64 (reducible def) - /// and `System.Platform.numBits` at its primitive address over a stuck - /// body: in Lean it is the value of the opaque `getNumBits ()`, which no - /// kernel reduces, so the chain takes its width from `bits64`. - fn usize_env() -> KEnv { - let mut env = nat_env(); - let empty = KEnv::new(); - let prims = Primitives::from_env(&empty); - - // `bits64 := 64` - env.insert( - mk_id("bits64"), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Abbrev, - lvls: 0, - ty: nat(), - val: mk_nat(64), - lean_all: (), - block: mk_id("bits64"), - }, - ); - - // System.Platform.numBits — insert at the real primitive address with a - // stuck body. - env.insert( - prims.system_platform_num_bits.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Abbrev, - lvls: 0, - ty: nat(), - val: AE::cnst(mk_id("opaque.bits"), Box::new([])), - lean_all: (), - block: prims.system_platform_num_bits.clone(), - }, - ); - - // Nat.pow at the real primitive address - let pow_ty = pi(nat(), pi(nat(), nat())); - let pow_val = lam(nat(), lam(nat(), var(1))); // dummy body - env.insert( - prims.nat_pow.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(0), - lvls: 0, - ty: pow_ty, - val: pow_val, - lean_all: (), - block: prims.nat_pow.clone(), - }, - ); - - // Nat.sub at the real primitive address - let sub_ty = pi(nat(), pi(nat(), nat())); - let sub_val = lam(nat(), lam(nat(), var(1))); // dummy body - env.insert( - prims.nat_sub.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(0), - lvls: 0, - ty: sub_ty, - val: sub_val, - lean_all: (), - block: prims.nat_sub.clone(), - }, - ); - - // Nat.pred at the real primitive address, defined via Nat.rec as in Lean. - let pred_ty = pi(nat(), nat()); - let rec = cnst("Nat.rec", &[AU::succ(AU::zero())]); - let motive = lam(nat(), nat()); - let zero_case = mk_nat(0); - let succ_case = lam(nat(), lam(nat(), var(1))); - let pred_val = - lam(nat(), app(app(app(app(rec, motive), zero_case), succ_case), var(0))); - env.insert( - prims.nat_pred.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(0), - lvls: 0, - ty: pred_ty, - val: pred_val, - lean_all: (), - block: prims.nat_pred.clone(), - }, - ); - - // USize.size := Nat.pow 2 bits64 - let usize_size_val = app( - app(AE::cnst(prims.nat_pow.clone(), Box::new([])), mk_nat(2)), - AE::cnst(mk_id("bits64"), Box::new([])), - ); - env.insert( - mk_id("USize.size"), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Abbrev, // @[reducible] - lvls: 0, - ty: nat(), - val: usize_size_val, - lean_all: (), - block: mk_id("USize.size"), - }, - ); - - env - } - - #[test] - fn whnf_system_platform_num_bits_is_not_a_literal() { - // `System.Platform.numBits` unfolds to its (here stuck) body; it is not - // reduced to 64, which would make `numBits = 64` hold by `rfl`. - let mut env = usize_env(); - let mut tc = TypeChecker::new(&mut env); - let num_bits = AE::cnst( - tc.primitives().unwrap().system_platform_num_bits.clone(), - Box::new([]), - ); - let result = tc.whnf(&num_bits).unwrap(); - assert_eq!(result, AE::cnst(mk_id("opaque.bits"), Box::new([]))); - } - - #[test] - fn whnf_nat_pow_2_64() { - // Nat.pow 2 64 should reduce to 2^64 - let mut env = usize_env(); - let mut tc = TypeChecker::new(&mut env); - let pow_const = - AE::cnst(tc.primitives().unwrap().nat_pow.clone(), Box::new([])); - let expr = app(app(pow_const, mk_nat(2)), mk_nat(64)); - let result = tc.whnf(&expr).unwrap(); - match result.data() { - ExprData::Nat(v, _, _) => assert_eq!( - v.0, - num_bigint::BigUint::from(1u64 << 63) * 2u64, - "Nat.pow 2 64 should be 2^64" - ), - other => panic!("expected Nat(2^64), got {:?}", other), - } - } - - #[test] - fn whnf_usize_size() { - // USize.size := Nat.pow 2 bits64 should reduce to 2^64 - let mut env = usize_env(); - let mut tc = TypeChecker::new(&mut env); - let usize_size = AE::cnst(mk_id("USize.size"), Box::new([])); - let result = tc.whnf(&usize_size).unwrap(); - let expected = num_bigint::BigUint::from(1u64 << 63) * 2u64; - match result.data() { - ExprData::Nat(v, _, _) => { - assert_eq!(v.0, expected, "USize.size should be 2^64") - }, - other => panic!("expected Nat(2^64), got {:?}", other), - } - } - - #[test] - fn whnf_nat_sub_usize_size_0() { - // Nat.sub USize.size 0 should reduce to 2^64 - let mut env = usize_env(); - let mut tc = TypeChecker::new(&mut env); - let sub_const = - AE::cnst(tc.primitives().unwrap().nat_sub.clone(), Box::new([])); - let usize_size = AE::cnst(mk_id("USize.size"), Box::new([])); - let expr = app(app(sub_const, usize_size), mk_nat(0)); - let result = tc.whnf(&expr).unwrap(); - let expected = num_bigint::BigUint::from(1u64 << 63) * 2u64; - match result.data() { - ExprData::Nat(v, _, _) => { - assert_eq!(v.0, expected, "Nat.sub USize.size 0 should be 2^64") - }, - other => panic!("expected Nat(2^64), got {:?}", other), - } - } - - #[test] - fn whnf_nat_pred_usize_size() { - // Nat.pred USize.size should reduce to 2^64 - 1 - let mut env = usize_env(); - let mut tc = TypeChecker::new(&mut env); - let pred_const = - AE::cnst(tc.primitives().unwrap().nat_pred.clone(), Box::new([])); - let usize_size = AE::cnst(mk_id("USize.size"), Box::new([])); - let expr = app(pred_const, usize_size); - let result = tc.whnf(&expr).unwrap(); - let expected = num_bigint::BigUint::from(1u64 << 63) * 2u64 - 1u64; - match result.data() { - ExprData::Nat(v, _, _) => { - assert_eq!(v.0, expected, "Nat.pred USize.size should be 2^64 - 1") - }, - other => panic!("expected Nat(2^64 - 1), got {:?}", other), - } - } - - #[test] - fn def_eq_usize_pred_sub_vs_sub_1() { - // Nat.pred (Nat.sub USize.size 0) =?= Nat.sub USize.size 1 - // This is the actual failing pattern from USize.toUInt16_ofNatTruncate_of_lt - let mut env = usize_env(); - let mut tc = TypeChecker::new(&mut env); - - let sub_const = - AE::cnst(tc.primitives().unwrap().nat_sub.clone(), Box::new([])); - let pred_const = - AE::cnst(tc.primitives().unwrap().nat_pred.clone(), Box::new([])); - let usize_size = AE::cnst(mk_id("USize.size"), Box::new([])); - - // LHS: Nat.pred (Nat.sub USize.size 0) - let lhs = app( - pred_const, - app(app(sub_const.clone(), usize_size.clone()), mk_nat(0)), - ); - // RHS: Nat.sub USize.size 1 - let rhs = app(app(sub_const, usize_size), mk_nat(1)); - - assert!( - tc.is_def_eq(&lhs, &rhs).unwrap(), - "Nat.pred (Nat.sub USize.size 0) should be def-eq to Nat.sub USize.size 1" - ); - } - - // ========================================================================= - // Native evaluation is outside the checked theory - // - // Lean's `reduceBool`/`reduceNat` are opaques whose kernel reduction would - // run the compiler. The kernel has no such rule: an application stays in - // weak head normal form, as in the certified checker. - // ========================================================================= - - #[test] - fn reduce_bool_application_stays_stuck() { - let empty = KEnv::::new(); - let prims = Primitives::from_env(&empty); - let bool_ty = AE::cnst(prims.bool_type.clone(), Box::new([])); - let bool_true = AE::cnst(prims.bool_true.clone(), Box::new([])); - - let mut env = KEnv::::new(); - // `BodyTrue := Bool.true`, the constant `reduceBool` would evaluate. - env.insert( - mk_id("BodyTrue"), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(0), - lvls: 0, - ty: bool_ty.clone(), - val: bool_true.clone(), - lean_all: (), - block: mk_id("BodyTrue"), - }, - ); - // `opaque reduceBool (b : Bool) : Bool := b`, at its canonical address. - env.insert( - prims.reduce_bool.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Opaque, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Opaque, - lvls: 0, - ty: AE::all((), (), bool_ty.clone(), bool_ty), - val: AE::lam( - (), - (), - AE::cnst(prims.bool_type.clone(), Box::new([])), - AE::var(0, ()), - ), - lean_all: (), - block: prims.reduce_bool.clone(), - }, - ); - - let mut tc = TypeChecker::new(&mut env); - let reduce_bool = - AE::cnst(tc.primitives().unwrap().reduce_bool.clone(), Box::new([])); - let expr = AE::app(reduce_bool, AE::cnst(mk_id("BodyTrue"), Box::new([]))); - let whnf = tc.whnf(&expr).unwrap(); - assert_eq!( - whnf.hash_key(), - expr.hash_key(), - "reduceBool must not evaluate" - ); - assert!( - !matches!(tc.is_def_eq(&expr, &bool_true), Ok(true)), - "reduceBool BodyTrue must not convert to Bool.true" - ); - } - - // ========================================================================= - // Reductions outside the certified specification set are off by default - // - // Each test below mirrors a test of one group's reducer, which enables the - // group explicitly, and checks that a default checker does not take it. - // ========================================================================= - - #[test] - fn extra_reductions_are_off_by_default() { - use super::super::extra_reductions::ExtraReduction as G; - let mut env = KEnv::::new(); - let tc = TypeChecker::new(&mut env); - for group in - [G::Decidable, G::BitVec, G::String, G::NatOffsets, G::NatRecLinear] - { - assert!(!tc.takes_extra(group), "{group:?} must be off by default"); - } - } - - /// Without the offset forms `Nat.add x 2` is an ordinary definition and - /// unfolds, instead of staying in compact form. - #[test] - fn whnf_nat_add_symbolic_literal_rhs_unfolds_by_default() { - 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.primitives().unwrap().nat_add.clone(), Box::new([])); - let succ = - AE::cnst(tc.primitives().unwrap().nat_succ.clone(), Box::new([])); - let expr = app(app(add, var(0)), mk_nat(2)); - let result = tc.whnf(&expr).unwrap(); - assert_eq!(result, app(succ.clone(), app(succ, var(0)))); - } - - /// Without the BitVec group `BitVec.toNat (BitVec.ofNat w 0)` is not read - /// as a literal. - #[test] - fn whnf_bitvec_to_nat_ofnat_is_not_read_by_default() { - use super::super::testing as kt; - - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - let bv_of_nat = kt::ME::cnst( - tc.primitives().unwrap().bit_vec_of_nat.clone(), - Box::new([]), - ); - let bv_to_nat = kt::ME::cnst( - tc.primitives().unwrap().bit_vec_to_nat.clone(), - Box::new([]), - ); - let zero = kt::apps(bv_of_nat, &[kt::var(0), mk_meta_nat(0)]); - let expr = kt::apps(bv_to_nat, &[kt::var(0), zero]); - let result = tc.whnf(&expr).unwrap(); - assert!( - !matches!(result.data(), ExprData::Nat(..)), - "BitVec.toNat must not be read as a literal, got {result:?}" - ); - } - - /// Without the String group `String.ofList` of a literal character list - /// does not collapse to a string literal. - #[test] - fn whnf_string_of_list_does_not_collapse_by_default() { - let (mut env, _, _) = string_env(); - let mut tc = TypeChecker::new(&mut env); - let of_list = - AE::cnst(tc.primitives().unwrap().string_of_list.clone(), Box::new([])); - let e = app(of_list, char_list_lit(tc.primitives().unwrap(), "ok")); - let result = tc.whnf(&e).unwrap(); - assert!( - !matches!(result.data(), ExprData::Str(..)), - "String.ofList must not collapse to a literal, got {result:?}" - ); - } - - /// Without the Decidable group `Nat.decLe 3 5` reduces only by unfolding - /// its definition, which this environment does not have. - #[test] - fn whnf_nat_dec_le_literals_are_not_decided_by_default() { - let mut env = KEnv::::new(); - let mut tc = TypeChecker::new(&mut env); - let dec_le = - AE::cnst(tc.primitives().unwrap().nat_dec_le.clone(), Box::new([])); - let expr = app(app(dec_le, mk_nat(3)), mk_nat(5)); - let result = tc.whnf(&expr).unwrap(); - assert_eq!(result, expr); - } - - // ========================================================================= - // Large-Nat iota runaway guard - // - // The shared per-constant budget guards against unbounded expansion of Nat - // literals into Nat.succ chains when the same recursor peels consecutive - // predecessors for thousands of steps. Give this adversarial unit a small - // explicit budget: production's 100M allowance is intentionally large for - // real certificate computations and would make the termination test slow. - // ========================================================================= - - #[test] - fn whnf_large_nat_literal_iota_cap() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - tc.rec_fuel = u64::from(MAX_WHNF_FUEL); - // A literal well above the 2^20 threshold. - let huge = mk_nat(1u64 << 25); - // Nat.rec : ∀ {motive} (zero) (succ) (t : Nat), motive t - let rec_const = cnst("Nat.rec", &[param(0)]); - let motive = lam(nat(), nat()); - let zero_branch = mk_nat(0); - let succ_branch = lam(nat(), lam(nat(), var(0))); - let application = - app(app(app(app(rec_const, motive), zero_branch), succ_branch), huge); - assert!(matches!(tc.whnf(&application), Err(TcError::MaxRecFuel))); - } - - // ========================================================================= - // Quotient reduction: `Quot.lift α r β f h (Quot.mk α r a) == f a` - // - // Sets up the Quot primitives at their canonical addresses so that - // `tc.primitives().unwrap().quot_ctor` / `quot_lift` / `quot_ind` resolve to real env - // entries. Values are kept opaque — we only check that the head-spine - // of the result matches `f a`. - // ========================================================================= - - /// Minimal Quot env: Quot / Quot.mk / Quot.lift / Quot.ind as axioms. - fn quot_env() -> KEnv { - let empty = KEnv::::new(); - let prims = Primitives::from_env(&empty); - - let mut env = KEnv::::new(); - // Types are placeholders; we only need these to live at canonical - // addresses so `try_quot_reduce` recognizes them. - env.insert( - prims.quot_type.clone(), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - ty: sort1(), - }, - ); - env.insert( - prims.quot_ctor.clone(), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - ty: sort0(), - }, - ); - env.insert( - prims.quot_lift.clone(), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 2, - ty: sort0(), - }, - ); - env.insert( - prims.quot_ind.clone(), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 1, - ty: sort0(), - }, - ); - env - } - - #[test] - fn whnf_quot_lift_reduces() { - // Quot.lift α r β f h (Quot.mk α r a) → f a - let mut env = quot_env(); - let mut tc = TypeChecker::new(&mut env); - - let alpha = AE::cnst(mk_id("α"), Box::new([])); - let r = AE::cnst(mk_id("r"), Box::new([])); - let beta = AE::cnst(mk_id("β"), Box::new([])); - let f = AE::cnst(mk_id("f"), Box::new([])); - let h = AE::cnst(mk_id("h"), Box::new([])); - let a = AE::cnst(mk_id("a"), Box::new([])); - - // Quot.mk α r a - let mk = AE::app( - AE::app( - AE::app( - AE::cnst(tc.primitives().unwrap().quot_ctor.clone(), Box::new([])), - alpha.clone(), - ), - r.clone(), - ), - a.clone(), - ); - // Quot.lift α r β f h mk - let lift = AE::app( - AE::app( - AE::app( - AE::app( - AE::app( - AE::app( - AE::cnst( - tc.primitives().unwrap().quot_lift.clone(), - Box::new([]), - ), - alpha, - ), - r, - ), - beta, - ), - f.clone(), - ), - h, - ), - mk, - ); - - let result = tc.whnf(&lift).unwrap(); - // Result head-spine: `f a`. - let (head, args) = collect_app_spine(&result); - assert_eq!(args.len(), 1); - assert!(head.hash_eq(&f)); - assert!(args[0].hash_eq(&a)); - } - - #[test] - fn whnf_quot_ind_reduces_and_preserves_suffix() { - // Quot.ind α r β f (Quot.mk α r a) extra → f a extra. - let mut env = quot_env(); - let mut tc = TypeChecker::new(&mut env); - - let alpha = AE::cnst(mk_id("α"), Box::new([])); - let r = AE::cnst(mk_id("r"), Box::new([])); - let beta = AE::cnst(mk_id("β"), Box::new([])); - let f = AE::cnst(mk_id("f"), Box::new([])); - let a = AE::cnst(mk_id("a"), Box::new([])); - let extra = AE::cnst(mk_id("extra"), Box::new([])); - let apps = |head: AE, args: &[AE]| args.iter().cloned().fold(head, AE::app); - - let mk = apps( - AE::cnst(tc.primitives().unwrap().quot_ctor.clone(), Box::new([])), - &[alpha.clone(), r.clone(), a.clone()], - ); - let ind = apps( - AE::cnst(tc.primitives().unwrap().quot_ind.clone(), Box::new([])), - &[alpha, r, beta, f.clone(), mk, extra.clone()], - ); - - let result = tc.whnf(&ind).unwrap(); - assert_eq!(result, AE::app(AE::app(f, a), extra)); - } - - #[test] - fn whnf_quot_stuck_on_overapplied_mk_major() { - // Lean recognizes `Quot.mk` at exact arity three. An extra argument on - // the major must not be mistaken for the represented quotient value. - let mut env = quot_env(); - let mut tc = TypeChecker::new(&mut env); - - let alpha = AE::cnst(mk_id("α"), Box::new([])); - let r = AE::cnst(mk_id("r"), Box::new([])); - let beta = AE::cnst(mk_id("β"), Box::new([])); - let f = AE::cnst(mk_id("f"), Box::new([])); - let h = AE::cnst(mk_id("h"), Box::new([])); - let a = AE::cnst(mk_id("a"), Box::new([])); - let extra = AE::cnst(mk_id("extra"), Box::new([])); - let apps = |head: AE, args: &[AE]| args.iter().cloned().fold(head, AE::app); - - let overapplied_mk = apps( - AE::cnst(tc.primitives().unwrap().quot_ctor.clone(), Box::new([])), - &[alpha.clone(), r.clone(), a, extra], - ); - let lift = apps( - AE::cnst(tc.primitives().unwrap().quot_lift.clone(), Box::new([])), - &[alpha, r, beta, f, h, overapplied_mk], - ); - - assert_eq!(tc.whnf(&lift).unwrap(), lift); - } - - #[test] - fn whnf_quot_lift_stuck_on_non_mk_major() { - // Major is not Quot.mk → no reduction. - let mut env = quot_env(); - // Major is an opaque axiom, not Quot.mk — include it in the env. - env.insert( - mk_id("opaque_q"), - KConst::Axio { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - ty: sort0(), - }, - ); - let mut tc = TypeChecker::new(&mut env); - - let alpha = AE::cnst(mk_id("α"), Box::new([])); - let r = AE::cnst(mk_id("r"), Box::new([])); - let beta = AE::cnst(mk_id("β"), Box::new([])); - let f = AE::cnst(mk_id("f"), Box::new([])); - let h = AE::cnst(mk_id("h"), Box::new([])); - let opaque = AE::cnst(mk_id("opaque_q"), Box::new([])); - - let lift = AE::app( - AE::app( - AE::app( - AE::app( - AE::app( - AE::app( - AE::cnst( - tc.primitives().unwrap().quot_lift.clone(), - Box::new([]), - ), - alpha, - ), - r, - ), - beta, - ), - f.clone(), - ), - h, - ), - opaque, - ); - - let result = tc.whnf(&lift).unwrap(); - // Result is the original (possibly with args WHNF'd) — head must - // still be Quot.lift. - let (head, _) = collect_app_spine(&result); - match head.data() { - ExprData::Const(id, _, _) => { - assert_eq!(id.addr, tc.primitives().unwrap().quot_lift.addr); - }, - other => panic!("expected Quot.lift head, got {other:?}"), - } - } - - #[test] - fn whnf_quot_lift_insufficient_args_stuck() { - // Fewer than 6 args → no reduction. - let mut env = quot_env(); - let mut tc = TypeChecker::new(&mut env); - // Only 3 args - let alpha = AE::cnst(mk_id("α"), Box::new([])); - let r = AE::cnst(mk_id("r"), Box::new([])); - let beta = AE::cnst(mk_id("β"), Box::new([])); - let lift_partial = AE::app( - AE::app( - AE::app( - AE::cnst(tc.primitives().unwrap().quot_lift.clone(), Box::new([])), - alpha, - ), - r, - ), - beta, - ); - let result = tc.whnf(&lift_partial).unwrap(); - let (head, args) = collect_app_spine(&result); - assert_eq!(args.len(), 3, "under-applied Quot.lift must stay partial"); - match head.data() { - ExprData::Const(id, _, _) => { - assert_eq!(id.addr, tc.primitives().unwrap().quot_lift.addr); - }, - other => panic!("expected Quot.lift head, got {other:?}"), - } - } - - // ========================================================================= - // `try_reduce_decidable` bail paths - // - // Full decidable reduction needs a substantial prelude (Decidable, - // Eq, Bool, Nat.le_of_ble_eq_true, etc.). Here we only verify the - // short-circuit paths: non-Nat args and under-application bail out - // rather than crashing. - // ========================================================================= - - #[test] - fn decidable_reduction_non_nat_arg_bails_out() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let dec_le = - AE::cnst(tc.primitives().unwrap().nat_dec_le.clone(), Box::new([])); - // Args are not Nat literals — decidable path must not panic, must - // not reduce. - let opaque1 = sort0(); - let opaque2 = sort0(); - let expr = AE::app(AE::app(dec_le, opaque1), opaque2); - let _ = tc.whnf(&expr).unwrap(); - } - - #[test] - fn decidable_reduction_underapplied_bails_out() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let dec_le = - AE::cnst(tc.primitives().unwrap().nat_dec_le.clone(), Box::new([])); - // Only 1 arg — path must bail out. - let expr = AE::app(dec_le, mk_nat(3)); - let _ = tc.whnf(&expr).unwrap(); - } - - // ========================================================================= - // Comprehensive Nat literal reduction mirror - // - // Companion to `Tests/Ix/Kernel/NatReduction.lean`. See - // `docs/nat-reduction-audit.md` for the divergence catalogue. - // - // These cover behaviors that are hard or impossible to observe through - // Lean's elaborator + `rfl`, in particular: - // - All binary primitives on raw literals (parity) - // - `Nat.zero` literal-extension recognition (D10) - // - `Nat.pow` cap at `2^24` and uncapped shifts - // - Non-literal arguments staying stuck - // - `Nat.pred` staying out of native Nat reduction - // ========================================================================= - - /// Build `op a b` using the canonical primitive address for `op`. - fn nat_bin_op(op: KId, a: AE, b: AE) -> AE { - AE::app(AE::app(AE::cnst(op, Box::new([])), a), b) - } - - /// Build `op a` for a unary primitive. - fn nat_unary_op(op: KId, a: AE) -> AE { - AE::app(AE::cnst(op, Box::new([])), a) - } - - fn assert_nat_lit(e: &AE, expected: u64) { - match e.data() { - ExprData::Nat(v, _, _) => assert_eq!( - v.0, - num_bigint::BigUint::from(expected), - "expected lit {expected}, got {v:?}" - ), - other => panic!("expected Nat literal, got {other:?}"), - } - } - - fn assert_bool_const(e: &AE, expected: bool, prims: &Primitives) { - match e.data() { - ExprData::Const(id, _, _) => { - let exp_addr = if expected { - prims.bool_true.addr.clone() - } else { - prims.bool_false.addr.clone() - }; - assert_eq!( - id.addr, - exp_addr, - "expected Bool.{}, got different const", - if expected { "true" } else { "false" } - ); - }, - other => panic!("expected Bool const, got {other:?}"), - } - } - - // ---- Section A: Per-primitive literal-on-literal (parity with reference) ---- - - #[test] - fn nat_add_lit_lit() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let cases = - [(0, 7, 7), (7, 0, 7), (2, 3, 5), (1_000_000, 2_000_000, 3_000_000)]; - for (a, b, r) in cases { - let e = nat_bin_op( - tc.primitives().unwrap().nat_add.clone(), - mk_nat(a), - mk_nat(b), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), r); - } - } - - #[test] - fn nat_sub_lit_lit() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - // Saturating: a < b ⇒ 0 - let cases = [(5, 3, 2), (5, 5, 0), (3, 5, 0), (5, 0, 5), (0, 0, 0)]; - for (a, b, r) in cases { - let e = nat_bin_op( - tc.primitives().unwrap().nat_sub.clone(), - mk_nat(a), - mk_nat(b), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), r); - } - } - - #[test] - fn nat_mul_lit_lit() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let cases = [(0, 7, 0), (7, 0, 0), (6, 7, 42), (1, 42, 42)]; - for (a, b, r) in cases { - let e = nat_bin_op( - tc.primitives().unwrap().nat_mul.clone(), - mk_nat(a), - mk_nat(b), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), r); - } - } - - #[test] - fn nat_div_lit_lit() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - // Lean convention: div by 0 ⇒ 0 - let cases = [(10, 2, 5), (7, 3, 2), (7, 0, 0), (0, 7, 0)]; - for (a, b, r) in cases { - let e = nat_bin_op( - tc.primitives().unwrap().nat_div.clone(), - mk_nat(a), - mk_nat(b), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), r); - } - } - - #[test] - fn nat_mod_lit_lit() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - // Lean convention: mod by 0 ⇒ a (the dividend) - let cases = [(10, 2, 0), (7, 3, 1), (7, 0, 7), (0, 7, 0)]; - for (a, b, r) in cases { - let e = nat_bin_op( - tc.primitives().unwrap().nat_mod.clone(), - mk_nat(a), - mk_nat(b), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), r); - } - } - - #[test] - fn nat_pow_lit_lit() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let cases = [(0, 5, 0), (5, 0, 1), (2, 10, 1024), (1, 100, 1)]; - for (a, b, r) in cases { - let e = nat_bin_op( - tc.primitives().unwrap().nat_pow.clone(), - mk_nat(a), - mk_nat(b), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), r); - } - } - - #[test] - fn nat_gcd_lit_lit() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let cases = [(0, 7, 7), (7, 0, 7), (9, 4, 1), (12, 18, 6)]; - for (a, b, r) in cases { - let e = nat_bin_op( - tc.primitives().unwrap().nat_gcd.clone(), - mk_nat(a), - mk_nat(b), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), r); - } - } - - #[test] - fn nat_beq_lit_lit() { - let mut env = nat_env(); - let prims_clone = { - let tc = TypeChecker::new(&mut env); - tc.primitives().unwrap().clone() - }; - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let cases = [ - (0, 0, true), - (5, 5, true), - (1, 2, false), - (42, 42, true), - (5, 3, false), - ]; - for (a, b, r) in cases { - let e = nat_bin_op( - tc.primitives().unwrap().nat_beq.clone(), - mk_nat(a), - mk_nat(b), - ); - assert_bool_const(&tc.whnf(&e).unwrap(), r, &prims_clone); - } - } - - #[test] - fn nat_ble_lit_lit() { - let mut env = nat_env(); - let prims_clone = { - let tc = TypeChecker::new(&mut env); - tc.primitives().unwrap().clone() - }; - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let cases = [(0, 0, true), (3, 5, true), (5, 5, true), (5, 3, false)]; - for (a, b, r) in cases { - let e = nat_bin_op( - tc.primitives().unwrap().nat_ble.clone(), - mk_nat(a), - mk_nat(b), - ); - assert_bool_const(&tc.whnf(&e).unwrap(), r, &prims_clone); - } - } - - #[test] - fn nat_bitwise_lit_lit() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - // land - let e = nat_bin_op( - tc.primitives().unwrap().nat_land.clone(), - mk_nat(0xF0), - mk_nat(0x0F), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), 0); - let e = nat_bin_op( - tc.primitives().unwrap().nat_land.clone(), - mk_nat(0xFF), - mk_nat(0x0F), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), 0xF); - // lor - let e = nat_bin_op( - tc.primitives().unwrap().nat_lor.clone(), - mk_nat(0xF0), - mk_nat(0x0F), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), 0xFF); - // xor - let e = nat_bin_op( - tc.primitives().unwrap().nat_xor.clone(), - mk_nat(0xFF), - mk_nat(0xFF), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), 0); - let e = nat_bin_op( - tc.primitives().unwrap().nat_xor.clone(), - mk_nat(0xFF), - mk_nat(0x0F), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), 0xF0); - } - - #[test] - fn nat_shift_small() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - // shift_left - let e = nat_bin_op( - tc.primitives().unwrap().nat_shift_left.clone(), - mk_nat(1), - mk_nat(4), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), 16); - let e = nat_bin_op( - tc.primitives().unwrap().nat_shift_left.clone(), - mk_nat(5), - mk_nat(0), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), 5); - // shift_right - let e = nat_bin_op( - tc.primitives().unwrap().nat_shift_right.clone(), - mk_nat(16), - mk_nat(4), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), 1); - let e = nat_bin_op( - tc.primitives().unwrap().nat_shift_right.clone(), - mk_nat(5), - mk_nat(0), - ); - assert_nat_lit(&tc.whnf(&e).unwrap(), 5); - } - - // ---- Section B: Nat.zero literal-extension recognition (D10) ---- - // `Nat.zero` constant must be treated as numeric `0` by primitive reduction. - - #[test] - fn nat_add_zero_ctor_left() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let zero = - AE::cnst(tc.primitives().unwrap().nat_zero.clone(), Box::new([])); - let e = - nat_bin_op(tc.primitives().unwrap().nat_add.clone(), zero, mk_nat(7)); - assert_nat_lit(&tc.whnf(&e).unwrap(), 7); - } - - #[test] - fn nat_mul_zero_ctor_right() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let zero = - AE::cnst(tc.primitives().unwrap().nat_zero.clone(), Box::new([])); - let e = - nat_bin_op(tc.primitives().unwrap().nat_mul.clone(), mk_nat(7), zero); - assert_nat_lit(&tc.whnf(&e).unwrap(), 0); - } - - #[test] - fn nat_beq_zero_ctor_lit() { - let mut env = nat_env(); - let prims_clone = { - let tc = TypeChecker::new(&mut env); - tc.primitives().unwrap().clone() - }; - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let zero = - AE::cnst(tc.primitives().unwrap().nat_zero.clone(), Box::new([])); - let e = - nat_bin_op(tc.primitives().unwrap().nat_beq.clone(), zero, mk_nat(0)); - assert_bool_const(&tc.whnf(&e).unwrap(), true, &prims_clone); - } - - // ---- Section C: Nat.succ chain reduction ---- - - #[test] - fn nat_succ_of_lit() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let succ = - AE::cnst(tc.primitives().unwrap().nat_succ.clone(), Box::new([])); - let e = AE::app(succ, mk_nat(41)); - assert_nat_lit(&tc.whnf(&e).unwrap(), 42); - } - - #[test] - fn nat_succ_chain_of_zero() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let succ = - AE::cnst(tc.primitives().unwrap().nat_succ.clone(), Box::new([])); - let zero = - AE::cnst(tc.primitives().unwrap().nat_zero.clone(), Box::new([])); - // Nat.succ (Nat.succ (Nat.succ Nat.zero)) - let chain = - AE::app(succ.clone(), AE::app(succ.clone(), AE::app(succ, zero))); - assert_nat_lit(&tc.whnf(&chain).unwrap(), 3); - } - - // ---- Section D: shifts are not capped at 2^24 ---- - - #[test] - fn nat_shift_left_over_former_cap_reduces() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let shift = (1u64 << 24) + 1; - let e = nat_bin_op( - tc.primitives().unwrap().nat_shift_left.clone(), - mk_nat(1), - mk_nat(shift), - ); - let r = tc.try_reduce_nat(&e).unwrap().expect("shiftLeft reduces"); - if let ExprData::Nat(v, _, _) = r.data() { - assert_eq!(v.0.bits(), shift + 1); - } else { - panic!("expected Nat lit"); - } - } - - #[test] - fn nat_shift_right_over_former_cap_reduces_to_zero() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let e = nat_bin_op( - tc.primitives().unwrap().nat_shift_right.clone(), - mk_nat(7), - mk_nat((1u64 << 24) + 1), - ); - let r = tc.try_reduce_nat(&e).unwrap(); - let r = r.expect("shiftRight reduces"); - assert_nat_lit(&r, 0); - } - - #[test] - fn nat_shift_left_at_former_cap_reduces() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let shift = 1u64 << 24; - let e = nat_bin_op( - tc.primitives().unwrap().nat_shift_left.clone(), - mk_nat(1), - mk_nat(shift), - ); - let r = tc.try_reduce_nat(&e).unwrap().expect("shiftLeft reduces"); - if let ExprData::Nat(v, _, _) = r.data() { - assert_eq!(v.0.bits(), shift + 1); - } else { - panic!("expected Nat lit"); - } - } - - // ---- Section D6: pow cap (matches reference) ---- - - #[test] - fn nat_pow_over_cap_stuck() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let e = nat_bin_op( - tc.primitives().unwrap().nat_pow.clone(), - mk_nat(2), - mk_nat((1u64 << 24) + 1), - ); - let r = tc.try_reduce_nat(&e).unwrap(); - assert!( - r.is_none(), - "D6: pow over cap should leave expr stuck (matches reference)" - ); - } - - #[test] - fn nat_pow_at_cap_reduces() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - // 1^(2^24) = 1; cap is "b > 2^24", so b == 2^24 reduces. - let e = nat_bin_op( - tc.primitives().unwrap().nat_pow.clone(), - mk_nat(1), - mk_nat(1u64 << 24), - ); - let r = tc.try_reduce_nat(&e).unwrap().expect("at cap reduces"); - assert_nat_lit(&r, 1); - } - - // ---- Section E: Nat.pred is not a native Nat reduction ---- - - #[test] - fn nat_pred_lit_stays_out_of_try_reduce_nat() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - for a in [5, 0, 1] { - let e = - nat_unary_op(tc.primitives().unwrap().nat_pred.clone(), mk_nat(a)); - assert!(tc.try_reduce_nat(&e).unwrap().is_none()); - } - } - - #[test] - fn nat_pred_zero_ctor_stays_out_of_try_reduce_nat() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let zero = - AE::cnst(tc.primitives().unwrap().nat_zero.clone(), Box::new([])); - let e = nat_unary_op(tc.primitives().unwrap().nat_pred.clone(), zero); - assert!(tc.try_reduce_nat(&e).unwrap().is_none()); - } - - // ---- Section F: non-literal binary arguments stay stuck ---- - - #[test] - fn nat_mul_symbolic_zero_stuck() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let e = nat_bin_op( - tc.primitives().unwrap().nat_mul.clone(), - AE::var(0, ()), - mk_nat(0), - ); - assert!(tc.try_reduce_nat(&e).unwrap().is_none()); - } - - #[test] - fn nat_mul_zero_symbolic_stuck() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let e = nat_bin_op( - tc.primitives().unwrap().nat_mul.clone(), - mk_nat(0), - AE::var(0, ()), - ); - assert!(tc.try_reduce_nat(&e).unwrap().is_none()); - } - - #[test] - fn nat_add_symbolic_small_stuck() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let e = nat_bin_op( - tc.primitives().unwrap().nat_add.clone(), - AE::var(0, ()), - mk_nat(3), - ); - assert!(tc.try_reduce_nat(&e).unwrap().is_none()); - } - - #[test] - fn nat_add_symbolic_large_stuck() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let e = nat_bin_op( - tc.primitives().unwrap().nat_add.clone(), - AE::var(0, ()), - mk_nat(100), - ); - let r = tc.try_reduce_nat(&e).unwrap(); - assert!(r.is_none(), "add with a symbolic argument should stay stuck"); - } - - #[test] - fn nat_add_both_symbolic_stuck() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let e = nat_bin_op( - tc.primitives().unwrap().nat_add.clone(), - AE::var(0, ()), - AE::var(1, ()), - ); - let r = tc.try_reduce_nat(&e).unwrap(); - assert!(r.is_none(), "both-symbolic add should be stuck"); - } - - #[test] - fn nat_div_symbolic_stuck() { - let mut env = nat_env(); - let mut tc = TypeChecker::new(&mut env); - let e = nat_bin_op( - tc.primitives().unwrap().nat_div.clone(), - AE::var(0, ()), - mk_nat(2), - ); - let r = tc.try_reduce_nat(&e).unwrap(); - assert!(r.is_none(), "div with symbolic dividend should be stuck"); - } - - // ------------------------------------------------------------------------- - // Native string-value fast paths (evaluator-grade whnf) - // ------------------------------------------------------------------------- - - /// Generous tick ceiling for the accelerated string paths. The - /// pre-native measurements were 10⁷ ticks to head-normalize a - /// two-char constructor-built string and > 4×10⁹ for a six-char - /// append; the native rules must land in double digits. - const STRING_FAST_PATH_TICKS: u64 = 64; - - fn str_lit(s: &str) -> AE { - AE::str(s.to_string(), Address::hash(s.as_bytes())) - } - - fn apps_ae(mut f: AE, args: &[AE]) -> AE { - for arg in args { - f = app(f, arg.clone()); - } - f - } - - /// `List.cons.{0} Char (Char.ofNat c) …` chain at the REAL primitive - /// addresses — byte-for-byte the shape `str_lit_to_constructor` - /// builds and the shape elaborated char-list literals take. - fn char_list_lit(prims: &Primitives, s: &str) -> AE { - let char_ty = AE::cnst(prims.char_type.clone(), Box::new([])); - let char_of_nat = AE::cnst(prims.char_of_nat.clone(), Box::new([])); - let nil = app( - AE::cnst(prims.list_nil.clone(), Box::new([AU::zero()])), - char_ty.clone(), - ); - let cons = AE::cnst(prims.list_cons.clone(), Box::new([AU::zero()])); - let mut list = nil; - for c in s.chars().rev() { - let ch = app(char_of_nat.clone(), mk_nat(c as u64)); - list = apps_ae(cons.clone(), &[char_ty.clone(), ch, list]); - } - list - } - - /// A String environment faithful to the compiled Lean 4.29 shape: - /// `String` is ByteArray-backed — the real constructor is - /// `String.ofByteArray (toByteArray) (isValidUTF8)` — while - /// `String.ofList` (= deprecated `String.mk`; one canonical address) - /// is a DEFINITION that UTF-8-encodes a `List Char`: - /// - /// ```lean - /// def String.ofList (data : List Char) : String := - /// ⟨List.utf8Encode data, .intro data rfl⟩ - /// ``` - /// - /// `List.utf8Encode` / `IsValidUTF8.intro` are stuck constants here: - /// the tests assert the constructor fields stay LAZY (unevaluated - /// encodings), which is exactly what makes Str-literal iota and - /// projection cheap. `Char.ofNat` gets a simplified body - /// (`λ n. Char.mk n`) — tests only need its delta to be observable, - /// not the real `dite` validity cascade it stands in for. - fn string_env() -> (KEnv, KId, KId) { - use super::super::constant::RecRule; - let prims = Primitives::from_env(&KEnv::::new()); - let mut env = KEnv::::new(); - - let string_id = KId::::new(prims.string.addr.clone(), ()); - let of_byte_array_id = mk_id("String.ofByteArray"); - let string_rec_id = mk_id("String.rec"); - let string_const = AE::cnst(string_id.clone(), Box::new([])); - - env.insert( - string_id.clone(), - KConst::Indc { - name: (), - level_params: (), - lvls: 0, - params: 0, - indices: 0, - is_unsafe: false, - block: string_id.clone(), - member_idx: 0, - ty: sort1(), - ctors: vec![of_byte_array_id.clone()], - lean_all: (), - }, - ); - env.insert( - of_byte_array_id.clone(), - KConst::Ctor { - name: (), - level_params: (), - is_unsafe: false, - lvls: 0, - induct: string_id.clone(), - cidx: 0, - params: 0, - fields: 2, - ty: AE::all( - (), - (), - sort0(), - AE::all((), (), sort0(), string_const.clone()), - ), - }, - ); - // String.rec: 0 params, 1 motive, 1 minor, 0 indices, K = false. - // rule rhs: λ motive minor f₀ f₁. minor f₀ f₁ - let rec_rhs = AE::lam( - (), - (), - sort0(), - AE::lam( - (), - (), - sort0(), - AE::lam( - (), - (), - sort0(), - AE::lam( - (), - (), - sort0(), - apps_ae(AE::var(2, ()), &[AE::var(1, ()), AE::var(0, ())]), - ), - ), - ), - ); - env.insert( - string_rec_id.clone(), - KConst::Recr { - name: (), - level_params: (), - k: false, - is_unsafe: false, - lvls: 0, - params: 0, - indices: 0, - motives: 1, - minors: 1, - block: string_id.clone(), - member_idx: 0, - ty: sort0(), - rules: vec![RecRule { ctor: (), fields: 2, rhs: rec_rhs }], - lean_all: (), - }, - ); - - // String.ofList = λ data. String.ofByteArray (List.utf8Encode data) - // (IsValidUTF8.intro data) - let of_byte_array = AE::cnst(of_byte_array_id.clone(), Box::new([])); - let utf8_encode = AE::cnst(mk_id("List.utf8Encode"), Box::new([])); - let is_valid_intro = - AE::cnst(mk_id("ByteArray.IsValidUTF8.intro"), Box::new([])); - let list_char = app( - AE::cnst( - KId::::new(prims.list.addr.clone(), ()), - Box::new([AU::zero()]), - ), - AE::cnst(prims.char_type.clone(), Box::new([])), - ); - let of_list_val = AE::lam( - (), - (), - list_char.clone(), - apps_ae( - of_byte_array, - &[ - app(utf8_encode.clone(), AE::var(0, ())), - app(is_valid_intro, AE::var(0, ())), - ], - ), - ); - let of_list_id = KId::::new(prims.string_of_list.addr.clone(), ()); - env.insert( - of_list_id.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(1), - lvls: 0, - ty: AE::all((), (), list_char, string_const.clone()), - val: of_list_val, - lean_all: (), - block: of_list_id.clone(), - }, - ); - - // Char.ofNat with an observable delta body: λ n. Char.mk n. The - // real definition is a `dite Nat.isValidChar …` cascade; these - // tests only need to see WHETHER whnf unfolded it. - let char_of_nat_id = KId::::new(prims.char_of_nat.addr.clone(), ()); - let char_mk = - AE::cnst(KId::::new(prims.char_mk.addr.clone(), ()), Box::new([])); - let nat_ty = - AE::cnst(KId::::new(prims.nat.addr.clone(), ()), Box::new([])); - let char_ty = AE::cnst(prims.char_type.clone(), Box::new([])); - env.insert( - char_of_nat_id.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(1), - lvls: 0, - ty: AE::all((), (), nat_ty, char_ty), - val: AE::lam((), (), sort0(), app(char_mk, AE::var(0, ()))), - lean_all: (), - block: char_of_nat_id.clone(), - }, - ); - - (env, of_byte_array_id, string_rec_id) - } - - #[test] - fn whnf_string_of_list_literal_collapses_to_str() { - let (mut env, _, _) = string_env(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let of_list = - AE::cnst(tc.primitives().unwrap().string_of_list.clone(), Box::new([])); - let e = app(of_list, char_list_lit(tc.primitives().unwrap(), "ok")); - let result = tc.whnf(&e).unwrap(); - match result.data() { - ExprData::Str(s, _, _) => assert_eq!(s, "ok"), - other => panic!("expected Str literal, got {other:?}"), - } - assert!( - tc.fuel_used() < STRING_FAST_PATH_TICKS, - "constructor-built string collapse burned {} ticks", - tc.fuel_used() - ); - } - - #[test] - fn whnf_string_of_list_empty_collapses_to_empty_str() { - let (mut env, _, _) = string_env(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let of_list = - AE::cnst(tc.primitives().unwrap().string_of_list.clone(), Box::new([])); - let e = app(of_list, char_list_lit(tc.primitives().unwrap(), "")); - let result = tc.whnf(&e).unwrap(); - match result.data() { - ExprData::Str(s, _, _) => assert_eq!(s, ""), - other => panic!("expected empty Str literal, got {other:?}"), - } - } - - #[test] - fn whnf_string_of_list_multibyte_chars_collapse() { - let (mut env, _, _) = string_env(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let of_list = - AE::cnst(tc.primitives().unwrap().string_of_list.clone(), Box::new([])); - let e = app(of_list, char_list_lit(tc.primitives().unwrap(), "L∃∀N")); - let result = tc.whnf(&e).unwrap(); - match result.data() { - ExprData::Str(s, _, _) => assert_eq!(s, "L∃∀N"), - other => panic!("expected Str literal, got {other:?}"), - } - } - - #[test] - fn whnf_string_of_list_invalid_codepoint_falls_back_structurally() { - // 0xD800 is a surrogate — `Char.ofNat 0xD800` is NOT a native char - // value ('\0' structurally), so the collapse must not fire and the - // ordinary delta path must expose the ofByteArray constructor form. - let (mut env, of_byte_array_id, _) = string_env(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let char_ty = - AE::cnst(tc.primitives().unwrap().char_type.clone(), Box::new([])); - let bad_char = app( - AE::cnst(tc.primitives().unwrap().char_of_nat.clone(), Box::new([])), - mk_nat(0xD800), - ); - let nil = app( - AE::cnst( - tc.primitives().unwrap().list_nil.clone(), - Box::new([AU::zero()]), - ), - char_ty.clone(), - ); - let list = apps_ae( - AE::cnst( - tc.primitives().unwrap().list_cons.clone(), - Box::new([AU::zero()]), - ), - &[char_ty, bad_char, nil], - ); - let of_list = - AE::cnst(tc.primitives().unwrap().string_of_list.clone(), Box::new([])); - let result = tc.whnf(&app(of_list, list)).unwrap(); - let (head, args) = collect_app_spine(&result); - match head.data() { - ExprData::Const(id, _, _) => { - assert_eq!( - id.addr, of_byte_array_id.addr, - "expected the structural ofByteArray form" - ); - }, - other => panic!("expected ofByteArray ctor app, got {other:?}"), - } - assert_eq!(args.len(), 2); - assert!( - !matches!(result.data(), ExprData::Str(..)), - "invalid codepoint must not collapse to a literal" - ); - } - - #[test] - fn whnf_string_append_literals_native() { - // `String.append` is dispatched purely by address — it is - // deliberately NOT in the env, so any non-native path would leave - // the term stuck rather than produce the literal. - let (mut env, _, _) = string_env(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let append = - AE::cnst(tc.primitives().unwrap().string_append.clone(), Box::new([])); - let e = apps_ae(append, &[str_lit("hello"), str_lit("!")]); - let result = tc.whnf(&e).unwrap(); - match result.data() { - ExprData::Str(s, _, _) => assert_eq!(s, "hello!"), - other => panic!("expected Str literal, got {other:?}"), - } - assert!( - tc.fuel_used() < STRING_FAST_PATH_TICKS, - "literal append burned {} ticks", - tc.fuel_used() - ); - } - - #[test] - fn whnf_string_append_of_collapsed_arg_native() { - // The append arguments go through the shared arg-whnf, so a - // constructor-built argument collapses first and append still - // reduces natively end-to-end. - let (mut env, _, _) = string_env(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let of_list = - AE::cnst(tc.primitives().unwrap().string_of_list.clone(), Box::new([])); - let ok = app(of_list, char_list_lit(tc.primitives().unwrap(), "ok")); - let append = - AE::cnst(tc.primitives().unwrap().string_append.clone(), Box::new([])); - let e = apps_ae(append, &[ok, str_lit("!")]); - let result = tc.whnf(&e).unwrap(); - match result.data() { - ExprData::Str(s, _, _) => assert_eq!(s, "ok!"), - other => panic!("expected Str literal, got {other:?}"), - } - assert!( - tc.fuel_used() < STRING_FAST_PATH_TICKS, - "collapse + append burned {} ticks", - tc.fuel_used() - ); - } - - #[test] - fn whnf_string_append_symbolic_arg_stays_stuck() { - let (mut env, _, _) = string_env(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let append = - AE::cnst(tc.primitives().unwrap().string_append.clone(), Box::new([])); - let e = apps_ae(append, &[AE::var(0, ()), str_lit("!")]); - let result = tc.whnf(&e).unwrap(); - assert!( - !matches!(result.data(), ExprData::Str(..)), - "symbolic append must stay stuck" - ); - } - - #[test] - fn whnf_char_of_nat_literal_is_a_value() { - // `Char.ofNat 111` must NOT delta-unfold into its body during whnf - // — it IS the native char value form. - let (mut env, _, _) = string_env(); - let mut tc = TypeChecker::new(&mut env); - let e = app( - AE::cnst(tc.primitives().unwrap().char_of_nat.clone(), Box::new([])), - mk_nat(111), - ); - let result = tc.whnf(&e).unwrap(); - assert!( - result.ptr_eq(&tc.env.intern.intern_expr(e.clone())) || { - let (head, args) = collect_app_spine(&result); - matches!(head.data(), ExprData::Const(id, _, _) - if id.addr == tc.primitives().unwrap().char_of_nat.addr) - && args.len() == 1 - }, - "Char.ofNat literal should stay stuck as a value, got {result}" - ); - assert!( - tc.fuel_used() < 8, - "char value whnf burned {} ticks", - tc.fuel_used() - ); - } - - #[test] - fn whnf_char_of_nat_invalid_codepoint_unfolds() { - // Invalid codepoints are NOT native char values: structural - // reduction (which maps them to '\0') must keep working, so delta - // proceeds — observable via the simplified body's Char.mk head. - let (mut env, _, _) = string_env(); - let mut tc = TypeChecker::new(&mut env); - let e = app( - AE::cnst(tc.primitives().unwrap().char_of_nat.clone(), Box::new([])), - mk_nat(0xD800), - ); - let result = tc.whnf(&e).unwrap(); - let (head, _) = collect_app_spine(&result); - match head.data() { - ExprData::Const(id, _, _) => assert_eq!( - id.addr, - tc.primitives().unwrap().char_mk.addr, - "invalid Char.ofNat should delta-unfold structurally" - ), - other => panic!("expected Char.mk head, got {other:?}"), - } - } - - #[test] - fn whnf_string_rec_iota_on_str_literal_keeps_fields_lazy() { - // `String.rec motive minor "ok"` must iota-reduce through the - // delta-stepped expansion — and hand the minor premise the LAZY - // fields (unevaluated `List.utf8Encode [chars]`), never a forced - // byte-level encoding. - let (mut env, _, string_rec_id) = string_env(); - let utf8_encode_addr = mk_addr("List.utf8Encode"); - let mut tc = TypeChecker::new(&mut env); - let motive = AE::cnst(mk_id("TestMotive"), Box::new([])); - let minor = AE::cnst(mk_id("TestMinor"), Box::new([])); - let e = apps_ae( - AE::cnst(string_rec_id, Box::new([])), - &[motive, minor.clone(), str_lit("ok")], - ); - let result = tc.whnf(&e).unwrap(); - let (head, args) = collect_app_spine(&result); - assert!( - matches!(head.data(), ExprData::Const(id, _, _) - if id.addr == mk_addr("TestMinor")), - "expected iota to reach the minor premise, got {result}" - ); - assert_eq!(args.len(), 2, "minor should receive both ctor fields"); - let (f0_head, f0_args) = collect_app_spine(&args[0]); - match f0_head.data() { - ExprData::Const(id, _, _) => assert_eq!( - id.addr, utf8_encode_addr, - "first field should be the LAZY utf8 encoding" - ), - other => panic!("expected List.utf8Encode app, got {other:?}"), - } - // …and the lazy encoding still carries the literal char list. - assert_eq!(f0_args.len(), 1); - let (list_head, _) = collect_app_spine(&f0_args[0]); - assert!(matches!(list_head.data(), ExprData::Const(id, _, _) - if id.addr == tc.primitives().unwrap().list_cons.addr)); - assert!( - tc.fuel_used() < STRING_FAST_PATH_TICKS, - "Str-literal iota burned {} ticks", - tc.fuel_used() - ); - } - - #[test] - fn whnf_proj_on_str_literal_projects_lazy_field() { - // `Prj(String, 0, "ok")` = `.toByteArray` of a literal: projects - // the unevaluated UTF-8 encoding out of the delta-stepped - // expansion. - let (mut env, _, _) = string_env(); - let utf8_encode_addr = mk_addr("List.utf8Encode"); - let mut tc = TypeChecker::new(&mut env); - let string_kid = - KId::::new(tc.primitives().unwrap().string.addr.clone(), ()); - let e = AE::prj(string_kid, 0, str_lit("ok")); - let result = tc.whnf(&e).unwrap(); - let (head, _) = collect_app_spine(&result); - match head.data() { - ExprData::Const(id, _, _) => assert_eq!( - id.addr, utf8_encode_addr, - "projection should yield the lazy encoding" - ), - other => panic!("expected List.utf8Encode app, got {other:?}"), - } - } - - #[test] - fn def_eq_str_literal_vs_of_list_constructor_form() { - let (mut env, _, _) = string_env(); - let mut tc = TypeChecker::new(&mut env); - let of_list = - AE::cnst(tc.primitives().unwrap().string_of_list.clone(), Box::new([])); - let ctor_built = - app(of_list.clone(), char_list_lit(tc.primitives().unwrap(), "ok")); - assert!(tc.is_def_eq(&str_lit("ok"), &ctor_built).unwrap()); - assert!(tc.is_def_eq(&ctor_built, &str_lit("ok")).unwrap()); - // …and a mismatched literal is definitionally distinct. - let ctor_built_no = - app(of_list, char_list_lit(tc.primitives().unwrap(), "no")); - assert!(!tc.is_def_eq(&str_lit("ok"), &ctor_built_no).unwrap()); - } - - #[test] - fn def_eq_distinct_str_literals_reject_fast() { - let (mut env, _, _) = string_env(); - let mut tc = TypeChecker::new(&mut env); - assert!(!tc.is_def_eq(&str_lit("a"), &str_lit("b")).unwrap()); - assert!( - tc.fuel_used() < STRING_FAST_PATH_TICKS, - "literal/literal mismatch burned {} ticks", - tc.fuel_used() - ); - } - - #[test] - fn def_eq_append_vs_literal() { - // `String.append "a" "b"` ≡ `"ab"` — the native rule fires inside - // def-eq's no-delta layer. - let (mut env, _, _) = string_env(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let append = - AE::cnst(tc.primitives().unwrap().string_append.clone(), Box::new([])); - let e = apps_ae(append, &[str_lit("a"), str_lit("b")]); - assert!(tc.is_def_eq(&e, &str_lit("ab")).unwrap()); - assert!(!tc.is_def_eq(&e, &str_lit("ba")).unwrap()); - } - - #[test] - fn whnf_string_dec_eq_equal_literals_is_true() { - let (mut env, _, _) = string_env(); - // String.decEq with its real type shape so the isTrue prop can be - // recovered from inference; the body is a stuck marker — only the - // native path can produce isTrue. - let prims = Primitives::from_env(&KEnv::::new()); - let string_const = AE::cnst(prims.string.clone(), Box::new([])); - let eq_app = apps_ae( - AE::cnst(prims.eq.clone(), Box::new([AU::succ(AU::zero())])), - &[string_const.clone(), AE::var(1, ()), AE::var(0, ())], - ); - let dec_ty = AE::all( - (), - (), - string_const.clone(), - AE::all( - (), - (), - string_const.clone(), - app(AE::cnst(mk_id("Decidable"), Box::new([])), eq_app), - ), - ); - let dec_eq_id = KId::::new(prims.string_dec_eq.addr.clone(), ()); - env.insert( - dec_eq_id.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(1), - lvls: 0, - ty: dec_ty, - val: AE::cnst(mk_id("String.decEq.stuckBody"), Box::new([])), - lean_all: (), - block: dec_eq_id.clone(), - }, - ); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let e = apps_ae( - AE::cnst(tc.primitives().unwrap().string_dec_eq.clone(), Box::new([])), - &[str_lit("ok"), str_lit("ok")], - ); - let result = tc.whnf(&e).unwrap(); - let (head, args) = collect_app_spine(&result); - match head.data() { - ExprData::Const(id, _, _) => { - assert_eq!(id.addr, tc.primitives().unwrap().decidable_is_true.addr); - }, - other => panic!("expected Decidable.isTrue, got {other:?}"), - } - assert_eq!(args.len(), 2); - // Witness: Eq.refl.{1} String "ok" - let (refl_head, refl_args) = collect_app_spine(&args[1]); - assert!(matches!(refl_head.data(), ExprData::Const(id, _, _) - if id.addr == tc.primitives().unwrap().eq_refl.addr)); - assert_eq!(refl_args.len(), 2); - assert!(matches!(refl_args[1].data(), ExprData::Str(s, _, _) if s == "ok")); - } - - #[test] - fn whnf_string_dec_eq_unequal_literals_falls_back() { - // No native isFalse witness — unequal literals must leave the - // structural path in charge (here: the stuck marker body). - let (mut env, _, _) = string_env(); - let prims = Primitives::from_env(&KEnv::::new()); - let dec_eq_id = KId::::new(prims.string_dec_eq.addr.clone(), ()); - env.insert( - dec_eq_id.clone(), - KConst::Defn { - name: (), - level_params: (), - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - hints: ReducibilityHints::Regular(1), - lvls: 0, - ty: sort0(), - val: AE::cnst(mk_id("String.decEq.stuckBody"), Box::new([])), - lean_all: (), - block: dec_eq_id.clone(), - }, - ); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let e = apps_ae( - AE::cnst(tc.primitives().unwrap().string_dec_eq.clone(), Box::new([])), - &[str_lit("ok"), str_lit("no")], - ); - let result = tc.whnf(&e).unwrap(); - let (head, _) = collect_app_spine(&result); - assert!( - matches!(head.data(), ExprData::Const(id, _, _) - if id.addr == mk_addr("String.decEq.stuckBody")), - "unequal decEq should have delta-unfolded structurally, got {result}" - ); - } - - #[test] - fn whnf_string_back_result_stays_native_char_value() { - // `String.back "abc"` reduces natively to `Char.ofNat 99` — and the - // char-value guard must keep THAT stuck instead of delta-unfolding - // it into the validity cascade. - let (mut env, _, _) = string_env(); - let mut tc = TypeChecker::new(&mut env); - tc.enable_extra_reductions(); - let back = - AE::cnst(tc.primitives().unwrap().string_back.clone(), Box::new([])); - let result = tc.whnf(&app(back, str_lit("abc"))).unwrap(); - let (head, args) = collect_app_spine(&result); - assert!(matches!(head.data(), ExprData::Const(id, _, _) - if id.addr == tc.primitives().unwrap().char_of_nat.addr)); - assert_eq!(args.len(), 1); - assert!(matches!(args[0].data(), ExprData::Nat(v, _, _) - if v.0 == num_bigint::BigUint::from(99u64))); - } -} - -#[cfg(test)] -#[path = "bound_primitive_tests.rs"] -mod bound_primitive_tests; diff --git a/crates/kernel/tests/fixtures/input_contracts/linear-match.const b/crates/kernel/tests/fixtures/input_contracts/linear-match.const deleted file mode 100644 index fb7b4e844..000000000 Binary files a/crates/kernel/tests/fixtures/input_contracts/linear-match.const and /dev/null differ diff --git a/crates/kernel/tests/fixtures/input_contracts/locality-mismatch.const b/crates/kernel/tests/fixtures/input_contracts/locality-mismatch.const deleted file mode 100644 index 2716aed29..000000000 Binary files a/crates/kernel/tests/fixtures/input_contracts/locality-mismatch.const and /dev/null differ diff --git a/crates/kernel/tests/fixtures/input_contracts/many-match.const b/crates/kernel/tests/fixtures/input_contracts/many-match.const deleted file mode 100644 index 0937f4e04..000000000 Binary files a/crates/kernel/tests/fixtures/input_contracts/many-match.const and /dev/null differ diff --git a/crates/kernel/tests/fixtures/input_contracts/ownership-mismatch.const b/crates/kernel/tests/fixtures/input_contracts/ownership-mismatch.const deleted file mode 100644 index 9841a06e3..000000000 Binary files a/crates/kernel/tests/fixtures/input_contracts/ownership-mismatch.const and /dev/null differ diff --git a/crates/kernel/tests/fixtures/input_contracts/result-only-unique.const b/crates/kernel/tests/fixtures/input_contracts/result-only-unique.const deleted file mode 100644 index 1472dc787..000000000 Binary files a/crates/kernel/tests/fixtures/input_contracts/result-only-unique.const and /dev/null differ diff --git a/crates/kernel/tests/fixtures/input_contracts/uses-mismatch.const b/crates/kernel/tests/fixtures/input_contracts/uses-mismatch.const deleted file mode 100644 index 6d6edf612..000000000 Binary files a/crates/kernel/tests/fixtures/input_contracts/uses-mismatch.const and /dev/null differ diff --git a/crates/kernel/tests/fixtures/role_admission/.gitignore b/crates/kernel/tests/fixtures/role_admission/.gitignore deleted file mode 100644 index fef14c23b..000000000 --- a/crates/kernel/tests/fixtures/role_admission/.gitignore +++ /dev/null @@ -1 +0,0 @@ -!*.ixe diff --git a/crates/kernel/tests/fixtures/role_admission/README.txt b/crates/kernel/tests/fixtures/role_admission/README.txt deleted file mode 100644 index 24d6536ff..000000000 --- a/crates/kernel/tests/fixtures/role_admission/README.txt +++ /dev/null @@ -1,9 +0,0 @@ -basis.ixe: the closure of the Nat, Bool, Eq, PUnit, Empty, False, Quot, And, -True, Iff and Nonempty blocks, of propext, Classical.choice and sorryAx, and of -the structural Nat operations (Nat.pred, Nat.add, Nat.sub, Nat.mul, Nat.pow, -Nat.beq, Nat.ble), extracted with `ix shard extract` from the bound Init+Std -artifact -(sha256 0d21dc6d906a86646deea50629d3367c2ab356dcd9a760c08fcf774ea389efd1) -and written back without the artifact's role binding. It carries the -compiler's records and names (without metadata, so only anonymous checks -apply); tests bind roles themselves. diff --git a/crates/kernel/tests/fixtures/role_admission/basis.ixe b/crates/kernel/tests/fixtures/role_admission/basis.ixe deleted file mode 100644 index a2bbccc64..000000000 Binary files a/crates/kernel/tests/fixtures/role_admission/basis.ixe and /dev/null differ diff --git a/crates/kernel/tests/fixtures/serialized_units/.gitignore b/crates/kernel/tests/fixtures/serialized_units/.gitignore deleted file mode 100644 index fef14c23b..000000000 --- a/crates/kernel/tests/fixtures/serialized_units/.gitignore +++ /dev/null @@ -1 +0,0 @@ -!*.ixe diff --git a/crates/kernel/tests/fixtures/serialized_units/README.txt b/crates/kernel/tests/fixtures/serialized_units/README.txt deleted file mode 100644 index a3e46058b..000000000 --- a/crates/kernel/tests/fixtures/serialized_units/README.txt +++ /dev/null @@ -1,6 +0,0 @@ -Canonical serialized-unit regression inputs measured before the fix. -The definition/family mixes and the family with a malformed recursor were -accepted through their valid primary while a sibling failed separately. -Each file is reference-free and carries no names, blobs, roles or assumptions. -Fixtures retain the original bytes; checking is through actual Ixon ingress, -work enumeration and the complete-unit checker, with fresh/reused workers. diff --git a/crates/kernel/tests/fixtures/serialized_units/family-bad-recursor.ixe b/crates/kernel/tests/fixtures/serialized_units/family-bad-recursor.ixe deleted file mode 100644 index 0799c226e..000000000 Binary files a/crates/kernel/tests/fixtures/serialized_units/family-bad-recursor.ixe and /dev/null differ diff --git a/crates/kernel/tests/fixtures/serialized_units/family-good.ixe b/crates/kernel/tests/fixtures/serialized_units/family-good.ixe deleted file mode 100644 index be50c9587..000000000 Binary files a/crates/kernel/tests/fixtures/serialized_units/family-good.ixe and /dev/null differ diff --git a/crates/kernel/tests/fixtures/serialized_units/homogeneous-bad-sibling.ixe b/crates/kernel/tests/fixtures/serialized_units/homogeneous-bad-sibling.ixe deleted file mode 100644 index 2bf7e46fd..000000000 Binary files a/crates/kernel/tests/fixtures/serialized_units/homogeneous-bad-sibling.ixe and /dev/null differ diff --git a/crates/kernel/tests/fixtures/serialized_units/homogeneous-good.ixe b/crates/kernel/tests/fixtures/serialized_units/homogeneous-good.ixe deleted file mode 100644 index 04374925d..000000000 Binary files a/crates/kernel/tests/fixtures/serialized_units/homogeneous-good.ixe and /dev/null differ diff --git a/crates/kernel/tests/fixtures/serialized_units/mixed-bad-primary.ixe b/crates/kernel/tests/fixtures/serialized_units/mixed-bad-primary.ixe deleted file mode 100644 index e5a833175..000000000 Binary files a/crates/kernel/tests/fixtures/serialized_units/mixed-bad-primary.ixe and /dev/null differ diff --git a/crates/kernel/tests/fixtures/serialized_units/mixed-bad-sibling.ixe b/crates/kernel/tests/fixtures/serialized_units/mixed-bad-sibling.ixe deleted file mode 100644 index df26425d3..000000000 Binary files a/crates/kernel/tests/fixtures/serialized_units/mixed-bad-sibling.ixe and /dev/null differ diff --git a/crates/kernel/tests/fixtures/serialized_units/mixed-good.ixe b/crates/kernel/tests/fixtures/serialized_units/mixed-good.ixe deleted file mode 100644 index 8ebf6cd8f..000000000 Binary files a/crates/kernel/tests/fixtures/serialized_units/mixed-good.ixe and /dev/null differ diff --git a/crates/kernel/tests/guarded_artifacts.rs b/crates/kernel/tests/guarded_artifacts.rs deleted file mode 100644 index 94be65ed0..000000000 --- a/crates/kernel/tests/guarded_artifacts.rs +++ /dev/null @@ -1,415 +0,0 @@ -//! Actual owned-worker and physical/semantic identity boundaries. - -use std::sync::Arc; - -use ix_common::address::Address; -use ix_common::env::Name; -use ix_kernel::anon_work::{build_artifact_work, closure_addrs}; -use ix_kernel::artifact_checker::ArtifactChecker; -use ix_kernel::mode::{Anon, Meta}; -use ix_kernel::role_admission::{ArtifactSource, PRELUDE, SPEC_VERSION}; -use ixon::constant::{ - Constant, ConstantInfo, MutConst, defn_proj_address, defn_proj_constant, -}; -use ixon::env::{Env, Named, RoleBinding}; -use ixon::metadata::{ConstantMeta, ConstantMetaInfo, ExprMeta}; -use ixon::role_table::{Role, RoleTable}; - -const GOOD: &[u8] = - include_bytes!("fixtures/serialized_units/homogeneous-good.ixe"); -const BASIS: &[u8] = include_bytes!("fixtures/role_admission/basis.ixe"); -const BAD: &[u8] = - include_bytes!("fixtures/serialized_units/homogeneous-bad-sibling.ixe"); - -fn loaded(bytes: &[u8]) -> Env { - let mut input = bytes; - let env = Env::get(&mut input).unwrap(); - assert!(input.is_empty()); - env -} - -fn owner(env: &Env) -> (Address, Arc) { - env - .consts - .iter() - .find_map(|entry| { - let record = entry.value().get().unwrap(); - matches!(record.info, ConstantInfo::Muts(_)) - .then(|| (entry.key().clone(), record)) - }) - .unwrap() -} - -/// Bind the prelude as a compiled artifact does: merge the compiled prelude's -/// records into `env` and bind every prelude role to them, so the bound -/// worker checks `env`'s own units. -fn prelude_binding(env: &mut Env) { - let basis = loaded(BASIS); - for entry in basis.consts.iter() { - env.consts.insert(entry.key().clone(), entry.value().clone()); - } - for entry in basis.blobs.iter() { - env.blobs.insert(entry.key().clone(), entry.value().clone()); - } - let mut bound: Vec<(Role, Address)> = PRELUDE - .iter() - .map(|role| { - let name = role - .lean_name() - .split('.') - .fold(Name::anon(), |prefix, part| Name::str(prefix, part.into())); - (*role, basis.lookup_name(&name).unwrap().addr) - }) - .collect(); - bound.sort_by_key(|(role, _)| role.index()); - let raw = RoleTable { version: SPEC_VERSION, bound, evidence: vec![] } - .to_bytes() - .unwrap(); - env.role_binding = - Some(RoleBinding { address: Address::hash(&raw), bytes: raw.into() }); -} - -fn name(label: &str) -> Name { - Name::str(Name::anon(), label.into()) -} -fn name_address(value: &Name) -> Address { - Address::from_blake3_hash(*value.get_hash()) -} - -fn name_definitions( - env: &Env, - owner: &Address, - record: &Constant, - prefix: &str, -) -> Vec { - let ConstantInfo::Muts(members) = &record.info else { - panic!("expected owner") - }; - let names: Vec<_> = - (0..members.len()).map(|i| name(&format!("{prefix}{i}"))).collect(); - let addresses: Vec<_> = names.iter().map(name_address).collect(); - for (i, member) in members.iter().enumerate() { - assert!(matches!(member, MutConst::Defn(_))); - env.store_name(addresses[i].clone(), names[i].clone()); - env.register_name( - names[i].clone(), - Named::new( - defn_proj_address(u64::try_from(i).unwrap(), owner), - ConstantMeta { - info: ConstantMetaInfo::Def { - name: addresses[i].clone(), - lvls: vec![], - all: addresses.clone(), - ctx: addresses.clone(), - arena: ExprMeta::default(), - type_root: 0, - value_root: 0, - }, - ..ConstantMeta::default() - }, - ), - ); - } - let root = name(&format!("{prefix}Owner")); - env.store_name(name_address(&root), root.clone()); - env.register_name( - root, - Named::new( - owner.clone(), - ConstantMeta { - info: ConstantMetaInfo::Muts { - all: addresses.into_iter().map(|a| vec![a]).collect(), - aux_layout: None, - }, - ..ConstantMeta::default() - }, - ), - ); - names -} - -#[test] -fn metadata_cannot_relabel_a_real_owner_payload_as_another_units_member() { - let mut env = loaded(GOOD); - let bad = loaded(BAD); - let (good_owner, good_record) = owner(&env); - let (bad_owner, bad_record) = owner(&bad); - assert_ne!(good_owner, bad_owner); - for entry in &bad.consts { - env.consts.insert(entry.key().clone(), entry.value().clone()); - } - let good_names = name_definitions(&env, &good_owner, &good_record, "good"); - let bad_names = name_definitions(&env, &bad_owner, &bad_record, "bad"); - prelude_binding(&mut env); - let baseline = ArtifactSource::snapshot(&env).unwrap(); - let mut baseline = ArtifactChecker::::from_named_source(Ok(baseline)); - assert!(baseline.check_name(&good_names[0]).is_ok()); - assert!(baseline.check_name(&bad_names[1]).is_err()); - - let mut forged = env.lookup_name(&good_names[1]).unwrap(); - forged.addr = defn_proj_address(1, &bad_owner); - env.register_name(good_names[1].clone(), forged); - let source = ArtifactSource::snapshot(&env).unwrap(); - let cold = ArtifactChecker::::from_named_source(Ok(source.clone())) - .check_name(&good_names[1]) - .is_ok(); - let mut warm = ArtifactChecker::::from_named_source(Ok(source)); - let primed = warm.check_name(&good_names[0]).is_ok(); - let after_prime = warm.check_name(&good_names[1]).is_ok(); - eprintln!( - "metadata substitution: cold={cold}, prime={primed}, after_prime={after_prime}" - ); - assert!(!cold, "invalid actual target unexpectedly checks cold"); - assert!(!primed, "optional metadata relabeled a member of another raw owner"); - assert!( - !after_prime, - "a relabeled payload contaminated the same-source declaration cache" - ); -} - -#[test] -fn owner_checks_accept_each_physical_projection_subset() { - for retained in [vec![], vec![1], vec![0, 1]] { - let mut env = loaded(GOOD); - let (owner, _) = owner(&env); - let targets = [defn_proj_address(0, &owner), defn_proj_address(1, &owner)]; - for (index, target) in targets.iter().enumerate() { - if !retained.contains(&index) { - env.consts.remove(target); - } - } - prelude_binding(&mut env); - let source = ArtifactSource::snapshot(&env).unwrap(); - // The owner's unit is one work item; the rest are the prelude's. - let work: Vec<_> = build_artifact_work(&source) - .unwrap() - .into_iter() - .filter(|item| item.targets().iter().any(|t| targets.contains(t))) - .collect(); - assert_eq!(work.len(), 1); - assert_eq!(work[0].targets(), targets); - let mut checker = ArtifactChecker::::from_source(Ok(source.clone())); - for (index, target) in targets.iter().enumerate() { - if !retained.contains(&index) { - assert!(source.record(target).is_err()); - assert!(checker.check_const(target).is_err()); - } - } - checker.check_work(&work[0]).unwrap(); - checker.check_work(&work[0]).unwrap(); - for (index, target) in targets.iter().enumerate() { - if !retained.contains(&index) { - assert!(checker.check_const(target).is_err()); - } - } - } -} - -#[test] -fn physical_coverage_rejects_orphans_and_invalid_carried_projections() { - let mut orphan = loaded(GOOD); - let (owner, _) = owner(&orphan); - orphan.consts.remove(&owner); - prelude_binding(&mut orphan); - assert!( - build_artifact_work(&ArtifactSource::snapshot(&orphan).unwrap()).is_err() - ); - - let mut env = loaded(GOOD); - let invalid = defn_proj_constant(99, owner); - let address = invalid.commit().0; - env.store_const(address, invalid); - prelude_binding(&mut env); - assert!( - build_artifact_work(&ArtifactSource::snapshot(&env).unwrap()).is_err() - ); -} - -#[test] -fn selected_closure_keeps_the_owners_other_projection_edges() { - let mut env = loaded(GOOD); - let (owner, _) = owner(&env); - let primary = defn_proj_address(0, &owner); - let other = defn_proj_address(1, &owner); - prelude_binding(&mut env); - let source = ArtifactSource::snapshot(&env).unwrap(); - let roots = [other]; - let expected = closure_addrs(&env, &roots); - let actual = source.closure(&roots).unwrap(); - assert!(actual.contains(&owner) && actual.contains(&primary)); - assert_eq!(actual.len(), expected.len()); - assert!(expected.iter().all(|address| actual.contains(address))); -} - -#[test] -fn cached_owner_members_do_not_authorize_absent_external_references() { - for carry_projection in [true, false] { - let mut env = loaded(GOOD); - let (owner, record) = owner(&env); - let ConstantInfo::Muts(members) = &record.info else { unreachable!() }; - let MutConst::Defn(definition) = &members[0] else { unreachable!() }; - assert_eq!(definition.lvls, 0); - let target = defn_proj_address(0, &owner); - let mut dependent = definition.clone(); - dependent.value = Arc::new(ixon::expr::Expr::Ref( - u64::try_from(record.refs.len()).unwrap(), - vec![], - )); - let mut dependent = Constant::new(ConstantInfo::Defn(dependent)); - dependent.refs = record.refs.clone(); - dependent.refs.push(target.clone()); - dependent.univs = record.univs.clone(); - dependent.sharing = record.sharing.clone(); - let address = dependent.commit().0; - env.store_const(address.clone(), dependent); - if !carry_projection { - env.consts.remove(&target); - } - prelude_binding(&mut env); - let source = ArtifactSource::snapshot(&env).unwrap(); - let mut checker = ArtifactChecker::::from_source(Ok(source)); - checker.check_owner(&owner).unwrap(); - assert_eq!(checker.check_const(&address).is_ok(), carry_projection); - } -} - -/// Register `label` as a named standalone definition at `address` whose -/// metadata gives the mutual context `ctx` and the expression arena `arena`. -fn name_standalone( - env: &Env, - label: &str, - address: &Address, - ctx: Vec
, - arena: ExprMeta, - value_root: u64, -) -> Name { - let named = name(label); - let own = name_address(&named); - env.store_name(own.clone(), named.clone()); - env.register_name( - named.clone(), - Named::new( - address.clone(), - ConstantMeta { - info: ConstantMetaInfo::Def { - name: own.clone(), - lvls: vec![], - all: vec![own], - ctx, - arena, - type_root: 0, - value_root, - }, - ..ConstantMeta::default() - }, - ), - ); - named -} - -/// A standalone definition with member 0's type and the given value. -fn standalone_like_member_0( - record: &Constant, - value: ixon::expr::Expr, - extra_ref: Option
, -) -> Constant { - let ConstantInfo::Muts(members) = &record.info else { unreachable!() }; - let MutConst::Defn(definition) = &members[0] else { unreachable!() }; - let mut definition = definition.clone(); - definition.value = Arc::new(value); - let mut constant = Constant::new(ConstantInfo::Defn(definition)); - constant.refs = record.refs.clone(); - constant.refs.extend(extra_ref); - constant.univs = record.univs.clone(); - constant.sharing = record.sharing.clone(); - constant -} - -/// `Expr::Rec` denotes a member of the serialized unit, never a constant -/// that the metadata's mutual context names. A standalone is its own -/// singleton unit, so `Rec(1)` is out of range in both modes, even though -/// the metadata context `[f, member 0]` would resolve it to a well-typed -/// constant. -#[test] -fn metadata_context_cannot_select_recursive_targets() { - let mut env = loaded(GOOD); - let (owner, record) = owner(&env); - let members = name_definitions(&env, &owner, &record, "good"); - let f = - standalone_like_member_0(&record, ixon::expr::Expr::Rec(1, vec![]), None); - let f_address = f.commit().0; - env.store_const(f_address.clone(), f); - let f_label = name("f"); - let f_name = name_standalone( - &env, - "f", - &f_address, - vec![name_address(&f_label), name_address(&members[0])], - ExprMeta::default(), - 0, - ); - prelude_binding(&mut env); - let source = ArtifactSource::snapshot(&env).unwrap(); - let mut named = - ArtifactChecker::::from_named_source(Ok(source.clone())); - assert!(named.check_name(&members[0]).is_ok(), "the aliased member is valid"); - let error = named.check_name(&f_name).unwrap_err(); - assert!( - format!("{error}").contains("invalid Rec index 1"), - "named ingress took a recursive target from metadata: {error}" - ); - let mut anon = ArtifactChecker::::from_source(Ok(source)); - assert!(anon.check_const(&f_address).is_err()); -} - -/// A reference must name an authentic carried record. Here the bytes under -/// projection 0's key are projection 1's, so the reference fails -/// authentication whether or not a worker has already installed the owner's -/// genuine members; installing them also checks every carried projection, so -/// the sibling's own check refuses the unit. -#[test] -fn present_corrupt_projection_is_authenticated_before_reuse() { - for corrupt in [false, true] { - let mut env = loaded(GOOD); - let (owner, record) = owner(&env); - let members = name_definitions(&env, &owner, &record, "good"); - let target = defn_proj_address(0, &owner); - let index = u64::try_from(record.refs.len()).unwrap(); - let dependent = standalone_like_member_0( - &record, - ixon::expr::Expr::Ref(index, vec![]), - Some(target.clone()), - ); - let address = dependent.commit().0; - env.store_const(address.clone(), dependent); - let mut arena = ExprMeta::default(); - arena.alloc(ixon::metadata::ExprMetaData::Leaf); - let value_root = arena.alloc(ixon::metadata::ExprMetaData::Ref { - name: name_address(&members[0]), - }); - let dependent_name = name_standalone( - &env, - "dependent", - &address, - vec![name_address(&name("dependent"))], - arena, - value_root, - ); - if corrupt { - let other = - env.consts.get(&defn_proj_address(1, &owner)).unwrap().value().clone(); - env.consts.insert(target.clone(), other); - } - prelude_binding(&mut env); - let source = ArtifactSource::snapshot(&env).unwrap(); - let cold = ArtifactChecker::::from_named_source(Ok(source.clone())) - .check_name(&dependent_name) - .is_ok(); - let mut warm = ArtifactChecker::::from_named_source(Ok(source)); - let primed = warm.check_name(&members[1]).is_ok(); - let after_prime = warm.check_name(&dependent_name).is_ok(); - assert_eq!(cold, !corrupt, "cold dependent (corrupt={corrupt})"); - assert_eq!(primed, !corrupt, "sibling owning a corrupt projection"); - assert_eq!(after_prime, !corrupt, "warm dependent (corrupt={corrupt})"); - } -} diff --git a/crates/kernel/tests/input_contracts.rs b/crates/kernel/tests/input_contracts.rs deleted file mode 100644 index 6598a7b28..000000000 --- a/crates/kernel/tests/input_contracts.rs +++ /dev/null @@ -1,384 +0,0 @@ -//! Canonical byte-boundary regressions for C+ input-contract conversion. -//! These do not invoke the separate resource validator. - -use ix_kernel::error::TcError; -use ix_kernel::ixon_checker::IxonChecker; -use ixon::constant::ConstantInfo; -use ixon::env::Env; -use ixon::expr::Expr; - -struct Case { - name: &'static str, - envelope: &'static [u8], - record: &'static [u8], - address: &'static str, - declared_input: u8, - value_input: u8, - result: u8, -} - -macro_rules! case { - ($name:literal, $address:literal, $declared:expr, $value:expr, $result:expr) => { - Case { - name: $name, - envelope: include_bytes!(concat!( - "fixtures/input_contracts/", - $name, - ".ixe" - )), - record: include_bytes!(concat!( - "fixtures/input_contracts/", - $name, - ".const" - )), - address: $address, - declared_input: $declared, - value_input: $value, - result: $result, - } - }; -} - -const MANY: Case = case!( - "many-match", - "9f57525dcb75ece681f20bbaf3ae34b3707bd2e380208ac9a2da5dfdff6f0d67", - 7, - 7, - 1 -); -const LINEAR: Case = case!( - "linear-match", - "49953b97a0a191ee7429caae22e82888f711791e527abc5a7c6c9fc662a2efbd", - 5, - 5, - 1 -); -const USES: Case = case!( - "uses-mismatch", - "cccdb41c603b9f57b65c8e5b41dced4e79b8d33b7467e566865c86b12a01a35a", - 5, - 7, - 1 -); -const OWNERSHIP: Case = case!( - "ownership-mismatch", - "5a099a47c3c0757d8216e43cdfea8fbb60e94243114045f77dacb8a311d44fea", - 3, - 7, - 1 -); -const LOCAL_MISMATCH: Case = case!( - "locality-mismatch", - "d1966bf100c57bc5d74bc4fab3837f0d8f52884f9259dedd3a865d5779e676c8", - 15, - 7, - 1 -); -const RESULT: Case = case!( - "result-only-unique", - "1c6192680f32a84a63b999cc3a61b372bbe3cce6f640c80037539f430d36012c", - 5, - 5, - 0 -); - -fn check(case: &Case, accepts: bool) { - assert_eq!(case.envelope.len(), 122); - assert_eq!(case.record.len(), 15); - let mut input = case.envelope; - let decoded = Env::get(&mut input).expect("canonical envelope"); - assert!(input.is_empty()); - assert_eq!(decoded.consts.len(), 1); - assert!( - decoded.blobs.is_empty() - && decoded.named.is_empty() - && decoded.assumptions.is_empty() - ); - let address = decoded.consts.iter().next().unwrap().key().clone(); - assert_eq!(address.hex(), case.address); - let record = decoded.get_const(&address).expect("committed record"); - assert!(record.refs.is_empty() && record.sharing.is_empty()); - assert_eq!(record.commit(), (address.clone(), case.record.to_vec())); - let ConstantInfo::Defn(definition) = &record.info else { - panic!("definition fixture") - }; - let Expr::All(declared, result, domain, codomain) = definition.typ.as_ref() - else { - panic!("Pi type") - }; - let Expr::Lam(value, lambda_domain, body) = definition.value.as_ref() else { - panic!("identity lambda") - }; - assert_eq!(declared.to_bits(), case.declared_input); - assert_eq!(value.to_bits(), case.value_input); - assert_eq!(result.to_bits(), case.result); - assert_eq!(domain.as_ref(), &Expr::Sort(0)); - assert_eq!(codomain.as_ref(), &Expr::Sort(0)); - assert_eq!(lambda_domain, domain); - assert_eq!(body.as_ref(), &Expr::Var(0)); - let mut roundtrip = Vec::new(); - decoded.put(&mut roundtrip).unwrap(); - assert_eq!(roundtrip, case.envelope); - let result = IxonChecker::new(&decoded).check_const(&address); - if accepts { - assert!(result.is_ok(), "{}: {result:?}", case.name); - } else { - assert!( - matches!(&result, Err(TcError::DeclTypeMismatch)), - "{}: {result:?}", - case.name - ); - } -} - -#[test] -fn matching_many_input_accepts() { - check(&MANY, true); -} - -#[test] -fn matching_linear_input_accepts() { - check(&LINEAR, true); -} - -#[test] -fn mismatched_input_usage_rejects() { - check(&USES, false); -} - -#[test] -fn mismatched_input_ownership_rejects() { - check(&OWNERSHIP, false); -} - -#[test] -fn mismatched_input_locality_rejects() { - check(&LOCAL_MISMATCH, false); -} - -#[test] -fn result_only_difference_accepts() { - check(&RESULT, true); -} - -#[test] -fn optional_metadata_is_transport_only_and_erasure_preserves_the_verdict() { - use ix_common::address::Address; - use ix_common::env::{BinderInfo, DataValue, Name}; - use ix_kernel::ingress::ixon_ingress; - use ix_kernel::mode::{Anon, Meta}; - use ixon::contract::source::decode; - use ixon::env::Named; - use ixon::metadata::{ - ConstantMeta, ConstantMetaInfo, DataValue as WireValue, ExprMeta, - ExprMetaData, - }; - - for (case, accepts) in [(&MANY, true), (&USES, false)] { - // A display-only layer is accepted; even an agreeing semantic layer is - // refused by Meta transport. Neither changes anonymous logical admission. - for semantic in [None, Some(23), Some(21)] { - let mut input = case.envelope; - let env = Env::get(&mut input).unwrap(); - let address = env.consts.iter().next().unwrap().key().clone(); - let name = Name::str(Name::anon(), "ContractProbe".into()); - let name_addr = Address::from_blake3_hash(*name.get_hash()); - env.store_name(name_addr.clone(), name.clone()); - let data = semantic.map_or_else( - || { - vec![( - Name::str(Name::anon(), "display".into()), - DataValue::OfBool(true), - )] - }, - |code| decode(1, code).unwrap().metadata(), - ); - let kvs = data - .into_iter() - .map(|(key, value)| { - let addr = Address::from_blake3_hash(*key.get_hash()); - env.store_name(addr.clone(), key); - let value = match value { - DataValue::OfNat(n) => { - WireValue::OfNat(env.store_blob(n.to_le_bytes())) - }, - DataValue::OfBool(b) => WireValue::OfBool(b), - _ => unreachable!(), - }; - (addr, value) - }) - .collect(); - let arena = ExprMeta { - nodes: vec![ - ExprMetaData::Leaf, - ExprMetaData::Binder { - name: name_addr.clone(), - info: BinderInfo::Default, - children: [0, 0], - }, - ExprMetaData::Mdata { mdata: vec![kvs], child: 1 }, - ], - }; - env.register_name( - name, - Named::new( - address.clone(), - ConstantMeta::new(ConstantMetaInfo::Def { - name: name_addr, - lvls: vec![], - all: vec![], - ctx: vec![], - arena, - type_root: 2, - value_root: 1, - }), - ), - ); - let mut bytes = Vec::new(); - env.put(&mut bytes).unwrap(); - let mut input = bytes.as_slice(); - let full = Env::get(&mut input).unwrap(); - assert!(input.is_empty()); - assert_eq!(full.get_const(&address).unwrap().commit().1, case.record); - let meta = ixon_ingress::(&full); - if semantic.is_some() { - assert!( - matches!(&meta, Err(error) if error.contains("semantic contracts cannot come from optional metadata")), - "{:?}", - meta.err() - ); - } else { - assert!(meta.is_ok(), "{:?}", meta.err()); - } - assert!(ixon_ingress::(&full).is_ok()); - assert_eq!( - IxonChecker::new(&full).check_const(&address).is_ok(), - accepts - ); - // get_anon intentionally leaves the optional metadata section unread. - let anon = Env::get_anon(&mut bytes.as_slice()).unwrap(); - assert!(anon.named.is_empty()); - assert_eq!(anon.get_const(&address).unwrap().commit().1, case.record); - assert_eq!( - IxonChecker::new(&anon).check_const(&address).is_ok(), - accepts - ); - } - } -} -#[test] -fn raw_contracted_inductive_keeps_the_existing_recursor_synthesis_policy() { - use ix_kernel::constant::KConst; - use ix_kernel::env::KEnv; - use ix_kernel::expr::ExprData; - use ix_kernel::id::KId; - use ix_kernel::mode::Anon; - use ix_kernel::tc::TypeChecker; - use ixon::constant::{ - Constant, Inductive, MutConst, Recursor, indc_proj_constant, - }; - use ixon::contract::{BinderContract, ValueContract}; - use ixon::univ::Univ; - - // A raw empty family (A : Type) : Type with a linear input is not a - // source-compiler fixture. Its recursor is written independently here. - // Generated telescope binders keep the ordinary defaults, matching - // IxC's replacePisPw/pisToLamsPw; no new recursor schema is introduced. - for (rec_input, result, accepts) in - [(7, 1, true), (5, 1, false), (7, 0, true)] - { - let env = Env::new(); - let family = Constant::with_tables( - ConstantInfo::Muts(vec![MutConst::Indc(Inductive { - is_unsafe: false, - lvls: 0, - params: 1, - indices: 0, - typ: Expr::all_contract( - BinderContract::from_bits(5).unwrap(), - ValueContract::shared(), - Expr::sort(1), - Expr::sort(1), - ), - ctors: vec![], - })]), - vec![], - vec![], - vec![Univ::zero(), Univ::succ(Univ::zero())], - ); - let family_block = family.commit().0; - env.store_const(family_block.clone(), family); - let projection = indc_proj_constant(0, family_block.clone()); - let family_addr = projection.commit().0; - env.store_const(family_addr.clone(), projection); - let applied_family = - |i| Expr::app(Expr::reference(0, vec![]), Expr::var(i)); - let motive = Expr::all(applied_family(0), Expr::sort(2)); - let typ = Expr::all_contract( - BinderContract::from_bits(rec_input).unwrap(), - ValueContract::from_bits(result).unwrap(), - Expr::sort(1), - Expr::all( - motive, - Expr::all(applied_family(1), Expr::app(Expr::var(1), Expr::var(0))), - ), - ); - let recursor = Constant::with_tables( - ConstantInfo::Recr(Recursor { - k: false, - is_unsafe: false, - lvls: 1, - params: 1, - indices: 0, - motives: 1, - minors: 0, - typ, - rules: vec![], - }), - vec![], - vec![family_addr.clone()], - vec![Univ::zero(), Univ::succ(Univ::zero()), Univ::var(0)], - ); - let rec_addr = recursor.commit().0; - env.store_const(rec_addr.clone(), recursor); - let mut bytes = Vec::new(); - env.put(&mut bytes).unwrap(); - let mut input = bytes.as_slice(); - let decoded = Env::get(&mut input).unwrap(); - assert!(input.is_empty()); - let mut kernel = KEnv::::new(); - let mut tc = TypeChecker::new_with_lazy_anon(&mut kernel, &decoded); - let family_id = KId::new(family_addr.clone(), ()); - let KConst::Indc { ty, .. } = tc.get_const(&family_id).unwrap() else { - panic!("raw family must remain an inductive") - }; - assert!( - matches!(ty.data(), ExprData::All(_, _, c, _, _, _, _) if c.to_bits() == 5) - ); - tc.check_const(&family_id).unwrap(); - let generated = - tc.env.recursor_cache.get(&KId::new(family_block, ())).unwrap(); - assert_eq!(generated.len(), 1); - assert!( - matches!(generated[0].ty.data(), ExprData::All(_, _, c, r, _, _, _) - if *c == BinderContract::default() && *r == ValueContract::shared()) - ); - let mut checker = IxonChecker::new(&decoded); - checker.check_const(&family_addr).unwrap(); - let verdict = checker.check_const(&rec_addr); - if accepts { - assert!(verdict.is_ok(), "{verdict:?}"); - } else { - // Selection requires a complete type match before the later per-rule - // checks, so a changed input leaves no matching generated recursor. - let expected = format!( - "check_recursor: no generated recursor for major {}", - &family_addr.hex()[..8] - ); - assert!( - matches!(&verdict, Err(TcError::Other(message)) if message == &expected), - "{verdict:?}" - ); - } - } -} diff --git a/crates/kernel/tests/reject_parity.rs b/crates/kernel/tests/reject_parity.rs deleted file mode 100644 index 6f99e65e1..000000000 --- a/crates/kernel/tests/reject_parity.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! The `.ixes` cases of the shared reject-parity fixture -//! (`Tests/Fixtures/ixon-v4/reject-parity.txt`): every manifest the Lean -//! parser (`parseIxesManifest`, the `ixon-reject-parity` suite) refuses, the -//! Rust reader refuses too. - -use ix_kernel::shard::ShardManifest; - -const FIXTURE: &str = - include_str!("../../../Tests/Fixtures/ixon-v4/reject-parity.txt"); - -/// The fixture's cases of `kind`: name and bytes. -fn of_kind(kind: &str) -> Vec<(&'static str, Vec)> { - let found: Vec<_> = FIXTURE - .lines() - .map(str::trim) - .filter(|line| !line.is_empty() && !line.starts_with('#')) - .filter_map(|line| { - let mut tokens = line.split_whitespace(); - if tokens.next() != Some(kind) { - return None; - } - let name = tokens.next().unwrap(); - let mut bytes = Vec::new(); - for token in tokens { - let (hex, count) = match token.split_once('*') { - Some((hex, count)) => (hex, count.parse::().unwrap()), - None => (token, 1), - }; - let chunk: Vec = (0..hex.len()) - .step_by(2) - .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap()) - .collect(); - for _ in 0..count { - bytes.extend_from_slice(&chunk); - } - } - Some((name, bytes)) - }) - .collect(); - assert!(!found.is_empty(), "no {kind} cases"); - found -} - -/// Runs `f` on a thread with a 256 MiB stack. The aggregation-tree reader -/// recurses once per level up to its 4096-level bound before it refuses a -/// deeper tree (`deep-tree`). That fits the default test stack in release -/// builds, but not with the larger frames of debug builds. -fn on_large_stack(f: impl FnOnce() + Send + 'static) { - if let Err(panic) = std::thread::Builder::new() - .stack_size(256 * 1024 * 1024) - .spawn(f) - .expect("spawn the test thread") - .join() - { - std::panic::resume_unwind(panic); - } -} - -#[test] -fn manifests_are_refused_as_the_lean_parser_refuses_them() { - on_large_stack(|| { - for (name, bytes) in of_kind("ixes-accept") { - ShardManifest::from_bytes(&bytes) - .unwrap_or_else(|e| panic!("{name}: {e}")); - } - for (name, bytes) in of_kind("ixes") { - assert!(ShardManifest::from_bytes(&bytes).is_err(), "{name} accepted"); - } - }); -} diff --git a/crates/kernel/tests/role_artifact.rs b/crates/kernel/tests/role_artifact.rs deleted file mode 100644 index 8d4df60a5..000000000 --- a/crates/kernel/tests/role_artifact.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Exercise the complete role binding emitted by the Lean generator. -//! The caller supplies a retained generator artifact through IX_ROLE_ARTIFACT. - -use ix_kernel::anon_work::build_artifact_work; -use ix_kernel::artifact_checker::ArtifactChecker; -use ix_kernel::mode::Anon; -use ix_kernel::role_admission::ArtifactSource; -use ixon::env::Env; -use ixon::role_table::Role; - -#[test] -#[ignore = "requires IX_ROLE_ARTIFACT from the validated Lean generator"] -fn generated_artifact_admits_every_role_and_checks_every_carried_unit() { - std::thread::Builder::new() - .stack_size(256 * 1024 * 1024) - .spawn(check_generated_artifact) - .unwrap() - .join() - .unwrap(); -} - -fn check_generated_artifact() { - let path = std::env::var("IX_ROLE_ARTIFACT") - .expect("IX_ROLE_ARTIFACT must name a generated all-role artifact"); - let bytes = std::fs::read(&path).expect("read generated role artifact"); - let mut input = bytes.as_slice(); - let env = Env::get(&mut input).expect("decode generated role artifact"); - assert!(input.is_empty(), "trailing artifact bytes"); - let source = - ArtifactSource::prepare(env).expect("authenticate role artifact"); - let started = std::time::Instant::now(); - let admission = source.admission(); - for (role, reason) in admission.refused() { - eprintln!("{} refused: {reason}", role.lean_name()); - } - eprintln!( - "admitted {} roles in {:?}", - admission.admitted_count(), - started.elapsed() - ); - assert!(admission.refused().is_empty()); - assert_eq!(admission.admitted_count(), Role::ALL.len()); - for role in Role::ALL { - assert!(source.is_admitted(role), "{} is not admitted", role.lean_name()); - } - let work = - build_artifact_work(&source).expect("exact authenticated unit coverage"); - let mut checker = ArtifactChecker::::from_source(Ok(source)); - let mut failures = Vec::new(); - for item in &work { - if let Err(error) = checker.check_work(item) { - failures.push((item.primary().hex(), error.to_string())); - } - } - eprintln!("checked {} units in {:?}", work.len(), started.elapsed()); - assert!(failures.is_empty(), "{failures:?}"); -} diff --git a/crates/kernel/tests/role_groups.rs b/crates/kernel/tests/role_groups.rs deleted file mode 100644 index 250b44ec2..000000000 --- a/crates/kernel/tests/role_groups.rs +++ /dev/null @@ -1,2206 +0,0 @@ -//! Group admission of bound roles, on the compiler's own basis blocks. - -use std::sync::Arc; - -use ix_common::address::Address; -use ix_common::env::{DefinitionSafety, Name}; -use ix_kernel::anon_work::{build_artifact_work, verdict_work}; -use ix_kernel::artifact_checker::ArtifactChecker; -use ix_kernel::mode::Anon; -use ix_kernel::role_admission::{ArtifactSource, PRELUDE, SPEC_VERSION}; -use ixon::constant::{Axiom, Constant, ConstantInfo, DefKind, Definition}; -use ixon::contract::{BinderContract, ValueContract}; -use ixon::env::{Env, RoleBinding}; -use ixon::expr::Expr; -use ixon::role_table::{Evidence, Role, RoleTable}; -use ixon::univ::Univ; - -const BASIS: &[u8] = include_bytes!("fixtures/role_admission/basis.ixe"); - -fn loaded() -> Env { - let mut input = BASIS; - let env = Env::get(&mut input).unwrap(); - assert!(input.is_empty()); - env -} - -fn name(dotted: &str) -> Name { - dotted - .split('.') - .fold(Name::anon(), |prefix, part| Name::str(prefix, part.into())) -} - -fn address_of(env: &Env, dotted: &str) -> Address { - env - .lookup_name(&name(dotted)) - .unwrap_or_else(|| panic!("{dotted} missing")) - .addr -} - -fn bind(env: &mut Env, bound: Vec<(Role, Address)>) { - bind_with_evidence(env, bound, vec![]); -} - -fn bind_with_evidence( - env: &mut Env, - mut bound: Vec<(Role, Address)>, - mut evidence: Vec<(Role, Vec)>, -) { - bound.sort_by_key(|(role, _)| role.index()); - evidence.sort_by_key(|(role, _)| role.index()); - let raw = - RoleTable { version: SPEC_VERSION, bound, evidence }.to_bytes().unwrap(); - env.role_binding = - Some(RoleBinding { address: Address::hash(&raw), bytes: raw.into() }); -} - -fn nat_roles(env: &Env) -> Vec<(Role, Address)> { - vec![ - (Role::Nat, address_of(env, "Nat")), - (Role::NatZero, address_of(env, "Nat.zero")), - (Role::NatSucc, address_of(env, "Nat.succ")), - (Role::NatRec, address_of(env, "Nat.rec")), - ] -} - -/// A definition `n : Nat := 5`, stored in `env`. -fn literal_definition(env: &Env, nat: &Address) -> Address { - let literal = env.store_blob(vec![5]); - let mut constant = Constant::new(ConstantInfo::Defn(Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: Arc::new(Expr::Ref(0, vec![])), - value: Arc::new(Expr::Nat(1)), - })); - constant.refs = vec![nat.clone(), literal]; - let address = constant.commit().0; - env.store_const(address.clone(), constant); - address -} - -#[test] -fn the_compiled_nat_block_is_admitted_with_its_literals() { - let mut env = loaded(); - let roles = nat_roles(&env); - let nat = roles[0].1.clone(); - let definition = literal_definition(&env, &nat); - let bound = with_prelude(&env, roles.clone()); - bind(&mut env, bound); - let source = ArtifactSource::snapshot(&env).unwrap(); - let admission = source.admission(); - assert!(admission.refused().is_empty(), "{:?}", admission.refused()); - for (role, address) in &roles { - assert_eq!(admission.address(*role), Some(address), "{role:?}"); - assert!(source.capability(*role).is_some()); - } - let mut checker = ArtifactChecker::::from_source(Ok(source)); - checker.check_const(&nat).unwrap(); - checker.check_const(&definition).unwrap(); -} - -#[test] -fn unbound_or_refused_nat_leaves_literals_refused() { - // Without any Nat role, and with Nat roles that fail their - // specification, a literal's type is not available. - for misbind in [false, true] { - let mut env = loaded(); - let nat = address_of(&env, "Nat"); - let definition = literal_definition(&env, &nat); - if misbind { - // `Bool` has `Nat`'s shape of unit (two constructors and a - // recursor), but not its declarations. - let as_nat = vec![ - (Role::Nat, address_of(&env, "Bool")), - (Role::NatZero, address_of(&env, "Bool.false")), - (Role::NatSucc, address_of(&env, "Bool.true")), - (Role::NatRec, address_of(&env, "Bool.rec")), - ]; - bind(&mut env, as_nat); - } else { - bind(&mut env, vec![]); - } - let source = ArtifactSource::snapshot(&env).unwrap(); - assert!(!source.is_admitted(Role::Nat)); - assert!(source.capability(Role::Nat).is_none()); - if misbind { - let refused = source.admission().refused(); - assert_eq!(refused.len(), 1, "{refused:?}"); - assert_eq!(refused[0].0, Role::Nat); - assert!(refused[0].1.contains("differs from the specification")); - } - let error = ArtifactChecker::::from_source(Ok(source)) - .check_const(&definition) - .unwrap_err(); - // Nat is in the prelude: without it no unit is checked at all. - assert!(format!("{error}").contains("prelude is not admitted"), "{error}"); - } -} - -#[test] -fn swapped_constructor_roles_are_refused() { - let mut env = loaded(); - let mut roles = nat_roles(&env); - let zero = roles[1].1.clone(); - roles[1].1 = roles[2].1.clone(); - roles[2].1 = zero; - bind(&mut env, roles); - let source = ArtifactSource::snapshot(&env).unwrap(); - assert!(!source.is_admitted(Role::Nat)); - let refused = source.admission().refused(); - assert!(refused[0].1.contains("not at its position"), "{refused:?}"); -} - -fn basis_roles(env: &Env) -> Vec<(Role, Address)> { - let mut roles = nat_roles(env); - for (role, name) in [ - (Role::Eq, "Eq"), - (Role::EqRefl, "Eq.refl"), - (Role::EqRec, "Eq.rec"), - (Role::PUnit, "PUnit"), - (Role::PUnitUnit, "PUnit.unit"), - (Role::PUnitRec, "PUnit.rec"), - (Role::Empty, "Empty"), - (Role::EmptyRec, "Empty.rec"), - (Role::False, "False"), - (Role::FalseRec, "False.rec"), - (Role::Quot, "Quot"), - (Role::QuotMk, "Quot.mk"), - (Role::QuotLift, "Quot.lift"), - (Role::QuotInd, "Quot.ind"), - (Role::QuotSound, "Quot.sound"), - ] { - roles.push((role, address_of(env, name))); - } - roles -} - -#[test] -fn the_compiled_basis_blocks_are_admitted() { - let mut env = loaded(); - let roles = basis_roles(&env); - let bound = with_prelude(&env, roles.clone()); - bind(&mut env, bound.clone()); - let source = ArtifactSource::snapshot(&env).unwrap(); - let admission = source.admission(); - assert!(admission.refused().is_empty(), "{:?}", admission.refused()); - assert_eq!(admission.admitted_count(), bound.len()); - let mut checker = ArtifactChecker::::from_source(Ok(source)); - for (role, address) in &roles { - checker.check_const(address).unwrap_or_else(|e| panic!("{role:?}: {e}")); - } -} - -#[test] -fn a_misbound_block_is_refused_with_the_groups_that_need_it() { - let mut env = loaded(); - let mut roles = basis_roles(&env); - // Bind `Eq`'s roles to `PUnit`'s block and `PUnit`'s to `Eq`'s. - let position = |roles: &[(Role, Address)], role| { - roles.iter().position(|(r, _)| *r == role).unwrap() - }; - for (a, b) in [ - (Role::Eq, Role::PUnit), - (Role::EqRefl, Role::PUnitUnit), - (Role::EqRec, Role::PUnitRec), - ] { - let (i, j) = (position(&roles, a), position(&roles, b)); - let address = roles[i].1.clone(); - roles[i].1 = roles[j].1.clone(); - roles[j].1 = address; - } - bind(&mut env, roles); - let source = ArtifactSource::snapshot(&env).unwrap(); - let refused: Vec = - source.admission().refused().iter().map(|(role, _)| *role).collect(); - assert_eq!(refused, [Role::Eq, Role::PUnit, Role::Quot]); - assert!(source.is_admitted(Role::Nat) && source.is_admitted(Role::False)); - let reason = &source.admission().refused()[2].1; - assert!(reason.contains("needs the unadmitted"), "{reason}"); -} - -#[test] -fn quotient_records_need_the_quot_group() { - let mut env = loaded(); - let quot = address_of(&env, "Quot"); - let nat = address_of(&env, "Nat"); - // `def : (Nat → Nat → Prop) → Type := @Quot.{1} Nat`. - let type1 = Arc::new(Univ::Succ(Arc::new(Univ::Zero))); - let relation = - pi(reference(1, vec![]), pi(reference(1, vec![]), arc(Expr::Sort(1)))); - let definition = store( - &env, - ConstantInfo::Defn(Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: pi(relation, arc(Expr::Sort(0))), - value: app(reference(0, vec![0]), reference(1, vec![])), - }), - vec![quot, nat], - vec![type1, Arc::new(Univ::Zero)], - ); - // The whole prelude but the quotient package. - let roles: Vec<_> = with_prelude(&env, nat_roles(&env)) - .into_iter() - .filter(|(role, _)| { - !matches!( - role, - Role::Quot - | Role::QuotMk - | Role::QuotLift - | Role::QuotInd - | Role::QuotSound - ) - }) - .collect(); - bind(&mut env, roles); - let error = ArtifactChecker::::from_source(Ok( - ArtifactSource::snapshot(&env).unwrap(), - )) - .check_const(&definition) - .unwrap_err(); - assert!( - format!("{error}").contains("does not bind the prelude role Quot"), - "{error}" - ); -} - -// Anchors are specified by type alone, so any safe declarations of the -// specified types serve. - -fn arc(expr: Expr) -> Arc { - Arc::new(expr) -} -fn pi(domain: Arc, body: Arc) -> Arc { - arc(Expr::All( - BinderContract::default(), - ValueContract::shared(), - domain, - body, - )) -} -fn app(f: Arc, a: Arc) -> Arc { - arc(Expr::App(f, a)) -} -fn reference(index: u64, levels: Vec) -> Arc { - arc(Expr::Ref(index, levels)) -} - -fn store( - env: &Env, - info: ConstantInfo, - refs: Vec
, - univs: Vec>, -) -> Address { - let mut constant = Constant::new(info); - constant.refs = refs; - constant.univs = univs; - let address = constant.commit().0; - env.store_const(address.clone(), constant); - address -} - -fn axiom( - env: &Env, - lvls: u64, - typ: Arc, - refs: Vec
, - univs: Vec>, -) -> Address { - store( - env, - ConstantInfo::Axio(Axiom { is_unsafe: false, lvls, typ }), - refs, - univs, - ) -} - -fn lambda(domain: Arc, body: Arc) -> Arc { - arc(Expr::Lam(BinderContract::default(), domain, body)) -} - -fn definition( - env: &Env, - lvls: u64, - typ: Arc, - value: Arc, - refs: Vec
, - univs: Vec>, -) -> Address { - store( - env, - ConstantInfo::Defn(Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls, - typ, - value, - }), - refs, - univs, - ) -} - -/// The literal-support anchors as definitions of the specified types, over -/// `Nat` and `PUnit`: `Char := Nat`, `String := PUnit`, `List α := PUnit`. -/// With `of_list_is_wrong`, `String.ofList : List Char → List Char`. -fn literal_anchors(env: &Env, of_list_is_wrong: bool) -> Vec<(Role, Address)> { - let nat = address_of(env, "Nat"); - let punit = address_of(env, "PUnit"); - let unit = address_of(env, "PUnit.unit"); - let zero = Arc::new(Univ::Zero); - let one = Arc::new(Univ::Succ(zero.clone())); - let succ_param = Arc::new(Univ::Succ(Arc::new(Univ::Var(0)))); - let char_t = definition( - env, - 0, - arc(Expr::Sort(0)), - reference(0, vec![]), - vec![nat.clone()], - vec![one.clone()], - ); - let string = definition( - env, - 0, - arc(Expr::Sort(0)), - reference(0, vec![0]), - vec![punit.clone()], - vec![one.clone()], - ); - // `Type u → Type u`; universe 0 is `u + 1`, universe 1 is `u`. - let list = definition( - env, - 1, - pi(arc(Expr::Sort(0)), arc(Expr::Sort(0))), - lambda(arc(Expr::Sort(0)), reference(0, vec![0])), - vec![punit], - vec![succ_param.clone()], - ); - let list_u = |arg| app(reference(0, vec![1]), arg); - let univs = vec![succ_param, Arc::new(Univ::Var(0))]; - let nil = definition( - env, - 1, - pi(arc(Expr::Sort(0)), list_u(arc(Expr::Var(0)))), - lambda(arc(Expr::Sort(0)), reference(1, vec![0])), - vec![list.clone(), unit.clone()], - univs.clone(), - ); - let cons = definition( - env, - 1, - pi( - arc(Expr::Sort(0)), - pi( - arc(Expr::Var(0)), - pi(list_u(arc(Expr::Var(1))), list_u(arc(Expr::Var(2)))), - ), - ), - lambda( - arc(Expr::Sort(0)), - lambda( - arc(Expr::Var(0)), - lambda(list_u(arc(Expr::Var(1))), reference(1, vec![0])), - ), - ), - vec![list.clone(), unit.clone()], - univs, - ); - let of_nat = definition( - env, - 0, - pi(reference(0, vec![]), reference(1, vec![])), - lambda(reference(0, vec![]), arc(Expr::Var(0))), - vec![nat, char_t.clone()], - vec![], - ); - // `List.{0} Char → String := fun _ => PUnit.unit.{1}`. - let list_char = || app(reference(0, vec![0]), reference(1, vec![])); - let of_list = if of_list_is_wrong { - definition( - env, - 0, - pi(list_char(), list_char()), - lambda(list_char(), arc(Expr::Var(0))), - vec![list.clone(), char_t.clone()], - vec![zero], - ) - } else { - definition( - env, - 0, - pi(list_char(), reference(2, vec![])), - lambda(list_char(), reference(3, vec![1])), - vec![list.clone(), char_t.clone(), string.clone(), unit], - vec![zero, one], - ) - }; - vec![ - (Role::Char, char_t), - (Role::String, string), - (Role::List, list), - (Role::ListNil, nil), - (Role::ListCons, cons), - (Role::CharOfNat, of_nat), - (Role::StringOfList, of_list), - ] -} - -/// `theorem : @Eq.{1} String "a" (String.ofList [Char.ofNat 97]) := -/// @Eq.refl.{1} String "a"`. -fn string_literal_theorem(env: &Env, anchors: &[(Role, Address)]) -> Address { - let anchor = |role| { - anchors.iter().find(|(r, _)| *r == role).map(|(_, a)| a.clone()).unwrap() - }; - let text = env.store_blob(b"a".to_vec()); - let code = env.store_blob(vec![97]); - let refs = vec![ - address_of(env, "Eq"), - address_of(env, "Eq.refl"), - anchor(Role::String), - anchor(Role::StringOfList), - anchor(Role::ListCons), - anchor(Role::ListNil), - anchor(Role::Char), - anchor(Role::CharOfNat), - text, - code, - ]; - // Universe 0 is `1`, universe 1 is `0`. - let univs = - vec![Arc::new(Univ::Succ(Arc::new(Univ::Zero))), Arc::new(Univ::Zero)]; - let string = || reference(2, vec![]); - let char_t = || reference(6, vec![]); - let literal = || arc(Expr::Str(8)); - let list = app( - app( - app(reference(4, vec![1]), char_t()), - app(reference(7, vec![]), arc(Expr::Nat(9))), - ), - app(reference(5, vec![1]), char_t()), - ); - let typ = app( - app(app(reference(0, vec![0]), string()), literal()), - app(reference(3, vec![]), list), - ); - let value = app(app(reference(1, vec![0]), string()), literal()); - store( - env, - ConstantInfo::Defn(Definition { - kind: DefKind::Theorem, - safety: DefinitionSafety::Safe, - lvls: 0, - typ, - value, - }), - refs, - univs, - ) -} - -#[test] -fn string_literals_use_the_admitted_anchors() { - let checked = |of_list_is_wrong: bool, bind_of_list: bool| { - let mut env = loaded(); - let anchors = literal_anchors(&env, of_list_is_wrong); - let theorem = string_literal_theorem(&env, &anchors); - let mut roles = basis_roles(&env); - roles.extend( - anchors - .into_iter() - .filter(|(role, _)| bind_of_list || *role != Role::StringOfList), - ); - let roles = with_prelude(&env, roles); - bind(&mut env, roles); - let source = ArtifactSource::snapshot(&env).unwrap(); - let refused = source.admission().refused().to_vec(); - let result = - ArtifactChecker::::from_source(Ok(source)).check_const(&theorem); - (refused, result) - }; - - // Every anchor admitted: the literal expands to its `String.ofList` form. - let (refused, result) = checked(false, true); - assert!(refused.is_empty(), "{refused:?}"); - result.unwrap(); - - // `String.ofList` declared but not bound: the expansion refuses. - let (refused, result) = checked(false, false); - assert!(refused.is_empty(), "{refused:?}"); - let error = result.unwrap_err(); - assert!( - format!("{error}").contains("String.ofList support is not admitted"), - "{error}" - ); - - // `String.ofList : List Char → List Char` is refused. - let (refused, _) = checked(true, true); - assert_eq!(refused.len(), 1, "{refused:?}"); - assert_eq!(refused[0].0, Role::StringOfList); - assert!(refused[0].1.contains("differs from the specification: type")); -} - -/// The structural operations with the number of their recurrence equations. -const STRUCTURAL: [(Role, &str, usize); 7] = [ - (Role::NatPred, "Nat.pred", 2), - (Role::NatAdd, "Nat.add", 2), - (Role::NatSub, "Nat.sub", 2), - (Role::NatMul, "Nat.mul", 2), - (Role::NatPow, "Nat.pow", 2), - (Role::NatBeq, "Nat.beq", 4), - (Role::NatBle, "Nat.ble", 3), -]; - -fn bool_roles(env: &Env) -> Vec<(Role, Address)> { - vec![ - (Role::Bool, address_of(env, "Bool")), - (Role::BoolFalse, address_of(env, "Bool.false")), - (Role::BoolTrue, address_of(env, "Bool.true")), - (Role::BoolRec, address_of(env, "Bool.rec")), - ] -} - -/// The basis, `Bool` and the structural operations, with their evidence. -fn bind_structural(env: &mut Env, swap_add_and_mul: bool) { - let mut roles = basis_roles(env); - roles.extend(bool_roles(env)); - let mut evidence = vec![]; - for (role, name, equations) in STRUCTURAL { - let name = match (swap_add_and_mul, name) { - (true, "Nat.add") => "Nat.mul", - (true, "Nat.mul") => "Nat.add", - _ => name, - }; - roles.push((role, address_of(env, name))); - evidence.push((role, vec![Evidence::DefEq; equations])); - } - let roles = with_prelude(env, roles); - bind_with_evidence(env, roles, evidence); -} - -#[derive(Clone, Copy, Debug)] -enum DefinitionMutation { - None, - AnchorAxiom, - WrongValue, - WrongType, - ExtraUniverse, - Opaque, - Unsafe, -} - -/// A small alternate representation: Int is Nat, ofNat is identity, and -/// negSucc is successor. A beta-expanded identity satisfies negOfNat's -/// actual generated equations; the mutants remain separately addressed. -fn equation_candidate( - env: &Env, - mutation: DefinitionMutation, -) -> Vec<(Role, Address)> { - let nat = address_of(env, "Nat"); - let zero = address_of(env, "Nat.zero"); - let succ = address_of(env, "Nat.succ"); - let one = Arc::new(Univ::Succ(Arc::new(Univ::Zero))); - let integer = if matches!(mutation, DefinitionMutation::AnchorAxiom) { - axiom(env, 0, arc(Expr::Sort(0)), vec![], vec![one]) - } else { - definition( - env, - 0, - arc(Expr::Sort(0)), - app(lambda(arc(Expr::Sort(0)), arc(Expr::Var(0))), reference(0, vec![])), - vec![nat.clone()], - vec![one], - ) - }; - let typ = pi(reference(0, vec![]), reference(1, vec![])); - let of_nat = definition( - env, - 0, - typ.clone(), - lambda(reference(0, vec![]), arc(Expr::Var(0))), - vec![nat.clone(), integer.clone()], - vec![], - ); - let negative = definition( - env, - 0, - typ.clone(), - reference(2, vec![]), - vec![nat.clone(), integer.clone(), succ], - vec![], - ); - let body = match mutation { - DefinitionMutation::WrongValue => reference(2, vec![]), - _ => { - app(lambda(reference(0, vec![]), arc(Expr::Var(0))), arc(Expr::Var(0))) - }, - }; - let (typ, value) = match mutation { - DefinitionMutation::WrongType => { - (reference(0, vec![]), reference(2, vec![])) - }, - _ => (typ, lambda(reference(0, vec![]), body)), - }; - let operation = store( - env, - ConstantInfo::Defn(Definition { - kind: if matches!(mutation, DefinitionMutation::Opaque) { - DefKind::Opaque - } else { - DefKind::Definition - }, - safety: if matches!(mutation, DefinitionMutation::Unsafe) { - DefinitionSafety::Unsafe - } else { - DefinitionSafety::Safe - }, - lvls: u64::from(matches!(mutation, DefinitionMutation::ExtraUniverse)), - typ, - value, - }), - vec![nat, integer.clone(), zero], - vec![], - ); - vec![ - (Role::Int, integer), - (Role::IntOfNat, of_nat), - (Role::IntNegSucc, negative), - (Role::IntNegOfNat, operation), - ] -} - -fn bind_equation_candidate( - env: &mut Env, - extra: &[(Role, Address)], - evidence: Vec, -) { - let mut roles = basis_roles(env); - roles.extend(extra.iter().cloned()); - let roles = with_prelude(env, roles); - bind_with_evidence(env, roles, vec![(Role::IntNegOfNat, evidence)]); -} - -#[test] -fn generated_equations_admit_actual_definitions_and_refuse_semantic_mutants() { - for (mutation, expected) in [ - (DefinitionMutation::None, None), - ( - DefinitionMutation::WrongValue, - Some("definitional equation does not hold"), - ), - (DefinitionMutation::WrongType, Some("definition type")), - (DefinitionMutation::ExtraUniverse, Some("universe parameter count")), - (DefinitionMutation::Opaque, Some("not a safe definition")), - (DefinitionMutation::Unsafe, Some("not a safe definition")), - ] { - let mut env = loaded(); - let roles = equation_candidate(&env, mutation); - bind_equation_candidate(&mut env, &roles, vec![Evidence::DefEq; 2]); - let source = ArtifactSource::snapshot(&env).unwrap(); - for role in - [Role::Nat, Role::Eq, Role::Int, Role::IntOfNat, Role::IntNegSucc] - { - assert!( - source.is_admitted(role), - "{mutation:?}: {:?}", - source.admission().refused() - ); - } - let refused = source.admission().refused(); - if let Some(expected) = expected { - assert_eq!(refused.len(), 1, "{mutation:?}: {refused:?}"); - assert_eq!(refused[0].0, Role::IntNegOfNat); - assert!(refused[0].1.contains(expected), "{mutation:?}: {refused:?}"); - assert!(!source.is_admitted(Role::IntNegOfNat)); - ArtifactChecker::::from_source(Ok(source)) - .check_const(&roles[3].1) - .unwrap_err(); - } else { - assert!(refused.is_empty(), "{refused:?}"); - assert!(source.is_admitted(Role::IntNegOfNat)); - ArtifactChecker::::from_source(Ok(source)) - .check_const(&roles[3].1) - .unwrap(); - } - } -} - -#[test] -fn generated_support_anchors_cannot_admit_a_new_axiom() { - let mut env = loaded(); - let roles = equation_candidate(&env, DefinitionMutation::AnchorAxiom); - bind_equation_candidate(&mut env, &roles, vec![Evidence::DefEq; 2]); - let source = ArtifactSource::snapshot(&env).unwrap(); - let refused = source.admission().refused(); - assert_eq!(refused.len(), 2, "{refused:?}"); - assert_eq!(refused[0].0, Role::Int); - assert!( - refused[0].1.contains("not an admitted standard axiom"), - "{refused:?}" - ); - assert_eq!(refused[1].0, Role::IntNegOfNat); - assert!(!source.is_admitted(Role::Int)); - assert!(!source.is_admitted(Role::IntNegOfNat)); -} - -#[test] -fn equation_rhs_anchors_require_their_helpers_to_be_well_typed() { - for ill_typed in [false, true] { - let mut env = loaded(); - let mut roles = equation_candidate(&env, DefinitionMutation::None); - let nat = address_of(&env, "Nat"); - let integer = roles[0].1.clone(); - let discarded = - address_of(&env, if ill_typed { "True.intro" } else { "Nat.zero" }); - // Both helpers reduce to successor. In the negative case the erased - // argument is a proof of True where the lambda requires a Nat. - let helper = definition( - &env, - 0, - pi(reference(0, vec![]), reference(1, vec![])), - lambda( - reference(0, vec![]), - app( - lambda( - reference(0, vec![]), - app(reference(2, vec![]), arc(Expr::Var(1))), - ), - reference(3, vec![]), - ), - ), - vec![ - nat.clone(), - integer.clone(), - address_of(&env, "Nat.succ"), - discarded, - ], - vec![], - ); - let rhs_anchor = definition( - &env, - 0, - pi(reference(0, vec![]), reference(1, vec![])), - reference(2, vec![]), - vec![nat, integer, helper.clone()], - vec![], - ); - assert_eq!(roles[2].0, Role::IntNegSucc); - roles[2].1 = rhs_anchor.clone(); - bind_equation_candidate(&mut env, &roles, vec![Evidence::DefEq; 2]); - let source = ArtifactSource::snapshot(&env).unwrap(); - let actual_closure = source.closure(&[roles[3].1.clone()]).unwrap(); - assert!(!actual_closure.contains(&rhs_anchor)); - assert!(!actual_closure.contains(&helper)); - let checked_helper = - ArtifactChecker::::from_source(Ok(source.clone())) - .check_const(&helper); - assert_eq!(checked_helper.is_ok(), !ill_typed, "{checked_helper:?}"); - let admitted = source.admission(); - if ill_typed { - assert!( - !source.is_admitted(Role::IntNegSucc), - "{:?}", - admitted.refused() - ); - assert!( - !source.is_admitted(Role::IntNegOfNat), - "{:?}", - admitted.refused() - ); - } else { - assert!(admitted.refused().is_empty(), "{:?}", admitted.refused()); - assert!(source.is_admitted(Role::IntNegSucc)); - assert!(source.is_admitted(Role::IntNegOfNat)); - } - } -} - -#[derive(Clone, Copy, Debug)] -enum ProofMutation { - None, - ValidHelper, - WrongStatement, - WrongBody, - BadHelper, - ExtraUniverse, -} - -/// Ordinary reflexivity witnesses for the alternate representation above. -/// A wrong-statement mutant is itself a valid theorem; it must fail the -/// generated equation check, independently of ordinary declaration checking. -fn equation_proofs( - env: &Env, - roles: &[(Role, Address)], - mutation: ProofMutation, -) -> Vec { - let integer = roles[0].1.clone(); - let of_nat = roles[1].1.clone(); - let negative = roles[2].1.clone(); - let operation = roles[3].1.clone(); - let mut refs = vec![ - address_of(env, "Eq"), - address_of(env, "Eq.refl"), - integer, - operation, - address_of(env, "Nat.zero"), - of_nat, - address_of(env, "Nat"), - address_of(env, "Nat.succ"), - negative, - ]; - let r = |i| reference(i, vec![]); - let equality = - |lhs, rhs| app(app(app(reference(0, vec![0]), r(2)), lhs), rhs); - let refl = |term| app(app(reference(1, vec![0]), r(2)), term); - let first = app(r(5), r(4)); - let mut statements = [ - equality(app(r(3), r(4)), first.clone()), - pi( - r(6), - equality( - app(r(3), app(r(7), arc(Expr::Var(0)))), - app(r(8), arc(Expr::Var(0))), - ), - ), - ]; - let mut values = - [refl(first), lambda(r(6), refl(app(r(8), arc(Expr::Var(0)))))]; - if matches!(mutation, ProofMutation::WrongStatement) { - let one = app(r(7), r(4)); - statements[0] = equality(one.clone(), one.clone()); - values[0] = refl(one); - } - if matches!(mutation, ProofMutation::WrongBody) { - values[0] = arc(Expr::Var(0)); - } - if matches!(mutation, ProofMutation::ValidHelper | ProofMutation::BadHelper) { - let helper = store( - env, - ConstantInfo::Defn(Definition { - kind: DefKind::Theorem, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: statements[0].clone(), - value: if matches!(mutation, ProofMutation::ValidHelper) { - values[0].clone() - } else { - arc(Expr::Var(0)) - }, - }), - refs.clone(), - vec![Arc::new(Univ::Succ(Arc::new(Univ::Zero)))], - ); - values[0] = reference(u64::try_from(refs.len()).unwrap(), vec![]); - refs.push(helper); - } - statements - .into_iter() - .zip(values) - .map(|(typ, value)| { - Evidence::Proof(store( - env, - ConstantInfo::Defn(Definition { - kind: DefKind::Theorem, - safety: DefinitionSafety::Safe, - lvls: u64::from(matches!(mutation, ProofMutation::ExtraUniverse)), - typ, - value, - }), - refs.clone(), - vec![Arc::new(Univ::Succ(Arc::new(Univ::Zero)))], - )) - }) - .collect() -} - -#[test] -fn generated_equations_check_proof_bodies_statements_and_universes() { - for mutation in [ - ProofMutation::None, - ProofMutation::ValidHelper, - ProofMutation::WrongStatement, - ProofMutation::WrongBody, - ProofMutation::BadHelper, - ProofMutation::ExtraUniverse, - ] { - let mut env = loaded(); - let roles = equation_candidate(&env, DefinitionMutation::None); - let evidence = equation_proofs(&env, &roles, mutation); - bind_equation_candidate(&mut env, &roles, evidence); - let source = ArtifactSource::snapshot(&env).unwrap(); - assert!( - source.is_admitted(Role::Int), - "{:?}", - source.admission().refused() - ); - let refused = source.admission().refused(); - if matches!(mutation, ProofMutation::None | ProofMutation::ValidHelper) { - assert!(refused.is_empty(), "{refused:?}"); - assert!(source.is_admitted(Role::IntNegOfNat)); - } else { - assert_eq!(refused.len(), 1, "{mutation:?}: {refused:?}"); - assert_eq!(refused[0].0, Role::IntNegOfNat); - assert!(!source.is_admitted(Role::IntNegOfNat)); - if matches!(mutation, ProofMutation::WrongStatement) { - assert!(refused[0].1.contains("different statement"), "{refused:?}"); - } - } - } -} - -/// `theorem : @Eq.{1} Nat (Nat.pow 2 64) 18446744073709551616 := Eq.refl _`, -/// which only the admitted `Nat.pow` fast path checks in reasonable time. -fn power_theorem(env: &Env) -> Address { - let two = env.store_blob(vec![2]); - let sixty_four = env.store_blob(vec![64]); - let mut big = vec![0; 8]; - big.push(1); - let big = env.store_blob(big); - let refs = vec![ - address_of(env, "Eq"), - address_of(env, "Eq.refl"), - address_of(env, "Nat"), - address_of(env, "Nat.pow"), - two, - sixty_four, - big, - ]; - let univs = vec![Arc::new(Univ::Succ(Arc::new(Univ::Zero)))]; - let power = - app(app(reference(3, vec![]), arc(Expr::Nat(4))), arc(Expr::Nat(5))); - let typ = app( - app(app(reference(0, vec![0]), reference(2, vec![])), power.clone()), - arc(Expr::Nat(6)), - ); - let value = app(app(reference(1, vec![0]), reference(2, vec![])), power); - store( - env, - ConstantInfo::Defn(Definition { - kind: DefKind::Theorem, - safety: DefinitionSafety::Safe, - lvls: 0, - typ, - value, - }), - refs, - univs, - ) -} - -#[test] -fn the_structural_operations_are_admitted_by_their_equations() { - let mut env = loaded(); - let theorem = power_theorem(&env); - bind_structural(&mut env, false); - let source = ArtifactSource::snapshot(&env).unwrap(); - let admission = source.admission(); - assert!(admission.refused().is_empty(), "{:?}", admission.refused()); - for (role, _, _) in STRUCTURAL { - assert!(source.is_admitted(role), "{role:?}"); - } - ArtifactChecker::::from_source(Ok(source)) - .check_const(&theorem) - .unwrap(); -} - -#[test] -fn an_operation_whose_equations_fail_is_refused_with_its_dependents() { - let mut env = loaded(); - bind_structural(&mut env, true); - let source = ArtifactSource::snapshot(&env).unwrap(); - let refused = source.admission().refused(); - let roles: Vec = refused.iter().map(|(role, _)| *role).collect(); - assert_eq!(roles, [Role::NatAdd, Role::NatMul, Role::NatPow], "{refused:?}"); - assert!(refused[0].1.contains("does not hold"), "{refused:?}"); - assert!(refused[1].1.contains("needs the unadmitted"), "{refused:?}"); - for role in [Role::NatPred, Role::NatSub, Role::NatBeq, Role::NatBle] { - assert!(source.is_admitted(role), "{role:?}"); - } -} - -/// `True`, `And`, `Iff` and `Nonempty` declared at their specified types, -/// the type formers and introductions as impredicative encodings and the -/// recursors as axioms (never used here), with the axioms `propext` and -/// `Classical.choice` of their specified types over them and a `sorryAx` of -/// type `∀ α, α`. The `True` and `And` anchors are specified by type alone, -/// so these serve; `Iff` and `Nonempty` are blocks, and `sorryAx` has its -/// type, so those are refused. -fn definitional_axiom_roles(env: &Env) -> Vec<(Role, Address)> { - let eq = address_of(env, "Eq"); - let zero = Arc::new(Univ::Zero); - let one = Arc::new(Univ::Succ(zero.clone())); - let param = Arc::new(Univ::Var(0)); - let v = |i: u64| arc(Expr::Var(i)); - // Universe 0 is always `Prop`; universe 1, where present, is `u`. - let prop = || arc(Expr::Sort(0)); - let sort_u = || arc(Expr::Sort(1)); - let props = vec![zero.clone(), param.clone()]; - let both = - |r: u64, a: u64, b: u64| app(app(reference(r, vec![]), v(a)), v(b)); - let four = |r: u64, args: [u64; 4]| { - args.iter().fold(reference(r, vec![]), |f, a| app(f, v(*a))) - }; - - // `True := ∀ p : Prop, p → p`, `True.intro := fun _ h => h`. - let true_t = definition( - env, - 0, - prop(), - pi(prop(), pi(v(0), v(1))), - vec![], - vec![zero.clone()], - ); - let true_intro = definition( - env, - 0, - reference(0, vec![]), - lambda(prop(), lambda(v(0), v(0))), - vec![true_t.clone()], - vec![zero.clone()], - ); - - // `And`, `And.intro`, `And.rec` as non-inductive definitions. - let real_and = address_of(env, "And"); - let real_and_intro = address_of(env, "And.intro"); - let real_and_rec = address_of(env, "And.rec"); - let binary = || pi(prop(), pi(prop(), prop())); - let and = definition( - env, - 0, - binary(), - reference(0, vec![]), - vec![real_and], - vec![zero.clone()], - ); - let and_intro = definition( - env, - 0, - pi(prop(), pi(prop(), pi(v(1), pi(v(1), both(0, 3, 2))))), - reference(1, vec![]), - vec![and.clone(), real_and_intro], - vec![zero.clone()], - ); - let and_rec = definition( - env, - 1, - pi( - prop(), - pi( - prop(), - pi( - pi(both(0, 1, 0), sort_u()), - pi( - pi(v(2), pi(v(2), app(v(2), four(1, [4, 3, 1, 0])))), - pi(both(0, 3, 2), app(v(2), v(0))), - ), - ), - ), - ), - reference(2, vec![1]), - vec![and.clone(), and_intro.clone(), real_and_rec], - props.clone(), - ); - - // `Iff a b := ∀ c : Prop, ((a → b) → (b → a) → c) → c`. - let iff = definition( - env, - 0, - binary(), - lambda( - prop(), - lambda( - prop(), - pi(prop(), pi(pi(pi(v(2), v(2)), pi(pi(v(2), v(4)), v(2))), v(1))), - ), - ), - vec![], - vec![zero.clone()], - ); - let iff_intro = definition( - env, - 0, - pi( - prop(), - pi(prop(), pi(pi(v(1), v(1)), pi(pi(v(1), v(3)), both(0, 3, 2)))), - ), - lambda( - prop(), - lambda( - prop(), - lambda( - pi(v(1), v(1)), - lambda( - pi(v(1), v(3)), - lambda( - prop(), - lambda( - pi(pi(v(4), v(4)), pi(pi(v(4), v(6)), v(2))), - app(app(v(0), v(3)), v(2)), - ), - ), - ), - ), - ), - ), - vec![iff.clone()], - vec![zero.clone()], - ); - let iff_rec = axiom( - env, - 1, - pi( - prop(), - pi( - prop(), - pi( - pi(both(0, 1, 0), sort_u()), - pi( - pi( - pi(v(2), v(2)), - pi(pi(v(2), v(4)), app(v(2), four(1, [4, 3, 1, 0]))), - ), - pi(both(0, 3, 2), app(v(2), v(0))), - ), - ), - ), - ), - vec![iff.clone(), iff_intro.clone()], - props.clone(), - ); - - // `Nonempty.{u} α := ∀ p : Prop, (α → p) → p`. - let nonempty = definition( - env, - 1, - pi(sort_u(), prop()), - lambda(sort_u(), pi(prop(), pi(pi(v(1), v(1)), v(1)))), - vec![], - props.clone(), - ); - let nonempty_u = |e| app(reference(0, vec![1]), e); - let nonempty_intro = definition( - env, - 1, - pi(sort_u(), pi(v(0), nonempty_u(v(1)))), - lambda( - sort_u(), - lambda(v(0), lambda(prop(), lambda(pi(v(2), v(1)), app(v(0), v(2))))), - ), - vec![nonempty.clone()], - props.clone(), - ); - let nonempty_rec = axiom( - env, - 1, - pi( - sort_u(), - pi( - pi(nonempty_u(v(0)), prop()), - pi( - pi(v(1), app(v(1), app(app(reference(1, vec![1]), v(2)), v(0)))), - pi(nonempty_u(v(2)), app(v(2), v(0))), - ), - ), - ), - vec![nonempty.clone(), nonempty_intro.clone()], - props.clone(), - ); - - // `propext {a b : Prop} : Iff a b → @Eq.{1} Prop a b`; universe 1 is `1`. - let propext = axiom( - env, - 0, - pi( - prop(), - pi( - prop(), - pi( - both(0, 1, 0), - app(app(app(reference(1, vec![1]), prop()), v(2)), v(1)), - ), - ), - ), - vec![iff.clone(), eq], - vec![zero.clone(), one], - ); - let choice = axiom( - env, - 1, - pi(sort_u(), pi(nonempty_u(v(0)), v(1))), - vec![nonempty.clone()], - props.clone(), - ); - let sorry = axiom(env, 1, pi(sort_u(), v(0)), vec![], props); - vec![ - (Role::True, true_t), - (Role::TrueIntro, true_intro), - (Role::And, and), - (Role::AndIntro, and_intro), - (Role::AndRec, and_rec), - (Role::Iff, iff), - (Role::IffIntro, iff_intro), - (Role::IffRec, iff_rec), - (Role::Nonempty, nonempty), - (Role::NonemptyIntro, nonempty_intro), - (Role::NonemptyRec, nonempty_rec), - (Role::Propext, propext), - (Role::Choice, choice), - (Role::SorryAx, sorry), - ] -} - -/// The `True` and `And` anchors as in [`definitional_axiom_roles`], with -/// the compiled `Iff` and `Nonempty` families, `propext`, -/// `Classical.choice` and `sorryAx`. -fn axiom_roles(env: &Env) -> Vec<(Role, Address)> { - let mut roles: Vec<(Role, Address)> = definitional_axiom_roles(env) - .into_iter() - .filter(|(role, _)| { - matches!( - role, - Role::True - | Role::TrueIntro - | Role::And - | Role::AndIntro - | Role::AndRec - ) - }) - .collect(); - for role in [ - Role::Iff, - Role::IffIntro, - Role::IffRec, - Role::Nonempty, - Role::NonemptyIntro, - Role::NonemptyRec, - Role::Propext, - Role::Choice, - Role::SorryAx, - ] { - roles.push((role, address_of(env, role.lean_name()))); - } - roles -} - -/// Bind the basis, the prelude and `roles`, and return the snapshot. -fn bound_with_basis( - env: &mut Env, - roles: &[(Role, Address)], -) -> ArtifactSource { - let mut bound = basis_roles(env); - bound.extend(roles.iter().cloned()); - let bound = with_prelude(env, bound); - bind(env, bound); - ArtifactSource::snapshot(env).unwrap() -} - -fn refused_roles(source: &ArtifactSource) -> Vec { - source.admission().refused().iter().map(|(role, _)| *role).collect() -} - -#[test] -fn families_specified_by_type_alone_are_refused() { - // `Iff` and `Nonempty` must be the inductives the standard axioms - // quantify over, and `sorryAx` must have its type: definitions of the - // families' types, and an axiom `∀ α, α`, are refused, and so are the - // axioms over those families. - let mut env = loaded(); - let roles = definitional_axiom_roles(&env); - let source = bound_with_basis(&mut env, &roles); - assert_eq!( - refused_roles(&source), - [Role::Iff, Role::Nonempty, Role::Propext, Role::Choice, Role::SorryAx] - ); - let refused = source.admission().refused(); - for (_, reason) in &refused[..2] { - assert!(reason.contains("not a member of a carried unit"), "{reason}"); - } - for (_, reason) in &refused[2..4] { - assert!(reason.contains("needs the unadmitted"), "{reason}"); - } - assert!(refused[4].1.contains("differs from the specification: type")); - for role in [Role::True, Role::And] { - assert!(source.is_admitted(role), "{role:?}"); - } -} - -/// `Nonempty.{u} := fun _ => True`, `Nonempty.intro := fun _ _ => -/// True.intro`, an axiom `choice'.{u} (α : Sort u) : Nonempty α → α` (the -/// specified type of `Classical.choice` over that `Nonempty`), and -/// `Nonempty.rec := fun α motive h t => h (choice' α t)`, well typed by -/// proof irrelevance since `Nonempty α` is `True`; with `theorem bad : False -/// := choice'.{0} False True.intro`. Returns the roles and `bad`. -fn nonempty_as_true(env: &Env) -> (Vec<(Role, Address)>, Address) { - let true_t = address_of(env, "True"); - let true_intro = address_of(env, "True.intro"); - let v = |i: u64| arc(Expr::Var(i)); - // Universe 0 is `Prop`, universe 1 is `u`. - let prop = || arc(Expr::Sort(0)); - let sort_u = || arc(Expr::Sort(1)); - let props = || vec![Arc::new(Univ::Zero), Arc::new(Univ::Var(0))]; - let nonempty_u = |e| app(reference(0, vec![1]), e); - let nonempty = definition( - env, - 1, - pi(sort_u(), prop()), - lambda(sort_u(), reference(0, vec![])), - vec![true_t.clone()], - props(), - ); - let intro = definition( - env, - 1, - pi(sort_u(), pi(v(0), nonempty_u(v(1)))), - lambda(sort_u(), lambda(v(0), reference(1, vec![]))), - vec![nonempty.clone(), true_intro.clone()], - props(), - ); - let choice = axiom( - env, - 1, - pi(sort_u(), pi(nonempty_u(v(0)), v(1))), - vec![nonempty.clone()], - props(), - ); - let motive = || pi(nonempty_u(v(0)), prop()); - let minor = - || pi(v(1), app(v(1), app(app(reference(1, vec![1]), v(2)), v(0)))); - let major = || nonempty_u(v(2)); - let rec = definition( - env, - 1, - pi(sort_u(), pi(motive(), pi(minor(), pi(major(), app(v(2), v(0)))))), - lambda( - sort_u(), - lambda( - motive(), - lambda( - minor(), - lambda( - major(), - app(v(1), app(app(reference(2, vec![1]), v(3)), v(0))), - ), - ), - ), - ), - vec![nonempty.clone(), intro.clone(), choice.clone()], - props(), - ); - let bad = store( - env, - ConstantInfo::Defn(Definition { - kind: DefKind::Theorem, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: reference(0, vec![]), - value: app( - app(reference(1, vec![0]), reference(0, vec![])), - reference(2, vec![]), - ), - }), - vec![address_of(env, "False"), choice.clone(), true_intro.clone()], - vec![Arc::new(Univ::Zero)], - ); - let roles = vec![ - (Role::True, true_t), - (Role::TrueIntro, true_intro), - (Role::Nonempty, nonempty), - (Role::NonemptyIntro, intro), - (Role::NonemptyRec, rec), - (Role::Choice, choice), - ]; - (roles, bad) -} - -#[test] -fn nonempty_as_true_does_not_admit_choice() { - // With `Nonempty` specified by its type alone, every group here was - // admitted, every unit checked, and `bad : False` with them. - let mut env = loaded(); - let (roles, bad) = nonempty_as_true(&env); - let source = bound_with_basis(&mut env, &roles); - assert_eq!(refused_roles(&source), [Role::Nonempty, Role::Choice]); - let role = |r: Role| roles.iter().find(|(x, _)| *x == r).unwrap().1.clone(); - let mut checker = ArtifactChecker::::from_source(Ok(source)); - for r in - [Role::Nonempty, Role::NonemptyIntro, Role::NonemptyRec, Role::Choice] - { - let error = checker.check_const(&role(r)).unwrap_err(); - assert!( - format!("{error}").contains("requires unadmitted role"), - "{r:?}: {error}" - ); - } - let error = checker.check_const(&bad).unwrap_err(); - assert!( - format!("{error}").contains("is not an admitted standard axiom"), - "{error}" - ); -} - -#[test] -fn sorry_bound_to_another_axiom_is_refused() { - // `sorryAx` is inert, but it has its type: a binding cannot make - // `axiom evil.{u} : False` the one tolerated axiom. - let mut env = loaded(); - let evil = axiom( - &env, - 1, - reference(0, vec![]), - vec![address_of(&env, "False")], - vec![], - ); - let bound = with_prelude(&env, vec![(Role::SorryAx, evil.clone())]); - bind(&mut env, bound); - let source = ArtifactSource::snapshot(&env).unwrap(); - assert_eq!(refused_roles(&source), [Role::SorryAx]); - let reason = &source.admission().refused()[0].1; - assert!(reason.contains("differs from the specification: type"), "{reason}"); - let error = ArtifactChecker::::from_source(Ok(source)) - .check_const(&evil) - .unwrap_err(); - assert!( - format!("{error}").contains("requires unadmitted role SorryAx"), - "{error}" - ); -} - -/// `theorem : True := e`, with `refs` for `e`'s references. -fn proof_of_true( - env: &Env, - true_t: &Address, - value: Arc, - mut refs: Vec
, - univs: Vec>, -) -> Address { - refs.insert(0, true_t.clone()); - store( - env, - ConstantInfo::Defn(Definition { - kind: DefKind::Theorem, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: reference(0, vec![]), - value, - }), - refs, - univs, - ) -} - -#[test] -fn only_the_standard_axioms_may_be_used() { - let mut env = loaded(); - let roles = axiom_roles(&env); - let role = |r: Role| roles.iter().find(|(x, _)| *x == r).unwrap().1.clone(); - let true_t = role(Role::True); - // `sorryAx.{0} True false`, and a nonstandard `axiom : True`. - let zero = Arc::new(Univ::Zero); - let uses_sorry = proof_of_true( - &env, - &true_t, - app(app(reference(1, vec![0]), reference(0, vec![])), reference(2, vec![])), - vec![role(Role::SorryAx), address_of(&env, "Bool.false")], - vec![zero.clone()], - ); - let nonstandard = - axiom(&env, 0, reference(0, vec![]), vec![true_t.clone()], vec![]); - let uses_nonstandard = proof_of_true( - &env, - &true_t, - reference(1, vec![]), - vec![nonstandard.clone()], - vec![], - ); - // `def : ∀ {a b : Prop}, Iff a b → a = b := @propext`, and an unsafe - // axiom, which is outside the safe theory's policy. - let prop = || arc(Expr::Sort(0)); - let uses_propext = store( - &env, - ConstantInfo::Defn(Definition { - kind: DefKind::Definition, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: pi( - prop(), - pi( - prop(), - pi( - app( - app(reference(0, vec![]), arc(Expr::Var(1))), - arc(Expr::Var(0)), - ), - app( - app(app(reference(1, vec![1]), prop()), arc(Expr::Var(2))), - arc(Expr::Var(1)), - ), - ), - ), - ), - value: reference(2, vec![]), - }), - vec![role(Role::Iff), address_of(&env, "Eq"), role(Role::Propext)], - vec![zero, Arc::new(Univ::Succ(Arc::new(Univ::Zero)))], - ); - let unsafe_axiom = store( - &env, - ConstantInfo::Axio(Axiom { - is_unsafe: true, - lvls: 0, - typ: reference(0, vec![]), - }), - vec![true_t.clone()], - vec![], - ); - let mut bound = basis_roles(&env); - bound.extend(roles.iter().cloned()); - let bound = with_prelude(&env, bound); - bind(&mut env, bound); - let source = ArtifactSource::snapshot(&env).unwrap(); - let refused = source.admission().refused(); - assert!(refused.is_empty(), "{refused:?}"); - let mut checker = ArtifactChecker::::from_source(Ok(source)); - for declared in [Role::Propext, Role::Choice, Role::SorryAx] { - checker - .check_const(&role(declared)) - .unwrap_or_else(|e| panic!("{declared:?}: {e}")); - } - checker.check_const(&uses_propext).unwrap(); - checker.check_const(&unsafe_axiom).unwrap(); - for (address, reason) in [ - (&uses_sorry, "is not an admitted standard axiom"), - (&nonstandard, "is not a standard axiom of the checked theory"), - (&uses_nonstandard, "is not an admitted standard axiom"), - ] { - let error = checker.check_const(address).unwrap_err(); - assert!(format!("{error}").contains(reason), "{error}"); - } -} - -/// Bind the basis, `Bool`, the structural operations and a `Nat.div` of -/// `fun x _ => x` whose three evidence entries are the theorem `proof`; -/// return why `Nat.div` was refused. -fn refused_division(mut env: Env, proof: Address) -> String { - let nat = address_of(&env, "Nat"); - let div = definition( - &env, - 0, - pi(reference(0, vec![]), pi(reference(0, vec![]), reference(0, vec![]))), - lambda( - reference(0, vec![]), - lambda(reference(0, vec![]), arc(Expr::Var(1))), - ), - vec![nat], - vec![], - ); - let mut roles = basis_roles(&env); - roles.extend(bool_roles(&env)); - let mut evidence = vec![]; - for (role, name, equations) in STRUCTURAL { - roles.push((role, address_of(&env, name))); - evidence.push((role, vec![Evidence::DefEq; equations])); - } - roles.push((Role::NatDiv, div)); - evidence.push((Role::NatDiv, vec![Evidence::Proof(proof); 3])); - bind_with_evidence(&mut env, roles, evidence); - let source = ArtifactSource::snapshot(&env).unwrap(); - assert!(!source.is_admitted(Role::NatDiv)); - let refused = source.admission().refused(); - assert_eq!(refused.len(), 1, "{refused:?}"); - assert_eq!(refused[0].0, Role::NatDiv); - refused[0].1.clone() -} - -#[test] -fn a_well_founded_operation_needs_local_proofs_of_its_statements() { - // A proof that mentions records outside the operation's closure. - let env = loaded(); - let roles = axiom_roles(&env); - let proof = proof_of_true( - &env, - &roles[0].1, - reference(1, vec![]), - vec![roles[1].1.clone()], - vec![], - ); - let reason = refused_division(env, proof); - assert!(reason.contains("outside the operation's closure"), "{reason}"); - - // A local term that proves nothing. - let env = loaded(); - let proof = store( - &env, - ConstantInfo::Defn(Definition { - kind: DefKind::Theorem, - safety: DefinitionSafety::Safe, - lvls: 0, - typ: reference(0, vec![]), - value: reference(1, vec![]), - }), - vec![address_of(&env, "Nat"), address_of(&env, "Nat.zero")], - vec![], - ); - let reason = refused_division(env, proof); - assert!(reason.contains("certificate 0"), "{reason}"); - - // An unsafe or partial theorem is refused at snapshot preparation. - for safety in [DefinitionSafety::Unsafe, DefinitionSafety::Partial] { - let mut env = loaded(); - let proof = store( - &env, - ConstantInfo::Defn(Definition { - kind: DefKind::Theorem, - safety, - lvls: 0, - typ: reference(0, vec![]), - value: reference(1, vec![]), - }), - vec![address_of(&env, "Nat"), address_of(&env, "Nat.zero")], - vec![], - ); - let nat = address_of(&env, "Nat"); - let div = definition( - &env, - 0, - pi(reference(0, vec![]), pi(reference(0, vec![]), reference(0, vec![]))), - lambda( - reference(0, vec![]), - lambda(reference(0, vec![]), arc(Expr::Var(1))), - ), - vec![nat], - vec![], - ); - let mut roles = basis_roles(&env); - roles.extend(bool_roles(&env)); - let mut evidence = vec![]; - for (role, name, equations) in STRUCTURAL { - roles.push((role, address_of(&env, name))); - evidence.push((role, vec![Evidence::DefEq; equations])); - } - roles.push((Role::NatDiv, div)); - evidence.push((Role::NatDiv, vec![Evidence::Proof(proof.clone()); 3])); - bind_with_evidence(&mut env, roles, evidence); - match ArtifactSource::snapshot(&env) { - Err(ix_kernel::role_admission::Error::NotTheorem(addr)) => { - assert_eq!(addr, proof); - }, - other => panic!("expected NotTheorem for {safety:?}, got {other:?}"), - } - } -} - -fn theorem( - env: &Env, - typ: Arc, - value: Arc, - refs: Vec
, - univs: Vec>, -) -> Address { - declare( - env, - DefKind::Theorem, - DefinitionSafety::Safe, - typ, - value, - refs, - univs, - ) -} - -fn apps(head: Arc, arguments: Vec>) -> Arc { - arguments.into_iter().fold(head, app) -} - -/// A `Nat.div` admitted on a lemma that does not check: the lemma is local -/// to the division's certificates only through a dead reference in the -/// bound `Nat.sub`'s value, so the closure of a constant that uses the -/// division does not reach it. A verdict on that constant must still check -/// the lemma, because admission assumed it. -#[test] -fn a_closure_verdict_checks_what_role_admission_assumed() { - let mut env = loaded(); - let a = |name: &str| address_of(&env, name); - let (nat, zero, succ, eq) = (a("Nat"), a("Nat.zero"), a("Nat.succ"), a("Eq")); - let (eq_refl, eq_rec) = (a("Eq.refl"), a("Eq.rec")); - let (boolean, true_c, false_c) = (a("Bool"), a("Bool.true"), a("Bool.false")); - let ble = a("Nat.ble"); - let one = || Arc::new(Univ::Succ(Arc::new(Univ::Zero))); - let v = |i: u64| arc(Expr::Var(i)); - let r = |i: u64| reference(i, vec![]); - let eq_at = |i: u64, t: Arc, lhs: Arc, rhs: Arc| { - apps(reference(i, vec![0]), vec![t, lhs, rhs]) - }; - - // `theorem zero_is_one : @Eq Nat Nat.zero (Nat.succ Nat.zero) - // := @Eq.refl Nat Nat.zero`, which does not check. - // refs: Eq, Nat, Nat.zero, Nat.succ, Eq.refl - let zero_is_one = theorem( - &env, - eq_at(0, r(1), r(2), app(r(3), r(2))), - apps(reference(4, vec![0]), vec![r(1), r(2)]), - vec![eq.clone(), nat.clone(), zero.clone(), succ.clone(), eq_refl.clone()], - vec![one()], - ); - // `def sub (x y : Nat) : Nat := (fun _ : 0 = 1 => Nat.sub x y) zero_is_one` - // refs: Nat, Nat.sub, Eq, Nat.zero, Nat.succ, zero_is_one - let sub = definition( - &env, - 0, - pi(r(0), pi(r(0), r(0))), - lambda( - r(0), - lambda( - r(0), - app( - lambda( - eq_at(2, r(0), r(3), app(r(4), r(3))), - apps(r(1), vec![v(2), v(1)]), - ), - r(5), - ), - ), - ), - vec![ - nat.clone(), - a("Nat.sub"), - eq.clone(), - zero.clone(), - succ.clone(), - zero_is_one.clone(), - ], - vec![one()], - ); - // `def div (_ _ : Nat) : Nat := Nat.zero` - let div = definition( - &env, - 0, - pi(r(0), pi(r(0), r(0))), - lambda(r(0), lambda(r(0), r(1))), - vec![nat.clone(), zero.clone()], - vec![], - ); - // The certificates, over refs Nat, Eq, Bool, Nat.ble, Bool.true, - // Bool.false, Nat.succ, Nat.zero, div, sub, zero_is_one, Eq.refl. - let refs = vec![ - nat.clone(), - eq.clone(), - boolean, - ble, - true_c, - false_c, - succ.clone(), - zero.clone(), - div.clone(), - sub.clone(), - zero_is_one.clone(), - eq_refl.clone(), - ]; - let guard = |a: Arc, b: Arc, holds: bool| { - eq_at(1, r(2), apps(r(3), vec![a, b]), r(if holds { 4 } else { 5 })) - }; - let one_n = || app(r(6), r(7)); - let div_of = |x: Arc, y: Arc| apps(r(8), vec![x, y]); - // `∀ x y, ble y x = true → ble 1 y = true → div x y = succ (div (sub x y) - // y) := fun _ _ _ _ => zero_is_one` - let recursive = theorem( - &env, - pi( - r(0), - pi( - r(0), - pi( - guard(v(0), v(1), true), - pi( - guard(one_n(), v(1), true), - eq_at( - 1, - r(0), - div_of(v(3), v(2)), - app(r(6), div_of(apps(r(9), vec![v(3), v(2)]), v(2))), - ), - ), - ), - ), - ), - lambda( - r(0), - lambda( - r(0), - lambda( - guard(v(0), v(1), true), - lambda(guard(one_n(), v(1), true), r(10)), - ), - ), - ), - refs.clone(), - vec![one()], - ); - // `∀ x y, g = false → div x y = 0 := fun _ _ _ => rfl`, for the guards - // `ble y x` and `ble 1 y`. - let base = |guard_of: &dyn Fn() -> Arc| { - theorem( - &env, - pi( - r(0), - pi(r(0), pi(guard_of(), eq_at(1, r(0), div_of(v(2), v(1)), r(7)))), - ), - lambda( - r(0), - lambda( - r(0), - lambda(guard_of(), apps(reference(11, vec![0]), vec![r(0), r(7)])), - ), - ), - refs.clone(), - vec![one()], - ) - }; - let below = base(&|| guard(v(0), v(1), false)); - let by_zero = base(&|| guard(one_n(), v(0), false)); - - // `theorem vanishes : ∀ n, div n 2 = 0 := fun _ => rfl`, by unfolding. - // refs: Nat, Eq, div, 2, Nat.zero, Eq.refl - let two = env.store_blob(vec![2]); - let four = env.store_blob(vec![4]); - let vanishes = theorem( - &env, - pi(r(0), eq_at(1, r(0), apps(r(2), vec![v(0), arc(Expr::Nat(3))]), r(4))), - lambda(r(0), apps(reference(5, vec![0]), vec![r(0), r(4)])), - vec![ - nat.clone(), - eq.clone(), - div.clone(), - two.clone(), - zero.clone(), - eq_refl.clone(), - ], - vec![one()], - ); - // `theorem bad : 2 = 0 := @Eq.rec (motive := fun b _ => 2 = b) - // (rfl : 2 = div 4 2) (vanishes 4)`: `div 4 2` is `2` natively. - // refs: Eq, Nat, 2, Nat.zero, Eq.rec, div, 4, Eq.refl, vanishes - let eq1 = |lhs, rhs| apps(reference(0, vec![1]), vec![r(1), lhs, rhs]); - let lit = |i: u64| arc(Expr::Nat(i)); - let quotient = apps(r(5), vec![lit(6), lit(2)]); - let bad = theorem( - &env, - eq1(lit(2), r(3)), - apps( - reference(4, vec![0, 1]), - vec![ - r(1), - quotient.clone(), - lambda(r(1), lambda(eq1(quotient, v(0)), eq1(lit(2), v(1)))), - apps(reference(7, vec![1]), vec![r(1), lit(2)]), - r(3), - app(r(8), lit(6)), - ], - ), - vec![eq, nat, two, zero, eq_rec, div.clone(), four, eq_refl, vanishes], - vec![Arc::new(Univ::Zero), one()], - ); - - let mut roles = basis_roles(&env); - roles.extend(bool_roles(&env)); - let mut evidence = vec![]; - for (role, name, equations) in STRUCTURAL { - let bound = if role == Role::NatSub { sub.clone() } else { a(name) }; - roles.push((role, bound)); - evidence.push((role, vec![Evidence::DefEq; equations])); - } - roles.push((Role::NatDiv, div)); - evidence.push(( - Role::NatDiv, - [recursive, below, by_zero].into_iter().map(Evidence::Proof).collect(), - )); - let roles = with_prelude(&env, roles); - bind_with_evidence(&mut env, roles, evidence); - let source = ArtifactSource::snapshot(&env).unwrap(); - assert!(!source.is_admitted(Role::NatSub)); - assert!(!source.is_admitted(Role::NatDiv)); - - let mut checker = ArtifactChecker::::from_source(Ok(source.clone())); - assert!(checker.check_const(&bad).is_err()); - assert!( - checker - .verdict_closure(std::slice::from_ref(&bad)) - .unwrap() - .contains(&zero_is_one) - ); - assert!(checker.check_const_with_verdict_closure(&bad).is_err()); - assert!(checker.check_verdict(std::slice::from_ref(&bad), true).is_err()); - assert!(checker.check_verdict(std::slice::from_ref(&bad), false).is_err()); - assert!(checker.check_const(&zero_is_one).is_err()); - - let work = build_artifact_work(&source).unwrap(); - for dependencies in [true, false] { - let selected = - verdict_work(&source, &work, std::slice::from_ref(&bad), dependencies) - .unwrap(); - assert!(selected.iter().any(|item| item.primary() == &bad)); - assert!(selected.iter().any(|item| item.primary() == &zero_is_one)); - let failed: Vec<&Address> = selected - .iter() - .map(|item| item.primary()) - .filter(|primary| checker.check_const(primary).is_err()) - .collect(); - assert!( - failed.contains(&&zero_is_one) && failed.contains(&&bad), - "dependencies: {dependencies}, failed: {failed:?}" - ); - } -} - -#[test] -fn anchor_and_operation_roles_reject_axiom_and_partial_declarations_and_helpers() - { - let one = || Arc::new(Univ::Succ(Arc::new(Univ::Zero))); - - // 1. Anchor role (`Char`) directly bound to an `Axio` or `Partial` definition. - for use_axiom in [true, false] { - let mut env = loaded(); - let nat = address_of(&env, "Nat"); - let bad_char = if use_axiom { - axiom(&env, 0, arc(Expr::Sort(0)), vec![], vec![one()]) - } else { - declare( - &env, - DefKind::Definition, - DefinitionSafety::Partial, - arc(Expr::Sort(0)), - reference(0, vec![]), - vec![nat], - vec![one()], - ) - }; - let roles = with_prelude(&env, vec![(Role::Char, bad_char.clone())]); - bind(&mut env, roles); - let source = ArtifactSource::snapshot(&env).unwrap(); - assert!(!source.is_admitted(Role::Char), "use_axiom={use_axiom}"); - let mut checker = ArtifactChecker::::from_source(Ok(source)); - assert!(checker.check_const(&bad_char).is_err(), "use_axiom={use_axiom}"); - } - - // 2. Anchor role (`Char`) with a helper dependency that is `Axio` or `Partial`. - for use_axiom in [true, false] { - let mut env = loaded(); - let nat = address_of(&env, "Nat"); - let helper = if use_axiom { - axiom(&env, 0, arc(Expr::Sort(0)), vec![], vec![one()]) - } else { - declare( - &env, - DefKind::Definition, - DefinitionSafety::Partial, - arc(Expr::Sort(0)), - reference(0, vec![]), - vec![nat], - vec![one()], - ) - }; - let char_with_helper = definition( - &env, - 0, - arc(Expr::Sort(0)), - reference(0, vec![]), - vec![helper], - vec![one()], - ); - let roles = - with_prelude(&env, vec![(Role::Char, char_with_helper.clone())]); - bind(&mut env, roles); - let source = ArtifactSource::snapshot(&env).unwrap(); - assert!(!source.is_admitted(Role::Char), "use_axiom={use_axiom}"); - let mut checker = ArtifactChecker::::from_source(Ok(source)); - assert!( - checker.check_const(&char_with_helper).is_err(), - "use_axiom={use_axiom}" - ); - } - - // 3. Structural operation (`Nat.pred`) with an `Axio` or `Partial` helper dependency. - for use_axiom in [true, false] { - let mut env = loaded(); - let nat = address_of(&env, "Nat"); - let zero = address_of(&env, "Nat.zero"); - let real_pred = address_of(&env, "Nat.pred"); - let helper = if use_axiom { - axiom(&env, 0, reference(0, vec![]), vec![nat.clone()], vec![]) - } else { - declare( - &env, - DefKind::Definition, - DefinitionSafety::Partial, - reference(0, vec![]), - reference(1, vec![]), - vec![nat.clone(), zero], - vec![], - ) - }; - // `def pred (n : Nat) : Nat := (fun _ : Nat => Nat.pred n) helper` - let pred = definition( - &env, - 0, - pi(reference(0, vec![]), reference(0, vec![])), - lambda( - reference(0, vec![]), - app( - lambda( - reference(0, vec![]), - app(reference(1, vec![]), arc(Expr::Var(1))), - ), - reference(2, vec![]), - ), - ), - vec![nat, real_pred, helper], - vec![], - ); - let mut roles = basis_roles(&env); - roles.extend(bool_roles(&env)); - let mut evidence = vec![]; - for (role, name, equations) in STRUCTURAL { - let bound = if role == Role::NatPred { - pred.clone() - } else { - address_of(&env, name) - }; - roles.push((role, bound)); - evidence.push((role, vec![Evidence::DefEq; equations])); - } - let roles = with_prelude(&env, roles); - bind_with_evidence(&mut env, roles, evidence); - let source = ArtifactSource::snapshot(&env).unwrap(); - assert!(!source.is_admitted(Role::NatPred), "use_axiom={use_axiom}"); - assert!(!source.is_admitted(Role::NatSub), "use_axiom={use_axiom}"); - let mut checker = ArtifactChecker::::from_source(Ok(source)); - assert!(checker.check_const(&pred).is_err(), "use_axiom={use_axiom}"); - } -} - -/// `roles` with every prelude role it lacks bound to the compiled records. -fn with_prelude( - env: &Env, - mut roles: Vec<(Role, Address)>, -) -> Vec<(Role, Address)> { - for role in PRELUDE { - if !roles.iter().any(|(bound, _)| *bound == role) { - roles.push((role, address_of(env, role.lean_name()))); - } - } - roles -} - -fn declare( - env: &Env, - kind: DefKind, - safety: DefinitionSafety, - typ: Arc, - value: Arc, - refs: Vec
, - univs: Vec>, -) -> Address { - store( - env, - ConstantInfo::Defn(Definition { kind, safety, lvls: 0, typ, value }), - refs, - univs, - ) -} - -#[test] -fn a_bound_artifact_follows_the_safety_tier() { - let mut env = loaded(); - let nat = address_of(&env, "Nat"); - let zero_blob = env.store_blob(vec![]); - let nat_t = || reference(0, vec![]); - let zero_of = |safety| { - declare( - &env, - DefKind::Definition, - safety, - nat_t(), - arc(Expr::Nat(1)), - vec![nat.clone(), zero_blob.clone()], - vec![], - ) - }; - // `partial def p : Nat := 0` and `unsafe def u : Nat := 0`. - let p = zero_of(DefinitionSafety::Partial); - let u = zero_of(DefinitionSafety::Unsafe); - // `def v : @Eq.{1} Nat c 0 := @Eq.refl.{1} Nat 0` at `v`'s safety, which - // needs `c` unfolded. - let one = Arc::new(Univ::Succ(Arc::new(Univ::Zero))); - let needs_unfolded = |c: &Address, safety| { - declare( - &env, - DefKind::Definition, - safety, - app( - app( - app(reference(0, vec![0]), reference(2, vec![])), - reference(3, vec![]), - ), - arc(Expr::Nat(4)), - ), - app(app(reference(1, vec![0]), reference(2, vec![])), arc(Expr::Nat(4))), - vec![ - address_of(&env, "Eq"), - address_of(&env, "Eq.refl"), - nat.clone(), - c.clone(), - zero_blob.clone(), - ], - vec![one.clone()], - ) - }; - let unfolds_p = needs_unfolded(&p, DefinitionSafety::Partial); - let unfolds_u = needs_unfolded(&u, DefinitionSafety::Unsafe); - // `unsafe opaque o : Nat := u`: an opaque is checked as safe. - let opaque = declare( - &env, - DefKind::Opaque, - DefinitionSafety::Unsafe, - nat_t(), - reference(1, vec![]), - vec![nat.clone(), u.clone()], - vec![], - ); - // A safe axiom whose type mentions `u`. - let mentions_u = axiom( - &env, - 0, - pi(nat_t(), app(reference(2, vec![]), reference(1, vec![]))), - vec![nat.clone(), u.clone(), opaque.clone()], - vec![], - ); - - // Unbound, the kernel follows Lean's: partial and unsafe definitions - // unfold, and the fence covers definitions only. - let unbound = ArtifactSource::snapshot(&env).unwrap(); - let mut checker = ArtifactChecker::::from_source(Ok(unbound)); - checker.check_const(&unfolds_p).unwrap(); - checker.check_const(&unfolds_u).unwrap(); - checker.check_const(&opaque).unwrap(); - - let roles = with_prelude(&env, vec![]); - bind(&mut env, roles); - let source = ArtifactSource::snapshot(&env).unwrap(); - assert!(source.in_safety_tier(&p) && source.in_safety_tier(&u)); - assert!(!source.in_safety_tier(&address_of(&env, "Nat.add"))); - let mut checker = ArtifactChecker::::from_source(Ok(source)); - checker.check_const(&p).unwrap(); - // Partial definitions stay opaque; unsafe ones unfold, as in Lean. - assert!(checker.check_const(&unfolds_p).is_err()); - checker.check_const(&unfolds_u).unwrap(); - let error = checker.check_const(&opaque).unwrap_err(); - assert!( - format!("{error}").contains("references unsafe definition"), - "{error}" - ); - let error = checker.check_const(&mentions_u).unwrap_err(); - assert!( - format!("{error}").contains("references unsafe definition"), - "{error}" - ); -} diff --git a/crates/kernel/tests/serialized_units.rs b/crates/kernel/tests/serialized_units.rs deleted file mode 100644 index f06c6186a..000000000 --- a/crates/kernel/tests/serialized_units.rs +++ /dev/null @@ -1,293 +0,0 @@ -//! Complete-unit validation for canonical serialized mutual blocks. -use ix_common::address::Address; -use ix_common::env::Name; -use ix_kernel::anon_work::{AnonWorkItem, build_anon_work}; -use ix_kernel::env::KEnv; -use ix_kernel::ingress::{ - build_ixon_ingress_lookups, ingress_anon_block, - ingress_const_shallow_into_kenv_with_lookups, -}; -use ix_kernel::ixon_checker::IxonChecker; -use ix_kernel::mode::{Anon, Meta}; -use ix_kernel::tc::TypeChecker; -use ixon::constant::{ - Constant, ConstantInfo, Constructor, MutConst, ctor_proj_constant, - defn_proj_constant, indc_proj_constant, recr_proj_constant, -}; -use ixon::env::{Env, Named}; -use ixon::expr::Expr; -use ixon::metadata::{ConstantMeta, ConstantMetaInfo, ExprMeta}; - -const GOOD: &[u8] = - include_bytes!("fixtures/serialized_units/homogeneous-good.ixe"); -const BAD: &[u8] = - include_bytes!("fixtures/serialized_units/homogeneous-bad-sibling.ixe"); -const FAMILY: &[u8] = - include_bytes!("fixtures/serialized_units/family-good.ixe"); -const BAD_RECURSOR: &[u8] = - include_bytes!("fixtures/serialized_units/family-bad-recursor.ixe"); -const MIXES: [&[u8]; 3] = [ - include_bytes!("fixtures/serialized_units/mixed-good.ixe"), - include_bytes!("fixtures/serialized_units/mixed-bad-primary.ixe"), - include_bytes!("fixtures/serialized_units/mixed-bad-sibling.ixe"), -]; - -fn load(bytes: &[u8]) -> (Env, Address, Constant, Vec
) { - let mut input = bytes; - let env = Env::get(&mut input).unwrap(); - assert!(input.is_empty()); - let mut roundtrip = Vec::new(); - env.put(&mut roundtrip).unwrap(); - assert_eq!(roundtrip, bytes); - assert!( - env.named.is_empty() && env.blobs.is_empty() && env.assumptions.is_empty() - ); - let (address, block) = env - .consts - .iter() - .find_map(|entry| { - let value = entry.get().unwrap(); - matches!(value.info, ConstantInfo::Muts(_)) - .then(|| (entry.key().clone(), (*value).clone())) - }) - .unwrap(); - let ConstantInfo::Muts(members) = &block.info else { unreachable!() }; - let targets = members - .iter() - .enumerate() - .map(|(i, member)| { - match member { - MutConst::Defn(_) => defn_proj_constant(i as u64, address.clone()), - MutConst::Indc(_) => indc_proj_constant(i as u64, address.clone()), - MutConst::Recr(_) => recr_proj_constant(i as u64, address.clone()), - } - .commit() - .0 - }) - .collect(); - (env, address, block, targets) -} - -fn check_every_target(env: &Env, targets: &[Address], accepts: bool) { - for first in targets { - let mut reused = IxonChecker::new(env); - assert_eq!(reused.check_const(first).is_ok(), accepts); - for target in targets { - assert_eq!(reused.check_const(target).is_ok(), accepts); - assert_eq!(IxonChecker::new(env).check_const(target).is_ok(), accepts); - } - } -} - -fn add_metadata( - env: &Env, - block_addr: &Address, - block: &Constant, - targets: &[Address], -) -> Vec { - let ConstantInfo::Muts(members) = &block.info else { unreachable!() }; - let names: Vec<_> = (0..members.len()) - .map(|i| Name::str(Name::anon(), format!("member{i}"))) - .collect(); - let name_addr = |n: &Name| Address::from_blake3_hash(*n.get_hash()); - let addresses: Vec<_> = names.iter().map(name_addr).collect(); - let universe = Name::str(Name::anon(), "u".into()); - env.store_name(name_addr(&universe), universe.clone()); - for (i, (name, member)) in names.iter().zip(members).enumerate() { - let info = match member { - MutConst::Defn(_) => ConstantMetaInfo::Def { - name: addresses[i].clone(), - lvls: vec![], - all: addresses.clone(), - ctx: addresses.clone(), - arena: ExprMeta::default(), - type_root: 0, - value_root: 0, - }, - MutConst::Indc(_) => ConstantMetaInfo::Indc { - name: addresses[i].clone(), - lvls: vec![], - ctors: vec![], - all: vec![addresses[i].clone()], - ctx: addresses.clone(), - arena: ExprMeta::default(), - type_root: 0, - }, - MutConst::Recr(_) => ConstantMetaInfo::Rec { - name: addresses[i].clone(), - lvls: vec![name_addr(&universe)], - rules: vec![], - all: vec![addresses[0].clone()], - ctx: addresses.clone(), - arena: ExprMeta::default(), - type_root: 0, - rule_roots: vec![], - }, - }; - env.store_name(addresses[i].clone(), name.clone()); - env.register_name( - name.clone(), - Named::new( - targets[i].clone(), - ConstantMeta { info, ..ConstantMeta::default() }, - ), - ); - } - let root = Name::str(Name::anon(), "family".into()); - env.store_name(name_addr(&root), root.clone()); - env.register_name( - root, - Named::new( - block_addr.clone(), - ConstantMeta { - info: ConstantMetaInfo::Muts { - all: addresses.into_iter().map(|a| vec![a]).collect(), - aux_layout: None, - }, - ..ConstantMeta::default() - }, - ), - ); - names -} - -#[test] -fn unsupported_serialized_mixtures_fail_before_publication_or_work() { - for bytes in MIXES { - let (env, address, block, targets) = load(bytes); - assert!(build_anon_work(&env).unwrap_err().contains("definitions mixed")); - check_every_target(&env, &targets, false); - let mut anon = KEnv::::new(); - assert!( - ingress_anon_block(&mut anon, &env, &block, &address) - .unwrap_err() - .contains("definitions mixed") - ); - assert!(anon.consts.is_empty() && anon.blocks.is_empty()); - let names = add_metadata(&env, &address, &block, &targets); - let lookups = build_ixon_ingress_lookups(&env); - let mut meta = KEnv::::new(); - assert!( - ingress_const_shallow_into_kenv_with_lookups( - &mut meta, &env, &lookups, &names[0] - ) - .unwrap_err() - .contains("definitions mixed") - ); - assert!(meta.consts.is_empty() && meta.blocks.is_empty()); - } -} - -#[test] -fn homogeneous_and_inductive_recursor_units_check_all_targets() { - for (bytes, accepts) in - [(GOOD, true), (BAD, false), (FAMILY, true), (BAD_RECURSOR, false)] - { - let (env, address, block, targets) = load(bytes); - let work = build_anon_work(&env).unwrap(); - assert_eq!(work.len(), 1); - let AnonWorkItem::Block { targets: actual, .. } = &work[0] else { - panic!("block") - }; - assert_eq!(actual, &targets); - assert_eq!(work[0].proven_targets().len(), targets.len() + 1); - check_every_target(&env, &targets, accepts); - let names = add_metadata(&env, &address, &block, &targets); - let lookups = build_ixon_ingress_lookups(&env); - for name in &names { - let mut kernel = KEnv::::new(); - let id = ingress_const_shallow_into_kenv_with_lookups( - &mut kernel, - &env, - &lookups, - name, - ) - .unwrap(); - let mut checker = - TypeChecker::new_with_lazy_ixon(&mut kernel, &env, &lookups); - assert_eq!(checker.check_ixon_const(&id).is_ok(), accepts); - } - } -} - -#[test] -fn empty_units_and_well_scoped_invalid_recursors_do_not_gain_coverage() { - let empty = Env::new(); - let block = Constant::new(ConstantInfo::Muts(vec![])); - let address = block.commit().0; - empty.store_const(address.clone(), block.clone()); - assert!(build_anon_work(&empty).unwrap_err().contains("empty serialized")); - let mut kernel = KEnv::::new(); - assert!(ingress_anon_block(&mut kernel, &empty, &block, &address).is_err()); - assert!(kernel.consts.is_empty()); - - let (_, _, mut block, _) = load(FAMILY); - let ConstantInfo::Muts(members) = &mut block.info else { unreachable!() }; - let MutConst::Recr(recursor) = &mut members[1] else { unreachable!() }; - recursor.typ = Expr::sort(1); // scoped, but not the required recursor type. - let env = Env::new(); - let address = block.commit().0; - env.store_const(address.clone(), block); - let mut targets = Vec::new(); - for c in - [indc_proj_constant(0, address.clone()), recr_proj_constant(1, address)] - { - let a = c.commit().0; - targets.push(a.clone()); - env.store_const(a, c); - } - assert_eq!(build_anon_work(&env).unwrap().len(), 1); - check_every_target(&env, &targets, false); -} - -#[test] -fn constructor_target_cannot_skip_the_family_recursor() { - let (_, _, mut block, _) = load(FAMILY); - let ConstantInfo::Muts(members) = &mut block.info else { unreachable!() }; - let MutConst::Indc(inductive) = &mut members[0] else { unreachable!() }; - inductive.ctors.push(Constructor { - is_unsafe: false, - lvls: 0, - cidx: 0, - params: 0, - fields: 0, - typ: Expr::rec(0, vec![]), - }); - // The retained recursor is scoped but still claims the empty family schema. - let env = Env::new(); - let address = block.commit().0; - env.store_const(address.clone(), block); - let mut targets = Vec::new(); - for constant in [ - indc_proj_constant(0, address.clone()), - ctor_proj_constant(0, 0, address.clone()), - recr_proj_constant(1, address), - ] { - let target = constant.commit().0; - targets.push(target.clone()); - env.store_const(target, constant); - } - let work = build_anon_work(&env).unwrap(); - assert_eq!(work.len(), 1); - assert_eq!(work[0].targets(), targets); - assert_eq!(work[0].proven_targets().len(), 4); - let block_address = match &work[0] { - AnonWorkItem::Block { block_addr, .. } => block_addr, - _ => unreachable!(), - }; - let mut kernel = KEnv::::new(); - ingress_anon_block( - &mut kernel, - &env, - &env.get_const(block_address).unwrap(), - block_address, - ) - .unwrap(); - let mut member_checker = TypeChecker::new(&mut kernel); - for target in &targets[..2] { - member_checker - .check_const(&ix_kernel::id::KId::new(target.clone(), ())) - .unwrap(); - } - check_every_target(&env, &targets, false); -} diff --git a/docs/Ixon.md b/docs/Ixon.md index 045ad7151..737febb07 100644 --- a/docs/Ixon.md +++ b/docs/Ixon.md @@ -642,16 +642,16 @@ table is part of the canonical definition of the constant. The canonical sharing of a constant is defined as a function of its anonymous expressions. Two constants with equal expanded roots, `ConstantInfo` fields, `refs`, `univs` and format must get identical bytes, whatever their -in-memory pointer sharing, construction history, compiler (Lean or Rust) or +in-memory pointer sharing, construction history, implementation or existing table. Metadata never influences it. Which parts of this the proofs cover, and which are checked at run time or by tests, is stated in [What is proved, and what is not](#what-is-proved-and-what-is-not). -Both compilers build every table with this construction -(`Ix.Sharing.Exact.canonicalSharingTiered .tagN`, Rust -`canonical_sharing_tiered(ShareLayout::TagN, ..)`): single constants and -mutual blocks, and in Rust also kernel egress. It is the only sharing -construction. +The compiler builds every table with this construction +(`Ix.Sharing.Exact.canonicalSharingTiered .tagN`): single constants and +mutual blocks. It is the only sharing construction, and Lean holds its only +implementation; the Rust port was retired in PR #677 +([retired Rust](retired-rust.md)). ### Table rules @@ -668,7 +668,8 @@ construction. Roots may reference any entry. Expanding each `Share(j)` to entry `j`, left to right, reproduces the roots exactly. No de Bruijn index is shifted at a `Share`. Readers reject a `Share` index outside the table; the - decompilers and kernel ingress do not yet check backwardness (see + decompiler does not yet check backwardness, and IxC's reader fails on a + self or forward reference only when it uses one (see [Sharing in metadata expressions](#sharing-in-metadata-expressions)). The canonical construction guarantees it for compiler output. - **Encoding.** A Share is `N4(0xB, idx)`: 1 byte for indices 0–7, 2 bytes @@ -684,13 +685,11 @@ The adopted canonicalization revision is **`sharing-k16-greedy-v1`**, with K = 1 It is recorded here and in [sharing-minimum §14](sharing-minimum.md#14-canonical-rule-revision-sharing-k16-greedy-v1). This is a change to the canonical choice of sharing, with no change to the wire grammar, checked-object/claim format 4, or the environment envelope versions (4 and 5). -Both compilers implement this revision. It replaces the previous unbounded +It replaces the previous unbounded phase-1 search; the table rules and phases 2 and 3 are unchanged. The canonical table is the output of `Ix.Sharing.Exact.canonicalSharingTiered -.tagN` (Lean, `Ix/Sharing/Exact/Tiered.lean`) and of -`canonical_sharing_tiered(ShareLayout::TagN, ..)` (Rust, -`crates/ixon/src/sharing_exact/tiered.rs`). It runs on the roots with every +.tagN` (`Ix/Sharing/Exact/Tiered.lean`). It runs on the roots with every existing `Share` expanded. A constant that already carries its canonical table normalizes to the same bytes (tested; proved under a hypothesis, see "Idempotence" below). @@ -743,62 +742,51 @@ table normalizes to the same bytes (tested; proved under a hypothesis, see the lowest failing width. **Run-time checks.** Each candidate is checked as it is built, and a failed -check is an internal error, never a different table. Lean builds and checks -all three candidates. Phase 3's checks (`rematerialize`) are, in order: the +check is an internal error, never a different table. All three candidates +are built and checked under the same limits; none is exempt because another +wins. Phase 3's checks (`rematerialize`) are, in order: the layout length equals the evaluated length; it is at most phase 1's layout length; the output re-expands, through the same interner, to the stored terms and to the input's root IDs; and every output expression is in the wire -domain. On a DAG of at least 1,024 terms the three widths run as parallel +domain. The serialized length is not measured at run time: it equals the +layout length by theorem (`canonicalSharingTiered_serialized`, below). On a +DAG of at least 1,024 terms the three widths run as parallel tasks (`tieredCandidates`; `tieredCandidates_eq` proves the result and the -error order equal to the sequential run). Rust's default mode, which the -compiler uses, applies every limit and every length check of phases 1 to 3 -to all three candidates, but builds only the returned candidate's expressions -and checks those: their layout price, their re-expansion and node count, and -their serialized length. Its checked mode (`ExactSharingLimits::full_check`, -used by the unit tests, the FFI parity hooks and `sharing_corpus ---full-check`) builds and checks all three; over all of Init and all of -Mathlib it returns the same output as the default mode. The exact list is the -"Checks" section of the module documentation of -`crates/ixon/src/sharing_exact/tiered.rs`. - -**Limits.** The construction runs under explicit resource limits (Lean -`Ix.Sharing.Exact.Limits`, Rust `ExactSharingLimits`). They are a safety net, +error order equal to the sequential run). Phase 3 evaluates the whole table +once when the order is closed under stored descendants and once per prefix +otherwise; `materializeTableOnePass_eq` proves the two paths equal on every +input, errors and work charges included, so the limits meter the +specification's work whichever path runs. + +**Limits.** The construction runs under explicit resource limits +(`Ix.Sharing.Exact.Limits`). They are a safety net, not part of the definition: they decide whether a run succeeds, never which bytes a success produces. The defaults are far above every value observed on -the Init and Mathlib corpora. Each limit is at least 2^6 (Lean) or 2^8 (Rust) +the Init and Mathlib corpora. Each limit is at least 2^6 times the earlier default under which those corpora were measured, for -example `states` 2^40 and `materialize_work` 2^56. `knapsack_cells` is 2^28 in -both languages, about 27 times the largest table-count knapsack of any width +example `states` 2^40 and `materialize_work` 2^56. `knapsack_cells` is 2^28, +about 27 times the largest table-count knapsack of any width on Init and Mathlib (9,959,424 cells). The number of sharing candidates is -recorded but not limited on this path in either language: Rust's -`candidates` limit (2^16) bounds only the exhaustive width-state search kept as -a test oracle, which is also the only meter of `transitions`. - -`ix compile --sharing-limits SPEC` (also `ix compile-lean --sharing-limits`) -overrides the defaults for the uncertified compilers (`--rust`, `--streamed`); -the certified compiler, whose correctness theorem fixes the defaults, refuses an -override. It sets the `IX_SHARING_LIMITS` environment variable, -which both compilers read once per run: Rust once per compile or decompile -entry point (`CompileState::sharing_limits`), Lean in the CLI's driver options (`Ix.Cli.driverOptionsFromEnv`) -and `decompileEnvFullParallel`. An invalid value therefore fails the run once, -before any block is shared. `SPEC` is a comma-separated list of items, each -`key=value` or the word `unbounded` (every production limit at its maximum). A -value is ASCII decimal digits (no sign, no `_` separator), `2^k` with -`k ≤ 63`, or `max` (2^64 − 1). Items, keys and values are trimmed of spaces, -tabs, line feeds and carriage returns only. The keys are the limit names of -either implementation (for example `states`, `depth`, `materialize_work`, -`distinct_nodes`); keys of the other language are accepted and ignored, so one -string serves both compilers. One list of 35 specifications pins both parsers -(`LIMIT_SPEC_PARITY` in `crates/ixon/src/sharing_exact/tests.rs`, -`limitSpecParity` in `Tests/Ix/SharingExact.lean`). +recorded but not limited on this path. `transitions` meters only the +exhaustive width-state search kept as a test oracle. + +The certified compiler (`Ix.Cert.compileReport`) always runs at the default +sharing limits (`Ix.CompileM.compilerSharingLimits`), which its correctness +theorem fixes. For unit tests, benchmarks, and study tools, +`Ix.Sharing.Exact.Limits.withOverrides` parses an override string `SPEC`: a +comma-separated list of items, each `key=value` or the word `unbounded` (every +production limit at its maximum). A value is ASCII decimal digits (no sign, no +`_` separator), `2^k` with `k ≤ 63`, or `max` (2^64 − 1). Items, keys and +values are trimmed of spaces, tabs, line feeds and carriage returns only. The +keys are the limit names (for example `states`, `depth`, `materialize_work`); +any other key, including those of the retired Rust implementation (for example +`distinct_nodes`), is an error. The 35 specifications of +`limitSpecParity` (`Tests/Ix/SharingExact.lean`) pin the grammar. Exceeding a limit is a compile error (`CompileError.resourceLimit`) that -names the limit's key and the override, for example `canonical sharing: -resource exhausted: states (limit 1099511627776); raise it with ---sharing-limits states=N (IX_SHARING_LIMITS)`. Every other construction -failure is `CompileError.sharingConstruction`. In Rust, kernel egress and the -decompiler's recompile name the constant in the message (`egress '': -…`, `recompile of '' (sharing): …`). The structural gate above fixes the +names the limit's key and bound, for example `canonical sharing: +resource exhausted: states (limit 1099511627776)`. Every other construction +failure is `CompileError.sharingConstruction`. The structural gate above fixes the exact or greedy family before search. A refusal never switches families or emits a partial or best-so-far table. @@ -929,8 +917,7 @@ re-expansion to the stored terms and the input's root IDs, and the wire domain. The retained exact reference has its `phase3_le_phase1` proof; the family successful-run theorem obtains that inequality from the executed guard. Proving the guard unreachable for the entire family domain remains follow-up. -Rust runs every check listed in the module documentation of -`sharing_exact/tiered.rs`. The theorems are stated for successful runs, so a +The theorems are stated for successful runs, so a failing check turns a run into an error, never into a different table. Not claimed: @@ -949,32 +936,15 @@ Not claimed: Earlier exact-route tests re-ran every stored constant and reproduced its bytes for the Init and Init+Std files compiled on Lean 4.34.0 and for the Init and Mathlib files compiled on Lean 4.33.1. -- **Rust.** The Rust implementation is not proved. It is checked against - Lean by differential tests (`exact-sharing-ffi`; with `IX_SHARING_CORPUS` - it runs over a whole `.ixe`). Its phase-1 search evaluates each - component's area under a truncated cost model, where Lean's specification - evaluates the component's whole closure; the outputs are equal on every - input tested. The following measurements are historical exact-route evidence; - §14.4 of sharing-minimum records the K16 prototype evidence, and the production - gates remain separate. On corpora compiled with `ix compile` on Lean 4.34.0, with - Rust in its checked mode, Lean and Rust produce the same bytes for all - 56,783 stored constants of Init and all 100,277 of Init+Std, with no - resource exhaustion on either side; on Lean 4.33.1 the same held for - Init (56,622 stored constants; the 4.34.0 `Init` is a larger library) and - for all 20,284 constants of a Mathlib sample (every 50th constant, plus - every constant with more than 2,000 candidates). The merge-queue suite - `lake test -- --ignored compile` compiles every constant of its test - environment (238,574 on Lean 4.34.0) with the Lean and the Rust compiler - and requires identical serialized environments - ([performance](sharing-minimum-performance.md)). -- **Length range in Rust.** Lean computes lengths with arbitrary-precision - `Nat`. Rust computes phase-1 costs and signed deltas in 128-bit integers - and, where a value leaves that range, again in exact integers, with the - same result wherever the 128-bit run succeeds (§12.18 of sharing-minimum), - so a long unshared length is no refusal. All 11 doubling fixtures, - including depths 127–256, have complete Rust/Lean byte parity, and the - Lean/Rust sharing suite compares four towers whose unshared length exceeds - `2^127` byte for byte. +- **A second implementation.** Lean holds the only implementation. Until + PR #677 a Rust port, unproved, was held to it by differential tests: the + same bytes for every stored constant of Init and Init+Std, a Mathlib + sample, and every constant of the merge-queue test environment compiled by + both compilers (sharing-minimum §12.10, §14.4; + [performance](sharing-minimum-performance.md)). That evidence is history; + a new implementation would have to be held to the Lean one again. + Lengths are computed in arbitrary-precision `Nat`, so a long unshared + length is never a refusal. - **Failure.** A run that exceeds a limit returns an error. On the totality domain (fewer than `2^64` input nodes and universes per reference, implied by the framed constant-body bound) no guard is reachable under a sufficient @@ -1014,10 +984,9 @@ D2 00 00 -- N4(0xD, 2) Axiom, not unsafe, lvls = 0 ``` The bytes were produced by `normalizeConstantSharingTiered .tagN`, and the -`exact-sharing` suite checks them. The compilers' sharing builders -(`Ix.CompileM.buildConstantWithSharing`, Rust -`apply_sharing_to_axiom_with_limits`) build the same bytes from the unshared -constant (`exact-sharing-ffi`). Every integer here is below the first TagN +`exact-sharing` suite checks them. The compiler's sharing builder +(`Ix.CompileM.buildConstantWithSharing`) runs the same construction on the +unshared constant's roots and writes them back in place. Every integer here is below the first TagN rung end, so v3's integer codes would write the same bytes for this table. ### Sharing in metadata expressions @@ -1046,25 +1015,23 @@ table (for a projection, the table of its `Muts` block) and `q` the size of Every metadata-to-metadata step goes to a lower entry, so expansion terminates. -Both decompilers implement the rule: Lean `Ix.DecompileM` (`ShareScope`, -`resolveShareIn`) and Rust `ix_compile::decompile` (`ShareScope`). When a -constant's metadata is loaded, they check the whole table first -(`validateMetaSharing`, Rust `validate_meta_sharing`): every `Share(i)` +The decompiler implements the rule (`Ix.DecompileM`: `ShareScope`, +`resolveShareIn`). When a constant's metadata is loaded, it checks the whole +table first (`validateMetaSharing`): every `Share(i)` occurring in `metaSharing[j]` must have `i < p + j`. The first violation, in entry order and then left-to-right pre-order, is reported as -`invalidMetaShareIndex` (Rust `DecompileError::InvalidMetaShareIndex`, -error tag 11). An out-of-range index in a primary expression is +`invalidMetaShareIndex`. An out-of-range index in a primary expression is `invalidShareIndex`; a record whose `_ix.inline` or arena root is out of range is a bad constant format. The semantic-contract scan -(`Ix.SemanticContract.containsIxon`, Rust `semantic_contract::contains_ixon`) -expands Shares with the same scopes. +(`Ix.SemanticContract.containsIxon`) expands Shares with the same scopes. Kernel ingress, the IxVM circuit and the resource validators do not read `metaSharing` contents. Two older gaps remain: a record nested inside a metadata expression may reference any `metaSharing` entry, including its own, -and neither the decompilers nor kernel ingress check that primary entries -reference only earlier entries. A malformed table of either kind can make -them loop instead of failing; hardening the readers against malformed input -is follow-up work. +and the decompiler does not check that primary entries reference only +earlier entries. A malformed table of either kind can make it loop instead +of failing; hardening it against malformed input is follow-up work. Kernel +ingress (IxC's reader, `MemberReader.mk'`) builds each primary entry against +the entries before it, so a self or forward reference it uses is an error. The primary table is built first and never depends on metadata. No compiler emits a `Share` in metadata expressions, and there is no canonical metadata @@ -1416,8 +1383,7 @@ Both compiler commands generate the binding and proof records from the actual published declarations and certificate sources before writing their output. Internal support compilation uses the same backend and preserves the original request's publication/failure accounting. Named metadata is removed only after -generation for anonymous output. The ordinary `compile-lean --rust-check` gate -compares the complete generated artifacts from both backends. +generation for anonymous output. For bound whole inputs, `check-lean` runs the actual authenticated logical or typing-tier entry, selected from record headers before checking. The logical @@ -1847,7 +1813,7 @@ Proof bytes are uniform opaque ZK proofs — witness data (e.g., merkle paths for Contains) is prover-side scratch consumed by the ZK circuit and NOT transmitted on the wire. Wrapper decoding alone proves nothing. IxVM supports Eval, Check, CheckEnv, Reveal, and Contains; Resource is currently -validated by the native Lean/Rust boundary and is explicitly rejected as an +validated natively (`Ix.Resource.validate`) and is explicitly rejected as an IxVM proof request. Aggregation folds only children whose contexts are equal, except for shape 15, which joins claims whose bindings share a literal class, and shape 16, which collects member claims under any contexts into a Catalog. diff --git a/docs/benchmarking.md b/docs/benchmarking.md index 3070eb4ec..4603de922 100644 --- a/docs/benchmarking.md +++ b/docs/benchmarking.md @@ -17,8 +17,7 @@ workflows execute, so every CI number is reproducible on your machine. ## The row contract Every measured tool reports through one shape — the **benchmark results JSON** — -and one exit-code convention (Rust: `crates/bench`; Lean: -`Ix/Benchmark/Results.lean`): +and one exit-code convention (`Ix/Benchmark/Results.lean`): ```json { "": { "status": "ok", "": 123, "phase-": 1.5 } } @@ -28,8 +27,13 @@ and one exit-code convention (Rust: `crates/bench`; Lean: - `status` is `ok` or `rejected` (written by the tool) or `oom` (merged in by `ix bench run` after an abnormal exit — a process never observes its own OOM kill). An `oom` row keeps whatever metrics landed before the kill. + `ix check-lean` also writes `partial` (exit 4: every declaration passed, but + the whole artifact carries no role binding) and `conditional` (exit 5: it + passed only with user axioms admitted). - Exit codes: `0` all ok · `2` usage error · `3` the kernel **rejected** a - constant (its row is on disk) · anything else is an infrastructure failure. + constant (its row is on disk) · `4`/`5` `ix check-lean`'s partial and + conditional runs (their rows are on disk) · anything else is an + infrastructure failure. There is no output scraping anywhere: no marker lines, no log grepping, no sentinel-key jq. State flows through rows and exit codes only. A rejected or @@ -179,8 +183,9 @@ arena-copy fix; they do not measure end-to-end proving or CI wall time. Caching the environment's role binding across constants in the benchmark runners (`assumeBinding := true` in `bench-typecheck`, matching the production claim tree where the binding is proven once and assumed by per-constant claims, -and upfront `verdict_work` support checking in `rs_kernel_check_anon_consts`) -eliminates redundant per-constant role-table re-admission: +and upfront `verdict_work` support checking in the Rust kernel's anonymous +check, since retired) eliminates redundant per-constant role-table re-admission. +The `ooc` column was measured when `ooc` ran the Rust kernel: | Constant | Checked constants (assumed binding) | Stage 1 FFT cost, billions (`09ee682` → assumed binding) | `ooc` check-time (s) | |---|---:|---:|---:| @@ -208,7 +213,7 @@ FFT cost from 115.65B to 99.33B (−14.1%), and `total-time` from 63.85s to | backend | what it measures | tool | |---|---|---| | `aiur` | the Aiur proof pipeline, per constant: the `ixvm` stage proves the IxVM typecheck, the `fri-verifier` stage executes and proves the in-circuit multi-stark verifier over that fresh proof (the KZG stages fold in as they land, each with its own measure prefix), closed by the pipeline ledger (total-time, pipeline-throughput, pipeline-peak-rss). Each stage's measures carry its prefix (`ixvm-prove-time`, `fri-verifier-fft-cost`, …). The whole system runs under the recursion-tuned FRI parameters. A second mode, execute, is the fast Phase-1-only signal (fft-cost, execute-time, throughput, peak-rss) — unscheduled, local/on-demand only (`!benchmark aiur execute`). The direct `--recursive --join` diagnostic takes exactly two constants as singleton `CheckEnv` shards and appends one pair row carrying `join-{execute-time,fft-cost,prove-time,peak-rss,proof-size,verify-time}`; the InitStd prove cell schedules this pair in a separate process after the per-constant runs. | `bench-typecheck --recursive` | -| `ooc` | out-of-circuit Rust kernel: whole-env row + one full-closure row per constant (`check-time` wraps only the check — the env loads once, outside every row's timed window) | `ix check-rs --json` | +| `ooc` | out-of-circuit Lean kernel (`IxC`): whole-env row + one full-closure row per constant (`check-time` wraps only the check — the env loads once, outside every row's timed window) | `ix check-lean --anon --json` | | `compile` | `ix compile .lean → .ixe`, the default certified compiler: compile-time, file-size, constants, throughput | `ix compile --json` | | `decompile` | inverse of compile — `ix decompile .ixe → Lean consts`: decompile-time, throughput, peak-rss, constants, file-size (input `.ixe`). Consumes the compile cell's `.ixe` rather than producing one; a malformed decompile reddens the cell. Deep roundtrip fidelity is gated by the canonical checks (`ix validate` / roundtrip tests), which need the original Lean env the `.ixe` can't supply | `ix decompile --json` | @@ -220,13 +225,10 @@ lines on the compile plots; they never enter the certified alert baseline. PR comparisons across the switch include the cost of certification and remain informational: the table displays the delta without failing on its size. Compiler failures, missing rows and kernel rejections still fail their jobs. -For a separate Rust measurement, run -`ix compile .lean --rust --out rust.ixe --json rust-compile.json --json-name `; -keep that row separate from the certified series. The certified checker's environment check (`kernel-check-ixe`, the verified checker behind the Ixon reader) is documented in [Benchmarks/Kernel/README.md](../Benchmarks/Kernel/README.md) -and [kernel.md](kernel.md). It is separate from the compiler and Rust kernel benchmarks above; +and [kernel.md](kernel.md). It is separate from the compiler and `ooc` benchmarks above; the former Lean4Lean replay backend has been retired. ### Aggregate baselines @@ -524,9 +526,7 @@ For context, the retired union `ix catalog` peaked 35.3 GiB at spine scale (N=12) and OOM'd this box on its first full-corpus attempt; the piece pipeline's box pressure is `jobs × member`, never a union. Per-member kernel checking (`lake exe truthmines check`, suite -`truthmines-check`) and metadata fidelity (`lake exe truthmines -validate`, suite `truthmines-validate`) are the other two rungs over -the same artifacts. +`truthmines-check`) is the other rung over the same artifacts. ## Not yet covered diff --git a/docs/certified-compiler-progress.md b/docs/certified-compiler-progress.md index f572c33b0..2168a6452 100644 --- a/docs/certified-compiler-progress.md +++ b/docs/certified-compiler-progress.md @@ -1,10 +1,8 @@ # Certified compiler architecture and performance summary The target is complete certified Lean-to-Ixon compilation of Init+Std in less -than 10 seconds and Mathlib in less than 60 seconds of wall-clock time. Both -targets are achieved by the integrated certified compilation pipeline while -preserving bit-identical canonical Ixon artifacts and full certificate -verification (`Ix.Cert.CompileCorrect`). +than 10 seconds and Mathlib in less than 60 seconds of wall-clock time. +Neither target is met; see "Workload performance" for what has been measured. ## Measurement contract @@ -16,37 +14,49 @@ role finalization, output writing and cleanup are retained. No workload Ixon output cache or partial-output option is used. Timed runs are exclusive. The current Init+Std workload has 117,694 input declarations and zero compiler -failures. Its complete 253,772,604-byte artifact has SHA256 -`e48adef624e70bcdd3f22e1d96251682ac046a088700ccf5cf512f1b49afcda1`. +failures. Its complete 253,772,604-byte artifact has Blake3 digest +`a24fd146fcf3c5ed6e6a4821f5f122a6d397270f7eb3106bd7b75ff23eeca9a3`. ## Integrated pipeline components | Subsystem | Implementation summary | | --- | --- | | `Ix.Compile.ReadyCompile` | Dependency-driven ready-queue scheduler with an `O(|E|)` reverse-dependency graph, lock-free atomic work-stealing waves, and a subtype equality bridge (`{ result : ReportResult // result = Ix.Cert.compileReport env }`) discharging `Ix.Cert.checkReport_sound` without re-running sequential `compileEnv`. | -| `Ix.Sharing.Exact` & `Ix.Sharing.FamilyParallel` | Canonical sharing analysis with open-addressed `ExprCache` (`UInt32` `NodeId` DAG), `UInt64` FNV-1a structural key tables, `ReIndex` byte-LUT leaf counting, deferred candidate width materialization (`Deferred.lean`), and parallel intra-block sharing (`decideSharingFormattedAuto`) for large declarations and inductive families. | -| `Ix.Cert.Compile` & `Ix.RoleBinding.Compile` | Fused single-pass `recordGraphPar` / `referenceCyclePar` condensation check, incremental post-finalization check fast-path (`FinalizedCheckTasks.spawn` with `added?`), direct single-pass `putEnvDirect` serialization, and asynchronous background arena teardown (`dropAsync`). | +| `Ix.Sharing.Exact` & `Ix.Sharing.FamilyParallel` | Canonical sharing analysis with a pointer-keyed `ExprCache` that checks exact structural equality before a hit (`UInt32` `NodeId` DAG), width-independent tables computed once per constant with the three widths run as parallel tasks on DAGs of at least 1,024 terms (`Exact.TieredFast`, `tieredCandidates_eq`), and parallel exact family tables (`Family.Parallel.exactTable`). | +| `Ix.Cert.Compile` & `Ix.RoleBinding.Compile` | Fused single-pass `recordGraphPar` / `referenceCyclePar` condensation check, incremental post-finalization check fast-path (`FinalizedCheckTasks.spawn` with `added?`), and asynchronous background teardown (`dropAsync`). | Trace source generation is deferred until the code-generation command requests it rather than running during unrelated CLI initialization. The integration -also includes implicit `Char` validation, producer-generated native/trace -outputs, and documentation of the certified, streamed, and Rust boundaries. - -## Verified workload performance - -On the 32-core / 64-thread benchmark host (`perfspec`), end-to-end certified -compilation (`ix compile --certified`) achieves: - -- **Init+Std** (117,694 declarations, 253,772,604-byte `.ixon`): **4.65 s** - total wall-clock time (target `< 10 s`), bit-identical to the sequential - reference artifact (`e48adef624e70bcdd3f22e1d96251682ac046a088700ccf5cf512f1b49afcda1`). -- **Mathlib** (783,115 declarations, 2,349,530,307-byte `.ixon`): **44.6 s** - total wall-clock time (target `< 60 s`), bit-identical to the reference - artifact (`458b779888c8f9627bba58d41a3a2cdf48c38eec32d0a4f36606856782c9374a`). +also includes implicit `Char` validation and producer-generated native/trace +outputs. The certified compiler is the only compiler +([retired Rust](retired-rust.md)). + +## Workload performance + +**Current (2026-11-05).** On a 64-core test host under load (load average +35 to 49), `ix compile-lean Benchmarks/Compile/CompileInitStd.lean` compiled +Init+Std to the artifact above in 37.4 s of total wall-clock time (target +`< 10 s`): 29.7 s in `compileReport` and 1.9 s in finalization. There is no +current Mathlib measurement. + +**Last recorded Mathlib run (2026-10-29).** A run on an earlier compiler +completed all 783,115 inputs with zero failures: 630.020 s for the full process +(369.915 s main report), 77,311,912 KiB peak RSS, and a 2,349,530,307-byte +output with SHA-256 +`458b779888c8f9627bba58d41a3a2cdf48c38eec32d0a4f36606856782c9374a`. The same +checkpoint recorded Init+Std process wall times of 68.62 to 83.18 s. + +**Withdrawn figures.** From 2026-10-31 (`d3e3a6f41`) to 2026-11-05 this page +claimed 4.65 s for Init+Std and 44.6 s for Mathlib with `ix compile +--certified`. `ix compile` never had a `--certified` flag: the certified +compiler was already its default. No measurement in the repository's history +supports either figure, which replaced the 2026-10-29 checkpoint (`d511618e0`) +that the previous paragraph quotes. Both figures are withdrawn. ## Verification gates -All runtime and logical axiom audits (`lake run check-kernel --with-model`), -source hygiene checks (`Scripts/SourceHygiene.lean`), strict compiler and -linter gates (`lake build --wfail`, `lake lint -- --wfail`), and the Lean and -Rust test suites pass with zero `sorry`s and zero new axioms. +The gates for this pipeline are the runtime and logical axiom audits +(`lake run check-kernel --with-model`), the source hygiene checks +(`Scripts/SourceHygiene.lean`), the strict compiler and linter gates +(`lake build --wfail`, `lake lint`), the Lean test suites and the Rust crate +tests, with no `sorry` and no new axioms. diff --git a/docs/ci.md b/docs/ci.md index 85d547a6b..3a7a3bdfa 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -44,23 +44,20 @@ the entry runs: | --- | --- | --- | | `primary` | `lake test` with no arguments: the `lean-test` job of CI Jobs, and Nix CI's `ix-tests` | the quick suites | | `ci:cli` | a step of the `lean-test` job | the command-line integration tests | -| `ci:compile-pipeline` | a merge-tests partition | the Lean and Rust compilers against each other (input conversion, the reference graph and its condensation, serialization, decompilation, Lean's auxiliaries on closure-only environments) and the compilers' refusals | -| `ci:compiler-closures` | a merge-tests partition | selected closures against the whole environment, checker-support grounding, packing, admission of selected targets, and the certified checker's pinned subjects and acceleration inputs | -| `ci:compile`, `ci:decompile` | one merge-tests partition each | the whole test environment through the compiler, and back | -| `ci:misc` | a merge-tests partition, with the ZK voting prover | the Rust kernel on the test environment and the tutorial, sharding, catalog and commit I/O, the primitives, and the IxVM suite | -| `ci:landing-compilers` | a merge-tests partition | the Lean compiler's output independent of its schedule, on the fixture corpus; the Rust compiler's compile, serialize and decompile roundtrip over the test environment | -| `ci:landing-initstd` | a merge-tests partition | end-to-end checks on Init+Std, in process: the certified compile with its report checks against the pipeline and the Rust compiler, role binding and evidence, a reading premise on every inductive, and the admission hash | -| `ci:landing-initstd-cli` | a merge-tests partition | Init+Std through the `ix` binary: `ix compile-lean` with its report checks and the Rust comparison, whose artifact must match the reference digest in `Tests/Fixtures/initstd-reference.txt` and which the Rust kernel must check with every role of the default table admitted; and `ix validate-lean` fidelity | +| `ci:compiler-closures` | a merge-tests partition | selected closures against the whole environment, auxiliary closure addresses, checker-support grounding, packing, the certified checker's pinned subjects and acceleration inputs, and the binding proof through the `ix` binary | +| `ci:misc` | a merge-tests partition, with the ZK voting prover | the tutorial, sharding, catalog and commit I/O, and the IxVM suite | +| `ci:landing-initstd-cli` | a merge-tests partition | Init+Std through the `ix` binary: `ix compile-lean` with its report checks, whose artifact must match the reference digest in `Tests/Fixtures/initstd-reference.txt` and which `ix check-lean` must check with every role of the default table admitted; the Rust, pure and admission BLAKE3 on every record, blob and binding of that artifact; and `ix validate-lean` fidelity | | `manual` | by hand only | entries that need inputs outside the repository, take minutes or hours, or are diagnostics; each states its reason | The manual runners that prove through the production `ix_aggr` circuit, `aggregate-proof` and `aggregate-binding-reuse`, take minutes of recursion and about 190 GB, three quarters of a 32-CPU merge-tests runner's memory, so they -stay manual. `binding-proof-cli` (`ci:cli`) runs +stay manual. `binding-proof-cli` (`ci:compiler-closures`) runs `aggregate-binding-reuse`'s flow without its two aggregates: the binding proof, the shard proofs that assume it, their composition with it, and the refusals without it; the aggregation circuit's handling of the binding is tested in -`ix-aggr` (primary). +`ix-aggr` (primary). Its proving budget is 64 GiB, so it runs in the merge +queue, on a 32-CPU runner, rather than in every pull request's `lean-test`. `aggregate-toolchains` is also manual: it proves the committed library fixtures from Lean 4.29.0, 4.33.1, 4.34.1 and 4.35.0 with one IxVM key, discharges each @@ -74,8 +71,8 @@ home directory. Its registration and manual-run reason live in `Tests/Main.lean`; its full flow is in [`Tests/AggrToolchains.lean`](../Tests/AggrToolchains.lean). -`lake test -- --tier ` runs one tier; `--tier landing` runs the three -landing partitions. The registry's primary self-test, `ixtests-registry`, +`lake test -- --tier ` runs one tier; `--tier landing` runs the landing +partition. The registry's primary self-test, `ixtests-registry`, fails when a CI tier has no entry, when no workflow runs a CI tier (comments do not count), or when a workflow names a tier the registry does not define. It also fails when an entry of `required` in `Tests/Main.lean` is missing, @@ -86,6 +83,17 @@ textual: a step disabled by an `if:` still counts. A merge-tests partition must finish well within the merge queue's status-check timeout: split a tier into two jobs before it approaches it. +The primary tier runs in two places per event: incrementally in `lean-test`, +and in Nix CI's sandbox from the Nix-built test binary on a fresh copy of the +tracked sources (the flake's one check, `ix-tests`; the Rust lints and tests +are `rust-test`'s). Nix CI's development shell only builds the library. In the +merge queue, the `build` job of Merge tests builds the partitions' targets once +on their sticky lineage, and the partitions start from its snapshot; each +partition's log says whether it did. Valgrind runs `ffi-tests`, the `ffi` +suite as an executable of its own, whose import closure leaves out the test +binary's proofs. The certification statements' freeze, `Ix.Cert.Audit`, is +built by `check-kernel` and `lake lint`, not by the test binary. + ## Required checks The branch protection or ruleset for `main` must require these aggregate @@ -104,11 +112,12 @@ Do not require the gates' `interrupted` or `Detect Spot interruption` checks. Independent GitHub-hosted or security requirements can remain, provided they also report the checks the merge queue expects. -CI Jobs and Nix CI run on both `pull_request` and `merge_group`. The ignored -tests and the certified kernel gate (`lake run check-kernel --with-model`, a -merge-tests partition) run against the merge group's synthetic commit, or on -an authorized `!merge-tests` comment. The pull-request-only stub publishes -`merge-gate / pass` so a PR can enter the queue before the ignored suites run. +CI Jobs and Nix CI run on both `pull_request` and `merge_group`. The +merge-queue test tiers and the certified kernel gate (`lake run check-kernel +--with-model`, a merge-tests partition) run against the merge group's synthetic +commit, or on an authorized `!merge-tests` comment. The pull-request-only stub +publishes `merge-gate / pass` so a PR can enter the queue before the +merge-queue suites run. It never runs on `merge_group`, and merge-tests.yml never runs on `pull_request`, so each commit shows one `merge-gate / pass` check under the shared workflow name. @@ -152,3 +161,40 @@ artifact under the same name so a recovered job cannot fail on a duplicate upload. See [RunsOn's merge queue guidance](https://runs-on.com/docs/costs/spot-pricing/#merge-queues). + +## Retired suites + +Two groups of suites were retired: + +- those that tested the deleted Rust compiler, Rust kernel and ShardMap; +- those whose property the certified compiler and kernel now prove. + +The certification proves soundness, that what is accepted is correct. It does not prove completeness, that every valid input is accepted. Completeness, and behaviour at scale, are exercised by the real-corpus runs in the merge queue: + +- `initstd-reference` compiles Init+Std, pins its bytes, and checks every constant with `ix check-lean`. +- `fidelity-initstd` reads back and decompiles every record of Init+Std. + +Each entry below says what replaced the suite, or why it was dropped. + +**Dropped, because the other side is deleted:** + +- **The Rust differentials:** `rust-compile`, `rust-decompile`, `rust-serialize`, the `*-cross` and `*-canon-roundtrip` checks, `kernel-check-const`, `kernel-check-env`, `kernel-ixon-roundtrip`, `exact-sharing-ffi`, `shard-map`, and `check-kernel`'s codec differential (`kernel-codec`). + +**Dropped, because a proof or a corpus run covers them:** + +- **`compile-schedule-identity`** (landing). It compared three things: the wave fold at several widths, a test-only live-state reference schedule, and the streamed pipeline at 1, 4 and 16 workers. None of these comparisons is needed now: + - The IO pipeline computes the fold by type: `reportIO` returns `{r // r = Ix.Cert.compileReport env}` (`Ix/Compile/ReadyCompile.lean`). + - The fold's value doesn't depend on its width (`Ix.Cert.Schedule.compileEnvFold_width`). + - No other path ran the reference schedule. It isn't proved that the wave-snapshot fold equals a fold that compiles one block at a time on live state (`Ix/Cert/Schedule.lean`), but nothing compiles that way. +- **`cert-compile-parity-initstd`** (landing). It compared `Ix.Cert.compile` with the IO pipeline and with the Rust compiler on Init+Std. The first equality is `reportIO`'s type, and the Rust compiler is deleted. `initstd-reference` pins the bytes of the Init+Std artifact. +- **The Batteries leg of `compile-determinism`.** `initstd-reference`'s pinned digest is the stronger check across processes at scale. +- **`check-kernel`'s level comparison.** `leqCore_sound` proves soundness, and the corpus runs exercise completeness. +- **`check-kernel`'s quotient Source on Init and Std.** The nominal-to-quotient Source bridge is proved (`Ix.LeanIngress.NominalQuotient`, `Ix.Cert.Quotient`), and `initstd-reference`'s compile runs the same ingress on Init+Std. +- **`check-kernel`'s Init+Std reader fidelity** (the first 20,000 records). `fidelity-initstd`'s phases 3 and 4 compare every record of Init+Std and require full source coverage. +- **`compile-claim-conflict`.** The compile is certified (`CompileCorrect`). The corpus compiles exercise its completeness. +- **`decompile` and `decompile-diff`.** `fidelity-initstd`'s phase 5 is the decompile round trip on Init+Std. +- **`kernel-read-cache`.** The read cache only serves `kernel-check-ixe`'s diagnostics. The user-axiom tests run its path. + +**Replaced:** + +- **`record-decoding`.** Its refusals moved into `kernel-record-check` (primary): orphan, noncanonical and oversized records, phase precedence, and a cache beside bad bytes. `kernel-check-ixe` passes host-decoded records straight to the certified step, so these refusals aren't covered by a proof. The parallel decoder's agreement with the serial one is proved (`Ix.RoleBinding.RecordDecoding.parallel_eq`, `recordStore_eq_parallel`). diff --git a/docs/ix_canonicity.md b/docs/ix_canonicity.md index 19490c673..d418e87fb 100644 --- a/docs/ix_canonicity.md +++ b/docs/ix_canonicity.md @@ -9,8 +9,7 @@ Whole constants can share an address when their anonymous encodings agree. This page describes those representation rules. The compiler theorem and its premises are in [kernel.md](kernel.md#relation-to-compiler-certification); the byte grammar is in [Ixon.md](Ixon.md). The implementation entry points are -[CompileM](../Ix/CompileM.lean), [CompileDriver](../Ix/CompileDriver.lean) and -the [Rust compiler](../crates/compile/src/compile.rs). +[CompileM](../Ix/CompileM.lean) and [CompileDriver](../Ix/CompileDriver.lean). ## What determines an address @@ -46,8 +45,8 @@ blocks. ## Block contents (canonicity §6.0) The reference graph and its condensation determine dependency scheduling. -[`sourceMembers`](../Ix/CompileM.lean) and Rust `source_members` order the -members of each block from Lean's metadata: +[`sourceMembers`](../Ix/CompileM.lean) orders the members of each block from +Lean's metadata: - An inductive uses its position in Lean's `all` array. - A definition-like member uses its position in its Lean `all` array. @@ -59,8 +58,7 @@ otherwise exact tie. Hash-map iteration is not the member-order rule. Constructors retain the order in the source inductive's constructor array. [`Ix.Cert.LeanOrder`](../Ix/Cert/LeanOrder.lean) proves the ordering facts. -The compiler represents these members as a flat `List MutConst` in Lean and -a flat vector of member references in Rust. Each member contributes one +The compiler represents these members as a flat `List MutConst`. Each member contributes one payload and its metadata. There is no representative/equivalent-member fold. | Compiled content | Record layout | @@ -120,24 +118,18 @@ separate representation operations and remain part of compilation. 5. Add the role-support closure, certificates and binding with `finalize`, then check the finalized artifact before writing it. -`--streamed` selects the checked streamed Lean comparison path. `ix compile ---rust` selects the uncertified Rust compiler; `ix compile-lean --rust-check` -compares its serialized result with the selected Lean path. Rust/Lean byte -parity is differential evidence, not a universal refinement theorem. - Name-only `prove`, `check` and `addr-of` use -`Ix.Check.loadNamedIxonEnv`, whose Lean path is the checked streamed loader -`compileLeanConstsValidated`. Supplying a `.ixe` uses that artifact instead. -These commands do not acquire direct `compileReport` coverage merely by -using the Lean loader. The precise boundary is described in +`Ix.Check.loadNamedIxonEnv`, which also runs `Ix.CompileM.compileCertified` +(`Ix.Cert.compileReport` via `Ix.Compile.ReadyCompile.reportIO`). Supplying a +`.ixe` uses that artifact instead. The uncertified streamed and Rust compiler +paths have been retired ([retired Rust](retired-rust.md)). The precise boundary is described in [the compiler theorem's scope](kernel.md#relation-to-compiler-certification). ## Canonical sharing (§6.7) Every compiled block is built with -`Ix.Sharing.Exact.canonicalSharingTiered .tagN`, mirrored by Rust's -`canonical_sharing_tiered`. Its roots are the payload expressions in the +`Ix.Sharing.Exact.canonicalSharingTiered .tagN`. Its roots are the payload expressions in the order specified by `constantInfoRootExprs`. Existing `Share` occurrences are expanded before the construction chooses its representation. @@ -156,7 +148,7 @@ On the stated totality domain, a sufficient budget prevents refusal and the output expands again. [`IxSharingVerify.SufficientBudget`](../IxSharingVerify/SufficientBudget.lean) provides a polynomial budget in the written input size for backward-referencing input tables. Larger budgets do not change an already successful result. -This is not a global byte-minimum theorem or a Rust-refinement theorem. +This is not a global byte-minimum theorem. The default compiler fixes its sharing limits. A limit failure refuses the block; it does not choose another sharing family or publish an intermediate @@ -176,8 +168,7 @@ anonymous reducibility hints, still has the separate invariants checked by The [Lean decompiler](../Ix/DecompileDriver.lean) selects stored source records through `originalView`, decodes them, and recomputes inductive flags from the -decoded declarations. The Rust decompiler follows the same source-record -approach. A missing source record is an error; decompilation does not create +decoded declarations. A missing source record is an error; decompilation does not create a replacement declaration by kernel regeneration. Tests state their fidelity criteria explicitly. There is no unrestricted compile/decompile bijection or total byte-fixed-point theorem for arbitrary metadata and artifacts. @@ -204,8 +195,7 @@ especially important for alias-specific names and universe spellings. This is the universe contract cited as **canonicity §10.6** in compiler, codec, ingress and decompiler comments. -[`Ixon.canonUniv`](../Ix/IxonUniv.lean), mirrored in -[`crates/ixon/src/canon_univ.rs`](../crates/ixon/src/canon_univ.rs), is the +[`Ixon.canonUniv`](../Ix/IxonUniv.lean) is the deterministic composition `linearize ∘ subsumption ∘ normalizeAux` on positional Ixon universes. It normalizes the spelling inside a universe expression. It does not reorder a declaration's universe-parameter list or @@ -230,7 +220,7 @@ semantic-quotient proof. ### Primary universe tables -Before compiling a block's members, both compilers collect its universe +Before compiling a block's members, the compiler collects its universe expressions, apply `canonUniv`, sort by serialized key and deduplicate. The expression compiler lowers and normalizes each later occurrence through the same operation and interns its result in that primary table. @@ -263,18 +253,14 @@ references are consumed by the decompiler and metadata reconstruction path. Anonymous admission and checking do not read them, so a patch cannot change the anonymous declaration being checked. -Metadata-mode Rust ingress carries the spelling as an expression decoration -and egress replays it. The decoration participates in metadata expression -identity, rather than the semantic universe-node identity; otherwise an -interned shared universe could overwrite another occurrence's spelling. Patchless readers retain the documented reconstruction fallback. Fidelity tests compare the reconstructed syntax under their stated policies. ### Universe regression controls (§17.9) -[`Tests/Ix/IxonUniverse.lean`](../Tests/Ix/IxonUniverse.lean) and the Rust -universe tests cover explicit witnesses, bounded exhaustive inputs, seeded -inputs, normalization properties and mirror parity. The +[`Tests/Ix/IxonUniverse.lean`](../Tests/Ix/IxonUniverse.lean) covers explicit +witnesses, bounded exhaustive inputs, seeded inputs and normalization +properties. The [level-spelling fixtures](../Tests/Ix/Compile/LevelSpellings.lean) exercise occurrence restoration through compilation and reconstruction. The unconditional value theorem remains separate from those finite tests. @@ -286,9 +272,9 @@ quotient Source, under its artifact and role-evidence premises. It includes the installed environment's syntactic renaming relation and the value statement quantified over every strong model. It does not add model existence, total compilation, fixed-fuel checker success, metadata fidelity, general -permutation invariance or universal Rust parity to its conclusion. +permutation invariance to its conclusion. -The compiler tests cover same-input Lean/Rust parity, schedule independence, +The compiler tests cover schedule independence, source-order mutual blocks, refusal behavior and reconstruction cases. [CI tiers](ci.md#test-tiers) identify the suites; exact corpus digests, timings and final integration receipts belong with the corresponding test diff --git a/docs/kernel.md b/docs/kernel.md index a807926c2..a900b856a 100644 --- a/docs/kernel.md +++ b/docs/kernel.md @@ -21,8 +21,8 @@ declarations are the ones the bytes encode, bounded resources), under which assu with which outcomes (accept, reject, decline; only an accept carries the theorems). -This entry does not by itself certify the Lean-to-Ixon compiler, the Rust -checker or IxVM. An accepted environment has a model; that it is the +This entry does not by itself certify the Lean-to-Ixon compiler or IxVM. +An accepted environment has a model; that it is the environment the original Lean source meant is outside this entry's claim; for the Lean compiler's output, the compiler theorem (next section) relates the two. @@ -92,30 +92,24 @@ report check that reads the artifact alone) before it is written. The certified compiler runs at the default sharing limits, which `compileReport` fixes: a sharing-limit override is refused there. -`ix compile --streamed` and `ix compile-lean --streamed` select the streamed -Lean pipeline (`compileLeanInputValidated` in -[`Ix/CompileResource.lean`](../Ix/CompileResource.lean)), which runs the same -report checks on the compile. Name-only `ix prove`, `ix check` and -`ix addr-of` use `Ix.Check.loadNamedIxonEnv` and its checked streamed loader, -`compileLeanConstsValidated`. They do not call `compileReport` directly; -supplying a `.ixe` uses the supplied artifact instead. - -The Rust compiler is opt-in and uncertified: `ix compile --rust` (and `--rust` -on `prove`, `check` and `addr-of`). It has no counterpart of the report -checks. The Rust path of `ix compile` checks its finalized artifact before -writing it; this does not establish the compiler theorem for that backend. -Bare compiler APIs remain unvalidated. `ix compile-lean --rust-check` compares the two -serialized outputs for that input; byte parity is empirical evidence, not a -compiler-refinement theorem for the Rust compiler. Metadata is outside the +Name-only `ix prove`, `ix check` and `ix addr-of` use +`Ix.Check.loadNamedIxonEnv`, which also compiles the closure through the +certified compiler `Ix.CompileM.compileCertified` (`Ix.Cert.compileReport`); +supplying a `.ixe` uses the supplied artifact instead. The uncertified +streamed and Rust compiler paths have been retired +([retired Rust](retired-rust.md)). Metadata is outside the certification claim. What the theorem covers on these paths: `CompileCorrect` is about the pure -function `Ix.Cert.compileReport`, which `ix compile` and `ix compile-lean` run on -the canonicalized input. The streamed pipeline's canonicalization, reference -graph, ground filter and condensation are tested equal to the pure ones -(`cert-compile-parity-init`), not proved; its fold over the hybrid environment -is proved equal to the pure fold (`Ix/Cert/Hybrid.lean`). `finalize`'s additions -are outside the theorem. +function `Ix.Cert.compileReport`, which `ix compile`, `ix compile-lean` and +`Ix.Check.loadNamedIxonEnv` run on the canonicalized input +(`Ix.Compile.ReadyCompile.reportIO` proves by type that the IO worker-queue +driver returns `Ix.Cert.compileReport`, and `Ix.Cert.FreshSnapshot` proves +wave-snapshot freshness and disjointness across blocks in a wave, feeding the +direct semantic proof on the wave-snapshot fold; equivalence between the +wave-snapshot fold and a one-block-at-a-time live-state fold is not proved in +`Ix.Cert.Schedule`). +`finalize`'s additions are outside the theorem. ## The certified entry @@ -386,8 +380,8 @@ increasing ω-chain of Grothendieck universes interpreting the universe tower. The Mathlib instance above supplies it from ω inaccessible cardinals, which its corollaries take as a hypothesis, not as an axiom. -**Input policy.** The entry never falls back to the Rust checker, which is -not in its closure ("Trust surface"). The certified theory +**Input policy.** The entry has no fallback: what it declines stays declined, +and no checker outside its closure ("Trust surface") is consulted. The certified theory uses Lean's three standard axioms: `propext`, `Classical.choice` and `Quot.sound`. The compiler-trust axioms `Lean.trustCompiler`, `Lean.ofReduceBool` and `Lean.ofReduceNat` are non-standard axioms here and @@ -399,6 +393,35 @@ nothing; any use of it declines (`IxC/Kernel/Core.lean`). The well-founded `Nat` operations are admitted through the role table's evidence, which the checker checks itself ("Roles and the role table" below). +**User axioms (uncertified).** Every other safe axiom, the three compiler-trust +axioms among them, declines at the certified entry and in every host driver +by default. A real Lean library therefore does not check whole: strict checking +of Init+Std declines `Lean.trustCompiler` and blocks the four native-evaluation +constants that depend on it, the other two axioms among them +(`Tests/Fixtures/initstd-reference.txt`). The host drivers can opt in with +`--allow-user-axioms` (`ix check-lean`, `kernel-check-ixe`): the declaration +checks then install a safe axiom whose key is an input constant's +(`Key.isCustomAxiomEligible`) as an axiom, and a whole `check-lean` run runs, +once, the entry under the `allowUser` axiom policy +(`Admission.checkBytesUnder .allowUser`, `checkBytesTieredUsingUnder .allowUser`, +through `Ix.RoleBinding.Artifact.checkConditional` and +`checkTieredConditional`). It returns the environment with the user axioms +among the declarations it ran over. When that list is empty the accept is the +certified entry's (T1, `Admission.checkBytesUnder_strict_of_nil`; the host +turns the receipt into the certified one with `ReceiptConditional.toReceipt`), +so the run issues the host statement as a strict run does. Otherwise no +soundness theorem covers the accept and there is no host statement. A run that admits a user +axiom prints `CONDITIONAL on K user axioms:` and lists them (the addresses the +declarations key them by, with their names in meta mode), and `check-lean` +exits with 5 rather than 0 ("`ix check-lean`" below). The binding must pass the +kernel's role-table check for user axioms (`Admission.admitAxiomPolicy +.allowUser`: `sorryAx`, `propext` and `Classical.choice` bound): a binding that +leaves one out keys Lean's own constant as an input constant, which the checks +would admit as a user axiom. The entries run that check themselves; the +record-by-record drivers never reach admission, so the host runs it first +(`Ix.RoleBinding.UserAxioms.requireAxiomRoles`), and such an artifact, like +one with no binding, is refused before any checking. + ## The typing tier The logical fold gives what it accepts a model, so it declines every @@ -536,7 +559,8 @@ Lean, through δ, β and instantiation: implicit arguments hidden, no notation, full names); safe code cannot read a `Squash`, so it takes the read as a parameter, and the one display-only `unsafe` read is `Ix/KernelDisplay/Peek.lean` (trust-surface allowlisted; - no certified root reaches it, `Tests/Ix/Kernel/DisplayRead.lean`). + no certified root reaches it, `IxC/Kernel/Audit/Roots.lean` and + `Ix/Cert/RuntimeAudit.lean`). Known limits: `.meta` keys are addresses, so the members of an alias fiber (α-equivalent constants, such as `HAdd.hAdd` and `HMul.hMul`) print as its @@ -624,9 +648,7 @@ names. A mismatch declines with `role specification mismatch`. The compiler also enforces `RoleRecordKinds` in its report checks. The equation checker used by pin generation validates actual definitions and checked evidence; it adds no native rule to IxC. IxVM validates the role specifications for -its native operations. Rust constructs the same terms from generated data -and validates their carried support before admission; its optional String -and BitVec reductions are restricted to admitted heads and specified inputs. +its native operations. **The role table** (`IxC/Kernel/Ixon/RoleTable.lean`) is input: `RoleTable` has the version of the specification set it was made for, the @@ -932,9 +954,8 @@ re-records it states why the closure or the statement moved. fixtures re-keyed to their BLAKE3 addresses; the certified fixtures (`IxC/Fixtures/*`, authenticated under a hasher-free fixture hash) were already built in step 1; -3. `kernel-codec` (production codec against Rust); -4. with `--with-model`, the `Models/SetTheory` build and its audit; -5. `kernel-layering` (`Tests/Ix/Kernel/Layering.lean`, derived from +3. with `--with-model`, the `Models/SetTheory` build and its audit; +4. `kernel-layering` (`Tests/Ix/Kernel/Layering.lean`, derived from con-leche's `tests/layering.sh`): the kernel's import layering, each file of `IxC/Kernel/` classified by its path (`Tests/Ix/Kernel/KernelLayout.lean`; an unclassified file fails): the checker never imports the theory, the @@ -945,7 +966,7 @@ re-records it states why the closure or the statement moved. `BinderMeta` carries (Ix's boundary modules `Admission`, `Ixon`, `Audit`, `Ingress`, `Egress`, `Ref` and `Search` import the kernel, not the reverse); -6. `kernel-trust-surface` (`Tests/Ix/Kernel/TrustSurface.lean`, derived from +5. `kernel-trust-surface` (`Tests/Ix/Kernel/TrustSurface.lean`, derived from con-leche's `tests/trust-surface.sh`, with its lexer self-test on `Tests/Fixtures/trust-surface/lexer.lean`): a lexer-based scan of the checker, the theory, the admission hash (`Ix/Address/Admission.lean`) and @@ -956,10 +977,7 @@ re-records it states why the closure or the statement moved. BasisGen}.lean`, `Ix/Address/Admission.lean`, and the display-only read `Ix/KernelDisplay/Peek.lean`) are permitted, each with its justification in the tool's header; -7. `kernel-level-comparison`: the level comparison (`Level.leq`, - `Level.isEquiv` and its Géran fallback) against brute-force evaluation, - on random levels and on Ixon's canonical forms; -8. `kernel-entry-cases`: Lean declarations of +6. `kernel-entry-cases`: Lean declarations of `Tests/Ix/Kernel/EntryCaseDefs.lean` compiled by Ix's compiler and submitted as canonical bytes to `Ixon.Authenticated.checkBytes`, each with an exact expected verdict (accepts: a definition, a theorem, an inductive @@ -974,21 +992,10 @@ re-records it states why the closure or the statement moved. declines: a `partial` definition's `_unsafe_rec` body, a non-standard axiom, a theorem of `False`). Rows go to `.lake/build/kernel-entry-cases.jsonl`; -9. `kernel-reader-fidelity --fixture` and `--check-kernel`: the Ixon reader - against a direct translation of the Lean constants it was compiled from, - and the projection output against the compiler's records, on the fixture - closure (the check the `lake test` suite `kernel-reader-roundtrip` runs) - and on the first records of Init and Std; the log goes to - `.lake/build/kernel-reader-fidelity.log`; -10. `kernel-meta-names`: the `.meta` and `.source` names against Lean's, on - ill-typed declarations and the fixtures; -11. `kernel-source-ingress --collapse --bundle-evidence --compare-prepared` - on Init and Std: quotient Source, as `checkSourceCollapsedBundledAt` runs - it, on a freshly compiled artifact; it fails when a collapse check fails - or the harness's prepared stream differs from production's. - -The CI job runs the same gate and keeps the codec, order, entry-case and -reader-fidelity logs. +7. `kernel-meta-names`: the `.meta` and `.source` names against Lean's, on + ill-typed declarations and the fixtures. + +The CI job runs the same gate and keeps the entry-case log. ### On a toolchain bump @@ -1061,9 +1068,8 @@ interleaved: writes `claims.tsv`, `addressed.tsv`, `resource.tsv` and the handoff set to `$IX_IXON_V4_DIR`; copy them into `Tests/Fixtures/ixon-v4/` after `lake exe ixon-v4-tests` validates them. If they moved, so did the - catalog claim digest (`Tests/Ix/Claim.lean`, `crates/ixon/src/proof.rs`) - and the environment-bytes hash (`crates/compile/src/graph.rs`); re-pin - them from the serializers' output. The independent expression vectors + catalog claim digest (`Tests/Ix/Claim.lean`, `crates/ixon/src/proof.rs`); + re-pin it from the serializers' output. The independent expression vectors (`Tests/Fixtures/ixon-v4/expressions.txt`) are written by hand from the specification. 5. **The default role table**, as step 2 of a toolchain bump: the three commands under @@ -1076,8 +1082,8 @@ interleaved: (`lTreeRecords`, `levelRecords`) with the names of the blocks they belong to; replace the lists and the owner addresses with the output. The byte vectors of `IxC/Fixtures/{Codec,ParserWork,ByteAdmission}.lean` - are the codec's: rewrite them for the new grammar (`kernel-codec` - compares the Lean codec with Rust's on them). + are the codec's: rewrite them for the new grammar (their `#guard`s run + when `lake -d IxC build --wfail` elaborates them). 7. **IxVM costs.** `lake test --wfail -- --ignored ixvm` reports every kernel-check FFT-cost pin of `Tests/Ix/IxVM.lean` and the shard pin in `Tests/Main.lean` that moved; set each to the measured value. @@ -1197,8 +1203,8 @@ CHECK_IXE_WATCH_MB=12000 .lake/build/bin/kernel-check-ixe --guarded --memory-max .lake/build/bin/kernel-check-ixe --report .lake/envs/initstd.jsonl ``` -Usage: `kernel-check-ixe [--load stream|eager] [--jobs ] - [limit]`. The records are streamed by default +Usage: `kernel-check-ixe [--load stream|eager] [--jobs ] [--allow-user-axioms] + [limit]`. The records are streamed by default (`Ix/KernelCheck/Stream.lean`): the order and the record views are built over record skeletons, and each record is decoded in full at its turn and dropped once it is read. `--load eager` decodes and keeps the whole @@ -1206,7 +1212,12 @@ environment up front. `--jobs ` installs every record first and then runs the recorded checks on `n` worker threads (`Ix/KernelCheck/Pool.lean`). All three write the same rows (`kernel-check-ixe --compare` shows it); with `--jobs`, a row's `micros` is -its install plus its checks. A checker failure's reason is rendered by +its install plus its checks. The check is strict: a safe axiom other than the +standard three declines. `--allow-user-axioms` admits such axioms on every +path, the read cache's included, after refusing (exit code 2) an artifact +whose binding does not bind `sorryAx`, `propext` and `Classical.choice`, and +ends its log with `CONDITIONAL on K user axioms:` and the axioms it admitted +("User axioms (uncertified)" above). A checker failure's reason is rendered by `.meta`: its keys spelled with the names the `.ixe`'s metadata gives them and the first eight hexadecimal digits of their addresses, `Nat.add@a1b2c3d4` (`Reader.spellWith`); the verdict is the `.anon` run's. @@ -1248,3 +1259,54 @@ Run one environment check at a time, under a memory cap, with no concurrent buil Mathlib's `.ixe` comes from `Benchmarks/Compile/CompileMathlib.lean` (`Benchmarks/Compile/README.md`). +### `ix check-lean` + +`ix check-lean ` (`Ix/Cli/CheckLeanCmd.lean`) is the host checker +behind the merge-queue gate `initstd-reference` and the `ooc` benchmark. It +authenticates the bytes and checks every declaration, or a selection +(`--consts`, `--consts-file`, `--max`), with the same incremental step as the +environment check. When a whole run of a bound artifact passes, it runs the +certified entry (`Ix.HostAdmission.certify`) and reports the host statement, +which `--host-out` and `--host-store` persist (`docs/host-admission.md`). A +whole bound run prints `bound roles: N admitted, M refused`: a role is refused +when the run refused its record. In meta mode, the default, an artifact whose +published role names disagree with its binding is refused before any check +(`Ix.RoleBinding.Compile.roleBindingError`, as the compiler's `finalize` +runs it). + +Checking is strict. With `--allow-user-axioms` a whole run runs the entry under +the `allowUser` policy once ("User axioms (uncertified)" above), and the user +axioms it lists choose the verdict. With none, its receipt is the certified +entry's and the run reports and issues the host statement as without the flag. +With some, the run prints + +```text +[check-lean] whole artifact: CONDITIONAL on K user axioms: +[check-lean]
+``` + +one line per axiom, the name in meta mode only, and never the `accepted all` +line of a certified run. A selected run whose declaration checks read a user +axiom prints the same list under `selected checking:`. + +| Exit | Meaning | +| --- | --- | +| 0 | everything asked for passed; for a whole bound run, through the certified entry | +| 1 | an infrastructure failure | +| 2 | a usage error: a bad flag or selector, a selected name that resolves to no declaration, or a host statement the run cannot issue (a selected, capped, unbound or conditional run) | +| 3 | a declaration failed, or the artifact was refused | +| 4 | partial: every declaration passed, but the whole artifact asked for carries no role binding, so its admission did not run | +| 5 | conditional: everything passed, but only with the listed user axioms admitted | + +`--json ` writes benchmark results rows (`Ix.Benchmark.Results`). A run +writes one row, keyed by `--json-name` (default `env`). With a selection it +runs each name's full closure on its own and writes one row per name, keyed +`/` when `--json-name` is given; a name that resolves to no +declaration is reported, and the other names still run. In every row, +`constants` is the number of records the run checked or read, and `throughput` +is that number per second; `status` names the run's verdict as its exit does: +`ok` (0), `rejected` (3), `partial` (4) or `conditional` (5). A host statement +the run cannot issue (exit 2) leaves `status` at the verdict. +`--per-const ` writes one CSV row per record for the same run or runs. A +run exits with the same status with `--json` as without. + diff --git a/docs/kernel_identity.md b/docs/kernel_identity.md index f0013d696..93b7598dc 100644 --- a/docs/kernel_identity.md +++ b/docs/kernel_identity.md @@ -1,11 +1,12 @@ # 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. +This document records the boundary between them: why the checker's internal +term identity 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. The checker is the certified IxC kernel +([kernel](kernel.md)); its executed term operations are in +`IxC/Kernel/Expr.lean` and `IxC/Kernel/Cached/`. ## Layer 1: Ixon content addresses (external, cryptographic, in proofs) @@ -16,22 +17,20 @@ 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`. -- Kernel claim roots are built from `Address`es: - `subject_root` / `assumptions_root` use `merkle_root_canonical` over - the certified / assumed constant addresses - (`crates/kernel/src/claim.rs`). -- Aggregation (`Claim::CheckEnv`, the `Contains` discharge) resolves +- The checker reads every record and blob under its Ixon `Address`. +- Claim roots are built from `Address`es: Merkle roots + (`Ix.Merkle.merkleRootCanonical`) over the certified and the assumed + constant addresses. +- Aggregation (`CheckEnv` claims, 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. +- Address↔bytes binding is checked before anything is decoded: the + certified entry authenticates every record and every blob as the hash of + the bytes stored under it (`Admission.authenticate`; [kernel, "The + certified entry"](kernel.md#the-certified-entry)). Host tools that load an + `.ixe` lazily check the same binding at first materialization + (`ixon::lazy::LazyConstant::get_at`). **Merkle inclusion proofs for individual constants live entirely in this layer** and are unaffected by anything below: proving "address `A` is in @@ -48,145 +47,97 @@ claim roots ──Merkle──▶ constant Addresses ### Definition dependency admission -`IxonChecker` consumes an already decoded `ixon::Env`. The path remains -`.ixe → Ixon decoding → kernel ingress → typechecking`. Each worker owns -a private `KEnv`, populated exclusively by anonymous Ixon ingress; callers -cannot insert raw declarations into it. - -Ingress verifies each constant's binding to its map key with -`LazyConstant::get_at`, including all projections admitted with a mutual -block. External dependency addresses are pinned by the constant's bytes: +External dependency addresses are pinned by the constant's bytes: constructing a cycle between blocks would require solving a hash preimage. -Local `Rec` slots need an explicit check. Ingress collects those edges while -converting definition types and values, follows shared expressions normally, -and rejects cycles reachable from safe definitions before publishing any -member of the block. Acyclic forward references remain valid. +Cycles inside a block go through local `Rec` slots and need an explicit +check. IxC's reader (`IxC/Kernel/Ixon/Reader.lean`) applies the safety +policy to every member of a definition block first, then orders the block +so that every member follows the members it names (`defOrder`). A safe +block with no such order is declined as a mutually recursive definition +block: like Lean's kernel, the checker has no safe mutual definitions. A +member that names itself is checked before it is added, where the +reference does not resolve. Consequently, checking one declaration need not traverse the expression trees of its entire external dependency closure. Those declarations remain assumptions until their own work items are checked, as in the proof claim -model above. Callers constructing a raw `KEnv` do not have this ingress -invariant; `TypeChecker` retains its defensive dependency traversal for them. +model above. ## 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 repeated hashing added work -for every constructed node. - -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). +in-memory expression nodes churned out by substitution, WHNF and def-eq: +for memo keys, cache keys and quick equality. These objects are ephemeral: +they are never serialized, never compared against Ixon addresses, and die +with the run. + +IxC has one expression type, `Ix.Kernel.Expr`, whose nodes carry derived +data as Lean `@[computed_field]`s: a hash, the loose bound- and +free-variable bounds and a level-parameter flag, packed into one word +(`Expr.data`; `IxC/Kernel/Cached/ExprNodes.lean`). There is no hash-consing +table. Terms are shared by Lean's own structure sharing: an operation +returns unchanged children by reference, so a result shares every unchanged +subterm with its input. + +- **Equality is decided, not assumed.** The executed equality follows the + official kernel: pointer equality first, then the computed hashes, then + structural descent (`IxC/Kernel/Expr.lean`). Every branch returns + `Decidable (a = b)`. Pointer equality is a fast path for equal terms and a + hash mismatch one for unequal terms; an equal hash decides nothing. +- **The equality memo is verified.** An entry is the pair of objects a + completed descent proved equal, with the proof. A probe finds a + candidate by the packed address pair (`beqKey`) and then checks the + candidate by identity, so a key collision between distinct pairs costs an + entry, never an answer. +- **Memo keys are terms.** The scope and definedness walks key their memos on + `Expr` itself (`Std.HashMap Expr α`: hashing off the cached field, + pointer-first equality); the substitution walks key theirs by address + (`IxC/Kernel/Cached/ExprOpsC.lean`). +- **Trust.** That the stored word agrees with the function defining it is + the code generator's guarantee, not a theorem of this repository: + `@[computed_field]` is implemented with `implemented_by`. It is the one + entry of the trust census in `IxC/Kernel/Cached/ExprNodes.lean`. + +Before IxC, this layer belonged to the retired Rust kernel: per-node blake3 +hashes at first, then intern-assigned `u64` uids from a process-global +counter, with a hash-consing intern table keyed by shallow structural keys +([retired Rust](retired-rust.md)). ## 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. +> `Address`. The checker's node hashes and object addresses must never +> escape into a serialized artifact. + +## Adversarial model + +The natural worry about non-cryptographic internal identity is collision: +*if two subterms had the same internal hash, a def-eq or WHNF cache hit +could return a result for the wrong term.* Here a collision costs time, +not soundness: + +- An equal hash never decides equality: the descent continues + structurally, so a collision makes a comparison slower, not wrong. +- Hash maps keyed by `Expr` compare keys with that decided equality, so a + bucket collision causes an extra comparison, not a wrong hit. +- The equality memo checks every candidate by identity before using it. +- Object addresses are not content: an input cannot choose them, and + nothing serializes them. + +An adversarial environment can choose term contents, and so can grind +contents to collide hashes and flood buckets. That slows the checker, +which is spending its own cycles; it does not change an answer. ## 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. +rather than inclusion of a constant in a claim), the checker's node hashes +are not it: they are not cryptographic, and no artifact carries them. The +right move is to compute that identity at the boundary: a one-time egress +pass over the relevant term, for example blake3 over the Ixon serialization +of the subterm, or a `(tag ‖ child hashes)` Merkle-DAG. 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/docs/resource-checking.md b/docs/resource-checking.md index 2685c384e..59fe65690 100644 --- a/docs/resource-checking.md +++ b/docs/resource-checking.md @@ -1,15 +1,15 @@ # Ixon resource checking (resource-v1) -The Lean and Rust resource validators consume the same canonical addressed -environment and an explicit profile. Both production compilers run this -validation, together with erased typechecking, before emitting annotated -constants. Resource claims bind the complete constant set and profile address. +The resource validator consumes a canonical addressed environment and an +explicit profile. The certified compiler runs this validation, together with +erased typechecking, on an input that carries semantic contracts, before and +after finalizing its artifact (`Ix/CompileCertified.lean`). Resource claims bind +the complete constant set and profile address. A Rust validator implemented the +same transitions until PR #677 retired it ([retired Rust](retired-rust.md)). The executable modules are [Basic](../Ix/Resource/Basic.lean), [Check](../Ix/Resource/Check.lean), and [Admit](../Ix/Resource/Admit.lean). -[Rust](../crates/ixon/src/resource.rs) implements the same transitions. -The production boundaries are [Validate](../Ix/Resource/Validate.lean) and -[the Rust kernel adapter](../crates/kernel/src/resource.rs). +The production boundary is [Validate](../Ix/Resource/Validate.lean). Lean's [typing half](../Ix/Resource/Typing.lean) uses IxC's certified tiered admission entry for the part it admits. Unsafe inductives, safe assumption @@ -152,9 +152,8 @@ required closure. Merely parsing a claim or proof wrapper validates no program. | Consumer | Advertised validation | | --- | --- | -| Lean and Rust resource admission | Resources plus erased typing, with a committed profile | +| Resource admission (`Ix.Resource.validate`) | Resources plus erased typing, with a committed profile | | IxC certified admission | Authenticated logical admission and the entry's stated partial/unsafe typing tier | -| Rust ordinary kernel | Erased Lean typing | | IxVM `Check` and `CheckEnv` | Erased Lean typing, validator byte `1` | | IxVM `Reveal` and `Contains` | Structural validation, validator byte `0` | | IxVM `Resource` | Explicitly unsupported; native success is not a circuit proof | @@ -183,8 +182,8 @@ These are executable-check and transition invariants. They do not replace the ordinary typechecking prerequisite or claim a proof of a machine backend's allocation behavior. -[Shared resource fixtures](../Tests/Fixtures/ixon-v4/resource.tsv) exercise -accepted and rejected terms in both implementations. The suites also cover +[Resource fixtures](../Tests/Fixtures/ixon-v4/resource.tsv) exercise +accepted and rejected terms. The suites also cover all 64 input/result combinations, imported-interface aliases, mutual groups, profile omissions, constructor captures, and bounded rejection of cyclic sharing. Tests explicitly distinguish wire validity from resource admission. diff --git a/docs/retired-rust.md b/docs/retired-rust.md new file mode 100644 index 000000000..bc12cd5ef --- /dev/null +++ b/docs/retired-rust.md @@ -0,0 +1,101 @@ +# Retired Rust implementations + +PR #677 removed three Rust implementations, with their FFI glue and the CLI +paths that used them: the compiler (`crates/compile`), the kernel +(`crates/kernel`) and the port of the sharing construction +(`crates/ixon/src/sharing_exact`). None was certified: no theorem covered +them, and neither the certified compiler (`Ix.Cert.compileReport`) nor the +certified checker (IxC, [kernel](kernel.md)) called them. Lean is now the +only implementation of each. + +The code is kept on the branch `jcb/archive-rust-compiler-kernel`, at commit +[`c6dc6d6c38dc`](https://github.com/argumentcomputer/ix/tree/c6dc6d6c38dce2f552a37c1884a25b3855fe3934). +Every link below is pinned to that commit. Comments that still name a Rust +function ("mirrors Rust `compile_mutual`") in `Ix/CompileM.lean`, +`Ix/CompileDriver.lean`, `Ix/Ground.lean`, `Ix/IndFlags.lean`, +`Ix/DecompileM.lean` or `Ix/IxVM/` refer to these crates at that commit. + +## Compiler: `crates/compile` + +It compiled a Lean environment to Ixon: reference graph, groundedness, +condensation into mutual blocks, inductive-flag validation, then each block +with the canonical sharing construction, on a work-stealing scheduler that +streamed the `.ixe` to disk. The crate also held the Rust decompiler and the +kernel's egress to Lean declarations. + +- **Relation to Lean.** A pure-Lean compiler existed first; the Rust compiler + became the fast production path, and the current Lean driver was then + ported back from it. At the archive commit, `Ix/CompileDriver.lean` opens + with "Port of the production scheduler semantics of + `crates/compile/src/compile/env.rs` … and the per-block dispatch of + `compile_const`", replacing work-stealing threads with a deterministic fold + over waves. +- **Trust.** Opt-in (`ix compile --rust`) and outside `CompileCorrect`: it ran + none of the compile report's checks. It was held to the Lean compiler only + by byte equality (suites `compile`, `cert-compile-parity-initstd`, + `initstd-reference` through `compile-lean --rust-check`, and the merge-queue + `--ignored compile` over the test environment). +- **Performance reference.** It remains the speed to aim at. In the + historical runs of [BENCHMARKS.md](../BENCHMARKS.md) ("Compilation", an + older pipeline), it compiled, serialized and wrote Mathlib in 63.6 s, where + the Lean compile phase took 723.0 s; Init+Std took 4.70 s against 39.9 s. + At the sharing cutover the Lean compile phase took 556.658 s on Mathlib's + 783,115 declarations ([sharing-minimum §14.4](sharing-minimum.md#144-evidence-and-release-gates)). +- **Code.** + [`compile.rs`](https://github.com/argumentcomputer/ix/blob/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/crates/compile/src/compile.rs), + [`compile/env.rs`](https://github.com/argumentcomputer/ix/blob/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/crates/compile/src/compile/env.rs) + (scheduler), [`graph.rs`](https://github.com/argumentcomputer/ix/blob/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/crates/compile/src/graph.rs), + [`ground.rs`](https://github.com/argumentcomputer/ix/blob/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/crates/compile/src/ground.rs), + [`decompile.rs`](https://github.com/argumentcomputer/ix/blob/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/crates/compile/src/decompile.rs), + the FFI [`crates/ffi/src/compile.rs`](https://github.com/argumentcomputer/ix/blob/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/crates/ffi/src/compile.rs). + +## Kernel: `crates/kernel` + +An out-of-circuit type checker over Ixon (`ix check-rs`), following lean4lean: +inductive validation and recursor generation, WHNF, definitional equality and +inference, plus claim builders, role admission and the shard partitioner (the +partitioner moved to `crates/ixon/src/shard.rs`). + +- **Trust.** Uncertified, and never a fallback for the certified entry. + IxC is a separate design, derived from con-leche's verified checker, not a + port of this kernel. Its tests compared verdicts: the tutorial and + check-env suites, and on Init+Std the `initstd-reference` fixture, where it + passed 99,427 of 99,432 constants (the five others are the + native-evaluation constants the certified checker declines or blocks). +- **Provenance only.** The IxVM circuit kernel was first written after this + kernel. Its correctness rests on the certified Lean, not on this kernel. +- **Code.** [`crates/kernel/src`](https://github.com/argumentcomputer/ix/tree/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/crates/kernel/src), + the FFI [`crates/ffi/src/kernel.rs`](https://github.com/argumentcomputer/ix/blob/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/crates/ffi/src/kernel.rs). + +## Sharing port: `crates/ixon/src/sharing_exact` + +The Rust compiler's copy of the canonical sharing construction +([Ixon, "Sharing System"](Ixon.md#sharing-system)). + +- **Relation to Lean.** A port of `Ix.Sharing.Exact`, written after it: + `tiered.rs` calls itself "a rule-for-rule port of the Lean + `Ix/Sharing/Exact/Tiered.lean`". Lean is the proved reference; the port was + unproved. +- **Testing.** The Lean/Rust suite `exact-sharing-ffi` compared bytes on + fixtures, generated inputs and the compiler route, and over whole `.ixe` + corpora; Init, Init+Std, Mathlib and the merge-queue environment compiled + identically ([sharing-minimum §12.10, §14.4](sharing-minimum.md)). Two test + pieces existed only in Rust: the MSS reference encoding (`mss.rs`) and the + limit-grammar list `LIMIT_SPEC_PARITY`, which Lean keeps as + `limitSpecParity`. +- **Speed.** On one captured input the port took 25.50 s where Lean took + 331.00 s, before Lean's area evaluator brought that to 133.245 s + ([sharing-minimum §14.4](sharing-minimum.md#144-evidence-and-release-gates)). +- **Code.** + [`sharing_exact.rs`](https://github.com/argumentcomputer/ix/blob/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/crates/ixon/src/sharing_exact.rs), + [`tiered.rs`](https://github.com/argumentcomputer/ix/blob/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/crates/ixon/src/sharing_exact/tiered.rs) + (its "Checks" section lists the port's run-time checks), + [`uniform.rs`](https://github.com/argumentcomputer/ix/blob/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/crates/ixon/src/sharing_exact/uniform.rs), + [`mss.rs`](https://github.com/argumentcomputer/ix/blob/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/crates/ixon/src/sharing_exact/mss.rs), + [`tests.rs`](https://github.com/argumentcomputer/ix/blob/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/crates/ixon/src/sharing_exact/tests.rs), + the parity suite [`Tests/Ix/SharingExactFFI.lean`](https://github.com/argumentcomputer/ix/blob/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/Tests/Ix/SharingExactFFI.lean). + +The same PR also removed the Rust resource validator +([`crates/ixon/src/resource.rs`](https://github.com/argumentcomputer/ix/blob/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/crates/ixon/src/resource.rs); +Lean's is `Ix/Resource/Validate.lean`) and the Rust copy of `Ixon.canonUniv` +([`crates/ixon/src/canon_univ.rs`](https://github.com/argumentcomputer/ix/blob/c6dc6d6c38dce2f552a37c1884a25b3855fe3934/crates/ixon/src/canon_univ.rs)). diff --git a/docs/sharing-minimum-arena.md b/docs/sharing-minimum-arena.md index e1a00b239..80cdf345d 100644 --- a/docs/sharing-minimum-arena.md +++ b/docs/sharing-minimum-arena.md @@ -3,9 +3,11 @@ Question: can the ExprMeta arenas (43% of the Mathlib `.ixe`) be made substantially smaller by a change small enough for the v4 PR? -**Status.** The implicit post-order encoding (§4) is part of format v4 in both -implementations; `docs/Ixon.md`, "ExprMeta Arena", is its normative description. -Hash-consing (§5) is not implemented; it is designed here for a stacked PR. §1–§5 were +**Status.** The implicit post-order encoding (§4) is part of format v4 in both codecs, +Lean `Ix/Ixon.lean` and Rust `crates/ixon/src/metadata.rs`; `docs/Ixon.md`, "ExprMeta Arena", is its normative description. +Hash-consing (§5) is not implemented; it is designed here for a stacked PR. The Rust compiler, +decompiler and kernel that §4 and §5 mention were retired in PR #677, so the compiler and the +decompiler are Lean only ([retired Rust](retired-rust.md)). §1–§5 were measured on v3 files (header `0xE3`, `Tag0` integers, heuristic sharing tables); §6 and §7 on files with TagN integers everywhere but still the header `0xE3`, heuristic sharing and no 4-byte TagN rung; §7 ends with the sizes of the final v4 files. The call-site kinds @@ -20,7 +22,7 @@ no 4-byte TagN rung; §7 ends with the sizes of the final v4 files. The call-sit by 19.95% on Mathlib (−667,051,250 bytes) and 19.11% on Init (−37,332,515). - Lean and Rust are byte-identical (§4). - **Stacked PR: hash-consing.** It saves a further 10.6% (Mathlib) and 10.3% (Init). It - changes the producers and both compilers. It is designed + changes the compiler's metadata producer. It is designed in §5 and kept separable. All figures below are exact byte counts from the `arena_study` tool (§8). Its v3 pricing @@ -161,9 +163,9 @@ Implicit encoding by node kind (Mathlib): **None.** The decoded `ExprMetaArena` / `ExprMeta` (absolute `u64` indices) is identical, so none of these change: -- the compilers (`Ix/CompileM.lean`, `crates/compile/src/compile.rs`); -- the decompilers (`Ix/DecompileM.lean`, `crates/compile/src/decompile.rs`); -- kernel meta ingress (`Ix/Tc/IngressMeta.lean`, `crates/kernel/src/ingress.rs`); +- the compiler (`Ix/CompileM.lean`); +- the decompiler (`Ix/DecompileM.lean`); +- kernel ingress (IxC's reader, `IxC/Kernel/Ixon/Reader.lean`, reads no arena); - the FFI marshalling (`crates/ffi/src/lean_ixon/meta.rs`); - the proofs (none is about the serialized arena; the sharing proofs in `IxSharingVerify` do not touch metadata); @@ -226,6 +228,10 @@ Tag0 → TagN replacement had not landed. ### Lean/Rust parity +These are the records of the time. The `ixon` suite's `Lean==Rust` codec checks still run; +the `ixon-corpus` gate read an environment written by the Rust compiler and was retired with +it, and so were the Rust `resource` and `sharing_exact` tests named below. + - **Lean `ixon` suite: 374 passed, 0 failed.** It includes: - the byte vectors; - "Surgery metadata env bytes Lean==Rust"; @@ -284,18 +290,18 @@ Tag0 → TagN replacement had not landed. **Consumer audit (tree assumptions).** -- **No code mutates an arena node after allocation**, in Lean or Rust. The only - post-allocation read is `finishEtaCallSite`, which only reads. +This audit was made when four consumers read arenas: the Lean and the Rust decompilers, Rust +kernel ingress and a Lean kernel ingress since removed. The decompiler (`Ix/DecompileM.lean`) +is the only one left; IxC's reader reads no metadata. + +- **No code mutates an arena node after allocation.** The only post-allocation read was + `finishEtaCallSite`, which only reads. - **Every expression-conversion cache is keyed by (expression, arena index)**, so a - shared node is safe: - - Rust decompile: `(Arc ptr, idx)`; - - Rust kernel ingress: `(expr ptr, arena_idx)`; - - Lean `DecompileM`: `(e, arenaIdx)`; - - Lean `IngressMeta`: `(shareIdx, arenaIdx)`. + shared node is safe: `DecompileM` keys its cache by `(e, arenaIdx, scope)`. - **No consumer does index arithmetic** on arena indices. The decompiler's `u64::MAX` "no metadata" sentinel is in-memory only. - **The one per-node side table is `univPatches`.** Level-spelling patches are keyed by - arena index, and all four consumers read them per node. Two occurrences with different + arena index and read per node. Two occurrences with different original spellings must not share a node, so patched nodes are never merged; the measurement above already obeys this. The compiler also clones the head's patch onto `callSite`/`etaCallSite` roots by index lookup during compilation, which is before any @@ -318,10 +324,10 @@ Tag0 → TagN replacement had not landed. **Size estimate.** -- the pass in Lean and Rust, about 120 lines each, plus Lean/Rust parity tests; -- one call at each metadata finalization site: Lean `takeArena` users in - `Ix/CompileM.lean` (defn, axio, quot, recr, ctor, inductive and mutual paths), and the - Rust equivalents in `compile.rs` and `kernel_egress.rs`; +- the pass, about 120 lines of Lean, plus tests (the estimate was made when a Rust copy and + Lean/Rust parity tests were also needed); +- one call at each metadata finalization site: the `takeArena` users in + `Ix/CompileM.lean` (defn, axio, quot, recr, ctor, inductive and mutual paths); - fixture regeneration, which v4 needs anyway. That is too large for "serializer only", hence a stacked PR. It needs no format change: diff --git a/docs/sharing-minimum-integration.md b/docs/sharing-minimum-integration.md index 7464f6880..fbb418fd4 100644 --- a/docs/sharing-minimum-integration.md +++ b/docs/sharing-minimum-integration.md @@ -3,16 +3,18 @@ This map says where Ixon v4's two changes, canonical sharing and TagN integers, live in the code, together with what follows from them: format version 4, the new identifiers, the reader rule for `Share` inside metadata, and the IxVM circuit codec. For each part it names the Lean, -Rust and IxVM code, the theorems that cover it and the fixtures that pin it. The normative +Rust and IxVM code, the theorems that cover it and the fixtures that pin it. The sharing +construction, the compiler and the decompiler exist in Lean only; Rust has the Ixon codec +(`crates/ixon`). The Rust port of the construction and the Rust compiler were retired in +PR #677 ([retired Rust](retired-rust.md)). The normative format is [Ixon](Ixon.md); the decisions behind it are in [`sharing-minimum.md`](sharing-minimum.md) §12–§14. Code is referred to by file and symbol, not by line. Decisions in force (`sharing-minimum.md` §12.8, §12.11–§12.16, §13–§14): -- the canonical sharing of a constant is `Ix.Sharing.Exact.canonicalSharingTiered .tagN` (Rust - `canonical_sharing_tiered(ShareLayout::TagN, ..)`), the only sharing construction of both - compilers, under `sharing-k16-greedy-v1` (K = 16); the heuristic and its proofs - are removed. The old unbounded exact search is a reference route; +- the canonical sharing of a constant is `Ix.Sharing.Exact.canonicalSharingTiered .tagN`, + the compiler's only sharing construction, under `sharing-k16-greedy-v1` (K = 16); the + heuristic and its proofs are removed. The old unbounded exact search is a reference route; - TagN with flag widths 0, 2 and 4 replaces Tag0, Tag2 and Tag4 at every site of the grammar and is the only integer code; there is one format, with no dual-format gate and no reader for the old one; @@ -20,8 +22,8 @@ Decisions in force (`sharing-minimum.md` §12.8, §12.11–§12.16, §13–§14) the resource validator ID (`"ixon-v4/resource-v1"`) and `wireFormatId` (`"ixon-v4"`). The environment envelope is v4 (`0xE4`), or v5 (`0xE5`) with a role binding; the extension does not change the checked payload grammar; -- compiler limits are a safety net: metering and fail-closed errors stay, the defaults sit far - above every corpus maximum, and `ix compile --sharing-limits` overrides them; +- compiler limits are a safety net: metering and fail-closed errors stay, and the defaults sit far + above every corpus maximum (`compileReport` fixes the default limits); - a `Share` inside a metadata expression is read in the extended index space of `sharing-minimum.md` §13.1; there is no metadata construction; - the IxVM circuit codec changes in the same PR. @@ -32,12 +34,12 @@ Decisions in force (`sharing-minimum.md` §12.8, §12.11–§12.16, §13–§14) |---|---| | TagN at every Lean, Rust and IxVM site; six rungs (widths 1, 2, 3, 4, 5, 9); old encoders, decoders and canonical-integer checks deleted | done | | Sharing pricing and tie-break bytes at TagN widths; count threshold θmax (finding 7); telescope-spine guard (finding 8) | implemented; guards can still refuse | -| Canonical construction as the only compiler route in Lean and Rust; heuristic removed | done (§3.2, §8) | -| Compiler limits as a safety net with a CLI override | done (§8) | +| Canonical construction as the only compiler route; heuristic removed | done (§3.2, §8) | +| Compiler limits as a safety net, fixed at the defaults in the certified compiler; overrides for tests and tools | done (§8) | | Successful-run proofs for the production family, input expansion, format and builder codec; audited fast twins | proved with the conditions described in §4, with totality and output-domain closure; a cost bound remains a follow-up | | Checked object format 4 and `ixon-v4` identifiers; v4/v5 environment envelopes | implemented; see [Ixon](Ixon.md#environment) for the envelope distinction | | `.ixe` caches keyed by the format version | done (§6) | -| Metadata `Share` reader rule in both decompilers | done; no metadata construction (§5.3) | +| Metadata `Share` reader rule in the decompiler | done; no metadata construction (§5.3) | | IxVM TagN codec, claim scope bytes and v4 primitive addresses | implemented (§3.1, §6); the historical gate record below does not establish current FFT cost-pin acceptance | | Corpus parity and performance | [K16 production evidence](sharing-minimum.md#144-evidence-and-release-gates); older exact-route measurements below are historical | @@ -53,13 +55,11 @@ Decisions in force (`sharing-minimum.md` §12.8, §12.11–§12.16, §13–§14) shorter on `[2^24, R4)` and `[2^32, R5)`. 3. **The cost model prices the wire bytes.** `Ix/Sharing/Exact/Basic.lean` defines `tag0Size := Ixon.tagNByteWidth 0` and `tag4Size := Ixon.tagNByteWidth 4`, and from them - `shareWidth`, `sizeInfo` and `exprSize`; Rust `sharing_exact/cost.rs` `tag0_len`/`tag4_len` - are `TagN::byte_width`. The tie-break header bytes are TagN encodings in both languages (Lean - `Dictionary.lean` `tag4Bytes` runs `putTagN 4`; Rust `cost.rs` `tag4_bytes_cmp` compares - `TagN::put` outputs). TagN is a prefix code, so distinct header values still differ inside - the shorter encoding. That the price of the tiered output is its serialized length is a - theorem (`canonicalSharingTiered_serialized`); Rust also serializes the returned candidate - and compares the lengths at run time (`tiered.rs`, check 9). + `shareWidth`, `sizeInfo` and `exprSize`. The tie-break header bytes are TagN encodings + (`Dictionary.lean`: `tag4Bytes` runs `putTagN 4`). TagN is a prefix code, so distinct header + values still differ inside the shorter encoding. That the price of the tiered output is its + serialized length is a theorem (`canonicalSharingTiered_serialized`), so the construction + does not serialize its output to measure it. 4. **There are three codecs.** Besides Lean (`IxC/Ixon/Codec.lean`, with the metadata and environment codecs in `Ix/Ixon.lean`) and Rust (`crates/ixon`), the IxVM circuit has its own codec (`Ix/IxVM/IxonDeserialize.lean`, `Ix/IxVM/IxonSerialize.lean`), @@ -75,15 +75,16 @@ Decisions in force (`sharing-minimum.md` §12.8, §12.11–§12.16, §13–§14) fail, so the singleton-driver tail concludes `SharingRunOK`: the run returns an exactly decodable block, or fails with an error the builder returns on some payload (§4). 7. **The certain-stored threshold follows the TagN count growth.** The uniform optimizer - (`Uniform.lean`, Rust `uniform.rs`) once used θ = 2 because one more table entry grew the + (`Uniform.lean`) once used θ = 2 because one more table entry grew the `Tag0` count by at most a byte. The TagN (`f = 0`) count grows by 4 bytes at 4,311,826,560 entries (5 → 9). The threshold is θmax = `tag0StepBound n + 1` with `n` the candidate count (2 - below that bound), in both languages, and `tag0BracketStart` returns the TagN rung start. + below that bound), and `tag0BracketStart` returns the TagN rung start. 8. **Telescope headers are subadditive only below `R5`.** The certain-excluded rule assumes `tag4Size (a + b) ≤ tag4Size a + tag4Size b`, which fails at `R5` = 4,311,811,080 (`f = 4`). `optimizeUniformExpanded` fails closed (`formatBound "telescope spine length"`) on a spine of `teleSubaddEnd = Ixon.tagNEnd5 4` nodes or more, and the optimality theorems take the bound - (`SpinesFit`) from that guard. Rust fails with `FormatBound::TelescopeSpine`. + (`SpinesFit`) from that guard. The K16 family route has no such guard (`sharing-minimum.md` + §14.2). 9. **Claim tags past the first rung changed.** Catalog and Resource claims (flag `0xE`, values 8 and 9) are written `E8 00` and `E8 01`; the old code wrote `E8 08` and `E8 09`. 10. **Version header aliasing (kept).** The env header and the claim tags share flag `0xE`: @@ -96,8 +97,8 @@ Decisions in force (`sharing-minimum.md` §12.8, §12.11–§12.16, §13–§14) |---|---| | §8.3 "The expression byte grammar need not change." | True for the construction alone. False after §12.12: every integer past the first rung changes, including Share indices ≥ 8. | | §8.2 "Metadata expressions may also contain Share references …" | The readers implement the extended index space (§5.3); no writer emits a `Share` into `metaSharing`. | -| §2 source map: Lean, Rust, FFI. | Also the IxVM circuit codec and its generated Rust (finding 4). | -| §8.1 "Derive roots from ConstantInfo … do not use out-of-range fallbacks." | Implemented: Lean `buildConstantWithSharing` takes the roots from `constantInfoRootExprs` and writes them back with `withRootExprs`, after checking that the construction returned one root per input root. Rust `apply_sharing_to_*` derive the roots from the payload. | +| §2 source map: Lean, Rust, FFI. | Also the IxVM circuit codec and its generated Rust (finding 4). The Rust construction, compiler routes and sharing FFI it lists are retired. | +| §8.1 "Derive roots from ConstantInfo … do not use out-of-range fallbacks." | Implemented: `buildConstantWithSharing` takes the roots from `constantInfoRootExprs` and writes them back with `withRootExprs`, after checking that the construction returned one root per input root. | ## 3. Call sites and consumers @@ -113,12 +114,11 @@ use the same code: `Ixon.DeserCursor.tagN0?`, `Ix.DecompileM.BlobCursor.readTagN syntax-blob writer `Ix.CompileM.tagN0Bytes`. **Rust**: `crates/ixon/src/tag.rs` defines only `TagN` (`put`, `get`, `byte_width`, -`end1`..`end5`). Every former `Tag0`/`Tag2`/`Tag4` site in `ixon`, `ix-compile` and `ix-kernel` -uses it: `serialize.rs` (`put_expr`, `get_expr`, constants, `Env::put`, `read_env_header`), -`metadata.rs` (with `deser_tagn0`), `proof.rs`, `comm.rs`, `assumption_tree.rs`, `lazy.rs`, -and `decompile.rs` (`read_tagn0`). `get_expr` is the only byte-level expression decoder in Rust; -the other readers (`lazy.rs`, `catalog.rs`, `proof.rs`, `diff.rs`, `resource.rs`) decode -expressions through it and skip constants by their length prefix. +`end1`..`end5`). Every former `Tag0`/`Tag2`/`Tag4` site in `ixon` uses it: `serialize.rs` +(`put_expr`, `get_expr`, constants, `Env::put`, `read_env_header`), `expr.rs`, `univ.rs`, +`constant.rs`, `metadata.rs`, `proof.rs`, `comm.rs`, `assumption_tree.rs`, `lazy.rs`, `env.rs` +and `catalog.rs`. `get_expr` is the only byte-level expression decoder in Rust; the other +readers decode expressions through it and skip constants by their length prefix. **IxVM**: `Ix/IxVM/IxonDeserialize.lean` reads with `get_tagn0`, `get_tagn2` and `get_tagn4`; the one-byte case is one narrow row and the longer encodings share `get_tagn_tail` and @@ -137,11 +137,10 @@ header is a rung-1 TagN, one byte. ### 3.2 The sharing construction and its callers The construction is `Ix.Sharing.Exact.canonicalSharingTiered` (`Ix/Sharing/Exact.lean` over -`Ix/Sharing/Exact/Tiered.lean`) and Rust `canonical_sharing_tiered` -(`crates/ixon/src/sharing_exact/tiered.rs`), with the deterministic parallel variant -`normalize_constant_sharing_tiered_par` (byte-identical to the sequential path). +`Ix/Sharing/Exact/Tiered.lean`); its compiled form runs the three widths in parallel on large +inputs with the sequential result (`tieredCandidates_eq`, §8). -**Lean.** `Ix.CompileM.buildConstantWithSharing limits info refs univs : Except CompileError +`Ix.CompileM.buildConstantWithSharing limits info refs univs : Except CompileError Constant` takes the roots from `constantInfoRootExprs`, runs `canonicalSharingTiered .tagN` under `limits` and writes the roots back with `withRootExprs`, which delegates to the checked `Ix.Sharing.Exact.withRoots` (one root per input root, no positional fallback). Every block goes @@ -153,42 +152,32 @@ through it: | `finishInductiveFamilyBlock` | standalone inductive families | | `finishMutualCompilation` → `buildCompiledMutualBlock` | mutual blocks | -**Rust** (`crates/compile/src`). `share_roots` runs the construction on a block's ordered roots. -`apply_sharing_to_{definition,axiom,quotient,recursor,mutual_block}_with_limits` run it under -the limits they are given and return the shared `Constant`. The compiler passes the limits it -read once per run (`CompileState::sharing_limits`, from `compiler_sharing_limits`: the defaults -with the `IX_SHARING_LIMITS` override); the FFI parity hook `rs_compiler_sharing_build` passes -explicit limits. +The certified compiler fixes `CompileEnv.sharingLimits` to the defaults +(`Ix.CompileM.compilerSharingLimits`, §8). -| Caller | Error handling | -|---|---| -| `compile.rs`: `compile_single_def`, `compile_const_inner`, `compile_mutual`, `compile_mutual_block` | `CompileError`, propagated with `?` | -| `kernel_egress.rs`: `egress_muts_block`, `egress_standalone` | mapped to the egress `String` errors, naming the constant (`egress '': …`) | - -Errors: resource exhaustion is `CompileError.resourceLimit` (it names the limit and the -override); every other construction failure is `CompileError.sharingConstruction`. +Errors: resource exhaustion is `CompileError.resourceLimit` (it names the limit and its +bound); every other construction failure is `CompileError.sharingConstruction`. The initial K16 gate selects the exact or greedy family before search; a failure does not switch families or emit a best-so-far result. ### 3.3 Share consumers that are independent of the encoding -These expand `Share(i)` against the decoded table and need no change for TagN: Lean +These expand `Share(i)` against the decoded table and need no change for TagN: `Ix/DecompileM.lean` (`resolveShareIn`, `collectIxonTelescopeExpandingShares`), IxC's reader (`IxC/Kernel/Ixon/Reader.lean`), `Ix/SemanticContract.lean` (`containsIxon`), `Ix/Resource/Addressed.lean` (offsets Share into a -program-wide table); Rust `crates/compile/src/decompile.rs`, `crates/kernel/src/ingress.rs`, -`crates/compile/src/semantic_contract.rs`, `crates/ixon/src/resource.rs`, -`resource/addressed.rs` and `diff.rs`. The IxVM ingress and conversion modules also consume the +program-wide table); Rust `crates/ixon/src/diff.rs` (`resolve_share`) and +`crates/ffi/src/lean_ixon/diff.rs`. The IxVM ingress and conversion modules also consume the table logically. ### 3.4 Pricing and tie-break bytes in the construction | Site | What it prices | |---|---| -| `Ix/Sharing/Exact/Basic.lean`: `tag4Size`, `tag0Size`, `shareWidth`, `sizeInfo`, `exprSize`; Rust `sharing_exact/cost.rs`: `tag4_len`, `tag0_len` | headers, counts, refs/univs indices and Share widths at TagN widths | -| `Ix/Sharing/Exact/Uniform.lean`: `tag0BracketStart`, `tag0StepBound`, `teleSubaddEnd`; Rust `uniform.rs`: `tag0_bracket_start`, `tag0_step_bound` | count brackets at the TagN rung starts, θmax (finding 7), the spine guard (finding 8) | -| `Ix/Sharing/Exact/Tiered.lean`: `ShareLayout.widthAt` (`tagNWidth`), `layoutBytes`, `rematerialize`; Rust `tiered.rs`: `tagn_width` | the real Share width at each index; the price is the serialized length (a theorem in Lean, a run-time check in Rust) | -| `Ix/Sharing/Exact/Dictionary.lean`: `tag4Bytes` (cut, inline and Share option bytes); Rust `cost.rs`: `tag4_bytes_cmp` | tie-break bytes as TagN encodings; a Share option loses every tie, since inline and cut options start with a flag nibble below `0xB` | +| `Ix/Sharing/Exact/Basic.lean`: `tag4Size`, `tag0Size`, `shareWidth`, `sizeInfo`, `exprSize` | headers, counts, refs/univs indices and Share widths at TagN widths | +| `Ix/Sharing/Exact/Uniform.lean`: `tag0BracketStart`, `tag0StepBound`, `teleSubaddEnd` | count brackets at the TagN rung starts, θmax (finding 7), the spine guard (finding 8) | +| `Ix/Sharing/Exact/Tiered.lean`: `ShareLayout.widthAt` (`tagNWidth`), `layoutBytes`, `rematerialize` | the real Share width at each index; the price is the serialized length (a theorem) | +| `Ix/Sharing/Exact/Dictionary.lean`: `tag4Bytes` (cut, inline and Share option bytes) | tie-break bytes as TagN encodings; a Share option loses every tie, since inline and cut options start with a flag nibble below `0xB` | ## 4. Verification theorems @@ -227,8 +216,7 @@ new-route success at an arbitrarily tight budget; idempotence does not silently supply second-run success. Totality on the stated input domain and output-domain closure are proved; a cost bound remains follow-up work ([§14.3](sharing-minimum.md#143-proof-scope-and-follow-up)). There is no global -minimum claim or phase-2 optimum for tables above 1,032 entries. Rust has -differential evidence, not a universal refinement proof. Sharing preserves +minimum claim or phase-2 optimum for tables above 1,032 entries. Sharing preserves the unfolded Ixon expressions; these results do not complete the Source acceptance or compiler value-preservation composition. @@ -242,7 +230,7 @@ codec, is retired (see [kernel](kernel.md)). - **Separate namespaces.** `ConstantMeta.metaSharing` is its own table, indexed from 0 per constant. No compiler writes it now: the call-site metadata and the decompile records that indexed it are retired. -- **Kernel ingress ignores it.** Rust `crates/kernel/src/ingress.rs` and IxC's reader do not +- **Kernel ingress ignores it.** IxC's reader does not read `metaSharing`. ### 5.2 Metadata payloads contain no primary Share references @@ -261,18 +249,18 @@ entry `i` when `i < p` (shares nested in that entry stay primary) and `metaShari primary entry. A record's `_ix.inline` indexes `metaSharing` directly and reads that entry in its own scope. -| | Lean | Rust | -|---|---|---| -| scopes and resolution | `Ix.DecompileM.ShareScope` (`primary`, `metaEntry j`), `resolveShareIn` | `decompile::ShareScope` (`Primary`, `Meta { entry }`) | -| table check on load | `validateMetaSharing` (in `withFreshBlock`) | `validate_meta_sharing` (in `load_meta_extensions`, used by `decompile_const`, `decompile_projection` and `roundtrip_block`) | -| error | `DecompileError.invalidMetaShareIndex idx entry primaryLen metaLen constant` | `DecompileError::InvalidMetaShareIndex { idx, entry, primary_len, meta_len, constant }` | -| semantic-contract scan | `Ix.SemanticContract.containsIxon` with the decompiler's scopes | `semantic_contract::contains_ixon` | +| | Decompiler (`Ix/DecompileM.lean`) | +|---|---| +| scopes and resolution | `ShareScope` (`primary`, `metaEntry j`), `resolveShareIn` | +| table check on load | `validateMetaSharing` (in `withFreshBlock`) | +| error | `DecompileError.invalidMetaShareIndex idx entry primaryLen metaLen constant` | +| semantic-contract scan | `Ix.SemanticContract.containsIxon` with the decompiler's scopes | -The error has tag 11 in both languages. The table check is iterative, does not expand shares and +The table check is iterative, does not expand shares and reports the first violation in entry order, then left-to-right pre-order. A malformed primary -`Share` found by the semantic-contract scan is reported as `InvalidShareIndex`; the record replay +`Share` found by the semantic-contract scan is reported as `invalidShareIndex`; the record replay uses the scan to refuse a record over a compiled term that carries a contract. The IxVM circuit, -kernel ingress and egress, `diff.rs` and the resource validators do not read `metaSharing` +kernel ingress, the Rust `diff.rs` and the resource validators do not read `metaSharing` contents, and no writer emits a metadata `Share`. The table check is linear in the DAG size of the table: each shared node is visited once. Its scope must be distinguished from the certified reader and from other host traversals: @@ -280,20 +268,17 @@ certified reader and from other host traversals: | Entry | Current checks and boundary | | --- | --- | | Certified IxC admission | `IxC/Kernel/Ixon/Reader.lean`, `MemberReader.mk'` builds each primary sharing entry against earlier entries only; `convExpr` returns a malformed-reference error when a self/forward reference is used. An unused failing entry need not be forced. This entry does not read optional `ConstantMeta` or replay `_ix.inline` records. | -| Host metadata-Share expansion | `validateMetaSharing` / `validate_meta_sharing` checks every metadata table entry, rejecting self, forward and out-of-range metadata Shares before decompilation. This does not validate the primary sharing table or the metadata arena/replay graph. | -| Raw host primary-Share expansion | `Ix.DecompileM.resolveShareIn`, Rust `BlockCache::resolve_share` and Rust kernel `ingress.rs`'s expression conversion check primary index range but have no backwardness/progress guard on that traversal. For example, `sharing = [Share(0)]` resolves `Share(0)` back to itself. These host paths are distinct from the certified IxC reader. | -| Host metadata replay | The arena codec represents arbitrary UInt64 child references with wrapping deltas (`Ix/Ixon.lean`, `crates/ixon/src/metadata.rs`). Its canonical byte encoding does not establish acyclicity. The decompilers follow `Mdata.child` and range-checked `_ix.inline` replay targets without a general active-cycle check; the metadata-Share bound does not constrain those edges. | +| Host metadata-Share expansion | `validateMetaSharing` checks every metadata table entry, rejecting self, forward and out-of-range metadata Shares before decompilation. This does not validate the primary sharing table or the metadata arena/replay graph. | +| Raw host primary-Share expansion | `Ix.DecompileM.resolveShareIn` checks the primary index range but has no backwardness/progress guard on that traversal. For example, `sharing = [Share(0)]` resolves `Share(0)` back to itself. This host path is distinct from the certified IxC reader. | +| Host metadata replay | The arena codec represents arbitrary UInt64 child references with wrapping deltas (`Ix/Ixon.lean`, `crates/ixon/src/metadata.rs`). Its canonical byte encoding does not establish acyclicity. The decompiler follows `Mdata.child` and range-checked `_ix.inline` replay targets without a general active-cycle check; the metadata-Share bound does not constrain those edges. | These remaining host traversal limits follow from the current resolver/replay code; they are not a blanket claim that all cyclic metadata bypasses checks. -Rust kernel `LocalDefnDependencies::validate` separately rejects local safe -definition cycles. That guard does not validate Share or metadata-replay edges. ### 5.4 How per-occurrence arena roots survive Arena nodes mirror the **unshared** logical tree; a Share is transparent and keeps the same -arena index (`Ix/DecompileM.lean`, Rust `decompile.rs`, -`ingress.rs`). Universe patches are keyed by arena index. Root indices +arena index (`Ix/DecompileM.lean`). Universe patches are keyed by arena index. Root indices (`typeRoot`, `valueRoot`, `ruleRoots`) are arena indices. So any change of which subterms are shared, or of where a Share cuts a telescope, leaves arena roots, binder data, universe patches and `Named.original` valid. Editing only the optional @@ -312,8 +297,8 @@ The sharing-rule revision is separate again (§14 of `sharing-minimum.md`). | environment envelope version | `Ixon.Env.envelopeVersion` | `Env::envelope_version` | 4 (`0xE4`) without a role binding; otherwise 5 (`0xE5`) | | object-format byte (claims, proofs, catalog manifests) | `Ixon.Env.OBJECT_FORMAT` | `Env::OBJECT_FORMAT` | 4 | | wire format ID | `Ixon.wireFormatId` | `WIRE_FORMAT_ID` | `"ixon-v4"` | -| resource validator ID (profile prefix `/profile\0`) | `Ix.Resource.validatorId` | `resource::addressed::VALIDATOR_ID` | `"ixon-v4/resource-v1"` | -| catalog manifest version | `Ix.Catalog.VERSION` | `CATALOG_VERSION` | 2 (unchanged) | +| resource validator ID (profile prefix `/profile\0`) | `Ix.Resource.validatorId` | none (the Rust resource validator is retired) | `"ixon-v4/resource-v1"` | +| catalog manifest version | `Ix.Catalog.VERSION` | `CATALOG_VERSION` | 3 | | text grammar version | `Ixon.Syntax.VERSION` | `syntax::VERSION` | 3 (unchanged; it covers no integer bytes) | The env readers (Lean `getEnv`, `getEnvVerifiedLazy`; Rust `read_env_header`, used by @@ -329,11 +314,10 @@ the claim" ("catalog: …" likewise). The in-circuit claim readers require objec | Fixture or pin | Location | Checked by | Producer | |---|---|---|---| -| handoff set | `Tests/Fixtures/ixon-v4/handoff/` (`accepted.ixe`, `rejected-local-escape.ixe`, `profile.bin`, `accepted.claim`, `manifest.json`) | `ixon-v4-tests` (byte equality); `ix-kernel` `resource::tests::cross_language_consumer_handoff` | `lake exe ixon-v4-tests --export-handoff` | -| claims, addressed resources, resource cases | `Tests/Fixtures/ixon-v4/{claims,addressed,resource}.tsv` | `ixon-v4-tests` (whole files); `ixon` `proof::tests::v4_claim_fixtures_and_strict_scope`, `resource::addressed::tests::canonical_cross_language_fixtures` | `lake exe ixon-v4-tests --export-fixtures` | +| handoff set | `Tests/Fixtures/ixon-v4/handoff/` (`accepted.ixe`, `rejected-local-escape.ixe`, `profile.bin`, `accepted.claim`, `manifest.json`) | `ixon-v4-tests` (byte equality) | `lake exe ixon-v4-tests --export-handoff` | +| claims, addressed resources, resource cases | `Tests/Fixtures/ixon-v4/{claims,addressed,resource}.tsv` | `ixon-v4-tests` (whole files); `ixon` `proof::tests::v4_claim_fixtures_and_strict_scope` | `lake exe ixon-v4-tests --export-fixtures` | | canonical primitive addresses | `Tests/Fixtures/ixon-v4/primitives.tsv`, `crates/common/src/prim_addrs.rs` (`PrimAddrs::new`), `Ix/PrimAddrs.lean`, the IxVM literals in `Ix/IxVM/Kernel/*.lean` | `lake exe ixon-v4-primitives` (file equality), suites `primitive-address-parity` and `prim-addrs` (IxVM literals) | `lake exe ixon-v4-primitives` | | catalog claim digest | `Tests/Ix/Claim.lean`, `crates/ixon/src/proof.rs` (`catalog_claim_wire_bytes_pinned`) | `claim` suite, `ixon` tests | the serializers' output | -| env-bytes hash | `crates/compile/src/graph.rs` | `ix-compile` `graph::tests::setup_scan_preserves_compiled_fixture_bytes` | rerun and pin | | independent expression bytes | `Tests/Fixtures/ixon-v4/expressions.txt` | `ixon-v4-tests`; `ixon` `v4_independent_golden_expressions` | written by hand from the specification (`text.tsv` holds no bytes) | | IxVM FFT-cost pins | `Tests/Ix/IxVM.lean` (kernel checks), the shard pin in `Tests/Main.lean` | ignored runner `ixvm` | rerun and pin | @@ -356,23 +340,22 @@ commit. To regenerate an `.ixe`: `lake exe ix compile .lean --out .ixe` ## 7. Risks -1. **Lean/Rust disagreement of tiered outputs** would fork the address space. Fixtures, - generated inputs and the compiler route agree byte for byte (`exact-sharing-ffi`). With Rust - in its checked mode, all stored constants of Init (56,783) and of Init+Std (100,277) compiled - on Lean 4.34.0 give identical bytes, as did all of Init (56,622) and a Mathlib sample of - 20,284 constants on Lean 4.33.1, with no resource exhaustion on either side; and the - merge-queue suite `lake test -- --ignored compile` requires the Lean and Rust compilers to - write identical environments (238,574 constants on Lean 4.34.0) - (`sharing-minimum-performance.md`). +1. **No second implementation.** The construction exists only in Lean. While the Rust port + existed, the two agreed byte for byte on fixtures, generated inputs, the compiler route, all + of Init and Init+Std, a Mathlib sample and every constant of the merge-queue test environment + (`sharing-minimum.md` §12.10, §14.4; `sharing-minimum-performance.md`). That differential + check is gone with the port; determinism now rests on the proofs (§4) and the Lean suites + (§8). The deleted parity suite was also the only test of two shapes: constants whose + tables reach 66,600 entries (Shares in the 4-, 5- and 9-byte rungs), and doubling towers + whose unshared length exceeds `2^127`. Lean-only versions of those cases are follow-up + work. 2. **Resource exhaustion in the compiler.** The construction fails closed; a constant over the - limits is a compile error for every caller (compile and kernel egress). The defaults sit - far above every corpus maximum (Mathlib: largest MSS table - 21,461 entries; 81,833 candidates; largest table-count knapsack 9,959,424 cells against a - `knapsack_cells` default of 2^28), and `--sharing-limits` raises them. Neither language limits - the candidate count on this path. Lean and Rust count work differently (Rust's `work` charges - the evaluations actually done: one evaluation of the whole table in one pass, one per prefix - otherwise), so equal limits can succeed in one language and fail in the other; every observed - case was a Lean-only exhaustion that matched Rust once raised. + limits is a compile error. The defaults sit far above every corpus maximum (Mathlib: largest + MSS table 21,461 entries; 81,833 candidates; largest table-count knapsack 9,959,424 cells + against a `knapsack_cells` default of 2^28). The certified compiler cannot raise them; tests + and study tools can (`Limits.withOverrides`). The candidate count is not limited on this + path. The meters count the specification's work, whichever path the compiled code takes + (`materializeTableOnePass_eq`, §8). 3. **The pointer cache is not evidence of term equality.** Successful input expansion is covered by the proofs (§4), including structural confirmation of cache hits. The unsafe pointer key remains an explicit implementation boundary in the audit. @@ -380,25 +363,13 @@ commit. To regenerate an `.ixe`: `lake exe ix compile .lean --out .ixe` threshold (finding 7) and the telescope-spine guard (finding 8) cover the two arguments that needed it; any new argument that assumes a header grows by at most one byte per boundary is unsound from `R5` on. -5. **Recompile invariant.** Rust `roundtrip_block` requires recompiled addresses to equal - `Named.original`; the recompile calls the compile path's `apply_sharing_*` functions under - the compiler's limits, so it follows the route. -6. **Header aliasing** (finding 10), kept by decision. -7. **Stale artifacts.** Caches are keyed by the format version (§6). Artifacts pinned by hash +5. **Header aliasing** (finding 10), kept by decision. +6. **Stale artifacts.** Caches are keyed by the format version (§6). Artifacts pinned by hash from outside the repository (the FLT benchmark) must be regenerated by their owners. -8. **Host traversal limits** (§5.3): raw host primary-Share expansion and metadata +7. **Host traversal limits** (§5.3): raw host primary-Share expansion and metadata arena/replay traversal still lack general cycle guards. The certified IxC reader uses only earlier primary entries, and host metadata-Share self/forward references already fail their dedicated check. These are separate entrypoint contracts. -9. **Length range.** General Rust costs and signed deltas still use checked 128-bit - arithmetic, so overflow can refuse where Lean's `Nat` succeeds. The earlier initial- - classification overflow on doubling DAGs is repaired: all 11 fixtures, including - depths 127–256, have Rust/Lean byte parity. That bounded predicate repair does not - prove the remaining general arithmetic bounds (§14.3–§14.4 of `sharing-minimum.md`). -10. **Rust's default mode checks built expressions for the returned candidate only.** A wrong - length of another candidate would change the selection without an error. The checked mode - (`ExactSharingLimits::full_check`) builds and checks all three; over all of Init and Mathlib - it gives the default mode's output. ## 8. Implementation notes @@ -418,43 +389,40 @@ is `tieredAtWidth`. Removed with the heuristic: `Ix/Sharing.lean`, `crates/ixon/ `Ix/Compile/Verify/Sharing.lean` and its two audit roots (`rewriteWithSharing_wireWF`, `applySharing_wireWF`), the heuristic seeds of the exact optimizers (the unshared length is the initial bound), the `sharing` suite and the heuristic FFI hooks. Experiment hooks used during -development (forced phase-1 width, a fourth phase-1 candidate, the MSS inspector, the -`sharing_corpus` modes `--layout`, `--width-experiment` and `--mss-*`) are removed; the -forced-width entry point survives only as a crate-private test helper. Also removed: the Rust -`apply_sharing_with_stats` and `apply_sharing_to_*_with_stats` wrappers, -`SingletonSharingResult`, `MutualBlockSharingResult`, `block_stats`/`BlockSizeStats`, -`optimize_dag`, `optimize_dag_uniform`, `optimize_sharing_uniform_reference` and `tier2_end`. -Renamed: `check_canonical_sharing` → `check_exact_minimum` (Lean `checkCanonicalSharing` → -`checkExactMinimum`, `CanonicalCheck` → `ExactMinimumCheck`). - -**Rust checks and metering.** The module documentation of `crates/ixon/src/sharing_exact/tiered.rs` -("Checks") lists every check. In the default mode every limit and every phase-1 to phase-3 length -check applies to all three candidates, and the checks on built expressions run for the returned -candidate; the checked mode (`ExactSharingLimits::full_check`: unit tests, the FFI parity hooks, -`sharing_corpus --full-check`) runs them for all three. Phase 1 is a dry run that builds no -expressions; phase 3 evaluates the whole table once when the order is closed under stored -descendants and per prefix otherwise (an App spine of 1,288 nodes ending in a stored term reaches -the per-prefix path; it is a fixture in both languages, 7,105 bytes). The cargo feature -`ixon/sharing-profile` (off by default, compiled in CI) adds phase timers. - -**Limits.** Lean `Ix.Sharing.Exact.Limits` and Rust `ExactSharingLimits` hold the defaults -(`docs/Ixon.md`, "Canonical construction"). `Limits.withOverrides` (`parseLimitValue`) and -`ExactSharingLimits::with_overrides` (`parse_limit_value`) parse one grammar: values are ASCII +development (forced phase-1 width, a fourth phase-1 candidate, the MSS inspector, the corpus +tool's layout and width experiments) are removed. Renamed: `checkCanonicalSharing` → +`checkExactMinimum`, `CanonicalCheck` → `ExactMinimumCheck`. + +**Checks and metering.** Every one of the three candidates runs phases 1 to 3 under the same +limits and with the same checks; there is no mode that checks only the returned candidate. +Phase 3's checks (`rematerialize`) are the layout length against the evaluated length and +against phase 1, the re-expansion to the stored terms and the input's root IDs, and the wire +domain; the serialized length equals the layout length by theorem and is not re-measured. An +error at any width fails the call with the lowest failing width's error. Phase 3 evaluates +the whole table once when the order is closed under stored descendants (`onePassOrder`) and +per prefix otherwise; `materializeTableOnePass_eq` proves the two paths give the same result, +errors and work charges, so limits are metered against the specification. The App spine of +1,288 nodes ending in a stored term in `Tests/Ix/SharingTiered.lean` reaches the per-prefix +path. `Exact.TieredFast` computes the width-independent tables once per constant and runs the +three widths as parallel tasks on a DAG of at least 1,024 terms (`tieredCandidates_eq`). The +full account is `sharing-minimum.md` §12.18. + +**Limits.** `Ix.Sharing.Exact.Limits` holds the defaults (`docs/Ixon.md`, "Canonical +construction"). `Limits.withOverrides` (`parseLimitValue`) parses an override: values are ASCII digits, `2^k` or `max`; items and keys are trimmed of spaces, tabs, line feeds and carriage -returns only; keys of the other language are accepted and ignored. One list of 35 -specifications pins both (`LIMIT_SPEC_PARITY` in `crates/ixon/src/sharing_exact/tests.rs`, -`limitSpecParity` in `Tests/Ix/SharingExact.lean`). `ix compile --sharing-limits` and -`ix compile-lean --sharing-limits` validate the override and publish it through -`IX_SHARING_LIMITS`. The Lean compiler reads it through -`Ix.Cli.driverOptionsFromEnv`, carried in `CompileEnv.sharingLimits`; the Rust -compile and regeneration paths use `CompileState::sharing_limits`. Lean's -`decompileEnvFullParallel` reads stored originals and ignores its historical -sharing-limits parameter; it does not invoke the sharing construction. - -**Test oracles.** The width-state search (`Ix.Sharing.Exact.Search`, `search.rs`, the `exact` -corpus mode), the tiny exhaustive oracle (`Exact.Oracle`, `oracle.rs`), the subset-enumeration -reference of phase 1 (`uniformSubsetSearch`, `uniform_subset_search`) and the MSS reference -encoding (`mss.rs`, test-only) are labelled as test oracles; the compiler path never calls them. +returns only. A key that names no limit, such as one of the retired Rust implementation's, is +an error. The 35 specifications of `limitSpecParity` (`Tests/Ix/SharingExact.lean`) +pin the grammar. The certified compiler +(`Ix.Cert.compileReport`) always uses the default `Ix.CompileM.compilerSharingLimits`, +carried in `CompileEnv.sharingLimits`. Lean's `decompileEnvFullParallel` reads +stored originals and ignores its historical sharing-limits parameter; it does +not invoke the sharing construction. + +**Test oracles.** The width-state search (`Ix.Sharing.Exact.Search`), the tiny exhaustive +oracle (`Exact.Oracle`) and the subset-enumeration reference of phase 1 (`uniformSubsetSearch`) +are labelled as test oracles; the compiler path never calls them. The MSS reference encoding +(store every node of in-degree at least 2, `sharing-minimum.md` §12.2) survives as the +baseline of `Benchmarks/SharingStudy.lean`; its Rust test reference went with the port. **Tests.** @@ -463,23 +431,18 @@ encoding (`mss.rs`, test-only) are labelled as test oracles; the compiler path n idempotence, pointer-layout independence, the tiered path at scale (tables above 1,032 entries, the 1,288-node spine, the parallel widths) and under each production limit, and the limit grammar (`limitSpecParity`). -- `exact-sharing-ffi` (`Tests/Ix/SharingExactFFI.lean`): Lean and Rust bytes on fixtures, - generated inputs, Share-bearing constants with tables up to 66,600 entries (Shares in the 4-, - 5- and 9-byte rungs), the re-normalization of 119 table-carrying inputs, malformed tables, and - the compiler route. With `IX_SHARING_CORPUS=` it runs over - every constant of an `.ixe` (modes `tiered-tagN`, `uniform-w`, `exact`; - `IX_SHARING_CORPUS_MODES`, `IX_SHARING_CORPUS_SELECT`, `IX_SHARING_CORPUS_LIMIT`). +- `sharing-components` (`Tests/Ix/SharingComponents.lean`): the uncertain-component pass + (`ufFind`, `uncertainComponents`) against its loop references on generated inputs. - `ixon` (`tagNUnits`: expected encodings, rejection vectors, rung-end vectors, boundary round trips), with the same vectors in Rust `tag.rs`; `ixvm-tagn` for the circuit. -- `ixon-v4-tests` and `ixon-v4-primitives` (§6); the Rust `sharing_exact` tests, including - parallel against sequential byte identity. +- `ixon-v4-tests` and `ixon-v4-primitives` (§6). - Benchmarks and studies: `sharing-study` (`Benchmarks/SharingStudy.lean`: the rebuild check, round trip, unshared size and the MSS baseline), `lean-sharing-prof full|split|hash - [select-file]` (`Benchmarks/LeanSharingProf.lean`, the Lean construction's phase profile), - `uniform-hard` (`Benchmarks/UniformHard.lean`), and the Rust example `sharing_corpus` - (`crates/ixon/examples/sharing_corpus.rs`; `--full-check`, `--sharing-limits SPEC`, and - `--check-sequential`, which compares the parallel and sequential paths under the run's - limits). + [select-file]` (`Benchmarks/LeanSharingProf.lean`, the Lean construction's phase profile) + and `uniform-hard` (`Benchmarks/UniformHard.lean`). + +The Lean/Rust differential suite `exact-sharing-ffi`, the Rust port's own tests and its +corpus example were retired with the port (risk 1). ### Historical gates before the K16 cutover @@ -501,14 +464,9 @@ and codec contracts. K16 evidence is recorded in ix-ffi/parallel,ix-ffi/net,ix-ffi/test-ffi,ixon/sharing-profile -- -D warnings`; `cargo check` with the same features; `cargo test --release --workspace` (1,716 passed, 20 ignored); `cargo deny check`. -- Merge-queue partitions run locally: `tc-unit`; the compile-pipeline partition - (`--ignored rust-canon-roundtrip serial-canon-roundtrip parallel-canon-roundtrip graph-cross - condense-cross rust-serialize ixon-corpus rust-decompile validate-aux aux-gen-diff - decompile-diff`); and `--ignored compile` (Lean and Rust compile all 237,295 constants of the - test environment; constants, metadata and serialized environments are identical), run on the - tree just before the final cleanup and documentation commits. The other ignored partitions - (`--ignored decompile`, the misc partition with the `ixvm` runner, the Tc ignored suites) are - run by the merge queue. +- Merge-queue partitions run locally at that time (historical): `tc-unit`; the + historical `compile-pipeline` partition; and `--ignored compile` (all 237,295 constants of the + test environment), run on the tree just before the final cleanup and documentation commits. ## 9. Owner decisions on the questions of this map @@ -520,10 +478,12 @@ and codec contracts. K16 evidence is recorded in (`canonicalSharingTiered_format`), with the endpoints stated over it (§4). Applied; the compiler's sharing builder theorems remain over it, and the per-declaration endpoints are retired with the compiler-correctness proofs. -4. Compiler limits: a safety net with a CLI override (§7 risk 2). Applied. +4. Compiler limits: a safety net with a CLI override (§7 risk 2). Applied; the override + later went with the uncertified compile routes, and the certified compiler fixes the + defaults. 5. `metaSharing`: the extended index space of `sharing-minimum.md` §13 (§5.3). Readers applied; no construction. 6. IxVM: updated in this PR (§3.1). Applied. 7. Header aliasing: kept; the env header is TagN(4, `0xE`, version), `0xE4` for v4 (finding 10). -8. Cost-model repricing: TagN widths in both languages (finding 3, §3.4). Applied. +8. Cost-model repricing: TagN widths (finding 3, §3.4). Applied. diff --git a/docs/sharing-minimum-measurements-mathlib.md b/docs/sharing-minimum-measurements-mathlib.md index 1c9623888..0a7a6331e 100644 --- a/docs/sharing-minimum-measurements-mathlib.md +++ b/docs/sharing-minimum-measurements-mathlib.md @@ -1,5 +1,8 @@ # Sharing corpus measurements on Mathlib +> **Historical record.** The corpus here was written by the Rust compiler, retired in +> PR #677 ([retired Rust](retired-rust.md)); the measurements are kept as they were taken. + This document reruns the corpus harness of [`sharing-minimum.md`](sharing-minimum.md), `Benchmarks/SharingStudy.lean` (`lake exe sharing-study`), on a Mathlib corpus instead of `Init`. The harness is a development version from before the heuristic was removed. The Init numbers quoted for comparison come from diff --git a/docs/sharing-minimum-measurements.md b/docs/sharing-minimum-measurements.md index a6471a6df..48aece35a 100644 --- a/docs/sharing-minimum-measurements.md +++ b/docs/sharing-minimum-measurements.md @@ -1,5 +1,9 @@ # Sharing corpus measurements +> **Historical record.** The corpus here was written by the Rust compiler, and some runs +> compare against the Rust port of the sharing construction. Both were retired in PR #677 +> ([retired Rust](retired-rust.md)); the measurements are kept as they were taken. + Corpus measurements for [`sharing-minimum.md`](sharing-minimum.md), taken on the `Init` corpus with the harness `Benchmarks/SharingStudy.lean` (`lake exe sharing-study`). These numbers inform the global exact search design. This document reports them and does not diff --git a/docs/sharing-minimum-performance.md b/docs/sharing-minimum-performance.md index 921ea21a8..563dadd13 100644 --- a/docs/sharing-minimum-performance.md +++ b/docs/sharing-minimum-performance.md @@ -1,5 +1,11 @@ # Exact sharing: production performance +> **Historical record.** The Rust paths this log measures (the Rust port of the sharing +> construction, its corpus example and FFI parity hooks, and the Rust compiler) were retired +> in PR #677. Their commands no longer run, and the Lean/Rust differential no longer exists. +> The numbers are kept as they were measured; [retired Rust](retired-rust.md) says what those +> implementations were and links their code at the last commit that had them. + This document covers the "Production viability measurements" deliverable of [`sharing-minimum.md`](sharing-minimum.md). It gathers in one place, for a PR reviewer, what has been measured on two corpora: diff --git a/docs/sharing-minimum.md b/docs/sharing-minimum.md index 158ec11c2..b16cec795 100644 --- a/docs/sharing-minimum.md +++ b/docs/sharing-minimum.md @@ -9,16 +9,19 @@ > scale (§12.1) and is **superseded** by the decisions of §12 wherever the two differ. Read §12 > together with §14. The construction and its retained components are: > -> - **Construction.** `Ix.Sharing.Exact.canonicalSharingTiered .tagN` (Lean -> `Ix/Sharing/Exact.lean`, Rust `crates/ixon/src/sharing_exact/tiered.rs`). Phase 1 is +> - **Construction.** `Ix.Sharing.Exact.canonicalSharingTiered .tagN` +> (`Ix/Sharing/Exact.lean` over `Ix/Sharing/Exact/Tiered.lean`). Phase 1 is > the minimum over a fixed component family at each width `w ∈ {1, 2, 3}` (§14). > Initial uncertain components of at most 16 members use every subset; larger components > use the deterministic greedy chain. The old exact optimum remains a reference theorem. > Phase 2 allocates an exact first tier of eight 1-byte slots, then orders the rest by Kahn > priority (§12.14, §12.15). Phase 3 re-materializes every entry and root at the real TagN > widths. The candidate with the fewest real bytes wins, ties going to the lower width. -> - **Single route.** Both compilers build every table with it. The heuristic +> - **Single route.** The compiler builds every table with it. The heuristic > (`Ix/Sharing.lean`, `crates/ixon/src/sharing.rs`) and its proofs are removed. +> - **One implementation.** The Lean construction is the only one. Its Rust port and the +> Rust compiler that called it were retired in PR #677. The Lean/Rust parity and Rust +> timing records below are history ([retired Rust](retired-rust.md)). > - **Format.** TagN replaces Tag0, Tag2 and Tag4 (§12.12, §12.16). Checked objects > retain format 4 and the identifiers `ixon-v4` and `ixon-v4/resource-v1`. > The `.ixe` envelope is v4 (`0xE4`); v5 (`0xE5`) when it carries a role @@ -32,13 +35,15 @@ > length with the serialized length, and wire validity remain machine-checked on the reference > route (§12.13). The compiled fast implementations are > tied to the specifications by audited `@[csimp]` equalities (§12.18). -> - **Limits.** Resource limits are a safety net with a CLI override (`ix compile -> --sharing-limits`). Exceeding one is a compile error. The component-size gate chooses the +> - **Limits.** Resource limits are a safety net. The certified compiler runs at the +> defaults, which its theorem fixes; tests, benchmarks and study tools can override them +> (`Limits.withOverrides`). The `ix compile --sharing-limits` flag went with the uncertified +> compile routes. Exceeding a limit is a compile error. The component-size gate chooses the > fixed greedy family before search; exhausting a budget never changes that family. > - **Compile time.** §12.18 records the earlier exact implementation. §14.4 records the > family measurements and whole-Mathlib byte parity after the proved evaluator and > dependency-planning repairs. Remaining release gates are listed there. -> - **Metadata.** The decompilers implement the extended index space of §13.1. The +> - **Metadata.** The decompiler implements the extended index space of §13.1. The > construction of §13.2 is not implemented. > - **Oracles.** The exact search of §6 is kept as a test oracle for small inputs (§12.1). > @@ -48,7 +53,10 @@ Design and implementation specification (revision 2). Target: an implementation and reviewable PR in the **Ix repository**, covering its Lean and Rust paths. §0–§11 are the initial specification, apart -from local paths; §12 and §13 record the decisions taken while implementing it. Revision 1 +from local paths; §12 and §13 record the decisions taken while implementing it. The plan's +Rust half (a Rust port of the construction, the Rust compiler's sharing routes and the +Lean/Rust parity tests) was built and later retired, so its Rust instructions are history. +Revision 1 and its research oracles are in a separate research directory, not part of this repository. Revision 2 adds §0 (workspace), §4.1 (proved reductions and bounds), §6.4 (sparse states), the corpus-measurement gate, and §11 (division of the work). @@ -58,9 +66,9 @@ the corpus-measurement gate, and §11 (division of the work). - The work is based on Ix `864130ec`, the revision the research inspected. - The toolchain comes from the flake: run every `lake`/`cargo` command as `nix develop --command bash -c ''` from the repository root. -- Tests: `lake test -- exact-sharing exact-sharing-ffi` runs the construction's suites (the +- Tests: `lake test -- exact-sharing sharing-components` runs the construction's suites (the plan was written against the heuristic's `sharing` suite, since removed); `lake test -- - ixon` the codec suite; `cargo test -p ixon` the Rust crate; `lake build --wfail IxSharingVerify` + ixon` the codec suite; `cargo test -p ixon` the Rust codec; `lake build --wfail IxSharingVerify` the proofs (`IxSharingVerify`; the TagN and codec proofs are in `IxC/Ixon/Verify` and build with `lake -d IxC build --wfail`). - Corpus: `Init`, compiled by the production compiler with @@ -119,7 +127,7 @@ functions in this table were removed by this work): | Lean codec and contracts | `Ix/Ixon.lean`: `putTag0`, `putTag4`, `putExpr`, `putConstant`, `Env.VERSION`; `Ix/IxonContract.lean`, `Ix/IxonMode.lean` | | Rust sharing/codec | `crates/ixon/src/sharing.rs`, `serialize.rs`, `expr.rs` | | Lean compiler integration | `Ix/CompileM.lean`: `constantInfoRootExprs`, `mutConstRootExprs`, `buildConstantWithSharing` | -| Rust compiler integration | `crates/compile/src/compile.rs`: `apply_sharing_with_stats`, `apply_sharing_to_*`; `kernel_egress.rs`; `decompile.rs` | +| Rust compiler integration | the Rust compiler, since retired: `apply_sharing_with_stats`, `apply_sharing_to_*`; `kernel_egress.rs`; `decompile.rs` | | Cross-language boundary | `crates/ffi/src/lean_ixon/sharing.rs`, existing sharing comparison FFI | | Metadata and ingress | `Ix/DecompileM.lean`, `Ix/Tc/Ingress.lean`, `Ix/Tc/IngressMeta.lean`, Rust `compile/decompile.rs` and kernel ingress consumers | | Proofs | `Ix/Compile/Verify/Sharing.lean`, `CompileSharingCodec.lean`, downstream compiler codec theorems and audit manifests | @@ -731,7 +739,7 @@ The work splits into four parts with disjoint files. Their interfaces are the sp | Lean exact core | `Ix/Sharing/Exact.lean` (new; split as needed), `Tests/Ix/SharingExact.lean`, `Tests/Main.lean` suite entry | Structural IDs, bounded share expansion, exact serializer-length helpers checked against `putExpr`, tiny all-order/all-occurrence oracle, fixed-dictionary telescope DP with materialization, sparse width-state DP with LB pruning and resource errors; exact-search tests including every §2 fixture | | Rust exact core | `crates/ixon/src/sharing_exact.rs` (new), its tests, `crates/ffi` differential hook | Same algorithm and key, checked arithmetic, differential test against Lean bytes through the FFI parity harness | | Corpus measurement | `Benchmarks/SharingStudy.lean`, `lakefile.lean` exe entry, `docs/sharing-minimum-measurements.md` | Candidate-count numbers on `init.ixe`; once the Lean core lands, rerun with the exact optimizer under explicit limits and report success rate, byte savings vs heuristic, states/transitions and wall time | -| Integration (after review of the other three) | `Ix/CompileM.lean`, `crates/compile`, metadata remap, version bump, proofs, docs | Compiler integration, migration and validation | +| Integration (after review of the other three) | `Ix/CompileM.lean`, the Rust compiler, metadata remap, version bump, proofs, docs | Compiler integration, migration and validation | Integration starts after the corpus-measurement gate has been evaluated and the exact cores agree on all fixtures. @@ -739,6 +747,8 @@ fixtures. ## 12. Decision record after the corpus-measurement gate Measurements are in `sharing-minimum-measurements.md` (Init corpus, 55,386 rooted constants). +Where a record below says "Rust", it means the Rust port of the construction or the Rust +compiler, both retired in PR #677; their numbers are kept as history. ### 12.1 The width-state DP is not a production algorithm @@ -961,8 +971,9 @@ Run-time checks that remain in Lean's phase 3 (`rematerialize`), each failing cl internal error, in this order: the layout length equals the evaluated length; it is at most phase 1's layout length (proved never to fail, `phase3_le_phase1`); the output re-expands, through the same interner, to the stored terms and the input's root IDs; and every output -expression is in the wire domain. Rust serializes the returned candidate and compares its -length (every candidate in its checked mode, §12.18). +expression is in the wire domain. They run for each of the three candidates (§12.18). The serialized length is +not re-measured at run time: it equals the layout length by theorem +(`canonicalSharingTiered_serialized`, above). The source-preservation proof uses exact pointer-selected cache hits (`ExprCache.lean`: the interner selects cache candidates by the `unsafe` pointer address `exprPtr` and reuses a @@ -980,8 +991,7 @@ is proved only under the hypothesis that the output expands to the input's DAG enumeration path (`uniformSubsetSearch = true`); structural preservation covers every successful `canonicalSharingTiered` run. Failure semantics: an error at any width fails the whole call, with the lowest failing width's -error (no fallback to other widths). Rust matches on every limit and length check; its default -mode runs the checks of built expressions only for the returned candidate (§12.18). +error (no fallback to other widths). ### 12.14 Best-of-four rejected; outlier diagnosis @@ -1043,50 +1053,55 @@ this PR (general pipeline work is deferred). Measured: 1.13–1.26× in the fina dropping it would cost 35,783 bytes on Mathlib (1,395 constants) and 6,891 bytes on Init (184 constants) and is held in reserve. -**Rust** (byte-identical on all of Init and Mathlib). Phase 3 evaluates the whole table once when -the order is closed under stored descendants (every order of Init and Mathlib is) and falls back -to one evaluation per prefix otherwise; width-independent tables are shared by the three widths; -the table-count knapsack works on ranks; phase 1 is a dry run that builds no expressions. Checks -(module documentation of `crates/ixon/src/sharing_exact/tiered.rs`, "Checks"): in the default -mode, which the compiler uses, every limit and every phase-1 to phase-3 length check applies to -all three candidates and an error at any width fails the call with the lowest failing width's -error, while the checks on built expressions (one-pass decisions and their `work`, the build, -the layout price, re-expansion and node count, the serialized length) run only for the returned -candidate. The checked mode (`ExactSharingLimits::full_check`: unit tests, the FFI parity hooks, -`sharing_corpus --full-check`) builds and verifies all three; over all of Init (56,622 -constants) and all of Mathlib (679,499) it has no failure and gives the same output as the -default mode. `work` charges the work done: one evaluation of the whole table plus the -hidden-top evaluations in one pass, one evaluation per prefix otherwise; the one-pass decisions -are charged to the returned candidate. The per-prefix path is reachable: an App spine of 1,288 -nodes ending in a stored term (7,105 bytes, the same as Lean's) is a fixture in both languages. - -**Lean.** Lean is the proved reference. Fast twins (`Exact.Phase3`, `Exact.Reexpand`, -`Exact.UniformSearchLocal`, `Exact.TierFast`, `Exact.PinnedDeps`, `Exact.PinnedFast`, -`Exact.KnapsackFast`, `Exact.TieredFast`; the module map of `Ix/Sharing/Exact.lean`) are attached -to the specifications by unconditional `@[csimp]` equalities, which are audit roots; -`IxSharingVerify/Audit/CompiledCode.lean` fails the build if a csimp theorem on the compiler's -import path is not a root, and the sorry frontier covers `Ix.Sharing`. Phase 3 also runs in one -pass (`materializeTableOnePass`, equal to `materializeTable` on every input, -`materializeTableOnePass_eq`, with the same `materializeWork`), and the three widths run as -parallel tasks above 1,024 DAG terms (`tieredCandidates_eq`). The merge-queue partition -(`lake test -- --ignored compile`, Lean and Rust compile every constant of the test environment) -takes 11:51 against 15:30 before the route switch; its Lean step takes 238 s against 177 s. -The construction is still 8–15× slower than Rust single-threaded (about 8–12× on Lean 4.34.0, -re-measured on Init samples of the same sizes: 7.9×, 10.5× and 12.1×), short of the 2–4× target: the -specification's cost model evaluates a component's whole closure where Rust evaluates its area, -so closing the gap needs a change of the specification and its proofs (follow-up). - -**Review fixes.** Rust no longer applies its `candidates` limit (2^16, meant for the width-state -oracle) to the canonical path; in both languages the candidate count is recorded, not limited -(Rust regression test: 70,000 distinct references used twice). `knapsack_cells` (2^28) is about -27 times the largest knapsack on Init and Mathlib. The limits override is parsed by one grammar -in both languages (digits only; 35 shared specifications) and read once per run. Rust's phase -1 computes in 128-bit integers and, where a value leaves that range, again in exact integers -(`uniform/arith.rs`; the result is the same wherever the 128-bit run succeeds), so it agrees with -Lean's `Nat` on inputs such as the chain `e(i+1) = App(e(i), e(i))` at depth 127 or more, whose -unshared length exceeds `2^127` (the Lean/Rust sharing suite compares four such towers byte for -byte). The layout length equals the serialized length by theorem (§12.13), and the metadata -table check is linear in both languages. +**Fast implementations.** The specifications are the proved reference. Fast twins +(`Exact.Phase3`, `Exact.Reexpand`, `Exact.UniformSearchLocal`, `Exact.TierFast`, +`Exact.PinnedDeps`, `Exact.PinnedFast`, `Exact.KnapsackFast`, `Exact.TieredFast`; the module map +of `Ix/Sharing/Exact.lean`) are attached to the specifications by unconditional `@[csimp]` +equalities, which are audit roots; `IxSharingVerify/Audit/CompiledCode.lean` fails the build if +a csimp theorem on the compiler's import path is not a root, and the sorry frontier covers +`Ix.Sharing`. What the compiled code does differently: + +- `Exact.TieredFast` computes the width-independent tables (`Prep.ofDag`, the graph facts, the + DAG checks, the candidate count) once per constant instead of once per width and phase, and + on a DAG of at least 1,024 terms (`tieredParMin`) runs the three widths as parallel tasks, + combined in width order (`tieredCandidates_eq`: the same value and the same error). +- The table-count knapsack works on ranks (`Exact.KnapsackFast`). +- Phase 3 evaluates the whole table once when the order is closed under stored descendants + (`onePassOrder`; every order of Init and Mathlib is), and once per prefix otherwise + (`materializeTableOnePass` falls back to `materializeTable`). The per-prefix path is + reachable: in `Tests/Ix/SharingTiered.lean` an App spine of 1,288 nodes ending in a stored + term sends one candidate down it, and a spine of 1,287 nodes sends none. + +**Checks and metering.** Every candidate, not only the returned one, runs all three phases +under the same limits and with the same checks. In phase 3 (`rematerialize`) these are the +layout length against the evaluated length and against phase 1, the re-expansion to the stored +terms and the input's root IDs, and the wire domain (§12.13). An error at any width fails the +call with the error of the lowest failing width. Metering follows the specification, not the +path the compiled code takes: `materializeTableOnePass` equals `materializeTable` on every +input, errors and `materializeWork` charges included (`materializeTableOnePass_eq`), so a +limit has the same outcome on either path. `materialize_work` (2^56) budgets the per-prefix +count, about `2·k·N` for `k` entries over `N` terms. The serialized length is not measured at +run time: it equals the layout length by theorem (§12.13). + +**Speed at the time.** The merge-queue partition `lake test -- --ignored compile`, which then +compiled every constant of the test environment with the Lean and the Rust compiler, took 11:51 +against 15:30 before the route switch; its Lean step took 238 s against 177 s. The Lean +construction was 8–15× slower than the Rust port single-threaded (about 8–12× on Lean 4.34.0, +re-measured on Init samples of the same sizes: 7.9×, 10.5× and 12.1×), short of the 2–4× +target, because the specification's cost model evaluates a component's whole closure where the +port evaluated its area. The area search (`Exact.AreaSearch`) and, for the K16 family, the +guarded area evaluator (§14.3) later brought area evaluation to Lean as proved twins. + +**Review fixes.** The candidate count is recorded, not limited, on the canonical path: the +`candidates` limit (2^16) belonged to the width-state oracle, and the Rust port had applied it +to the canonical path until a regression test of 70,000 distinct references used twice caught +it. `knapsack_cells` (2^28) is about 27 times the largest knapsack on Init and Mathlib. A limits +override has one grammar (`Limits.withOverrides`: digits, `2^k` or `max`; the 35 specifications +of `limitSpecParity`, `Tests/Ix/SharingExact.lean`). Lean computes lengths in `Nat`, so a long +unshared length is never a refusal; the chain `e(i+1) = App(e(i), e(i))` at depth 127 or more, +whose unshared length exceeds `2^127`, is where the port needed exact integers past 128 bits. +The layout length equals the serialized length by theorem (§12.13), and the metadata table +check is linear. ## 13. Metadata sharing: the extended index space @@ -1118,21 +1133,22 @@ entries before it (`p ≤ i < p + j`); a reader expands `metaSharing` in order, `sharing ++ metaSharing[0..j)`. No compiler writes metadata expressions now (§8.2); the readers keep the rule. -Readers. Only the two decompilers read `metaSharing` contents, and both implement the rule: +Readers. Only the decompiler reads `metaSharing` contents, and it implements the rule: Lean `Ix.DecompileM` (`ShareScope`, `resolveShareIn`; also the semantic-contract scan -`Ix.SemanticContract.containsIxon`) and Rust `ix_compile::decompile` (`ShareScope`; also -`semantic_contract::contains_ixon`). A primary `Share(i)` with `i ≥ p` is -`invalidShareIndex`; a metadata `Share(i)` outside `[0, p + j)` is `invalidMetaShareIndex` -(Rust `InvalidMetaShareIndex`, error tag 11 in both languages), with `i ≥ p + q` out of range +`Ix.SemanticContract.containsIxon`). A primary `Share(i)` with `i ≥ p` is +`invalidShareIndex`; a metadata `Share(i)` outside `[0, p + j)` is `invalidMetaShareIndex`, +with `i ≥ p + q` out of range and `p + j ≤ i < p + q` a forward or self reference. When a constant's metadata is loaded, the -whole table is checked first (`validateMetaSharing`, Rust `validate_meta_sharing`), reporting +whole table is checked first (`validateMetaSharing`), reporting the first violation in entry order and then left-to-right pre-order. Kernel ingress -(`Ix/Tc/IngressMeta.lean`, Rust `crates/kernel/src/ingress.rs`), the IxVM circuit and the +(IxC's reader), the IxVM circuit and the resource validators do not read `metaSharing` contents. Two gaps of the readers predate this rule and are not addressed by it: a record nested inside a metadata expression may reference -any `metaSharing` entry, including its own, and neither the decompilers nor kernel ingress check -that primary table entries reference only earlier entries. A malformed table of either kind can -therefore make them loop instead of failing; hardening the readers is follow-up work. +any `metaSharing` entry, including its own, and the decompiler does not check that primary +table entries reference only earlier entries. A malformed table of either kind can therefore +make it loop instead of failing; hardening it is follow-up work. IxC's reader builds each +primary entry against the entries before it, so a self or forward reference it uses is an +error. ### 13.2 Canonical construction (specified, not implemented) @@ -1157,7 +1173,7 @@ into the canonical DAG of the constant): Obligations when implemented: the primary table, primary roots and primary bytes are identical with and without metadata (a theorem, by construction: the primary result is an input); -expansion round trip of every metadata root; idempotence; Lean/Rust byte parity; fixtures. +expansion round trip of every metadata root; idempotence; fixtures. Like §8.2, this does not make the combined primary-plus-metadata artifact a global minimum. ## 14. Canonical rule revision `sharing-k16-greedy-v1` @@ -1226,8 +1242,7 @@ The Lean cutover attaches these statements and the proved fast twins to the prod entry and its audits, preserving compiler-facing meaning, format and codec theorems without new premises. Its audit has 263 theorem roots and checks all 30 compiler-path `csimp` equalities; the accepted unsafe set is unchanged. The old exact-optimum theorems remain -on the reference path. Rust is a -separately tested implementation; byte parity is not a universal refinement proof. +on the reference path. The guarded area evaluator in `Ix/Sharing/FamilyNormalizeArea.lean` is proved equal to the closure evaluator for complete results, mathematical work charges and errors. @@ -1321,46 +1336,38 @@ first-tier search sets the degree (`B⁹` states, constant work each), and the u is at most `O(N³)` apart from the one extra factor on the charged component work. The bound is polynomial, not near-linear, and measured running times stay within it while -growing faster than linearly on some shapes. These shapes also reach the production -sharing code: - -- **A long `Lam` telescope.** Rust's telescope scan (`dict.rs`, `scan_telescope`) walks the - whole spine below each telescope node, in phase-1 materialization and phase-3 costs: - about 1 s at 10,000 binders, 4 s at 20,000 and 105 s at 100,000. The work is charged. - Lean's evaluator follows links to the next available spine node and is linear on this - shape when no spine node is stored. Porting those links to Rust is not a cheap, - byte-neutral change; it is follow-up work. -- **A long argument chain.** Rust collected each term's distinct parents with a linear - search, quadratic in a chain's parents: 1.27 s for 200,000 arguments and 7.19 s for - 400,000. It now compares only with the last parent recorded, since parents arrive in - increasing order (0.29 s and 0.60 s). On the Init+Std corpus the outputs are identical - and the time unchanged (median wall 52.4 s before, 52.3 s after, seven runs each). -- **Many repeated references.** A probe with many repeated references grows - quadratically in Rust (80 ms at 1,000 repetitions, 357 ms at 2,000). The cause is not - yet isolated; the suspect is the representative lists of the uncertain-component pass, - which grow the same way in Lean (above). -- **The exact continuation.** Rust's exact-integer continuation past 128 bits is slower - than the 128-bit path. It runs only where a value leaves 128 bits. - -Rust has exact wide phase-1 arithmetic, word-sized term IDs and iterative expression -teardown. +growing faster than linearly on some shapes. These shapes were timed on the retired Rust +port, which ran the same rule; what is known of Lean on each is stated with it: + +- **A long `Lam` telescope.** The port's telescope scan walked the whole spine below each + telescope node, in phase-1 materialization and phase-3 costs: about 1 s at 10,000 binders, + 4 s at 20,000 and 105 s at 100,000. Lean's evaluator follows links to the next available + spine node and is linear on this shape when no spine node is stored; its base evaluations + and expression builders still scan up to a spine length per term (above). The work is + charged. +- **A long argument chain.** The port collected each term's distinct parents with a linear + search, quadratic in a chain's parents (1.27 s for 200,000 arguments, 7.19 s for 400,000), + until it compared only with the last parent recorded, since parents arrive in increasing + order (0.29 s and 0.60 s). Lean has not been timed on this shape. +- **Many repeated references.** A probe with many repeated references grew quadratically in + the port (80 ms at 1,000 repetitions, 357 ms at 2,000). The cause was not isolated; the + suspect is the representative lists of the uncertain-component pass, which grow the same + way in Lean (above). Of the internal guards the proofs make unreachable, the family route no longer runs three: the component partition check (`componentsChecked`, quadratic in the uncertain terms), the negative-model check, and the check that the retained best row sits at its own count. Each removal is byte-neutral and does no metered work, so refusals under every budget are unchanged; the proofs take the partition from `stage_componentsChecked` and the model's sign -from `selected_model_nonnegative`. Rust has no counterpart of the first and third, and -converts the model to a `u64`, a format bound rather than an internal check. The other -guards stay: +from `selected_model_nonnegative`. The other guards stay: - the DAG checks (`uniformDagChecks`), which the `csimp` equalities of the compiled family route need on an arbitrary `Expanded` input; - the wire-domain check of the re-materialized expressions, a genuine domain check: the format, serialized-length and output-expansion theorems rely on it, and its passing proof needs the input domain (fewer than `2^64` terms and universes per reference); -- the two re-expansion checks, which charge expression visits (Rust charges the same - counts) and which the meaning theorem consumes; +- the two re-expansion checks, which charge expression visits and which the meaning + theorem consumes; - the first-tier fuel, the row retention's option checks and the materializer's internal checks, which are structural; - the uniform finisher's range, class and model-length checks, the allocation's @@ -1369,13 +1376,15 @@ guards stay: removing them needs family-only copies of the finisher and re-materializer, or a rework of the reference route's proofs. -Rust keeps its own self-checks: no proof covers the Rust implementation, and they fire -only on a Rust defect. The compiler theorem (`Ix.Cert.CompileCorrect`, proved by +The compiler theorem (`Ix.Cert.CompileCorrect`, proved by `Ix.Cert.ReaderStreamsTrace.compileCorrect`) is stated for compiles that report no failure, so a guard that fires puts the closure outside it rather than making it false. ### 14.4 Evidence and release gates +This section records the cutover. Its Rust timings and Lean/Rust parity checks were taken +while the Rust port and the Rust compiler existed; both were retired in PR #677. + With references held fixed, K16 normalization of the saved corpora changed 16 of 100,327 Init+Std constants (net +117 payload bytes) and 112 of 689,469 Mathlib constants (net +223). Every observed difference involved a fallback component; no wholly exact constant changed. @@ -1409,10 +1418,11 @@ size for initial classification. That predicate now uses the proved sufficient c Rust outputs identical to Lean, including the four former refusals at depths 127, 128, 130 and 256. The repaired capture is byte-identical in 25.61 s; all 100,327 Init+Std output signatures and recorded candidate meters match the preceding K16 run. This targeted repair -does not eliminate the remaining general arithmetic or index bounds. +was to the port's arithmetic; Lean computes these sizes in `Nat`. Release requires focused mixed-family oracle tests, strict builds and audits, exact-gate snapshot compatibility, direct corpus normalization with the production binaries, whole -Init+Std/Mathlib compiles with Lean/Rust parity and attributed address changes, the formerly -stalled whole-environment compile, and all affected CI tiers. Prototype measurements do not -stand in for these production gates. +Init+Std/Mathlib compiles with attributed address changes, the formerly stalled +whole-environment compile, and all affected CI tiers. Prototype measurements do not stand in +for these production gates. The cutover's gates also required Lean/Rust parity on those +compiles; that gate went with the Rust port. diff --git a/flake.nix b/flake.nix index 2407dc563..6a0fbfeb8 100644 --- a/flake.nix +++ b/flake.nix @@ -97,7 +97,7 @@ ./Tests/Fixtures/ixon-v4 ./Tests/Fixtures/host-admission-v1 ./Tests/Fixtures/role-binding-v2 - ./crates/kernel/tests/fixtures + ./crates/ixon/tests/fixtures ]; }; # Rust code generation for the sandboxed builds. `.cargo/config.toml` @@ -151,27 +151,10 @@ pkgs.libiconv ]; }; - # Sources for the workspace checks, including the gnark test data - # the Rust tests embed. - testArgs = craneArgs // { - src = pkgs.lib.fileset.toSource { - root = ./.; - fileset = pkgs.lib.fileset.unions [ - cargoSource - compressionData - ./Tests/Fixtures/ixon-v4 - ./Tests/Fixtures/host-admission-v1 - ./Tests/Fixtures/role-binding-v2 - ./crates/kernel/tests/fixtures - plonkSources - ./crates/proof-compression/cuda - ]; - }; - }; # Build dependencies once with every host feature enabled so the # `net` stack (tokio/iroh) is compiled and cached here, then shared - # by the package builds and clippy. CUDA remains opt-in and is - # compiled separately in CI with the CUDA toolkit available. + # by the package builds. CUDA remains opt-in and is compiled + # separately in CI with the CUDA toolkit available. cargoArtifacts = craneLib.buildDepsOnly ( craneArgs // { @@ -180,8 +163,8 @@ ); # Test build: parallel + test-ffi (only used by ixTest). - # doCheck = false: the `nextest` check is the single place cargo - # tests run, so package builds only compile. + # doCheck = false: package builds only compile. Clippy and the + # cargo tests run in CI Jobs' rust-test job, not in the flake. rustPkgTest = craneLib.buildPackage ( craneArgs // { @@ -370,25 +353,10 @@ }; }; + # The Nix packaging check. Clippy and the cargo tests are CI Jobs' + # rust-test job (`.github/workflows/ci.yml`), with the same + # features and profile; the flake does not repeat them. checks = { - # Lint the host workspace; warnings are errors. - clippy = craneLib.cargoClippy ( - testArgs - // { - inherit cargoArtifacts; - cargoExtraArgs = "--locked --workspace --features ix-ffi/parallel,ix-ffi/test-ffi,ix-ffi/net"; - cargoClippyExtraArgs = "--all-targets -- -D warnings"; - } - ); - # Non-ignored Rust unit tests across the host workspace. - nextest = craneLib.cargoNextest ( - testArgs - // { - inherit cargoArtifacts; - cargoExtraArgs = "--locked --workspace"; - cargoNextestExtraArgs = "--profile ci"; - } - ); # Lean test suite. The suite reads fixtures and writes scratch # files by paths relative to the working dir, so run it from a # writable copy of the source tree, as if from a checkout. diff --git a/lakefile.lean b/lakefile.lean index 6ffa69057..058bf29fd 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -186,8 +186,8 @@ def profileLeancArgs : Array String := /-- The main library, with Lake's default root: the module `Ix` (`Ix.lean`) and its imports, not every module under `Ix/`. A module that `Ix.lean` does not -import, such as `Ix.Cert.Audit`, is built only by a target that imports it, -such as `IxTests`. Nothing the `ix-kernel` dependency or `IxSharingVerify` owns +import, such as `Ix.Cert.Audit`, is built only when named, as the +`check-kernel` gate and `lake lint`'s module coverage name it. Nothing the `ix-kernel` dependency or `IxSharingVerify` owns is under the `Ix.` module root (their declaration namespaces are, their module names are not), so this library never shadows them. Keep new modules of theirs under their roots. -/ @@ -220,6 +220,13 @@ lean_exe IxTests where needs := #[`@/ix, `@/«kernel-check-ixe», `@/EntryCaseFixtures] moreLinkObjs := #[ix_rs_test] +/-- The FFI suite (`IxTests ffi`) on its own, linked like `IxTests` with the +test-only FFI code, for the merge queue's Valgrind job: its import closure is +the host library's, without the test binary's proofs. -/ +lean_exe «ffi-tests» where + root := `Tests.FFIMain + moreLinkObjs := #[ix_rs_test] + lean_exe «arena-exclude» where root := `Tests.Ix.Kernel.ArenaExclude supportInterpreter := true @@ -283,9 +290,6 @@ lean_exe «bench-ixvm» where root := `Benchmarks.IxVM supportInterpreter := true -lean_exe «bench-shardmap» where - root := `Benchmarks.ShardMap - lean_exe «bench-typecheck» where root := `Benchmarks.Typecheck supportInterpreter := true @@ -594,11 +598,6 @@ lean_exe «kernel-layering» where lean_exe «kernel-trust-surface» where root := `Tests.Ix.Kernel.TrustSurface -lean_exe «kernel-codec» where - root := `Tests.Ix.Kernel.CodecHost - moreLinkObjs := #[ix_rs_test] - needs := #[`@/ix, `@/«kernel-check-ixe»] - /-- Host-compiled Lean declarations through the certified entry `Ix.Kernel.Admission.checkBytes`, each with an exact expected verdict. -/ lean_exe «kernel-entry-cases» where @@ -606,17 +605,10 @@ lean_exe «kernel-entry-cases» where supportInterpreter := true moreLinkObjs := #[ix_rs] -/-- The kernel's universe-level comparison (`Level.leq`, `Level.isEquiv`, and -its Géran fallback `Level.Geran.leq`) against brute-force evaluation, on -random levels and on Ixon's canonical forms. -/ -lean_exe «kernel-level-comparison» where - root := `Tests.Ix.Kernel.LevelComparison - moreLinkObjs := #[ix_rs] - /-- The Ixon reader against a direct translation of Lean's constants over `Init` and `Std` (`Tests/Ix/Kernel/ReaderFidelity.lean`): the compiled environment (`kernel-reader-fidelity .lake/envs/initstd.ixe [limit]`), `Init` and `Std` -compiled in process (`--compile [limit]`), `check-kernel`'s run +compiled in process (`--compile [limit]`), bounded prefix check (`--check-kernel`) or the `lake test` fixture (`--fixture`). -/ lean_exe «kernel-reader-fidelity» where root := `Tests.Ix.Kernel.ReaderFidelityMain @@ -665,12 +657,10 @@ lean_exe «kernel-pin-gen» where moreLinkObjs := #[ix_rs] /-- Run the certified kernel gate: the standalone strict build with its audits, -the host-side tests, the codec differential against Rust, -optionally the set-theory model, the layering and trust-surface fences, the -level comparison, the certified entry's host-compiled cases, and the reader's -fidelity against Lean (the fixture closure and the first records of Init and -Std), the `.meta` and `.source` names against Lean's, and quotient Source on -Init and Std. -/ +the host-side tests, optionally the set-theory model, the certification +statements' freeze (`Ix.Cert.Audit`), the layering and +trust-surface fences, the certified entry's host-compiled +cases, and the `.meta` and `.source` names against Lean's. -/ script "check-kernel" (args) := do unless args.isEmpty || args == ["--with-model"] do IO.eprintln "usage: lake run check-kernel [--with-model]" @@ -681,25 +671,16 @@ script "check-kernel" (args) := do unless code == 0 do throw <| IO.userError s!"{cmd} {args} failed with exit code {code}" run "lake" #["-d", "IxC", "build", "--wfail"] - run "lake" #["build", "--wfail", "Ix.Ixon.Projection.Audit", "Ix.Ixon.BlockOrder.Audit", "Ix.Ixon.Authenticated.Audit", "Ix.Cert.RuntimeAudit", "Ix.Cert.ModuleAudit", "Tests.Ix.Kernel.BlockOrder", "Tests.Ix.Kernel.AddressPure", "Tests.Ix.Kernel.Projection", "Tests.Ix.Kernel.Reader", "Tests.Ix.Kernel.CertifiedEntry", "Tests.Ix.Kernel.Authentication", "Tests.Ix.Kernel.Contracts", "Tests.Ix.Kernel.ReaderRoundtrip", "Tests.Ix.Kernel.Axioms", "Ix.SpecDigest", "Tests.Ix.Kernel.Erasure", "Tests.Ix.Kernel.DisplayRead"] - run "lake" #["build", "--wfail", "kernel-codec", "ix", "kernel-check-ixe"] - let codec ← IO.Process.output { cmd := ".lake/build/bin/kernel-codec" } - IO.FS.writeFile ".lake/build/kernel-codec.log" (codec.stdout ++ codec.stderr) - IO.eprint codec.stderr - unless codec.exitCode == 0 do - IO.eprint codec.stdout - throw <| IO.userError "kernel-codec failed; see .lake/build/kernel-codec.log" - IO.println "Production Ixon codec and Rust differential checks passed." + run "lake" #["build", "--wfail", "Ix.Ixon.Projection.Audit", "Ix.Ixon.BlockOrder.Audit", "Ix.Ixon.Authenticated.Audit", "Ix.Cert.RuntimeAudit", "Ix.Cert.ModuleAudit", "Tests.Ix.Kernel.BlockOrder", "Tests.Ix.Kernel.AddressPure", "Tests.Ix.Kernel.Projection", "Tests.Ix.Kernel.Reader", "Tests.Ix.Kernel.CertifiedEntry", "Tests.Ix.Kernel.Authentication", "Tests.Ix.Kernel.Contracts", "Tests.Ix.Kernel.Axioms", "Ix.SpecDigest", "Tests.Ix.Kernel.Erasure"] if args == ["--with-model"] then run "lake" #["-d", "Models/SetTheory", "build", "--wfail"] + -- The certification statements' freeze, which no test binary imports. + run "lake" #["build", "--wfail", "Ix.Cert.Audit"] -- The kernel's fences, derived from con-leche's: import layering and the -- per-file escape allowlist, with the trust-surface lexer's self-test. run "lake" #["build", "--wfail", "kernel-layering", "kernel-trust-surface"] run ".lake/build/bin/kernel-layering" #[] run ".lake/build/bin/kernel-trust-surface" #[] - -- The level comparison against brute-force evaluation. - run "lake" #["build", "--wfail", "kernel-level-comparison"] - run ".lake/build/bin/kernel-level-comparison" #[] -- Host-compiled Lean declarations through the certified entry, each with -- an exact expected verdict (`Tests/Ix/Kernel/EntryCases.lean`). run "lake" #["build", "--wfail", "kernel-entry-cases"] @@ -709,40 +690,10 @@ script "check-kernel" (args) := do unless entry.exitCode == 0 do IO.eprint entry.stdout throw <| IO.userError "kernel-entry-cases failed; see .lake/build/kernel-entry-cases.jsonl" - -- The Ixon reader against a direct translation of the Lean constants it was - -- compiled from, and the kernel's projection output against the compiler's - -- records: the fixture closure (the `lake test` suite's check) and the - -- first records of Init and Std (`Tests/Ix/Kernel/ReaderFidelity.lean`). - run "lake" #["build", "--wfail", "kernel-reader-fidelity"] - if ← System.FilePath.pathExists ".lake/envs/initstd.ixe" then - IO.FS.removeFile ".lake/envs/initstd.ixe" - let mut fidelityLog := "" - for mode in #["--fixture", "--check-kernel"] do - let out ← IO.Process.output { cmd := ".lake/build/bin/kernel-reader-fidelity", args := #[mode] } - fidelityLog := fidelityLog ++ s!"== {mode}\n{out.stdout}{out.stderr}" - IO.FS.writeFile ".lake/build/kernel-reader-fidelity.log" fidelityLog - IO.eprint out.stderr - unless out.exitCode == 0 do - IO.eprint out.stdout - throw <| IO.userError s!"kernel-reader-fidelity {mode} failed; see .lake/build/kernel-reader-fidelity.log" - IO.println "Reader fidelity checks passed." -- The `.meta` and `.source` names against Lean's, on ill-typed declarations -- and the fixtures (`Tests/Ix/Kernel/MetaNames.lean`). run "lake" #["build", "--wfail", "kernel-meta-names"] run ".lake/build/bin/kernel-meta-names" #[] - -- Quotient Source on Init and Std, as `checkSourceCollapsedBundledAt` runs it: - -- the artifact's alias fibers, the committed bundle's evidence, the collapse - -- checks, and the harness's prepared stream against production's; it fails - -- when a collapse check fails or the streams differ. The artifact is - -- compiled afresh, never read from an earlier run. - run "lake" #["build", "--wfail", "kernel-source-ingress"] - let ingressIxe := ".lake/build/kernel-source-ingress-initstd.ixe" - for stale in #[ingressIxe, ingressIxe ++ ".diff", ingressIxe ++ ".rows"] do - if ← System.FilePath.pathExists stale then IO.FS.removeFile stale - run ".lake/build/bin/kernel-source-ingress" - #["--workers", "16", "--collapse", "--bundle-evidence", "--compare-prepared", - "--ixe", ingressIxe, "Init", "Std"] - IO.println "Meta names and quotient Source on Init and Std passed." IO.println "Certified kernel checks passed." return 0 diff --git a/scripts/RustRoleSpecs.lean b/scripts/RustRoleSpecs.lean index 29b785e1b..98865c0fc 100644 --- a/scripts/RustRoleSpecs.lean +++ b/scripts/RustRoleSpecs.lean @@ -2,13 +2,9 @@ import Ix.RoleBinding.RustSpecGen def main (args : List String) : IO UInt32 := do let source ← IO.ofExcept Ix.RoleBinding.RustSpecGen.render - let path : System.FilePath := "crates/kernel/src/role_admission/specs.rs" + let shapePath : System.FilePath := "Tests/Fixtures/role-binding-v2/spec-shape.tsv" match args with | ["--check"] => - unless (← path.pathExists) && (← IO.FS.readFile path) == source do - IO.eprintln s!"{path} is missing or stale; regenerate Rust role specifications" - return 1 - let shapePath : System.FilePath := "Tests/Fixtures/role-binding-v2/spec-shape.tsv" unless (← shapePath.pathExists) && (← IO.FS.readFile shapePath) == Ix.RoleBinding.RustSpecGen.shape do IO.eprintln s!"{shapePath} is missing or stale; regenerate the role shape fixture" @@ -16,8 +12,7 @@ def main (args : List String) : IO UInt32 := do | ["--out", output] => IO.FS.writeFile output source | ["--shape-out", output] => IO.FS.writeFile output Ix.RoleBinding.RustSpecGen.shape | [] => - IO.FS.writeFile path source - IO.FS.writeFile "Tests/Fixtures/role-binding-v2/spec-shape.tsv" Ix.RoleBinding.RustSpecGen.shape + IO.FS.writeFile shapePath Ix.RoleBinding.RustSpecGen.shape | _ => IO.eprintln "usage: RustRoleSpecs.lean [--check | --out PATH | --shape-out PATH]" return 1